AST fuzzer: wrap unary operator

Add a mutation that wraps an expression in a unary operator.
Valid unary operators depend on the type of the expression.

Fixes: tint:1111
Change-Id: If5a63c5da7e3c212acbec4e838d6542303e59481
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/62000
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Alastair Donaldson <afdx@google.com>
This commit is contained in:
Shiyu Liu 2022-04-01 08:05:46 +00:00 committed by Tint LUCI CQ
parent e47b313d0c
commit 79db32aff2
14 changed files with 928 additions and 5 deletions

View File

@ -50,10 +50,14 @@ if (build_with_chromium) {
"mutation_finders/change_binary_operators.h", "mutation_finders/change_binary_operators.h",
"mutation_finders/replace_identifiers.cc", "mutation_finders/replace_identifiers.cc",
"mutation_finders/replace_identifiers.h", "mutation_finders/replace_identifiers.h",
"mutation_finders/wrap_unary_operators.cc",
"mutation_finders/wrap_unary_operators.h",
"mutations/change_binary_operator.cc", "mutations/change_binary_operator.cc",
"mutations/change_binary_operator.h", "mutations/change_binary_operator.h",
"mutations/replace_identifier.cc", "mutations/replace_identifier.cc",
"mutations/replace_identifier.h", "mutations/replace_identifier.h",
"mutations/wrap_unary_operator.cc",
"mutations/wrap_unary_operator.h",
"mutator.cc", "mutator.cc",
"mutator.h", "mutator.h",
"node_id_map.cc", "node_id_map.cc",

View File

@ -42,8 +42,10 @@ set(LIBTINT_AST_FUZZER_SOURCES
mutation_finder.h mutation_finder.h
mutation_finders/change_binary_operators.h mutation_finders/change_binary_operators.h
mutation_finders/replace_identifiers.h mutation_finders/replace_identifiers.h
mutation_finders/wrap_unary_operators.h
mutations/change_binary_operator.h mutations/change_binary_operator.h
mutations/replace_identifier.h mutations/replace_identifier.h
mutations/wrap_unary_operator.h
mutator.h mutator.h
node_id_map.h node_id_map.h
probability_context.h probability_context.h
@ -59,8 +61,10 @@ set(LIBTINT_AST_FUZZER_SOURCES ${LIBTINT_AST_FUZZER_SOURCES}
mutation_finder.cc mutation_finder.cc
mutation_finders/change_binary_operators.cc mutation_finders/change_binary_operators.cc
mutation_finders/replace_identifiers.cc mutation_finders/replace_identifiers.cc
mutation_finders/wrap_unary_operators.cc
mutations/change_binary_operator.cc mutations/change_binary_operator.cc
mutations/replace_identifier.cc mutations/replace_identifier.cc
mutations/wrap_unary_operator.cc
mutator.cc mutator.cc
node_id_map.cc node_id_map.cc
probability_context.cc probability_context.cc
@ -97,7 +101,8 @@ add_tint_ast_fuzzer(tint_ast_wgsl_writer_fuzzer)
if (${TINT_BUILD_TESTS}) if (${TINT_BUILD_TESTS})
set(TEST_SOURCES set(TEST_SOURCES
mutations/change_binary_operator_test.cc mutations/change_binary_operator_test.cc
mutations/replace_identifier_test.cc) mutations/replace_identifier_test.cc
mutations/wrap_unary_operator_test.cc)
add_executable(tint_ast_fuzzer_unittests ${TEST_SOURCES}) add_executable(tint_ast_fuzzer_unittests ${TEST_SOURCES})

View File

@ -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_binary_operator.h"
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h" #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
namespace tint { namespace tint {
namespace fuzzers { namespace fuzzers {
@ -34,6 +35,9 @@ std::unique_ptr<Mutation> Mutation::FromMessage(
case protobufs::Mutation::kChangeBinaryOperator: case protobufs::Mutation::kChangeBinaryOperator:
return std::make_unique<MutationChangeBinaryOperator>( return std::make_unique<MutationChangeBinaryOperator>(
message.change_binary_operator()); message.change_binary_operator());
case protobufs::Mutation::kWrapUnaryOperator:
return std::make_unique<MutationWrapUnaryOperator>(
message.wrap_unary_operator());
case protobufs::Mutation::MUTATION_NOT_SET: case protobufs::Mutation::MUTATION_NOT_SET:
assert(false && "Mutation is not set"); assert(false && "Mutation is not set");
break; break;

View File

@ -0,0 +1,81 @@
// 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/wrap_unary_operators.h"
#include <memory>
#include <vector>
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
#include "src/tint/sem/expression.h"
#include "src/tint/sem/statement.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
MutationList MutationFinderWrapUnaryOperators::FindMutations(
const tint::Program& program,
NodeIdMap* node_id_map,
ProbabilityContext* probability_context) const {
MutationList result;
// Iterate through all ast nodes and for each expression node, try to wrap
// the inside a valid unary operator based on the type of the expression.
for (const auto* node : program.ASTNodes().Objects()) {
const auto* expr_ast_node = tint::As<ast::Expression>(node);
// Transformation applies only when the node represents a valid expression.
if (!expr_ast_node) {
continue;
}
const auto* expr_sem_node =
tint::As<sem::Expression>(program.Sem().Get(expr_ast_node));
// Transformation applies only when the semantic node for the given
// expression is present.
if (!expr_sem_node) {
continue;
}
std::vector<ast::UnaryOp> valid_operators =
MutationWrapUnaryOperator::GetValidUnaryWrapper(*expr_sem_node);
// Transformation only applies when there are available unary operators
// for the given expression.
if (valid_operators.empty()) {
continue;
}
ast::UnaryOp unary_op_wrapper =
valid_operators[probability_context->GetRandomIndex(valid_operators)];
result.push_back(std::make_unique<MutationWrapUnaryOperator>(
node_id_map->GetId(expr_ast_node), node_id_map->TakeFreshId(),
unary_op_wrapper));
}
return result;
}
uint32_t MutationFinderWrapUnaryOperators::GetChanceOfApplyingMutation(
ProbabilityContext* probability_context) const {
return probability_context->GetChanceOfWrappingUnaryOperators();
}
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,43 @@
// 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_WRAP_UNARY_OPERATORS_H_
#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
/// Looks for opportunities to apply
/// `MutationFinderWrapUnaryOperators`.
///
/// For each expression in the module, try to wrap it within
/// a unary operator.
class MutationFinderWrapUnaryOperators : 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 ast_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_

View File

@ -0,0 +1,127 @@
// 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/wrap_unary_operator.h"
#include <utility>
#include <vector>
#include "src/tint/program_builder.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
MutationWrapUnaryOperator::MutationWrapUnaryOperator(
protobufs::MutationWrapUnaryOperator message)
: message_(std::move(message)) {}
MutationWrapUnaryOperator::MutationWrapUnaryOperator(
uint32_t expression_id,
uint32_t fresh_id,
ast::UnaryOp unary_op_wrapper) {
message_.set_expression_id(expression_id);
message_.set_fresh_id(fresh_id);
message_.set_unary_op_wrapper(static_cast<uint32_t>(unary_op_wrapper));
}
bool MutationWrapUnaryOperator::IsApplicable(
const tint::Program& program,
const NodeIdMap& node_id_map) const {
// Check if id that will be assigned is fresh.
if (!node_id_map.IdIsFreshAndValid(message_.fresh_id())) {
return false;
}
const auto* expression_ast_node =
tint::As<ast::Expression>(node_id_map.GetNode(message_.expression_id()));
if (!expression_ast_node) {
// Either the node is not present with the given id or
// the node is not a valid expression type.
return false;
}
const auto* expression_sem_node =
tint::As<sem::Expression>(program.Sem().Get(expression_ast_node));
if (!expression_sem_node) {
// Semantic information for the expression ast node is not present
// or the semantic node is not a valid expression type node.
return false;
}
ast::UnaryOp unary_op_wrapper =
static_cast<ast::UnaryOp>(message_.unary_op_wrapper());
std::vector<ast::UnaryOp> valid_ops =
GetValidUnaryWrapper(*expression_sem_node);
// There is no available unary operator or |unary_op_wrapper| is a
// type that is not allowed for the given expression.
if (std::find(valid_ops.begin(), valid_ops.end(), unary_op_wrapper) ==
valid_ops.end()) {
return false;
}
return true;
}
void MutationWrapUnaryOperator::Apply(const NodeIdMap& node_id_map,
tint::CloneContext* clone_context,
NodeIdMap* new_node_id_map) const {
auto* expression_node =
tint::As<ast::Expression>(node_id_map.GetNode(message_.expression_id()));
auto* replacement_expression_node =
clone_context->dst->create<ast::UnaryOpExpression>(
static_cast<ast::UnaryOp>(message_.unary_op_wrapper()),
clone_context->Clone(expression_node));
clone_context->Replace(expression_node, replacement_expression_node);
new_node_id_map->Add(replacement_expression_node, message_.fresh_id());
}
protobufs::Mutation MutationWrapUnaryOperator::ToMessage() const {
protobufs::Mutation mutation;
*mutation.mutable_wrap_unary_operator() = message_;
return mutation;
}
std::vector<ast::UnaryOp> MutationWrapUnaryOperator::GetValidUnaryWrapper(
const sem::Expression& expr) {
const auto* expr_type = expr.Type();
if (expr_type->is_bool_scalar_or_vector()) {
return {ast::UnaryOp::kNot};
}
if (expr_type->is_signed_scalar_or_vector()) {
return {ast::UnaryOp::kNegation, ast::UnaryOp::kComplement};
}
if (expr_type->is_unsigned_scalar_or_vector()) {
return {ast::UnaryOp::kComplement};
}
if (expr_type->is_float_scalar_or_vector()) {
return {ast::UnaryOp::kNegation};
}
return {};
}
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,85 @@
// 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_WRAP_UNARY_OPERATOR_H_
#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_
#include <vector>
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h"
#include "src/tint/sem/variable.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
/// @see MutationWrapUnaryOperator::Apply
class MutationWrapUnaryOperator : public Mutation {
public:
/// @brief Constructs an instance of this mutation from a protobuf message.
/// @param message - protobuf message
explicit MutationWrapUnaryOperator(
protobufs::MutationWrapUnaryOperator message);
/// @brief Constructor.
/// @param expression_id - the id of an expression.
/// @param fresh_id - a fresh id for the created expression node with
/// unary operator wrapper.
/// @param unary_op_wrapper - a `ast::UnaryOp` instance.
MutationWrapUnaryOperator(uint32_t expression_id,
uint32_t fresh_id,
ast::UnaryOp unary_op_wrapper);
/// @copybrief Mutation::IsApplicable
///
/// The mutation is applicable iff:
/// - `expression_id` must refer to a valid expression that can be wrapped
/// with unary operator.
/// - `fresh_id` must be fresh.
/// - `unary_op_wrapper` is a unary expression that is valid based on the
/// type of the given expression.
///
/// @copydetails Mutation::IsApplicable
bool IsApplicable(const tint::Program& program,
const NodeIdMap& node_id_map) const override;
/// @copybrief Mutation::Apply
///
/// Wrap an expression in a unary operator that is valid based on
/// the type of the expression.
///
/// @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 list of unary operator wrappers allowed for the given
/// expression.
/// @param expr - an `ast::Expression` instance from node id map.
/// @return a list of unary operators.
static std::vector<ast::UnaryOp> GetValidUnaryWrapper(
const sem::Expression& expr);
private:
protobufs::MutationWrapUnaryOperator message_;
};
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_

View File

@ -0,0 +1,548 @@
// 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 <string>
#include "gtest/gtest.h"
#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.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 {
namespace fuzzers {
namespace ast_fuzzer {
namespace {
TEST(WrapUnaryOperatorTest, Applicable1) {
std::string content = R"(
fn main() {
var a = 5;
if (a < 5) {
a = 6;
}
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
auto expression_id = node_id_map.GetId(
main_fn_statements[1]->As<ast::IfStatement>()->condition);
ASSERT_NE(expression_id, 0);
ASSERT_TRUE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNot),
node_id_map, &program, &node_id_map, nullptr));
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&program, options);
ASSERT_TRUE(result.success) << result.error;
std::string expected_shader = R"(fn main() {
var a = 5;
if (!((a < 5))) {
a = 6;
}
}
)";
ASSERT_EQ(expected_shader, result.wgsl);
}
TEST(WrapUnaryOperatorTest, Applicable2) {
std::string content = R"(
fn main() {
let a = vec3<bool>(true, false, true);
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
ASSERT_TRUE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNot),
node_id_map, &program, &node_id_map, nullptr));
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&program, options);
ASSERT_TRUE(result.success) << result.error;
std::string expected_shader = R"(fn main() {
let a = !(vec3<bool>(true, false, true));
}
)";
ASSERT_EQ(expected_shader, result.wgsl);
}
TEST(WrapUnaryOperatorTest, Applicable3) {
std::string content = R"(
fn main() {
var a : u32;
a = 6u;
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[1]->As<ast::AssignmentStatement>()->rhs;
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
ASSERT_TRUE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kComplement),
node_id_map, &program, &node_id_map, nullptr));
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&program, options);
ASSERT_TRUE(result.success) << result.error;
std::string expected_shader = R"(fn main() {
var a : u32;
a = ~(6u);
}
)";
ASSERT_EQ(expected_shader, result.wgsl);
}
TEST(WrapUnaryOperatorTest, Applicable4) {
std::string content = R"(
fn main() -> vec2<bool> {
var a = (vec2<u32> (1u, 2u) == vec2<u32> (1u, 2u));
return a;
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::BinaryExpression>()
->lhs;
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
ASSERT_TRUE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kComplement),
node_id_map, &program, &node_id_map, nullptr));
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&program, options);
ASSERT_TRUE(result.success) << result.error;
std::string expected_shader = R"(fn main() -> vec2<bool> {
var a = (~(vec2<u32>(1u, 2u)) == vec2<u32>(1u, 2u));
return a;
}
)";
ASSERT_EQ(expected_shader, result.wgsl);
}
TEST(WrapUnaryOperatorTest, Applicable5) {
std::string content = R"(
fn main() {
let a : f32 = -(1.0);
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::UnaryOpExpression>()
->expr;
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
ASSERT_TRUE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNegation),
node_id_map, &program, &node_id_map, nullptr));
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&program, options);
ASSERT_TRUE(result.success) << result.error;
std::string expected_shader = R"(fn main() {
let a : f32 = -(-(1.0));
}
)";
ASSERT_EQ(expected_shader, result.wgsl);
}
TEST(WrapUnaryOperatorTest, Applicable6) {
std::string content = R"(
fn main() {
var a : vec4<f32> = vec4<f32>(-1.0, -1.0, -1.0, -1.0);
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
ASSERT_TRUE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNegation),
node_id_map, &program, &node_id_map, nullptr));
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&program, options);
ASSERT_TRUE(result.success) << result.error;
std::string expected_shader = R"(fn main() {
var a : vec4<f32> = -(vec4<f32>(-1.0, -1.0, -1.0, -1.0));
}
)";
ASSERT_EQ(expected_shader, result.wgsl);
}
TEST(WrapUnaryOperatorTest, Applicable7) {
std::string content = R"(
fn main() {
var a = 1;
for(var i : i32 = 1; i < 5; i = i + 1) {
a = a + 1;
}
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[1]
->As<ast::ForLoopStatement>()
->initializer->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
ASSERT_TRUE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNegation),
node_id_map, &program, &node_id_map, nullptr));
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&program, options);
ASSERT_TRUE(result.success) << result.error;
std::string expected_shader = R"(fn main() {
var a = 1;
for(var i : i32 = -(1); (i < 5); i = (i + 1)) {
a = (a + 1);
}
}
)";
ASSERT_EQ(expected_shader, result.wgsl);
}
TEST(WrapUnaryOperatorTest, Applicable8) {
std::string content = R"(
fn main() {
var a : vec4<i32> = vec4<i32>(1, 0, -1, 0);
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
ASSERT_TRUE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kComplement),
node_id_map, &program, &node_id_map, nullptr));
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
writer::wgsl::Options options;
auto result = writer::wgsl::Generate(&program, options);
ASSERT_TRUE(result.success) << result.error;
std::string expected_shader = R"(fn main() {
var a : vec4<i32> = ~(vec4<i32>(1, 0, -1, 0));
}
)";
ASSERT_EQ(expected_shader, result.wgsl);
}
TEST(WrapUnaryOperatorTest, NotApplicable1) {
std::string content = R"(
fn main() {
let a = mat2x3<f32>(vec3<f32>(1.,0.,1.), vec3<f32>(0.,1.,0.));
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
// There is no unary operator that can be applied to matrix type.
ASSERT_FALSE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNegation),
node_id_map, &program, &node_id_map, nullptr));
}
TEST(WrapUnaryOperatorTest, NotApplicable2) {
std::string content = R"(
fn main() {
let a = 1;
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
// Not cannot be applied to integer types.
ASSERT_FALSE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNot),
node_id_map, &program, &node_id_map, nullptr));
}
TEST(WrapUnaryOperatorTest, NotApplicable3) {
std::string content = R"(
fn main() {
let a = vec2<u32>(1u, 2u);
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
// Negation cannot be applied to unsigned integer scalar or vectors.
ASSERT_FALSE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNegation),
node_id_map, &program, &node_id_map, nullptr));
}
TEST(WrapUnaryOperatorTest, NotApplicable4) {
std::string content = R"(
fn main() {
let a = 1.5;
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
// Cannot wrap float types with complement operator.
ASSERT_FALSE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kComplement),
node_id_map, &program, &node_id_map, nullptr));
}
TEST(WrapUnaryOperatorTest, NotApplicable5) {
std::string content = R"(
fn main() {
let a = 1.5;
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* expr = main_fn_statements[0]
->As<ast::VariableDeclStatement>()
->variable->constructor->As<ast::Expression>();
const auto expression_id = node_id_map.GetId(expr);
ASSERT_NE(expression_id, 0);
// Id for the replacement expression is not fresh.
ASSERT_FALSE(
MaybeApplyMutation(program,
MutationWrapUnaryOperator(expression_id, expression_id,
ast::UnaryOp::kNegation),
node_id_map, &program, &node_id_map, nullptr));
}
TEST(WrapUnaryOperatorTest, NotApplicable6) {
std::string content = R"(
fn main() {
let a = 1.5;
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_statements =
program.AST().Functions()[0]->body->statements;
const auto* statement =
main_fn_statements[0]->As<ast::VariableDeclStatement>();
const auto statement_id = node_id_map.GetId(statement);
ASSERT_NE(statement_id, 0);
// The id provided for the expression is not a valid expression type.
ASSERT_FALSE(MaybeApplyMutation(
program,
MutationWrapUnaryOperator(statement_id, node_id_map.TakeFreshId(),
ast::UnaryOp::kNegation),
node_id_map, &program, &node_id_map, nullptr));
}
} // namespace
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -22,8 +22,8 @@
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h" #include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h"
#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.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" #include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
#include "src/tint/program_builder.h" #include "src/tint/program_builder.h"
namespace tint { namespace tint {
@ -50,6 +50,8 @@ MutationFinderList CreateMutationFinders(
enable_all_mutations, probability_context, &result); enable_all_mutations, probability_context, &result);
MaybeAddFinder<MutationFinderReplaceIdentifiers>( MaybeAddFinder<MutationFinderReplaceIdentifiers>(
enable_all_mutations, probability_context, &result); enable_all_mutations, probability_context, &result);
MaybeAddFinder<MutationFinderWrapUnaryOperators>(
enable_all_mutations, probability_context, &result);
} while (result.empty()); } while (result.empty());
return result; return result;
} }

View File

@ -51,7 +51,7 @@ void NodeIdMap::Add(const ast::Node* node, IdType id) {
} }
} }
bool NodeIdMap::IdIsFreshAndValid(IdType id) { bool NodeIdMap::IdIsFreshAndValid(IdType id) const {
return id && !id_to_node_.count(id); return id && !id_to_node_.count(id);
} }

View File

@ -72,7 +72,7 @@ class NodeIdMap {
/// @param id - an id that is used to check in the map. /// @param id - an id that is used to check in the map.
/// @return true the given id is fresh and valid (non-zero). /// @return true the given id is fresh and valid (non-zero).
/// @return false otherwise. /// @return false otherwise.
bool IdIsFreshAndValid(IdType id); bool IdIsFreshAndValid(IdType id) const;
/// @brief Returns an id that is guaranteed to be unoccupied in this map. /// @brief Returns an id that is guaranteed to be unoccupied in this map.
/// ///

View File

@ -23,6 +23,7 @@ namespace {
const std::pair<uint32_t, uint32_t> kChanceOfChangingBinaryOperators = {30, 90}; const std::pair<uint32_t, uint32_t> kChanceOfChangingBinaryOperators = {30, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70}; const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfWrappingUnaryOperators = {30, 70};
} // namespace } // namespace
@ -31,7 +32,9 @@ ProbabilityContext::ProbabilityContext(RandomGenerator* generator)
chance_of_changing_binary_operators_( chance_of_changing_binary_operators_(
RandomFromRange(kChanceOfChangingBinaryOperators)), RandomFromRange(kChanceOfChangingBinaryOperators)),
chance_of_replacing_identifiers_( chance_of_replacing_identifiers_(
RandomFromRange(kChanceOfReplacingIdentifiers)) { RandomFromRange(kChanceOfReplacingIdentifiers)),
chance_of_wrapping_unary_operators_(
RandomFromRange(kChanceOfWrappingUnaryOperators)) {
assert(generator != nullptr && "generator must not be nullptr"); assert(generator != nullptr && "generator must not be nullptr");
} }

View File

@ -65,6 +65,11 @@ class ProbabilityContext {
return chance_of_replacing_identifiers_; return chance_of_replacing_identifiers_;
} }
/// @return the probability of wrapping an expression in a unary operator.
uint32_t GetChanceOfWrappingUnaryOperators() const {
return chance_of_wrapping_unary_operators_;
}
private: private:
/// @param range - a pair of integers `a` and `b` s.t. `a <= b`. /// @param range - a pair of integers `a` and `b` s.t. `a <= b`.
/// @return an random number in the range `[a; b]`. /// @return an random number in the range `[a; b]`.
@ -74,6 +79,7 @@ class ProbabilityContext {
uint32_t chance_of_changing_binary_operators_; uint32_t chance_of_changing_binary_operators_;
uint32_t chance_of_replacing_identifiers_; uint32_t chance_of_replacing_identifiers_;
uint32_t chance_of_wrapping_unary_operators_;
}; };
} // namespace ast_fuzzer } // namespace ast_fuzzer

View File

@ -20,6 +20,7 @@ message Mutation {
oneof mutation { oneof mutation {
MutationReplaceIdentifier replace_identifier = 1; MutationReplaceIdentifier replace_identifier = 1;
MutationChangeBinaryOperator change_binary_operator = 2; MutationChangeBinaryOperator change_binary_operator = 2;
MutationWrapUnaryOperator wrap_unary_operator = 3;
}; };
} }
@ -59,3 +60,17 @@ message MutationChangeBinaryOperator {
// A BinaryOp representing the new binary operator. // A BinaryOp representing the new binary operator.
uint32 new_operator = 2; uint32 new_operator = 2;
} }
message MutationWrapUnaryOperator {
// This transformation wraps an expression with a allowed unary
// expression operator.
// The id of the expression.
uint32 expression_id = 1;
// A fresh id for the created unary expression.
uint32 fresh_id = 2;
// The unary operator to wrap the expression with.
uint32 unary_op_wrapper = 3;
}