[validation] Validate globals in declared order

Instead of validating all global variables and then functions,
validate global declarations in the order they were added to the AST.

This fixes false-positive "redeclared identifier" errors when a global
variable is declared after a function that declares a variable of the
same name, and false-negative "identifier not declared" errors when a
global variable is declared after a function that tries to use it.

Change-Id: Ibf5e5265bc2f8ca892096f0420757b70e1984525
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/41302
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
James Price 2021-02-09 23:22:32 +00:00 committed by Commit Bot service account
parent 558385357f
commit c0f30195a0
5 changed files with 149 additions and 121 deletions

View File

@ -116,9 +116,8 @@ TEST_F(ValidateFunctionTest, FunctionTypeMustMatchReturnStatementType_Pass) {
}); });
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_TRUE(v.ValidateFunctions(program->AST().Functions())) << v.error(); EXPECT_TRUE(v.Validate()) << v.error();
} }
TEST_F(ValidateFunctionTest, FunctionTypeMustMatchReturnStatementType_fail) { TEST_F(ValidateFunctionTest, FunctionTypeMustMatchReturnStatementType_fail) {

View File

@ -286,101 +286,85 @@ void ValidatorImpl::add_error(const Source& src, const std::string& msg) {
} }
bool ValidatorImpl::Validate() { bool ValidatorImpl::Validate() {
function_stack_.push_scope(); // Validate global declarations in the order they appear in the module.
if (!ValidateGlobalVariables(program_->AST().GlobalVariables())) { for (auto* decl : program_->AST().GlobalDeclarations()) {
return false; if (auto* ty = decl->As<type::Type>()) {
} if (!ValidateConstructedType(ty)) {
if (!ValidateConstructedTypes(program_->AST().ConstructedTypes())) { return false;
return false; }
} } else if (auto* func = decl->As<ast::Function>()) {
if (!ValidateFunctions(program_->AST().Functions())) { current_function_ = func;
return false; if (!ValidateFunction(func)) {
return false;
}
current_function_ = nullptr;
} else if (auto* var = decl->As<ast::Variable>()) {
if (!ValidateGlobalVariable(var)) {
return false;
}
} else {
assert(false /* unreachable */);
}
} }
if (!ValidateEntryPoint(program_->AST().Functions())) { if (!ValidateEntryPoint(program_->AST().Functions())) {
return false; return false;
} }
function_stack_.pop_scope();
return true; return true;
} }
bool ValidatorImpl::ValidateConstructedTypes( bool ValidatorImpl::ValidateConstructedType(const type::Type* type) {
const std::vector<type::Type*>& constructed_types) { if (auto* st = type->As<type::Struct>()) {
for (auto* const ct : constructed_types) { for (auto* member : st->impl()->members()) {
if (auto* st = ct->As<type::Struct>()) { if (auto* r = member->type()->UnwrapAll()->As<type::Array>()) {
for (auto* member : st->impl()->members()) { if (r->IsRuntimeArray()) {
if (auto* r = member->type()->UnwrapAll()->As<type::Array>()) { if (member != st->impl()->members().back()) {
if (r->IsRuntimeArray()) { add_error(member->source(), "v-0015",
if (member != st->impl()->members().back()) { "runtime arrays may only appear as the last member of "
add_error(member->source(), "v-0015", "a struct");
"runtime arrays may only appear as the last member of " return false;
"a struct"); }
return false; if (!st->IsBlockDecorated()) {
} add_error(member->source(), "v-0015",
if (!st->IsBlockDecorated()) { "a struct containing a runtime-sized array "
add_error(member->source(), "v-0015", "requires the [[block]] attribute: '" +
"a struct containing a runtime-sized array " program_->Symbols().NameFor(st->symbol()) + "'");
"requires the [[block]] attribute: '" + return false;
program_->Symbols().NameFor(st->symbol()) + "'");
return false;
}
} }
} }
} }
} }
} }
return true; return true;
} }
bool ValidatorImpl::ValidateGlobalVariables( bool ValidatorImpl::ValidateGlobalVariable(const ast::Variable* var) {
const ast::VariableList& global_vars) { auto* sem = program_->Sem().Get(var);
for (auto* var : global_vars) { if (!sem) {
auto* sem = program_->Sem().Get(var); add_error(var->source(), "no semantic information for variable '" +
if (!sem) { program_->Symbols().NameFor(var->symbol()) +
add_error(var->source(), "no semantic information for variable '" + "'");
program_->Symbols().NameFor(var->symbol()) + return false;
"'");
return false;
}
if (variable_stack_.has(var->symbol())) {
add_error(var->source(), "v-0011",
"redeclared global identifier '" +
program_->Symbols().NameFor(var->symbol()) + "'");
return false;
}
if (!var->is_const() && sem->StorageClass() == ast::StorageClass::kNone) {
add_error(var->source(), "v-0022",
"global variables must have a storage class");
return false;
}
if (var->is_const() && !(sem->StorageClass() == ast::StorageClass::kNone)) {
add_error(var->source(), "v-global01",
"global constants shouldn't have a storage class");
return false;
}
variable_stack_.set_global(var->symbol(), var);
}
return true;
}
bool ValidatorImpl::ValidateFunctions(const ast::FunctionList& funcs) {
for (auto* func : funcs) {
if (function_stack_.has(func->symbol())) {
add_error(func->source(), "v-0016",
"function names must be unique '" +
program_->Symbols().NameFor(func->symbol()) + "'");
return false;
}
function_stack_.set(func->symbol(), func);
current_function_ = func;
if (!ValidateFunction(func)) {
return false;
}
current_function_ = nullptr;
} }
if (variable_stack_.has(var->symbol())) {
add_error(var->source(), "v-0011",
"redeclared global identifier '" +
program_->Symbols().NameFor(var->symbol()) + "'");
return false;
}
if (!var->is_const() && sem->StorageClass() == ast::StorageClass::kNone) {
add_error(var->source(), "v-0022",
"global variables must have a storage class");
return false;
}
if (var->is_const() && !(sem->StorageClass() == ast::StorageClass::kNone)) {
add_error(var->source(), "v-global01",
"global constants shouldn't have a storage class");
return false;
}
variable_stack_.set_global(var->symbol(), var);
return true; return true;
} }
@ -425,6 +409,15 @@ bool ValidatorImpl::ValidateEntryPoint(const ast::FunctionList& funcs) {
} }
bool ValidatorImpl::ValidateFunction(const ast::Function* func) { bool ValidatorImpl::ValidateFunction(const ast::Function* func) {
if (function_stack_.has(func->symbol())) {
add_error(func->source(), "v-0016",
"function names must be unique '" +
program_->Symbols().NameFor(func->symbol()) + "'");
return false;
}
function_stack_.set(func->symbol(), func);
variable_stack_.push_scope(); variable_stack_.push_scope();
for (auto* param : func->params()) { for (auto* param : func->params()) {
@ -906,7 +899,7 @@ bool ValidatorImpl::ValidateBadAssignmentToIdentifier(
// It wasn't an identifier in the first place. // It wasn't an identifier in the first place.
return true; return true;
} }
ast::Variable* var; const ast::Variable* var;
if (variable_stack_.get(ident->symbol(), &var)) { if (variable_stack_.get(ident->symbol(), &var)) {
// Give a nicer message if the LHS of the assignment is a const identifier. // Give a nicer message if the LHS of the assignment is a const identifier.
// It's likely to be a common programmer error. // It's likely to be a common programmer error.
@ -989,7 +982,7 @@ bool ValidatorImpl::ValidateExpression(const ast::Expression* expr) {
} }
bool ValidatorImpl::ValidateIdentifier(const ast::IdentifierExpression* ident) { bool ValidatorImpl::ValidateIdentifier(const ast::IdentifierExpression* ident) {
ast::Variable* var; const ast::Variable* var;
if (!variable_stack_.get(ident->symbol(), &var)) { if (!variable_stack_.get(ident->symbol(), &var)) {
add_error(ident->source(), "v-0006", add_error(ident->source(), "v-0006",
"'" + program_->Symbols().NameFor(ident->symbol()) + "'" + program_->Symbols().NameFor(ident->symbol()) +

View File

@ -76,14 +76,10 @@ class ValidatorImpl {
/// @param msg the error message /// @param msg the error message
void add_error(const Source& src, const std::string& msg); void add_error(const Source& src, const std::string& msg);
/// Validate global variables /// Validates a global variable
/// @param global_vars list of global variables to check /// @param var the global variable to check
/// @returns true if the validation was successful /// @returns true if the validation was successful
bool ValidateGlobalVariables(const ast::VariableList& global_vars); bool ValidateGlobalVariable(const ast::Variable* var);
/// Validates Functions
/// @param funcs the functions to check
/// @returns true if the validation was successful
bool ValidateFunctions(const ast::FunctionList& funcs);
/// Validates a function /// Validates a function
/// @param func the function to check /// @param func the function to check
/// @returns true if the validation was successful /// @returns true if the validation was successful
@ -144,11 +140,10 @@ class ValidatorImpl {
/// @returns true if the valdiation was successful /// @returns true if the valdiation was successful
bool ValidateEntryPoint(const ast::FunctionList& funcs); bool ValidateEntryPoint(const ast::FunctionList& funcs);
/// Validates constructed types /// Validates a constructed type
/// @param constructed_types the types to check /// @param type the type to check
/// @returns true if the valdiation was successful /// @returns true if the valdiation was successful
bool ValidateConstructedTypes( bool ValidateConstructedType(const type::Type* type);
const std::vector<type::Type*>& constructed_types);
/// Returns true if the given type is storable. This uses and /// Returns true if the given type is storable. This uses and
/// updates `storable_` and `not_storable_`. /// updates `storable_` and `not_storable_`.
@ -165,8 +160,8 @@ class ValidatorImpl {
private: private:
const Program* program_; const Program* program_;
diag::List diags_; diag::List diags_;
ScopeStack<ast::Variable*> variable_stack_; ScopeStack<const ast::Variable*> variable_stack_;
ScopeStack<ast::Function*> function_stack_; ScopeStack<const ast::Function*> function_stack_;
ast::Function* current_function_ = nullptr; ast::Function* current_function_ = nullptr;
}; };

View File

@ -375,15 +375,13 @@ TEST_F(ValidatorTest, AssignIncompatibleTypesInNestedBlockStatement_Fail) {
TEST_F(ValidatorTest, GlobalVariableWithStorageClass_Pass) { TEST_F(ValidatorTest, GlobalVariableWithStorageClass_Pass) {
// var<in> gloabl_var: f32; // var<in> gloabl_var: f32;
Global(Source{Source::Location{12, 34}}, "global_var", auto* var = Global(Source{Source::Location{12, 34}}, "global_var",
ast::StorageClass::kInput, ty.f32(), nullptr, ast::StorageClass::kInput, ty.f32(), nullptr,
ast::VariableDecorationList{}); ast::VariableDecorationList{});
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_TRUE(v.ValidateGlobalVariables(program->AST().GlobalVariables())) EXPECT_TRUE(v.ValidateGlobalVariable(var)) << v.error();
<< v.error();
} }
TEST_F(ValidatorTest, GlobalVariableNoStorageClass_Fail) { TEST_F(ValidatorTest, GlobalVariableNoStorageClass_Fail) {
@ -449,6 +447,32 @@ TEST_F(ValidatorTest, UsingUndefinedVariableGlobalVariable_Fail) {
EXPECT_EQ(v.error(), "12:34 v-0006: 'not_global_var' is not declared"); EXPECT_EQ(v.error(), "12:34 v-0006: 'not_global_var' is not declared");
} }
TEST_F(ValidatorTest, UsingUndefinedVariableGlobalVariableAfter_Fail) {
// fn my_func() -> void {
// global_var = 3.14f;
// }
// var global_var: f32 = 2.1;
SetSource(Source{Source::Location{12, 34}});
auto* lhs = Expr("global_var");
auto* rhs = Expr(3.14f);
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(lhs, rhs),
},
ast::FunctionDecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
Global("global_var", ast::StorageClass::kPrivate, ty.f32(), Expr(2.1f),
ast::VariableDecorationList{});
ValidatorImpl& v = Build();
EXPECT_FALSE(v.Validate());
EXPECT_EQ(v.error(), "12:34 v-0006: 'global_var' is not declared");
}
TEST_F(ValidatorTest, UsingUndefinedVariableGlobalVariable_Pass) { TEST_F(ValidatorTest, UsingUndefinedVariableGlobalVariable_Pass) {
// var global_var: f32 = 2.1; // var global_var: f32 = 2.1;
// fn my_func() -> void { // fn my_func() -> void {
@ -579,18 +603,17 @@ TEST_F(ValidatorTest, UsingUndefinedVariableDifferentScope_Fail) {
TEST_F(ValidatorTest, GlobalVariableUnique_Pass) { TEST_F(ValidatorTest, GlobalVariableUnique_Pass) {
// var global_var0 : f32 = 0.1; // var global_var0 : f32 = 0.1;
// var global_var1 : i32 = 0; // var global_var1 : i32 = 0;
Global("global_var0", ast::StorageClass::kPrivate, ty.f32(), Expr(0.1f), auto* var0 = Global("global_var0", ast::StorageClass::kPrivate, ty.f32(),
ast::VariableDecorationList{}); Expr(0.1f), ast::VariableDecorationList{});
Global(Source{Source::Location{12, 34}}, "global_var1", auto* var1 = Global(Source{Source::Location{12, 34}}, "global_var1",
ast::StorageClass::kPrivate, ty.f32(), Expr(0), ast::StorageClass::kPrivate, ty.f32(), Expr(0),
ast::VariableDecorationList{}); ast::VariableDecorationList{});
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_TRUE(v.ValidateGlobalVariables(program->AST().GlobalVariables())) EXPECT_TRUE(v.ValidateGlobalVariable(var0)) << v.error();
<< v.error(); EXPECT_TRUE(v.ValidateGlobalVariable(var1)) << v.error();
} }
TEST_F(ValidatorTest, GlobalVariableNotUnique_Fail) { TEST_F(ValidatorTest, GlobalVariableNotUnique_Fail) {
@ -604,9 +627,8 @@ TEST_F(ValidatorTest, GlobalVariableNotUnique_Fail) {
ast::VariableDecorationList{}); ast::VariableDecorationList{});
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_FALSE(v.ValidateGlobalVariables(program->AST().GlobalVariables())); EXPECT_FALSE(v.Validate());
EXPECT_EQ(v.error(), EXPECT_EQ(v.error(),
"12:34 v-0011: redeclared global identifier 'global_var'"); "12:34 v-0011: redeclared global identifier 'global_var'");
} }
@ -639,6 +661,30 @@ TEST_F(ValidatorTest, AssignToConstant_Fail) {
EXPECT_EQ(v.error(), "12:34 v-0021: cannot re-assign a constant: 'a'"); EXPECT_EQ(v.error(), "12:34 v-0021: cannot re-assign a constant: 'a'");
} }
TEST_F(ValidatorTest, GlobalVariableFunctionVariableNotUnique_Pass) {
// fn my_func -> void {
// var a: f32 = 2.0;
// }
// var a: f32 = 2.1;
auto* var = Var("a", ast::StorageClass::kNone, ty.f32(), Expr(2.0f),
ast::VariableDecorationList{});
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::VariableDeclStatement>(var),
},
ast::FunctionDecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
Global("a", ast::StorageClass::kPrivate, ty.f32(), Expr(2.1f),
ast::VariableDecorationList{});
ValidatorImpl& v = Build();
EXPECT_TRUE(v.Validate()) << v.error();
}
TEST_F(ValidatorTest, GlobalVariableFunctionVariableNotUnique_Fail) { TEST_F(ValidatorTest, GlobalVariableFunctionVariableNotUnique_Fail) {
// var a: f32 = 2.1; // var a: f32 = 2.1;
// fn my_func -> void { // fn my_func -> void {
@ -665,7 +711,7 @@ TEST_F(ValidatorTest, GlobalVariableFunctionVariableNotUnique_Fail) {
EXPECT_EQ(v.error(), "12:34 v-0013: redeclared identifier 'a'"); EXPECT_EQ(v.error(), "12:34 v-0013: redeclared identifier 'a'");
} }
TEST_F(ValidatorTest, RedeclaredIndentifier_Fail) { TEST_F(ValidatorTest, RedeclaredIdentifier_Fail) {
// fn my_func() -> void { // fn my_func() -> void {
// var a :i32 = 2; // var a :i32 = 2;
// var a :f21 = 2.0; // var a :f21 = 2.0;

View File

@ -54,9 +54,8 @@ TEST_F(ValidatorTypeTest, RuntimeArrayIsLast_Pass) {
AST().AddConstructedType(struct_type); AST().AddConstructedType(struct_type);
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_TRUE(v.ValidateConstructedTypes(program->AST().ConstructedTypes())); EXPECT_TRUE(v.ValidateConstructedType(struct_type));
} }
TEST_F(ValidatorTypeTest, RuntimeArrayIsLastNoBlock_Fail) { TEST_F(ValidatorTypeTest, RuntimeArrayIsLastNoBlock_Fail) {
@ -75,9 +74,8 @@ TEST_F(ValidatorTypeTest, RuntimeArrayIsLastNoBlock_Fail) {
AST().AddConstructedType(struct_type); AST().AddConstructedType(struct_type);
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_FALSE(v.ValidateConstructedTypes(program->AST().ConstructedTypes())); EXPECT_FALSE(v.ValidateConstructedType(struct_type));
EXPECT_EQ(v.error(), EXPECT_EQ(v.error(),
"v-0015: a struct containing a runtime-sized array requires the " "v-0015: a struct containing a runtime-sized array requires the "
"[[block]] attribute: 'Foo'"); "[[block]] attribute: 'Foo'");
@ -104,9 +102,8 @@ TEST_F(ValidatorTypeTest, RuntimeArrayIsNotLast_Fail) {
AST().AddConstructedType(struct_type); AST().AddConstructedType(struct_type);
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_FALSE(v.ValidateConstructedTypes(program->AST().ConstructedTypes())); EXPECT_FALSE(v.ValidateConstructedType(struct_type));
EXPECT_EQ(v.error(), EXPECT_EQ(v.error(),
"12:34 v-0015: runtime arrays may only appear as the last member " "12:34 v-0015: runtime arrays may only appear as the last member "
"of a struct"); "of a struct");
@ -131,9 +128,8 @@ TEST_F(ValidatorTypeTest, AliasRuntimeArrayIsNotLast_Fail) {
AST().AddConstructedType(struct_type); AST().AddConstructedType(struct_type);
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_FALSE(v.ValidateConstructedTypes(program->AST().ConstructedTypes())); EXPECT_FALSE(v.ValidateConstructedType(struct_type));
EXPECT_EQ(v.error(), EXPECT_EQ(v.error(),
"v-0015: runtime arrays may only appear as the last member " "v-0015: runtime arrays may only appear as the last member "
"of a struct"); "of a struct");
@ -158,9 +154,8 @@ TEST_F(ValidatorTypeTest, AliasRuntimeArrayIsLast_Pass) {
AST().AddConstructedType(struct_type); AST().AddConstructedType(struct_type);
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
const Program* program = v.program();
EXPECT_TRUE(v.ValidateConstructedTypes(program->AST().ConstructedTypes())); EXPECT_TRUE(v.ValidateConstructedType(struct_type));
} }
TEST_F(ValidatorTypeTest, RuntimeArrayInFunction_Fail) { TEST_F(ValidatorTypeTest, RuntimeArrayInFunction_Fail) {