Edit

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

Branch :

  • Show log

    Commit

  • Author : Olli Etuaho
    Date : 2015-07-06 15:25:19
    Hash : ce39f6ff
    Message : Disallow user-defined function calls in global variable init Generate an error message when an user-defined function call is found in a global variable initializer. Even before this patch, the call graph already marked functions that were only called from the global scope as unused. This change was tested extensively with popular WebGL content, with no regressions found. TEST=angle_unittests BUG=angleproject:988 Change-Id: Iec1b16d2af386f1e5c383f86926d80cef553b694 Reviewed-on: https://chromium-review.googlesource.com/283291 Tested-by: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Zhenyao Mo <zmo@chromium.org>

  • 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"
    
    #define SHADER(Src) #Src
    
    class ExpressionLimitTest : public testing::Test {
    protected:
        static const int kMaxExpressionComplexity = 16;
        static const int kMaxCallStackDepth = 16;
        static const char* kExpressionTooComplex;
        static const char* kCallStackTooDeep;
        static const char* kHasRecursion;
    
        virtual void SetUp()
        {
            memset(&resources, 0, sizeof(resources));
    
            GenerateResources(&resources);
        }
    
        // Set up the per compile resources
        void GenerateResources(ShBuiltInResources* resources)
        {
            ShInitBuiltInResources(resources);
    
            resources->MaxVertexAttribs = 8;
            resources->MaxVertexUniformVectors = 128;
            resources->MaxVaryingVectors = 8;
            resources->MaxVertexTextureImageUnits = 0;
            resources->MaxCombinedTextureImageUnits = 8;
            resources->MaxTextureImageUnits = 8;
            resources->MaxFragmentUniformVectors = 16;
            resources->MaxDrawBuffers = 1;
    
            resources->OES_standard_derivatives = 0;
            resources->OES_EGL_image_external = 0;
    
            resources->MaxExpressionComplexity = kMaxExpressionComplexity;
            resources->MaxCallStackDepth = kMaxCallStackDepth;
        }
    
        void GenerateLongExpression(int length, std::stringstream* ss)
        {
            for (int ii = 0; ii < length; ++ii) {
              *ss << "+ vec4(" << ii << ")";
            }
        }
    
        std::string GenerateShaderWithLongExpression(int length)
        {
            static const char* shaderStart = SHADER(
                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();
        }
    
        std::string GenerateShaderWithUnusedLongExpression(int length)
        {
            static const char* shaderStart = SHADER(
                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();
        }
    
        void GenerateDeepFunctionStack(int length, std::stringstream* ss)
        {
            static const char* shaderStart = SHADER(
                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";
            }
        }
    
        std::string GenerateShaderWithDeepFunctionStack(int length)
        {
            std::stringstream ss;
    
            GenerateDeepFunctionStack(length, &ss);
    
            ss << "void main() {\n"
               << "  gl_FragColor = function" << length << "();\n"
               << "}";
    
            return ss.str();
        }
    
        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();
        }
    
        // 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,
                                    int compileOptions,
                                    const char *expected_error)
        {
            bool success = ShCompile(compiler, &source, 1, compileOptions) != 0;
            if (success)
            {
                success = !expected_error;
            }
            else
            {
                std::string log = ShGetInfoLog(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 =
        "Function recursion detected";
    
    TEST_F(ExpressionLimitTest, ExpressionComplexity)
    {
        ShShaderSpec spec = SH_WEBGL_SPEC;
        ShShaderOutput output = SH_ESSL_OUTPUT;
        ShHandle vertexCompiler = ShConstructCompiler(
            GL_FRAGMENT_SHADER, spec, output, &resources);
        int compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY;
    
        // Test expression under the limit passes.
        EXPECT_TRUE(CheckShaderCompilation(
            vertexCompiler,
            GenerateShaderWithLongExpression(
                kMaxExpressionComplexity - 10).c_str(),
            compileOptions, NULL));
        // 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, NULL));
        ShDestruct(vertexCompiler);
    }
    
    TEST_F(ExpressionLimitTest, UnusedExpressionComplexity)
    {
        ShShaderSpec spec = SH_WEBGL_SPEC;
        ShShaderOutput output = SH_ESSL_OUTPUT;
        ShHandle vertexCompiler = ShConstructCompiler(
            GL_FRAGMENT_SHADER, spec, output, &resources);
        int compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY;
    
        // Test expression under the limit passes.
        EXPECT_TRUE(CheckShaderCompilation(
            vertexCompiler,
            GenerateShaderWithUnusedLongExpression(
                kMaxExpressionComplexity - 10).c_str(),
            compileOptions, NULL));
        // 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, NULL));
        ShDestruct(vertexCompiler);
    }
    
    TEST_F(ExpressionLimitTest, CallStackDepth)
    {
        ShShaderSpec spec = SH_WEBGL_SPEC;
        ShShaderOutput output = SH_ESSL_OUTPUT;
        ShHandle vertexCompiler = ShConstructCompiler(
            GL_FRAGMENT_SHADER, spec, output, &resources);
        int compileOptions = SH_LIMIT_CALL_STACK_DEPTH;
    
        // Test call stack under the limit passes.
        EXPECT_TRUE(CheckShaderCompilation(
            vertexCompiler,
            GenerateShaderWithDeepFunctionStack(
                kMaxCallStackDepth - 10).c_str(),
            compileOptions, NULL));
        // 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, NULL));
        ShDestruct(vertexCompiler);
    }
    
    TEST_F(ExpressionLimitTest, UnusedCallStackDepth)
    {
        ShShaderSpec spec = SH_WEBGL_SPEC;
        ShShaderOutput output = SH_ESSL_OUTPUT;
        ShHandle vertexCompiler = ShConstructCompiler(
            GL_FRAGMENT_SHADER, spec, output, &resources);
        int compileOptions = SH_LIMIT_CALL_STACK_DEPTH;
    
        // Test call stack under the limit passes.
        EXPECT_TRUE(CheckShaderCompilation(
            vertexCompiler,
            GenerateShaderWithUnusedDeepFunctionStack(
                kMaxCallStackDepth - 10).c_str(),
            compileOptions, NULL));
        // 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, NULL));
        ShDestruct(vertexCompiler);
    }
    
    TEST_F(ExpressionLimitTest, Recursion)
    {
        ShShaderSpec spec = SH_WEBGL_SPEC;
        ShShaderOutput output = SH_ESSL_OUTPUT;
        ShHandle vertexCompiler = ShConstructCompiler(
            GL_FRAGMENT_SHADER, spec, output, &resources);
        int compileOptions = 0;
    
        static const char* shaderWithRecursion0 = SHADER(
            precision mediump float;
            uniform vec4 u_color;
            vec4 someFunc()  {
                return someFunc();
            }
    
            void main() {
                gl_FragColor = u_color * someFunc();
            }
        );
    
        static const char* shaderWithRecursion1 = SHADER(
            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 = SHADER(
            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 = SHADER(
            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 = SHADER(
            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 = SHADER(
            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 = SHADER(
            precision mediump float;
            uniform vec4 u_color;
            vec4 someFunc()  {
                return someFunc();
            }
    
            void main() {
                gl_FragColor = u_color;
            }
        );
    
        static const char* shaderWithNoRecursion = SHADER(
            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 = SHADER(
            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 = SHADER(
            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, NULL));
        // Check unused recursions passes if limiting call stack.
        EXPECT_TRUE(CheckShaderCompilation(
            vertexCompiler, shaderWithNoRecursion,
            compileOptions | SH_LIMIT_CALL_STACK_DEPTH, NULL));
        ShDestruct(vertexCompiler);
    }