Commit 58eabfd7 by Shahbaz Youssefi Committed by Commit Bot

Translator: Validate consistent variable references

Some transformations change variable declarations. This validation ensures that all references to said variables are appropriately replaced as well. Bug: angleproject:2733 Change-Id: I6c2873968eeed4cba66e70069f84eb69a1f77074 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2818140 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com>
parent 79402fbd
...@@ -547,6 +547,12 @@ bool TCompiler::validateAST(TIntermNode *root) ...@@ -547,6 +547,12 @@ bool TCompiler::validateAST(TIntermNode *root)
{ {
bool valid = ValidateAST(root, &mDiagnostics, mValidateASTOptions); bool valid = ValidateAST(root, &mDiagnostics, mValidateASTOptions);
#if defined(ANGLE_ENABLE_ASSERTS)
if (!valid)
{
fprintf(stderr, "AST validation error(s):\n%s\n", mInfoSink.info.c_str());
}
#endif
// In debug, assert validation. In release, validation errors will be returned back to the // In debug, assert validation. In release, validation errors will be returned back to the
// application as internal ANGLE errors. // application as internal ANGLE errors.
ASSERT(valid); ASSERT(valid);
...@@ -561,6 +567,10 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root, ...@@ -561,6 +567,10 @@ bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
ShCompileOptions compileOptions) ShCompileOptions compileOptions)
{ {
mValidateASTOptions = {}; mValidateASTOptions = {};
if (!validateAST(root))
{
return false;
}
// Disallow expressions deemed too complex. // Disallow expressions deemed too complex.
if ((compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY) != 0 && !limitExpressionComplexity(root)) if ((compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY) != 0 && !limitExpressionComplexity(root))
......
...@@ -214,6 +214,9 @@ class TCompiler : public TShHandleBase ...@@ -214,6 +214,9 @@ class TCompiler : public TShHandleBase
std::vector<sh::InterfaceBlock> mUniformBlocks; std::vector<sh::InterfaceBlock> mUniformBlocks;
std::vector<sh::InterfaceBlock> mShaderStorageBlocks; std::vector<sh::InterfaceBlock> mShaderStorageBlocks;
// Track what should be validated given passes currently applied.
ValidateASTOptions mValidateASTOptions;
// Specialization constant usage bits // Specialization constant usage bits
SpecConstUsageBits mSpecConstUsageBits; SpecConstUsageBits mSpecConstUsageBits;
...@@ -330,9 +333,6 @@ class TCompiler : public TShHandleBase ...@@ -330,9 +333,6 @@ class TCompiler : public TShHandleBase
TPragma mPragma; TPragma mPragma;
// Track what should be validated given passes currently applied.
ValidateASTOptions mValidateASTOptions;
ShCompileOptions mCompileOptions; ShCompileOptions mCompileOptions;
}; };
......
...@@ -413,6 +413,9 @@ TIntermBlock::TIntermBlock(const TIntermBlock &node) ...@@ -413,6 +413,9 @@ TIntermBlock::TIntermBlock(const TIntermBlock &node)
{ {
mStatements.push_back(node->deepCopy()); mStatements.push_back(node->deepCopy());
} }
ASSERT(!node.mIsTreeRoot);
mIsTreeRoot = false;
} }
size_t TIntermBlock::getChildCount() const size_t TIntermBlock::getChildCount() const
......
...@@ -673,7 +673,7 @@ class TIntermAggregate : public TIntermOperator, public TIntermAggregateBase ...@@ -673,7 +673,7 @@ class TIntermAggregate : public TIntermOperator, public TIntermAggregateBase
class TIntermBlock : public TIntermNode, public TIntermAggregateBase class TIntermBlock : public TIntermNode, public TIntermAggregateBase
{ {
public: public:
TIntermBlock() : TIntermNode() {} TIntermBlock() : TIntermNode(), mIsTreeRoot(false) {}
~TIntermBlock() override {} ~TIntermBlock() override {}
TIntermBlock *getAsBlock() override { return this; } TIntermBlock *getAsBlock() override { return this; }
...@@ -694,9 +694,16 @@ class TIntermBlock : public TIntermNode, public TIntermAggregateBase ...@@ -694,9 +694,16 @@ class TIntermBlock : public TIntermNode, public TIntermAggregateBase
TIntermBlock *deepCopy() const override { return new TIntermBlock(*this); } TIntermBlock *deepCopy() const override { return new TIntermBlock(*this); }
void setIsTreeRoot() { mIsTreeRoot = true; }
bool isTreeRoot() const { return mIsTreeRoot; }
protected: protected:
TIntermSequence mStatements; TIntermSequence mStatements;
// Used to distinguish the tree root from the other blocks. When validating the AST, some
// validations are not applicable if not run on the entire tree and are thus skipped.
bool mIsTreeRoot;
private: private:
TIntermBlock(const TIntermBlock &); TIntermBlock(const TIntermBlock &);
}; };
......
...@@ -399,6 +399,12 @@ void TParseContext::outOfRangeError(bool isError, ...@@ -399,6 +399,12 @@ void TParseContext::outOfRangeError(bool isError,
} }
} }
void TParseContext::setTreeRoot(TIntermBlock *treeRoot)
{
mTreeRoot = treeRoot;
mTreeRoot->setIsTreeRoot();
}
// //
// Same error message for all places assignments don't work. // Same error message for all places assignments don't work.
// //
......
...@@ -64,7 +64,7 @@ class TParseContext : angle::NonCopyable ...@@ -64,7 +64,7 @@ class TParseContext : angle::NonCopyable
const char *token); const char *token);
TIntermBlock *getTreeRoot() const { return mTreeRoot; } TIntermBlock *getTreeRoot() const { return mTreeRoot; }
void setTreeRoot(TIntermBlock *treeRoot) { mTreeRoot = treeRoot; } void setTreeRoot(TIntermBlock *treeRoot);
bool getFragmentPrecisionHigh() const bool getFragmentPrecisionHigh() const
{ {
......
...@@ -284,6 +284,10 @@ ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic( ...@@ -284,6 +284,10 @@ ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic(
TIntermBlock *root, TIntermBlock *root,
const DriverUniformMetal *driverUniforms) const DriverUniformMetal *driverUniforms)
{ {
// This transformation leaves the tree in an inconsistent state by using a variable that's
// defined in text, outside of the knowledge of the AST.
mValidateASTOptions.validateVariableReferences = false;
TSymbolTable *symbolTable = &getSymbolTable(); TSymbolTable *symbolTable = &getSymbolTable();
// Insert coverageMaskEnabled specialization constant and sample_mask writing function. // Insert coverageMaskEnabled specialization constant and sample_mask writing function.
...@@ -337,6 +341,10 @@ ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic( ...@@ -337,6 +341,10 @@ ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic(
ANGLE_NO_DISCARD bool TranslatorMetal::insertRasterizerDiscardLogic(TInfoSinkBase &sink, ANGLE_NO_DISCARD bool TranslatorMetal::insertRasterizerDiscardLogic(TInfoSinkBase &sink,
TIntermBlock *root) TIntermBlock *root)
{ {
// This transformation leaves the tree in an inconsistent state by using a variable that's
// defined in text, outside of the knowledge of the AST.
mValidateASTOptions.validateVariableReferences = false;
TSymbolTable *symbolTable = &getSymbolTable(); TSymbolTable *symbolTable = &getSymbolTable();
// Insert rasterizationDisabled specialization constant. // Insert rasterizationDisabled specialization constant.
......
...@@ -890,6 +890,9 @@ bool TranslatorVulkan::translateImpl(TInfoSinkBase &sink, ...@@ -890,6 +890,9 @@ bool TranslatorVulkan::translateImpl(TInfoSinkBase &sink,
if (defaultUniformCount > 0) if (defaultUniformCount > 0)
{ {
// This transformation leaves the tree in an inconsistent state. TODO: anglebug.com/4889
mValidateASTOptions.validateVariableReferences = false;
sink << "\nlayout(set=0, binding=" << outputGLSL->nextUnusedBinding() sink << "\nlayout(set=0, binding=" << outputGLSL->nextUnusedBinding()
<< ", std140) uniform " << kDefaultUniformNames[packedShaderType] << "\n{\n"; << ", std140) uniform " << kDefaultUniformNames[packedShaderType] << "\n{\n";
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
#include "compiler/translator/ValidateAST.h" #include "compiler/translator/ValidateAST.h"
#include "compiler/translator/Diagnostics.h" #include "compiler/translator/Diagnostics.h"
#include "compiler/translator/Symbol.h"
#include "compiler/translator/tree_util/IntermTraverse.h" #include "compiler/translator/tree_util/IntermTraverse.h"
#include "compiler/translator/tree_util/SpecializationConstant.h"
namespace sh namespace sh
{ {
...@@ -48,6 +50,10 @@ class ValidateAST : public TIntermTraverser ...@@ -48,6 +50,10 @@ class ValidateAST : public TIntermTraverser
// Visit as a generic node // Visit as a generic node
void visitNode(Visit visit, TIntermNode *node); void visitNode(Visit visit, TIntermNode *node);
void scope(Visit visit);
bool isVariableDeclared(const TVariable *variable);
bool variableNeedsDeclaration(const TVariable *variable);
void expectNonNullChildren(Visit visit, TIntermNode *node, size_t least_count); void expectNonNullChildren(Visit visit, TIntermNode *node, size_t least_count);
bool validateInternal(); bool validateInternal();
...@@ -59,10 +65,15 @@ class ValidateAST : public TIntermTraverser ...@@ -59,10 +65,15 @@ class ValidateAST : public TIntermTraverser
std::map<TIntermNode *, TIntermNode *> mParent; std::map<TIntermNode *, TIntermNode *> mParent;
bool mSingleParentFailed = false; bool mSingleParentFailed = false;
// For validateNullNodes // For validateVariableReferences:
std::vector<std::set<const TVariable *>> mDeclaredVariables;
std::set<ImmutableString> mNamelessInterfaceBlockFields;
bool mVariableReferencesFailed = false;
// For validateNullNodes:
bool mNullNodesFailed = false; bool mNullNodesFailed = false;
// For validateMultiDeclarations // For validateMultiDeclarations:
bool mMultiDeclarationsFailed = false; bool mMultiDeclarationsFailed = false;
}; };
...@@ -80,6 +91,14 @@ ValidateAST::ValidateAST(TIntermNode *root, ...@@ -80,6 +91,14 @@ ValidateAST::ValidateAST(TIntermNode *root,
const ValidateASTOptions &options) const ValidateASTOptions &options)
: TIntermTraverser(true, false, true, nullptr), mOptions(options), mDiagnostics(diagnostics) : 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) if (mOptions.validateSingleParent)
{ {
mParent[root] = nullptr; mParent[root] = nullptr;
...@@ -111,6 +130,55 @@ void ValidateAST::visitNode(Visit visit, TIntermNode *node) ...@@ -111,6 +130,55 @@ void ValidateAST::visitNode(Visit visit, TIntermNode *node)
} }
} }
void ValidateAST::scope(Visit visit)
{
if (mOptions.validateVariableReferences)
{
if (visit == PreVisit)
{
mDeclaredVariables.push_back({});
}
else if (visit == PostVisit)
{
mDeclaredVariables.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;
}
void ValidateAST::expectNonNullChildren(Visit visit, TIntermNode *node, size_t least_count) void ValidateAST::expectNonNullChildren(Visit visit, TIntermNode *node, size_t least_count)
{ {
if (visit == PreVisit && mOptions.validateNullNodes) if (visit == PreVisit && mOptions.validateNullNodes)
...@@ -136,6 +204,36 @@ void ValidateAST::expectNonNullChildren(Visit visit, TIntermNode *node, size_t l ...@@ -136,6 +204,36 @@ void ValidateAST::expectNonNullChildren(Visit visit, TIntermNode *node, size_t l
void ValidateAST::visitSymbol(TIntermSymbol *node) void ValidateAST::visitSymbol(TIntermSymbol *node)
{ {
visitNode(PreVisit, 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 name.
if (type.getInterfaceBlock() && !type.isInterfaceBlock())
{
if (mNamelessInterfaceBlockFields.count(node->getName()) == 0)
{
mDiagnostics->error(node->getLine(),
"Found reference to undeclared nameless interface block field "
"<validateVariableReferences>",
node->getName().data());
mVariableReferencesFailed = true;
}
}
else
{
if (!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) void ValidateAST::visitConstantUnion(TIntermConstantUnion *node)
...@@ -193,6 +291,31 @@ void ValidateAST::visitFunctionPrototype(TIntermFunctionPrototype *node) ...@@ -193,6 +291,31 @@ void ValidateAST::visitFunctionPrototype(TIntermFunctionPrototype *node)
bool ValidateAST::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) bool ValidateAST::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node)
{ {
visitNode(visit, 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; return true;
} }
...@@ -206,6 +329,7 @@ bool ValidateAST::visitAggregate(Visit visit, TIntermAggregate *node) ...@@ -206,6 +329,7 @@ bool ValidateAST::visitAggregate(Visit visit, TIntermAggregate *node)
bool ValidateAST::visitBlock(Visit visit, TIntermBlock *node) bool ValidateAST::visitBlock(Visit visit, TIntermBlock *node)
{ {
visitNode(visit, node); visitNode(visit, node);
scope(visit);
expectNonNullChildren(visit, node, 0); expectNonNullChildren(visit, node, 0);
return true; return true;
} }
...@@ -222,11 +346,61 @@ bool ValidateAST::visitDeclaration(Visit visit, TIntermDeclaration *node) ...@@ -222,11 +346,61 @@ bool ValidateAST::visitDeclaration(Visit visit, TIntermDeclaration *node)
visitNode(visit, node); visitNode(visit, node);
expectNonNullChildren(visit, node, 0); expectNonNullChildren(visit, node, 0);
if (mOptions.validateMultiDeclarations && node->getSequence()->size() > 1) const TIntermSequence &sequence = *(node->getSequence());
if (mOptions.validateMultiDeclarations && sequence.size() > 1)
{ {
mMultiDeclarationsFailed = true; mMultiDeclarationsFailed = true;
} }
if (mOptions.validateVariableReferences && visit == PreVisit)
{
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 (isVariableDeclared(variable))
{
mDiagnostics->error(
node->getLine(),
"Found two declarations of the same variable <validateVariableReferences>",
variable->name().data());
mVariableReferencesFailed = true;
break;
}
mDeclaredVariables.back().insert(variable);
if (variable->symbolType() == SymbolType::Empty &&
variable->getType().getInterfaceBlock())
{
// Nameless interface blocks can only be declared at the top level. Their fields
// are not identified by TVariables, so resort to name-matching. Conflict in names
// should have already generated a compile error.
ASSERT(mDeclaredVariables.size() == 1);
const TFieldList &fieldList = variable->getType().getInterfaceBlock()->fields();
for (size_t memberIndex = 0; memberIndex < fieldList.size(); ++memberIndex)
{
TField *field = fieldList[memberIndex];
ASSERT(mNamelessInterfaceBlockFields.count(field->name()) == 0);
mNamelessInterfaceBlockFields.insert(field->name());
}
}
}
}
return true; return true;
} }
...@@ -249,7 +423,8 @@ void ValidateAST::visitPreprocessorDirective(TIntermPreprocessorDirective *node) ...@@ -249,7 +423,8 @@ void ValidateAST::visitPreprocessorDirective(TIntermPreprocessorDirective *node)
bool ValidateAST::validateInternal() bool ValidateAST::validateInternal()
{ {
return !mSingleParentFailed && !mNullNodesFailed && !mMultiDeclarationsFailed; return !mSingleParentFailed && !mVariableReferencesFailed && !mNullNodesFailed &&
!mMultiDeclarationsFailed;
} }
} // anonymous namespace } // anonymous namespace
......
...@@ -23,6 +23,8 @@ struct ValidateASTOptions ...@@ -23,6 +23,8 @@ struct ValidateASTOptions
// Check that every node always has only one parent, // Check that every node always has only one parent,
bool validateSingleParent = true; bool validateSingleParent = true;
// Check that all symbols reference TVariables that have been declared.
bool validateVariableReferences = true;
// Check that all EOpCallFunctionInAST have their corresponding function definitions in the AST, // Check that all EOpCallFunctionInAST have their corresponding function definitions in the AST,
// with matching symbol ids. There should also be at least a prototype declaration before the // with matching symbol ids. There should also be at least a prototype declaration before the
// function is called. // function is called.
......
...@@ -196,10 +196,12 @@ const TFunction *MonomorphizeFunction(TSymbolTable *symbolTable, ...@@ -196,10 +196,12 @@ const TFunction *MonomorphizeFunction(TSymbolTable *symbolTable,
if (nextReplacedArg >= replacedArguments->size() || if (nextReplacedArg >= replacedArguments->size() ||
paramIndex != (*replacedArguments)[nextReplacedArg].argumentIndex) paramIndex != (*replacedArguments)[nextReplacedArg].argumentIndex)
{ {
TVariable *substituteArgument =
new TVariable(symbolTable, originalParam->name(), &originalParam->getType(),
originalParam->symbolType());
// Not replaced, add an identical parameter. // Not replaced, add an identical parameter.
substituteFunction->addParameter(new TVariable(symbolTable, originalParam->name(), substituteFunction->addParameter(substituteArgument);
&originalParam->getType(), (*argumentMapOut)[originalParam] = new TIntermSymbol(substituteArgument);
originalParam->symbolType()));
} }
else else
{ {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "compiler/translator/SymbolTable.h" #include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/IntermTraverse.h" #include "compiler/translator/tree_util/IntermTraverse.h"
#include "compiler/translator/tree_util/ReplaceVariable.h"
namespace sh namespace sh
{ {
...@@ -51,6 +52,15 @@ class Traverser : public TIntermTraverser ...@@ -51,6 +52,15 @@ class Traverser : public TIntermTraverser
return false; return false;
} }
void visitSymbol(TIntermSymbol *symbol) override
{
const TVariable *variable = &symbol->variable();
if (mVariableMap.count(variable) > 0)
{
queueReplacement(mVariableMap[variable]->deepCopy(), OriginalNode::IS_DROPPED);
}
}
private: private:
void doReplacement(TIntermDeclaration *decl, void doReplacement(TIntermDeclaration *decl,
TIntermTyped *declarator, TIntermTyped *declarator,
...@@ -85,11 +95,15 @@ class Traverser : public TIntermTraverser ...@@ -85,11 +95,15 @@ class Traverser : public TIntermTraverser
namedDecl->appendDeclarator(newSymbol); namedDecl->appendDeclarator(newSymbol);
newSequence.push_back(namedDecl); newSequence.push_back(namedDecl);
mVariableMap[&asSymbol->variable()] = new TIntermSymbol(newVar);
} }
mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl, mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl,
std::move(newSequence)); std::move(newSequence));
} }
VariableReplacementMap mVariableMap;
}; };
} // anonymous namespace } // anonymous namespace
......
...@@ -471,4 +471,11 @@ TIntermBinary *SpecConst::getHalfRenderArea() ...@@ -471,4 +471,11 @@ TIntermBinary *SpecConst::getHalfRenderArea()
return rotatedHalfRenderArea; return rotatedHalfRenderArea;
} }
bool SpecConst::IsSpecConstName(const ImmutableString &name)
{
return name == kLineRasterEmulationSpecConstVarName ||
name == kSurfaceRotationSpecConstVarName || name == kDrawableWidthSpecConstVarName ||
name == kDrawableHeightSpecConstVarName;
}
} // namespace sh } // namespace sh
...@@ -46,6 +46,8 @@ class SpecConst ...@@ -46,6 +46,8 @@ class SpecConst
void outputLayoutString(TInfoSinkBase &sink) const; void outputLayoutString(TInfoSinkBase &sink) const;
SpecConstUsageBits getSpecConstUsageBits() const { return mUsageBits; } SpecConstUsageBits getSpecConstUsageBits() const { return mUsageBits; }
static bool IsSpecConstName(const ImmutableString &name);
private: private:
TIntermSymbol *getFlipRotation(); TIntermSymbol *getFlipRotation();
TIntermTyped *getNegFlipY(); TIntermTyped *getNegFlipY();
......
...@@ -17,6 +17,13 @@ namespace angle ...@@ -17,6 +17,13 @@ namespace angle
void InitTestHarness(int *argc, char **argv); void InitTestHarness(int *argc, char **argv);
} // namespace angle } // namespace angle
// If we ever move to a text-based expectations format, we should move this list in that file.
namespace
{
const char *kSlowTests[] = {
"dEQP.KHR_GLES31/core_arrays_of_arrays_ConstructorsAndUnsizedDeclConstructors1"};
} // namespace
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
#if defined(ANGLE_PLATFORM_MACOS) #if defined(ANGLE_PLATFORM_MACOS)
...@@ -27,5 +34,6 @@ int main(int argc, char **argv) ...@@ -27,5 +34,6 @@ int main(int argc, char **argv)
angle::InitTestHarness(&argc, argv); angle::InitTestHarness(&argc, argv);
angle::TestSuite testSuite(&argc, argv); angle::TestSuite testSuite(&argc, argv);
testSuite.registerSlowTests(kSlowTests, ArraySize(kSlowTests));
return testSuite.run(); return testSuite.run();
} }
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