From 79db32aff201c8bdffa47231018615f33de6f813 Mon Sep 17 00:00:00 2001 From: Shiyu Liu Date: Fri, 1 Apr 2022 08:05:46 +0000 Subject: [PATCH] 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 Reviewed-by: Ryan Harrison Commit-Queue: Alastair Donaldson --- src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn | 4 + .../fuzzers/tint_ast_fuzzer/CMakeLists.txt | 7 +- src/tint/fuzzers/tint_ast_fuzzer/mutation.cc | 4 + .../mutation_finders/wrap_unary_operators.cc | 81 +++ .../mutation_finders/wrap_unary_operators.h | 43 ++ .../mutations/wrap_unary_operator.cc | 127 ++++ .../mutations/wrap_unary_operator.h | 85 +++ .../mutations/wrap_unary_operator_test.cc | 548 ++++++++++++++++++ src/tint/fuzzers/tint_ast_fuzzer/mutator.cc | 4 +- .../fuzzers/tint_ast_fuzzer/node_id_map.cc | 2 +- .../fuzzers/tint_ast_fuzzer/node_id_map.h | 2 +- .../tint_ast_fuzzer/probability_context.cc | 5 +- .../tint_ast_fuzzer/probability_context.h | 6 + .../protobufs/tint_ast_fuzzer.proto | 15 + 14 files changed, 928 insertions(+), 5 deletions(-) create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h create mode 100644 src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc diff --git a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn index 70941acb6f..b6d205ac54 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn +++ b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn @@ -50,10 +50,14 @@ if (build_with_chromium) { "mutation_finders/change_binary_operators.h", "mutation_finders/replace_identifiers.cc", "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.h", "mutations/replace_identifier.cc", "mutations/replace_identifier.h", + "mutations/wrap_unary_operator.cc", + "mutations/wrap_unary_operator.h", "mutator.cc", "mutator.h", "node_id_map.cc", diff --git a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt index a31d4118e4..25dd3fcaf5 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt +++ b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt @@ -42,8 +42,10 @@ set(LIBTINT_AST_FUZZER_SOURCES mutation_finder.h mutation_finders/change_binary_operators.h mutation_finders/replace_identifiers.h + mutation_finders/wrap_unary_operators.h mutations/change_binary_operator.h mutations/replace_identifier.h + mutations/wrap_unary_operator.h mutator.h node_id_map.h probability_context.h @@ -59,8 +61,10 @@ set(LIBTINT_AST_FUZZER_SOURCES ${LIBTINT_AST_FUZZER_SOURCES} mutation_finder.cc mutation_finders/change_binary_operators.cc mutation_finders/replace_identifiers.cc + mutation_finders/wrap_unary_operators.cc mutations/change_binary_operator.cc mutations/replace_identifier.cc + mutations/wrap_unary_operator.cc mutator.cc node_id_map.cc probability_context.cc @@ -97,7 +101,8 @@ add_tint_ast_fuzzer(tint_ast_wgsl_writer_fuzzer) if (${TINT_BUILD_TESTS}) set(TEST_SOURCES 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}) diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc index 1c4fb2e83e..c3c89a665b 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc @@ -18,6 +18,7 @@ #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/wrap_unary_operator.h" namespace tint { namespace fuzzers { @@ -34,6 +35,9 @@ std::unique_ptr Mutation::FromMessage( case protobufs::Mutation::kChangeBinaryOperator: return std::make_unique( message.change_binary_operator()); + case protobufs::Mutation::kWrapUnaryOperator: + return std::make_unique( + message.wrap_unary_operator()); case protobufs::Mutation::MUTATION_NOT_SET: assert(false && "Mutation is not set"); break; diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc new file mode 100644 index 0000000000..da026f1e8c --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc @@ -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 +#include + +#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(node); + + // Transformation applies only when the node represents a valid expression. + if (!expr_ast_node) { + continue; + } + + const auto* expr_sem_node = + tint::As(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 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( + 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 diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h new file mode 100644 index 0000000000..02538fc213 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h @@ -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_ diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc new file mode 100644 index 0000000000..1f5d5aed70 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc @@ -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 +#include + +#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(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(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(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(message_.unary_op_wrapper()); + + std::vector 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(node_id_map.GetNode(message_.expression_id())); + + auto* replacement_expression_node = + clone_context->dst->create( + static_cast(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 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 diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h new file mode 100644 index 0000000000..25fec0064d --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h @@ -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 + +#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 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_ diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc new file mode 100644 index 0000000000..4b8435ef16 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc @@ -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 + +#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()->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(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() + ->variable->constructor->As(); + + 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(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()->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 { + var a = (vec2 (1u, 2u) == vec2 (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() + ->variable->constructor->As() + ->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 { + var a = (~(vec2(1u, 2u)) == vec2(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() + ->variable->constructor->As() + ->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 = vec4(-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() + ->variable->constructor->As(); + + 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 = -(vec4(-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() + ->initializer->As() + ->variable->constructor->As(); + + 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 = vec4(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() + ->variable->constructor->As(); + + 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 = ~(vec4(1, 0, -1, 0)); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(WrapUnaryOperatorTest, NotApplicable1) { + std::string content = R"( + fn main() { + let a = mat2x3(vec3(1.,0.,1.), vec3(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() + ->variable->constructor->As(); + + 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() + ->variable->constructor->As(); + + 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(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() + ->variable->constructor->As(); + + 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() + ->variable->constructor->As(); + + 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() + ->variable->constructor->As(); + + 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(); + + 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 diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc index 63d62e2466..ab73a3a1b8 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc @@ -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/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/program_builder.h" namespace tint { @@ -50,6 +50,8 @@ MutationFinderList CreateMutationFinders( enable_all_mutations, probability_context, &result); MaybeAddFinder( enable_all_mutations, probability_context, &result); + MaybeAddFinder( + enable_all_mutations, probability_context, &result); } while (result.empty()); return result; } diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc index 543a57befd..117fd2d08b 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc @@ -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); } diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h index 18dd96a244..1aae93f506 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h +++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h @@ -72,7 +72,7 @@ class NodeIdMap { /// @param id - an id that is used to check in the map. /// @return true the given id is fresh and valid (non-zero). /// @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. /// diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc index f41f5b9db6..1d9461f04a 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc @@ -23,6 +23,7 @@ namespace { const std::pair kChanceOfChangingBinaryOperators = {30, 90}; const std::pair kChanceOfReplacingIdentifiers = {30, 70}; +const std::pair kChanceOfWrappingUnaryOperators = {30, 70}; } // namespace @@ -31,7 +32,9 @@ ProbabilityContext::ProbabilityContext(RandomGenerator* generator) chance_of_changing_binary_operators_( RandomFromRange(kChanceOfChangingBinaryOperators)), chance_of_replacing_identifiers_( - RandomFromRange(kChanceOfReplacingIdentifiers)) { + RandomFromRange(kChanceOfReplacingIdentifiers)), + chance_of_wrapping_unary_operators_( + RandomFromRange(kChanceOfWrappingUnaryOperators)) { assert(generator != nullptr && "generator must not be nullptr"); } diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h index b890abafce..eacc1bd9ee 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h +++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h @@ -65,6 +65,11 @@ class ProbabilityContext { 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: /// @param range - a pair of integers `a` and `b` s.t. `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_replacing_identifiers_; + uint32_t chance_of_wrapping_unary_operators_; }; } // namespace ast_fuzzer diff --git a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto index e60e35de4d..0a5951fc24 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto +++ b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto @@ -20,6 +20,7 @@ message Mutation { oneof mutation { MutationReplaceIdentifier replace_identifier = 1; MutationChangeBinaryOperator change_binary_operator = 2; + MutationWrapUnaryOperator wrap_unary_operator = 3; }; } @@ -59,3 +60,17 @@ message MutationChangeBinaryOperator { // A BinaryOp representing the new binary operator. 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; +}