| // 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_mutate_pointer.h" | |
| #include "source/fuzz/fuzzer_util.h" | |
| #include "source/fuzz/instruction_descriptor.h" | |
| namespace spvtools { | |
| namespace fuzz { | |
| TransformationMutatePointer::TransformationMutatePointer( | |
| protobufs::TransformationMutatePointer message) | |
| : message_(std::move(message)) {} | |
| TransformationMutatePointer::TransformationMutatePointer( | |
| uint32_t pointer_id, uint32_t fresh_id, | |
| const protobufs::InstructionDescriptor& insert_before) { | |
| message_.set_pointer_id(pointer_id); | |
| message_.set_fresh_id(fresh_id); | |
| *message_.mutable_insert_before() = insert_before; | |
| } | |
| bool TransformationMutatePointer::IsApplicable( | |
| opt::IRContext* ir_context, | |
| const TransformationContext& transformation_context) const { | |
| // Check that |fresh_id| is fresh. | |
| if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) { | |
| return false; | |
| } | |
| auto* insert_before_inst = | |
| FindInstruction(message_.insert_before(), ir_context); | |
| // Check that |insert_before| is a valid instruction descriptor. | |
| if (!insert_before_inst) { | |
| return false; | |
| } | |
| // Check that it is possible to insert OpLoad and OpStore before | |
| // |insert_before_inst|. We are only using OpLoad here since the result does | |
| // not depend on the opcode. | |
| if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(spv::Op::OpLoad, | |
| insert_before_inst)) { | |
| return false; | |
| } | |
| const auto* pointer_inst = | |
| ir_context->get_def_use_mgr()->GetDef(message_.pointer_id()); | |
| // Check that |pointer_id| is a result id of a valid pointer instruction. | |
| if (!pointer_inst || !IsValidPointerInstruction(ir_context, *pointer_inst)) { | |
| return false; | |
| } | |
| // Check that the module contains an irrelevant constant that will be used to | |
| // mutate |pointer_inst|. The constant is irrelevant so that the latter | |
| // transformation can change its value to something more interesting. | |
| auto constant_id = fuzzerutil::MaybeGetZeroConstant( | |
| ir_context, transformation_context, | |
| fuzzerutil::GetPointeeTypeIdFromPointerType(ir_context, | |
| pointer_inst->type_id()), | |
| true); | |
| if (!constant_id) { | |
| return false; | |
| } | |
| assert(fuzzerutil::IdIsAvailableBeforeInstruction( | |
| ir_context, insert_before_inst, constant_id) && | |
| "Global constant instruction is not available before " | |
| "|insert_before_inst|"); | |
| // Check that |pointer_inst| is available before |insert_before_inst|. | |
| return fuzzerutil::IdIsAvailableBeforeInstruction( | |
| ir_context, insert_before_inst, pointer_inst->result_id()); | |
| } | |
| void TransformationMutatePointer::Apply( | |
| opt::IRContext* ir_context, | |
| TransformationContext* transformation_context) const { | |
| auto* insert_before_inst = | |
| FindInstruction(message_.insert_before(), ir_context); | |
| assert(insert_before_inst && "|insert_before| descriptor is invalid"); | |
| opt::BasicBlock* enclosing_block = | |
| ir_context->get_instr_block(insert_before_inst); | |
| auto pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType( | |
| ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id())); | |
| // Back up the original value. | |
| auto backup_instruction = MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpLoad, pointee_type_id, message_.fresh_id(), | |
| opt::Instruction::OperandList{ | |
| {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}}); | |
| auto backup_instruction_ptr = backup_instruction.get(); | |
| insert_before_inst->InsertBefore(std::move(backup_instruction)); | |
| ir_context->get_def_use_mgr()->AnalyzeInstDefUse(backup_instruction_ptr); | |
| ir_context->set_instr_block(backup_instruction_ptr, enclosing_block); | |
| // Insert a new value. | |
| auto new_value_instruction = MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpStore, 0, 0, | |
| opt::Instruction::OperandList{ | |
| {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}, | |
| {SPV_OPERAND_TYPE_ID, | |
| {fuzzerutil::MaybeGetZeroConstant( | |
| ir_context, *transformation_context, pointee_type_id, true)}}}); | |
| auto new_value_instruction_ptr = new_value_instruction.get(); | |
| insert_before_inst->InsertBefore(std::move(new_value_instruction)); | |
| ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_value_instruction_ptr); | |
| ir_context->set_instr_block(new_value_instruction_ptr, enclosing_block); | |
| // Restore the original value. | |
| auto restore_instruction = MakeUnique<opt::Instruction>( | |
| ir_context, spv::Op::OpStore, 0, 0, | |
| opt::Instruction::OperandList{ | |
| {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}, | |
| {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}}}); | |
| auto restore_instruction_ptr = restore_instruction.get(); | |
| insert_before_inst->InsertBefore(std::move(restore_instruction)); | |
| ir_context->get_def_use_mgr()->AnalyzeInstDefUse(restore_instruction_ptr); | |
| ir_context->set_instr_block(restore_instruction_ptr, enclosing_block); | |
| fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); | |
| } | |
| protobufs::Transformation TransformationMutatePointer::ToMessage() const { | |
| protobufs::Transformation result; | |
| *result.mutable_mutate_pointer() = message_; | |
| return result; | |
| } | |
| bool TransformationMutatePointer::IsValidPointerInstruction( | |
| opt::IRContext* ir_context, const opt::Instruction& inst) { | |
| // |inst| must have both result id and type id and it may not cause undefined | |
| // behaviour. | |
| if (!inst.result_id() || !inst.type_id() || | |
| inst.opcode() == spv::Op::OpUndef || | |
| inst.opcode() == spv::Op::OpConstantNull) { | |
| return false; | |
| } | |
| opt::Instruction* type_inst = | |
| ir_context->get_def_use_mgr()->GetDef(inst.type_id()); | |
| assert(type_inst != nullptr && "|inst| has invalid type id"); | |
| // |inst| must be a pointer. | |
| if (type_inst->opcode() != spv::Op::OpTypePointer) { | |
| return false; | |
| } | |
| // |inst| must have a supported storage class. | |
| switch ( | |
| static_cast<spv::StorageClass>(type_inst->GetSingleWordInOperand(0))) { | |
| case spv::StorageClass::Function: | |
| case spv::StorageClass::Private: | |
| case spv::StorageClass::Workgroup: | |
| break; | |
| default: | |
| return false; | |
| } | |
| // |inst|'s pointee must consist of scalars and/or composites. | |
| return fuzzerutil::CanCreateConstant(ir_context, | |
| type_inst->GetSingleWordInOperand(1)); | |
| } | |
| std::unordered_set<uint32_t> TransformationMutatePointer::GetFreshIds() const { | |
| return {message_.fresh_id()}; | |
| } | |
| } // namespace fuzz | |
| } // namespace spvtools |