#include "source/fuzz/fuzzer_pass_donate_modules.h"
#include <map>
#include <queue>
#include <set>
#include "source/fuzz/call_graph.h"
#include "source/fuzz/instruction_message.h"
#include "source/fuzz/transformation_add_constant_boolean.h"
#include "source/fuzz/transformation_add_constant_composite.h"
#include "source/fuzz/transformation_add_constant_null.h"
#include "source/fuzz/transformation_add_constant_scalar.h"
#include "source/fuzz/transformation_add_function.h"
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_global_variable.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"
#include "source/fuzz/transformation_add_type_float.h"
#include "source/fuzz/transformation_add_type_function.h"
#include "source/fuzz/transformation_add_type_int.h"
#include "source/fuzz/transformation_add_type_matrix.h"
#include "source/fuzz/transformation_add_type_pointer.h"
#include "source/fuzz/transformation_add_type_struct.h"
#include "source/fuzz/transformation_add_type_vector.h"
namespace spvtools {
namespace fuzz {
FuzzerPassDonateModules::FuzzerPassDonateModules(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations,
bool ignore_inapplicable_transformations,
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations, ignore_inapplicable_transformations),
donor_suppliers_(std::move(donor_suppliers)) {}
void FuzzerPassDonateModules::Apply() {
if (donor_suppliers_.empty()) {
return;
}
do {
std::unique_ptr<opt::IRContext> donor_ir_context = donor_suppliers_.at(
GetFuzzerContext()->RandomIndex(donor_suppliers_))();
assert(donor_ir_context != nullptr && "Supplying of donor failed");
assert(
fuzzerutil::IsValid(donor_ir_context.get(),
GetTransformationContext()->GetValidatorOptions(),
fuzzerutil::kSilentMessageConsumer) &&
"The donor module must be valid");
bool make_livesafe = GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->ChanceOfMakingDonorLivesafe());
DonateSingleModule(donor_ir_context.get(), make_livesafe);
} while (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfDonatingAdditionalModule()));
}
void FuzzerPassDonateModules::DonateSingleModule(
opt::IRContext* donor_ir_context, bool make_livesafe) {
for (const auto& capability_inst : donor_ir_context->capabilities()) {
auto capability =
static_cast<spv::Capability>(capability_inst.GetSingleWordInOperand(0));
if (!GetIRContext()->get_feature_mgr()->HasCapability(capability)) {
return;
}
}
std::map<uint32_t, uint32_t> original_id_to_donated_id;
HandleExternalInstructionImports(donor_ir_context,
&original_id_to_donated_id);
HandleTypesAndValues(donor_ir_context, &original_id_to_donated_id);
HandleFunctions(donor_ir_context, &original_id_to_donated_id, make_livesafe);
}
spv::StorageClass FuzzerPassDonateModules::AdaptStorageClass(
spv::StorageClass donor_storage_class) {
switch (donor_storage_class) {
case spv::StorageClass::Function:
case spv::StorageClass::Private:
case spv::StorageClass::Workgroup:
return donor_storage_class;
case spv::StorageClass::Input:
case spv::StorageClass::Output:
case spv::StorageClass::Uniform:
case spv::StorageClass::UniformConstant:
case spv::StorageClass::PushConstant:
case spv::StorageClass::Image:
case spv::StorageClass::StorageBuffer:
return spv::StorageClass::Private;
default:
assert(false && "Currently unsupported storage class.");
return spv::StorageClass::Max;
}
}
void FuzzerPassDonateModules::HandleExternalInstructionImports(
opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
for (auto& donor_import : donor_ir_context->module()->ext_inst_imports()) {
const auto& donor_import_name_words = donor_import.GetInOperand(0).words;
for (auto& existing_import : GetIRContext()->module()->ext_inst_imports()) {
const auto& existing_import_name_words =
existing_import.GetInOperand(0).words;
if (donor_import_name_words == existing_import_name_words) {
original_id_to_donated_id->insert(
{donor_import.result_id(), existing_import.result_id()});
break;
}
}
assert(original_id_to_donated_id->count(donor_import.result_id()) &&
"Donation of imports is not yet supported.");
}
}
void FuzzerPassDonateModules::HandleTypesAndValues(
opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
for (auto& type_or_value : donor_ir_context->module()->types_values()) {
HandleTypeOrValue(type_or_value, original_id_to_donated_id);
}
}
void FuzzerPassDonateModules::HandleTypeOrValue(
const opt::Instruction& type_or_value,
std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
uint32_t new_result_id;
switch (type_or_value.opcode()) {
case spv::Op::OpTypeImage:
case spv::Op::OpTypeSampledImage:
case spv::Op::OpTypeSampler:
return;
case spv::Op::OpTypeVoid: {
opt::analysis::Void void_type;
new_result_id = GetIRContext()->get_type_mgr()->GetId(&void_type);
assert(new_result_id &&
"The module being transformed will always have 'void' type "
"declared.");
} break;
case spv::Op::OpTypeBool: {
opt::analysis::Bool bool_type;
auto bool_type_id = GetIRContext()->get_type_mgr()->GetId(&bool_type);
if (bool_type_id) {
new_result_id = bool_type_id;
} else {
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeBoolean(new_result_id));
}
} break;
case spv::Op::OpTypeInt: {
const uint32_t width = type_or_value.GetSingleWordInOperand(0);
const bool is_signed =
static_cast<bool>(type_or_value.GetSingleWordInOperand(1));
opt::analysis::Integer int_type(width, is_signed);
auto int_type_id = GetIRContext()->get_type_mgr()->GetId(&int_type);
if (int_type_id) {
new_result_id = int_type_id;
} else {
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypeInt(new_result_id, width, is_signed));
}
} break;
case spv::Op::OpTypeFloat: {
const uint32_t width = type_or_value.GetSingleWordInOperand(0);
opt::analysis::Float float_type(width);
auto float_type_id = GetIRContext()->get_type_mgr()->GetId(&float_type);
if (float_type_id) {
new_result_id = float_type_id;
} else {
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeFloat(new_result_id, width));
}
} break;
case spv::Op::OpTypeVector: {
uint32_t component_type_id = original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(0));
auto component_type =
GetIRContext()->get_type_mgr()->GetType(component_type_id);
assert(component_type && "The base type should be registered.");
auto component_count = type_or_value.GetSingleWordInOperand(1);
opt::analysis::Vector vector_type(component_type, component_count);
auto vector_type_id = GetIRContext()->get_type_mgr()->GetId(&vector_type);
if (vector_type_id) {
new_result_id = vector_type_id;
} else {
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeVector(
new_result_id, component_type_id, component_count));
}
} break;
case spv::Op::OpTypeMatrix: {
uint32_t column_type_id = original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(0));
auto column_type =
GetIRContext()->get_type_mgr()->GetType(column_type_id);
assert(column_type && column_type->AsVector() &&
"The column type should be a registered vector type.");
auto column_count = type_or_value.GetSingleWordInOperand(1);
opt::analysis::Matrix matrix_type(column_type, column_count);
auto matrix_type_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type);
if (matrix_type_id) {
new_result_id = matrix_type_id;
} else {
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeMatrix(
new_result_id, column_type_id, column_count));
}
} break;
case spv::Op::OpTypeArray: {
uint32_t component_type_id = type_or_value.GetSingleWordInOperand(0);
if (!original_id_to_donated_id->count(component_type_id)) {
return;
}
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeArray(
new_result_id, original_id_to_donated_id->at(component_type_id),
original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(1))));
} break;
case spv::Op::OpTypeRuntimeArray: {
uint32_t component_type_id = type_or_value.GetSingleWordInOperand(0);
if (!original_id_to_donated_id->count(component_type_id)) {
return;
}
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeArray(
new_result_id, original_id_to_donated_id->at(component_type_id),
FindOrCreateIntegerConstant(
{GetFuzzerContext()->GetRandomSizeForNewArray()}, 32, false,
false)));
} break;
case spv::Op::OpTypeStruct: {
std::vector<uint32_t> member_type_ids;
for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
auto component_type_id = type_or_value.GetSingleWordInOperand(i);
if (!original_id_to_donated_id->count(component_type_id)) {
return;
}
member_type_ids.push_back(
original_id_to_donated_id->at(component_type_id));
}
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(
TransformationAddTypeStruct(new_result_id, member_type_ids));
} break;
case spv::Op::OpTypePointer: {
uint32_t pointee_type_id = type_or_value.GetSingleWordInOperand(1);
if (!original_id_to_donated_id->count(pointee_type_id)) {
return;
}
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypePointer(
new_result_id,
AdaptStorageClass(static_cast<spv::StorageClass>(
type_or_value.GetSingleWordInOperand(0))),
original_id_to_donated_id->at(pointee_type_id)));
} break;
case spv::Op::OpTypeFunction: {
std::vector<uint32_t> return_and_parameter_types;
for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
uint32_t return_or_parameter_type =
type_or_value.GetSingleWordInOperand(i);
if (!original_id_to_donated_id->count(return_or_parameter_type)) {
return;
}
return_and_parameter_types.push_back(
original_id_to_donated_id->at(return_or_parameter_type));
}
uint32_t existing_function_id = fuzzerutil::FindFunctionType(
GetIRContext(), return_and_parameter_types);
if (existing_function_id) {
new_result_id = existing_function_id;
} else {
new_result_id = GetFuzzerContext()->GetFreshId();
std::vector<uint32_t> argument_type_ids;
for (uint32_t i = 1; i < type_or_value.NumInOperands(); i++) {
argument_type_ids.push_back(original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(i)));
}
ApplyTransformation(TransformationAddTypeFunction(
new_result_id,
original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(0)),
argument_type_ids));
}
} break;
case spv::Op::OpSpecConstantOp: {
new_result_id = GetFuzzerContext()->GetFreshId();
auto type_id = original_id_to_donated_id->at(type_or_value.type_id());
auto opcode =
static_cast<spv::Op>(type_or_value.GetSingleWordInOperand(0));
opt::Instruction::OperandList operands;
for (uint32_t i = 1; i < type_or_value.NumInOperands(); ++i) {
const auto& operand = type_or_value.GetInOperand(i);
auto data =
operand.type == SPV_OPERAND_TYPE_ID
? opt::Operand::OperandData{original_id_to_donated_id->at(
operand.words[0])}
: operand.words;
operands.push_back({operand.type, std::move(data)});
}
ApplyTransformation(TransformationAddSpecConstantOp(
new_result_id, type_id, opcode, std::move(operands)));
} break;
case spv::Op::OpSpecConstantTrue:
case spv::Op::OpSpecConstantFalse:
case spv::Op::OpConstantTrue:
case spv::Op::OpConstantFalse: {
new_result_id = GetFuzzerContext()->GetFreshId();
auto value = type_or_value.opcode() == spv::Op::OpConstantTrue ||
type_or_value.opcode() == spv::Op::OpSpecConstantTrue;
ApplyTransformation(
TransformationAddConstantBoolean(new_result_id, value, false));
} break;
case spv::Op::OpSpecConstant:
case spv::Op::OpConstant: {
new_result_id = GetFuzzerContext()->GetFreshId();
std::vector<uint32_t> data_words;
type_or_value.ForEachInOperand([&data_words](const uint32_t* in_operand) {
data_words.push_back(*in_operand);
});
ApplyTransformation(TransformationAddConstantScalar(
new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
data_words, false));
} break;
case spv::Op::OpSpecConstantComposite:
case spv::Op::OpConstantComposite: {
assert(original_id_to_donated_id->count(type_or_value.type_id()) &&
"Composite types for which it is possible to create a constant "
"should have been donated.");
new_result_id = GetFuzzerContext()->GetFreshId();
std::vector<uint32_t> constituent_ids;
type_or_value.ForEachInId([&constituent_ids, &original_id_to_donated_id](
const uint32_t* constituent_id) {
assert(original_id_to_donated_id->count(*constituent_id) &&
"The constants used to construct this composite should "
"have been donated.");
constituent_ids.push_back(
original_id_to_donated_id->at(*constituent_id));
});
ApplyTransformation(TransformationAddConstantComposite(
new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
constituent_ids, false));
} break;
case spv::Op::OpConstantNull: {
if (!original_id_to_donated_id->count(type_or_value.type_id())) {
return;
}
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantNull(
new_result_id,
original_id_to_donated_id->at(type_or_value.type_id())));
} break;
case spv::Op::OpVariable: {
if (!original_id_to_donated_id->count(type_or_value.type_id())) {
return;
}
new_result_id = GetFuzzerContext()->GetFreshId();
uint32_t remapped_pointer_type =
original_id_to_donated_id->at(type_or_value.type_id());
uint32_t initializer_id;
spv::StorageClass storage_class =
static_cast<spv::StorageClass>(type_or_value.GetSingleWordInOperand(
0)) == spv::StorageClass::Workgroup
? spv::StorageClass::Workgroup
: spv::StorageClass::Private;
if (type_or_value.NumInOperands() == 1) {
initializer_id = storage_class == spv::StorageClass::Workgroup
? 0
: FindOrCreateZeroConstant(
fuzzerutil::GetPointeeTypeIdFromPointerType(
GetIRContext(), remapped_pointer_type),
false);
} else {
initializer_id = original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(1));
}
ApplyTransformation(
TransformationAddGlobalVariable(new_result_id, remapped_pointer_type,
storage_class, initializer_id, true));
} break;
case spv::Op::OpUndef: {
if (!original_id_to_donated_id->count(type_or_value.type_id())) {
return;
}
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddGlobalUndef(
new_result_id,
original_id_to_donated_id->at(type_or_value.type_id())));
} break;
default: {
assert(0 && "Unknown type/value.");
new_result_id = 0;
} break;
}
original_id_to_donated_id->insert({type_or_value.result_id(), new_result_id});
}
void FuzzerPassDonateModules::HandleFunctions(
opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
bool make_livesafe) {
auto topological_order =
CallGraph(donor_ir_context).GetFunctionsInTopologicalOrder();
for (auto function_id = topological_order.rbegin();
function_id != topological_order.rend(); ++function_id) {
opt::Function* function_to_donate = nullptr;
for (auto& function : *donor_ir_context->module()) {
if (function.result_id() == *function_id) {
function_to_donate = &function;
break;
}
}
assert(function_to_donate && "Function to be donated was not found.");
if (!original_id_to_donated_id->count(
function_to_donate->DefInst().GetSingleWordInOperand(1))) {
continue;
}
std::vector<protobufs::Instruction> donated_instructions;
std::set<uint32_t> skipped_instructions;
function_to_donate->ForEachInst(
[this, &donated_instructions, donor_ir_context,
&original_id_to_donated_id,
&skipped_instructions](const opt::Instruction* instruction) {
if (instruction->opcode() == spv::Op::OpArrayLength) {
HandleOpArrayLength(*instruction, original_id_to_donated_id,
&donated_instructions);
} else if (!CanDonateInstruction(donor_ir_context, *instruction,
*original_id_to_donated_id,
skipped_instructions)) {
HandleDifficultInstruction(*instruction, original_id_to_donated_id,
&donated_instructions,
&skipped_instructions);
} else {
PrepareInstructionForDonation(*instruction, donor_ir_context,
original_id_to_donated_id,
&donated_instructions);
}
});
if (!make_livesafe ||
!MaybeAddLivesafeFunction(*function_to_donate, donor_ir_context,
*original_id_to_donated_id,
donated_instructions)) {
ApplyTransformation(TransformationAddFunction(donated_instructions));
}
}
}
bool FuzzerPassDonateModules::CanDonateInstruction(
opt::IRContext* donor_ir_context, const opt::Instruction& instruction,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
const std::set<uint32_t>& skipped_instructions) const {
if (instruction.type_id() &&
!original_id_to_donated_id.count(instruction.type_id())) {
return false;
}
switch (instruction.opcode()) {
case spv::Op::OpAtomicLoad:
case spv::Op::OpAtomicStore:
case spv::Op::OpAtomicExchange:
case spv::Op::OpAtomicCompareExchange:
case spv::Op::OpAtomicCompareExchangeWeak:
case spv::Op::OpAtomicIIncrement:
case spv::Op::OpAtomicIDecrement:
case spv::Op::OpAtomicIAdd:
case spv::Op::OpAtomicISub:
case spv::Op::OpAtomicSMin:
case spv::Op::OpAtomicUMin:
case spv::Op::OpAtomicSMax:
case spv::Op::OpAtomicUMax:
case spv::Op::OpAtomicAnd:
case spv::Op::OpAtomicOr:
case spv::Op::OpAtomicXor:
case spv::Op::OpImageSampleImplicitLod:
case spv::Op::OpImageSampleExplicitLod:
case spv::Op::OpImageSampleDrefImplicitLod:
case spv::Op::OpImageSampleDrefExplicitLod:
case spv::Op::OpImageSampleProjImplicitLod:
case spv::Op::OpImageSampleProjExplicitLod:
case spv::Op::OpImageSampleProjDrefImplicitLod:
case spv::Op::OpImageSampleProjDrefExplicitLod:
case spv::Op::OpImageFetch:
case spv::Op::OpImageGather:
case spv::Op::OpImageDrefGather:
case spv::Op::OpImageRead:
case spv::Op::OpImageWrite:
case spv::Op::OpImageSparseSampleImplicitLod:
case spv::Op::OpImageSparseSampleExplicitLod:
case spv::Op::OpImageSparseSampleDrefImplicitLod:
case spv::Op::OpImageSparseSampleDrefExplicitLod:
case spv::Op::OpImageSparseSampleProjImplicitLod:
case spv::Op::OpImageSparseSampleProjExplicitLod:
case spv::Op::OpImageSparseSampleProjDrefImplicitLod:
case spv::Op::OpImageSparseSampleProjDrefExplicitLod:
case spv::Op::OpImageSparseFetch:
case spv::Op::OpImageSparseGather:
case spv::Op::OpImageSparseDrefGather:
case spv::Op::OpImageSparseRead:
case spv::Op::OpImageSampleFootprintNV:
case spv::Op::OpImage:
case spv::Op::OpImageQueryFormat:
case spv::Op::OpImageQueryLevels:
case spv::Op::OpImageQueryLod:
case spv::Op::OpImageQueryOrder:
case spv::Op::OpImageQuerySamples:
case spv::Op::OpImageQuerySize:
case spv::Op::OpImageQuerySizeLod:
case spv::Op::OpSampledImage:
return false;
case spv::Op::OpLoad:
switch (donor_ir_context->get_def_use_mgr()
->GetDef(instruction.type_id())
->opcode()) {
case spv::Op::OpTypeImage:
case spv::Op::OpTypeSampledImage:
case spv::Op::OpTypeSampler:
return false;
default:
break;
}
default:
break;
}
bool result = true;
instruction.WhileEachInId(
[donor_ir_context, &original_id_to_donated_id, &result,
&skipped_instructions](const uint32_t* in_id) -> bool {
if (!original_id_to_donated_id.count(*in_id)) {
if (skipped_instructions.count(*in_id) ||
!donor_ir_context->get_instr_block(*in_id)) {
result = false;
return false;
}
}
return true;
});
return result;
}
bool FuzzerPassDonateModules::IsBasicType(
const opt::Instruction& instruction) const {
switch (instruction.opcode()) {
case spv::Op::OpTypeArray:
case spv::Op::OpTypeBool:
case spv::Op::OpTypeFloat:
case spv::Op::OpTypeInt:
case spv::Op::OpTypeMatrix:
case spv::Op::OpTypeStruct:
case spv::Op::OpTypeVector:
return true;
default:
return false;
}
}
void FuzzerPassDonateModules::HandleOpArrayLength(
const opt::Instruction& instruction,
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
std::vector<protobufs::Instruction>* donated_instructions) const {
assert(instruction.opcode() == spv::Op::OpArrayLength &&
"Precondition: instruction must be OpArrayLength.");
uint32_t donated_variable_id =
original_id_to_donated_id->at(instruction.GetSingleWordInOperand(0));
auto donated_variable_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(donated_variable_id);
auto pointer_to_struct_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(
donated_variable_instruction->type_id());
assert(pointer_to_struct_instruction->opcode() == spv::Op::OpTypePointer &&
"Type of variable must be pointer.");
auto donated_struct_type_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(
pointer_to_struct_instruction->GetSingleWordInOperand(1));
assert(donated_struct_type_instruction->opcode() == spv::Op::OpTypeStruct &&
"Pointee type of pointer used by OpArrayLength must be struct.");
assert(donated_struct_type_instruction->NumInOperands() ==
instruction.GetSingleWordInOperand(1) + 1 &&
"OpArrayLength must refer to the final member of the given "
"struct.");
uint32_t fixed_size_array_type_id =
donated_struct_type_instruction->GetSingleWordInOperand(
donated_struct_type_instruction->NumInOperands() - 1);
auto fixed_size_array_type_instruction =
GetIRContext()->get_def_use_mgr()->GetDef(fixed_size_array_type_id);
assert(fixed_size_array_type_instruction->opcode() == spv::Op::OpTypeArray &&
"The donated array type must be fixed-size.");
auto array_size_id =
fixed_size_array_type_instruction->GetSingleWordInOperand(1);
if (instruction.result_id() &&
!original_id_to_donated_id->count(instruction.result_id())) {
original_id_to_donated_id->insert(
{instruction.result_id(), GetFuzzerContext()->GetFreshId()});
}
donated_instructions->push_back(MakeInstructionMessage(
spv::Op::OpCopyObject,
original_id_to_donated_id->at(instruction.type_id()),
original_id_to_donated_id->at(instruction.result_id()),
opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {array_size_id}}})));
}
void FuzzerPassDonateModules::HandleDifficultInstruction(
const opt::Instruction& instruction,
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
std::vector<protobufs::Instruction>* donated_instructions,
std::set<uint32_t>* skipped_instructions) {
if (!instruction.result_id()) {
return;
}
if (!original_id_to_donated_id->count(instruction.type_id())) {
skipped_instructions->insert(instruction.result_id());
return;
}
auto remapped_type_id = original_id_to_donated_id->at(instruction.type_id());
if (!IsBasicType(
*GetIRContext()->get_def_use_mgr()->GetDef(remapped_type_id))) {
skipped_instructions->insert(instruction.result_id());
return;
}
if (!original_id_to_donated_id->count(instruction.result_id())) {
original_id_to_donated_id->insert(
{instruction.result_id(), GetFuzzerContext()->GetFreshId()});
}
auto zero_constant = FindOrCreateZeroConstant(remapped_type_id, true);
donated_instructions->push_back(MakeInstructionMessage(
spv::Op::OpCopyObject, remapped_type_id,
original_id_to_donated_id->at(instruction.result_id()),
opt::Instruction::OperandList({{SPV_OPERAND_TYPE_ID, {zero_constant}}})));
}
void FuzzerPassDonateModules::PrepareInstructionForDonation(
const opt::Instruction& instruction, opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id,
std::vector<protobufs::Instruction>* donated_instructions) {
opt::Instruction::OperandList input_operands;
for (uint32_t in_operand_index = 0;
in_operand_index < instruction.NumInOperands(); in_operand_index++) {
std::vector<uint32_t> operand_data;
const opt::Operand& in_operand = instruction.GetInOperand(in_operand_index);
if (spvIsIdType(in_operand.type)) {
auto operand_id = in_operand.words[0];
if (!original_id_to_donated_id->count(operand_id)) {
(void)(donor_ir_context);
assert((donor_ir_context->get_def_use_mgr()
->GetDef(operand_id)
->opcode() == spv::Op::OpLabel ||
instruction.opcode() == spv::Op::OpPhi) &&
"Unsupported forward reference.");
original_id_to_donated_id->insert(
{operand_id, GetFuzzerContext()->GetFreshId()});
}
operand_data.push_back(original_id_to_donated_id->at(operand_id));
} else {
for (auto word : in_operand.words) {
operand_data.push_back(word);
}
}
input_operands.push_back({in_operand.type, operand_data});
}
if (instruction.opcode() == spv::Op::OpVariable &&
instruction.NumInOperands() == 1) {
input_operands.push_back(
{SPV_OPERAND_TYPE_ID,
{FindOrCreateZeroConstant(
fuzzerutil::GetPointeeTypeIdFromPointerType(
GetIRContext(),
original_id_to_donated_id->at(instruction.type_id())),
false)}});
}
if (instruction.result_id() &&
!original_id_to_donated_id->count(instruction.result_id())) {
original_id_to_donated_id->insert(
{instruction.result_id(), GetFuzzerContext()->GetFreshId()});
}
donated_instructions->push_back(MakeInstructionMessage(
instruction.opcode(),
instruction.type_id()
? original_id_to_donated_id->at(instruction.type_id())
: 0,
instruction.result_id()
? original_id_to_donated_id->at(instruction.result_id())
: 0,
input_operands));
}
bool FuzzerPassDonateModules::CreateLoopLimiterInfo(
opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
protobufs::LoopLimiterInfo* out) {
assert(loop_header.IsLoopHeader() && "|loop_header| is not a loop header");
out->set_loop_header_id(original_id_to_donated_id.at(loop_header.id()));
out->set_load_id(GetFuzzerContext()->GetFreshId());
out->set_increment_id(GetFuzzerContext()->GetFreshId());
out->set_compare_id(GetFuzzerContext()->GetFreshId());
out->set_logical_op_id(GetFuzzerContext()->GetFreshId());
const auto back_edge_block_id = TransformationAddFunction::GetBackEdgeBlockId(
donor_ir_context, loop_header.id());
if (!back_edge_block_id) {
return true;
}
auto* back_edge_block = donor_ir_context->cfg()->block(back_edge_block_id);
assert(back_edge_block && "|back_edge_block_id| is invalid");
const auto* merge_block =
donor_ir_context->cfg()->block(loop_header.MergeBlockId());
assert(merge_block && "Loop header has invalid merge block id");
if (back_edge_block->IsSuccessor(merge_block)) {
return true;
}
for (const auto& inst : *merge_block) {
if (inst.opcode() != spv::Op::OpPhi) {
break;
}
uint32_t suitable_operand_id = 0;
for (uint32_t i = 0; i < inst.NumInOperands(); i += 2) {
auto dependency_inst_id = inst.GetSingleWordInOperand(i);
if (fuzzerutil::IdIsAvailableBeforeInstruction(
donor_ir_context, back_edge_block->terminator(),
dependency_inst_id)) {
suitable_operand_id = original_id_to_donated_id.at(dependency_inst_id);
break;
}
}
if (suitable_operand_id == 0 &&
IsBasicType(
*donor_ir_context->get_def_use_mgr()->GetDef(inst.type_id()))) {
suitable_operand_id = FindOrCreateZeroConstant(
original_id_to_donated_id.at(inst.type_id()), true);
}
if (suitable_operand_id == 0) {
return false;
}
out->add_phi_id(suitable_operand_id);
}
return true;
}
bool FuzzerPassDonateModules::MaybeAddLivesafeFunction(
const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
const std::vector<protobufs::Instruction>& donated_instructions) {
FindOrCreateBoolType();
FindOrCreatePointerToIntegerType(
32, false,
spv::StorageClass::Function);
FindOrCreateIntegerConstant({0}, 32, false,
false);
FindOrCreateIntegerConstant({1}, 32, false,
false);
const uint32_t loop_limiter_variable_id = GetFuzzerContext()->GetFreshId();
const uint32_t loop_limit = FindOrCreateIntegerConstant(
{GetFuzzerContext()->GetRandomLoopLimit()}, 32, false, false);
std::vector<protobufs::LoopLimiterInfo> loop_limiters;
for (auto& block : function_to_donate) {
if (block.IsLoopHeader()) {
protobufs::LoopLimiterInfo loop_limiter;
if (!CreateLoopLimiterInfo(donor_ir_context, block,
original_id_to_donated_id, &loop_limiter)) {
return false;
}
loop_limiters.emplace_back(std::move(loop_limiter));
}
}
std::vector<protobufs::AccessChainClampingInfo> access_chain_clamping_info;
for (auto& block : function_to_donate) {
for (auto& inst : block) {
switch (inst.opcode()) {
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain: {
protobufs::AccessChainClampingInfo clamping_info;
clamping_info.set_access_chain_id(
original_id_to_donated_id.at(inst.result_id()));
auto base_object = donor_ir_context->get_def_use_mgr()->GetDef(
inst.GetSingleWordInOperand(0));
assert(base_object && "The base object must exist.");
auto pointer_type = donor_ir_context->get_def_use_mgr()->GetDef(
base_object->type_id());
assert(pointer_type &&
pointer_type->opcode() == spv::Op::OpTypePointer &&
"The base object must have pointer type.");
auto should_be_composite_type =
donor_ir_context->get_def_use_mgr()->GetDef(
pointer_type->GetSingleWordInOperand(1));
for (uint32_t index = 1; index < inst.NumInOperands(); index++) {
auto compare_and_select_ids =
clamping_info.add_compare_and_select_ids();
compare_and_select_ids->set_first(GetFuzzerContext()->GetFreshId());
compare_and_select_ids->set_second(
GetFuzzerContext()->GetFreshId());
uint32_t bound;
if (should_be_composite_type->opcode() ==
spv::Op::OpTypeRuntimeArray) {
auto fixed_size_array_type =
GetIRContext()->get_def_use_mgr()->GetDef(
original_id_to_donated_id.at(
should_be_composite_type->result_id()));
assert(fixed_size_array_type->opcode() == spv::Op::OpTypeArray &&
"A runtime array type in the donor should have been "
"replaced by a fixed-sized array in the recipient.");
bound = fuzzerutil::GetBoundForCompositeIndex(
*fixed_size_array_type, GetIRContext());
} else {
bound = fuzzerutil::GetBoundForCompositeIndex(
*should_be_composite_type, donor_ir_context);
}
const uint32_t index_id = inst.GetSingleWordInOperand(index);
auto index_inst =
donor_ir_context->get_def_use_mgr()->GetDef(index_id);
auto index_type_inst = donor_ir_context->get_def_use_mgr()->GetDef(
index_inst->type_id());
assert(index_type_inst->opcode() == spv::Op::OpTypeInt);
opt::analysis::Integer* index_int_type =
donor_ir_context->get_type_mgr()
->GetType(index_type_inst->result_id())
->AsInteger();
if (index_inst->opcode() != spv::Op::OpConstant) {
FindOrCreateIntegerConstant({bound - 1}, 32,
index_int_type->IsSigned(), false);
}
should_be_composite_type =
TransformationAddFunction::FollowCompositeIndex(
donor_ir_context, *should_be_composite_type, index_id);
}
access_chain_clamping_info.push_back(clamping_info);
break;
}
default:
break;
}
}
}
uint32_t kill_unreachable_return_value_id = 0;
auto function_return_type_inst =
donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id());
if (function_return_type_inst->opcode() != spv::Op::OpTypeVoid &&
fuzzerutil::FunctionContainsOpKillOrUnreachable(function_to_donate)) {
kill_unreachable_return_value_id = FindOrCreateZeroConstant(
original_id_to_donated_id.at(function_return_type_inst->result_id()),
false);
}
return MaybeApplyTransformation(TransformationAddFunction(
donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,
kill_unreachable_return_value_id, access_chain_clamping_info));
}
}
}