blob: c83cbe2eb8bd7137f76c25f6ded52035f14dd0b2 [file] [log] [blame]
//
// 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);
}