blob: 6c8e74644a5f5a4b92f91838846c9e269c748028 [file]
//
// Copyright 2024 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.
//
// Parse_test.cpp:
// Test for parsing erroneous and correct GLSL input.
//
#include <memory>
#include "GLSLANG/ShaderLang.h"
#include "angle_gl.h"
#include "compiler/translator/glsl/TranslatorESSL.h"
#include "gtest/gtest.h"
using namespace sh;
class ParseTest : public testing::Test
{
public:
ParseTest()
{
InitBuiltInResources(&mResources);
mResources.FragmentPrecisionHigh = 1;
mCompileOptions.intermediateTree = true;
}
protected:
void TearDown() override { mTranslator.reset(); }
testing::AssertionResult compile(const std::string &shaderString)
{
if (mTranslator == nullptr)
{
std::unique_ptr<TranslatorESSL> translator =
std::make_unique<TranslatorESSL>(GL_FRAGMENT_SHADER, mShaderSpec);
if (!translator->Init(mResources))
{
return testing::AssertionFailure() << "Failed to initialize translator";
}
mTranslator = std::move(translator);
}
const char *shaderStrings[] = {shaderString.c_str()};
bool compilationSuccess = mTranslator->compile(shaderStrings, 1, mCompileOptions);
TInfoSink &infoSink = mTranslator->getInfoSink();
mInfoLog = RemoveSymbolIdsFromInfoLog(infoSink.info.c_str());
if (!compilationSuccess)
{
return testing::AssertionFailure() << "Shader compilation failed " << mInfoLog;
}
return testing::AssertionSuccess();
}
bool foundErrorInIntermediateTree() const { return foundInIntermediateTree("ERROR:"); }
bool foundInIntermediateTree(const char *stringToFind) const
{
return mInfoLog.find(stringToFind) != std::string::npos;
}
ShBuiltInResources mResources;
ShCompileOptions mCompileOptions{};
ShShaderSpec mShaderSpec = SH_WEBGL_SPEC;
private:
// Remove symbol ids from info log - the tests don't care about them.
static std::string RemoveSymbolIdsFromInfoLog(const char *infoLog)
{
std::string filteredLog(infoLog);
size_t idPrefixPos = 0u;
do
{
idPrefixPos = filteredLog.find(" (symbol id");
if (idPrefixPos != std::string::npos)
{
size_t idSuffixPos = filteredLog.find(")", idPrefixPos);
filteredLog.erase(idPrefixPos, idSuffixPos - idPrefixPos + 1u);
}
} while (idPrefixPos != std::string::npos);
return filteredLog;
}
std::unique_ptr<TranslatorESSL> mTranslator;
std::string mInfoLog;
};
TEST_F(ParseTest, UnsizedArrayConstructorNoCrash)
{
const char kShader[] = R"(#version 310 es\n"
int A[];
int B[int[][](A)];)";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("constructing from an unsized array"));
}
TEST_F(ParseTest, UniformBlockNameReferenceNoCrash)
{
const char kShader[] = R"(#version 300 es
precision mediump float;
out float o;
uniform a { float r; } UBOA;
void main() {
o = float(UBOA);
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree(
"interface block cannot be used as a constructor argument for this type"));
}
TEST_F(ParseTest, Precise320NoCrash)
{
const char kShader[] = R"(#version 320 es
precision mediump float;
void main(){
float t;
precise t;
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("unsupported shader version"));
}
// Tests that layout(index=0) is parsed in es 100 shaders if an extension like
// EXT_shader_framebuffer_fetch is enabled, but this does not cause a crash.
TEST_F(ParseTest, ShaderFramebufferFetchLayoutIndexNoCrash)
{
mResources.EXT_blend_func_extended = 1;
mResources.MaxDualSourceDrawBuffers = 1;
mResources.EXT_shader_framebuffer_fetch = 1;
const char kShader[] = R"(
#extension GL_EXT_blend_func_extended: require
#extension GL_EXT_shader_framebuffer_fetch : require
layout(index=0)mediump vec4 c;
void main() { }
)";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("'index' : invalid layout qualifier"));
}
TEST_F(ParseTest, Radians320NoCrash)
{
const char kShader[] = R"(#version 320 es
precision mediump float;
vec4 s() { writeonly vec4 color; radians(color); return vec4(1); })";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("'writeonly' : Only allowed with shader storage blocks,"));
EXPECT_TRUE(foundInIntermediateTree(
"'radians' : wrong operand type - no operation 'radians' exists that"));
}
TEST_F(ParseTest, CoherentCoherentNoCrash)
{
const char kShader[] = R"(#version 310 es
uniform highp coherent coherent readonly image2D image1;\n"
void main() {
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("coherent specified multiple times"));
}
TEST_F(ParseTest, LargeArrayIndexNoCrash)
{
mShaderSpec = SH_WEBGL2_SPEC;
const char kShader[] = R"(#version 300 es
int rr[~1U];
out int o;
void main() {
o = rr[1];
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(
foundInIntermediateTree("Size of declared variable exceeds implementation-defined limit"));
}
// Tests that separating variable declaration of multiple instances of a anonymous structure
// rewrites the expression types for expressions that use the variables. At the time of writing
// the expression types were left referencing the original anonymous function.
TEST_F(ParseTest, SeparateAnonymousFunctionsRewritesExpressions)
{
const char kShader[] = R"(
struct {
mediump vec2 d;
} s0, s1;
void main() {
s0 = s0;
s1 = s1;
})";
EXPECT_TRUE(compile(kShader));
EXPECT_FALSE(foundInIntermediateTree("anonymous"));
}
// Tests that constant folding a division of a void variable does not crash during parsing.
TEST_F(ParseTest, ConstStructWithVoidAndDivNoCrash)
{
const char kShader[] = R"(
const struct s { void i; } ss = s();
void main() {
highp vec3 q = ss.i / ss.i;
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("illegal use of type 'void'"));
EXPECT_TRUE(foundInIntermediateTree("constructor does not have any arguments"));
EXPECT_TRUE(foundInIntermediateTree("operation with void operands"));
EXPECT_TRUE(foundInIntermediateTree(
"wrong operand types - no operation '/' exists that takes a left-hand operand of type "
"'const void' and a right operand of type 'const void'"));
EXPECT_TRUE(foundInIntermediateTree(
"cannot convert from 'const void' to 'highp 3-component vector of float'"));
}
// Tests that division of void variable returns the same errors as division of constant
// void variable (see above).
TEST_F(ParseTest, StructWithVoidAndDivErrorCheck)
{
const char kShader[] = R"(
struct s { void i; } ss = s();
void main() {
highp vec3 q = ss.i / ss.i;
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("illegal use of type 'void'"));
EXPECT_TRUE(foundInIntermediateTree("constructor does not have any arguments"));
EXPECT_TRUE(foundInIntermediateTree("operation with void operands"));
EXPECT_TRUE(foundInIntermediateTree(
"wrong operand types - no operation '/' exists that takes a left-hand operand of type "
"'void' and a right operand of type 'void'"));
EXPECT_TRUE(foundInIntermediateTree(
"cannot convert from 'void' to 'highp 3-component vector of float'"));
}
// Tests that usage of BuildIn struct type name does not crash during parsing.
TEST_F(ParseTest, BuildInStructTypeNameDeclarationNoCrash)
{
mCompileOptions.validateAST = 1;
const char kShader[] = R"(
void main() {
gl_DepthRangeParameters testVariable;
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("reserved built-in name"));
}
TEST_F(ParseTest, BuildInStructTypeNameFunctionArgumentNoCrash)
{
mCompileOptions.validateAST = 1;
const char kShader[] = R"(
void testFunction(gl_DepthRangeParameters testParam){}
void main() {
testFunction(gl_DepthRange);
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("reserved built-in name"));
}
TEST_F(ParseTest, BuildInStructTypeNameFunctionReturnValueNoCrash)
{
mCompileOptions.validateAST = 1;
const char kShader[] = R"(
gl_DepthRangeParameters testFunction(){return gl_DepthRange;}
void main() {
testFunction();
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("reserved built-in name"));
}
// Tests that imod of const void variable does not crash during parsing.
TEST_F(ParseTest, ConstStructVoidAndImodAndNoCrash)
{
const char kShader[] = R"(#version 310 es
const struct s { void i; } ss = s();
void main() {
highp vec3 q = ss.i % ss.i;
})";
EXPECT_FALSE(compile(kShader));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("illegal use of type 'void'"));
EXPECT_TRUE(foundInIntermediateTree("constructor does not have any arguments"));
EXPECT_TRUE(foundInIntermediateTree("operation with void operands"));
EXPECT_TRUE(foundInIntermediateTree(
"wrong operand types - no operation '%' exists that takes a left-hand operand of type "
"'const void' and a right operand of type 'const void'"));
EXPECT_TRUE(foundInIntermediateTree(
"cannot convert from 'const void' to 'highp 3-component vector of float'"));
}
TEST_F(ParseTest, HugeUnsizedMultidimensionalArrayConstructorNoCrash)
{
mCompileOptions.limitExpressionComplexity = true;
std::ostringstream shader;
shader << R"(#version 310 es
int E=int)";
for (int i = 0; i < 10000; ++i)
{
shader << "[]";
}
shader << "()";
EXPECT_FALSE(compile(shader.str()));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("array has too many dimensions"));
}
TEST_F(ParseTest, HugeMultidimensionalArrayConstructorNoCrash)
{
mCompileOptions.limitExpressionComplexity = true;
std::ostringstream shader;
shader << R"(#version 310 es
int E=int)";
for (int i = 0; i < 10000; ++i)
{
shader << "[1]";
}
for (int i = 0; i < 10000; ++i)
{
shader << "(2)";
}
EXPECT_FALSE(compile(shader.str()));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("array has too many dimensions"));
}
TEST_F(ParseTest, DeeplyNestedWhileStatementsNoCrash)
{
mShaderSpec = SH_WEBGL2_SPEC;
std::ostringstream shader;
shader << R"(#version 300 es
void main() {
)";
for (int i = 0; i < 1700; ++i)
{
shader << " while(true)";
}
shader << "; }";
EXPECT_FALSE(compile(shader.str()));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("statement is too deeply nested"));
}
TEST_F(ParseTest, DeeplyNestedForStatementsNoCrash)
{
mShaderSpec = SH_WEBGL2_SPEC;
std::ostringstream shader;
shader << R"(#version 300 es
void main() {
)";
for (int i = 0; i < 1700; ++i)
{
shader << " for(int i = 0; i < 10; i++)";
}
shader << "; }";
EXPECT_FALSE(compile(shader.str()));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("statement is too deeply nested"));
}
TEST_F(ParseTest, DeeplyNestedDoWhileStatementsNoCrash)
{
mShaderSpec = SH_WEBGL2_SPEC;
std::ostringstream shader;
shader << R"(#version 300 es
void main() {
)";
for (int i = 0; i < 1700; ++i)
{
shader << " do {";
}
for (int i = 0; i < 1700; ++i)
{
shader << "} while(true);";
}
shader << "}";
EXPECT_FALSE(compile(shader.str()));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("statement is too deeply nested"));
}
TEST_F(ParseTest, DeeplyNestedSwitchStatementsNoCrash)
{
mShaderSpec = SH_WEBGL2_SPEC;
std::ostringstream shader;
shader << R"(#version 300 es
void main() {
)";
for (int i = 0; i < 1700; ++i)
{
shader << " switch(1) { default: int i=0;";
}
for (int i = 0; i < 1700; ++i)
{
shader << "}";
}
shader << "}";
EXPECT_FALSE(compile(shader.str()));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("statement is too deeply nested"));
}
TEST_F(ParseTest, ManyChainedUnaryExpressionsNoCrash)
{
mCompileOptions.limitExpressionComplexity = true;
mShaderSpec = SH_WEBGL2_SPEC;
std::ostringstream shader;
shader << R"(#version 300 es
precision mediump float;
void main() {
int iterations=0;)";
for (int i = 0; i < 6000; ++i)
{
shader << "~";
}
shader << R"(++iterations;
}
)";
EXPECT_FALSE(compile(shader.str()));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("Expression too complex"));
}
TEST_F(ParseTest, ManyChainedAssignmentsNoCrash)
{
mCompileOptions.limitExpressionComplexity = true;
mShaderSpec = SH_WEBGL2_SPEC;
std::ostringstream shader;
shader << R"(#version 300 es
void main() {
int c = 0;
)";
for (int i = 0; i < 3750; ++i)
{
shader << "c=\n";
}
shader << "c+1; }";
EXPECT_FALSE(compile(shader.str()));
EXPECT_TRUE(foundErrorInIntermediateTree());
EXPECT_TRUE(foundInIntermediateTree("Expression too complex"));
}