Move assignment validation from Validator to Resolver

* With this change, ProgramBuilder::WrapInStatement(expr), which
produced an "expr = expr" assignment expression, would fail validation
in some cases like for call expressions. Replaced this with a
declaration of a variable with type inferred from expr.

* Moved existing validation tests to resolver\assignment_validation.cc,
and added missing tests: AssignFromPointer_Fail.

* Fixed broken tests as a result of this change.

Bug: tint:642
Change-Id: I6a738b67edb0e116a46e936d652c2dcb4389281e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/46161
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
Antonio Maiorano
2021-03-31 13:26:43 +00:00
committed by Commit Bot service account
parent 39a65a1d1e
commit e09989ae34
8 changed files with 217 additions and 245 deletions

View File

@@ -16,6 +16,8 @@
#include "gmock/gmock.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/type/access_control_type.h"
#include "src/type/storage_texture_type.h"
namespace tint {
namespace resolver {
@@ -139,6 +141,143 @@ TEST_F(ResolverAssignmentValidationTest,
R"(12:34 error: invalid assignment: cannot assign value of type 'f32' to a variable of type 'i32')");
}
TEST_F(ResolverAssignmentValidationTest, AssignToScalar_Fail) {
// var my_var : i32 = 2;
// 1 = my_var;
auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* lhs = Expr(1);
auto* rhs = Expr("my_var");
auto* assign = create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs);
WrapInFunction(Decl(var), assign);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error v-000x: invalid assignment: left-hand-side does not "
"reference storage: i32");
}
TEST_F(ResolverAssignmentValidationTest, AssignCompatibleTypes_Pass) {
// var a :i32 = 2;
// a = 2
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* lhs = Expr("a");
auto* rhs = Expr(2);
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
WrapInFunction(Decl(var), assign);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverAssignmentValidationTest,
AssignCompatibleTypesThroughAlias_Pass) {
// alias myint = i32;
// var a :myint = 2;
// a = 2
auto* myint = ty.alias("myint", ty.i32());
auto* var = Var("a", myint, ast::StorageClass::kNone, Expr(2));
auto* lhs = Expr("a");
auto* rhs = Expr(2);
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
WrapInFunction(Decl(var), assign);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverAssignmentValidationTest,
AssignCompatibleTypesInferRHSLoad_Pass) {
// var a : i32 = 2;
// var b : i32 = 3;
// a = b;
auto* var_a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* var_b = Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3));
auto* lhs = Expr("a");
auto* rhs = Expr("b");
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
WrapInFunction(Decl(var_a), Decl(var_b), assign);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverAssignmentValidationTest, AssignThroughPointer_Pass) {
// var a :i32;
// const b : ptr<function,i32> = a;
// b = 2;
const auto func = ast::StorageClass::kFunction;
auto* var_a = Var("a", ty.i32(), func, Expr(2), {});
auto* var_b = Const("b", ty.pointer<int>(func), Expr("a"), {});
auto* lhs = Expr("b");
auto* rhs = Expr(2);
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
WrapInFunction(Decl(var_a), Decl(var_b), assign);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverAssignmentValidationTest, AssignToConstant_Fail) {
// {
// const a :i32 = 2;
// a = 2
// }
auto* var = Const("a", ty.i32(), Expr(2));
auto* lhs = Expr("a");
auto* rhs = Expr(2);
auto* body = create<ast::BlockStatement>(ast::StatementList{
create<ast::VariableDeclStatement>(var),
create<ast::AssignmentStatement>(Source{Source::Location{12, 34}}, lhs,
rhs),
});
WrapInFunction(body);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error v-0021: cannot re-assign a constant: 'a'");
}
TEST_F(ResolverAssignmentValidationTest, AssignFromPointer_Fail) {
// var a : [[access(read)]] texture_storage_1d<rgba8unorm>;
// var b : [[access(read)]] texture_storage_1d<rgba8unorm>;
// a = b;
auto* tex_type = create<type::StorageTexture>(
type::TextureDimension::k1d, type::ImageFormat::kRgba8Unorm,
type::StorageTexture::SubtypeFor(type::ImageFormat::kRgba8Unorm,
Types()));
auto* tex_ac =
create<type::AccessControl>(ast::AccessControl::kReadOnly, tex_type);
auto* var_a = Var("a", tex_ac, ast::StorageClass::kFunction);
auto* var_b = Var("b", tex_ac, ast::StorageClass::kFunction);
auto* lhs = Expr("a");
auto* rhs = Expr("b");
auto* assign = create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs);
WrapInFunction(Decl(var_a), Decl(var_b), assign);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error v-000x: invalid assignment: right-hand-side is not "
"storable: ptr<function, [[access(read)]] "
"texture_storage_1d<rgba8unorm>>");
}
} // namespace
} // namespace resolver
} // namespace tint

View File

@@ -333,23 +333,7 @@ bool Resolver::Statement(ast::Statement* stmt) {
ScopedAssignment<semantic::Statement*> sa(current_statement_, sem_statement);
if (auto* a = stmt->As<ast::AssignmentStatement>()) {
if (!Expression(a->lhs()) || !Expression(a->rhs())) {
return false;
}
// TODO(crbug.com/tint/659): This logic needs updating once pointers are
// pinned down in the WGSL spec.
auto* lhs_type = TypeOf(a->lhs())->UnwrapAll();
auto* rhs_type = TypeOf(a->rhs());
if (!IsValidAssignment(lhs_type, rhs_type)) {
diagnostics_.add_error(
"invalid assignment: cannot assign value of type '" +
rhs_type->FriendlyName(builder_->Symbols()) +
"' to a variable of type '" +
lhs_type->FriendlyName(builder_->Symbols()) + "'",
stmt->source());
return false;
}
return true;
return Assignment(a);
}
if (auto* b = stmt->As<ast::BlockStatement>()) {
return BlockStatement(b);
@@ -1770,6 +1754,74 @@ bool Resolver::Switch(ast::SwitchStatement* s) {
return true;
}
bool Resolver::ValidateAssignment(const ast::AssignmentStatement* a) {
auto* lhs = a->lhs();
auto* rhs = a->rhs();
// TODO(crbug.com/tint/659): This logic needs updating once pointers are
// pinned down in the WGSL spec.
auto* lhs_type = TypeOf(lhs)->UnwrapAll();
auto* rhs_type = TypeOf(rhs);
if (!IsValidAssignment(lhs_type, rhs_type)) {
diagnostics_.add_error("invalid assignment: cannot assign value of type '" +
rhs_type->FriendlyName(builder_->Symbols()) +
"' to a variable of type '" +
lhs_type->FriendlyName(builder_->Symbols()) +
"'",
a->source());
return false;
}
// Pointers are not storable in WGSL, but the right-hand side must be
// storable. The raw right-hand side might be a pointer value which must be
// loaded (dereferenced) to provide the value to be stored.
auto* rhs_result_type = TypeOf(rhs)->UnwrapAll();
if (!IsStorable(rhs_result_type)) {
diagnostics_.add_error(
"v-000x",
"invalid assignment: right-hand-side is not storable: " +
TypeOf(rhs)->FriendlyName(builder_->Symbols()),
a->source());
return false;
}
// lhs must be a pointer or a constant
auto* lhs_result_type = TypeOf(lhs)->UnwrapIfNeeded();
if (!lhs_result_type->Is<type::Pointer>()) {
// In case lhs is a constant identifier, output a nicer message as it's
// likely to be a common programmer error.
if (auto* ident = lhs->As<ast::IdentifierExpression>()) {
VariableInfo* var;
if (variable_stack_.get(ident->symbol(), &var) &&
var->declaration->is_const()) {
diagnostics_.add_error(
"v-0021",
"cannot re-assign a constant: '" +
builder_->Symbols().NameFor(ident->symbol()) + "'",
a->source());
return false;
}
}
// Issue a generic error.
diagnostics_.add_error(
"v-000x",
"invalid assignment: left-hand-side does not reference storage: " +
TypeOf(lhs)->FriendlyName(builder_->Symbols()),
a->source());
return false;
}
return true;
}
bool Resolver::Assignment(ast::AssignmentStatement* a) {
if (!Expression(a->lhs()) || !Expression(a->rhs())) {
return false;
}
return ValidateAssignment(a);
}
bool Resolver::ApplyStorageClassUsageToType(ast::StorageClass sc,
type::Type* ty,
Source usage) {

View File

@@ -223,6 +223,7 @@ class Resolver {
bool VariableDeclStatement(const ast::VariableDeclStatement*);
bool Return(ast::ReturnStatement* ret);
bool Switch(ast::SwitchStatement* s);
bool Assignment(ast::AssignmentStatement* a);
// AST and Type validation methods
// Each return true on success, false on failure.
@@ -232,6 +233,7 @@ class Resolver {
bool ValidateStructure(const type::Struct* st);
bool ValidateReturn(const ast::ReturnStatement* ret);
bool ValidateSwitch(const ast::SwitchStatement* s);
bool ValidateAssignment(const ast::AssignmentStatement* a);
/// @returns the semantic information for the array `arr`, building it if it
/// hasn't been constructed already. If an error is raised, nullptr is

View File

@@ -639,14 +639,13 @@ TEST_F(ResolverTest, Expr_Identifier_GlobalConstant) {
TEST_F(ResolverTest, Expr_Identifier_FunctionVariable_Const) {
auto* my_var_a = Expr("my_var");
auto* my_var_b = Expr("my_var");
auto* var = Const("my_var", ty.f32());
auto* assign = create<ast::AssignmentStatement>(my_var_a, my_var_b);
auto* decl = Decl(Var("b", ty.f32(), ast::StorageClass::kFunction, my_var_a));
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::VariableDeclStatement>(var),
assign,
decl,
},
ast::DecorationList{});
@@ -654,11 +653,8 @@ TEST_F(ResolverTest, Expr_Identifier_FunctionVariable_Const) {
ASSERT_NE(TypeOf(my_var_a), nullptr);
EXPECT_TRUE(TypeOf(my_var_a)->Is<type::F32>());
EXPECT_EQ(StmtOf(my_var_a), assign);
ASSERT_NE(TypeOf(my_var_b), nullptr);
EXPECT_TRUE(TypeOf(my_var_b)->Is<type::F32>());
EXPECT_EQ(StmtOf(my_var_b), assign);
EXPECT_TRUE(CheckVarUsers(var, {my_var_a, my_var_b}));
EXPECT_EQ(StmtOf(my_var_a), decl);
EXPECT_TRUE(CheckVarUsers(var, {my_var_a}));
}
TEST_F(ResolverTest, Expr_Identifier_FunctionVariable) {