Add fuzzer pass to copy objects (#2853)
A new fuzzer pass that randomly introduces OpCopyObject instructions
that make copies of ids, and uses the fact manager to record the fact
that an id %id is synonymous with an id generated by an OpCopyObject
applied to %id. (A future pass will exploit such synonym facts.)
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 7b5deb8..c0592b3 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -34,6 +34,7 @@
fuzzer_pass_add_dead_breaks.h
fuzzer_pass_add_dead_continues.h
fuzzer_pass_add_useful_constructs.h
+ fuzzer_pass_copy_objects.h
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_permute_blocks.h
fuzzer_pass_split_blocks.h
@@ -69,6 +70,7 @@
fuzzer_pass_add_dead_breaks.cpp
fuzzer_pass_add_dead_continues.cpp
fuzzer_pass_add_useful_constructs.cpp
+ fuzzer_pass_copy_objects.cpp
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_permute_blocks.cpp
fuzzer_pass_split_blocks.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 89250a0..9d2a7c7 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -22,6 +22,7 @@
#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
#include "source/fuzz/fuzzer_pass_split_blocks.h"
@@ -107,6 +108,9 @@
.Apply();
// Apply some semantics-preserving passes.
+ FuzzerPassCopyObjects(ir_context.get(), &fact_manager, &fuzzer_context,
+ transformation_sequence_out)
+ .Apply();
FuzzerPassSplitBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
transformation_sequence_out)
.Apply();
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 59f8f32..e9398d5 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -26,6 +26,7 @@
const uint32_t kDefaultChanceOfAddingDeadBreak = 20;
const uint32_t kDefaultChanceOfAddingDeadContinue = 20;
+const uint32_t kDefaultChanceOfCopyingObject = 20;
const uint32_t kDefaultChanceOfMovingBlockDown = 25;
const uint32_t kDefaultChanceOfObfuscatingConstant = 20;
const uint32_t kDefaultChanceOfSplittingBlock = 20;
@@ -48,6 +49,7 @@
next_fresh_id_(min_fresh_id),
chance_of_adding_dead_break_(kDefaultChanceOfAddingDeadBreak),
chance_of_adding_dead_continue_(kDefaultChanceOfAddingDeadContinue),
+ chance_of_copying_object_(kDefaultChanceOfCopyingObject),
chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown),
chance_of_obfuscating_constant_(kDefaultChanceOfObfuscatingConstant),
chance_of_splitting_block_(kDefaultChanceOfSplittingBlock),
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 588a460..ed6a355 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -61,6 +61,7 @@
uint32_t GetChanceOfAddingDeadContinue() {
return chance_of_adding_dead_continue_;
}
+ uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
uint32_t GetChanceOfObfuscatingConstant() {
return chance_of_obfuscating_constant_;
@@ -83,6 +84,7 @@
// Keep them in alphabetical order.
uint32_t chance_of_adding_dead_break_;
uint32_t chance_of_adding_dead_continue_;
+ uint32_t chance_of_copying_object_;
uint32_t chance_of_moving_block_down_;
uint32_t chance_of_obfuscating_constant_;
uint32_t chance_of_splitting_block_;
diff --git a/source/fuzz/fuzzer_pass_copy_objects.cpp b/source/fuzz/fuzzer_pass_copy_objects.cpp
new file mode 100644
index 0000000..a1b118a
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_copy_objects.cpp
@@ -0,0 +1,148 @@
+// 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_copy_objects.h"
+
+#include "source/fuzz/transformation_copy_object.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassCopyObjects::FuzzerPassCopyObjects(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassCopyObjects::~FuzzerPassCopyObjects() = default;
+
+void FuzzerPassCopyObjects::Apply() {
+ // Consider every block in every function.
+ for (auto& function : *GetIRContext()->module()) {
+ for (auto& block : function) {
+ // We now consider every instruction in the block, randomly deciding
+ // whether to add an object copy before the instruction.
+
+ // In order to insert an object copy instruction, we need to be able to
+ // identify the instruction a copy should be inserted before. We do this
+ // by tracking a base instruction, which must generate a result id, and an
+ // offset (to allow us to identify instructions that do not generate
+ // result ids).
+
+ // The initial base instruction is the block label.
+ uint32_t base = block.id();
+ uint32_t offset = 0;
+ // Consider every instruction in the block.
+ for (auto inst_it = block.begin(); inst_it != block.end(); ++inst_it) {
+ if (inst_it->HasResultId()) {
+ // In the case that the instruction has a result id, we use the
+ // instruction as its own base, with zero offset.
+ base = inst_it->result_id();
+ offset = 0;
+ } else {
+ // The instruction does not have a result id, so we need to identify
+ // it via the latest instruction that did have a result id (base), and
+ // an incremented offset.
+ offset++;
+ }
+
+ // Check whether it is legitimate to insert a copy before this
+ // instruction.
+ if (!TransformationCopyObject::CanInsertCopyBefore(inst_it)) {
+ continue;
+ }
+
+ // Randomly decide whether to try inserting an object copy here.
+ if (!GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()->GetChanceOfCopyingObject())) {
+ continue;
+ }
+
+ // Populate list of potential instructions that can be copied.
+ // TODO(afd) The following is (relatively) simple, but may end up being
+ // prohibitively inefficient, as it walks the whole dominator tree for
+ // every copy that is added.
+ std::vector<opt::Instruction*> copyable_instructions;
+
+ // Consider all global declarations
+ for (auto& global : GetIRContext()->module()->types_values()) {
+ if (TransformationCopyObject::IsCopyable(GetIRContext(), &global)) {
+ copyable_instructions.push_back(&global);
+ }
+ }
+
+ // Consider all previous instructions in this block
+ for (auto prev_inst_it = block.begin(); prev_inst_it != inst_it;
+ ++prev_inst_it) {
+ if (TransformationCopyObject::IsCopyable(GetIRContext(),
+ &*prev_inst_it)) {
+ copyable_instructions.push_back(&*prev_inst_it);
+ }
+ }
+
+ // Walk the dominator tree to consider all instructions from dominating
+ // blocks
+ auto dominator_analysis =
+ GetIRContext()->GetDominatorAnalysis(&function);
+ for (auto next_dominator =
+ dominator_analysis->ImmediateDominator(&block);
+ next_dominator != nullptr;
+ next_dominator =
+ dominator_analysis->ImmediateDominator(next_dominator)) {
+ for (auto& dominating_inst : *next_dominator) {
+ if (TransformationCopyObject::IsCopyable(GetIRContext(),
+ &dominating_inst)) {
+ copyable_instructions.push_back(&dominating_inst);
+ }
+ }
+ }
+
+ // At this point, |copyable_instructions| contains all the instructions
+ // we might think of copying.
+
+ if (!copyable_instructions.empty()) {
+ // Choose a copyable instruction at random, and create and apply an
+ // object copying transformation based on it.
+ uint32_t index =
+ GetFuzzerContext()->RandomIndex(copyable_instructions);
+ TransformationCopyObject transformation(
+ copyable_instructions[index]->result_id(), base, offset,
+ GetFuzzerContext()->GetFreshId());
+ assert(
+ transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
+ "This transformation should be applicable by construction.");
+ transformation.Apply(GetIRContext(), GetFactManager());
+ *GetTransformations()->add_transformation() =
+ transformation.ToMessage();
+
+ if (!inst_it->HasResultId()) {
+ // We have inserted a new instruction before the current
+ // instruction, and we are tracking the current id-less instruction
+ // via an offset (offset) from a previous instruction (base) that
+ // has an id. We increment |offset| to reflect the newly-inserted
+ // instruction.
+ //
+ // This is slightly preferable to the alternative of setting |base|
+ // to be the result id of the new instruction, since on replay we
+ // might end up eliminating this copy but keeping a subsequent copy.
+ offset++;
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_copy_objects.h b/source/fuzz/fuzzer_pass_copy_objects.h
new file mode 100644
index 0000000..5419459
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_copy_objects.h
@@ -0,0 +1,38 @@
+// 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_COPY_OBJECTS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_COPY_OBJECTS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for adding adding copies of objects to the module.
+class FuzzerPassCopyObjects : public FuzzerPass {
+ public:
+ FuzzerPassCopyObjects(opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassCopyObjects();
+
+ void Apply() override;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_COPY_OBJECTS_H_
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index a252734..bf2cdcd 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -25,6 +25,7 @@
#include "transformation_add_type_float.h"
#include "transformation_add_type_int.h"
#include "transformation_add_type_pointer.h"
+#include "transformation_copy_object.h"
#include "transformation_move_block_down.h"
#include "transformation_replace_boolean_constant_with_constant_binary.h"
#include "transformation_replace_constant_with_uniform.h"
@@ -59,6 +60,8 @@
case protobufs::Transformation::TransformationCase::kAddTypePointer:
return MakeUnique<TransformationAddTypePointer>(
message.add_type_pointer());
+ case protobufs::Transformation::TransformationCase::kCopyObject:
+ return MakeUnique<TransformationCopyObject>(message.copy_object());
case protobufs::Transformation::TransformationCase::kMoveBlockDown:
return MakeUnique<TransformationMoveBlockDown>(message.move_block_down());
case protobufs::Transformation::TransformationCase::
@@ -71,13 +74,12 @@
message.replace_constant_with_uniform());
case protobufs::Transformation::TransformationCase::kSplitBlock:
return MakeUnique<TransformationSplitBlock>(message.split_block());
- default:
- assert(message.transformation_case() ==
- protobufs::Transformation::TRANSFORMATION_NOT_SET &&
- "Unhandled transformation type.");
+ case protobufs::Transformation::TRANSFORMATION_NOT_SET:
assert(false && "An unset transformation was encountered.");
return nullptr;
}
+ assert(false && "Should be unreachable as all cases must be handled above.");
+ return nullptr;
}
} // namespace fuzz
diff --git a/source/fuzz/transformation_copy_object.cpp b/source/fuzz/transformation_copy_object.cpp
index f9ead43..cfd97ae 100644
--- a/source/fuzz/transformation_copy_object.cpp
+++ b/source/fuzz/transformation_copy_object.cpp
@@ -46,18 +46,7 @@
if (!object_inst) {
return false;
}
- if (!object_inst->type_id()) {
- // We can only apply OpCopyObject to instructions that have types.
- return false;
- }
- if (!context->get_decoration_mgr()
- ->GetDecorationsFor(message_.object(), true)
- .empty()) {
- // We do not copy objects that have decorations: if the copy is not
- // decorated analogously, using the original object vs. its copy may not be
- // equivalent.
- // TODO(afd): it would be possible to make the copy but not add an id
- // synonym.
+ if (!IsCopyable(context, object_inst)) {
return false;
}
@@ -81,22 +70,11 @@
// The offset was inappropriate.
return false;
}
- if (insert_before->PreviousNode() &&
- (insert_before->PreviousNode()->opcode() == SpvOpLoopMerge ||
- insert_before->PreviousNode()->opcode() == SpvOpSelectionMerge)) {
- // We cannot insert a copy directly after a merge instruction.
+
+ if (!CanInsertCopyBefore(insert_before)) {
return false;
}
- if (insert_before->opcode() == SpvOpVariable) {
- // We cannot insert a copy directly before a variable; variables in a
- // function must be contiguous in the entry block.
- return false;
- }
- // We cannot insert a copy directly before OpPhi, because OpPhi instructions
- // need to be contiguous at the start of a block.
- if (insert_before->opcode() == SpvOpPhi) {
- return false;
- }
+
// |message_object| must be available at the point where we want to add the
// copy. It is available if it is at global scope (in which case it has no
// block), or if it dominates the point of insertion but is different from the
@@ -154,5 +132,43 @@
return result;
}
+bool TransformationCopyObject::IsCopyable(opt::IRContext* ir_context,
+ opt::Instruction* inst) {
+ if (!inst->HasResultId()) {
+ // We can only apply OpCopyObject to instructions that generate ids.
+ return false;
+ }
+ if (!inst->type_id()) {
+ // We can only apply OpCopyObject to instructions that have types.
+ return false;
+ }
+ // We do not copy objects that have decorations: if the copy is not
+ // decorated analogously, using the original object vs. its copy may not be
+ // equivalent.
+ // TODO(afd): it would be possible to make the copy but not add an id
+ // synonym.
+ return ir_context->get_decoration_mgr()
+ ->GetDecorationsFor(inst->result_id(), true)
+ .empty();
+}
+
+bool TransformationCopyObject::CanInsertCopyBefore(
+ const opt::BasicBlock::iterator& instruction_in_block) {
+ if (instruction_in_block->PreviousNode() &&
+ (instruction_in_block->PreviousNode()->opcode() == SpvOpLoopMerge ||
+ instruction_in_block->PreviousNode()->opcode() == SpvOpSelectionMerge)) {
+ // We cannot insert a copy directly after a merge instruction.
+ return false;
+ }
+ if (instruction_in_block->opcode() == SpvOpVariable) {
+ // We cannot insert a copy directly before a variable; variables in a
+ // function must be contiguous in the entry block.
+ return false;
+ }
+ // We cannot insert a copy directly before OpPhi, because OpPhi instructions
+ // need to be contiguous at the start of a block.
+ return instruction_in_block->opcode() != SpvOpPhi;
+}
+
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/transformation_copy_object.h b/source/fuzz/transformation_copy_object.h
index 6ce72df..20e5874 100644
--- a/source/fuzz/transformation_copy_object.h
+++ b/source/fuzz/transformation_copy_object.h
@@ -28,8 +28,8 @@
explicit TransformationCopyObject(
const protobufs::TransformationCopyObject& message);
- TransformationCopyObject(uint32_t fresh_id, uint32_t object,
- uint32_t insert_after_id, uint32_t offset);
+ TransformationCopyObject(uint32_t object, uint32_t base_instruction_id,
+ uint32_t offset, uint32_t fresh_id);
// - |message_.fresh_id| must not be used by the module.
// - |message_.object| must be a result id that is a legitimate operand for
@@ -58,6 +58,14 @@
protobufs::Transformation ToMessage() const override;
+ // Determines whether it is OK to make a copy of |inst|.
+ static bool IsCopyable(opt::IRContext* ir_context, opt::Instruction* inst);
+
+ // Determines whether it is OK to insert a copy instruction before the given
+ // instruction.
+ static bool CanInsertCopyBefore(
+ const opt::BasicBlock::iterator& instruction_in_block);
+
private:
protobufs::TransformationCopyObject message_;
};