spirv-fuzz: Add fuzzer pass to perform module donation (#3117)

This change adds a fuzzer pass that allows code from other SPIR-V
modules to be donated into the module under transformation.  It also
changes the command-line options of the tools so that, in fuzzing
mode, a file must be specified that contains the names of available
donor modules.
This commit is contained in:
Alastair Donaldson
2020-01-07 08:39:55 +00:00
committed by GitHub
parent 94c3a3ce91
commit ced7a09ab6
16 changed files with 2106 additions and 688 deletions
+2
View File
@@ -47,6 +47,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_apply_id_synonyms.h
fuzzer_pass_construct_composites.h
fuzzer_pass_copy_objects.h
fuzzer_pass_donate_modules.h
fuzzer_pass_merge_blocks.h
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_outline_functions.h
@@ -115,6 +116,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_apply_id_synonyms.cpp
fuzzer_pass_construct_composites.cpp
fuzzer_pass_copy_objects.cpp
fuzzer_pass_donate_modules.cpp
fuzzer_pass_merge_blocks.cpp
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_outline_functions.cpp
+14 -3
View File
@@ -31,6 +31,7 @@
#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
#include "source/fuzz/fuzzer_pass_construct_composites.h"
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_pass_donate_modules.h"
#include "source/fuzz/fuzzer_pass_merge_blocks.h"
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
#include "source/fuzz/fuzzer_pass_outline_functions.h"
@@ -52,15 +53,21 @@ const uint32_t kTransformationLimit = 500;
const uint32_t kChanceOfApplyingAnotherPass = 85;
template <typename T>
// A convenience method to add a fuzzer pass to |passes| with probability 0.5.
// All fuzzer passes take |ir_context|, |fact_manager|, |fuzzer_context| and
// |transformation_sequence_out| as parameters. Extra arguments can be provided
// via |extra_args|.
template <typename T, typename... Args>
void MaybeAddPass(
std::vector<std::unique_ptr<FuzzerPass>>* passes,
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformation_sequence_out) {
protobufs::TransformationSequence* transformation_sequence_out,
Args&&... extra_args) {
if (fuzzer_context->ChooseEven()) {
passes->push_back(MakeUnique<T>(ir_context, fact_manager, fuzzer_context,
transformation_sequence_out));
transformation_sequence_out,
std::forward<Args>(extra_args)...));
}
}
@@ -114,6 +121,7 @@ bool Fuzzer::Impl::ApplyPassAndCheckValidity(
Fuzzer::FuzzerResultStatus Fuzzer::Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const {
// Check compatibility between the library version being linked with and the
@@ -184,6 +192,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassCopyObjects>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassDonateModules>(
&passes, ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out, donor_suppliers);
MaybeAddPass<FuzzerPassMergeBlocks>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
+6 -2
View File
@@ -18,6 +18,7 @@
#include <memory>
#include <vector>
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "spirv-tools/libspirv.hpp"
@@ -57,11 +58,14 @@ class Fuzzer {
// Transforms |binary_in| to |binary_out| by running a number of randomized
// fuzzer passes. Initial facts about the input binary and the context in
// which it will execute are provided via |initial_facts|. The transformation
// sequence that was applied is returned via |transformation_sequence_out|.
// which it will execute are provided via |initial_facts|. A source of donor
// modules to be used by transformations is provided via |donor_suppliers|.
// The transformation sequence that was applied is returned via
// |transformation_sequence_out|.
FuzzerResultStatus Run(
const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
std::vector<uint32_t>* binary_out,
protobufs::TransformationSequence* transformation_sequence_out) const;
+3
View File
@@ -36,6 +36,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
@@ -83,6 +84,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_constructing_composite_ =
ChooseBetweenMinAndMax(kChanceOfConstructingComposite);
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
chance_of_donating_additional_module_ =
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
chance_of_moving_block_down_ =
ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
+4
View File
@@ -81,6 +81,9 @@ class FuzzerContext {
return chance_of_constructing_composite_;
}
uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
uint32_t GetChanceOfDonatingAdditionalModule() {
return chance_of_donating_additional_module_;
}
uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; }
uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
uint32_t GetChanceOfObfuscatingConstant() {
@@ -123,6 +126,7 @@ class FuzzerContext {
uint32_t chance_of_adjusting_selection_control_;
uint32_t chance_of_constructing_composite_;
uint32_t chance_of_copying_object_;
uint32_t chance_of_donating_additional_module_;
uint32_t chance_of_merging_blocks_;
uint32_t chance_of_moving_block_down_;
uint32_t chance_of_obfuscating_constant_;
+10
View File
@@ -89,6 +89,16 @@ class FuzzerPass {
const protobufs::InstructionDescriptor& instruction_descriptor)>
maybe_apply_transformation);
// A generic helper for applying a transforamtion that should be appplicable
// by construction, and adding it to the sequence of applied transformations.
template <typename TransformationType>
void ApplyTransformation(const TransformationType& transformation) {
assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
"Transformation should be applicable by construction.");
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}
private:
opt::IRContext* ir_context_;
FactManager* fact_manager_;
+597
View File
@@ -0,0 +1,597 @@
// Copyright (c) 2019 Google LLC
//
// 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_donate_modules.h"
#include <map>
#include <queue>
#include <set>
#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_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_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, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations,
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers)
: FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations),
donor_suppliers_(donor_suppliers) {}
FuzzerPassDonateModules::~FuzzerPassDonateModules() = default;
void FuzzerPassDonateModules::Apply() {
// If there are no donor suppliers, this fuzzer pass is a no-op.
if (donor_suppliers_.empty()) {
return;
}
// Donate at least one module, and probabilistically decide when to stop
// donating modules.
do {
// Choose a donor supplier at random, and get the module that it provides.
std::unique_ptr<opt::IRContext> donor_ir_context = donor_suppliers_.at(
GetFuzzerContext()->RandomIndex(donor_suppliers_))();
assert(donor_ir_context != nullptr && "Supplying of donor failed");
// Donate the supplied module.
DonateSingleModule(donor_ir_context.get());
} while (GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfDonatingAdditionalModule()));
}
void FuzzerPassDonateModules::DonateSingleModule(
opt::IRContext* donor_ir_context) {
// The ids used by the donor module may very well clash with ids defined in
// the recipient module. Furthermore, some instructions defined in the donor
// module will be equivalent to instructions defined in the recipient module,
// and it is not always legal to re-declare equivalent instructions. For
// example, OpTypeVoid cannot be declared twice.
//
// To handle this, we maintain a mapping from an id used in the donor module
// to the corresponding id that will be used by the donated code when it
// appears in the recipient module.
//
// This mapping is populated in two ways:
// (1) by mapping a donor instruction's result id to the id of some equivalent
// existing instruction in the recipient (e.g. this has to be done for
// OpTypeVoid)
// (2) by mapping a donor instruction's result id to a freshly chosen id that
// is guaranteed to be different from any id already used by the recipient
// (or from any id already chosen to handle a previous donor id)
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);
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3115) Handle some
// kinds of decoration.
}
SpvStorageClass FuzzerPassDonateModules::AdaptStorageClass(
SpvStorageClass donor_storage_class) {
switch (donor_storage_class) {
case SpvStorageClassFunction:
case SpvStorageClassPrivate:
// We leave these alone
return donor_storage_class;
case SpvStorageClassInput:
case SpvStorageClassOutput:
case SpvStorageClassUniform:
case SpvStorageClassUniformConstant:
case SpvStorageClassPushConstant:
// We change these to Private
return SpvStorageClassPrivate;
default:
// Handle other cases on demand.
assert(false && "Currently unsupported storage class.");
return SpvStorageClassMax;
}
}
void FuzzerPassDonateModules::HandleExternalInstructionImports(
opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id) {
// Consider every external instruction set import in the donor module.
for (auto& donor_import : donor_ir_context->module()->ext_inst_imports()) {
const auto& donor_import_name_words = donor_import.GetInOperand(0).words;
// Look for an identical import in the recipient module.
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) {
// A matching import has found. Map the result id for the donor import
// to the id of the existing import, so that when donor instructions
// rely on the import they will be rewritten to use the existing import.
original_id_to_donated_id->insert(
{donor_import.result_id(), existing_import.result_id()});
break;
}
}
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3116): At present
// we do not handle donation of instruction imports, i.e. we do not allow
// the donor to import instruction sets that the recipient did not already
// import. It might be a good idea to allow this, but it requires some
// thought.
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) {
// Consider every type/global/constant/undef in the module.
for (auto& type_or_value : donor_ir_context->module()->types_values()) {
// Each such instruction generates a result id, and as part of donation we
// need to associate the donor's result id with a new result id. That new
// result id will either be the id of some existing instruction, or a fresh
// id. This variable captures it.
uint32_t new_result_id;
// Decide how to handle each kind of instruction on a case-by-case basis.
//
// Because the donor module is required to be valid, when we encounter a
// type comprised of component types (e.g. an aggregate or pointer), we know
// that its component types will have been considered previously, and that
// |original_id_to_donated_id| will already contain an entry for them.
switch (type_or_value.opcode()) {
case SpvOpTypeVoid: {
// Void has to exist already in order for us to have an entry point.
// Get the existing id of void.
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 SpvOpTypeBool: {
// Bool cannot be declared multiple times, so use its existing id if
// present, or add a declaration of Bool with a fresh id if not.
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 SpvOpTypeInt: {
// Int cannot be declared multiple times with the same width and
// signedness, so check whether an existing identical Int type is
// present and use its id if so. Otherwise add a declaration of the
// Int type used by the donor, with a fresh id.
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 SpvOpTypeFloat: {
// Similar to SpvOpTypeInt.
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 SpvOpTypeVector: {
// It is not legal to have two Vector type declarations with identical
// element types and element counts, so check whether an existing
// identical Vector type is present and use its id if so. Otherwise add
// a declaration of the Vector type used by the donor, with a fresh id.
// When considering the vector's component type id, we look up the id
// use in the donor to find the id to which this has been remapped.
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 SpvOpTypeMatrix: {
// Similar to SpvOpTypeVector.
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 SpvOpTypeArray: {
// It is OK to have multiple structurally identical array types, so
// we go ahead and add a remapped version of the type declared by the
// donor.
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypeArray(
new_result_id,
original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(0)),
original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(1))));
} break;
case SpvOpTypeStruct: {
// Similar to SpvOpTypeArray.
new_result_id = GetFuzzerContext()->GetFreshId();
std::vector<uint32_t> member_type_ids;
type_or_value.ForEachInId(
[&member_type_ids,
&original_id_to_donated_id](const uint32_t* component_type_id) {
member_type_ids.push_back(
original_id_to_donated_id->at(*component_type_id));
});
ApplyTransformation(
TransformationAddTypeStruct(new_result_id, member_type_ids));
} break;
case SpvOpTypePointer: {
// Similar to SpvOpTypeArray.
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddTypePointer(
new_result_id,
AdaptStorageClass(static_cast<SpvStorageClass>(
type_or_value.GetSingleWordInOperand(0))),
original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(1))));
} break;
case SpvOpTypeFunction: {
// It is not OK to have multiple function types that use identical ids
// for their return and parameter types. We thus first look for a
// matching function type in the recipient module and use the id of this
// type if a match is found. Otherwise we add a remapped version of the
// function type.
// Build a sequence of types used as parameters for the function type.
std::vector<const opt::analysis::Type*> parameter_types;
// We start iterating at 1 because 0 is the function's return type.
for (uint32_t index = 1; index < type_or_value.NumInOperands();
index++) {
parameter_types.push_back(GetIRContext()->get_type_mgr()->GetType(
original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(index))));
}
// Make a type object corresponding to the function type.
opt::analysis::Function function_type(
GetIRContext()->get_type_mgr()->GetType(
original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(0))),
parameter_types);
// Check whether a function type corresponding to this this type object
// is already declared by the module.
auto function_type_id =
GetIRContext()->get_type_mgr()->GetId(&function_type);
if (function_type_id) {
// A suitable existing function was found - use its id.
new_result_id = function_type_id;
} else {
// No match was found, so add a remapped version of the function type
// to the module, with a fresh id.
new_result_id = GetFuzzerContext()->GetFreshId();
std::vector<uint32_t> argument_type_ids;
for (uint32_t index = 1; index < type_or_value.NumInOperands();
index++) {
argument_type_ids.push_back(original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(index)));
}
ApplyTransformation(TransformationAddTypeFunction(
new_result_id,
original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(0)),
argument_type_ids));
}
} break;
case SpvOpConstantTrue:
case SpvOpConstantFalse: {
// It is OK to have duplicate definitions of True and False, so add
// these to the module, using a remapped Bool type.
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddConstantBoolean(
new_result_id, type_or_value.opcode() == SpvOpConstantTrue));
} break;
case SpvOpConstant: {
// It is OK to have duplicate constant definitions, so add this to the
// module using a remapped result type.
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));
} break;
case SpvOpConstantComposite: {
// It is OK to have duplicate constant composite definitions, so add
// this to the module using remapped versions of all consituent ids and
// the result type.
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) {
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));
} break;
case SpvOpVariable: {
// This is a global variable that could have one of various storage
// classes. However, we change all global variable pointer storage
// classes (such as Uniform, Input and Output) to private when donating
// pointer types. Thus this variable's pointer type is guaranteed to
// have storage class private. As a result, we simply add a Private
// storage class global variable, using remapped versions of the result
// type and initializer ids for the global variable in the donor.
new_result_id = GetFuzzerContext()->GetFreshId();
ApplyTransformation(TransformationAddGlobalVariable(
new_result_id,
original_id_to_donated_id->at(type_or_value.type_id()),
type_or_value.NumInOperands() == 1
? 0
: original_id_to_donated_id->at(
type_or_value.GetSingleWordInOperand(1))));
} break;
case SpvOpUndef: {
// It is fine to have multiple Undef instructions of the same type, so
// we just add this to the recipient module.
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;
}
// Update the id mapping to associate the instruction's result id with its
// corresponding id in the recipient.
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) {
// Get the ids of functions in the donor module, topologically sorted
// according to the donor's call graph.
auto topological_order =
GetFunctionsInCallGraphTopologicalOrder(donor_ir_context);
// Donate the functions in reverse topological order. This ensures that a
// function gets donated before any function that depends on it. This allows
// donation of the functions to be separated into a number of transformations,
// each adding one function, such that every prefix of transformations leaves
// the module valid.
for (auto function_id = topological_order.rbegin();
function_id != topological_order.rend(); ++function_id) {
// Find the function to be donated.
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.");
// We will collect up protobuf messages representing the donor function's
// instructions here, and use them to create an AddFunction transformation.
std::vector<protobufs::Instruction> donated_instructions;
// Scan through the function, remapping each result id that it generates to
// a fresh id. This is necessary because functions include forward
// references, e.g. to labels.
function_to_donate->ForEachInst([this, &original_id_to_donated_id](
const opt::Instruction* instruction) {
if (instruction->result_id()) {
original_id_to_donated_id->insert(
{instruction->result_id(), GetFuzzerContext()->GetFreshId()});
}
});
// Consider every instruction of the donor function.
function_to_donate->ForEachInst(
[&donated_instructions,
&original_id_to_donated_id](const opt::Instruction* instruction) {
// Get the instruction's input operands into donation-ready form,
// remapping any id uses in the process.
opt::Instruction::OperandList input_operands;
// Consider each input operand in turn.
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);
switch (in_operand.type) {
case SPV_OPERAND_TYPE_ID:
case SPV_OPERAND_TYPE_TYPE_ID:
case SPV_OPERAND_TYPE_RESULT_ID:
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
case SPV_OPERAND_TYPE_SCOPE_ID:
// This is an id operand - it consists of a single word of data,
// which needs to be remapped so that it is replaced with the
// donated form of the id.
operand_data.push_back(
original_id_to_donated_id->at(in_operand.words[0]));
break;
default:
// For non-id operands, we just add each of the data words.
for (auto word : in_operand.words) {
operand_data.push_back(word);
}
break;
}
input_operands.push_back({in_operand.type, operand_data});
}
// Remap the result type and result id (if present) of the
// instruction, and turn it into a protobuf message.
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));
});
ApplyTransformation(TransformationAddFunction(donated_instructions));
}
}
std::vector<uint32_t>
FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder(
opt::IRContext* context) {
// This is an implementation of Kahns algorithm for topological sorting.
// For each function id, stores the number of distinct functions that call
// the function.
std::map<uint32_t, uint32_t> function_in_degree;
// We first build a call graph for the module, and compute the in-degree for
// each function in the process.
// TODO(afd): If there is functionality elsewhere in the SPIR-V tools
// framework to construct call graphs it could be nice to re-use it here.
std::map<uint32_t, std::set<uint32_t>> call_graph_edges;
// Initialize function in-degree and call graph edges to 0 and empty.
for (auto& function : *context->module()) {
function_in_degree[function.result_id()] = 0;
call_graph_edges[function.result_id()] = std::set<uint32_t>();
}
// Consider every function.
for (auto& function : *context->module()) {
// Avoid considering the same callee of this function multiple times by
// recording known callees.
std::set<uint32_t> known_callees;
// Consider every function call instruction in every block.
for (auto& block : function) {
for (auto& instruction : block) {
if (instruction.opcode() != SpvOpFunctionCall) {
continue;
}
// Get the id of the function being called.
uint32_t callee = instruction.GetSingleWordInOperand(0);
if (known_callees.count(callee)) {
// We have already considered a call to this function - ignore it.
continue;
}
// Increase the callee's in-degree and add an edge to the call graph.
function_in_degree[callee]++;
call_graph_edges[function.result_id()].insert(callee);
// Mark the callee as 'known'.
known_callees.insert(callee);
}
}
}
// This is the sorted order of function ids that we will eventually return.
std::vector<uint32_t> result;
// Populate a queue with all those function ids with in-degree zero.
std::queue<uint32_t> queue;
for (auto& entry : function_in_degree) {
if (entry.second == 0) {
queue.push(entry.first);
}
}
// Pop ids from the queue, adding them to the sorted order and decreasing the
// in-degrees of their successors. A successor who's in-degree becomes zero
// gets added to the queue.
while (!queue.empty()) {
auto next = queue.front();
queue.pop();
result.push_back(next);
for (auto successor : call_graph_edges.at(next)) {
assert(function_in_degree.at(successor) > 0 &&
"The in-degree cannot be zero if the function is a successor.");
function_in_degree[successor] = function_in_degree.at(successor) - 1;
if (function_in_degree.at(successor) == 0) {
queue.push(successor);
}
}
}
assert(result.size() == function_in_degree.size() &&
"Every function should appear in the sort.");
return result;
}
} // namespace fuzz
} // namespace spvtools
+88
View File
@@ -0,0 +1,88 @@
// Copyright (c) 2019 Google LLC
//
// 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_DONATE_MODULES_H_
#define SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_
#include <vector>
#include "source/fuzz/fuzzer_pass.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that randomly adds code from other SPIR-V modules to the module
// being transformed.
class FuzzerPassDonateModules : public FuzzerPass {
public:
FuzzerPassDonateModules(
opt::IRContext* ir_context, FactManager* fact_manager,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations,
const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers);
~FuzzerPassDonateModules();
void Apply() override;
// Donates the global declarations and functions of |donor_ir_context| into
// the fuzzer pass's IR context.
void DonateSingleModule(opt::IRContext* donor_ir_context);
private:
// Adapts a storage class coming from a donor module so that it will work
// in a recipient module, e.g. by changing Uniform to Private.
static SpvStorageClass AdaptStorageClass(SpvStorageClass donor_storage_class);
// Identifies all external instruction set imports in |donor_ir_context| and
// populates |original_id_to_donated_id| with a mapping from the donor's id
// for such an import to a corresponding import in the recipient. Aborts if
// no such corresponding import is available.
void HandleExternalInstructionImports(
opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
// Considers all types, globals, constants and undefs in |donor_ir_context|.
// For each instruction, uses |original_to_donated_id| to map its result id to
// either (1) the id of an existing identical instruction in the recipient, or
// (2) to a fresh id, in which case the instruction is also added to the
// recipient (with any operand ids that it uses being remapped via
// |original_id_to_donated_id|).
void HandleTypesAndValues(
opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
// Assumes that |donor_ir_context| does not exhibit recursion. Considers the
// functions in |donor_ir_context|'s call graph in a reverse-topologically-
// sorted order (leaves-to-root), adding each function to the recipient
// module, rewritten to use fresh ids and using |original_id_to_donated_id| to
// remap ids.
void HandleFunctions(opt::IRContext* donor_ir_context,
std::map<uint32_t, uint32_t>* original_id_to_donated_id);
// Returns the ids of all functions in |context| in a topological order in
// relation to the call graph of |context|, which is assumed to be recursion-
// free.
static std::vector<uint32_t> GetFunctionsInCallGraphTopologicalOrder(
opt::IRContext* context);
// Functions that supply SPIR-V modules
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_
+4 -1
View File
@@ -25,9 +25,12 @@
namespace spvtools {
namespace fuzz {
// Provides global utility methods for use by the fuzzer
// Provides types and global utility methods for use by the fuzzer
namespace fuzzerutil {
// Function type that produces a SPIR-V module.
using ModuleSupplier = std::function<std::unique_ptr<opt::IRContext>()>;
// Returns true if and only if the module does not define the given id.
bool IsFreshId(opt::IRContext* context, uint32_t id);
+3 -4
View File
@@ -21,16 +21,15 @@ namespace fuzz {
protobufs::Instruction MakeInstructionMessage(
SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
const std::vector<std::pair<uint32_t, std::vector<uint32_t>>>&
input_operands) {
const opt::Instruction::OperandList& input_operands) {
protobufs::Instruction result;
result.set_opcode(opcode);
result.set_result_type_id(result_type_id);
result.set_result_id(result_id);
for (auto& operand : input_operands) {
auto operand_message = result.add_input_operand();
operand_message->set_operand_type(operand.first);
for (auto operand_word : operand.second) {
operand_message->set_operand_type(static_cast<uint32_t>(operand.type));
for (auto operand_word : operand.words) {
operand_message->add_operand_data(operand_word);
}
}
+1 -2
View File
@@ -27,8 +27,7 @@ namespace fuzz {
// Creates an Instruction protobuf message from its component parts.
protobufs::Instruction MakeInstructionMessage(
SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
const std::vector<std::pair<uint32_t, std::vector<uint32_t>>>&
input_operands);
const opt::Instruction::OperandList& input_operands);
// Creates and returns an opt::Instruction from protobuf message
// |instruction_message|, relative to |ir_context|. In the process, the module
+2
View File
@@ -22,12 +22,14 @@ if (${SPIRV_BUILD_FUZZER})
fact_manager_test.cpp
fuzz_test_util.cpp
fuzzer_pass_add_useful_constructs_test.cpp
fuzzer_pass_donate_modules_test.cpp
instruction_descriptor_test.cpp
transformation_add_constant_boolean_test.cpp
transformation_add_constant_composite_test.cpp
transformation_add_constant_scalar_test.cpp
transformation_add_dead_break_test.cpp
transformation_add_dead_continue_test.cpp
transformation_add_function_test.cpp
transformation_add_global_undef_test.cpp
transformation_add_global_variable_test.cpp
transformation_add_no_contraction_decoration_test.cpp
@@ -0,0 +1,620 @@
// Copyright (c) 2019 Google LLC
//
// 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_donate_modules.h"
#include "source/fuzz/pseudo_random_generator.h"
#include "test/fuzz/fuzz_test_util.h"
namespace spvtools {
namespace fuzz {
namespace {
TEST(FuzzerPassDonateModulesTest, BasicDonation) {
std::string recipient_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"
OpName %10 "m"
OpName %16 "v"
OpDecorate %16 RelaxedPrecision
OpDecorate %20 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 3
%8 = OpTypeMatrix %7 2
%9 = OpTypePointer Private %8
%10 = OpVariable %9 Private
%11 = OpTypeInt 32 1
%12 = OpConstant %11 0
%13 = OpTypeInt 32 0
%14 = OpTypeVector %13 4
%15 = OpTypePointer Private %14
%16 = OpVariable %15 Private
%17 = OpConstant %13 2
%18 = OpTypePointer Private %13
%22 = OpConstant %13 0
%23 = OpTypePointer Private %6
%4 = OpFunction %2 None %3
%5 = OpLabel
%19 = OpAccessChain %18 %16 %17
%20 = OpLoad %13 %19
%21 = OpConvertUToF %6 %20
%24 = OpAccessChain %23 %10 %12 %22
OpStore %24 %21
OpReturn
OpFunctionEnd
)";
std::string donor_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"
OpName %12 "bar(mf24;"
OpName %11 "m"
OpName %20 "foo(vu4;"
OpName %19 "v"
OpName %23 "x"
OpName %26 "param"
OpName %29 "result"
OpName %31 "i"
OpName %81 "param"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 4
%8 = OpTypeMatrix %7 2
%9 = OpTypePointer Function %8
%10 = OpTypeFunction %6 %9
%14 = OpTypeInt 32 0
%15 = OpTypeVector %14 4
%16 = OpTypePointer Function %15
%17 = OpTypeInt 32 1
%18 = OpTypeFunction %17 %16
%22 = OpTypePointer Function %17
%24 = OpConstant %14 2
%25 = OpConstantComposite %15 %24 %24 %24 %24
%28 = OpTypePointer Function %6
%30 = OpConstant %6 0
%32 = OpConstant %17 0
%39 = OpConstant %17 10
%40 = OpTypeBool
%43 = OpConstant %17 3
%50 = OpConstant %17 1
%55 = OpConstant %14 0
%56 = OpTypePointer Function %14
%59 = OpConstant %14 1
%65 = OpConstant %17 2
%68 = OpConstant %6 1
%69 = OpConstant %6 2
%70 = OpConstant %6 3
%71 = OpConstant %6 4
%72 = OpConstant %14 3
%76 = OpConstant %6 6
%77 = OpConstant %6 7
%4 = OpFunction %2 None %3
%5 = OpLabel
%23 = OpVariable %22 Function
%26 = OpVariable %16 Function
OpStore %26 %25
%27 = OpFunctionCall %17 %20 %26
OpStore %23 %27
OpReturn
OpFunctionEnd
%12 = OpFunction %6 None %10
%11 = OpFunctionParameter %9
%13 = OpLabel
%29 = OpVariable %28 Function
%31 = OpVariable %22 Function
OpStore %29 %30
OpStore %31 %32
OpBranch %33
%33 = OpLabel
OpLoopMerge %35 %36 None
OpBranch %37
%37 = OpLabel
%38 = OpLoad %17 %31
%41 = OpSLessThan %40 %38 %39
OpBranchConditional %41 %34 %35
%34 = OpLabel
%42 = OpLoad %17 %31
%44 = OpExtInst %17 %1 SClamp %42 %32 %43
%45 = OpAccessChain %28 %11 %32 %44
%46 = OpLoad %6 %45
%47 = OpLoad %6 %29
%48 = OpFAdd %6 %47 %46
OpStore %29 %48
OpBranch %36
%36 = OpLabel
%49 = OpLoad %17 %31
%51 = OpIAdd %17 %49 %50
OpStore %31 %51
OpBranch %33
%35 = OpLabel
%52 = OpLoad %6 %29
OpReturnValue %52
OpFunctionEnd
%20 = OpFunction %17 None %18
%19 = OpFunctionParameter %16
%21 = OpLabel
%81 = OpVariable %9 Function
%57 = OpAccessChain %56 %19 %55
%58 = OpLoad %14 %57
%60 = OpAccessChain %56 %19 %59
%61 = OpLoad %14 %60
%62 = OpUGreaterThan %40 %58 %61
OpSelectionMerge %64 None
OpBranchConditional %62 %63 %67
%63 = OpLabel
OpReturnValue %65
%67 = OpLabel
%73 = OpAccessChain %56 %19 %72
%74 = OpLoad %14 %73
%75 = OpConvertUToF %6 %74
%78 = OpCompositeConstruct %7 %30 %68 %69 %70
%79 = OpCompositeConstruct %7 %71 %75 %76 %77
%80 = OpCompositeConstruct %8 %78 %79
OpStore %81 %80
%82 = OpFunctionCall %6 %12 %81
%83 = OpConvertFToS %17 %82
OpReturnValue %83
%64 = OpLabel
%85 = OpUndef %17
OpReturnValue %85
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto recipient_context =
BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, recipient_context.get()));
const auto donor_context =
BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, donor_context.get()));
FactManager fact_manager;
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
protobufs::TransformationSequence transformation_sequence;
FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
&fuzzer_context, &transformation_sequence,
{});
fuzzer_pass.DonateSingleModule(donor_context.get());
// We just check that the result is valid. Checking to what it should be
// exactly equal to would be very fragile.
ASSERT_TRUE(IsValid(env, recipient_context.get()));
}
TEST(FuzzerPassDonateModulesTest, DonationWithUniforms) {
// This test checks that when donating a shader that contains uniforms,
// uniform variables and associated pointer types are demoted from having
// Uniform storage class to Private storage class.
std::string recipient_and_donor_shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpMemberDecorate %9 0 Offset 0
OpDecorate %9 Block
OpDecorate %11 DescriptorSet 0
OpDecorate %11 Binding 0
OpMemberDecorate %19 0 Offset 0
OpDecorate %19 Block
OpDecorate %21 DescriptorSet 0
OpDecorate %21 Binding 1
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypePointer Function %6
%9 = OpTypeStruct %6
%10 = OpTypePointer Uniform %9
%11 = OpVariable %10 Uniform
%12 = OpTypeInt 32 1
%13 = OpConstant %12 0
%14 = OpTypePointer Uniform %6
%17 = OpTypePointer Function %12
%19 = OpTypeStruct %12
%20 = OpTypePointer Uniform %19
%21 = OpVariable %20 Uniform
%22 = OpTypePointer Uniform %12
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%18 = OpVariable %17 Function
%15 = OpAccessChain %14 %11 %13
%16 = OpLoad %6 %15
OpStore %8 %16
%23 = OpAccessChain %22 %21 %13
%24 = OpLoad %12 %23
OpStore %18 %24
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto recipient_context = BuildModule(
env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, recipient_context.get()));
const auto donor_context = BuildModule(
env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, donor_context.get()));
FactManager fact_manager;
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
protobufs::TransformationSequence transformation_sequence;
FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
&fuzzer_context, &transformation_sequence,
{});
fuzzer_pass.DonateSingleModule(donor_context.get());
ASSERT_TRUE(IsValid(env, recipient_context.get()));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpMemberDecorate %9 0 Offset 0
OpDecorate %9 Block
OpDecorate %11 DescriptorSet 0
OpDecorate %11 Binding 0
OpMemberDecorate %19 0 Offset 0
OpDecorate %19 Block
OpDecorate %21 DescriptorSet 0
OpDecorate %21 Binding 1
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypePointer Function %6
%9 = OpTypeStruct %6
%10 = OpTypePointer Uniform %9
%11 = OpVariable %10 Uniform
%12 = OpTypeInt 32 1
%13 = OpConstant %12 0
%14 = OpTypePointer Uniform %6
%17 = OpTypePointer Function %12
%19 = OpTypeStruct %12
%20 = OpTypePointer Uniform %19
%21 = OpVariable %20 Uniform
%22 = OpTypePointer Uniform %12
%100 = OpTypePointer Function %6
%101 = OpTypeStruct %6
%102 = OpTypePointer Private %101
%103 = OpVariable %102 Private
%104 = OpConstant %12 0
%105 = OpTypePointer Private %6
%106 = OpTypePointer Function %12
%107 = OpTypeStruct %12
%108 = OpTypePointer Private %107
%109 = OpVariable %108 Private
%110 = OpTypePointer Private %12
%4 = OpFunction %2 None %3
%5 = OpLabel
%8 = OpVariable %7 Function
%18 = OpVariable %17 Function
%15 = OpAccessChain %14 %11 %13
%16 = OpLoad %6 %15
OpStore %8 %16
%23 = OpAccessChain %22 %21 %13
%24 = OpLoad %12 %23
OpStore %18 %24
OpReturn
OpFunctionEnd
%111 = OpFunction %2 None %3
%112 = OpLabel
%113 = OpVariable %100 Function
%114 = OpVariable %106 Function
%115 = OpAccessChain %105 %103 %104
%116 = OpLoad %6 %115
OpStore %113 %116
%117 = OpAccessChain %110 %109 %104
%118 = OpLoad %12 %117
OpStore %114 %118
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, recipient_context.get()));
}
TEST(FuzzerPassDonateModulesTest, DonationWithInputAndOutputVariables) {
// This test checks that when donating a shader that contains input and output
// variables, such variables and associated pointer types are demoted to have
// the Private storage class.
std::string recipient_and_donor_shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %9 %11
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpDecorate %9 Location 0
OpDecorate %11 Location 1
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 4
%8 = OpTypePointer Output %7
%9 = OpVariable %8 Output
%10 = OpTypePointer Input %7
%11 = OpVariable %10 Input
%4 = OpFunction %2 None %3
%5 = OpLabel
%12 = OpLoad %7 %11
OpStore %9 %12
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto recipient_context = BuildModule(
env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, recipient_context.get()));
const auto donor_context = BuildModule(
env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, donor_context.get()));
FactManager fact_manager;
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
protobufs::TransformationSequence transformation_sequence;
FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
&fuzzer_context, &transformation_sequence,
{});
fuzzer_pass.DonateSingleModule(donor_context.get());
ASSERT_TRUE(IsValid(env, recipient_context.get()));
std::string after_transformation = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main" %9 %11
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpDecorate %9 Location 0
OpDecorate %11 Location 1
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeVector %6 4
%8 = OpTypePointer Output %7
%9 = OpVariable %8 Output
%10 = OpTypePointer Input %7
%11 = OpVariable %10 Input
%100 = OpTypePointer Private %7
%101 = OpVariable %100 Private
%102 = OpTypePointer Private %7
%103 = OpVariable %102 Private
%4 = OpFunction %2 None %3
%5 = OpLabel
%12 = OpLoad %7 %11
OpStore %9 %12
OpReturn
OpFunctionEnd
%104 = OpFunction %2 None %3
%105 = OpLabel
%106 = OpLoad %7 %103
OpStore %101 %106
OpReturn
OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, recipient_context.get()));
}
TEST(FuzzerPassDonateModulesTest, Miscellaneous1) {
std::string recipient_shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
std::string donor_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"
OpName %6 "foo("
OpName %10 "x"
OpName %12 "i"
OpName %33 "i"
OpName %42 "j"
OpDecorate %10 RelaxedPrecision
OpDecorate %12 RelaxedPrecision
OpDecorate %19 RelaxedPrecision
OpDecorate %23 RelaxedPrecision
OpDecorate %24 RelaxedPrecision
OpDecorate %25 RelaxedPrecision
OpDecorate %26 RelaxedPrecision
OpDecorate %27 RelaxedPrecision
OpDecorate %28 RelaxedPrecision
OpDecorate %30 RelaxedPrecision
OpDecorate %33 RelaxedPrecision
OpDecorate %39 RelaxedPrecision
OpDecorate %42 RelaxedPrecision
OpDecorate %49 RelaxedPrecision
OpDecorate %52 RelaxedPrecision
OpDecorate %53 RelaxedPrecision
OpDecorate %58 RelaxedPrecision
OpDecorate %59 RelaxedPrecision
OpDecorate %60 RelaxedPrecision
OpDecorate %63 RelaxedPrecision
OpDecorate %64 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%8 = OpTypeInt 32 1
%9 = OpTypePointer Function %8
%11 = OpConstant %8 2
%13 = OpConstant %8 0
%20 = OpConstant %8 100
%21 = OpTypeBool
%29 = OpConstant %8 1
%40 = OpConstant %8 10
%43 = OpConstant %8 20
%61 = OpConstant %8 4
%4 = OpFunction %2 None %3
%5 = OpLabel
%33 = OpVariable %9 Function
%42 = OpVariable %9 Function
%32 = OpFunctionCall %2 %6
OpStore %33 %13
OpBranch %34
%34 = OpLabel
OpLoopMerge %36 %37 None
OpBranch %38
%38 = OpLabel
%39 = OpLoad %8 %33
%41 = OpSLessThan %21 %39 %40
OpBranchConditional %41 %35 %36
%35 = OpLabel
OpStore %42 %43
OpBranch %44
%44 = OpLabel
OpLoopMerge %46 %47 None
OpBranch %48
%48 = OpLabel
%49 = OpLoad %8 %42
%50 = OpSGreaterThan %21 %49 %13
OpBranchConditional %50 %45 %46
%45 = OpLabel
%51 = OpFunctionCall %2 %6
%52 = OpLoad %8 %42
%53 = OpISub %8 %52 %29
OpStore %42 %53
OpBranch %47
%47 = OpLabel
OpBranch %44
%46 = OpLabel
OpBranch %54
%54 = OpLabel
OpLoopMerge %56 %57 None
OpBranch %55
%55 = OpLabel
%58 = OpLoad %8 %33
%59 = OpIAdd %8 %58 %29
OpStore %33 %59
OpBranch %57
%57 = OpLabel
%60 = OpLoad %8 %33
%62 = OpSLessThan %21 %60 %61
OpBranchConditional %62 %54 %56
%56 = OpLabel
OpBranch %37
%37 = OpLabel
%63 = OpLoad %8 %33
%64 = OpIAdd %8 %63 %29
OpStore %33 %64
OpBranch %34
%36 = OpLabel
OpReturn
OpFunctionEnd
%6 = OpFunction %2 None %3
%7 = OpLabel
%10 = OpVariable %9 Function
%12 = OpVariable %9 Function
OpStore %10 %11
OpStore %12 %13
OpBranch %14
%14 = OpLabel
OpLoopMerge %16 %17 None
OpBranch %18
%18 = OpLabel
%19 = OpLoad %8 %12
%22 = OpSLessThan %21 %19 %20
OpBranchConditional %22 %15 %16
%15 = OpLabel
%23 = OpLoad %8 %12
%24 = OpLoad %8 %10
%25 = OpIAdd %8 %24 %23
OpStore %10 %25
%26 = OpLoad %8 %10
%27 = OpIMul %8 %26 %11
OpStore %10 %27
OpBranch %17
%17 = OpLabel
%28 = OpLoad %8 %12
%30 = OpIAdd %8 %28 %29
OpStore %12 %30
OpBranch %14
%16 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_5;
const auto consumer = nullptr;
const auto recipient_context =
BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, recipient_context.get()));
const auto donor_context =
BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
ASSERT_TRUE(IsValid(env, donor_context.get()));
FactManager fact_manager;
FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
protobufs::TransformationSequence transformation_sequence;
FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), &fact_manager,
&fuzzer_context, &transformation_sequence,
{});
fuzzer_pass.DonateSingleModule(donor_context.get());
// We just check that the result is valid. Checking to what it should be
// exactly equal to would be very fragile.
ASSERT_TRUE(IsValid(env, recipient_context.get()));
}
} // namespace
} // namespace fuzz
} // namespace spvtools
+302 -290
View File
@@ -13,6 +13,7 @@
// limitations under the License.
#include "source/fuzz/fuzzer.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/replayer.h"
#include "source/fuzz/uniform_buffer_element_descriptor.h"
#include "test/fuzz/fuzz_test_util.h"
@@ -23,109 +24,35 @@ namespace {
const uint32_t kNumFuzzerRuns = 20;
void AddConstantUniformFact(protobufs::FactSequence* facts,
uint32_t descriptor_set, uint32_t binding,
std::vector<uint32_t>&& indices, uint32_t value) {
protobufs::FactConstantUniform fact;
*fact.mutable_uniform_buffer_element_descriptor() =
MakeUniformBufferElementDescriptor(descriptor_set, binding,
std::move(indices));
*fact.mutable_constant_word()->Add() = value;
protobufs::Fact temp;
*temp.mutable_constant_uniform_fact() = fact;
*facts->mutable_fact()->Add() = temp;
}
// The SPIR-V came from this GLSL:
//
// #version 310 es
//
// void foo() {
// int x;
// x = 2;
// for (int i = 0; i < 100; i++) {
// x += i;
// x = x * 2;
// }
// return;
// }
//
// void main() {
// foo();
// for (int i = 0; i < 10; i++) {
// int j = 20;
// while(j > 0) {
// foo();
// j--;
// }
// do {
// i++;
// } while(i < 4);
// }
// }
// Reinterpret the bits of |value| as a 32-bit unsigned int
uint32_t FloatBitsAsUint(float value) {
uint32_t result;
memcpy(&result, &value, sizeof(float));
return result;
}
// Assembles the given |shader| text, and then runs the fuzzer |num_runs|
// times, using successive seeds starting from |initial_seed|. Checks that
// the binary produced after each fuzzer run is valid, and that replaying
// the transformations that were applied during fuzzing leads to an
// identical binary.
void RunFuzzerAndReplayer(const std::string& shader,
const protobufs::FactSequence& initial_facts,
uint32_t initial_seed, uint32_t num_runs) {
const auto env = SPV_ENV_UNIVERSAL_1_5;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
t.SetMessageConsumer(kConsoleMessageConsumer);
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
ASSERT_TRUE(t.Validate(binary_in));
for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
std::vector<uint32_t> fuzzer_binary_out;
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
Fuzzer fuzzer(env, seed, true);
fuzzer.SetMessageConsumer(kSilentConsumer);
auto fuzzer_result_status =
fuzzer.Run(binary_in, initial_facts, &fuzzer_binary_out,
&fuzzer_transformation_sequence_out);
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
std::vector<uint32_t> replayer_binary_out;
protobufs::TransformationSequence replayer_transformation_sequence_out;
Replayer replayer(env, false);
replayer.SetMessageConsumer(kSilentConsumer);
auto replayer_result_status = replayer.Run(
binary_in, initial_facts, fuzzer_transformation_sequence_out,
&replayer_binary_out, &replayer_transformation_sequence_out);
ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
replayer_result_status);
// After replaying the transformations applied by the fuzzer, exactly those
// transformations should have been applied, and the binary resulting from
// replay should be identical to that which resulted from fuzzing.
std::string fuzzer_transformations_string;
std::string replayer_transformations_string;
fuzzer_transformation_sequence_out.SerializeToString(
&fuzzer_transformations_string);
replayer_transformation_sequence_out.SerializeToString(
&replayer_transformations_string);
ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string);
ASSERT_EQ(fuzzer_binary_out, replayer_binary_out);
}
}
TEST(FuzzerReplayerTest, Miscellaneous1) {
// The SPIR-V came from this GLSL:
//
// #version 310 es
//
// void foo() {
// int x;
// x = 2;
// for (int i = 0; i < 100; i++) {
// x += i;
// x = x * 2;
// }
// return;
// }
//
// void main() {
// foo();
// for (int i = 0; i < 10; i++) {
// int j = 20;
// while(j > 0) {
// foo();
// j--;
// }
// do {
// i++;
// } while(i < 4);
// }
// }
std::string shader = R"(
const std::string kTestShader1 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
@@ -260,66 +187,60 @@ TEST(FuzzerReplayerTest, Miscellaneous1) {
OpFunctionEnd
)";
// Do some fuzzer runs, starting from an initial seed of 0 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, kNumFuzzerRuns);
}
// The SPIR-V came from this GLSL, which was then optimized using spirv-opt
// with the -O argument:
//
// #version 310 es
//
// precision highp float;
//
// layout(location = 0) out vec4 _GLF_color;
//
// layout(set = 0, binding = 0) uniform buf0 {
// vec2 injectionSwitch;
// };
// layout(set = 0, binding = 1) uniform buf1 {
// vec2 resolution;
// };
// bool checkSwap(float a, float b)
// {
// return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
// }
// void main()
// {
// float data[10];
// for(int i = 0; i < 10; i++)
// {
// data[i] = float(10 - i) * injectionSwitch.y;
// }
// for(int i = 0; i < 9; i++)
// {
// for(int j = 0; j < 10; j++)
// {
// if(j < i + 1)
// {
// continue;
// }
// bool doSwap = checkSwap(data[i], data[j]);
// if(doSwap)
// {
// float temp = data[i];
// data[i] = data[j];
// data[j] = temp;
// }
// }
// }
// if(gl_FragCoord.x < resolution.x / 2.0)
// {
// _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
// }
// else
// {
// _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
// }
// }
TEST(FuzzerReplayerTest, Miscellaneous2) {
// The SPIR-V came from this GLSL, which was then optimized using spirv-opt
// with the -O argument:
//
// #version 310 es
//
// precision highp float;
//
// layout(location = 0) out vec4 _GLF_color;
//
// layout(set = 0, binding = 0) uniform buf0 {
// vec2 injectionSwitch;
// };
// layout(set = 0, binding = 1) uniform buf1 {
// vec2 resolution;
// };
// bool checkSwap(float a, float b)
// {
// return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
// }
// void main()
// {
// float data[10];
// for(int i = 0; i < 10; i++)
// {
// data[i] = float(10 - i) * injectionSwitch.y;
// }
// for(int i = 0; i < 9; i++)
// {
// for(int j = 0; j < 10; j++)
// {
// if(j < i + 1)
// {
// continue;
// }
// bool doSwap = checkSwap(data[i], data[j]);
// if(doSwap)
// {
// float temp = data[i];
// data[i] = data[j];
// data[j] = temp;
// }
// }
// }
// if(gl_FragCoord.x < resolution.x / 2.0)
// {
// _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
// }
// else
// {
// _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
// }
// }
std::string shader = R"(
const std::string kTestShader2 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
@@ -507,121 +428,115 @@ TEST(FuzzerReplayerTest, Miscellaneous2) {
OpFunctionEnd
)";
// Do some fuzzer runs, starting from an initial seed of 10 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, kNumFuzzerRuns);
}
// The SPIR-V came from this GLSL, which was then optimized using spirv-opt
// with the -O argument:
//
// #version 310 es
//
// precision highp float;
//
// layout(location = 0) out vec4 _GLF_color;
//
// layout(set = 0, binding = 0) uniform buf0 {
// vec2 resolution;
// };
// void main(void)
// {
// float A[50];
// for(
// int i = 0;
// i < 200;
// i ++
// )
// {
// if(i >= int(resolution.x))
// {
// break;
// }
// if((4 * (i / 4)) == i)
// {
// A[i / 4] = float(i);
// }
// }
// for(
// int i = 0;
// i < 50;
// i ++
// )
// {
// if(i < int(gl_FragCoord.x))
// {
// break;
// }
// if(i > 0)
// {
// A[i] += A[i - 1];
// }
// }
// if(int(gl_FragCoord.x) < 20)
// {
// _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 40)
// {
// _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 60)
// {
// _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 80)
// {
// _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 100)
// {
// _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 120)
// {
// _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 140)
// {
// _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 160)
// {
// _GLF_color = vec4(A[35] / resolution.x, A[39] /
// resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 180)
// {
// _GLF_color = vec4(A[40] / resolution.x, A[44] /
// resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 180)
// {
// _GLF_color = vec4(A[45] / resolution.x, A[49] /
// resolution.y, 1.0, 1.0);
// }
// else
// {
// discard;
// }
// }
TEST(FuzzerReplayerTest, Miscellaneous3) {
// The SPIR-V came from this GLSL, which was then optimized using spirv-opt
// with the -O argument:
//
// #version 310 es
//
// precision highp float;
//
// layout(location = 0) out vec4 _GLF_color;
//
// layout(set = 0, binding = 0) uniform buf0 {
// vec2 resolution;
// };
// void main(void)
// {
// float A[50];
// for(
// int i = 0;
// i < 200;
// i ++
// )
// {
// if(i >= int(resolution.x))
// {
// break;
// }
// if((4 * (i / 4)) == i)
// {
// A[i / 4] = float(i);
// }
// }
// for(
// int i = 0;
// i < 50;
// i ++
// )
// {
// if(i < int(gl_FragCoord.x))
// {
// break;
// }
// if(i > 0)
// {
// A[i] += A[i - 1];
// }
// }
// if(int(gl_FragCoord.x) < 20)
// {
// _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 40)
// {
// _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 60)
// {
// _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 80)
// {
// _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 100)
// {
// _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 120)
// {
// _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 140)
// {
// _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 160)
// {
// _GLF_color = vec4(A[35] / resolution.x, A[39] /
// resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 180)
// {
// _GLF_color = vec4(A[40] / resolution.x, A[44] /
// resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 180)
// {
// _GLF_color = vec4(A[45] / resolution.x, A[49] /
// resolution.y, 1.0, 1.0);
// }
// else
// {
// discard;
// }
// }
std::string shader = R"(
const std::string kTestShader3 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
@@ -969,21 +884,10 @@ TEST(FuzzerReplayerTest, Miscellaneous3) {
OpFunctionEnd
)";
// Add the facts "resolution.x == 250" and "resolution.y == 100".
protobufs::FactSequence facts;
AddConstantUniformFact(&facts, 0, 0, {0, 0}, 250);
AddConstantUniformFact(&facts, 0, 0, {0, 1}, 100);
// The SPIR-V comes from the 'matrices_smart_loops' GLSL shader that ships
// with GraphicsFuzz.
// Do some fuzzer runs, starting from an initial seed of 94 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(shader, facts, 94, kNumFuzzerRuns);
}
TEST(FuzzerReplayerTest, Miscellaneous4) {
// The SPIR-V comes from the 'matrices_smart_loops' GLSL shader that ships
// with GraphicsFuzz.
std::string shader = R"(
const std::string kTestShader4 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
@@ -1611,6 +1515,114 @@ TEST(FuzzerReplayerTest, Miscellaneous4) {
OpFunctionEnd
)";
void AddConstantUniformFact(protobufs::FactSequence* facts,
uint32_t descriptor_set, uint32_t binding,
std::vector<uint32_t>&& indices, uint32_t value) {
protobufs::FactConstantUniform fact;
*fact.mutable_uniform_buffer_element_descriptor() =
MakeUniformBufferElementDescriptor(descriptor_set, binding,
std::move(indices));
*fact.mutable_constant_word()->Add() = value;
protobufs::Fact temp;
*temp.mutable_constant_uniform_fact() = fact;
*facts->mutable_fact()->Add() = temp;
}
// Reinterpret the bits of |value| as a 32-bit unsigned int
uint32_t FloatBitsAsUint(float value) {
uint32_t result;
memcpy(&result, &value, sizeof(float));
return result;
}
// Assembles the given |shader| text, and then runs the fuzzer |num_runs|
// times, using successive seeds starting from |initial_seed|. Checks that
// the binary produced after each fuzzer run is valid, and that replaying
// the transformations that were applied during fuzzing leads to an
// identical binary.
void RunFuzzerAndReplayer(const std::string& shader,
const protobufs::FactSequence& initial_facts,
uint32_t initial_seed, uint32_t num_runs) {
const auto env = SPV_ENV_UNIVERSAL_1_5;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
t.SetMessageConsumer(kConsoleMessageConsumer);
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
ASSERT_TRUE(t.Validate(binary_in));
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers;
for (auto donor :
{&kTestShader1, &kTestShader2, &kTestShader3, &kTestShader4}) {
donor_suppliers.emplace_back([donor]() {
return BuildModule(env, kConsoleMessageConsumer, *donor,
kFuzzAssembleOption);
});
}
for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
std::vector<uint32_t> fuzzer_binary_out;
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
Fuzzer fuzzer(env, seed, true);
fuzzer.SetMessageConsumer(kSilentConsumer);
auto fuzzer_result_status =
fuzzer.Run(binary_in, initial_facts, donor_suppliers,
&fuzzer_binary_out, &fuzzer_transformation_sequence_out);
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
std::vector<uint32_t> replayer_binary_out;
protobufs::TransformationSequence replayer_transformation_sequence_out;
Replayer replayer(env, false);
replayer.SetMessageConsumer(kSilentConsumer);
auto replayer_result_status = replayer.Run(
binary_in, initial_facts, fuzzer_transformation_sequence_out,
&replayer_binary_out, &replayer_transformation_sequence_out);
ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
replayer_result_status);
// After replaying the transformations applied by the fuzzer, exactly those
// transformations should have been applied, and the binary resulting from
// replay should be identical to that which resulted from fuzzing.
std::string fuzzer_transformations_string;
std::string replayer_transformations_string;
fuzzer_transformation_sequence_out.SerializeToString(
&fuzzer_transformations_string);
replayer_transformation_sequence_out.SerializeToString(
&replayer_transformations_string);
ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string);
ASSERT_EQ(fuzzer_binary_out, replayer_binary_out);
}
}
TEST(FuzzerReplayerTest, Miscellaneous1) {
// Do some fuzzer runs, starting from an initial seed of 0 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(kTestShader1, protobufs::FactSequence(), 0,
kNumFuzzerRuns);
}
TEST(FuzzerReplayerTest, Miscellaneous2) {
// Do some fuzzer runs, starting from an initial seed of 10 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(kTestShader2, protobufs::FactSequence(), 10,
kNumFuzzerRuns);
}
TEST(FuzzerReplayerTest, Miscellaneous3) {
// Add the facts "resolution.x == 250" and "resolution.y == 100".
protobufs::FactSequence facts;
AddConstantUniformFact(&facts, 0, 0, {0, 0}, 250);
AddConstantUniformFact(&facts, 0, 0, {0, 1}, 100);
// Do some fuzzer runs, starting from an initial seed of 94 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(kTestShader3, facts, 94, kNumFuzzerRuns);
}
TEST(FuzzerReplayerTest, Miscellaneous4) {
// Add the facts:
// - "one == 1.0"
// - "resolution.y == 256.0",
@@ -1621,7 +1633,7 @@ TEST(FuzzerReplayerTest, Miscellaneous4) {
// Do some fuzzer runs, starting from an initial seed of 14 (seed value chosen
// arbitrarily).
RunFuzzerAndReplayer(shader, facts, 14, kNumFuzzerRuns);
RunFuzzerAndReplayer(kTestShader4, facts, 14, kNumFuzzerRuns);
}
} // namespace
+388 -379
View File
@@ -16,6 +16,7 @@
#include <vector>
#include "source/fuzz/fuzzer.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/pseudo_random_generator.h"
#include "source/fuzz/shrinker.h"
#include "source/fuzz/uniform_buffer_element_descriptor.h"
@@ -25,217 +26,35 @@ namespace spvtools {
namespace fuzz {
namespace {
// Abstract class exposing an interestingness function as a virtual method.
class InterestingnessTest {
public:
virtual ~InterestingnessTest() = default;
// Abstract method that subclasses should implement for specific notions of
// interestingness. Its signature matches Shrinker::InterestingnessFunction.
// Argument |binary| is the SPIR-V binary to be checked; |counter| is used for
// debugging purposes.
virtual bool Interesting(const std::vector<uint32_t>& binary,
uint32_t counter) = 0;
// Yields the Interesting instance method wrapped in a function object.
Shrinker::InterestingnessFunction AsFunction() {
return std::bind(&InterestingnessTest::Interesting, this,
std::placeholders::_1, std::placeholders::_2);
}
};
// A test that says all binaries are interesting.
class AlwaysInteresting : public InterestingnessTest {
public:
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
return true;
}
};
// A test that says a binary is interesting first time round, and uninteresting
// thereafter.
class OnlyInterestingFirstTime : public InterestingnessTest {
public:
explicit OnlyInterestingFirstTime() : first_time_(true) {}
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
if (first_time_) {
first_time_ = false;
return true;
}
return false;
}
private:
bool first_time_;
};
// A test that says a binary is interesting first time round, after which
// interestingness ping pongs between false and true.
class PingPong : public InterestingnessTest {
public:
explicit PingPong() : interesting_(false) {}
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
interesting_ = !interesting_;
return interesting_;
}
private:
bool interesting_;
};
// A test that says a binary is interesting first time round, thereafter
// decides at random whether it is interesting. This allows the logic of the
// shrinker to be exercised quite a bit.
class InterestingThenRandom : public InterestingnessTest {
public:
InterestingThenRandom(const PseudoRandomGenerator& random_generator)
: first_time_(true), random_generator_(random_generator) {}
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
if (first_time_) {
first_time_ = false;
return true;
}
return random_generator_.RandomBool();
}
private:
bool first_time_;
PseudoRandomGenerator random_generator_;
};
// |binary_in| and |initial_facts| are a SPIR-V binary and sequence of facts to
// which |transformation_sequence_in| can be applied. Shrinking of
// |transformation_sequence_in| gets performed with respect to
// |interestingness_function|. If |expected_binary_out| is non-empty, it must
// match the binary obtained by applying the final shrunk set of
// transformations, in which case the number of such transformations should
// equal |expected_transformations_out_size|.
// The following SPIR-V came from this GLSL:
//
// The |step_limit| parameter restricts the number of steps that the shrinker
// will try; it can be set to something small for a faster (but less thorough)
// test.
void RunAndCheckShrinker(
const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const protobufs::TransformationSequence& transformation_sequence_in,
const Shrinker::InterestingnessFunction& interestingness_function,
const std::vector<uint32_t>& expected_binary_out,
uint32_t expected_transformations_out_size, uint32_t step_limit) {
// Run the shrinker.
Shrinker shrinker(target_env, step_limit, false);
shrinker.SetMessageConsumer(kSilentConsumer);
// #version 310 es
//
// void foo() {
// int x;
// x = 2;
// for (int i = 0; i < 100; i++) {
// x += i;
// x = x * 2;
// }
// return;
// }
//
// void main() {
// foo();
// for (int i = 0; i < 10; i++) {
// int j = 20;
// while(j > 0) {
// foo();
// j--;
// }
// do {
// i++;
// } while(i < 4);
// }
// }
std::vector<uint32_t> binary_out;
protobufs::TransformationSequence transformations_out;
Shrinker::ShrinkerResultStatus shrinker_result_status =
shrinker.Run(binary_in, initial_facts, transformation_sequence_in,
interestingness_function, &binary_out, &transformations_out);
ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete ==
shrinker_result_status ||
Shrinker::ShrinkerResultStatus::kStepLimitReached ==
shrinker_result_status);
// If a non-empty expected binary was provided, check that it matches the
// result of shrinking and that the expected number of transformations remain.
if (!expected_binary_out.empty()) {
ASSERT_EQ(expected_binary_out, binary_out);
ASSERT_EQ(expected_transformations_out_size,
static_cast<uint32_t>(transformations_out.transformation_size()));
}
}
// Assembles the given |shader| text, and then:
// - Runs the fuzzer with |seed| to yield a set of transformations
// - Shrinks the transformation with various interestingness functions,
// asserting some properties about the result each time
void RunFuzzerAndShrinker(const std::string& shader,
const protobufs::FactSequence& initial_facts,
uint32_t seed) {
const auto env = SPV_ENV_UNIVERSAL_1_5;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
t.SetMessageConsumer(kConsoleMessageConsumer);
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
ASSERT_TRUE(t.Validate(binary_in));
// Run the fuzzer and check that it successfully yields a valid binary.
std::vector<uint32_t> fuzzer_binary_out;
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
Fuzzer fuzzer(env, seed, true);
fuzzer.SetMessageConsumer(kSilentConsumer);
auto fuzzer_result_status =
fuzzer.Run(binary_in, initial_facts, &fuzzer_binary_out,
&fuzzer_transformation_sequence_out);
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
const uint32_t kReasonableStepLimit = 50;
const uint32_t kSmallStepLimit = 20;
// With the AlwaysInteresting test, we should quickly shrink to the original
// binary with no transformations remaining.
RunAndCheckShrinker(
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit);
// With the OnlyInterestingFirstTime test, no shrinking should be achieved.
RunAndCheckShrinker(
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
static_cast<uint32_t>(
fuzzer_transformation_sequence_out.transformation_size()),
kReasonableStepLimit);
// The PingPong test is unpredictable; passing an empty expected binary
// means that we don't check anything beyond that shrinking completes
// successfully.
RunAndCheckShrinker(env, binary_in, initial_facts,
fuzzer_transformation_sequence_out,
PingPong().AsFunction(), {}, 0, kSmallStepLimit);
// The InterestingThenRandom test is unpredictable; passing an empty
// expected binary means that we do not check anything about shrinking
// results.
RunAndCheckShrinker(
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
kSmallStepLimit);
}
TEST(FuzzerShrinkerTest, Miscellaneous1) {
// The following SPIR-V came from this GLSL:
//
// #version 310 es
//
// void foo() {
// int x;
// x = 2;
// for (int i = 0; i < 100; i++) {
// x += i;
// x = x * 2;
// }
// return;
// }
//
// void main() {
// foo();
// for (int i = 0; i < 10; i++) {
// int j = 20;
// while(j > 0) {
// foo();
// j--;
// }
// do {
// i++;
// } while(i < 4);
// }
// }
const std::string shader = R"(
const std::string kTestShader1 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
@@ -371,64 +190,60 @@ TEST(FuzzerShrinkerTest, Miscellaneous1) {
)";
RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2);
}
// The following SPIR-V came from this GLSL, which was then optimized using
// spirv-opt with the -O argument:
//
// #version 310 es
//
// precision highp float;
//
// layout(location = 0) out vec4 _GLF_color;
//
// layout(set = 0, binding = 0) uniform buf0 {
// vec2 injectionSwitch;
// };
// layout(set = 0, binding = 1) uniform buf1 {
// vec2 resolution;
// };
// bool checkSwap(float a, float b)
// {
// return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
// }
// void main()
// {
// float data[10];
// for(int i = 0; i < 10; i++)
// {
// data[i] = float(10 - i) * injectionSwitch.y;
// }
// for(int i = 0; i < 9; i++)
// {
// for(int j = 0; j < 10; j++)
// {
// if(j < i + 1)
// {
// continue;
// }
// bool doSwap = checkSwap(data[i], data[j]);
// if(doSwap)
// {
// float temp = data[i];
// data[i] = data[j];
// data[j] = temp;
// }
// }
// }
// if(gl_FragCoord.x < resolution.x / 2.0)
// {
// _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
// }
// else
// {
// _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
// }
// }
TEST(FuzzerShrinkerTest, Miscellaneous2) {
// The following SPIR-V came from this GLSL, which was then optimized using
// spirv-opt with the -O argument:
//
// #version 310 es
//
// precision highp float;
//
// layout(location = 0) out vec4 _GLF_color;
//
// layout(set = 0, binding = 0) uniform buf0 {
// vec2 injectionSwitch;
// };
// layout(set = 0, binding = 1) uniform buf1 {
// vec2 resolution;
// };
// bool checkSwap(float a, float b)
// {
// return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
// }
// void main()
// {
// float data[10];
// for(int i = 0; i < 10; i++)
// {
// data[i] = float(10 - i) * injectionSwitch.y;
// }
// for(int i = 0; i < 9; i++)
// {
// for(int j = 0; j < 10; j++)
// {
// if(j < i + 1)
// {
// continue;
// }
// bool doSwap = checkSwap(data[i], data[j]);
// if(doSwap)
// {
// float temp = data[i];
// data[i] = data[j];
// data[j] = temp;
// }
// }
// }
// if(gl_FragCoord.x < resolution.x / 2.0)
// {
// _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
// }
// else
// {
// _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
// }
// }
const std::string shader = R"(
const std::string kTestShader2 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
@@ -616,119 +431,115 @@ TEST(FuzzerShrinkerTest, Miscellaneous2) {
OpFunctionEnd
)";
RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19);
}
// The following SPIR-V came from this GLSL, which was then optimized using
// spirv-opt with the -O argument:
//
// #version 310 es
//
// precision highp float;
//
// layout(location = 0) out vec4 _GLF_color;
//
// layout(set = 0, binding = 0) uniform buf0 {
// vec2 resolution;
// };
// void main(void)
// {
// float A[50];
// for(
// int i = 0;
// i < 200;
// i ++
// )
// {
// if(i >= int(resolution.x))
// {
// break;
// }
// if((4 * (i / 4)) == i)
// {
// A[i / 4] = float(i);
// }
// }
// for(
// int i = 0;
// i < 50;
// i ++
// )
// {
// if(i < int(gl_FragCoord.x))
// {
// break;
// }
// if(i > 0)
// {
// A[i] += A[i - 1];
// }
// }
// if(int(gl_FragCoord.x) < 20)
// {
// _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 40)
// {
// _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 60)
// {
// _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 80)
// {
// _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 100)
// {
// _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 120)
// {
// _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 140)
// {
// _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 160)
// {
// _GLF_color = vec4(A[35] / resolution.x, A[39] /
// resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 180)
// {
// _GLF_color = vec4(A[40] / resolution.x, A[44] /
// resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 180)
// {
// _GLF_color = vec4(A[45] / resolution.x, A[49] /
// resolution.y, 1.0, 1.0);
// }
// else
// {
// discard;
// }
// }
TEST(FuzzerShrinkerTest, Miscellaneous3) {
// The following SPIR-V came from this GLSL, which was then optimized using
// spirv-opt with the -O argument:
//
// #version 310 es
//
// precision highp float;
//
// layout(location = 0) out vec4 _GLF_color;
//
// layout(set = 0, binding = 0) uniform buf0 {
// vec2 resolution;
// };
// void main(void)
// {
// float A[50];
// for(
// int i = 0;
// i < 200;
// i ++
// )
// {
// if(i >= int(resolution.x))
// {
// break;
// }
// if((4 * (i / 4)) == i)
// {
// A[i / 4] = float(i);
// }
// }
// for(
// int i = 0;
// i < 50;
// i ++
// )
// {
// if(i < int(gl_FragCoord.x))
// {
// break;
// }
// if(i > 0)
// {
// A[i] += A[i - 1];
// }
// }
// if(int(gl_FragCoord.x) < 20)
// {
// _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 40)
// {
// _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 60)
// {
// _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 80)
// {
// _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 100)
// {
// _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 120)
// {
// _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 140)
// {
// _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
// 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 160)
// {
// _GLF_color = vec4(A[35] / resolution.x, A[39] /
// resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 180)
// {
// _GLF_color = vec4(A[40] / resolution.x, A[44] /
// resolution.y, 1.0, 1.0);
// }
// else
// if(int(gl_FragCoord.x) < 180)
// {
// _GLF_color = vec4(A[45] / resolution.x, A[49] /
// resolution.y, 1.0, 1.0);
// }
// else
// {
// discard;
// }
// }
const std::string shader = R"(
const std::string kTestShader3 = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
@@ -1076,6 +887,204 @@ TEST(FuzzerShrinkerTest, Miscellaneous3) {
OpFunctionEnd
)";
// Abstract class exposing an interestingness function as a virtual method.
class InterestingnessTest {
public:
virtual ~InterestingnessTest() = default;
// Abstract method that subclasses should implement for specific notions of
// interestingness. Its signature matches Shrinker::InterestingnessFunction.
// Argument |binary| is the SPIR-V binary to be checked; |counter| is used for
// debugging purposes.
virtual bool Interesting(const std::vector<uint32_t>& binary,
uint32_t counter) = 0;
// Yields the Interesting instance method wrapped in a function object.
Shrinker::InterestingnessFunction AsFunction() {
return std::bind(&InterestingnessTest::Interesting, this,
std::placeholders::_1, std::placeholders::_2);
}
};
// A test that says all binaries are interesting.
class AlwaysInteresting : public InterestingnessTest {
public:
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
return true;
}
};
// A test that says a binary is interesting first time round, and uninteresting
// thereafter.
class OnlyInterestingFirstTime : public InterestingnessTest {
public:
explicit OnlyInterestingFirstTime() : first_time_(true) {}
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
if (first_time_) {
first_time_ = false;
return true;
}
return false;
}
private:
bool first_time_;
};
// A test that says a binary is interesting first time round, after which
// interestingness ping pongs between false and true.
class PingPong : public InterestingnessTest {
public:
explicit PingPong() : interesting_(false) {}
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
interesting_ = !interesting_;
return interesting_;
}
private:
bool interesting_;
};
// A test that says a binary is interesting first time round, thereafter
// decides at random whether it is interesting. This allows the logic of the
// shrinker to be exercised quite a bit.
class InterestingThenRandom : public InterestingnessTest {
public:
InterestingThenRandom(const PseudoRandomGenerator& random_generator)
: first_time_(true), random_generator_(random_generator) {}
bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
if (first_time_) {
first_time_ = false;
return true;
}
return random_generator_.RandomBool();
}
private:
bool first_time_;
PseudoRandomGenerator random_generator_;
};
// |binary_in| and |initial_facts| are a SPIR-V binary and sequence of facts to
// which |transformation_sequence_in| can be applied. Shrinking of
// |transformation_sequence_in| gets performed with respect to
// |interestingness_function|. If |expected_binary_out| is non-empty, it must
// match the binary obtained by applying the final shrunk set of
// transformations, in which case the number of such transformations should
// equal |expected_transformations_out_size|.
//
// The |step_limit| parameter restricts the number of steps that the shrinker
// will try; it can be set to something small for a faster (but less thorough)
// test.
void RunAndCheckShrinker(
const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
const protobufs::FactSequence& initial_facts,
const protobufs::TransformationSequence& transformation_sequence_in,
const Shrinker::InterestingnessFunction& interestingness_function,
const std::vector<uint32_t>& expected_binary_out,
uint32_t expected_transformations_out_size, uint32_t step_limit) {
// Run the shrinker.
Shrinker shrinker(target_env, step_limit, false);
shrinker.SetMessageConsumer(kSilentConsumer);
std::vector<uint32_t> binary_out;
protobufs::TransformationSequence transformations_out;
Shrinker::ShrinkerResultStatus shrinker_result_status =
shrinker.Run(binary_in, initial_facts, transformation_sequence_in,
interestingness_function, &binary_out, &transformations_out);
ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete ==
shrinker_result_status ||
Shrinker::ShrinkerResultStatus::kStepLimitReached ==
shrinker_result_status);
// If a non-empty expected binary was provided, check that it matches the
// result of shrinking and that the expected number of transformations remain.
if (!expected_binary_out.empty()) {
ASSERT_EQ(expected_binary_out, binary_out);
ASSERT_EQ(expected_transformations_out_size,
static_cast<uint32_t>(transformations_out.transformation_size()));
}
}
// Assembles the given |shader| text, and then:
// - Runs the fuzzer with |seed| to yield a set of transformations
// - Shrinks the transformation with various interestingness functions,
// asserting some properties about the result each time
void RunFuzzerAndShrinker(const std::string& shader,
const protobufs::FactSequence& initial_facts,
uint32_t seed) {
const auto env = SPV_ENV_UNIVERSAL_1_5;
std::vector<uint32_t> binary_in;
SpirvTools t(env);
t.SetMessageConsumer(kConsoleMessageConsumer);
ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
ASSERT_TRUE(t.Validate(binary_in));
std::vector<fuzzerutil::ModuleSupplier> donor_suppliers;
for (auto donor : {&kTestShader1, &kTestShader2, &kTestShader3}) {
donor_suppliers.emplace_back([donor]() {
return BuildModule(env, kConsoleMessageConsumer, *donor,
kFuzzAssembleOption);
});
}
// Run the fuzzer and check that it successfully yields a valid binary.
std::vector<uint32_t> fuzzer_binary_out;
protobufs::TransformationSequence fuzzer_transformation_sequence_out;
Fuzzer fuzzer(env, seed, true);
fuzzer.SetMessageConsumer(kSilentConsumer);
auto fuzzer_result_status =
fuzzer.Run(binary_in, initial_facts, donor_suppliers, &fuzzer_binary_out,
&fuzzer_transformation_sequence_out);
ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
ASSERT_TRUE(t.Validate(fuzzer_binary_out));
const uint32_t kReasonableStepLimit = 50;
const uint32_t kSmallStepLimit = 20;
// With the AlwaysInteresting test, we should quickly shrink to the original
// binary with no transformations remaining.
RunAndCheckShrinker(
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit);
// With the OnlyInterestingFirstTime test, no shrinking should be achieved.
RunAndCheckShrinker(
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
static_cast<uint32_t>(
fuzzer_transformation_sequence_out.transformation_size()),
kReasonableStepLimit);
// The PingPong test is unpredictable; passing an empty expected binary
// means that we don't check anything beyond that shrinking completes
// successfully.
RunAndCheckShrinker(env, binary_in, initial_facts,
fuzzer_transformation_sequence_out,
PingPong().AsFunction(), {}, 0, kSmallStepLimit);
// The InterestingThenRandom test is unpredictable; passing an empty
// expected binary means that we do not check anything about shrinking
// results.
RunAndCheckShrinker(
env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
kSmallStepLimit);
}
TEST(FuzzerShrinkerTest, Miscellaneous1) {
RunFuzzerAndShrinker(kTestShader1, protobufs::FactSequence(), 2);
}
TEST(FuzzerShrinkerTest, Miscellaneous2) {
RunFuzzerAndShrinker(kTestShader2, protobufs::FactSequence(), 19);
}
TEST(FuzzerShrinkerTest, Miscellaneous3) {
// Add the facts "resolution.x == 250" and "resolution.y == 100".
protobufs::FactSequence facts;
{
@@ -1099,7 +1108,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous3) {
// Do 2 fuzzer runs, starting from an initial seed of 194 (seed value chosen
// arbitrarily).
RunFuzzerAndShrinker(shader, facts, 194);
RunFuzzerAndShrinker(kTestShader3, facts, 194);
}
} // namespace
+62 -7
View File
@@ -23,6 +23,7 @@
#include "source/fuzz/force_render_red.h"
#include "source/fuzz/fuzzer.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/replayer.h"
#include "source/fuzz/shrinker.h"
@@ -75,7 +76,8 @@ void PrintUsage(const char* program) {
printf(
R"(%s - Fuzzes an equivalent SPIR-V binary based on a given binary.
USAGE: %s [options] <input.spv> -o <output.spv>
USAGE: %s [options] <input.spv> -o <output.spv> \
--donors=<donors.txt>
USAGE: %s [options] <input.spv> -o <output.spv> \
--shrink=<input.transformations> -- <interestingness_test> [args...]
@@ -100,6 +102,11 @@ Options (in lexicographical order):
-h, --help
Print this help.
--donors=
File specifying a series of donor files, one per line. Must be
provided if the tool is invoked in fuzzing mode; incompatible
with replay and shrink modes. The file should be empty if no
donors are to be used.
--force-render-red
Transforms the input shader into a shader that writes red to the
output buffer, and then captures the original shader as the body
@@ -154,7 +161,7 @@ void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/,
}
FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
std::string* out_binary_file,
std::string* out_binary_file, std::string* donors_file,
std::string* replay_transformations_file,
std::vector<std::string>* interestingness_test,
std::string* shrink_transformations_file,
@@ -181,6 +188,9 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
PrintUsage(argv[0]);
return {FuzzActions::STOP, 1};
}
} else if (0 == strncmp(cur_arg, "--donors=", sizeof("--donors=") - 1)) {
const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
*donors_file = std::string(split_flag.second);
} else if (0 == strncmp(cur_arg, "--force-render-red",
sizeof("--force-render-red") - 1)) {
force_render_red = true;
@@ -285,6 +295,18 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
return {FuzzActions::STOP, 1};
}
if (!replay_transformations_file->empty() ||
!shrink_transformations_file->empty()) {
// Donors should not be provided when replaying or shrinking: they only make
// sense during fuzzing.
if (!donors_file->empty()) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"The --donors argument is not compatible with --replay "
"nor --shrink.");
return {FuzzActions::STOP, 1};
}
}
if (!replay_transformations_file->empty()) {
// A replay transformations file was given, thus the tool is being invoked
// in replay mode.
@@ -305,6 +327,12 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
return {FuzzActions::SHRINK, 0};
}
// The tool is being invoked in fuzz mode.
if (donors_file->empty()) {
spvtools::Error(FuzzDiagnostic, nullptr, {},
"Fuzzing requires that the --donors option is used.");
return {FuzzActions::STOP, 1};
}
return {FuzzActions::FUZZ, 0};
}
@@ -408,18 +436,44 @@ bool Fuzz(const spv_target_env& target_env,
spv_const_fuzzer_options fuzzer_options,
const std::vector<uint32_t>& binary_in,
const spvtools::fuzz::protobufs::FactSequence& initial_facts,
std::vector<uint32_t>* binary_out,
const std::string& donors, std::vector<uint32_t>* binary_out,
spvtools::fuzz::protobufs::TransformationSequence*
transformations_applied) {
auto message_consumer = spvtools::utils::CLIMessageConsumer;
std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donor_suppliers;
std::ifstream donors_file(donors);
if (!donors_file) {
spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error opening donors file");
return false;
}
std::string donor_filename;
while (std::getline(donors_file, donor_filename)) {
donor_suppliers.emplace_back(
[donor_filename, message_consumer,
target_env]() -> std::unique_ptr<spvtools::opt::IRContext> {
std::vector<uint32_t> donor_binary;
if (!ReadFile<uint32_t>(donor_filename.c_str(), "rb",
&donor_binary)) {
return nullptr;
}
return spvtools::BuildModule(target_env, message_consumer,
donor_binary.data(),
donor_binary.size());
});
}
spvtools::fuzz::Fuzzer fuzzer(
target_env,
fuzzer_options->has_random_seed
? fuzzer_options->random_seed
: static_cast<uint32_t>(std::random_device()()),
fuzzer_options->fuzzer_pass_validation_enabled);
fuzzer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
fuzzer.SetMessageConsumer(message_consumer);
auto fuzz_result_status =
fuzzer.Run(binary_in, initial_facts, binary_out, transformations_applied);
fuzzer.Run(binary_in, initial_facts, donor_suppliers, binary_out,
transformations_applied);
if (fuzz_result_status !=
spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
@@ -452,6 +506,7 @@ const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
int main(int argc, const char** argv) {
std::string in_binary_file;
std::string out_binary_file;
std::string donors_file;
std::string replay_transformations_file;
std::vector<std::string> interestingness_test;
std::string shrink_transformations_file;
@@ -460,7 +515,7 @@ int main(int argc, const char** argv) {
spvtools::FuzzerOptions fuzzer_options;
FuzzStatus status = ParseFlags(
argc, argv, &in_binary_file, &out_binary_file,
argc, argv, &in_binary_file, &out_binary_file, &donors_file,
&replay_transformations_file, &interestingness_test,
&shrink_transformations_file, &shrink_temp_file_prefix, &fuzzer_options);
@@ -507,7 +562,7 @@ int main(int argc, const char** argv) {
break;
case FuzzActions::FUZZ:
if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
&binary_out, &transformations_applied)) {
donors_file, &binary_out, &transformations_applied)) {
return 1;
}
break;