Commit 923ecef6 by Olli Etuaho Committed by Commit Bot

Fix switch statement validation corner cases

The grammar needs to generate AST nodes even for no-op statements, since they might be the last statement in a switch statement that is required for switch statement validity. Change the grammar to generate nodes from empty blocks and empty declarations. We also need to do some further processing of the AST. This is because PruneEmptyDeclarations will still remove empty declarations, and at least the NVIDIA driver GLSL compiler doesn't accept some types of no-op statements as the last statement inside a switch statement. So after parsing has finished we do rudimentary dead code elimination to remove dead cases from the end of switch statements. BUG=angleproject:2181 TEST=angle_unittests Change-Id: I586f2e4a3ac2171e65f1f0ccb7a7de220e3cc225 Reviewed-on: https://chromium-review.googlesource.com/712574 Commit-Queue: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent d92e93b8
......@@ -111,6 +111,8 @@
'compiler/translator/RemoveArrayLengthMethod.h',
'compiler/translator/RemoveInvariantDeclaration.cpp',
'compiler/translator/RemoveInvariantDeclaration.h',
'compiler/translator/RemoveNoOpCasesFromEndOfSwitchStatements.cpp',
'compiler/translator/RemoveNoOpCasesFromEndOfSwitchStatements.h',
'compiler/translator/RemovePow.cpp',
'compiler/translator/RemovePow.h',
'compiler/translator/RewriteDoWhile.cpp',
......
......@@ -29,6 +29,7 @@
#include "compiler/translator/RegenerateStructNames.h"
#include "compiler/translator/RemoveArrayLengthMethod.h"
#include "compiler/translator/RemoveInvariantDeclaration.h"
#include "compiler/translator/RemoveNoOpCasesFromEndOfSwitchStatements.h"
#include "compiler/translator/RemovePow.h"
#include "compiler/translator/RewriteDoWhile.h"
#include "compiler/translator/ScalarizeVecAndMatConstructorArgs.h"
......@@ -374,6 +375,20 @@ TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[],
if (success && (compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY))
success = limitExpressionComplexity(root);
// Prune empty declarations to work around driver bugs and to keep declaration output
// simple. We want to do this before most other operations since TIntermDeclaration nodes
// without any children are tricky to work with.
if (success)
PruneEmptyDeclarations(root);
// In case the last case inside a switch statement is a certain type of no-op, GLSL
// compilers in drivers may not accept it. In this case we clean up the dead code from the
// end of switch statements. This is also required because PruneEmptyDeclarations may have
// left switch statements that only contained an empty declaration inside the final case in
// an invalid state. Relies on that PruneEmptyDeclarations has already been run.
if (success)
RemoveNoOpCasesFromEndOfSwitchStatements(root, &symbolTable);
// Create the function DAG and check there is no recursion
if (success)
success = initCallDag(root);
......@@ -392,11 +407,6 @@ TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[],
if (success && !(compileOptions & SH_DONT_PRUNE_UNUSED_FUNCTIONS))
success = pruneUnusedFunctions(root);
// Prune empty declarations to work around driver bugs and to keep declaration output
// simple.
if (success)
PruneEmptyDeclarations(root);
if (success && shaderVersion >= 310)
{
success = ValidateVaryingLocations(root, &mDiagnostics);
......
......@@ -480,10 +480,11 @@ bool TIntermAggregate::hasSideEffects() const
void TIntermBlock::appendStatement(TIntermNode *statement)
{
// Declaration nodes with no children can appear if all the declarators just added constants to
// the symbol table instead of generating code. They're no-ops so they aren't added to blocks.
if (statement != nullptr && (statement->getAsDeclarationNode() == nullptr ||
!statement->getAsDeclarationNode()->getSequence()->empty()))
// Declaration nodes with no children can appear if it was an empty declaration or if all the
// declarators just added constants to the symbol table instead of generating code. We still
// need to add the declaration to the AST in that case because it might be relevant to the
// validity of switch/case.
if (statement != nullptr)
{
mStatements.push_back(statement);
}
......@@ -526,6 +527,7 @@ bool TIntermSwitch::replaceChildNode(TIntermNode *original, TIntermNode *replace
{
REPLACE_IF_IS(mInit, TIntermTyped, original, replacement);
REPLACE_IF_IS(mStatementList, TIntermBlock, original, replacement);
ASSERT(mStatementList);
return false;
}
......@@ -938,6 +940,28 @@ TIntermLoop::TIntermLoop(TLoopType type,
}
}
TIntermIfElse::TIntermIfElse(TIntermTyped *cond, TIntermBlock *trueB, TIntermBlock *falseB)
: TIntermNode(), mCondition(cond), mTrueBlock(trueB), mFalseBlock(falseB)
{
// Prune empty false blocks so that there won't be unnecessary operations done on it.
if (mFalseBlock && mFalseBlock->getSequence()->empty())
{
mFalseBlock = nullptr;
}
}
TIntermSwitch::TIntermSwitch(TIntermTyped *init, TIntermBlock *statementList)
: TIntermNode(), mInit(init), mStatementList(statementList)
{
ASSERT(mStatementList);
}
void TIntermSwitch::setStatementList(TIntermBlock *statementList)
{
ASSERT(statementList);
mStatementList = statementList;
}
// static
TQualifier TIntermTernary::DetermineQualifier(TIntermTyped *cond,
TIntermTyped *trueExpression,
......
......@@ -843,10 +843,7 @@ class TIntermTernary : public TIntermTyped
class TIntermIfElse : public TIntermNode
{
public:
TIntermIfElse(TIntermTyped *cond, TIntermBlock *trueB, TIntermBlock *falseB)
: TIntermNode(), mCondition(cond), mTrueBlock(trueB), mFalseBlock(falseB)
{
}
TIntermIfElse(TIntermTyped *cond, TIntermBlock *trueB, TIntermBlock *falseB);
void traverse(TIntermTraverser *it) override;
bool replaceChildNode(TIntermNode *original, TIntermNode *replacement) override;
......@@ -868,10 +865,7 @@ class TIntermIfElse : public TIntermNode
class TIntermSwitch : public TIntermNode
{
public:
TIntermSwitch(TIntermTyped *init, TIntermBlock *statementList)
: TIntermNode(), mInit(init), mStatementList(statementList)
{
}
TIntermSwitch(TIntermTyped *init, TIntermBlock *statementList);
void traverse(TIntermTraverser *it) override;
bool replaceChildNode(TIntermNode *original, TIntermNode *replacement) override;
......@@ -880,7 +874,9 @@ class TIntermSwitch : public TIntermNode
TIntermTyped *getInit() { return mInit; }
TIntermBlock *getStatementList() { return mStatementList; }
void setStatementList(TIntermBlock *statementList) { mStatementList = statementList; }
// Must be called with a non-null statementList.
void setStatementList(TIntermBlock *statementList);
protected:
TIntermTyped *mInit;
......
......@@ -812,16 +812,9 @@ bool TOutputGLSLBase::visitIfElse(Visit visit, TIntermIfElse *node)
bool TOutputGLSLBase::visitSwitch(Visit visit, TIntermSwitch *node)
{
if (node->getStatementList())
{
writeTriplet(visit, "switch (", ") ", nullptr);
// The curly braces get written when visiting the statementList aggregate
}
else
{
// No statementList, so it won't output curly braces
writeTriplet(visit, "switch (", ") {", "}\n");
}
ASSERT(node->getStatementList());
writeTriplet(visit, "switch (", ") ", nullptr);
// The curly braces get written when visiting the statementList aggregate
return true;
}
......
......@@ -2135,20 +2135,13 @@ bool OutputHLSL::visitSwitch(Visit visit, TIntermSwitch *node)
{
TInfoSinkBase &out = getInfoSink();
if (node->getStatementList())
{
if (visit == PreVisit)
{
node->setStatementList(RemoveSwitchFallThrough(node->getStatementList()));
}
outputTriplet(out, visit, "switch (", ") ", "");
// The curly braces get written when visiting the statementList block.
}
else
ASSERT(node->getStatementList());
if (visit == PreVisit)
{
// No statementList, so it won't output curly braces
outputTriplet(out, visit, "switch (", ") {", "}\n");
node->setStatementList(RemoveSwitchFallThrough(node->getStatementList()));
}
outputTriplet(out, visit, "switch (", ") ", "");
// The curly braces get written when visiting the statementList block.
return true;
}
......
......@@ -4633,12 +4633,10 @@ TIntermSwitch *TParseContext::addSwitch(TIntermTyped *init,
return nullptr;
}
if (statementList)
ASSERT(statementList);
if (!ValidateSwitchStatementList(switchType, mDiagnostics, statementList, loc))
{
if (!ValidateSwitchStatementList(switchType, mDiagnostics, statementList, loc))
{
return nullptr;
}
return nullptr;
}
TIntermSwitch *node = new TIntermSwitch(init, statementList);
......
......@@ -19,14 +19,14 @@ namespace
class PruneEmptyDeclarationsTraverser : private TIntermTraverser
{
public:
static void apply(TIntermNode *root);
static void apply(TIntermBlock *root);
private:
PruneEmptyDeclarationsTraverser();
bool visitDeclaration(Visit, TIntermDeclaration *node) override;
};
void PruneEmptyDeclarationsTraverser::apply(TIntermNode *root)
void PruneEmptyDeclarationsTraverser::apply(TIntermBlock *root)
{
PruneEmptyDeclarationsTraverser prune;
root->traverse(&prune);
......@@ -62,22 +62,10 @@ bool PruneEmptyDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration
else if (sym->getBasicType() != EbtStruct)
{
// Single struct declarations may just declare the struct type and no variables, so
// they should not be pruned. All other single empty declarations can be pruned
// entirely. Example of an empty declaration that will be pruned:
// float;
TIntermSequence emptyReplacement;
TIntermBlock *parentAsBlock = getParentNode()->getAsBlock();
// The declaration may be inside a block or in a loop init expression.
ASSERT(parentAsBlock != nullptr || getParentNode()->getAsLoopNode() != nullptr);
if (parentAsBlock)
{
mMultiReplacements.push_back(
NodeReplaceWithMultipleEntry(parentAsBlock, node, emptyReplacement));
}
else
{
queueReplacement(nullptr, OriginalNode::IS_DROPPED);
}
// they should not be pruned. If there are entirely empty non-struct declarations,
// they result in TIntermDeclaration nodes without any children in the parsing
// stage. This will be handled further down in the code.
UNREACHABLE();
}
else if (sym->getType().getQualifier() != EvqGlobal &&
sym->getType().getQualifier() != EvqTemporary)
......@@ -102,12 +90,29 @@ bool PruneEmptyDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration
}
}
}
else
{
// We have a declaration with no declarators.
// The declaration may be either inside a block or in a loop init expression.
TIntermBlock *parentAsBlock = getParentNode()->getAsBlock();
ASSERT(parentAsBlock != nullptr || getParentNode()->getAsLoopNode() != nullptr);
if (parentAsBlock)
{
TIntermSequence emptyReplacement;
mMultiReplacements.push_back(
NodeReplaceWithMultipleEntry(parentAsBlock, node, emptyReplacement));
}
else
{
queueReplacement(nullptr, OriginalNode::IS_DROPPED);
}
}
return false;
}
} // namespace
void PruneEmptyDeclarations(TIntermNode *root)
void PruneEmptyDeclarations(TIntermBlock *root)
{
PruneEmptyDeclarationsTraverser::apply(root);
}
......
......@@ -11,9 +11,9 @@
namespace sh
{
class TIntermNode;
class TIntermBlock;
void PruneEmptyDeclarations(TIntermNode *root);
void PruneEmptyDeclarations(TIntermBlock *root);
}
#endif // COMPILER_TRANSLATOR_PRUNEEMPTYDECLARATIONS_H_
//
// Copyright (c) 2017 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.
//
// RemoveNoOpCasesFromEndOfSwitchStatements.cpp: Clean up cases from the end of a switch statement
// that only contain no-ops.
#include "compiler/translator/RemoveNoOpCasesFromEndOfSwitchStatements.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/IntermNode_util.h"
#include "compiler/translator/IntermTraverse.h"
#include "compiler/translator/SymbolTable.h"
namespace sh
{
namespace
{
bool AreEmptyBlocks(TIntermSequence *statements, size_t i);
bool IsEmptyBlock(TIntermNode *node)
{
TIntermBlock *asBlock = node->getAsBlock();
if (asBlock)
{
if (asBlock->getSequence()->empty())
{
return true;
}
return AreEmptyBlocks(asBlock->getSequence(), 0u);
}
// Empty declarations should have already been pruned, otherwise they would need to be handled
// here. Note that declarations for struct types do contain a nameless child node.
ASSERT(node->getAsDeclarationNode() == nullptr ||
!node->getAsDeclarationNode()->getSequence()->empty());
return false;
}
// Return true if all statements in "statements" starting from index i consist only of empty blocks
// and empty declarations. Returns true also if there are no statements.
bool AreEmptyBlocks(TIntermSequence *statements, size_t i)
{
for (; i < statements->size(); ++i)
{
if (!IsEmptyBlock(statements->at(i)))
{
return false;
}
}
return true;
}
void RemoveNoOpCasesFromEndOfStatementList(TIntermBlock *statementList, TSymbolTable *symbolTable)
{
TIntermSequence *statements = statementList->getSequence();
bool foundDeadCase = false;
do
{
if (statements->empty())
{
return;
}
// Find the last case label.
size_t i = statements->size();
while (i > 0u && !(*statements)[i - 1]->getAsCaseNode())
{
--i;
}
// Now i is the index of the first statement following the last label inside the switch
// statement.
ASSERT(i > 0u);
foundDeadCase = AreEmptyBlocks(statements, i);
if (foundDeadCase)
{
statements->erase(statements->begin() + (i - 1u), statements->end());
}
} while (foundDeadCase);
}
class RemoveNoOpCasesFromEndOfSwitchTraverser : public TIntermTraverser
{
public:
RemoveNoOpCasesFromEndOfSwitchTraverser(TSymbolTable *symbolTable)
: TIntermTraverser(true, false, false, symbolTable)
{
}
bool visitSwitch(Visit visit, TIntermSwitch *node) override;
};
bool RemoveNoOpCasesFromEndOfSwitchTraverser::visitSwitch(Visit visit, TIntermSwitch *node)
{
// Here we may mutate the statement list, but it's safe since traversal has not yet reached
// there.
RemoveNoOpCasesFromEndOfStatementList(node->getStatementList(), mSymbolTable);
// Handle also nested switch statements.
return true;
}
} // anonymous namespace
void RemoveNoOpCasesFromEndOfSwitchStatements(TIntermBlock *root, TSymbolTable *symbolTable)
{
RemoveNoOpCasesFromEndOfSwitchTraverser traverser(symbolTable);
root->traverse(&traverser);
}
} // namespace sh
//
// Copyright (c) 2017 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.
//
// RemoveNoOpCasesFromEndOfSwitchStatements.h: Clean up cases from the end of a switch statement
// that only contain no-ops.
#ifndef COMPILER_TRANSLATOR_REMOVENOOPCASESFROMENDOFSWITCHSTATEMENTS_H_
#define COMPILER_TRANSLATOR_REMOVENOOPCASESFROMENDOFSWITCHSTATEMENTS_H_
namespace sh
{
class TIntermBlock;
class TSymbolTable;
void RemoveNoOpCasesFromEndOfSwitchStatements(TIntermBlock *root, TSymbolTable *symbolTable);
} // namespace sh
#endif // COMPILER_TRANSLATOR_REMOVENOOPCASESFROMENDOFSWITCHSTATEMENTS_H_
......@@ -25,9 +25,12 @@ class ValidateSwitch : public TIntermTraverser
void visitSymbol(TIntermSymbol *) override;
void visitConstantUnion(TIntermConstantUnion *) override;
bool visitDeclaration(Visit, TIntermDeclaration *) override;
bool visitBlock(Visit, TIntermBlock *) override;
bool visitBinary(Visit, TIntermBinary *) override;
bool visitUnary(Visit, TIntermUnary *) override;
bool visitTernary(Visit, TIntermTernary *) override;
bool visitSwizzle(Visit, TIntermSwizzle *) override;
bool visitIfElse(Visit visit, TIntermIfElse *) override;
bool visitSwitch(Visit, TIntermSwitch *) override;
bool visitCase(Visit, TIntermCase *node) override;
......@@ -96,6 +99,25 @@ void ValidateSwitch::visitConstantUnion(TIntermConstantUnion *)
mLastStatementWasCase = false;
}
bool ValidateSwitch::visitDeclaration(Visit, TIntermDeclaration *)
{
if (!mFirstCaseFound)
mStatementBeforeCase = true;
mLastStatementWasCase = false;
return true;
}
bool ValidateSwitch::visitBlock(Visit, TIntermBlock *)
{
if (getParentNode() != nullptr)
{
if (!mFirstCaseFound)
mStatementBeforeCase = true;
mLastStatementWasCase = false;
}
return true;
}
bool ValidateSwitch::visitBinary(Visit, TIntermBinary *)
{
if (!mFirstCaseFound)
......@@ -120,6 +142,14 @@ bool ValidateSwitch::visitTernary(Visit, TIntermTernary *)
return true;
}
bool ValidateSwitch::visitSwizzle(Visit, TIntermSwizzle *)
{
if (!mFirstCaseFound)
mStatementBeforeCase = true;
mLastStatementWasCase = false;
return true;
}
bool ValidateSwitch::visitIfElse(Visit visit, TIntermIfElse *)
{
if (visit == PreVisit)
......
......@@ -205,7 +205,7 @@ extern void yyerror(YYLTYPE* yylloc, TParseContext* context, void *scanner, cons
%type <interm.intermNode> condition conditionopt
%type <interm.intermBlock> translation_unit
%type <interm.intermNode> function_definition statement simple_statement
%type <interm.intermBlock> statement_list compound_statement compound_statement_no_new_scope
%type <interm.intermBlock> statement_list compound_statement_with_scope compound_statement_no_new_scope
%type <interm.intermNode> declaration_statement selection_statement expression_statement
%type <interm.intermNode> declaration external_declaration
%type <interm.intermNode> for_init_statement
......@@ -1232,8 +1232,8 @@ declaration_statement
;
statement
: compound_statement { $$ = $1; }
| simple_statement { $$ = $1; }
: compound_statement_with_scope { $$ = $1; }
| simple_statement { $$ = $1; }
;
// Grammar Note: Labeled statements for SWITCH only; 'goto' is not supported.
......@@ -1248,8 +1248,11 @@ simple_statement
| jump_statement { $$ = $1; }
;
compound_statement
: LEFT_BRACE RIGHT_BRACE { $$ = 0; }
compound_statement_with_scope
: LEFT_BRACE RIGHT_BRACE {
$$ = new TIntermBlock();
$$->setLine(@$);
}
| LEFT_BRACE { context->symbolTable.push(); } statement_list { context->symbolTable.pop(); } RIGHT_BRACE {
$3->setLine(@$);
$$ = $3;
......@@ -1267,9 +1270,10 @@ statement_with_scope
;
compound_statement_no_new_scope
// Statement that doesn't create a new scope, for selection_statement, iteration_statement
// Statement that doesn't create a new scope for iteration_statement, function definition (scope is created for parameters)
: LEFT_BRACE RIGHT_BRACE {
$$ = nullptr;
$$ = new TIntermBlock();
$$->setLine(@$);
}
| LEFT_BRACE statement_list RIGHT_BRACE {
$2->setLine(@$);
......@@ -1310,8 +1314,10 @@ selection_rest_statement
}
;
// Note that we've diverged from the spec grammar here a bit for the sake of simplicity.
// We're reusing compound_statement_with_scope instead of having separate rules for switch.
switch_statement
: SWITCH LEFT_PAREN expression RIGHT_PAREN { context->incrSwitchNestingLevel(); } compound_statement {
: SWITCH LEFT_PAREN expression RIGHT_PAREN { context->incrSwitchNestingLevel(); } compound_statement_with_scope {
$$ = context->addSwitch($3, $6, @1);
context->decrSwitchNestingLevel();
}
......
......@@ -766,11 +766,11 @@ static const yytype_uint16 yyrline[] =
1157, 1160, 1163, 1166, 1169, 1172, 1180, 1180, 1183, 1183,
1189, 1192, 1198, 1201, 1208, 1212, 1218, 1221, 1227, 1231,
1235, 1236, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1252,
1253, 1253, 1253, 1260, 1261, 1265, 1265, 1266, 1266, 1271,
1274, 1281, 1285, 1292, 1293, 1297, 1303, 1307, 1314, 1314,
1321, 1324, 1330, 1334, 1340, 1340, 1345, 1345, 1349, 1349,
1357, 1360, 1366, 1369, 1375, 1379, 1386, 1389, 1392, 1395,
1398, 1406, 1412, 1418, 1421, 1427, 1427
1256, 1256, 1256, 1263, 1264, 1268, 1268, 1269, 1269, 1274,
1278, 1285, 1289, 1296, 1297, 1301, 1307, 1311, 1320, 1320,
1327, 1330, 1336, 1340, 1346, 1346, 1351, 1351, 1355, 1355,
1363, 1366, 1372, 1375, 1381, 1385, 1392, 1395, 1398, 1401,
1404, 1412, 1418, 1424, 1427, 1433, 1433
};
#endif
......@@ -832,7 +832,7 @@ static const char *const yytname[] =
"$@1", "$@2", "struct_declaration_list", "struct_declaration",
"struct_declarator_list", "struct_declarator", "initializer",
"declaration_statement", "statement", "simple_statement",
"compound_statement", "$@3", "$@4", "statement_no_new_scope",
"compound_statement_with_scope", "$@3", "$@4", "statement_no_new_scope",
"statement_with_scope", "$@5", "$@6", "compound_statement_no_new_scope",
"statement_list", "expression_statement", "selection_statement",
"selection_rest_statement", "switch_statement", "$@7", "case_label",
......@@ -4559,7 +4559,10 @@ yyreduce:
case 249:
{ (yyval.interm.intermBlock) = 0; }
{
(yyval.interm.intermBlock) = new TIntermBlock();
(yyval.interm.intermBlock)->setLine((yyloc));
}
break;
......@@ -4623,7 +4626,8 @@ yyreduce:
case 259:
{
(yyval.interm.intermBlock) = nullptr;
(yyval.interm.intermBlock) = new TIntermBlock();
(yyval.interm.intermBlock)->setLine((yyloc));
}
break;
......
......@@ -5011,4 +5011,55 @@ TEST_F(VertexShaderValidationTest, LocationConflictsOnStructElement)
{
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
\ No newline at end of file
}
// Test that a block can follow the final case in a switch statement.
// GLSL ES 3.00.5 section 6 and the grammar suggest that an empty block is a statement.
TEST_F(FragmentShaderValidationTest, SwitchFinalCaseHasEmptyBlock)
{
const std::string &shaderString =
R"(#version 300 es
precision mediump float;
uniform int i;
void main()
{
switch (i)
{
case 0:
break;
default:
{}
}
})";
if (!compile(shaderString))
{
FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
}
}
// Test that an empty declaration can follow the final case in a switch statement.
TEST_F(FragmentShaderValidationTest, SwitchFinalCaseHasEmptyDeclaration)
{
const std::string &shaderString =
R"(#version 300 es
precision mediump float;
uniform int i;
void main()
{
switch (i)
{
case 0:
break;
default:
float;
}
})";
if (!compile(shaderString))
{
FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
}
}
......@@ -3538,6 +3538,48 @@ TEST_P(GLSLTest_ES3, SwitchFallThroughCodeDuplication)
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}
// Test that a switch statement with an empty block inside as a final statement compiles.
TEST_P(GLSLTest_ES3, SwitchFinalCaseHasEmptyBlock)
{
const std::string &fragmentShader =
R"(#version 300 es
precision mediump float;
uniform int i;
void main()
{
switch (i)
{
case 0:
break;
default:
{}
}
})";
ANGLE_GL_PROGRAM(program, mSimpleVSSource, fragmentShader);
}
// Test that a switch statement with an empty declaration inside as a final statement compiles.
TEST_P(GLSLTest_ES3, SwitchFinalCaseHasEmptyDeclaration)
{
const std::string &fragmentShader =
R"(#version 300 es
precision mediump float;
uniform int i;
void main()
{
switch (i)
{
case 0:
break;
default:
float;
}
})";
ANGLE_GL_PROGRAM(program, mSimpleVSSource, fragmentShader);
}
// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
ANGLE_INSTANTIATE_TEST(GLSLTest,
ES2_D3D9(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment