blob: 5e53961bf1b51ec449b1833ae6abb6ec7fc635b0 [file] [log] [blame]
// 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/transformation_add_function.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_message.h"
namespace spvtools {
namespace fuzz {
TransformationAddFunction::TransformationAddFunction(
const spvtools::fuzz::protobufs::TransformationAddFunction& message)
: message_(message) {}
TransformationAddFunction::TransformationAddFunction(
const std::vector<protobufs::Instruction>& instructions) {
for (auto& instruction : instructions) {
*message_.add_instruction() = instruction;
}
}
bool TransformationAddFunction::IsApplicable(
opt::IRContext* context,
const spvtools::fuzz::FactManager& /*unused*/) const {
// Because checking all the conditions for a function to be valid is a big
// job that the SPIR-V validator can already do, a "try it and see" approach
// is taken here.
// We first clone the current module, so that we can try adding the new
// function without risking wrecking |context|.
auto cloned_module = fuzzerutil::CloneIRContext(context);
// We try to add a function to the cloned module, which may fail if
// |message_.instruction| is not sufficiently well-formed.
if (!TryToAddFunction(cloned_module.get())) {
return false;
}
// Having managed to add the new function to the cloned module, we ascertain
// whether the cloned module is still valid. If it is, the transformation is
// applicable.
return fuzzerutil::IsValid(cloned_module.get());
}
void TransformationAddFunction::Apply(
opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
auto success = TryToAddFunction(context);
assert(success && "The function should be successfully added.");
(void)(success); // Keep release builds happy (otherwise they may complain
// that |success| is not used).
context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
protobufs::Transformation TransformationAddFunction::ToMessage() const {
protobufs::Transformation result;
*result.mutable_add_function() = message_;
return result;
}
bool TransformationAddFunction::TryToAddFunction(
opt::IRContext* context) const {
// This function returns false if |message_.instruction| was not well-formed
// enough to actually create a function and add it to |context|.
// A function must have at least some instructions.
if (message_.instruction().empty()) {
return false;
}
// A function must start with OpFunction.
auto function_begin = message_.instruction(0);
if (function_begin.opcode() != SpvOpFunction) {
return false;
}
// Make a function, headed by the OpFunction instruction.
std::unique_ptr<opt::Function> new_function = MakeUnique<opt::Function>(
InstructionFromMessage(context, function_begin));
// Keeps track of which instruction protobuf message we are currently
// considering.
uint32_t instruction_index = 1;
const auto num_instructions =
static_cast<uint32_t>(message_.instruction().size());
// Iterate through all function parameter instructions, adding parameters to
// the new function.
while (instruction_index < num_instructions &&
message_.instruction(instruction_index).opcode() ==
SpvOpFunctionParameter) {
new_function->AddParameter(InstructionFromMessage(
context, message_.instruction(instruction_index)));
instruction_index++;
}
// After the parameters, there needs to be a label.
if (instruction_index == num_instructions ||
message_.instruction(instruction_index).opcode() != SpvOpLabel) {
return false;
}
// Iterate through the instructions block by block until the end of the
// function is reached.
while (instruction_index < num_instructions &&
message_.instruction(instruction_index).opcode() != SpvOpFunctionEnd) {
// Invariant: we should always be at a label instruction at this point.
assert(message_.instruction(instruction_index).opcode() == SpvOpLabel);
// Make a basic block using the label instruction, with the new function
// as its parent.
std::unique_ptr<opt::BasicBlock> block =
MakeUnique<opt::BasicBlock>(InstructionFromMessage(
context, message_.instruction(instruction_index)));
block->SetParent(new_function.get());
// Consider successive instructions until we hit another label or the end
// of the function, adding each such instruction to the block.
instruction_index++;
while (instruction_index < num_instructions &&
message_.instruction(instruction_index).opcode() !=
SpvOpFunctionEnd &&
message_.instruction(instruction_index).opcode() != SpvOpLabel) {
block->AddInstruction(InstructionFromMessage(
context, message_.instruction(instruction_index)));
instruction_index++;
}
// Add the block to the new function.
new_function->AddBasicBlock(std::move(block));
}
// Having considered all the blocks, we should be at the last instruction and
// it needs to be OpFunctionEnd.
if (instruction_index != num_instructions - 1 ||
message_.instruction(instruction_index).opcode() != SpvOpFunctionEnd) {
return false;
}
// Set the function's final instruction, add the function to the module and
// report success.
new_function->SetFunctionEnd(
InstructionFromMessage(context, message_.instruction(instruction_index)));
context->AddFunction(std::move(new_function));
return true;
}
} // namespace fuzz
} // namespace spvtools