[validation] validate switch statement

This CL adds validation for the following rules:
v-switch01: switch statement selector expression must be of a scalar integer type
v-0008: switch statement must have exactly one default clause
v-switch03: the case selector values must have the same type as the selector expression the case selectors for a switch statement
v-switch04: a literal value must not appear more than once in the case selectors for a switch statement
v-switch05: a fallthrough statement must not appear as the last statement in last clause of a switch

Bug: tint: 6
Change-Id: I264d5079cc6cb31075965c8721651dc76f3e2a24
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/28062
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
Sarah Mashayekhi 2020-09-10 14:37:17 +00:00 committed by Commit Bot service account
parent ed0527a016
commit 2e9f1f54c8
3 changed files with 249 additions and 26 deletions

View File

@ -22,6 +22,7 @@
#include "src/ast/scalar_constructor_expression.h" #include "src/ast/scalar_constructor_expression.h"
#include "src/ast/sint_literal.h" #include "src/ast/sint_literal.h"
#include "src/ast/switch_statement.h" #include "src/ast/switch_statement.h"
#include "src/ast/type/alias_type.h"
#include "src/ast/type/f32_type.h" #include "src/ast/type/f32_type.h"
#include "src/ast/type/i32_type.h" #include "src/ast/type/i32_type.h"
#include "src/ast/type/u32_type.h" #include "src/ast/type/u32_type.h"
@ -37,8 +38,7 @@ namespace {
class ValidateControlBlockTest : public ValidatorTestHelper, class ValidateControlBlockTest : public ValidatorTestHelper,
public testing::Test {}; public testing::Test {};
TEST_F(ValidateControlBlockTest, TEST_F(ValidateControlBlockTest, SwitchSelectorExpressionNoneIntegerType_Fail) {
DISABLED_SwitchSelectorExpressionNoneIntegerType_Fail) {
// var a : f32 = 3.14; // var a : f32 = 3.14;
// switch (a) { // switch (a) {
// default: {} // default: {}
@ -49,7 +49,7 @@ TEST_F(ValidateControlBlockTest,
var->set_constructor(std::make_unique<ast::ScalarConstructorExpression>( var->set_constructor(std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::SintLiteral>(&f32, 3.14f))); std::make_unique<ast::SintLiteral>(&f32, 3.14f)));
auto cond = std::make_unique<ast::IdentifierExpression>("a"); auto cond = std::make_unique<ast::IdentifierExpression>(Source{12, 34}, "a");
ast::CaseSelectorList default_csl; ast::CaseSelectorList default_csl;
auto block_default = std::make_unique<ast::BlockStatement>(); auto block_default = std::make_unique<ast::BlockStatement>();
ast::CaseStatementList body; ast::CaseStatementList body;
@ -58,8 +58,8 @@ TEST_F(ValidateControlBlockTest,
auto block = std::make_unique<ast::BlockStatement>(); auto block = std::make_unique<ast::BlockStatement>();
block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var))); block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
block->append(std::make_unique<ast::SwitchStatement>( block->append(
Source{12, 34}, std::move(cond), std::move(body))); std::make_unique<ast::SwitchStatement>(std::move(cond), std::move(body)));
EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error(); EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error();
EXPECT_FALSE(v()->ValidateStatements(block.get())); EXPECT_FALSE(v()->ValidateStatements(block.get()));
@ -68,7 +68,7 @@ TEST_F(ValidateControlBlockTest,
"of a scalar integer type"); "of a scalar integer type");
} }
TEST_F(ValidateControlBlockTest, DISABLED_SwitchWithoutDefault_Fail) { TEST_F(ValidateControlBlockTest, SwitchWithoutDefault_Fail) {
// var a : i32 = 2; // var a : i32 = 2;
// switch (a) { // switch (a) {
// case 1: {} // case 1: {}
@ -94,11 +94,11 @@ TEST_F(ValidateControlBlockTest, DISABLED_SwitchWithoutDefault_Fail) {
EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error(); EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error();
EXPECT_FALSE(v()->ValidateStatements(block.get())); EXPECT_FALSE(v()->ValidateStatements(block.get()));
EXPECT_EQ(v()->error(), EXPECT_EQ(v()->error(),
"12:34: v-switch02: switch statement must have exactly one default " "12:34: v-0008: switch statement must have exactly one default "
"clause"); "clause");
} }
TEST_F(ValidateControlBlockTest, DISABLED_SwitchWithTwoDefault_Fail) { TEST_F(ValidateControlBlockTest, SwitchWithTwoDefault_Fail) {
// var a : i32 = 2; // var a : i32 = 2;
// switch (a) { // switch (a) {
// default: {} // default: {}
@ -128,7 +128,46 @@ TEST_F(ValidateControlBlockTest, DISABLED_SwitchWithTwoDefault_Fail) {
ast::CaseSelectorList default_csl_2; ast::CaseSelectorList default_csl_2;
auto block_default_2 = std::make_unique<ast::BlockStatement>(); auto block_default_2 = std::make_unique<ast::BlockStatement>();
switch_body.push_back(std::make_unique<ast::CaseStatement>( switch_body.push_back(std::make_unique<ast::CaseStatement>(
Source{12, 34}, std::move(default_csl_2), std::move(block_default_2))); std::move(default_csl_2), std::move(block_default_2)));
auto block = std::make_unique<ast::BlockStatement>();
block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
block->append(std::make_unique<ast::SwitchStatement>(
Source{12, 34}, std::move(cond), std::move(switch_body)));
EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error();
EXPECT_FALSE(v()->ValidateStatements(block.get()));
EXPECT_EQ(v()->error(),
"12:34: v-0008: switch statement must have exactly one default "
"clause");
}
TEST_F(ValidateControlBlockTest,
SwitchConditionTypeMustMatchSelectorType2_Fail) {
// var a : i32 = 2;
// switch (a) {
// case 1: {}
// default: {}
// }
ast::type::U32Type u32;
ast::type::I32Type i32;
auto var =
std::make_unique<ast::Variable>("a", ast::StorageClass::kNone, &i32);
var->set_constructor(std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::SintLiteral>(&i32, 2)));
ast::CaseStatementList switch_body;
auto cond = std::make_unique<ast::IdentifierExpression>("a");
ast::CaseSelectorList csl;
csl.push_back(std::make_unique<ast::UintLiteral>(&u32, 1));
switch_body.push_back(std::make_unique<ast::CaseStatement>(
Source{12, 34}, std::move(csl), std::make_unique<ast::BlockStatement>()));
ast::CaseSelectorList default_csl;
auto block_default = std::make_unique<ast::BlockStatement>();
switch_body.push_back(std::make_unique<ast::CaseStatement>(
std::move(default_csl), std::move(block_default)));
auto block = std::make_unique<ast::BlockStatement>(); auto block = std::make_unique<ast::BlockStatement>();
block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var))); block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
@ -138,12 +177,12 @@ TEST_F(ValidateControlBlockTest, DISABLED_SwitchWithTwoDefault_Fail) {
EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error(); EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error();
EXPECT_FALSE(v()->ValidateStatements(block.get())); EXPECT_FALSE(v()->ValidateStatements(block.get()));
EXPECT_EQ(v()->error(), EXPECT_EQ(v()->error(),
"12:34: v-switch02: switch statement must have exactly one default " "12:34: v-switch03: the case selector values must have the same "
"clause"); "type as the selector expression.");
} }
TEST_F(ValidateControlBlockTest, TEST_F(ValidateControlBlockTest,
DISABLED_SwitchConditionTypeMustMatchSelectorType_Fail) { SwitchConditionTypeMustMatchSelectorType_Fail) {
// var a : u32 = 2; // var a : u32 = 2;
// switch (a) { // switch (a) {
// case -1: {} // case -1: {}
@ -176,14 +215,57 @@ TEST_F(ValidateControlBlockTest,
EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error(); EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error();
EXPECT_FALSE(v()->ValidateStatements(block.get())); EXPECT_FALSE(v()->ValidateStatements(block.get()));
EXPECT_EQ( EXPECT_EQ(v()->error(),
v()->error(), "12:34: v-switch03: the case selector values must have the same "
"12:34: v-switch03: the case selector values must have the same type " "type as the selector expression.");
"as the selector expression.");
} }
TEST_F(ValidateControlBlockTest, TEST_F(ValidateControlBlockTest, NonUniqueCaseSelectorValueUint_Fail) {
DISABLED_NonUniqueCaseSelectorLiteralValue_Fail) { // var a : u32 = 3;
// switch (a) {
// case 0: {}
// case 2, 2: {}
// default: {}
// }
ast::type::U32Type u32;
auto var =
std::make_unique<ast::Variable>("a", ast::StorageClass::kNone, &u32);
var->set_constructor(std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::UintLiteral>(&u32, 3)));
ast::CaseStatementList switch_body;
auto cond = std::make_unique<ast::IdentifierExpression>("a");
ast::CaseSelectorList csl_1;
csl_1.push_back(std::make_unique<ast::UintLiteral>(&u32, 0));
switch_body.push_back(std::make_unique<ast::CaseStatement>(
std::move(csl_1), std::make_unique<ast::BlockStatement>()));
ast::CaseSelectorList csl_2;
csl_2.push_back(std::make_unique<ast::UintLiteral>(&u32, 2));
csl_2.push_back(std::make_unique<ast::UintLiteral>(&u32, 2));
switch_body.push_back(std::make_unique<ast::CaseStatement>(
Source{12, 34}, std::move(csl_2),
std::make_unique<ast::BlockStatement>()));
ast::CaseSelectorList default_csl;
auto block_default = std::make_unique<ast::BlockStatement>();
switch_body.push_back(std::make_unique<ast::CaseStatement>(
std::move(default_csl), std::move(block_default)));
auto block = std::make_unique<ast::BlockStatement>();
block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
block->append(std::make_unique<ast::SwitchStatement>(std::move(cond),
std::move(switch_body)));
EXPECT_TRUE(td()->DetermineStatements(block.get())) << td()->error();
EXPECT_FALSE(v()->ValidateStatements(block.get()));
EXPECT_EQ(v()->error(),
"12:34: v-switch04: a literal value must not appear more than once "
"in the case selectors for a switch statement: '2'");
}
TEST_F(ValidateControlBlockTest, NonUniqueCaseSelectorValueSint_Fail) {
// var a : i32 = 2; // var a : i32 = 2;
// switch (a) { // switch (a) {
// case 10: {} // case 10: {}
@ -210,7 +292,7 @@ TEST_F(ValidateControlBlockTest,
csl_2.push_back(std::make_unique<ast::SintLiteral>(&i32, 2)); csl_2.push_back(std::make_unique<ast::SintLiteral>(&i32, 2));
csl_2.push_back(std::make_unique<ast::SintLiteral>(&i32, 10)); csl_2.push_back(std::make_unique<ast::SintLiteral>(&i32, 10));
switch_body.push_back(std::make_unique<ast::CaseStatement>( switch_body.push_back(std::make_unique<ast::CaseStatement>(
Source{12, 34}, std::move(csl_1), Source{12, 34}, std::move(csl_2),
std::make_unique<ast::BlockStatement>())); std::make_unique<ast::BlockStatement>()));
ast::CaseSelectorList default_csl; ast::CaseSelectorList default_csl;
@ -228,11 +310,10 @@ TEST_F(ValidateControlBlockTest,
EXPECT_EQ( EXPECT_EQ(
v()->error(), v()->error(),
"12:34: v-switch04: a literal value must not appear more than once in " "12:34: v-switch04: a literal value must not appear more than once in "
"the case selectors for a switch statement"); "the case selectors for a switch statement: '10'");
} }
TEST_F(ValidateControlBlockTest, TEST_F(ValidateControlBlockTest, LastClauseLastStatementIsFallthrough_Fail) {
DISABLED_LastClauseLastStatementIsFallthrough_Fail) {
// var a : i32 = 2; // var a : i32 = 2;
// switch (a) { // switch (a) {
// default: { fallthrough; } // default: { fallthrough; }
@ -246,10 +327,11 @@ TEST_F(ValidateControlBlockTest,
auto cond = std::make_unique<ast::IdentifierExpression>("a"); auto cond = std::make_unique<ast::IdentifierExpression>("a");
ast::CaseSelectorList default_csl; ast::CaseSelectorList default_csl;
auto block_default = std::make_unique<ast::BlockStatement>(); auto block_default = std::make_unique<ast::BlockStatement>();
block_default->append(std::make_unique<ast::FallthroughStatement>()); block_default->append(
std::make_unique<ast::FallthroughStatement>(Source{12, 34}));
ast::CaseStatementList body; ast::CaseStatementList body;
body.push_back(std::make_unique<ast::CaseStatement>( body.push_back(std::make_unique<ast::CaseStatement>(
Source{12, 34}, std::move(default_csl), std::move(block_default))); std::move(default_csl), std::move(block_default)));
auto block = std::make_unique<ast::BlockStatement>(); auto block = std::make_unique<ast::BlockStatement>();
block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var))); block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
@ -296,5 +378,50 @@ TEST_F(ValidateControlBlockTest, SwitchCase_Pass) {
EXPECT_TRUE(v()->ValidateStatements(block.get())) << v()->error(); EXPECT_TRUE(v()->ValidateStatements(block.get())) << v()->error();
} }
TEST_F(ValidateControlBlockTest, SwitchCaseAlias_Pass) {
// entry_point vertex = main
// type MyInt = u32;
// fn main()->void {
// var v: MyInt;
// switch(v){
// default: {}
// }
// }
ast::type::U32Type u32;
ast::type::AliasType my_int{"MyInt", &u32};
auto var =
std::make_unique<ast::Variable>("a", ast::StorageClass::kNone, &my_int);
var->set_constructor(std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::SintLiteral>(&u32, 2)));
auto cond = std::make_unique<ast::IdentifierExpression>("a");
ast::CaseSelectorList default_csl;
auto block_default = std::make_unique<ast::BlockStatement>();
ast::CaseStatementList body;
body.push_back(std::make_unique<ast::CaseStatement>(
Source{12, 34}, std::move(default_csl), std::move(block_default)));
auto block = std::make_unique<ast::BlockStatement>();
block->append(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
block->append(
std::make_unique<ast::SwitchStatement>(std::move(cond), std::move(body)));
block->append(std::make_unique<ast::ReturnStatement>());
ast::type::VoidType void_type;
ast::VariableList params;
auto func =
std::make_unique<ast::Function>("main", std::move(params), &void_type);
func->set_body(std::move(block));
auto entry_point = std::make_unique<ast::EntryPoint>(
ast::PipelineStage::kVertex, "", "main");
mod()->AddFunction(std::move(func));
mod()->AddAliasType(&my_int);
mod()->AddEntryPoint(std::move(entry_point));
EXPECT_TRUE(td()->Determine()) << td()->error();
EXPECT_TRUE(v()->Validate(mod())) << v()->error();
}
} // namespace } // namespace
} // namespace tint } // namespace tint

View File

@ -14,10 +14,17 @@
#include <cassert> #include <cassert>
#include "src/validator_impl.h" #include "src/validator_impl.h"
#include <unordered_set>
#include "src/ast/call_statement.h" #include "src/ast/call_statement.h"
#include "src/ast/function.h" #include "src/ast/function.h"
#include "src/ast/int_literal.h"
#include "src/ast/intrinsic.h" #include "src/ast/intrinsic.h"
#include "src/ast/sint_literal.h"
#include "src/ast/switch_statement.h"
#include "src/ast/type/i32_type.h"
#include "src/ast/type/u32_type.h"
#include "src/ast/type/void_type.h" #include "src/ast/type/void_type.h"
#include "src/ast/uint_literal.h"
#include "src/ast/variable_decl_statement.h" #include "src/ast/variable_decl_statement.h"
namespace tint { namespace tint {
@ -239,13 +246,94 @@ bool ValidatorImpl::ValidateStatement(const ast::Statement* stmt) {
if (stmt->IsCall()) { if (stmt->IsCall()) {
return ValidateCallExpr(stmt->AsCall()->expr()); return ValidateCallExpr(stmt->AsCall()->expr());
} }
if (stmt->IsSwitch()) {
return ValidateSwitch(stmt->AsSwitch());
}
if (stmt->IsCase()) {
return ValidateCase(stmt->AsCase());
}
return true;
}
bool ValidatorImpl::ValidateSwitch(const ast::SwitchStatement* s) {
if (!ValidateExpression(s->condition())) {
return false;
}
auto* cond_type = s->condition()->result_type()->UnwrapAliasPtrAlias();
if (!(cond_type->IsI32() || cond_type->IsU32())) {
set_error(s->condition()->source(),
"v-switch01: switch statement selector expression must be of a "
"scalar integer type");
return false;
}
int default_counter = 0;
std::unordered_set<int32_t> selector_set;
for (const auto& case_stmt : s->body()) {
if (!ValidateStatement(case_stmt.get())) {
return false;
}
if (case_stmt.get()->IsDefault()) {
default_counter++;
}
for (const auto& selector : case_stmt.get()->selectors()) {
auto* selector_ptr = selector.get();
if (cond_type != selector_ptr->type()) {
set_error(case_stmt.get()->source(),
"v-switch03: the case selector values must have the same "
"type as the selector expression.");
return false;
}
auto v = static_cast<int32_t>(selector_ptr->type()->IsU32()
? selector_ptr->AsUint()->value()
: selector_ptr->AsSint()->value());
if (selector_set.count(v)) {
auto v_str = selector_ptr->type()->IsU32()
? selector_ptr->AsUint()->to_str()
: selector_ptr->AsSint()->to_str();
set_error(
case_stmt.get()->source(),
"v-switch04: a literal value must not appear more than once in "
"the case selectors for a switch statement: '" +
v_str + "'");
return false;
}
selector_set.emplace(v);
}
}
if (default_counter != 1) {
set_error(s->source(),
"v-0008: switch statement must have exactly one default clause");
return false;
}
auto* last_clause = s->body().back().get();
auto* last_stmt_of_last_clause = last_clause->AsCase()->body()->last();
if (last_stmt_of_last_clause && last_stmt_of_last_clause->IsFallthrough()) {
set_error(last_stmt_of_last_clause->source(),
"v-switch05: a fallthrough statement must not appear as "
"the last statement in last clause of a switch");
return false;
}
return true;
}
bool ValidatorImpl::ValidateCase(const ast::CaseStatement* c) {
if (!ValidateStatement(c->body())) {
return false;
}
return true; return true;
} }
bool ValidatorImpl::ValidateCallExpr(const ast::CallExpression* expr) { bool ValidatorImpl::ValidateCallExpr(const ast::CallExpression* expr) {
if (!expr) { if (!expr) {
// TODO(sarahM0): Here and other Validate.*: figure out whether return false // TODO(sarahM0): Here and other Validate.*: figure out whether return
// or true // false or true
return false; return false;
} }

View File

@ -115,6 +115,14 @@ class ValidatorImpl {
/// @param eps the vector of entry points to check /// @param eps the vector of entry points to check
/// @return true if the validation was successful /// @return true if the validation was successful
bool ValidateEntryPoints(const ast::EntryPointList& eps); bool ValidateEntryPoints(const ast::EntryPointList& eps);
/// Validates switch statements
/// @param s the switch statement to check
/// @returns true if the valdiation was successful
bool ValidateSwitch(const ast::SwitchStatement* s);
/// Validates case statements
/// @param c the case statement to check
/// @returns true if the valdiation was successful
bool ValidateCase(const ast::CaseStatement* c);
private: private:
std::string error_; std::string error_;