| // |
| // Copyright 2002 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| #include "GLSLANG/ShaderLang.h" |
| #include "angle_gl.h" |
| #include "gtest/gtest.h" |
| |
| class ExpressionLimitTest : public testing::Test |
| { |
| protected: |
| static const int kMaxExpressionComplexity = 16; |
| static const int kMaxCallStackDepth = 16; |
| static const int kMaxFunctionParameters = 16; |
| static const char *kExpressionTooComplex; |
| static const char *kCallStackTooDeep; |
| static const char *kHasRecursion; |
| static const char *kTooManyParameters; |
| static const char *kTooComplexSwitch; |
| static const char *kGlobalVariableInit; |
| |
| virtual void SetUp() |
| { |
| memset(&resources, 0, sizeof(resources)); |
| |
| GenerateResources(&resources); |
| } |
| |
| // Set up the per compile resources |
| static void GenerateResources(ShBuiltInResources *res) |
| { |
| sh::InitBuiltInResources(res); |
| |
| res->MaxVertexAttribs = 8; |
| res->MaxVertexUniformVectors = 128; |
| res->MaxVaryingVectors = 8; |
| res->MaxVertexTextureImageUnits = 0; |
| res->MaxCombinedTextureImageUnits = 8; |
| res->MaxTextureImageUnits = 8; |
| res->MaxFragmentUniformVectors = 16; |
| res->MaxDrawBuffers = 1; |
| |
| res->OES_standard_derivatives = 0; |
| res->OES_EGL_image_external = 0; |
| |
| res->MaxExpressionComplexity = kMaxExpressionComplexity; |
| res->MaxCallStackDepth = kMaxCallStackDepth; |
| res->MaxFunctionParameters = kMaxFunctionParameters; |
| } |
| |
| static void GenerateLongExpression(int length, std::stringstream *ss) |
| { |
| for (int ii = 0; ii < length; ++ii) |
| { |
| *ss << "+ vec4(" << ii << ")"; |
| } |
| } |
| |
| static std::string GenerateShaderWithLongExpression(int length) |
| { |
| static const char *shaderStart = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| void main() |
| { |
| gl_FragColor = u_color |
| )"; |
| |
| std::stringstream ss; |
| ss << shaderStart; |
| GenerateLongExpression(length, &ss); |
| ss << "; }"; |
| |
| return ss.str(); |
| } |
| |
| static std::string GenerateShaderWithUnusedLongExpression(int length) |
| { |
| static const char *shaderStart = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| void main() |
| { |
| gl_FragColor = u_color; |
| } |
| vec4 someFunction() { |
| return u_color |
| )"; |
| |
| std::stringstream ss; |
| |
| ss << shaderStart; |
| GenerateLongExpression(length, &ss); |
| ss << "; }"; |
| |
| return ss.str(); |
| } |
| |
| static void GenerateDeepFunctionStack(int length, std::stringstream *ss) |
| { |
| static const char *shaderStart = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| vec4 function0() { |
| return u_color; |
| } |
| )"; |
| |
| *ss << shaderStart; |
| for (int ii = 0; ii < length; ++ii) |
| { |
| *ss << "vec4 function" << (ii + 1) << "() {\n" |
| << " return function" << ii << "();\n" |
| << "}\n"; |
| } |
| } |
| |
| static std::string GenerateShaderWithDeepFunctionStack(int length) |
| { |
| std::stringstream ss; |
| |
| GenerateDeepFunctionStack(length, &ss); |
| |
| ss << "void main() {\n" |
| << " gl_FragColor = function" << length << "();\n" |
| << "}"; |
| |
| return ss.str(); |
| } |
| |
| static std::string GenerateShaderWithUnusedDeepFunctionStack(int length) |
| { |
| std::stringstream ss; |
| |
| GenerateDeepFunctionStack(length, &ss); |
| |
| ss << "void main() {\n" |
| << " gl_FragColor = vec4(0,0,0,0);\n" |
| << "}"; |
| |
| return ss.str(); |
| } |
| |
| static std::string GenerateShaderWithFunctionParameters(int parameters) |
| { |
| std::stringstream ss; |
| |
| ss << "precision mediump float;\n" |
| << "\n" |
| << "float foo("; |
| for (int i = 0; i < parameters; ++i) |
| { |
| ss << "float f" << i; |
| if (i + 1 < parameters) |
| { |
| ss << ", "; |
| } |
| } |
| ss << ")\n" |
| << "{\n" |
| << " return f0;\n" |
| << "}\n" |
| << "\n" |
| << "void main()\n" |
| << "{\n" |
| << " gl_FragColor = vec4(0,0,0,0);\n" |
| << "}"; |
| |
| return ss.str(); |
| } |
| |
| static std::string GenerateShaderWithNestingInsideSwitch(int nesting) |
| { |
| std::stringstream shaderString; |
| shaderString << |
| R"(#version 300 es |
| uniform int u; |
| |
| void main() |
| { |
| int x; |
| switch (u) |
| { |
| case 0: |
| x = x)"; |
| for (int i = 0; i < nesting; ++i) |
| { |
| shaderString << " + x"; |
| } |
| shaderString << |
| R"(; |
| } // switch (u) |
| })"; |
| return shaderString.str(); |
| } |
| |
| static std::string GenerateShaderWithNestingInsideGlobalInitializer(int nesting) |
| { |
| std::stringstream shaderString; |
| shaderString << |
| R"(uniform int u; |
| int x = u)"; |
| |
| for (int i = 0; i < nesting; ++i) |
| { |
| shaderString << " + u"; |
| } |
| shaderString << R"(; |
| void main() |
| { |
| gl_FragColor = vec4(0.0); |
| })"; |
| return shaderString.str(); |
| } |
| |
| // Compiles a shader and if there's an error checks for a specific |
| // substring in the error log. This way we know the error is specific |
| // to the issue we are testing. |
| bool CheckShaderCompilation(ShHandle compiler, |
| const char *source, |
| ShCompileOptions compileOptions, |
| const char *expected_error) |
| { |
| bool success = sh::Compile(compiler, &source, 1, compileOptions) != 0; |
| if (success) |
| { |
| success = !expected_error; |
| } |
| else |
| { |
| std::string log = sh::GetInfoLog(compiler); |
| if (expected_error) |
| success = log.find(expected_error) != std::string::npos; |
| |
| EXPECT_TRUE(success) << log << "\n----shader----\n" << source; |
| } |
| return success; |
| } |
| |
| ShBuiltInResources resources; |
| }; |
| |
| const char *ExpressionLimitTest::kExpressionTooComplex = "Expression too complex"; |
| const char *ExpressionLimitTest::kCallStackTooDeep = "Call stack too deep"; |
| const char *ExpressionLimitTest::kHasRecursion = |
| "Recursive function call in the following call chain"; |
| const char *ExpressionLimitTest::kTooManyParameters = "Function has too many parameters"; |
| const char *ExpressionLimitTest::kTooComplexSwitch = |
| "too complex expressions inside a switch statement"; |
| const char *ExpressionLimitTest::kGlobalVariableInit = |
| "global variable initializers must be constant expressions"; |
| |
| TEST_F(ExpressionLimitTest, ExpressionComplexity) |
| { |
| ShShaderSpec spec = SH_WEBGL_SPEC; |
| ShShaderOutput output = SH_ESSL_OUTPUT; |
| ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources); |
| ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY; |
| |
| // Test expression under the limit passes. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity - 10).c_str(), |
| compileOptions, nullptr)); |
| // Test expression over the limit fails. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity + 10).c_str(), |
| compileOptions, kExpressionTooComplex)); |
| // Test expression over the limit without a limit does not fail. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity + 10).c_str(), |
| compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr)); |
| sh::Destruct(vertexCompiler); |
| } |
| |
| TEST_F(ExpressionLimitTest, UnusedExpressionComplexity) |
| { |
| ShShaderSpec spec = SH_WEBGL_SPEC; |
| ShShaderOutput output = SH_ESSL_OUTPUT; |
| ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources); |
| ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY; |
| |
| // Test expression under the limit passes. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, |
| GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity - 10).c_str(), |
| compileOptions, nullptr)); |
| // Test expression over the limit fails. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, |
| GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity + 10).c_str(), |
| compileOptions, kExpressionTooComplex)); |
| // Test expression over the limit without a limit does not fail. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, |
| GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity + 10).c_str(), |
| compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr)); |
| sh::Destruct(vertexCompiler); |
| } |
| |
| TEST_F(ExpressionLimitTest, CallStackDepth) |
| { |
| ShShaderSpec spec = SH_WEBGL_SPEC; |
| ShShaderOutput output = SH_ESSL_OUTPUT; |
| ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources); |
| ShCompileOptions compileOptions = SH_LIMIT_CALL_STACK_DEPTH; |
| |
| // Test call stack under the limit passes. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth - 10).c_str(), |
| compileOptions, nullptr)); |
| // Test call stack over the limit fails. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth + 10).c_str(), |
| compileOptions, kCallStackTooDeep)); |
| // Test call stack over the limit without limit does not fail. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth + 10).c_str(), |
| compileOptions & ~SH_LIMIT_CALL_STACK_DEPTH, nullptr)); |
| sh::Destruct(vertexCompiler); |
| } |
| |
| TEST_F(ExpressionLimitTest, UnusedCallStackDepth) |
| { |
| ShShaderSpec spec = SH_WEBGL_SPEC; |
| ShShaderOutput output = SH_ESSL_OUTPUT; |
| ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources); |
| ShCompileOptions compileOptions = SH_LIMIT_CALL_STACK_DEPTH; |
| |
| // Test call stack under the limit passes. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth - 10).c_str(), |
| compileOptions, nullptr)); |
| // Test call stack over the limit fails. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth + 10).c_str(), |
| compileOptions, kCallStackTooDeep)); |
| // Test call stack over the limit without limit does not fail. |
| EXPECT_TRUE(CheckShaderCompilation( |
| vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth + 10).c_str(), |
| compileOptions & ~SH_LIMIT_CALL_STACK_DEPTH, nullptr)); |
| sh::Destruct(vertexCompiler); |
| } |
| |
| TEST_F(ExpressionLimitTest, Recursion) |
| { |
| ShShaderSpec spec = SH_WEBGL_SPEC; |
| ShShaderOutput output = SH_ESSL_OUTPUT; |
| ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources); |
| ShCompileOptions compileOptions = 0; |
| |
| static const char *shaderWithRecursion0 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| vec4 someFunc() { |
| return someFunc(); |
| } |
| |
| void main() { |
| gl_FragColor = u_color * someFunc(); |
| } |
| )"; |
| |
| static const char *shaderWithRecursion1 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| |
| vec4 someFunc(); |
| |
| vec4 someFunc1() { |
| return someFunc(); |
| } |
| |
| vec4 someFunc() { |
| return someFunc1(); |
| } |
| |
| void main() { |
| gl_FragColor = u_color * someFunc(); |
| } |
| )"; |
| |
| static const char *shaderWithRecursion2 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| vec4 someFunc() { |
| if (u_color.x > 0.5) { |
| return someFunc(); |
| } else { |
| return vec4(1); |
| } |
| } |
| |
| void main() { |
| gl_FragColor = someFunc(); |
| } |
| )"; |
| |
| static const char *shaderWithRecursion3 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| vec4 someFunc() { |
| if (u_color.x > 0.5) { |
| return vec4(1); |
| } else { |
| return someFunc(); |
| } |
| } |
| |
| void main() { |
| gl_FragColor = someFunc(); |
| } |
| )"; |
| |
| static const char *shaderWithRecursion4 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| vec4 someFunc() { |
| return (u_color.x > 0.5) ? vec4(1) : someFunc(); |
| } |
| |
| void main() { |
| gl_FragColor = someFunc(); |
| } |
| )"; |
| |
| static const char *shaderWithRecursion5 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| vec4 someFunc() { |
| return (u_color.x > 0.5) ? someFunc() : vec4(1); |
| } |
| |
| void main() { |
| gl_FragColor = someFunc(); |
| } |
| )"; |
| |
| static const char *shaderWithRecursion6 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| vec4 someFunc() { |
| return someFunc(); |
| } |
| |
| void main() { |
| gl_FragColor = u_color; |
| } |
| )"; |
| |
| static const char *shaderWithNoRecursion = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| |
| vec3 rgb(int r, int g, int b) { |
| return vec3(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0); |
| } |
| |
| void main() { |
| vec3 hairColor0 = rgb(151, 200, 234); |
| vec3 faceColor2 = rgb(183, 148, 133); |
| gl_FragColor = u_color + vec4(hairColor0 + faceColor2, 0); |
| } |
| )"; |
| |
| static const char *shaderWithRecursion7 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| |
| vec4 function2() { |
| return u_color; |
| } |
| |
| vec4 function1() { |
| vec4 a = function2(); |
| vec4 b = function1(); |
| return a + b; |
| } |
| |
| void main() { |
| gl_FragColor = function1(); |
| } |
| )"; |
| |
| static const char *shaderWithRecursion8 = |
| R"(precision mediump float; |
| uniform vec4 u_color; |
| |
| vec4 function1(); |
| |
| vec4 function3() { |
| return function1(); |
| } |
| |
| vec4 function2() { |
| return function3(); |
| } |
| |
| vec4 function1() { |
| return function2(); |
| } |
| |
| void main() { |
| gl_FragColor = function1(); |
| } |
| )"; |
| |
| // Check simple recursions fails. |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion0, compileOptions, |
| kHasRecursion)); |
| // Check simple recursions fails. |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion1, compileOptions, |
| kHasRecursion)); |
| // Check if recursions fails. |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion2, compileOptions, |
| kHasRecursion)); |
| // Check if recursions fails. |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion3, compileOptions, |
| kHasRecursion)); |
| // Check ternary recursions fails. |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion4, compileOptions, |
| kHasRecursion)); |
| // Check ternary recursions fails. |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion5, compileOptions, |
| kHasRecursion)); |
| |
| // Check some more forms of recursion |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion6, compileOptions, |
| kHasRecursion)); |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion7, compileOptions, |
| kHasRecursion)); |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion8, compileOptions, |
| kHasRecursion)); |
| // Check unused recursions fails if limiting call stack |
| // since we check all paths. |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion6, |
| compileOptions | SH_LIMIT_CALL_STACK_DEPTH, kHasRecursion)); |
| |
| // Check unused recursions passes. |
| EXPECT_TRUE( |
| CheckShaderCompilation(vertexCompiler, shaderWithNoRecursion, compileOptions, nullptr)); |
| // Check unused recursions passes if limiting call stack. |
| EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithNoRecursion, |
| compileOptions | SH_LIMIT_CALL_STACK_DEPTH, nullptr)); |
| sh::Destruct(vertexCompiler); |
| } |
| |
| TEST_F(ExpressionLimitTest, FunctionParameterCount) |
| { |
| ShShaderSpec spec = SH_WEBGL_SPEC; |
| ShShaderOutput output = SH_ESSL_OUTPUT; |
| ShHandle compiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources); |
| ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY; |
| |
| // Test parameters under the limit succeeds. |
| EXPECT_TRUE(CheckShaderCompilation( |
| compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters).c_str(), |
| compileOptions, nullptr)); |
| // Test parameters over the limit fails. |
| EXPECT_TRUE(CheckShaderCompilation( |
| compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters + 1).c_str(), |
| compileOptions, kTooManyParameters)); |
| // Test parameters over the limit without limit does not fail. |
| EXPECT_TRUE(CheckShaderCompilation( |
| compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters + 1).c_str(), |
| compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr)); |
| sh::Destruct(compiler); |
| } |
| |
| TEST_F(ExpressionLimitTest, NestingInsideSwitch) |
| { |
| ShShaderSpec spec = SH_WEBGL2_SPEC; |
| ShShaderOutput output = SH_ESSL_OUTPUT; |
| ShHandle compiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources); |
| ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY; |
| |
| // Test nesting over the limit fails. |
| EXPECT_TRUE(CheckShaderCompilation( |
| compiler, GenerateShaderWithNestingInsideSwitch(kMaxExpressionComplexity + 1).c_str(), |
| compileOptions, kExpressionTooComplex)); |
| // Test nesting over the limit without limit does not fail. |
| EXPECT_TRUE(CheckShaderCompilation( |
| compiler, GenerateShaderWithNestingInsideSwitch(kMaxExpressionComplexity + 1).c_str(), |
| compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr)); |
| // Test that nesting way over the limit doesn't cause stack overflow but is handled |
| // gracefully. |
| EXPECT_TRUE(CheckShaderCompilation(compiler, |
| GenerateShaderWithNestingInsideSwitch(5000).c_str(), |
| compileOptions, kTooComplexSwitch)); |
| sh::Destruct(compiler); |
| } |
| |
| TEST_F(ExpressionLimitTest, NestingInsideGlobalInitializer) |
| { |
| ShShaderSpec spec = SH_WEBGL_SPEC; |
| ShShaderOutput output = SH_ESSL_OUTPUT; |
| ShHandle compiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources); |
| ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY; |
| |
| // Test nesting over the limit fails. |
| EXPECT_TRUE(CheckShaderCompilation( |
| compiler, |
| GenerateShaderWithNestingInsideGlobalInitializer(kMaxExpressionComplexity + 1).c_str(), |
| compileOptions, kExpressionTooComplex)); |
| // Test nesting over the limit without limit does not fail. |
| EXPECT_TRUE(CheckShaderCompilation( |
| compiler, |
| GenerateShaderWithNestingInsideGlobalInitializer(kMaxExpressionComplexity + 1).c_str(), |
| compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr)); |
| // Test that nesting way over the limit doesn't cause stack overflow but is handled |
| // gracefully. |
| EXPECT_TRUE(CheckShaderCompilation( |
| compiler, GenerateShaderWithNestingInsideGlobalInitializer(5000).c_str(), compileOptions, |
| kGlobalVariableInit)); |
| sh::Destruct(compiler); |
| } |