From 29ba53f2a2475d271fd4023c9e91f6600d1fa4fe Mon Sep 17 00:00:00 2001 From: Vasyl Teliman Date: Tue, 23 Jun 2020 19:40:44 +0300 Subject: [PATCH] spirv-fuzz: Implement FuzzerPassAddParameters (#3399) Fixes #3384. --- source/fuzz/CMakeLists.txt | 4 + source/fuzz/fuzzer.cpp | 4 + source/fuzz/fuzzer_context.cpp | 9 + source/fuzz/fuzzer_context.h | 13 ++ source/fuzz/fuzzer_pass_add_parameters.cpp | 143 +++++++++++++ source/fuzz/fuzzer_pass_add_parameters.h | 51 +++++ .../fuzzer_pass_permute_function_parameters.h | 2 +- source/fuzz/protobufs/spvtoolsfuzz.proto | 22 ++ source/fuzz/transformation.cpp | 3 + source/fuzz/transformation_add_parameters.cpp | 201 ++++++++++++++++++ source/fuzz/transformation_add_parameters.h | 70 ++++++ test/fuzz/CMakeLists.txt | 1 + .../transformation_add_parameters_test.cpp | 177 +++++++++++++++ 13 files changed, 699 insertions(+), 1 deletion(-) create mode 100644 source/fuzz/fuzzer_pass_add_parameters.cpp create mode 100644 source/fuzz/fuzzer_pass_add_parameters.h create mode 100644 source/fuzz/transformation_add_parameters.cpp create mode 100644 source/fuzz/transformation_add_parameters.h create mode 100644 test/fuzz/transformation_add_parameters_test.cpp diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index fd6c5c946..8893d3ea0 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -48,6 +48,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_loads.h fuzzer_pass_add_local_variables.h fuzzer_pass_add_no_contraction_decorations.h + fuzzer_pass_add_parameters.h fuzzer_pass_add_stores.h fuzzer_pass_add_vector_shuffle_instructions.h fuzzer_pass_adjust_branch_weights.h @@ -94,6 +95,7 @@ if(SPIRV_BUILD_FUZZER) transformation_add_global_variable.h transformation_add_local_variable.h transformation_add_no_contraction_decoration.h + transformation_add_parameters.h transformation_add_spec_constant_op.h transformation_add_type_array.h transformation_add_type_boolean.h @@ -154,6 +156,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_loads.cpp fuzzer_pass_add_local_variables.cpp fuzzer_pass_add_no_contraction_decorations.cpp + fuzzer_pass_add_parameters.cpp fuzzer_pass_add_stores.cpp fuzzer_pass_add_vector_shuffle_instructions.cpp fuzzer_pass_adjust_branch_weights.cpp @@ -199,6 +202,7 @@ if(SPIRV_BUILD_FUZZER) transformation_add_global_variable.cpp transformation_add_local_variable.cpp transformation_add_no_contraction_decoration.cpp + transformation_add_parameters.cpp transformation_add_spec_constant_op.cpp transformation_add_type_array.cpp transformation_add_type_boolean.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index 26585c7e8..36e7f90c3 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -32,6 +32,7 @@ #include "source/fuzz/fuzzer_pass_add_loads.h" #include "source/fuzz/fuzzer_pass_add_local_variables.h" #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" +#include "source/fuzz/fuzzer_pass_add_parameters.h" #include "source/fuzz/fuzzer_pass_add_stores.h" #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h" #include "source/fuzz/fuzzer_pass_adjust_branch_weights.h" @@ -224,6 +225,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out); diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index 7f10642a2..97e145be8 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -38,6 +38,7 @@ const std::pair kChanceOfAddingLocalVariable = {20, 90}; const std::pair kChanceOfAddingMatrixType = {20, 70}; const std::pair kChanceOfAddingNoContractionDecoration = { 5, 70}; +const std::pair kChanceOfAddingParameters = {5, 70}; const std::pair kChanceOfAddingStore = {5, 50}; const std::pair kChanceOfAddingVectorType = {20, 70}; const std::pair kChanceOfAddingVectorShuffle = {20, 70}; @@ -81,6 +82,10 @@ const uint32_t kDefaultMaxLoopControlPartialCount = 100; const uint32_t kDefaultMaxLoopControlPeelCount = 100; const uint32_t kDefaultMaxLoopLimit = 20; const uint32_t kDefaultMaxNewArraySizeLimit = 100; +// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3424): +// think whether there is a better limit on the maximum number of parameters. +const uint32_t kDefaultMaxNumberOfFunctionParameters = 128; +const uint32_t kDefaultMaxNumberOfNewParameters = 15; // Default functions for controlling how deep to go during recursive // generation/transformation. Keep them in alphabetical order. @@ -104,6 +109,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, max_loop_control_peel_count_(kDefaultMaxLoopControlPeelCount), max_loop_limit_(kDefaultMaxLoopLimit), max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit), + max_number_of_function_parameters_(kDefaultMaxNumberOfFunctionParameters), + max_number_of_new_parameters_(kDefaultMaxNumberOfNewParameters), go_deeper_in_constant_obfuscation_( kDefaultGoDeeperInConstantObfuscation) { chance_of_adding_access_chain_ = @@ -129,6 +136,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfAddingMatrixType); chance_of_adding_no_contraction_decoration_ = ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration); + chance_of_adding_parameters = + ChooseBetweenMinAndMax(kChanceOfAddingParameters); chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore); chance_of_adding_vector_shuffle_ = ChooseBetweenMinAndMax(kChanceOfAddingVectorShuffle); diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index f4bf1a700..459812416 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -135,6 +135,7 @@ class FuzzerContext { uint32_t GetChanceOfAddingNoContractionDecoration() { return chance_of_adding_no_contraction_decoration_; } + uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; } uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; } uint32_t GetChanceOfAddingVectorShuffle() { return chance_of_adding_vector_shuffle_; @@ -210,6 +211,9 @@ class FuzzerContext { uint32_t GetMaximumEquivalenceClassSizeForDataSynonymFactClosure() { return max_equivalence_class_size_for_data_synonym_fact_closure_; } + uint32_t GetMaximumNumberOfFunctionParameters() { + return max_number_of_function_parameters_; + } std::pair GetRandomBranchWeights() { std::pair branch_weights = {0, 0}; @@ -245,6 +249,12 @@ class FuzzerContext { uint32_t GetRandomLoopLimit() { return random_generator_->RandomUint32(max_loop_limit_); } + uint32_t GetRandomNumberOfNewParameters(uint32_t num_of_params) { + assert(num_of_params < GetMaximumNumberOfFunctionParameters()); + return ChooseBetweenMinAndMax( + {1, std::min(max_number_of_new_parameters_, + GetMaximumNumberOfFunctionParameters() - num_of_params)}); + } uint32_t GetRandomSizeForNewArray() { // Ensure that the array size is non-zero. return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1; @@ -273,6 +283,7 @@ class FuzzerContext { uint32_t chance_of_adding_local_variable_; uint32_t chance_of_adding_matrix_type_; uint32_t chance_of_adding_no_contraction_decoration_; + uint32_t chance_of_adding_parameters; uint32_t chance_of_adding_store_; uint32_t chance_of_adding_vector_shuffle_; uint32_t chance_of_adding_vector_type_; @@ -309,6 +320,8 @@ class FuzzerContext { uint32_t max_loop_control_peel_count_; uint32_t max_loop_limit_; uint32_t max_new_array_size_limit_; + uint32_t max_number_of_function_parameters_; + uint32_t max_number_of_new_parameters_; // Functions to determine with what probability to go deeper when generating // or mutating constructs recursively. diff --git a/source/fuzz/fuzzer_pass_add_parameters.cpp b/source/fuzz/fuzzer_pass_add_parameters.cpp new file mode 100644 index 000000000..c93130207 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_parameters.cpp @@ -0,0 +1,143 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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 "source/fuzz/fuzzer_pass_add_parameters.h" + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_add_parameters.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddParameters::FuzzerPassAddParameters( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddParameters::~FuzzerPassAddParameters() = default; + +void FuzzerPassAddParameters::Apply() { + const auto& type_candidates = ComputeTypeCandidates(); + + if (type_candidates.empty()) { + // The module contains no suitable types to use in new parameters. + return; + } + + // Iterate over all functions in the module. + for (const auto& function : *GetIRContext()->module()) { + // Skip all entry-point functions - we don't want to change those. + if (fuzzerutil::FunctionIsEntryPoint(GetIRContext(), + function.result_id())) { + continue; + } + + if (GetNumberOfParameters(function) >= + GetFuzzerContext()->GetMaximumNumberOfFunctionParameters()) { + continue; + } + + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingParameters())) { + continue; + } + + const auto* type_inst = + fuzzerutil::GetFunctionType(GetIRContext(), &function); + assert(type_inst); + + // -1 because we don't take return type into account. + auto num_old_parameters = type_inst->NumInOperands() - 1; + auto num_new_parameters = + GetFuzzerContext()->GetRandomNumberOfNewParameters( + GetNumberOfParameters(function)); + + std::vector all_types(num_old_parameters); + std::vector new_types(num_new_parameters); + std::vector parameter_ids(num_new_parameters); + std::vector constant_ids(num_new_parameters); + + // Get type ids for old parameters. + for (uint32_t i = 0; i < num_old_parameters; ++i) { + // +1 since we don't take return type into account. + all_types[i] = type_inst->GetSingleWordInOperand(i + 1); + } + + for (uint32_t i = 0; i < num_new_parameters; ++i) { + // Get type ids for new parameters. + new_types[i] = + type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)]; + + // Create constants to initialize new parameters from. + constant_ids[i] = FindOrCreateZeroConstant(new_types[i]); + } + + // Append new parameters to the old ones. + all_types.insert(all_types.end(), new_types.begin(), new_types.end()); + + // Generate result ids for new parameters. + for (auto& id : parameter_ids) { + id = GetFuzzerContext()->GetFreshId(); + } + + auto result_type_id = type_inst->GetSingleWordInOperand(0); + ApplyTransformation(TransformationAddParameters( + function.result_id(), + FindOrCreateFunctionType(result_type_id, all_types), + std::move(new_types), std::move(parameter_ids), + std::move(constant_ids))); + } +} + +std::vector FuzzerPassAddParameters::ComputeTypeCandidates() const { + std::vector result; + + for (const auto* type_inst : GetIRContext()->module()->GetTypes()) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403): + // the number of types we support here is limited by the number of types + // supported by |FindOrCreateZeroConstant|. + switch (type_inst->opcode()) { + case SpvOpTypeBool: + case SpvOpTypeInt: + case SpvOpTypeFloat: + case SpvOpTypeArray: + case SpvOpTypeMatrix: + case SpvOpTypeVector: + case SpvOpTypeStruct: { + result.push_back(type_inst->result_id()); + } break; + default: + // Ignore other types. + break; + } + } + + return result; +} + +uint32_t FuzzerPassAddParameters::GetNumberOfParameters( + const opt::Function& function) const { + const auto* type = GetIRContext()->get_type_mgr()->GetType( + function.DefInst().GetSingleWordInOperand(1)); + assert(type && type->AsFunction()); + + return static_cast(type->AsFunction()->param_types().size()); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_add_parameters.h b/source/fuzz/fuzzer_pass_add_parameters.h new file mode 100644 index 000000000..feb009f9e --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_parameters.h @@ -0,0 +1,51 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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 SOURCE_FUZZ_FUZZER_PASS_ADD_PARAMETERS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_PARAMETERS_H_ + +#include + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Randomly decides for each non-entry-point function in the module whether to +// add new parameters to it. If so, randomly determines the number of parameters +// to add, their type and creates constants used to initialize them. +class FuzzerPassAddParameters : public FuzzerPass { + public: + FuzzerPassAddParameters(opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddParameters() override; + + void Apply() override; + + private: + // Uses types, defined in the module, to compute a vector of their ids, which + // will be used as type ids of new parameters. + std::vector ComputeTypeCandidates() const; + + // Returns number of parameters of |function|. + uint32_t GetNumberOfParameters(const opt::Function& function) const; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_PARAMETERS_H_ diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.h b/source/fuzz/fuzzer_pass_permute_function_parameters.h index 3f328642a..bc1031cbb 100644 --- a/source/fuzz/fuzzer_pass_permute_function_parameters.h +++ b/source/fuzz/fuzzer_pass_permute_function_parameters.h @@ -34,7 +34,7 @@ class FuzzerPassPermuteFunctionParameters : public FuzzerPass { FuzzerContext* fuzzer_context, protobufs::TransformationSequence* transformations); - ~FuzzerPassPermuteFunctionParameters(); + ~FuzzerPassPermuteFunctionParameters() override; void Apply() override; }; diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index 71273e74c..b8d07cf8d 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -380,6 +380,7 @@ message Transformation { TransformationReplaceLinearAlgebraInstruction replace_linear_algebra_instruction = 49; TransformationSwapConditionalBranchOperands swap_conditional_branch_operands = 50; TransformationPermutePhiOperands permute_phi_operands = 51; + TransformationAddParameters add_parameters = 52; // Add additional option using the next available number. } } @@ -623,6 +624,27 @@ message TransformationAddNoContractionDecoration { } +message TransformationAddParameters { + + // Adds new parameters into the function. + + // Result id of the function to add parameters to. + uint32 function_id = 1; + + // New type of the function. + uint32 new_type_id = 2; + + // Type ids of parameters to add to the function. + repeated uint32 new_parameter_type = 3; + + // Result ids for new parameters. + repeated uint32 new_parameter_id = 4; + + // Constants to initialize new parameters from. + repeated uint32 constant_id = 5; + +} + message TransformationAddSpecConstantOp { // Adds OpSpecConstantOp into the module. diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index 686b46f35..3177de0ac 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -30,6 +30,7 @@ #include "source/fuzz/transformation_add_global_variable.h" #include "source/fuzz/transformation_add_local_variable.h" #include "source/fuzz/transformation_add_no_contraction_decoration.h" +#include "source/fuzz/transformation_add_parameters.h" #include "source/fuzz/transformation_add_spec_constant_op.h" #include "source/fuzz/transformation_add_type_array.h" #include "source/fuzz/transformation_add_type_boolean.h" @@ -114,6 +115,8 @@ std::unique_ptr Transformation::FromMessage( kAddNoContractionDecoration: return MakeUnique( message.add_no_contraction_decoration()); + case protobufs::Transformation::TransformationCase::kAddParameters: + return MakeUnique(message.add_parameters()); case protobufs::Transformation::TransformationCase::kAddSpecConstantOp: return MakeUnique( message.add_spec_constant_op()); diff --git a/source/fuzz/transformation_add_parameters.cpp b/source/fuzz/transformation_add_parameters.cpp new file mode 100644 index 000000000..28af85490 --- /dev/null +++ b/source/fuzz/transformation_add_parameters.cpp @@ -0,0 +1,201 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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 "source/fuzz/transformation_add_parameters.h" + +#include + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddParameters::TransformationAddParameters( + const protobufs::TransformationAddParameters& message) + : message_(message) {} + +TransformationAddParameters::TransformationAddParameters( + uint32_t function_id, uint32_t new_type_id, + const std::vector& new_parameter_type, + const std::vector& new_parameter_id, + const std::vector& constant_id) { + message_.set_function_id(function_id); + message_.set_new_type_id(new_type_id); + + for (auto id : new_parameter_type) { + message_.add_new_parameter_type(id); + } + + for (auto id : new_parameter_id) { + message_.add_new_parameter_id(id); + } + + for (auto id : constant_id) { + message_.add_constant_id(id); + } +} + +bool TransformationAddParameters::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // Check that function exists + const auto* function = + fuzzerutil::FindFunction(ir_context, message_.function_id()); + if (!function || + fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) { + return false; + } + + // Validate new parameters. + const auto& new_type_ids = message_.new_parameter_type(); + const auto& new_parameter_ids = message_.new_parameter_id(); + const auto& constant_ids = message_.constant_id(); + + // All three vectors must have the same size. + if (new_type_ids.size() != new_parameter_ids.size() || + new_type_ids.size() != constant_ids.size()) { + return false; + } + + // Vectors must have at least one component. + if (new_type_ids.empty()) { + return false; + } + + // Check that type ids exist in the module and are not OpTypeVoid. + for (auto id : new_type_ids) { + const auto* type = ir_context->get_type_mgr()->GetType(id); + if (!type || type->AsVoid()) { + return false; + } + } + + // Check that all parameter ids are fresh. + for (auto id : new_parameter_ids) { + if (!fuzzerutil::IsFreshId(ir_context, id)) { + return false; + } + } + + // Check that constants exist and have valid type. + for (int i = 0, n = constant_ids.size(); i < n; ++i) { + const auto* inst = ir_context->get_def_use_mgr()->GetDef(constant_ids[i]); + if (!inst || inst->type_id() != new_type_ids[i]) { + return false; + } + } + + // Validate new function type. + const auto* old_type_inst = fuzzerutil::GetFunctionType(ir_context, function); + const auto* new_type_inst = + ir_context->get_def_use_mgr()->GetDef(message_.new_type_id()); + + // Both types must exist. + assert(old_type_inst && old_type_inst->opcode() == SpvOpTypeFunction); + if (!new_type_inst || new_type_inst->opcode() != SpvOpTypeFunction) { + return false; + } + + auto num_old_parameters = old_type_inst->NumInOperands(); + auto num_new_parameters = new_type_ids.size(); + + // New function type has been added to the module which means that it's valid. + // Thus, we don't need to check whether the limit on the number of arguments + // is satisfied. + + // New type = old type + new parameters. + if (new_type_inst->NumInOperands() != + num_old_parameters + num_new_parameters) { + return false; + } + + // Check that old parameters and the return type are preserved. + for (uint32_t i = 0; i < num_old_parameters; ++i) { + if (new_type_inst->GetSingleWordInOperand(i) != + old_type_inst->GetSingleWordInOperand(i)) { + return false; + } + } + + // Check that new parameters have been appended. + for (int i = 0; i < num_new_parameters; ++i) { + if (new_type_inst->GetSingleWordInOperand(i + num_old_parameters) != + new_type_ids[i]) { + return false; + } + } + + return true; +} + +void TransformationAddParameters::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + // Retrieve all data from the message + auto function_id = message_.function_id(); + const auto& new_parameter_type = message_.new_parameter_type(); + const auto& new_parameter_id = message_.new_parameter_id(); + const auto& constant_id = message_.constant_id(); + + // Find the function that will be transformed + auto* function = fuzzerutil::FindFunction(ir_context, function_id); + assert(function && "Can't find the function"); + + // Change function's type + function->DefInst().SetInOperand(1, {message_.new_type_id()}); + + // Add new parameters to the function. + for (int i = 0, n = new_parameter_id.size(); i < n; ++i) { + function->AddParameter(MakeUnique( + ir_context, SpvOpFunctionParameter, new_parameter_type[i], + new_parameter_id[i], opt::Instruction::OperandList())); + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403): + // Add an PointeeValueIsIrrelevant fact if the parameter is a pointer. + } + + // Fix all OpFunctionCall instructions. + ir_context->get_def_use_mgr()->ForEachUser( + &function->DefInst(), + [function_id, &constant_id](opt::Instruction* call) { + if (call->opcode() != SpvOpFunctionCall || + call->GetSingleWordInOperand(0) != function_id) { + return; + } + + for (auto id : constant_id) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177): + // it would be good to mark this usage of |id| as irrelevant, so that + // we can perform some interesting transformations on it later. + call->AddOperand({SPV_OPERAND_TYPE_ID, {id}}); + } + }); + + // Update module's id bound. We can safely dereference the result of + // max_element since |new_parameter_id| is guaranteed to have elements. + fuzzerutil::UpdateModuleIdBound( + ir_context, + *std::max_element(new_parameter_id.begin(), new_parameter_id.end())); + + // Make sure our changes are analyzed. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddParameters::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_parameters() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_add_parameters.h b/source/fuzz/transformation_add_parameters.h new file mode 100644 index 000000000..681195c19 --- /dev/null +++ b/source/fuzz/transformation_add_parameters.h @@ -0,0 +1,70 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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 SOURCE_FUZZ_TRANSFORMATION_ADD_PARAMETERS_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_PARAMETERS_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddParameters : public Transformation { + public: + explicit TransformationAddParameters( + const protobufs::TransformationAddParameters& message); + + TransformationAddParameters(uint32_t function_id, uint32_t new_type_id, + const std::vector& new_parameter_type, + const std::vector& new_parameter_id, + const std::vector& constant_id); + + // - |function_id| must be a valid result id of some non-entry-point function + // in the module. + // - |new_type_id| must be a result id of OpTypeFunction instruction. + // - New type of the function must have the same return type. New function + // parameters must be appended to the old ones. + // - |new_parameter_type| contains result ids of some OpType* instructions in + // the module. It may not contain result ids of OpTypeVoid. + // - |new_parameter_id| contains fresh ids. + // - |constant_id| contains result ids used to initialize new parameters. Type + // ids of these instructions must be the same as |new_parameter_type| (i.e. + // |new_parameter_type[i] == GetDef(constant_id[i])->type_id()|). + // - |new_parameter_id|, |new_parameter_type| and |constant_id| should all + // have the same size and may not be empty. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // - Creates new OpFunctionParameter instructions for the function with + // |function_id|. + // - Changes type of the function to |new_type_id|. + // - Adds ids from |constant_id| to every OpFunctionCall instruction that + // calls the function. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddParameters message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_PARAMETERS_H_ diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 9a5974271..7d9c35c5a 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -37,6 +37,7 @@ if (${SPIRV_BUILD_FUZZER}) transformation_add_global_variable_test.cpp transformation_add_local_variable_test.cpp transformation_add_no_contraction_decoration_test.cpp + transformation_add_parameters_test.cpp transformation_add_type_array_test.cpp transformation_add_type_boolean_test.cpp transformation_add_type_float_test.cpp diff --git a/test/fuzz/transformation_add_parameters_test.cpp b/test/fuzz/transformation_add_parameters_test.cpp new file mode 100644 index 000000000..37c2e9339 --- /dev/null +++ b/test/fuzz/transformation_add_parameters_test.cpp @@ -0,0 +1,177 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// 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 "source/fuzz/transformation_add_parameters.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationAddParametersTest, BasicTest) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %7 = OpTypeBool + %11 = OpTypeInt 32 1 + %3 = OpTypeFunction %2 + %20 = OpTypeFunction %2 %7 + %6 = OpTypeFunction %7 %7 + %12 = OpTypeFunction %7 %7 %11 + %13 = OpTypeFunction %7 %7 %7 + %14 = OpTypeFunction %11 %7 %11 + %15 = OpTypeFunction %7 %11 %11 + %16 = OpTypeFunction %7 %7 %11 %11 + %8 = OpConstant %11 23 + %17 = OpConstantTrue %7 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %18 = OpFunctionCall %7 %9 %17 + OpReturn + OpFunctionEnd + %9 = OpFunction %7 None %6 + %19 = OpFunctionParameter %7 + %10 = OpLabel + OpReturnValue %17 + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Can't modify entry point function. + ASSERT_FALSE(TransformationAddParameters(4, 6, {20}, {21}, {17}) + .IsApplicable(context.get(), transformation_context)); + + // There is no function with result id 10. + ASSERT_FALSE(TransformationAddParameters(29, 12, {11}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // There is no OpTypeFunction instruction with result id 21. + ASSERT_FALSE(TransformationAddParameters(9, 21, {11}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // Function type with id 6 has fewer parameters than required. + ASSERT_FALSE(TransformationAddParameters(9, 6, {11}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // Function type with id 16 has more parameters than required. + ASSERT_FALSE(TransformationAddParameters(9, 16, {11}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // New function type is not OpTypeFunction instruction. + ASSERT_FALSE(TransformationAddParameters(9, 11, {11}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // Return type is invalid. + ASSERT_FALSE(TransformationAddParameters(9, 14, {11}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // Types of original parameters are invalid. + ASSERT_FALSE(TransformationAddParameters(9, 15, {11}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // Types of new parameters are invalid. + ASSERT_FALSE(TransformationAddParameters(9, 13, {11}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // OpTypeVoid can't be the type of function parameter. + ASSERT_FALSE(TransformationAddParameters(9, 12, {2}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // Vectors, that describe parameters, have different sizes. + ASSERT_FALSE(TransformationAddParameters(9, 12, {}, {21}, {8}) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {}, {8}) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {21}, {}) + .IsApplicable(context.get(), transformation_context)); + + // Vectors cannot be empty. + ASSERT_FALSE(TransformationAddParameters(9, 12, {}, {}, {}) + .IsApplicable(context.get(), transformation_context)); + + // Parameters' ids are not fresh. + ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {20}, {8}) + .IsApplicable(context.get(), transformation_context)); + + // Constants for parameters don't exist. + ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {21}, {21}) + .IsApplicable(context.get(), transformation_context)); + + // Constants for parameters have invalid type. + ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {21}, {17}) + .IsApplicable(context.get(), transformation_context)); + + // Correct transformation. + TransformationAddParameters correct(9, 12, {11}, {21}, {8}); + ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context)); + correct.Apply(context.get(), &transformation_context); + + // The module remains valid. + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %7 = OpTypeBool + %11 = OpTypeInt 32 1 + %3 = OpTypeFunction %2 + %20 = OpTypeFunction %2 %7 + %6 = OpTypeFunction %7 %7 + %12 = OpTypeFunction %7 %7 %11 + %13 = OpTypeFunction %7 %7 %7 + %14 = OpTypeFunction %11 %7 %11 + %15 = OpTypeFunction %7 %11 %11 + %16 = OpTypeFunction %7 %7 %11 %11 + %8 = OpConstant %11 23 + %17 = OpConstantTrue %7 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %18 = OpFunctionCall %7 %9 %17 %8 + OpReturn + OpFunctionEnd + %9 = OpFunction %7 None %12 + %19 = OpFunctionParameter %7 + %21 = OpFunctionParameter %11 + %10 = OpLabel + OpReturnValue %17 + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools