diff options
Diffstat (limited to 'gfx/angle/checkout/src/compiler/translator/ValidateAST.cpp')
-rw-r--r-- | gfx/angle/checkout/src/compiler/translator/ValidateAST.cpp | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/compiler/translator/ValidateAST.cpp b/gfx/angle/checkout/src/compiler/translator/ValidateAST.cpp new file mode 100644 index 0000000000..e9503ac483 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/ValidateAST.cpp @@ -0,0 +1,597 @@ +// +// Copyright 2019 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 "compiler/translator/ValidateAST.h" + +#include "compiler/translator/Diagnostics.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/SpecializationConstant.h" + +namespace sh +{ + +namespace +{ + +class ValidateAST : public TIntermTraverser +{ + public: + static bool validate(TIntermNode *root, + TDiagnostics *diagnostics, + const ValidateASTOptions &options); + + void visitSymbol(TIntermSymbol *node) override; + void visitConstantUnion(TIntermConstantUnion *node) override; + bool visitSwizzle(Visit visit, TIntermSwizzle *node) override; + bool visitBinary(Visit visit, TIntermBinary *node) override; + bool visitUnary(Visit visit, TIntermUnary *node) override; + bool visitTernary(Visit visit, TIntermTernary *node) override; + bool visitIfElse(Visit visit, TIntermIfElse *node) override; + bool visitSwitch(Visit visit, TIntermSwitch *node) override; + bool visitCase(Visit visit, TIntermCase *node) override; + void visitFunctionPrototype(TIntermFunctionPrototype *node) override; + bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitBlock(Visit visit, TIntermBlock *node) override; + bool visitGlobalQualifierDeclaration(Visit visit, + TIntermGlobalQualifierDeclaration *node) override; + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override; + bool visitLoop(Visit visit, TIntermLoop *node) override; + bool visitBranch(Visit visit, TIntermBranch *node) override; + void visitPreprocessorDirective(TIntermPreprocessorDirective *node) override; + + private: + ValidateAST(TIntermNode *root, TDiagnostics *diagnostics, const ValidateASTOptions &options); + + // Visit as a generic node + void visitNode(Visit visit, TIntermNode *node); + // Visit a structure or interface block, and recursively visit its fields of structure type. + void visitStructOrInterfaceBlockDeclaration(const TType &type, const TSourceLoc &location); + void visitStructInDeclarationUsage(const TType &type, const TSourceLoc &location); + + void scope(Visit visit); + bool isVariableDeclared(const TVariable *variable); + bool variableNeedsDeclaration(const TVariable *variable); + const TFieldListCollection *getStructOrInterfaceBlock(const TType &type, + ImmutableString *typeNameOut); + + void expectNonNullChildren(Visit visit, TIntermNode *node, size_t least_count); + + bool validateInternal(); + + ValidateASTOptions mOptions; + TDiagnostics *mDiagnostics; + + // For validateSingleParent: + std::map<TIntermNode *, TIntermNode *> mParent; + bool mSingleParentFailed = false; + + // For validateVariableReferences: + std::vector<std::set<const TVariable *>> mDeclaredVariables; + std::set<const TInterfaceBlock *> mNamelessInterfaceBlocks; + bool mVariableReferencesFailed = false; + + // For validateNullNodes: + bool mNullNodesFailed = false; + + // For validateStructUsage: + std::vector<std::map<ImmutableString, const TFieldListCollection *>> mStructsAndBlocksByName; + bool mStructUsageFailed = false; + + // For validateMultiDeclarations: + bool mMultiDeclarationsFailed = false; +}; + +bool ValidateAST::validate(TIntermNode *root, + TDiagnostics *diagnostics, + const ValidateASTOptions &options) +{ + ValidateAST validate(root, diagnostics, options); + root->traverse(&validate); + return validate.validateInternal(); +} + +ValidateAST::ValidateAST(TIntermNode *root, + TDiagnostics *diagnostics, + const ValidateASTOptions &options) + : TIntermTraverser(true, false, true, nullptr), mOptions(options), mDiagnostics(diagnostics) +{ + bool isTreeRoot = root->getAsBlock() && root->getAsBlock()->isTreeRoot(); + + // Some validations are not applicable unless run on the entire tree. + if (!isTreeRoot) + { + mOptions.validateVariableReferences = false; + } + + if (mOptions.validateSingleParent) + { + mParent[root] = nullptr; + } +} + +void ValidateAST::visitNode(Visit visit, TIntermNode *node) +{ + if (visit == PreVisit && mOptions.validateSingleParent) + { + size_t childCount = node->getChildCount(); + for (size_t i = 0; i < childCount; ++i) + { + TIntermNode *child = node->getChildNode(i); + if (mParent.find(child) != mParent.end()) + { + // If child is visited twice but through the same parent, the problem is in one of + // the ancestors. + if (mParent[child] != node) + { + mDiagnostics->error(node->getLine(), "Found child with two parents", + "<validateSingleParent>"); + mSingleParentFailed = true; + } + } + + mParent[child] = node; + } + } +} + +void ValidateAST::visitStructOrInterfaceBlockDeclaration(const TType &type, + const TSourceLoc &location) +{ + if (type.getStruct() == nullptr && type.getInterfaceBlock() == nullptr) + { + return; + } + + // Make sure the structure or interface block is not doubly defined. + ImmutableString typeName(""); + const TFieldListCollection *structOrBlock = getStructOrInterfaceBlock(type, &typeName); + + if (structOrBlock) + { + ASSERT(!typeName.empty()); + + if (mStructsAndBlocksByName.back().find(typeName) != mStructsAndBlocksByName.back().end()) + { + mDiagnostics->error(location, + "Found redeclaration of struct or interface block with the same " + "name in the same scope <validateStructUsage>", + typeName.data()); + mStructUsageFailed = true; + } + else + { + // First encounter. + mStructsAndBlocksByName.back()[typeName] = structOrBlock; + } + } + + // Recurse the fields of the structure or interface block and check members of structure type. + // Note that structOrBlock was previously only set for named structures, so make sure nameless + // structs are also recursed. + if (structOrBlock == nullptr) + { + structOrBlock = type.getStruct(); + } + ASSERT(structOrBlock != nullptr); + + for (const TField *field : structOrBlock->fields()) + { + visitStructInDeclarationUsage(*field->type(), field->line()); + } +} + +void ValidateAST::visitStructInDeclarationUsage(const TType &type, const TSourceLoc &location) +{ + if (type.getStruct() == nullptr) + { + return; + } + + // Make sure the structure being referenced has the same pointer as the closest (in scope) + // definition. + const TStructure *structure = type.getStruct(); + const ImmutableString &typeName = structure->name(); + + bool foundDeclaration = false; + for (size_t scopeIndex = mStructsAndBlocksByName.size(); scopeIndex > 0; --scopeIndex) + { + const std::map<ImmutableString, const TFieldListCollection *> &scopeDecls = + mStructsAndBlocksByName[scopeIndex - 1]; + + auto iter = scopeDecls.find(typeName); + if (iter != scopeDecls.end()) + { + foundDeclaration = true; + + if (iter->second != structure) + { + mDiagnostics->error(location, + "Found reference to struct or interface block with doubly " + "created type <validateStructUsage>", + typeName.data()); + mStructUsageFailed = true; + } + } + } + + if (!foundDeclaration) + { + mDiagnostics->error(location, + "Found reference to struct or interface block with no declaration " + "<validateStructUsage>", + typeName.data()); + mStructUsageFailed = true; + } +} + +void ValidateAST::scope(Visit visit) +{ + if (mOptions.validateVariableReferences) + { + if (visit == PreVisit) + { + mDeclaredVariables.push_back({}); + } + else if (visit == PostVisit) + { + mDeclaredVariables.pop_back(); + } + } + + if (mOptions.validateStructUsage) + { + if (visit == PreVisit) + { + mStructsAndBlocksByName.push_back({}); + } + else if (visit == PostVisit) + { + mStructsAndBlocksByName.pop_back(); + } + } +} + +bool ValidateAST::isVariableDeclared(const TVariable *variable) +{ + ASSERT(mOptions.validateVariableReferences); + + for (const std::set<const TVariable *> &scopeVariables : mDeclaredVariables) + { + if (scopeVariables.count(variable) > 0) + { + return true; + } + } + + return false; +} + +bool ValidateAST::variableNeedsDeclaration(const TVariable *variable) +{ + // Don't expect declaration for built-in variables. + if (variable->name().beginsWith("gl_")) + { + return false; + } + + // Additionally, don't expect declaration for Vulkan specialization constants. There is no + // representation for them in the AST. + if (variable->symbolType() == SymbolType::AngleInternal && + SpecConst::IsSpecConstName(variable->name())) + { + return false; + } + + return true; +} + +const TFieldListCollection *ValidateAST::getStructOrInterfaceBlock(const TType &type, + ImmutableString *typeNameOut) +{ + const TStructure *structure = type.getStruct(); + const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock(); + + ASSERT(structure != nullptr || interfaceBlock != nullptr); + + // Make sure the structure or interface block is not doubly defined. + const TFieldListCollection *structOrBlock = nullptr; + if (structure != nullptr && structure->symbolType() != SymbolType::Empty) + { + structOrBlock = structure; + *typeNameOut = structure->name(); + } + else if (interfaceBlock != nullptr) + { + structOrBlock = interfaceBlock; + *typeNameOut = interfaceBlock->name(); + } + + return structOrBlock; +} + +void ValidateAST::expectNonNullChildren(Visit visit, TIntermNode *node, size_t least_count) +{ + if (visit == PreVisit && mOptions.validateNullNodes) + { + size_t childCount = node->getChildCount(); + if (childCount < least_count) + { + mDiagnostics->error(node->getLine(), "Too few children", "<validateNullNodes>"); + mNullNodesFailed = true; + } + + for (size_t i = 0; i < childCount; ++i) + { + if (node->getChildNode(i) == nullptr) + { + mDiagnostics->error(node->getLine(), "Found nullptr child", "<validateNullNodes>"); + mNullNodesFailed = true; + } + } + } +} + +void ValidateAST::visitSymbol(TIntermSymbol *node) +{ + visitNode(PreVisit, node); + + const TVariable *variable = &node->variable(); + const TType &type = node->getType(); + + if (mOptions.validateVariableReferences && variableNeedsDeclaration(variable)) + { + // If it's a reference to a field of a nameless interface block, match it by index and name. + if (type.getInterfaceBlock() && !type.isInterfaceBlock()) + { + const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock(); + const TFieldList &fieldList = interfaceBlock->fields(); + const size_t fieldIndex = type.getInterfaceBlockFieldIndex(); + + if (mNamelessInterfaceBlocks.count(interfaceBlock) == 0) + { + mDiagnostics->error(node->getLine(), + "Found reference to undeclared or inconsistenly redeclared " + "nameless interface block <validateVariableReferences>", + node->getName().data()); + mVariableReferencesFailed = true; + } + else if (fieldIndex >= fieldList.size() || + node->getName() != fieldList[fieldIndex]->name()) + { + mDiagnostics->error(node->getLine(), + "Found reference to inconsistenly redeclared nameless " + "interface block field <validateVariableReferences>", + node->getName().data()); + mVariableReferencesFailed = true; + } + } + else + { + const bool isStructDeclaration = + type.isStructSpecifier() && variable->symbolType() == SymbolType::Empty; + + if (!isStructDeclaration && !isVariableDeclared(variable)) + { + mDiagnostics->error(node->getLine(), + "Found reference to undeclared or inconsistently redeclared " + "variable <validateVariableReferences>", + node->getName().data()); + mVariableReferencesFailed = true; + } + } + } +} + +void ValidateAST::visitConstantUnion(TIntermConstantUnion *node) +{ + visitNode(PreVisit, node); +} + +bool ValidateAST::visitSwizzle(Visit visit, TIntermSwizzle *node) +{ + visitNode(visit, node); + return true; +} + +bool ValidateAST::visitBinary(Visit visit, TIntermBinary *node) +{ + visitNode(visit, node); + return true; +} + +bool ValidateAST::visitUnary(Visit visit, TIntermUnary *node) +{ + visitNode(visit, node); + return true; +} + +bool ValidateAST::visitTernary(Visit visit, TIntermTernary *node) +{ + visitNode(visit, node); + return true; +} + +bool ValidateAST::visitIfElse(Visit visit, TIntermIfElse *node) +{ + visitNode(visit, node); + return true; +} + +bool ValidateAST::visitSwitch(Visit visit, TIntermSwitch *node) +{ + visitNode(visit, node); + return true; +} + +bool ValidateAST::visitCase(Visit visit, TIntermCase *node) +{ + visitNode(visit, node); + return true; +} + +void ValidateAST::visitFunctionPrototype(TIntermFunctionPrototype *node) +{ + visitNode(PreVisit, node); +} + +bool ValidateAST::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) +{ + visitNode(visit, node); + scope(visit); + + if (mOptions.validateVariableReferences && visit == PreVisit) + { + const TFunction *function = node->getFunction(); + + size_t paramCount = function->getParamCount(); + for (size_t paramIndex = 0; paramIndex < paramCount; ++paramIndex) + { + const TVariable *variable = function->getParam(paramIndex); + + if (isVariableDeclared(variable)) + { + mDiagnostics->error(node->getLine(), + "Found two declarations of the same function argument " + "<validateVariableReferences>", + variable->name().data()); + mVariableReferencesFailed = true; + break; + } + + mDeclaredVariables.back().insert(variable); + } + } + + return true; +} + +bool ValidateAST::visitAggregate(Visit visit, TIntermAggregate *node) +{ + visitNode(visit, node); + expectNonNullChildren(visit, node, 0); + return true; +} + +bool ValidateAST::visitBlock(Visit visit, TIntermBlock *node) +{ + visitNode(visit, node); + scope(visit); + expectNonNullChildren(visit, node, 0); + return true; +} + +bool ValidateAST::visitGlobalQualifierDeclaration(Visit visit, + TIntermGlobalQualifierDeclaration *node) +{ + visitNode(visit, node); + return true; +} + +bool ValidateAST::visitDeclaration(Visit visit, TIntermDeclaration *node) +{ + visitNode(visit, node); + expectNonNullChildren(visit, node, 0); + + const TIntermSequence &sequence = *(node->getSequence()); + + if (mOptions.validateMultiDeclarations && sequence.size() > 1) + { + mMultiDeclarationsFailed = true; + } + + if (visit == PreVisit) + { + bool validateStructUsage = mOptions.validateStructUsage; + + for (TIntermNode *instance : sequence) + { + TIntermSymbol *symbol = instance->getAsSymbolNode(); + if (symbol == nullptr) + { + TIntermBinary *init = instance->getAsBinaryNode(); + ASSERT(init && init->getOp() == EOpInitialize); + symbol = init->getLeft()->getAsSymbolNode(); + } + ASSERT(symbol); + + const TVariable *variable = &symbol->variable(); + + if (mOptions.validateVariableReferences) + { + if (isVariableDeclared(variable)) + { + mDiagnostics->error( + node->getLine(), + "Found two declarations of the same variable <validateVariableReferences>", + variable->name().data()); + mVariableReferencesFailed = true; + break; + } + + mDeclaredVariables.back().insert(variable); + + const TInterfaceBlock *interfaceBlock = variable->getType().getInterfaceBlock(); + + if (variable->symbolType() == SymbolType::Empty && interfaceBlock != nullptr) + { + // Nameless interface blocks can only be declared at the top level. Their + // fields are matched by field index, and then verified to match by name. + // Conflict in names should have already generated a compile error. + ASSERT(mDeclaredVariables.size() == 1); + ASSERT(mNamelessInterfaceBlocks.count(interfaceBlock) == 0); + + mNamelessInterfaceBlocks.insert(interfaceBlock); + } + } + + if (validateStructUsage) + { + // Only declare the struct once. + validateStructUsage = false; + + const TType &type = variable->getType(); + if (type.isStructSpecifier() || type.isInterfaceBlock()) + visitStructOrInterfaceBlockDeclaration(type, node->getLine()); + } + } + } + + return true; +} + +bool ValidateAST::visitLoop(Visit visit, TIntermLoop *node) +{ + visitNode(visit, node); + return true; +} + +bool ValidateAST::visitBranch(Visit visit, TIntermBranch *node) +{ + visitNode(visit, node); + return true; +} + +void ValidateAST::visitPreprocessorDirective(TIntermPreprocessorDirective *node) +{ + visitNode(PreVisit, node); +} + +bool ValidateAST::validateInternal() +{ + return !mSingleParentFailed && !mVariableReferencesFailed && !mNullNodesFailed && + !mStructUsageFailed && !mMultiDeclarationsFailed; +} + +} // anonymous namespace + +bool ValidateAST(TIntermNode *root, TDiagnostics *diagnostics, const ValidateASTOptions &options) +{ + return ValidateAST::validate(root, diagnostics, options); +} + +} // namespace sh |