Rework management of probabilities in spirv-fuzz (#2839)

Before this change there was quite a lot of duplication in the code
being used to choose random percentages, and some of it was incorrect
so that a percentage chance of (100-N)% instead of N% was being used.
Also there was a lot of duplicate code to choose a random index into a
vector.  This change eliminates that duplication (fixing up the
percentage problem), and gets rid of direct access to the random
number generator being used for fuzzing, so that all randomization
requests must go through the FuzzerContext class, discouraging future
ad-hoc uses of the random number generator.
This commit is contained in:
Alastair Donaldson 2019-09-10 15:02:25 +01:00 committed by GitHub
parent 7ee8f443ea
commit e2e95172df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 53 additions and 49 deletions

View File

@ -58,8 +58,11 @@ FuzzerContext::~FuzzerContext() = default;
uint32_t FuzzerContext::GetFreshId() { return next_fresh_id_++; }
RandomGenerator* FuzzerContext::GetRandomGenerator() {
return random_generator_;
bool FuzzerContext::ChooseEven() { return random_generator_->RandomBool(); }
bool FuzzerContext::ChoosePercentage(uint32_t percentage_chance) {
assert(percentage_chance <= 100);
return random_generator_->RandomPercentage() < percentage_chance;
}
} // namespace fuzz

View File

@ -34,8 +34,22 @@ class FuzzerContext {
~FuzzerContext();
// Provides the random generator used to control fuzzing.
RandomGenerator* GetRandomGenerator();
// Returns a random boolean.
bool ChooseEven();
// Returns true if and only if a randomly-chosen integer in the range [0, 100]
// is less than |percentage_chance|.
bool ChoosePercentage(uint32_t percentage_chance);
// Returns a random index into |sequence|, which is expected to have a 'size'
// method, and which must be non-empty. Typically 'HasSizeMethod' will be an
// std::vector.
template <typename HasSizeMethod>
uint32_t RandomIndex(HasSizeMethod sequence) {
assert(sequence.size() > 0);
return random_generator_->RandomUint32(
static_cast<uint32_t>(sequence.size()));
}
// Yields an id that is guaranteed not to be used in the module being fuzzed,
// or to have been issued before.
@ -53,11 +67,10 @@ class FuzzerContext {
}
uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
// Probability distributions to control how deeply to recurse.
// Functions to control how deeply to recurse.
// Keep them in alphabetical order.
const std::function<bool(uint32_t, RandomGenerator*)>&
GoDeeperInConstantObfuscation() {
return go_deeper_in_constant_obfuscation_;
bool GoDeeperInConstantObfuscation(uint32_t depth) {
return go_deeper_in_constant_obfuscation_(depth, random_generator_);
}
private:

View File

@ -51,8 +51,7 @@ void FuzzerPassAddDeadBreaks::Apply() {
// merge blocks. This will lead to interesting opportunities being
// missed.
auto candidate_transformation = TransformationAddDeadBreak(
block.id(), merge_block_id,
GetFuzzerContext()->GetRandomGenerator()->RandomBool(), {});
block.id(), merge_block_id, GetFuzzerContext()->ChooseEven(), {});
if (candidate_transformation.IsApplicable(GetIRContext(),
*GetFactManager())) {
// Only consider a transformation as a candidate if it is applicable.
@ -77,16 +76,15 @@ void FuzzerPassAddDeadBreaks::Apply() {
while (!candidate_transformations.empty()) {
// Choose a random index into the sequence of remaining candidate
// transformations.
auto index = GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(candidate_transformations.size()));
auto index = GetFuzzerContext()->RandomIndex(candidate_transformations);
// Remove the transformation at the chosen index from the sequence.
auto transformation = std::move(candidate_transformations[index]);
candidate_transformations.erase(candidate_transformations.begin() + index);
// Probabilistically decide whether to try to apply it vs. ignore it, in the
// case that it is applicable.
if (transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
GetFuzzerContext()->GetChanceOfAddingDeadBreak()) {
GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingDeadBreak())) {
transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() = transformation.ToMessage();
}

View File

@ -40,14 +40,13 @@ void FuzzerPassAddDeadContinues::Apply() {
// merge blocks. This will lead to interesting opportunities being
// missed.
auto candidate_transformation = TransformationAddDeadContinue(
block.id(), GetFuzzerContext()->GetRandomGenerator()->RandomBool(),
{});
block.id(), GetFuzzerContext()->ChooseEven(), {});
// Probabilistically decide whether to apply the transformation in the
// case that it is applicable.
if (candidate_transformation.IsApplicable(GetIRContext(),
*GetFactManager()) &&
GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
GetFuzzerContext()->GetChanceOfAddingDeadContinue()) {
GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfAddingDeadContinue())) {
candidate_transformation.Apply(GetIRContext(), GetFactManager());
*GetTransformations()->add_transformation() =
candidate_transformation.ToMessage();

View File

@ -48,14 +48,12 @@ void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaConstantPair(
// a 'greater than' or 'less than' kind of opcode, and then select a
// random opcode from the resulting subset.
SpvOp comparison_opcode;
if (GetFuzzerContext()->GetRandomGenerator()->RandomBool()) {
comparison_opcode = greater_than_opcodes
[GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(greater_than_opcodes.size()))];
if (GetFuzzerContext()->ChooseEven()) {
comparison_opcode = greater_than_opcodes[GetFuzzerContext()->RandomIndex(
greater_than_opcodes)];
} else {
comparison_opcode = less_than_opcodes
[GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(less_than_opcodes.size()))];
comparison_opcode =
less_than_opcodes[GetFuzzerContext()->RandomIndex(less_than_opcodes)];
}
// We now need to decide how to order constant_id_1 and constant_id_2 such
@ -103,8 +101,7 @@ void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaConstantPair(
for (uint32_t index : {0u, 1u}) {
// We randomly decide, based on the current depth of obfuscation, whether
// to further obfuscate this operand.
if (GetFuzzerContext()->GoDeeperInConstantObfuscation()(
depth, GetFuzzerContext()->GetRandomGenerator())) {
if (GetFuzzerContext()->GoDeeperInConstantObfuscation(depth)) {
auto in_operand_use = transformation::MakeIdUseDescriptor(
binary_operator_instruction->GetSingleWordInOperand(index),
binary_operator_instruction->opcode(), index,
@ -252,9 +249,9 @@ void FuzzerPassObfuscateConstants::ObfuscateBoolConstant(
// elements with known values.
return;
}
auto chosen_type_id = available_types_with_uniforms
[GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(available_types_with_uniforms.size()))];
auto chosen_type_id =
available_types_with_uniforms[GetFuzzerContext()->RandomIndex(
available_types_with_uniforms)];
auto available_constants =
GetFactManager()->GetConstantsAvailableFromUniformsForType(
GetIRContext(), chosen_type_id);
@ -269,15 +266,12 @@ void FuzzerPassObfuscateConstants::ObfuscateBoolConstant(
// We know we have at least two known-to-be-constant uniforms of the chosen
// type. Pick one of them at random.
auto constant_index_1 =
GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(available_constants.size()));
auto constant_index_1 = GetFuzzerContext()->RandomIndex(available_constants);
uint32_t constant_index_2;
// Now choose another one distinct from the first one.
do {
constant_index_2 = GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(available_constants.size()));
constant_index_2 = GetFuzzerContext()->RandomIndex(available_constants);
} while (constant_index_1 == constant_index_2);
auto constant_id_1 = available_constants[constant_index_1];
@ -321,9 +315,7 @@ void FuzzerPassObfuscateConstants::ObfuscateScalarConstant(
// Choose a random available uniform known to be equal to the constant.
protobufs::UniformBufferElementDescriptor uniform_descriptor =
uniform_descriptors
[GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(uniform_descriptors.size()))];
uniform_descriptors[GetFuzzerContext()->RandomIndex(uniform_descriptors)];
// Create, apply and record a transformation to replace the constant use with
// the result of a load from the chosen uniform.
auto transformation = TransformationReplaceConstantWithUniform(
@ -445,13 +437,12 @@ void FuzzerPassObfuscateConstants::Apply() {
// Go through the constant uses in a random order by repeatedly pulling out a
// constant use at a random index.
while (!constant_uses.empty()) {
auto index = GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(constant_uses.size()));
auto index = GetFuzzerContext()->RandomIndex(constant_uses);
auto constant_use = std::move(constant_uses[index]);
constant_uses.erase(constant_uses.begin() + index);
// Decide probabilistically whether to skip or obfuscate this constant use.
if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
GetFuzzerContext()->GetChanceOfObfuscatingConstant()) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfObfuscatingConstant())) {
continue;
}
ObfuscateConstant(0, constant_use);

View File

@ -57,8 +57,8 @@ void FuzzerPassPermuteBlocks::Apply() {
// would provide more freedom for A to move.
for (auto id = block_ids.rbegin(); id != block_ids.rend(); ++id) {
// Randomly decide whether to ignore the block id.
if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
GetFuzzerContext()->GetChanceOfMovingBlockDown()) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfMovingBlockDown())) {
continue;
}
// Keep pushing the block down, until pushing down fails.

View File

@ -44,8 +44,9 @@ void FuzzerPassSplitBlocks::Apply() {
// Now go through all the block pointers that were gathered.
for (auto& block : blocks) {
// Probabilistically decide whether to try to split this block.
if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
GetFuzzerContext()->GetChanceOfSplittingBlock()) {
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfSplittingBlock())) {
// We are not going to try to split this block.
continue;
}
// We are going to try to split this block. We now need to choose where
@ -77,9 +78,8 @@ void FuzzerPassSplitBlocks::Apply() {
}
// Having identified all the places we might be able to split the block,
// we choose one of them.
auto base_offset = base_offset_pairs
[GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
static_cast<uint32_t>(base_offset_pairs.size()))];
auto base_offset =
base_offset_pairs[GetFuzzerContext()->RandomIndex(base_offset_pairs)];
auto transformation =
TransformationSplitBlock(base_offset.first, base_offset.second,
GetFuzzerContext()->GetFreshId());