Edit

kc3-lang/angle/src/tests/compiler_tests/ExpressionLimit_test.cpp

Branch :

  • Show log

    Commit

  • Author : Olli Etuaho
    Date : 2018-04-04 16:55:34
    Hash : 4002e92a
    Message : Guard traversers used during parsing against stack overflow Traversers used during parsing can be vulnerable to stack overflow since the AST has not yet been validated for max depth. Make sure to check for traversal depth in traversers used during parsing. We set the maximum traversal depth in ValidateGlobalInitializer and ValidateSwitchStatementList to 256, which matches the default value for validating general AST complexity. The depth check is on regardless of compiler options. In case the traversers go over the maximum traversal depth, they fail validation. BUG=angleproject:2453 TEST=angle_unittests Change-Id: I89ba576e8ef69663ba35d7b9050a6da319f1757c Reviewed-on: https://chromium-review.googlesource.com/995795 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>

  • src/tests/compiler_tests/ExpressionLimit_test.cpp
  • //
    // Copyright (c) 2002-2013 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 "angle_gl.h"
    #include "gtest/gtest.h"
    #include "GLSLANG/ShaderLang.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);
    }