Commit bb7e5a7c by Olli Etuaho Committed by Commit Bot

GLSL parser: Fix empty declaration qualifier checks

The shader validation now does the same checks for qualifier combinations regardless of if a declaration is empty, as is specified. Some of these checks used to be in singleDeclarationErrorCheck, and have now been moved to a new function declarationQualifierErrorCheck. The other parts of singleDeclarationErrorCheck are under a renamed nonEmptyDeclarationErrorCheck. The patch also contains another related cleanup: Unnecessary symbol nodes won't be created for empty declarations any more. BUG=angleproject:2020 TEST=angle_unittests Change-Id: I1c864a5e151c52703926d8c550450b2561bfcbb2 Reviewed-on: https://chromium-review.googlesource.com/493227 Commit-Queue: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 6ba99e3d
...@@ -96,7 +96,7 @@ TParseContext::TParseContext(TSymbolTable &symt, ...@@ -96,7 +96,7 @@ TParseContext::TParseContext(TSymbolTable &symt,
const ShBuiltInResources &resources) const ShBuiltInResources &resources)
: intermediate(), : intermediate(),
symbolTable(symt), symbolTable(symt),
mDeferredSingleDeclarationErrorCheck(false), mDeferredNonEmptyDeclarationErrorCheck(false),
mShaderType(type), mShaderType(type),
mShaderSpec(spec), mShaderSpec(spec),
mCompileOptions(options), mCompileOptions(options),
...@@ -1136,6 +1136,60 @@ bool TParseContext::checkCanUseExtension(const TSourceLoc &line, const TString & ...@@ -1136,6 +1136,60 @@ bool TParseContext::checkCanUseExtension(const TSourceLoc &line, const TString &
return true; return true;
} }
// ESSL 3.00.6 section 4.8 Empty Declarations: "The combinations of qualifiers that cause
// compile-time or link-time errors are the same whether or not the declaration is empty".
// This function implements all the checks that are done on qualifiers regardless of if the
// declaration is empty.
void TParseContext::declarationQualifierErrorCheck(const sh::TQualifier qualifier,
const sh::TLayoutQualifier &layoutQualifier,
const TSourceLoc &location)
{
if (qualifier == EvqShared && !layoutQualifier.isEmpty())
{
error(location, "Shared memory declarations cannot have layout specified", "layout");
}
if (layoutQualifier.matrixPacking != EmpUnspecified)
{
error(location, "layout qualifier only valid for interface blocks",
getMatrixPackingString(layoutQualifier.matrixPacking));
return;
}
if (layoutQualifier.blockStorage != EbsUnspecified)
{
error(location, "layout qualifier only valid for interface blocks",
getBlockStorageString(layoutQualifier.blockStorage));
return;
}
if (qualifier == EvqFragmentOut)
{
if (layoutQualifier.location != -1 && layoutQualifier.yuv == true)
{
error(location, "invalid layout qualifier combination", "yuv");
return;
}
}
else
{
checkYuvIsNotSpecified(location, layoutQualifier.yuv);
}
bool canHaveLocation = qualifier == EvqVertexIn || qualifier == EvqFragmentOut;
if (mShaderVersion >= 310 && qualifier == EvqUniform)
{
canHaveLocation = true;
// We're not checking whether the uniform location is in range here since that depends on
// the type of the variable.
// The type can only be fully determined for non-empty declarations.
}
if (!canHaveLocation)
{
checkLocationIsNotSpecified(location, layoutQualifier);
}
}
void TParseContext::emptyDeclarationErrorCheck(const TPublicType &publicType, void TParseContext::emptyDeclarationErrorCheck(const TPublicType &publicType,
const TSourceLoc &location) const TSourceLoc &location)
{ {
...@@ -1145,15 +1199,11 @@ void TParseContext::emptyDeclarationErrorCheck(const TPublicType &publicType, ...@@ -1145,15 +1199,11 @@ void TParseContext::emptyDeclarationErrorCheck(const TPublicType &publicType,
// error. It is assumed that this applies to empty declarations as well. // error. It is assumed that this applies to empty declarations as well.
error(location, "empty array declaration needs to specify a size", ""); error(location, "empty array declaration needs to specify a size", "");
} }
if (publicType.qualifier == EvqShared && !publicType.layoutQualifier.isEmpty())
{
error(location, "Shared memory declarations cannot have layout specified", "layout");
}
} }
// These checks are common for all declarations starting a declarator list, and declarators that // These checks are done for all declarations that are non-empty. They're done for non-empty
// follow an empty declaration. // declarations starting a declarator list, and declarators that follow an empty declaration.
void TParseContext::singleDeclarationErrorCheck(const TPublicType &publicType, void TParseContext::nonEmptyDeclarationErrorCheck(const TPublicType &publicType,
const TSourceLoc &identifierLocation) const TSourceLoc &identifierLocation)
{ {
switch (publicType.qualifier) switch (publicType.qualifier)
...@@ -1197,30 +1247,8 @@ void TParseContext::singleDeclarationErrorCheck(const TPublicType &publicType, ...@@ -1197,30 +1247,8 @@ void TParseContext::singleDeclarationErrorCheck(const TPublicType &publicType,
return; return;
} }
// check for layout qualifier issues
const TLayoutQualifier layoutQualifier = publicType.layoutQualifier;
if (layoutQualifier.matrixPacking != EmpUnspecified)
{
error(identifierLocation, "layout qualifier only valid for interface blocks",
getMatrixPackingString(layoutQualifier.matrixPacking));
return;
}
if (layoutQualifier.blockStorage != EbsUnspecified)
{
error(identifierLocation, "layout qualifier only valid for interface blocks",
getBlockStorageString(layoutQualifier.blockStorage));
return;
}
bool canHaveLocation =
publicType.qualifier == EvqVertexIn || publicType.qualifier == EvqFragmentOut;
if (mShaderVersion >= 310 && publicType.qualifier == EvqUniform) if (mShaderVersion >= 310 && publicType.qualifier == EvqUniform)
{ {
canHaveLocation = true;
// Valid uniform declarations can't be unsized arrays since uniforms can't be initialized. // Valid uniform declarations can't be unsized arrays since uniforms can't be initialized.
// But invalid shaders may still reach here with an unsized array declaration. // But invalid shaders may still reach here with an unsized array declaration.
if (!publicType.isUnsizedArray()) if (!publicType.isUnsizedArray())
...@@ -1230,23 +1258,9 @@ void TParseContext::singleDeclarationErrorCheck(const TPublicType &publicType, ...@@ -1230,23 +1258,9 @@ void TParseContext::singleDeclarationErrorCheck(const TPublicType &publicType,
publicType.layoutQualifier); publicType.layoutQualifier);
} }
} }
if (!canHaveLocation)
{
checkLocationIsNotSpecified(identifierLocation, publicType.layoutQualifier);
}
if (publicType.qualifier == EvqFragmentOut) // check for layout qualifier issues
{ const TLayoutQualifier layoutQualifier = publicType.layoutQualifier;
if (layoutQualifier.location != -1 && layoutQualifier.yuv == true)
{
error(identifierLocation, "invalid layout qualifier combination", "yuv");
return;
}
}
else
{
checkYuvIsNotSpecified(identifierLocation, layoutQualifier.yuv);
}
if (IsImage(publicType.getBasicType())) if (IsImage(publicType.getBasicType()))
{ {
...@@ -1880,7 +1894,7 @@ void TParseContext::checkInputOutputTypeIsValidES3(const TQualifier qualifier, ...@@ -1880,7 +1894,7 @@ void TParseContext::checkInputOutputTypeIsValidES3(const TQualifier qualifier,
{ {
error(qualifierLocation, "cannot be array", getQualifierString(qualifier)); error(qualifierLocation, "cannot be array", getQualifierString(qualifier));
} }
// Vertex inputs with a struct type are disallowed in singleDeclarationErrorCheck // Vertex inputs with a struct type are disallowed in nonEmptyDeclarationErrorCheck
return; return;
case EvqFragmentOut: case EvqFragmentOut:
// ESSL 3.00 section 4.3.6 // ESSL 3.00 section 4.3.6
...@@ -1888,7 +1902,7 @@ void TParseContext::checkInputOutputTypeIsValidES3(const TQualifier qualifier, ...@@ -1888,7 +1902,7 @@ void TParseContext::checkInputOutputTypeIsValidES3(const TQualifier qualifier,
{ {
error(qualifierLocation, "cannot be matrix", getQualifierString(qualifier)); error(qualifierLocation, "cannot be matrix", getQualifierString(qualifier));
} }
// Fragment outputs with a struct type are disallowed in singleDeclarationErrorCheck // Fragment outputs with a struct type are disallowed in nonEmptyDeclarationErrorCheck
return; return;
default: default:
break; break;
...@@ -2007,38 +2021,45 @@ TIntermDeclaration *TParseContext::parseSingleDeclaration( ...@@ -2007,38 +2021,45 @@ TIntermDeclaration *TParseContext::parseSingleDeclaration(
} }
} }
TIntermSymbol *symbol = intermediate.addSymbol(0, identifier, type, identifierOrTypeLocation); declarationQualifierErrorCheck(publicType.qualifier, publicType.layoutQualifier,
identifierOrTypeLocation);
bool emptyDeclaration = (identifier == ""); bool emptyDeclaration = (identifier == "");
mDeferredNonEmptyDeclarationErrorCheck = emptyDeclaration;
mDeferredSingleDeclarationErrorCheck = emptyDeclaration; TIntermSymbol *symbol = nullptr;
TIntermDeclaration *declaration = new TIntermDeclaration();
declaration->setLine(identifierOrTypeLocation);
if (emptyDeclaration) if (emptyDeclaration)
{ {
emptyDeclarationErrorCheck(publicType, identifierOrTypeLocation); emptyDeclarationErrorCheck(publicType, identifierOrTypeLocation);
// In most cases we don't need to create a symbol node for an empty declaration.
// But if the empty declaration is declaring a struct type, the symbol node will store that.
if (type.getBasicType() == EbtStruct)
{
symbol = intermediate.addSymbol(0, "", type, identifierOrTypeLocation);
}
} }
else else
{ {
singleDeclarationErrorCheck(publicType, identifierOrTypeLocation); nonEmptyDeclarationErrorCheck(publicType, identifierOrTypeLocation);
checkCanBeDeclaredWithoutInitializer(identifierOrTypeLocation, identifier, &publicType); checkCanBeDeclaredWithoutInitializer(identifierOrTypeLocation, identifier, &publicType);
TVariable *variable = nullptr; TVariable *variable = nullptr;
declareVariable(identifierOrTypeLocation, identifier, type, &variable); declareVariable(identifierOrTypeLocation, identifier, type, &variable);
if (variable && symbol) if (variable)
{ {
symbol->setId(variable->getUniqueId()); symbol = intermediate.addSymbol(variable->getUniqueId(), identifier, type,
identifierOrTypeLocation);
} }
} }
// We append the symbol even if the declaration is empty, mainly because of struct declarations TIntermDeclaration *declaration = new TIntermDeclaration();
// that may just declare a type. declaration->setLine(identifierOrTypeLocation);
if (symbol)
{
declaration->appendDeclarator(symbol); declaration->appendDeclarator(symbol);
}
return declaration; return declaration;
} }
...@@ -2048,9 +2069,12 @@ TIntermDeclaration *TParseContext::parseSingleArrayDeclaration(TPublicType &publ ...@@ -2048,9 +2069,12 @@ TIntermDeclaration *TParseContext::parseSingleArrayDeclaration(TPublicType &publ
const TSourceLoc &indexLocation, const TSourceLoc &indexLocation,
TIntermTyped *indexExpression) TIntermTyped *indexExpression)
{ {
mDeferredSingleDeclarationErrorCheck = false; mDeferredNonEmptyDeclarationErrorCheck = false;
declarationQualifierErrorCheck(publicType.qualifier, publicType.layoutQualifier,
identifierLocation);
singleDeclarationErrorCheck(publicType, identifierLocation); nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
checkCanBeDeclaredWithoutInitializer(identifierLocation, identifier, &publicType); checkCanBeDeclaredWithoutInitializer(identifierLocation, identifier, &publicType);
...@@ -2085,9 +2109,12 @@ TIntermDeclaration *TParseContext::parseSingleInitDeclaration(const TPublicType ...@@ -2085,9 +2109,12 @@ TIntermDeclaration *TParseContext::parseSingleInitDeclaration(const TPublicType
const TSourceLoc &initLocation, const TSourceLoc &initLocation,
TIntermTyped *initializer) TIntermTyped *initializer)
{ {
mDeferredSingleDeclarationErrorCheck = false; mDeferredNonEmptyDeclarationErrorCheck = false;
singleDeclarationErrorCheck(publicType, identifierLocation); declarationQualifierErrorCheck(publicType.qualifier, publicType.layoutQualifier,
identifierLocation);
nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
TIntermDeclaration *declaration = new TIntermDeclaration(); TIntermDeclaration *declaration = new TIntermDeclaration();
declaration->setLine(identifierLocation); declaration->setLine(identifierLocation);
...@@ -2112,9 +2139,12 @@ TIntermDeclaration *TParseContext::parseSingleArrayInitDeclaration( ...@@ -2112,9 +2139,12 @@ TIntermDeclaration *TParseContext::parseSingleArrayInitDeclaration(
const TSourceLoc &initLocation, const TSourceLoc &initLocation,
TIntermTyped *initializer) TIntermTyped *initializer)
{ {
mDeferredSingleDeclarationErrorCheck = false; mDeferredNonEmptyDeclarationErrorCheck = false;
declarationQualifierErrorCheck(publicType.qualifier, publicType.layoutQualifier,
identifierLocation);
singleDeclarationErrorCheck(publicType, identifierLocation); nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
checkIsValidTypeAndQualifierForArray(indexLocation, publicType); checkIsValidTypeAndQualifierForArray(indexLocation, publicType);
...@@ -2207,10 +2237,10 @@ void TParseContext::parseDeclarator(TPublicType &publicType, ...@@ -2207,10 +2237,10 @@ void TParseContext::parseDeclarator(TPublicType &publicType,
{ {
// If the declaration starting this declarator list was empty (example: int,), some checks were // If the declaration starting this declarator list was empty (example: int,), some checks were
// not performed. // not performed.
if (mDeferredSingleDeclarationErrorCheck) if (mDeferredNonEmptyDeclarationErrorCheck)
{ {
singleDeclarationErrorCheck(publicType, identifierLocation); nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
mDeferredSingleDeclarationErrorCheck = false; mDeferredNonEmptyDeclarationErrorCheck = false;
} }
checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType); checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType);
...@@ -2238,10 +2268,10 @@ void TParseContext::parseArrayDeclarator(TPublicType &publicType, ...@@ -2238,10 +2268,10 @@ void TParseContext::parseArrayDeclarator(TPublicType &publicType,
{ {
// If the declaration starting this declarator list was empty (example: int,), some checks were // If the declaration starting this declarator list was empty (example: int,), some checks were
// not performed. // not performed.
if (mDeferredSingleDeclarationErrorCheck) if (mDeferredNonEmptyDeclarationErrorCheck)
{ {
singleDeclarationErrorCheck(publicType, identifierLocation); nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
mDeferredSingleDeclarationErrorCheck = false; mDeferredNonEmptyDeclarationErrorCheck = false;
} }
checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType); checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType);
...@@ -2275,10 +2305,10 @@ void TParseContext::parseInitDeclarator(const TPublicType &publicType, ...@@ -2275,10 +2305,10 @@ void TParseContext::parseInitDeclarator(const TPublicType &publicType,
{ {
// If the declaration starting this declarator list was empty (example: int,), some checks were // If the declaration starting this declarator list was empty (example: int,), some checks were
// not performed. // not performed.
if (mDeferredSingleDeclarationErrorCheck) if (mDeferredNonEmptyDeclarationErrorCheck)
{ {
singleDeclarationErrorCheck(publicType, identifierLocation); nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
mDeferredSingleDeclarationErrorCheck = false; mDeferredNonEmptyDeclarationErrorCheck = false;
} }
checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType); checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType);
...@@ -2307,10 +2337,10 @@ void TParseContext::parseArrayInitDeclarator(const TPublicType &publicType, ...@@ -2307,10 +2337,10 @@ void TParseContext::parseArrayInitDeclarator(const TPublicType &publicType,
{ {
// If the declaration starting this declarator list was empty (example: int,), some checks were // If the declaration starting this declarator list was empty (example: int,), some checks were
// not performed. // not performed.
if (mDeferredSingleDeclarationErrorCheck) if (mDeferredNonEmptyDeclarationErrorCheck)
{ {
singleDeclarationErrorCheck(publicType, identifierLocation); nonEmptyDeclarationErrorCheck(publicType, identifierLocation);
mDeferredSingleDeclarationErrorCheck = false; mDeferredNonEmptyDeclarationErrorCheck = false;
} }
checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType); checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType);
......
...@@ -136,9 +136,17 @@ class TParseContext : angle::NonCopyable ...@@ -136,9 +136,17 @@ class TParseContext : angle::NonCopyable
const TTypeQualifierBuilder &typeQualifierBuilder, const TTypeQualifierBuilder &typeQualifierBuilder,
TType *type); TType *type);
bool checkCanUseExtension(const TSourceLoc &line, const TString &extension); bool checkCanUseExtension(const TSourceLoc &line, const TString &extension);
void singleDeclarationErrorCheck(const TPublicType &publicType,
// Done for all declarations, whether empty or not.
void declarationQualifierErrorCheck(const sh::TQualifier qualifier,
const sh::TLayoutQualifier &layoutQualifier,
const TSourceLoc &location);
// Done for the first non-empty declarator in a declaration.
void nonEmptyDeclarationErrorCheck(const TPublicType &publicType,
const TSourceLoc &identifierLocation); const TSourceLoc &identifierLocation);
// Done only for empty declarations.
void emptyDeclarationErrorCheck(const TPublicType &publicType, const TSourceLoc &location); void emptyDeclarationErrorCheck(const TPublicType &publicType, const TSourceLoc &location);
void checkLayoutQualifierSupported(const TSourceLoc &location, void checkLayoutQualifierSupported(const TSourceLoc &location,
const TString &layoutQualifierName, const TString &layoutQualifierName,
int versionRequired); int versionRequired);
...@@ -433,8 +441,10 @@ class TParseContext : angle::NonCopyable ...@@ -433,8 +441,10 @@ class TParseContext : angle::NonCopyable
const TSourceLoc &location, const TSourceLoc &location,
bool insertParametersToSymbolTable); bool insertParametersToSymbolTable);
// Set to true when the last/current declarator list was started with an empty declaration. // Set to true when the last/current declarator list was started with an empty declaration. The
bool mDeferredSingleDeclarationErrorCheck; // non-empty declaration error check will need to be performed if the empty declaration is
// followed by a declarator.
bool mDeferredNonEmptyDeclarationErrorCheck;
sh::GLenum mShaderType; // vertex or fragment language (future: pack or unpack) sh::GLenum mShaderType; // vertex or fragment language (future: pack or unpack)
ShShaderSpec mShaderSpec; // The language specification compiler conforms to - GLES2 or WebGL. ShShaderSpec mShaderSpec; // The language specification compiler conforms to - GLES2 or WebGL.
......
...@@ -2345,6 +2345,22 @@ TEST_F(FragmentShaderValidationTest, InvariantNonOuput) ...@@ -2345,6 +2345,22 @@ TEST_F(FragmentShaderValidationTest, InvariantNonOuput)
} }
} }
// Invariant cannot be used with a non-output variable in ESSL3.
// ESSL 3.00.6 section 4.8: This applies even if the declaration is empty.
TEST_F(FragmentShaderValidationTest, InvariantNonOuputEmptyDeclaration)
{
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"invariant in float;\n"
"void main() {}\n";
if (compile(shaderString))
{
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
// Invariant declaration should follow the following format "invariant <out variable name>". // Invariant declaration should follow the following format "invariant <out variable name>".
// Test having an incorrect qualifier in the invariant declaration. // Test having an incorrect qualifier in the invariant declaration.
TEST_F(FragmentShaderValidationTest, InvariantDeclarationWithStorageQualifier) TEST_F(FragmentShaderValidationTest, InvariantDeclarationWithStorageQualifier)
...@@ -3729,3 +3745,20 @@ TEST_F(FragmentShaderValidationTest, ConstructStructContainingVoidArray) ...@@ -3729,3 +3745,20 @@ TEST_F(FragmentShaderValidationTest, ConstructStructContainingVoidArray)
FAIL() << "Shader compilation succeeded, expecting failure " << mInfoLog; FAIL() << "Shader compilation succeeded, expecting failure " << mInfoLog;
} }
} }
// Uniforms can't have location in ESSL 3.00.
// Test this with an empty declaration (ESSL 3.00.6 section 4.8: The combinations of qualifiers that
// cause compile-time or link-time errors are the same whether or not the declaration is empty).
TEST_F(FragmentShaderValidationTest, UniformLocationEmptyDeclaration)
{
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"layout(location=0) uniform float;\n"
"void main() {}\n";
if (compile(shaderString))
{
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
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