Edit

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

Branch :

  • Show log

    Commit

  • Author : Stuart Morgan
    Date : 2019-08-14 12:25:12
    Hash : 9d737966
    Message : Standardize copyright notices to project style For all "ANGLE Project" copyrights, standardize to the format specified by the style guide. Changes: - "Copyright (c)" and "Copyright(c)" changed to just "Copyright". - Removed the second half of date ranges ("Y1Y1-Y2Y2"->"Y1Y1"). - Fixed a small number of files that had no copyright date using the initial commit year from the version control history. - Fixed one instance of copyright being "The ANGLE Project" rather than "The ANGLE Project Authors" These changes are applied both to the copyright of source file, and where applicable to copyright statements that are generated by templates. BUG=angleproject:3811 Change-Id: I973dd65e4ef9deeba232d5be74c768256a0eb2e5 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1754397 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Jamie Madill <jmadill@chromium.org>

  • src/tests/compiler_tests/ExpressionLimit_test.cpp
  • //
    // 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);
    }