diff --git a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn index 8430c2ce2a..69ef3fef91 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn +++ b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn @@ -52,12 +52,16 @@ if (build_with_chromium) { "mutation_finder.h", "mutation_finders/change_binary_operators.cc", "mutation_finders/change_binary_operators.h", + "mutation_finders/change_unary_operators.cc", + "mutation_finders/change_unary_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/change_unary_operator.cc", + "mutations/change_unary_operator.h", "mutations/replace_identifier.cc", "mutations/replace_identifier.h", "mutations/wrap_unary_operator.cc", diff --git a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt index 1a458973ea..5bd8c507bd 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt +++ b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt @@ -42,9 +42,11 @@ set(LIBTINT_AST_FUZZER_SOURCES mutation.h mutation_finder.h mutation_finders/change_binary_operators.h + mutation_finders/change_unary_operators.h mutation_finders/replace_identifiers.h mutation_finders/wrap_unary_operators.h mutations/change_binary_operator.h + mutations/change_unary_operator.h mutations/replace_identifier.h mutations/wrap_unary_operator.h mutator.h @@ -62,9 +64,11 @@ set(LIBTINT_AST_FUZZER_SOURCES ${LIBTINT_AST_FUZZER_SOURCES} mutation.cc mutation_finder.cc mutation_finders/change_binary_operators.cc + mutation_finders/change_unary_operators.cc mutation_finders/replace_identifiers.cc mutation_finders/wrap_unary_operators.cc mutations/change_binary_operator.cc + mutations/change_unary_operator.cc mutations/replace_identifier.cc mutations/wrap_unary_operator.cc mutator.cc @@ -104,8 +108,9 @@ if (${TINT_BUILD_TESTS}) set(TEST_SOURCES expression_size_test.cc mutations/change_binary_operator_test.cc + mutations/change_unary_operator_test.cc mutations/replace_identifier_test.cc - mutations/wrap_unary_operator_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 10bf4951f7..6f60fc3349 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc @@ -17,6 +17,7 @@ #include #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/replace_identifier.h" #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h" @@ -26,6 +27,8 @@ Mutation::~Mutation() = default; std::unique_ptr Mutation::FromMessage(const protobufs::Mutation& message) { switch (message.mutation_case()) { + case protobufs::Mutation::kChangeUnaryOperator: + return std::make_unique(message.change_unary_operator()); case protobufs::Mutation::kReplaceIdentifier: return std::make_unique(message.replace_identifier()); case protobufs::Mutation::kChangeBinaryOperator: diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc new file mode 100644 index 0000000000..ed2f442ccf --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.cc @@ -0,0 +1,65 @@ +// 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/change_unary_operators.h" + +#include + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/util.h" +#include "src/tint/sem/reference.h" + +namespace tint::fuzzers::ast_fuzzer { + +MutationList MutationFinderChangeUnaryOperators::FindMutations( + const tint::Program& program, + NodeIdMap* node_id_map, + ProbabilityContext* /*unused*/) const { + MutationList result; + + // Iterate through all AST nodes and for each valid unary op expression node, + // try to replace the node's unary operator. + for (const auto* node : program.ASTNodes().Objects()) { + const auto* unary_expr = tint::As(node); + + // Transformation applies only when the node represents a valid unary + // expression. + if (!unary_expr) { + continue; + } + + // Get the type of the unary expression. + const auto* type = program.Sem().Get(unary_expr)->Type(); + const auto* basic_type = + type->Is() ? type->As()->StoreType() : type; + + // Only signed integer or vector of signed integer can be mutated. + if (!basic_type->is_signed_scalar_or_vector()) { + continue; + } + + result.push_back(std::make_unique( + node_id_map->GetId(unary_expr), + MutationChangeUnaryOperator::ToggleOperator(unary_expr->op))); + } + + return result; +} + +uint32_t MutationFinderChangeUnaryOperators::GetChanceOfApplyingMutation( + ProbabilityContext* probability_context) const { + return probability_context->GetChanceOfChangingUnaryOperators(); +} + +} // namespace tint::fuzzers::ast_fuzzer diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.h new file mode 100644 index 0000000000..5a86b5d3d4 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_unary_operators.h @@ -0,0 +1,37 @@ +// 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_CHANGE_UNARY_OPERATORS_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_CHANGE_UNARY_OPERATORS_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h" + +namespace tint::fuzzers::ast_fuzzer { + +/// Looks for opportunities to apply +/// `MutationFinderChangeUnaryOperators`. +/// +/// Concretely, for each unary expression in the module, try to change its +/// operator with a permitted one. +class MutationFinderChangeUnaryOperators : 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_CHANGE_UNARY_OPERATORS_H_ diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.cc new file mode 100644 index 0000000000..d32d569748 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.cc @@ -0,0 +1,107 @@ +// 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/change_unary_operator.h" + +#include + +#include "src/tint/fuzzers/tint_ast_fuzzer/util.h" +#include "src/tint/program_builder.h" +#include "src/tint/sem/reference.h" + +namespace tint::fuzzers::ast_fuzzer { + +MutationChangeUnaryOperator::MutationChangeUnaryOperator( + protobufs::MutationChangeUnaryOperator message) + : message_(std::move(message)) {} + +MutationChangeUnaryOperator::MutationChangeUnaryOperator(uint32_t unary_expr_id, + ast::UnaryOp new_operator) { + message_.set_unary_expr_id(unary_expr_id); + message_.set_new_operator(static_cast(new_operator)); +} + +bool MutationChangeUnaryOperator::IsApplicable(const tint::Program& program, + const NodeIdMap& node_id_map) const { + const auto* unary_expr_node = + tint::As(node_id_map.GetNode(message_.unary_expr_id())); + + if (!unary_expr_node) { + // Either the id does not exist, or does not correspond to a unary + // expression. + return false; + } + + auto new_unary_operator = static_cast(message_.new_operator()); + + // Get the type of the unary expression. + const auto* type = program.Sem().Get(unary_expr_node)->Type(); + const auto* basic_type = + type->Is() ? type->As()->StoreType() : type; + + // Only signed integer or vector of signed integer has more than 1 + // unary operators to change between. + if (!basic_type->is_signed_scalar_or_vector()) { + return false; + } + + // The new unary operator must not be the same as the original one. + if (new_unary_operator != ToggleOperator(unary_expr_node->op)) { + return false; + } + + return true; +} + +void MutationChangeUnaryOperator::Apply(const NodeIdMap& node_id_map, + tint::CloneContext* clone_context, + NodeIdMap* new_node_id_map) const { + const auto* unary_expr_node = + tint::As(node_id_map.GetNode(message_.unary_expr_id())); + + const ast::UnaryOpExpression* cloned_replacement; + switch (static_cast(message_.new_operator())) { + case ast::UnaryOp::kComplement: + cloned_replacement = + clone_context->dst->Complement(clone_context->Clone(unary_expr_node->expr)); + break; + case ast::UnaryOp::kNegation: + cloned_replacement = + clone_context->dst->Negation(clone_context->Clone(unary_expr_node->expr)); + break; + default: + cloned_replacement = nullptr; + assert(false && "Unreachable"); + } + // Set things up so that the original unary expression will be replaced with + // its clone, and update the id mapping. + clone_context->Replace(unary_expr_node, cloned_replacement); + new_node_id_map->Add(cloned_replacement, message_.unary_expr_id()); +} + +protobufs::Mutation MutationChangeUnaryOperator::ToMessage() const { + protobufs::Mutation mutation; + *mutation.mutable_change_unary_operator() = message_; + return mutation; +} + +ast::UnaryOp MutationChangeUnaryOperator::ToggleOperator(const ast::UnaryOp& original_op) { + if (original_op == ast::UnaryOp::kComplement) { + return ast::UnaryOp::kNegation; + } + assert(original_op == ast::UnaryOp::kNegation && "Unexpected operator."); + return ast::UnaryOp::kComplement; +} + +} // namespace tint::fuzzers::ast_fuzzer diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.h new file mode 100644 index 0000000000..d80ec3f271 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator.h @@ -0,0 +1,72 @@ +// 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_CHANGE_UNARY_OPERATOR_H_ +#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_CHANGE_UNARY_OPERATOR_H_ + +#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h" +#include "src/tint/sem/variable.h" + +namespace tint::fuzzers::ast_fuzzer { + +/// @see MutationChangeUnaryOperator::Apply +class MutationChangeUnaryOperator : public Mutation { + public: + /// @brief Constructs an instance of this mutation from a protobuf message. + /// @param message - protobuf message + explicit MutationChangeUnaryOperator(protobufs::MutationChangeUnaryOperator message); + + /// @brief Constructor. + /// @param unary_expr_id - the id of the `ast::UnaryOpExpression` instance + /// to change its operator. + /// @param new_operator - A new unary operator for the unary expression + /// specified by `expression_id`. + MutationChangeUnaryOperator(uint32_t unary_expr_id, ast::UnaryOp new_operator); + + /// @copybrief Mutation::IsApplicable + /// + /// The mutation is applicable if and only if: + /// - `expression_id` is an id of an `ast::UnaryOpExpression`, that references + /// a valid unary expression. + /// - `new_unary_op` is a valid unary operator of type `ast::UnaryOp` + /// to the target expression. + /// + /// @copydetails Mutation::IsApplicable + bool IsApplicable(const tint::Program& program, const NodeIdMap& node_id_map) const override; + + /// @copybrief Mutation::Apply + /// + /// Replaces the operator of an unary op expression with `expression_id` + /// with a new unary operator specified by `new_unary_op'. The modified + /// expression preserves the same type as the original 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; + + /// Toggles between the complement and negate unary operators. + /// @param original_op - a complement or negation unary operator. + /// @return the other operator. + static ast::UnaryOp ToggleOperator(const ast::UnaryOp& original_op); + + private: + protobufs::MutationChangeUnaryOperator message_; +}; + +} // namespace tint::fuzzers::ast_fuzzer + +#endif // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_CHANGE_UNARY_OPERATOR_H_ diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator_test.cc new file mode 100644 index 0000000000..511af4fc26 --- /dev/null +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/change_unary_operator_test.cc @@ -0,0 +1,299 @@ +// 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/ast/call_statement.h" +#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_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::fuzzers::ast_fuzzer { +namespace { + +TEST(ChangeUnaryOperatorTest, Operator_Not_Applicable) { + std::string content = R"( + fn main() { + let a : f32 = 1.1; + let b = vec2(1, -1); + let c : u32 = 0u; + let d : vec3 = vec3 (false, false, true); + + var neg_a = -a; + var not_b = ~b; + var not_c = ~c; + var neg_d = !d; + } + )"; + 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; + + // Get variable from statements. + const auto* neg_a_var = main_fn_statements[4]->As()->variable; + ASSERT_NE(neg_a_var, nullptr); + + const auto* not_b_var = main_fn_statements[5]->As()->variable; + ASSERT_NE(not_b_var, nullptr); + + const auto* not_c_var = main_fn_statements[6]->As()->variable; + ASSERT_NE(not_c_var, nullptr); + + const auto* neg_d_var = main_fn_statements[7]->As()->variable; + ASSERT_NE(neg_d_var, nullptr); + + // Get the expression from variable declaration. + const auto* neg_a_expr = neg_a_var->constructor->As(); + ASSERT_NE(neg_a_expr, nullptr); + + const auto* not_b_expr = not_b_var->constructor->As(); + ASSERT_NE(not_b_expr, nullptr); + + const auto* not_c_expr = not_c_var->constructor->As(); + ASSERT_NE(not_c_expr, nullptr); + + const auto* neg_d_expr = neg_d_var->constructor->As(); + ASSERT_NE(neg_d_expr, nullptr); + + // The following mutations are not applicable. + auto neg_a_id = node_id_map.GetId(neg_a_expr); + // Only negation is allowed for float type. Cannot change + // the operator of float types to any other. + ASSERT_FALSE(MaybeApplyMutation( + program, MutationChangeUnaryOperator(neg_a_id, ast::UnaryOp::kComplement), node_id_map, + &program, &node_id_map, nullptr)); + ASSERT_FALSE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(neg_a_id, ast::UnaryOp::kNot), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_FALSE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(neg_a_id, ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); + + auto not_b_id = node_id_map.GetId(not_b_expr); + // Only complement and negation is allowed for signed integer type. + ASSERT_FALSE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(not_b_id, ast::UnaryOp::kNot), + node_id_map, &program, &node_id_map, nullptr)); + // Cannot change to the same unary operator. + ASSERT_FALSE(MaybeApplyMutation( + program, MutationChangeUnaryOperator(not_b_id, ast::UnaryOp::kComplement), node_id_map, + &program, &node_id_map, nullptr)); + + auto not_c_id = node_id_map.GetId(not_c_expr); + // Only complement is allowed for unsigned integer.Cannot change + // // the operator of float types to any other. + ASSERT_FALSE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(not_c_id, ast::UnaryOp::kNot), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_FALSE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(not_c_id, ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_FALSE(MaybeApplyMutation( + program, MutationChangeUnaryOperator(not_c_id, ast::UnaryOp::kComplement), node_id_map, + &program, &node_id_map, nullptr)); + + auto neg_d_id = node_id_map.GetId(neg_d_expr); + // Only logical negation (not) is allowed for bool type. Cannot change + // the operator of float types to any other. + ASSERT_FALSE(MaybeApplyMutation( + program, MutationChangeUnaryOperator(neg_d_id, ast::UnaryOp::kComplement), node_id_map, + &program, &node_id_map, nullptr)); + ASSERT_FALSE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(neg_d_id, ast::UnaryOp::kNegation), + node_id_map, &program, &node_id_map, nullptr)); + ASSERT_FALSE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(neg_d_id, ast::UnaryOp::kNot), + node_id_map, &program, &node_id_map, nullptr)); +} + +TEST(ChangeUnaryOperatorTest, Signed_Integer_Types_Applicable1) { + std::string content = R"( + fn main() { + let a : i32 = 5; + let comp_a = ~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; + + // Get variable from statements. + const auto* comp_a_var = main_fn_statements[1]->As()->variable; + ASSERT_NE(comp_a_var, nullptr); + + // Get the expression from variable declaration. + const auto* comp_a_expr = comp_a_var->constructor->As(); + ASSERT_NE(comp_a_expr, nullptr); + + // Assert mutation to be applicable and apply mutation. + auto comp_a_id = node_id_map.GetId(comp_a_expr); + ASSERT_TRUE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(comp_a_id, 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 : i32 = 5; + let comp_a = -(a); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(ChangeUnaryOperatorTest, Signed_Integer_Types_Applicable2) { + std::string content = R"( + fn main() { + let b : vec3 = vec3(1, 3, -1); + var comp_b : vec3 = ~b; + })"; + 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; + + // Get variable from statements. + const auto* comp_b_var = main_fn_statements[1]->As()->variable; + ASSERT_NE(comp_b_var, nullptr); + + // Get the expression from variable declaration. + const auto* comp_b_expr = comp_b_var->constructor->As(); + ASSERT_NE(comp_b_expr, nullptr); + + // Assert mutation to be applicable and apply mutation. + auto comp_b_id = node_id_map.GetId(comp_b_expr); + ASSERT_TRUE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(comp_b_id, 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 b : vec3 = vec3(1, 3, -1); + var comp_b : vec3 = -(b); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(ChangeUnaryOperatorTest, Signed_Integer_Types_Applicable3) { + std::string content = R"( + fn main() { + var a = -5; + + var neg_a = -(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; + + // Get variable from statements. + const auto* neg_a_var = main_fn_statements[1]->As()->variable; + ASSERT_NE(neg_a_var, nullptr); + + // Get the expression from variable declaration. + const auto* neg_a_expr = neg_a_var->constructor->As(); + ASSERT_NE(neg_a_expr, nullptr); + + // Assert mutation to be applicable and apply mutation. + auto neg_a_id = node_id_map.GetId(neg_a_expr); + ASSERT_TRUE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(neg_a_id, 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 = -5; + var neg_a = ~(a); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +TEST(ChangeUnaryOperatorTest, Signed_Integer_Types_Applicable4) { + std::string content = R"( + fn main() { + var b : vec3 = vec3(1, 3, -1); + let neg_b : vec3 = -b; + } + )"; + 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; + + // Get variable from statements. + const auto* neg_b_var = main_fn_statements[1]->As()->variable; + ASSERT_NE(neg_b_var, nullptr); + + // Get the expression from variable declaration. + const auto* neg_b_expr = neg_b_var->constructor->As(); + ASSERT_NE(neg_b_expr, nullptr); + + // Assert mutation to be applicable and apply mutation. + auto neg_b_id = node_id_map.GetId(neg_b_expr); + ASSERT_TRUE(MaybeApplyMutation(program, + MutationChangeUnaryOperator(neg_b_id, 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 b : vec3 = vec3(1, 3, -1); + let neg_b : vec3 = ~(b); +} +)"; + ASSERT_EQ(expected_shader, result.wgsl); +} + +} // namespace +} // namespace tint::fuzzers::ast_fuzzer diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc index 421a75f818..f70a9479f8 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc @@ -21,6 +21,7 @@ #include #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/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" @@ -45,6 +46,8 @@ MutationFinderList CreateMutationFinders(ProbabilityContext* probability_context do { MaybeAddFinder(enable_all_mutations, probability_context, &result); + MaybeAddFinder(enable_all_mutations, + probability_context, &result); MaybeAddFinder(enable_all_mutations, probability_context, &result); MaybeAddFinder(enable_all_mutations, probability_context, diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc index 7896b3d529..639bd60fbf 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc +++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc @@ -20,6 +20,7 @@ namespace tint::fuzzers::ast_fuzzer { namespace { const std::pair kChanceOfChangingBinaryOperators = {30, 90}; +const std::pair kChanceOfChangingUnaryOperators = {30, 70}; const std::pair kChanceOfReplacingIdentifiers = {30, 70}; const std::pair kChanceOfWrappingUnaryOperators = {30, 70}; @@ -28,6 +29,7 @@ const std::pair kChanceOfWrappingUnaryOperators = {30, 70}; ProbabilityContext::ProbabilityContext(RandomGenerator* generator) : generator_(generator), chance_of_changing_binary_operators_(RandomFromRange(kChanceOfChangingBinaryOperators)), + chance_of_changing_unary_operators_(RandomFromRange(kChanceOfChangingUnaryOperators)), chance_of_replacing_identifiers_(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 f4dacb6ab9..ce467386b9 100644 --- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h +++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h @@ -56,6 +56,11 @@ class ProbabilityContext { return chance_of_changing_binary_operators_; } + /// @return the probability of changing operator for an unary expression. + uint32_t GetChanceOfChangingUnaryOperators() const { + return chance_of_changing_unary_operators_; + } + /// @return the probability of replacing some identifier with some other one. uint32_t GetChanceOfReplacingIdentifiers() const { return chance_of_replacing_identifiers_; } @@ -72,6 +77,7 @@ class ProbabilityContext { RandomGenerator* generator_; uint32_t chance_of_changing_binary_operators_; + uint32_t chance_of_changing_unary_operators_; uint32_t chance_of_replacing_identifiers_; uint32_t chance_of_wrapping_unary_operators_; }; 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 0a5951fc24..fd3cd81454 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 @@ -21,6 +21,7 @@ message Mutation { MutationReplaceIdentifier replace_identifier = 1; MutationChangeBinaryOperator change_binary_operator = 2; MutationWrapUnaryOperator wrap_unary_operator = 3; + MutationChangeUnaryOperator change_unary_operator = 4; }; } @@ -41,15 +42,7 @@ message MutatorState { MutationSequence mutation_sequence = 2; } -message MutationReplaceIdentifier { - // This transformation replaces a use of one variable with another. - - // The id of the use of a variable in the AST. - uint32 use_id = 1; - - // The id of a definition of a variable to replace the use with. - uint32 replacement_id = 2; -} +// Keep mutation messages in alphabetical order. message MutationChangeBinaryOperator { // This transformation replaces one binary operator with another. @@ -61,6 +54,26 @@ message MutationChangeBinaryOperator { uint32 new_operator = 2; } +message MutationChangeUnaryOperator { + // This transformation replaces one unary operator with another. + + // The id of a unary expression in the AST. + uint32 unary_expr_id = 1; + + // A UnaryOp representing the new unary operator. + uint32 new_operator = 2; +} + +message MutationReplaceIdentifier { + // This transformation replaces a use of one variable with another. + + // The id of the use of a variable in the AST. + uint32 use_id = 1; + + // The id of a definition of a variable to replace the use with. + uint32 replacement_id = 2; +} + message MutationWrapUnaryOperator { // This transformation wraps an expression with a allowed unary // expression operator. diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h index 5c3feee42b..945d632480 100644 --- a/src/tint/program_builder.h +++ b/src/tint/program_builder.h @@ -1538,6 +1538,15 @@ class ProgramBuilder { Expr(std::forward(expr))); } + /// @param expr the expression to perform a unary negation on + /// @return an ast::UnaryOpExpression that is the unary negation of the + /// input expression + template + const ast::UnaryOpExpression* Negation(EXPR&& expr) { + return create(ast::UnaryOp::kNegation, + Expr(std::forward(expr))); + } + /// @param source the source information /// @param func the function name /// @param args the function call arguments