AST fuzzer: mutation that deletes statements
Adds a mutation that deletes a statement. Fixes: tint:1110 Change-Id: I8a8595378481d12e7586103e2e88a0858d869ef9 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/88446 Commit-Queue: Alastair Donaldson <afdx@google.com> Reviewed-by: Ryan Harrison <rharrison@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
parent
a092cb7c36
commit
953d610aa2
|
@ -46,6 +46,8 @@ if (build_with_chromium) {
|
|||
"expression_size.cc",
|
||||
"expression_size.h",
|
||||
"fuzzer.cc",
|
||||
"jump_tracker.cc",
|
||||
"jump_tracker.h",
|
||||
"mutation.cc",
|
||||
"mutation.h",
|
||||
"mutation_finder.cc",
|
||||
|
@ -54,6 +56,8 @@ if (build_with_chromium) {
|
|||
"mutation_finders/change_binary_operators.h",
|
||||
"mutation_finders/change_unary_operators.cc",
|
||||
"mutation_finders/change_unary_operators.h",
|
||||
"mutation_finders/delete_statements.cc",
|
||||
"mutation_finders/delete_statements.h",
|
||||
"mutation_finders/replace_identifiers.cc",
|
||||
"mutation_finders/replace_identifiers.h",
|
||||
"mutation_finders/wrap_unary_operators.cc",
|
||||
|
@ -62,6 +66,8 @@ if (build_with_chromium) {
|
|||
"mutations/change_binary_operator.h",
|
||||
"mutations/change_unary_operator.cc",
|
||||
"mutations/change_unary_operator.h",
|
||||
"mutations/delete_statement.cc",
|
||||
"mutations/delete_statement.h",
|
||||
"mutations/replace_identifier.cc",
|
||||
"mutations/replace_identifier.h",
|
||||
"mutations/wrap_unary_operator.cc",
|
||||
|
|
|
@ -39,14 +39,17 @@ set(LIBTINT_AST_FUZZER_SOURCES
|
|||
../random_generator.h
|
||||
../random_generator_engine.h
|
||||
expression_size.h
|
||||
jump_tracker.h
|
||||
mutation.h
|
||||
mutation_finder.h
|
||||
mutation_finders/change_binary_operators.h
|
||||
mutation_finders/change_unary_operators.h
|
||||
mutation_finders/delete_statements.h
|
||||
mutation_finders/replace_identifiers.h
|
||||
mutation_finders/wrap_unary_operators.h
|
||||
mutations/change_binary_operator.h
|
||||
mutations/change_unary_operator.h
|
||||
mutations/delete_statement.h
|
||||
mutations/replace_identifier.h
|
||||
mutations/wrap_unary_operator.h
|
||||
mutator.h
|
||||
|
@ -61,14 +64,17 @@ set(LIBTINT_AST_FUZZER_SOURCES ${LIBTINT_AST_FUZZER_SOURCES}
|
|||
../random_generator.cc
|
||||
../random_generator_engine.cc
|
||||
expression_size.cc
|
||||
jump_tracker.cc
|
||||
mutation.cc
|
||||
mutation_finder.cc
|
||||
mutation_finders/change_binary_operators.cc
|
||||
mutation_finders/change_unary_operators.cc
|
||||
mutation_finders/delete_statements.cc
|
||||
mutation_finders/replace_identifiers.cc
|
||||
mutation_finders/wrap_unary_operators.cc
|
||||
mutations/change_binary_operator.cc
|
||||
mutations/change_unary_operator.cc
|
||||
mutations/delete_statement.cc
|
||||
mutations/replace_identifier.cc
|
||||
mutations/wrap_unary_operator.cc
|
||||
mutator.cc
|
||||
|
@ -107,8 +113,10 @@ add_tint_ast_fuzzer(tint_ast_wgsl_writer_fuzzer)
|
|||
if (${TINT_BUILD_TESTS})
|
||||
set(TEST_SOURCES
|
||||
expression_size_test.cc
|
||||
jump_tracker_test.cc
|
||||
mutations/change_binary_operator_test.cc
|
||||
mutations/change_unary_operator_test.cc
|
||||
mutations/delete_statement_test.cc
|
||||
mutations/replace_identifier_test.cc
|
||||
mutations/wrap_unary_operator_test.cc)
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2022 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "src/tint/ast/break_statement.h"
|
||||
#include "src/tint/ast/discard_statement.h"
|
||||
#include "src/tint/ast/for_loop_statement.h"
|
||||
#include "src/tint/ast/loop_statement.h"
|
||||
#include "src/tint/ast/return_statement.h"
|
||||
#include "src/tint/ast/switch_statement.h"
|
||||
#include "src/tint/ast/while_statement.h"
|
||||
#include "src/tint/sem/statement.h"
|
||||
|
||||
namespace tint::fuzzers::ast_fuzzer {
|
||||
|
||||
JumpTracker::JumpTracker(const Program& program) {
|
||||
// Consider every AST node, looking for break, return and discard statements.
|
||||
for (auto* node : program.ASTNodes().Objects()) {
|
||||
auto* stmt = node->As<ast::Statement>();
|
||||
if (stmt == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (stmt->As<ast::BreakStatement>()) {
|
||||
// This break statement either exits a loop or a switch statement.
|
||||
// Walk up the AST until either a loop or switch statement is found. In the former case,
|
||||
// it is the innermost loop containing the break statement, and thus all the nodes
|
||||
// encountered along the way are nodes that contain a break from the innermost loop.
|
||||
|
||||
// This records the statements encountered when walking up the AST from the break
|
||||
// statement to the innermost enclosing loop or switch statement.
|
||||
std::unordered_set<const ast::Statement*> candidate_statements;
|
||||
for (const ast::Statement* current = stmt;;
|
||||
current =
|
||||
As<sem::Statement>(program.Sem().Get(current))->Parent()->Declaration()) {
|
||||
if (current->Is<ast::ForLoopStatement>() || current->Is<ast::LoopStatement>() ||
|
||||
current->Is<ast::WhileStatement>()) {
|
||||
// A loop has been encountered, thus all that statements recorded until this
|
||||
// point contain a break from their innermost loop.
|
||||
for (auto* candidate : candidate_statements) {
|
||||
contains_break_for_innermost_loop_.insert(candidate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (current->Is<ast::SwitchStatement>()) {
|
||||
// A switch statement has been encountered, so the break does not correspond to
|
||||
// a loop break.
|
||||
break;
|
||||
}
|
||||
candidate_statements.insert(current);
|
||||
}
|
||||
} else if (stmt->As<ast::ReturnStatement>() || stmt->As<ast::DiscardStatement>()) {
|
||||
// Walk up the AST from the return or discard statement, recording that every node
|
||||
// encountered along the way contains a return/discard.
|
||||
auto& target_set = stmt->As<ast::ReturnStatement>() ? contains_return_
|
||||
: contains_intraprocedural_discard_;
|
||||
const ast::Statement* current = stmt;
|
||||
while (true) {
|
||||
target_set.insert(current);
|
||||
auto* parent = program.Sem().Get(current)->Parent();
|
||||
if (parent == nullptr) {
|
||||
break;
|
||||
}
|
||||
current = parent->Declaration();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace tint::fuzzers::ast_fuzzer
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2022 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_JUMP_TRACKER_H_
|
||||
#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_JUMP_TRACKER_H_
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "src/tint/ast/statement.h"
|
||||
#include "src/tint/program.h"
|
||||
|
||||
namespace tint::fuzzers::ast_fuzzer {
|
||||
|
||||
/// This class computes information on which statements contain loop breaks, returns and discards.
|
||||
/// It could be extended to handle other jumps, such as switch breaks and loop continues, should
|
||||
/// such information prove useful.
|
||||
class JumpTracker {
|
||||
public:
|
||||
/// Initializes jump tracking information for the given program.
|
||||
/// @param program - the program for which jumps will be tracked;
|
||||
/// must remain in scope as long as this instance exists.
|
||||
explicit JumpTracker(const Program& program);
|
||||
|
||||
/// Indicates whether a statement contains a break statement for the innermost loop (if any).
|
||||
/// @param statement - the statement of interest.
|
||||
/// @return true if and only if the statement is, or contains, a break for the innermost
|
||||
/// enclosing loop.
|
||||
bool ContainsBreakForInnermostLoop(const ast::Statement& statement) const {
|
||||
return contains_break_for_innermost_loop_.count(&statement) > 0;
|
||||
}
|
||||
|
||||
/// Indicates whether a statement contains a return statement.
|
||||
/// @param statement - the statement of interest.
|
||||
/// @return true if and only if the statement is, or contains, a return statement.
|
||||
bool ContainsReturn(const ast::Statement& statement) const {
|
||||
return contains_return_.count(&statement) > 0;
|
||||
}
|
||||
|
||||
/// Indicates whether a statement contains a discard statement.
|
||||
/// @param statement - the statement of interest.
|
||||
/// @return true if and only if the statement is, or contains, a discard statement. This is
|
||||
/// determined in an intraprocedural fashion: the answer will be "false" if no discard occurs
|
||||
/// inside the statement, even if the statement calls a function that may lead to a discard
|
||||
/// being performed.
|
||||
bool ContainsIntraproceduralDiscard(const ast::Statement& statement) const {
|
||||
return contains_intraprocedural_discard_.count(&statement) > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_set<const ast::Statement*> contains_break_for_innermost_loop_;
|
||||
std::unordered_set<const ast::Statement*> contains_return_;
|
||||
std::unordered_set<const ast::Statement*> contains_intraprocedural_discard_;
|
||||
};
|
||||
|
||||
} // namespace tint::fuzzers::ast_fuzzer
|
||||
|
||||
#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_JUMP_TRACKER_H_
|
|
@ -0,0 +1,332 @@
|
|||
// Copyright 2022 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "src/tint/ast/block_statement.h"
|
||||
#include "src/tint/ast/break_statement.h"
|
||||
#include "src/tint/ast/discard_statement.h"
|
||||
#include "src/tint/ast/for_loop_statement.h"
|
||||
#include "src/tint/ast/if_statement.h"
|
||||
#include "src/tint/ast/loop_statement.h"
|
||||
#include "src/tint/ast/module.h"
|
||||
#include "src/tint/ast/return_statement.h"
|
||||
#include "src/tint/ast/switch_statement.h"
|
||||
#include "src/tint/ast/while_statement.h"
|
||||
#include "src/tint/program.h"
|
||||
#include "src/tint/reader/wgsl/parser.h"
|
||||
|
||||
namespace tint::fuzzers::ast_fuzzer {
|
||||
namespace {
|
||||
|
||||
TEST(JumpTrackerTest, Breaks) {
|
||||
std::string content = R"(
|
||||
fn main() {
|
||||
var x : u32;
|
||||
for (var i : i32 = 0; i < 100; i++) {
|
||||
if (i == 40) {
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (var j : i32 = 0; j < 10; j++) {
|
||||
loop {
|
||||
if (i > j) {
|
||||
break;
|
||||
}
|
||||
continuing {
|
||||
i++;
|
||||
j-=2;
|
||||
}
|
||||
}
|
||||
switch (j) {
|
||||
case 0: {
|
||||
if (i == j) {
|
||||
break;
|
||||
}
|
||||
i = i + 1;
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
Source::File file("test.wgsl", content);
|
||||
auto program = reader::wgsl::Parse(&file);
|
||||
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
|
||||
|
||||
JumpTracker jump_tracker(program);
|
||||
|
||||
const auto* outer_loop_body =
|
||||
program.AST().Functions()[0]->body->statements[1]->As<ast::ForLoopStatement>()->body;
|
||||
const auto* first_if = outer_loop_body->statements[0]->As<ast::IfStatement>();
|
||||
const auto* first_if_body = first_if->body;
|
||||
const auto* block_in_first_if = first_if_body->statements[0]->As<ast::BlockStatement>();
|
||||
const auto* break_in_first_if = block_in_first_if->statements[0]->As<ast::BreakStatement>();
|
||||
|
||||
const auto* innermost_loop_body = outer_loop_body->statements[1]
|
||||
->As<ast::ForLoopStatement>()
|
||||
->body->statements[0]
|
||||
->As<ast::LoopStatement>()
|
||||
->body;
|
||||
const auto* innermost_loop_if = innermost_loop_body->statements[0]->As<ast::IfStatement>();
|
||||
const auto* innermost_loop_if_body = innermost_loop_if->body;
|
||||
const auto* break_in_innermost_loop =
|
||||
innermost_loop_if_body->statements[0]->As<ast::BreakStatement>();
|
||||
|
||||
std::unordered_set<const ast::Statement*> containing_loop_break = {
|
||||
outer_loop_body, first_if,
|
||||
first_if_body, block_in_first_if,
|
||||
break_in_first_if, innermost_loop_body,
|
||||
innermost_loop_if, innermost_loop_if_body,
|
||||
break_in_innermost_loop};
|
||||
|
||||
for (auto* node : program.ASTNodes().Objects()) {
|
||||
auto* stmt = node->As<ast::Statement>();
|
||||
if (stmt == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (containing_loop_break.count(stmt) > 0) {
|
||||
ASSERT_TRUE(jump_tracker.ContainsBreakForInnermostLoop(*stmt));
|
||||
} else {
|
||||
ASSERT_FALSE(jump_tracker.ContainsBreakForInnermostLoop(*stmt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JumpTrackerTest, Returns) {
|
||||
std::string content = R"(
|
||||
fn main() {
|
||||
var x : u32;
|
||||
for (var i : i32 = 0; i < 100; i++) {
|
||||
if (i == 40) {
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (var j : i32 = 0; j < 10; j++) {
|
||||
loop {
|
||||
if (i > j) {
|
||||
return;
|
||||
}
|
||||
continuing {
|
||||
i++;
|
||||
j-=2;
|
||||
}
|
||||
}
|
||||
switch (j) {
|
||||
case 0: {
|
||||
if (i == j) {
|
||||
break;
|
||||
}
|
||||
i = i + 1;
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
Source::File file("test.wgsl", content);
|
||||
auto program = reader::wgsl::Parse(&file);
|
||||
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
|
||||
|
||||
JumpTracker jump_tracker(program);
|
||||
|
||||
const auto* function_body = program.AST().Functions()[0]->body;
|
||||
const auto* outer_loop = function_body->statements[1]->As<ast::ForLoopStatement>();
|
||||
const auto* outer_loop_body = outer_loop->body;
|
||||
const auto* first_if = outer_loop_body->statements[0]->As<ast::IfStatement>();
|
||||
const auto* first_if_body = first_if->body;
|
||||
const auto* block_in_first_if = first_if_body->statements[0]->As<ast::BlockStatement>();
|
||||
const auto* return_in_first_if = block_in_first_if->statements[0]->As<ast::ReturnStatement>();
|
||||
const auto* inner_for_loop = outer_loop_body->statements[1]->As<ast::ForLoopStatement>();
|
||||
const auto* inner_for_loop_body = inner_for_loop->body;
|
||||
const auto* innermost_loop = inner_for_loop_body->statements[0]->As<ast::LoopStatement>();
|
||||
const auto* innermost_loop_body = innermost_loop->body;
|
||||
const auto* innermost_loop_if = innermost_loop_body->statements[0]->As<ast::IfStatement>();
|
||||
const auto* innermost_loop_if_body = innermost_loop_if->body;
|
||||
const auto* return_in_innermost_loop =
|
||||
innermost_loop_if_body->statements[0]->As<ast::ReturnStatement>();
|
||||
const auto* switch_statement = inner_for_loop_body->statements[1]->As<ast::SwitchStatement>();
|
||||
const auto* default_statement = switch_statement->body[1];
|
||||
const auto* default_statement_body = default_statement->body;
|
||||
const auto* return_in_default_statement =
|
||||
default_statement_body->statements[0]->As<ast::ReturnStatement>();
|
||||
|
||||
std::unordered_set<const ast::Statement*> containing_return = {
|
||||
function_body, outer_loop,
|
||||
outer_loop_body, first_if,
|
||||
first_if_body, block_in_first_if,
|
||||
return_in_first_if, inner_for_loop,
|
||||
inner_for_loop_body, innermost_loop,
|
||||
innermost_loop_body, innermost_loop_if,
|
||||
innermost_loop_if_body, return_in_innermost_loop,
|
||||
switch_statement, default_statement,
|
||||
default_statement_body, return_in_default_statement};
|
||||
|
||||
for (auto* node : program.ASTNodes().Objects()) {
|
||||
auto* stmt = node->As<ast::Statement>();
|
||||
if (stmt == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (containing_return.count(stmt) > 0) {
|
||||
ASSERT_TRUE(jump_tracker.ContainsReturn(*stmt));
|
||||
} else {
|
||||
ASSERT_FALSE(jump_tracker.ContainsReturn(*stmt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JumpTrackerTest, Discards) {
|
||||
std::string content = R"(
|
||||
fn main() {
|
||||
var x : u32;
|
||||
for (var i : i32 = 0; i < 100; i++) {
|
||||
if (i == 40) {
|
||||
{
|
||||
discard;
|
||||
}
|
||||
}
|
||||
for (var j : i32 = 0; j < 10; j++) {
|
||||
loop {
|
||||
if (i > j) {
|
||||
discard;
|
||||
}
|
||||
continuing {
|
||||
i++;
|
||||
j-=2;
|
||||
}
|
||||
}
|
||||
switch (j) {
|
||||
case 0: {
|
||||
if (i == j) {
|
||||
break;
|
||||
}
|
||||
i = i + 1;
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
Source::File file("test.wgsl", content);
|
||||
auto program = reader::wgsl::Parse(&file);
|
||||
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
|
||||
|
||||
JumpTracker jump_tracker(program);
|
||||
|
||||
const auto* function_body = program.AST().Functions()[0]->body;
|
||||
const auto* outer_loop = function_body->statements[1]->As<ast::ForLoopStatement>();
|
||||
const auto* outer_loop_body = outer_loop->body;
|
||||
const auto* first_if = outer_loop_body->statements[0]->As<ast::IfStatement>();
|
||||
const auto* first_if_body = first_if->body;
|
||||
const auto* block_in_first_if = first_if_body->statements[0]->As<ast::BlockStatement>();
|
||||
const auto* discard_in_first_if = block_in_first_if->statements[0]->As<ast::DiscardStatement>();
|
||||
const auto* inner_for_loop = outer_loop_body->statements[1]->As<ast::ForLoopStatement>();
|
||||
const auto* inner_for_loop_body = inner_for_loop->body;
|
||||
const auto* innermost_loop = inner_for_loop_body->statements[0]->As<ast::LoopStatement>();
|
||||
const auto* innermost_loop_body = innermost_loop->body;
|
||||
const auto* innermost_loop_if = innermost_loop_body->statements[0]->As<ast::IfStatement>();
|
||||
const auto* innermost_loop_if_body = innermost_loop_if->body;
|
||||
const auto* discard_in_innermost_loop =
|
||||
innermost_loop_if_body->statements[0]->As<ast::DiscardStatement>();
|
||||
const auto* switch_statement = inner_for_loop_body->statements[1]->As<ast::SwitchStatement>();
|
||||
const auto* default_statement = switch_statement->body[1];
|
||||
const auto* default_statement_body = default_statement->body;
|
||||
const auto* discard_in_default_statement =
|
||||
default_statement_body->statements[0]->As<ast::DiscardStatement>();
|
||||
|
||||
std::unordered_set<const ast::Statement*> containing_discard = {
|
||||
function_body, outer_loop,
|
||||
outer_loop_body, first_if,
|
||||
first_if_body, block_in_first_if,
|
||||
discard_in_first_if, inner_for_loop,
|
||||
inner_for_loop_body, innermost_loop,
|
||||
innermost_loop_body, innermost_loop_if,
|
||||
innermost_loop_if_body, discard_in_innermost_loop,
|
||||
switch_statement, default_statement,
|
||||
default_statement_body, discard_in_default_statement};
|
||||
|
||||
for (auto* node : program.ASTNodes().Objects()) {
|
||||
auto* stmt = node->As<ast::Statement>();
|
||||
if (stmt == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (containing_discard.count(stmt) > 0) {
|
||||
ASSERT_TRUE(jump_tracker.ContainsIntraproceduralDiscard(*stmt));
|
||||
} else {
|
||||
ASSERT_FALSE(jump_tracker.ContainsIntraproceduralDiscard(*stmt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JumpTrackerTest, WhileLoop) {
|
||||
std::string content = R"(
|
||||
fn main() {
|
||||
var x : u32;
|
||||
x = 0;
|
||||
while (x < 100) {
|
||||
if (x > 50) {
|
||||
break;
|
||||
}
|
||||
x = x + 1;
|
||||
}
|
||||
}
|
||||
)";
|
||||
Source::File file("test.wgsl", content);
|
||||
auto program = reader::wgsl::Parse(&file);
|
||||
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
|
||||
|
||||
JumpTracker jump_tracker(program);
|
||||
|
||||
const auto* while_loop_body =
|
||||
program.AST().Functions()[0]->body->statements[2]->As<ast::WhileStatement>()->body;
|
||||
const auto* if_statement = while_loop_body->statements[0]->As<ast::IfStatement>();
|
||||
const auto* if_statement_body = if_statement->body;
|
||||
const auto* break_in_if = if_statement_body->statements[0]->As<ast::BreakStatement>();
|
||||
|
||||
std::unordered_set<const ast::Statement*> containing_loop_break = {
|
||||
while_loop_body, if_statement, if_statement_body, break_in_if};
|
||||
|
||||
for (auto* node : program.ASTNodes().Objects()) {
|
||||
auto* stmt = node->As<ast::Statement>();
|
||||
if (stmt == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (containing_loop_break.count(stmt) > 0) {
|
||||
ASSERT_TRUE(jump_tracker.ContainsBreakForInnermostLoop(*stmt));
|
||||
} else {
|
||||
ASSERT_FALSE(jump_tracker.ContainsBreakForInnermostLoop(*stmt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tint::fuzzers::ast_fuzzer
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
|
||||
|
||||
|
@ -33,6 +34,8 @@ std::unique_ptr<Mutation> Mutation::FromMessage(const protobufs::Mutation& messa
|
|||
return std::make_unique<MutationReplaceIdentifier>(message.replace_identifier());
|
||||
case protobufs::Mutation::kChangeBinaryOperator:
|
||||
return std::make_unique<MutationChangeBinaryOperator>(message.change_binary_operator());
|
||||
case protobufs::Mutation::kDeleteStatement:
|
||||
return std::make_unique<MutationDeleteStatement>(message.delete_statement());
|
||||
case protobufs::Mutation::kWrapUnaryOperator:
|
||||
return std::make_unique<MutationWrapUnaryOperator>(message.wrap_unary_operator());
|
||||
case protobufs::Mutation::MUTATION_NOT_SET:
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
|
||||
#include "src/tint/sem/expression.h"
|
||||
#include "src/tint/sem/statement.h"
|
||||
#include "src/tint/sem/variable.h"
|
||||
|
||||
namespace tint::fuzzers::ast_fuzzer {
|
||||
|
||||
MutationList MutationFinderDeleteStatements::FindMutations(const tint::Program& program,
|
||||
NodeIdMap* node_id_map,
|
||||
ProbabilityContext* /*unused*/) const {
|
||||
MutationList result;
|
||||
|
||||
JumpTracker jump_tracker(program);
|
||||
|
||||
// Consider every statement node in the AST.
|
||||
for (auto* node : program.ASTNodes().Objects()) {
|
||||
auto* statement_node = tint::As<ast::Statement>(node);
|
||||
|
||||
if (!statement_node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* statement_sem_node =
|
||||
tint::As<sem::Statement>(program.Sem().Get(statement_node));
|
||||
|
||||
// Semantic information for the node is required in order to delete it.
|
||||
if (!statement_sem_node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that this kind of statement can be deleted.
|
||||
if (!MutationDeleteStatement::CanBeDeleted(*statement_node, program, jump_tracker)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push_back(
|
||||
std::make_unique<MutationDeleteStatement>(node_id_map->GetId(statement_node)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t MutationFinderDeleteStatements::GetChanceOfApplyingMutation(
|
||||
ProbabilityContext* probability_context) const {
|
||||
return probability_context->GetChanceOfDeletingStatements();
|
||||
}
|
||||
|
||||
} // namespace tint::fuzzers::ast_fuzzer
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_DELETE_STATEMENTS_H_
|
||||
#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_DELETE_STATEMENTS_H_
|
||||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h"
|
||||
|
||||
namespace tint::fuzzers::ast_fuzzer {
|
||||
|
||||
/// Looks for opportunities to apply
|
||||
/// `MutationFinderDeleteStatements`.
|
||||
///
|
||||
/// Considers statements for deletion if their deletion would not make the module invalid.
|
||||
class MutationFinderDeleteStatements : public MutationFinder {
|
||||
public:
|
||||
MutationList FindMutations(const tint::Program& program,
|
||||
NodeIdMap* node_id_map,
|
||||
ProbabilityContext* probability_context) const override;
|
||||
uint32_t GetChanceOfApplyingMutation(ProbabilityContext* probability_context) const override;
|
||||
};
|
||||
|
||||
} // namespace tint::fuzzers::ast_fuzzer
|
||||
|
||||
#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_DELETE_STATEMENTS_H_
|
|
@ -0,0 +1,208 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "src/tint/ast/block_statement.h"
|
||||
#include "src/tint/ast/fallthrough_statement.h"
|
||||
#include "src/tint/ast/for_loop_statement.h"
|
||||
#include "src/tint/ast/if_statement.h"
|
||||
#include "src/tint/ast/loop_statement.h"
|
||||
#include "src/tint/ast/variable_decl_statement.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
|
||||
#include "src/tint/program_builder.h"
|
||||
#include "src/tint/sem/for_loop_statement.h"
|
||||
#include "src/tint/sem/if_statement.h"
|
||||
#include "src/tint/sem/loop_statement.h"
|
||||
#include "src/tint/sem/statement.h"
|
||||
|
||||
namespace tint::fuzzers::ast_fuzzer {
|
||||
|
||||
MutationDeleteStatement::MutationDeleteStatement(protobufs::MutationDeleteStatement message)
|
||||
: message_(std::move(message)) {}
|
||||
|
||||
MutationDeleteStatement::MutationDeleteStatement(uint32_t statement_id) {
|
||||
message_.set_statement_id(statement_id);
|
||||
}
|
||||
|
||||
bool MutationDeleteStatement::IsApplicable(const tint::Program& program,
|
||||
const NodeIdMap& node_id_map) const {
|
||||
auto* statement_node = tint::As<ast::Statement>(node_id_map.GetNode(message_.statement_id()));
|
||||
|
||||
if (!statement_node) {
|
||||
// The statement id is invalid or does not refer to a statement.
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* statement_sem_node = tint::As<sem::Statement>(program.Sem().Get(statement_node));
|
||||
|
||||
if (!statement_sem_node) {
|
||||
// Semantic information for the statement is not available. This
|
||||
// information is required in order to perform the deletion.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether it is OK to delete this statement.
|
||||
if (!CanBeDeleted(*statement_node, program, JumpTracker(program))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MutationDeleteStatement::Apply(const NodeIdMap& node_id_map,
|
||||
tint::CloneContext* clone_context,
|
||||
NodeIdMap* /* unused */) const {
|
||||
const auto* statement_node =
|
||||
tint::As<ast::Statement>(node_id_map.GetNode(message_.statement_id()));
|
||||
const auto* statement_sem_node =
|
||||
tint::As<sem::Statement>(clone_context->src->Sem().Get(statement_node));
|
||||
const auto* sem_parent = statement_sem_node->Parent();
|
||||
|
||||
if (tint::Is<sem::IfStatement>(sem_parent) &&
|
||||
tint::As<ast::IfStatement>(sem_parent->Declaration())->else_statement == statement_node) {
|
||||
// Remove the "else" part of an if statement.
|
||||
clone_context->Replace(statement_node, static_cast<const ast::Statement*>(nullptr));
|
||||
} else if (tint::Is<sem::ForLoopStatement>(sem_parent) &&
|
||||
tint::As<ast::ForLoopStatement>(sem_parent->Declaration())->initializer ==
|
||||
statement_node) {
|
||||
// Remove the initializer of a for loop.
|
||||
clone_context->Replace(statement_node, static_cast<const ast::Statement*>(nullptr));
|
||||
} else if (tint::Is<sem::ForLoopStatement>(sem_parent) &&
|
||||
tint::As<ast::ForLoopStatement>(sem_parent->Declaration())->continuing ==
|
||||
statement_node) {
|
||||
// Remove the "continuing" statement of a for loop.
|
||||
clone_context->Replace(statement_node, static_cast<const ast::Statement*>(nullptr));
|
||||
} else if (tint::Is<sem::LoopContinuingBlockStatement>(statement_sem_node)) {
|
||||
// Remove the "continuing" block of a loop.
|
||||
clone_context->Replace(statement_node, static_cast<const ast::Statement*>(nullptr));
|
||||
} else if (tint::Is<ast::CaseStatement>(statement_node)) {
|
||||
// Remove a case statement from its enclosing switch statement.
|
||||
const std::vector<const ast::CaseStatement*>* case_statement_list =
|
||||
&sem_parent->Declaration()->As<ast::SwitchStatement>()->body;
|
||||
assert(std::find(case_statement_list->begin(), case_statement_list->end(),
|
||||
statement_node) != case_statement_list->end() &&
|
||||
"Statement not found.");
|
||||
clone_context->Remove(*case_statement_list, statement_node);
|
||||
} else if (tint::Is<ast::BlockStatement>(statement_node)) {
|
||||
// Remove a block statement from the block that encloses it. A special case is required for
|
||||
// this, since a sem::Block has itself as its associated sem::Block, so it is necessary to
|
||||
// look at the parent to get the enclosing block.
|
||||
const auto& statement_list =
|
||||
sem_parent->Declaration()->As<ast::BlockStatement>()->statements;
|
||||
assert(std::find(statement_list.begin(), statement_list.end(), statement_node) !=
|
||||
statement_list.end() &&
|
||||
"Statement not found.");
|
||||
clone_context->Remove(statement_list, statement_node);
|
||||
} else {
|
||||
// Remove a non-block statement from the block that encloses it.
|
||||
const auto& statement_list =
|
||||
statement_sem_node->Block()->Declaration()->As<ast::BlockStatement>()->statements;
|
||||
assert(std::find(statement_list.begin(), statement_list.end(), statement_node) !=
|
||||
statement_list.end() &&
|
||||
"Statement not found.");
|
||||
clone_context->Remove(statement_list, statement_node);
|
||||
}
|
||||
}
|
||||
|
||||
protobufs::Mutation MutationDeleteStatement::ToMessage() const {
|
||||
protobufs::Mutation mutation;
|
||||
*mutation.mutable_delete_statement() = message_;
|
||||
return mutation;
|
||||
}
|
||||
|
||||
bool MutationDeleteStatement::CanBeDeleted(const ast::Statement& statement_node,
|
||||
const Program& program,
|
||||
const JumpTracker& jump_tracker) {
|
||||
if (statement_node.Is<ast::VariableDeclStatement>()) {
|
||||
// This is conservative. It would be possible to delete variable declarations if they are
|
||||
// not used. Further analysis could allow that.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jump_tracker.ContainsReturn(statement_node) ||
|
||||
jump_tracker.ContainsIntraproceduralDiscard(statement_node)) {
|
||||
// This is conservative. It would be possible to delete a return/discard statement as long
|
||||
// as there is still a return/discard on every control flow path.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jump_tracker.ContainsBreakForInnermostLoop(statement_node)) {
|
||||
// This is conservative. Disallowing the removal of breaks ensures that loops cannot become
|
||||
// statically infinite. However, a loop might in practice have multiple breaks, some of
|
||||
// which can be removed.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto* case_statement = statement_node.As<ast::CaseStatement>()) {
|
||||
// It is not OK to delete the final case statement in a switch statement if the penultimate
|
||||
// case statement falls through to the final case statement.
|
||||
auto* switch_statement =
|
||||
program.Sem().Get(case_statement)->Parent()->Declaration()->As<ast::SwitchStatement>();
|
||||
|
||||
if (switch_statement->body.size() > 1 &&
|
||||
switch_statement->body[switch_statement->body.size() - 1] == case_statement) {
|
||||
// There are at least two case statements, and this is the final case statement.
|
||||
auto& penultimate_case_statement_body_statements =
|
||||
switch_statement->body[switch_statement->body.size() - 2]->body->statements;
|
||||
if (penultimate_case_statement_body_statements.size() > 0 &&
|
||||
penultimate_case_statement_body_statements
|
||||
[penultimate_case_statement_body_statements.size() - 1]
|
||||
->Is<ast::FallthroughStatement>()) {
|
||||
// The penultimate case statement falls through to the final case statement, thus
|
||||
// the final case statement cannot be removed.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto* parent_sem = program.Sem().Get(&statement_node)->Parent();
|
||||
if (parent_sem == nullptr) {
|
||||
// Semantic information for the parent node is required.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* parent_stmt = parent_sem->Declaration();
|
||||
|
||||
// It does not make sense to delete the entire body of a loop or if statement.
|
||||
if (auto* for_loop = parent_stmt->As<ast::ForLoopStatement>()) {
|
||||
if (for_loop->body == &statement_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (auto* loop = parent_stmt->As<ast::LoopStatement>()) {
|
||||
if (loop->body == &statement_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (auto* while_loop = parent_stmt->As<ast::WhileStatement>()) {
|
||||
if (while_loop->body == &statement_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (auto* if_statement = parent_stmt->As<ast::IfStatement>()) {
|
||||
if (if_statement->body == &statement_node) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace tint::fuzzers::ast_fuzzer
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_DELETE_STATEMENT_H_
|
||||
#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_DELETE_STATEMENT_H_
|
||||
|
||||
#include "src/tint/ast/statement.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/jump_tracker.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h"
|
||||
|
||||
namespace tint::fuzzers::ast_fuzzer {
|
||||
|
||||
/// @see MutationDeleteStatement::Apply
|
||||
class MutationDeleteStatement : public Mutation {
|
||||
public:
|
||||
/// @brief Constructs an instance of this mutation from a protobuf message.
|
||||
/// @param message - protobuf message
|
||||
explicit MutationDeleteStatement(protobufs::MutationDeleteStatement message);
|
||||
|
||||
/// @brief Constructor.
|
||||
/// @param statement_id - the id of the statement to delete.
|
||||
explicit MutationDeleteStatement(uint32_t statement_id);
|
||||
|
||||
/// @copybrief Mutation::IsApplicable
|
||||
///
|
||||
/// The mutation is applicable iff:
|
||||
/// - `statement_id` corresponds to a statement in the AST.
|
||||
/// - `statement_id` does not refer to a variable declaration, since the declared variables will
|
||||
/// be inaccessible if the statement is deleted.
|
||||
/// - `statement_id` is not a return statement, since removing return statements arbitrarily can
|
||||
/// make the program invalid.
|
||||
/// - `statement_id` is not a break statement, since removing break statements can lead to
|
||||
/// syntactically infinite loops.
|
||||
///
|
||||
/// @copydetails Mutation::IsApplicable
|
||||
bool IsApplicable(const tint::Program& program, const NodeIdMap& node_id_map) const override;
|
||||
|
||||
/// @copybrief Mutation::Apply
|
||||
///
|
||||
/// Delete the statement referenced by `statement_id`.
|
||||
///
|
||||
/// @copydetails Mutation::Apply
|
||||
void Apply(const NodeIdMap& node_id_map,
|
||||
tint::CloneContext* clone_context,
|
||||
NodeIdMap* new_node_id_map) const override;
|
||||
|
||||
protobufs::Mutation ToMessage() const override;
|
||||
|
||||
/// Return whether the given statement is suitable for deletion.
|
||||
/// @param statement_node - the statement to be considered for deletion.
|
||||
/// @param program - the program containing the statement.
|
||||
/// @param jump_tracker - information about jump statements for the program.
|
||||
/// @return true if and only if it is OK to delete the statement.
|
||||
static bool CanBeDeleted(const ast::Statement& statement_node,
|
||||
const Program& program,
|
||||
const JumpTracker& jump_tracker);
|
||||
|
||||
private:
|
||||
protobufs::MutationDeleteStatement message_;
|
||||
};
|
||||
|
||||
} // namespace tint::fuzzers::ast_fuzzer
|
||||
|
||||
#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_DELETE_STATEMENT_H_
|
|
@ -0,0 +1,750 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/delete_statement.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "src/tint/ast/assignment_statement.h"
|
||||
#include "src/tint/ast/block_statement.h"
|
||||
#include "src/tint/ast/case_statement.h"
|
||||
#include "src/tint/ast/fallthrough_statement.h"
|
||||
#include "src/tint/ast/for_loop_statement.h"
|
||||
#include "src/tint/ast/if_statement.h"
|
||||
#include "src/tint/ast/switch_statement.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h"
|
||||
#include "src/tint/program_builder.h"
|
||||
#include "src/tint/reader/wgsl/parser.h"
|
||||
#include "src/tint/writer/wgsl/generator.h"
|
||||
|
||||
namespace tint::fuzzers::ast_fuzzer {
|
||||
namespace {
|
||||
|
||||
void CheckStatementDeletionWorks(
|
||||
const std::string& original,
|
||||
const std::string& expected,
|
||||
const std::function<const ast::Statement*(const Program&)>& statement_finder) {
|
||||
Source::File original_file("original.wgsl", original);
|
||||
auto program = reader::wgsl::Parse(&original_file);
|
||||
|
||||
Source::File expected_file("expected.wgsl", expected);
|
||||
auto expected_program = reader::wgsl::Parse(&expected_file);
|
||||
|
||||
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
|
||||
ASSERT_TRUE(expected_program.IsValid()) << expected_program.Diagnostics().str();
|
||||
|
||||
NodeIdMap node_id_map(program);
|
||||
const auto* statement = statement_finder(program);
|
||||
ASSERT_NE(statement, nullptr);
|
||||
auto statement_id = node_id_map.GetId(statement);
|
||||
ASSERT_NE(statement_id, 0);
|
||||
ASSERT_TRUE(MaybeApplyMutation(program, MutationDeleteStatement(statement_id), node_id_map,
|
||||
&program, &node_id_map, nullptr));
|
||||
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
|
||||
writer::wgsl::Options options;
|
||||
auto transformed_result = writer::wgsl::Generate(&program, options);
|
||||
auto expected_result = writer::wgsl::Generate(&expected_program, options);
|
||||
ASSERT_TRUE(transformed_result.success) << transformed_result.error;
|
||||
ASSERT_TRUE(expected_result.success) << expected_result.error;
|
||||
ASSERT_EQ(expected_result.wgsl, transformed_result.wgsl);
|
||||
}
|
||||
|
||||
void CheckStatementDeletionNotAllowed(
|
||||
const std::string& original,
|
||||
const std::function<const ast::Statement*(const Program&)>& statement_finder) {
|
||||
Source::File original_file("original.wgsl", original);
|
||||
auto program = reader::wgsl::Parse(&original_file);
|
||||
|
||||
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
|
||||
|
||||
NodeIdMap node_id_map(program);
|
||||
const auto* statement = statement_finder(program);
|
||||
ASSERT_NE(statement, nullptr);
|
||||
auto statement_id = node_id_map.GetId(statement);
|
||||
ASSERT_NE(statement_id, 0);
|
||||
ASSERT_FALSE(MaybeApplyMutation(program, MutationDeleteStatement(statement_id), node_id_map,
|
||||
&program, &node_id_map, nullptr));
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteAssignStatement) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
{
|
||||
var a : i32 = 5;
|
||||
a = 6;
|
||||
}
|
||||
})";
|
||||
auto expected = R"(fn main() {
|
||||
{
|
||||
var a : i32 = 5;
|
||||
}
|
||||
}
|
||||
)";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::BlockStatement>()
|
||||
->statements[1]
|
||||
->As<ast::AssignmentStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteForStatement) {
|
||||
auto original =
|
||||
R"(
|
||||
fn main() {
|
||||
for (var i : i32 = 0; i < 10; i++) {
|
||||
}
|
||||
}
|
||||
)";
|
||||
auto expected = "fn main() { }";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::ForLoopStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteIfStatement) {
|
||||
auto original =
|
||||
R"(
|
||||
fn main() {
|
||||
if (true) { } else { }
|
||||
}
|
||||
)";
|
||||
auto expected = "fn main() { }";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::IfStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteBlockStatement) {
|
||||
auto original = "fn main() { { } }";
|
||||
auto expected = "fn main() { }";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::BlockStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteSwitchStatement) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
switch(1) {
|
||||
case 0, 1: {
|
||||
}
|
||||
default: {
|
||||
fallthrough;
|
||||
}
|
||||
case 2: {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto expected = R"(fn main() { })";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::SwitchStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteCaseStatement) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
switch(1) {
|
||||
case 0, 1: {
|
||||
}
|
||||
default: {
|
||||
fallthrough;
|
||||
}
|
||||
case 2: {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
switch(1) {
|
||||
default: {
|
||||
fallthrough;
|
||||
}
|
||||
case 2: {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::SwitchStatement>()
|
||||
->body[0]
|
||||
->As<ast::CaseStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteFallthroughStatement) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
switch(1) {
|
||||
case 0, 1: {
|
||||
}
|
||||
default: {
|
||||
fallthrough;
|
||||
}
|
||||
case 2: {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
switch(1) {
|
||||
case 0, 1: {
|
||||
}
|
||||
default: {
|
||||
}
|
||||
case 2: {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::SwitchStatement>()
|
||||
->body[1]
|
||||
->As<ast::CaseStatement>()
|
||||
->body->statements[0]
|
||||
->As<ast::FallthroughStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteElse) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
if (true) {
|
||||
} else {
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
if (true) {
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::IfStatement>()
|
||||
->else_statement;
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteCall) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
sin(1.0);
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::CallStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteCompoundAssign) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
x += 2;;
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[1]
|
||||
->As<ast::CompoundAssignmentStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteLoop) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
loop {
|
||||
if (x > 100) {
|
||||
break;
|
||||
}
|
||||
continuing {
|
||||
x++;
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[1]->As<ast::LoopStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteContinuingBlock) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
loop {
|
||||
if (x > 100) {
|
||||
break;
|
||||
}
|
||||
continuing {
|
||||
x++;
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
loop {
|
||||
if (x > 100) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[1]
|
||||
->As<ast::LoopStatement>()
|
||||
->continuing;
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteContinue) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
loop {
|
||||
if (x > 100) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
continuing {
|
||||
x++;
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
loop {
|
||||
if (x > 100) {
|
||||
break;
|
||||
}
|
||||
continuing {
|
||||
x++;
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[1]
|
||||
->As<ast::LoopStatement>()
|
||||
->body->statements[1]
|
||||
->As<ast::ContinueStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteIncrement) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
loop {
|
||||
if (x > 100) {
|
||||
break;
|
||||
}
|
||||
continuing {
|
||||
x++;
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
var x : i32 = 0;
|
||||
loop {
|
||||
if (x > 100) {
|
||||
break;
|
||||
}
|
||||
continuing {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[1]
|
||||
->As<ast::LoopStatement>()
|
||||
->continuing->statements[0]
|
||||
->As<ast::IncrementDecrementStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteForLoopInitializer) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var x : i32;
|
||||
for (x = 0; x < 100; x++) {
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
var x : i32;
|
||||
for (; x < 100; x++) {
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[1]
|
||||
->As<ast::ForLoopStatement>()
|
||||
->initializer->As<ast::AssignmentStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DeleteForLoopContinuing) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var x : i32;
|
||||
for (x = 0; x < 100; x++) {
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
var x : i32;
|
||||
for (x = 0; x < 100;) {
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[1]
|
||||
->As<ast::ForLoopStatement>()
|
||||
->continuing->As<ast::IncrementDecrementStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, AllowDeletionOfInnerLoopWithBreak) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
loop {
|
||||
loop {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
loop {
|
||||
break;
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::LoopStatement>()
|
||||
->body->statements[0]
|
||||
->As<ast::LoopStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, AllowDeletionOfInnerCaseWithBreak) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
loop {
|
||||
switch(0) {
|
||||
case 1: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
loop {
|
||||
switch(0) {
|
||||
default: {
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::LoopStatement>()
|
||||
->body->statements[0]
|
||||
->As<ast::SwitchStatement>()
|
||||
->body[0];
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, AllowDeletionOfBreakFromSwitch) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
switch(0) {
|
||||
case 1: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto expected = R"(
|
||||
fn main() {
|
||||
switch(0) {
|
||||
case 1: {
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::SwitchStatement>()
|
||||
->body[0]
|
||||
->body->statements[0]
|
||||
->As<ast::BreakStatement>();
|
||||
};
|
||||
CheckStatementDeletionWorks(original, expected, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotDeleteVariableDeclaration) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var x : i32;
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::VariableDeclStatement>();
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotDeleteCaseDueToFallthrough) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
switch(1) {
|
||||
default: {
|
||||
fallthrough;
|
||||
}
|
||||
case 2: {
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::SwitchStatement>()
|
||||
->body[1]
|
||||
->As<ast::CaseStatement>();
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotMakeLoopInfinite1) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
loop {
|
||||
break;
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::LoopStatement>()
|
||||
->body->statements[0]
|
||||
->As<ast::BreakStatement>();
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotMakeLoopInfinite2) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
loop {
|
||||
if (true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST()
|
||||
.Functions()[0]
|
||||
->body->statements[0]
|
||||
->As<ast::LoopStatement>()
|
||||
->body->statements[0]
|
||||
->As<ast::IfStatement>();
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveReturn) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
return;
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::ReturnStatement>();
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveStatementContainingReturn) {
|
||||
auto original = R"(
|
||||
fn foo() -> i32 {
|
||||
if (true) {
|
||||
return 1;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::IfStatement>();
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveDiscard) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
discard;
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::DiscardStatement>();
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveStatementContainingDiscard) {
|
||||
auto original = R"(
|
||||
fn foo() -> i32 {
|
||||
if (true) {
|
||||
discard;
|
||||
} else {
|
||||
discard;
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::IfStatement>();
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveLoopBody) {
|
||||
auto original = R"(
|
||||
fn foo() {
|
||||
discard;
|
||||
}
|
||||
fn main() {
|
||||
loop {
|
||||
foo();
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[1]->body->statements[0]->As<ast::LoopStatement>()->body;
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveForLoopBody) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
for(var i : i32 = 0; i < 10; i++) {
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::ForLoopStatement>()->body;
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveWhileBody) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
var i : i32 = 0;
|
||||
while(i < 10) {
|
||||
i++;
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[1]->As<ast::WhileStatement>()->body;
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveIfBody) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
if(true) {
|
||||
}
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body->statements[0]->As<ast::IfStatement>()->body;
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
TEST(DeleteStatementTest, DoNotRemoveFunctionBody) {
|
||||
auto original = R"(
|
||||
fn main() {
|
||||
})";
|
||||
auto statement_finder = [](const Program& program) -> const ast::Statement* {
|
||||
return program.AST().Functions()[0]->body;
|
||||
};
|
||||
CheckStatementDeletionNotAllowed(original, statement_finder);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tint::fuzzers::ast_fuzzer
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/delete_statements.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h"
|
||||
#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
|
||||
|
@ -48,6 +49,8 @@ MutationFinderList CreateMutationFinders(ProbabilityContext* probability_context
|
|||
probability_context, &result);
|
||||
MaybeAddFinder<MutationFinderChangeUnaryOperators>(enable_all_mutations,
|
||||
probability_context, &result);
|
||||
MaybeAddFinder<MutationFinderDeleteStatements>(enable_all_mutations, probability_context,
|
||||
&result);
|
||||
MaybeAddFinder<MutationFinderReplaceIdentifiers>(enable_all_mutations, probability_context,
|
||||
&result);
|
||||
MaybeAddFinder<MutationFinderWrapUnaryOperators>(enable_all_mutations, probability_context,
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace {
|
|||
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfChangingBinaryOperators = {30, 90};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfChangingUnaryOperators = {30, 70};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfDeletingStatements = {30, 70};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70};
|
||||
const std::pair<uint32_t, uint32_t> kChanceOfWrappingUnaryOperators = {30, 70};
|
||||
|
||||
|
@ -30,6 +31,7 @@ ProbabilityContext::ProbabilityContext(RandomGenerator* generator)
|
|||
: generator_(generator),
|
||||
chance_of_changing_binary_operators_(RandomFromRange(kChanceOfChangingBinaryOperators)),
|
||||
chance_of_changing_unary_operators_(RandomFromRange(kChanceOfChangingUnaryOperators)),
|
||||
chance_of_deleting_statements_(RandomFromRange(kChanceOfDeletingStatements)),
|
||||
chance_of_replacing_identifiers_(RandomFromRange(kChanceOfReplacingIdentifiers)),
|
||||
chance_of_wrapping_unary_operators_(RandomFromRange(kChanceOfWrappingUnaryOperators)) {
|
||||
assert(generator != nullptr && "generator must not be nullptr");
|
||||
|
|
|
@ -61,6 +61,9 @@ class ProbabilityContext {
|
|||
return chance_of_changing_unary_operators_;
|
||||
}
|
||||
|
||||
/// @return the probability of changing operator for a binary expression.
|
||||
uint32_t GetChanceOfDeletingStatements() const { return chance_of_deleting_statements_; }
|
||||
|
||||
/// @return the probability of replacing some identifier with some other one.
|
||||
uint32_t GetChanceOfReplacingIdentifiers() const { return chance_of_replacing_identifiers_; }
|
||||
|
||||
|
@ -78,6 +81,7 @@ class ProbabilityContext {
|
|||
|
||||
uint32_t chance_of_changing_binary_operators_;
|
||||
uint32_t chance_of_changing_unary_operators_;
|
||||
uint32_t chance_of_deleting_statements_;
|
||||
uint32_t chance_of_replacing_identifiers_;
|
||||
uint32_t chance_of_wrapping_unary_operators_;
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ message Mutation {
|
|||
MutationChangeBinaryOperator change_binary_operator = 2;
|
||||
MutationWrapUnaryOperator wrap_unary_operator = 3;
|
||||
MutationChangeUnaryOperator change_unary_operator = 4;
|
||||
MutationDeleteStatement delete_statement = 5;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -64,6 +65,14 @@ message MutationChangeUnaryOperator {
|
|||
uint32 new_operator = 2;
|
||||
}
|
||||
|
||||
message MutationDeleteStatement {
|
||||
// This transformation deletes a statement, as long as doing so does not
|
||||
// invalidate the program.
|
||||
|
||||
// The id of a statement to be deleted.
|
||||
uint32 statement_id = 1;
|
||||
}
|
||||
|
||||
message MutationReplaceIdentifier {
|
||||
// This transformation replaces a use of one variable with another.
|
||||
|
||||
|
|
Loading…
Reference in New Issue