1 // Copyright (c) 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "source/fuzz/transformation_function_call.h"
16
17 #include "source/fuzz/call_graph.h"
18 #include "source/fuzz/fuzzer_util.h"
19 #include "source/fuzz/instruction_descriptor.h"
20
21 namespace spvtools {
22 namespace fuzz {
23
TransformationFunctionCall(const spvtools::fuzz::protobufs::TransformationFunctionCall & message)24 TransformationFunctionCall::TransformationFunctionCall(
25 const spvtools::fuzz::protobufs::TransformationFunctionCall& message)
26 : message_(message) {}
27
TransformationFunctionCall(uint32_t fresh_id,uint32_t callee_id,const std::vector<uint32_t> & argument_id,const protobufs::InstructionDescriptor & instruction_to_insert_before)28 TransformationFunctionCall::TransformationFunctionCall(
29 uint32_t fresh_id, uint32_t callee_id,
30 const std::vector<uint32_t>& argument_id,
31 const protobufs::InstructionDescriptor& instruction_to_insert_before) {
32 message_.set_fresh_id(fresh_id);
33 message_.set_callee_id(callee_id);
34 for (auto argument : argument_id) {
35 message_.add_argument_id(argument);
36 }
37 *message_.mutable_instruction_to_insert_before() =
38 instruction_to_insert_before;
39 }
40
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const41 bool TransformationFunctionCall::IsApplicable(
42 opt::IRContext* ir_context,
43 const TransformationContext& transformation_context) const {
44 // The result id must be fresh
45 if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
46 return false;
47 }
48
49 // The function must exist
50 auto callee_inst =
51 ir_context->get_def_use_mgr()->GetDef(message_.callee_id());
52 if (!callee_inst || callee_inst->opcode() != SpvOpFunction) {
53 return false;
54 }
55
56 // The function must not be an entry point
57 if (fuzzerutil::FunctionIsEntryPoint(ir_context, message_.callee_id())) {
58 return false;
59 }
60
61 auto callee_type_inst = ir_context->get_def_use_mgr()->GetDef(
62 callee_inst->GetSingleWordInOperand(1));
63 assert(callee_type_inst->opcode() == SpvOpTypeFunction &&
64 "Bad function type.");
65
66 // The number of expected function arguments must match the number of given
67 // arguments. The number of expected arguments is one less than the function
68 // type's number of input operands, as one operand is for the return type.
69 if (callee_type_inst->NumInOperands() - 1 !=
70 static_cast<uint32_t>(message_.argument_id().size())) {
71 return false;
72 }
73
74 // The instruction descriptor must refer to a position where it is valid to
75 // insert the call
76 auto insert_before =
77 FindInstruction(message_.instruction_to_insert_before(), ir_context);
78 if (!insert_before) {
79 return false;
80 }
81 if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpFunctionCall,
82 insert_before)) {
83 return false;
84 }
85
86 auto block = ir_context->get_instr_block(insert_before);
87 auto enclosing_function = block->GetParent();
88
89 // If the block is not dead, the function must be livesafe
90 bool block_is_dead =
91 transformation_context.GetFactManager()->BlockIsDead(block->id());
92 if (!block_is_dead &&
93 !transformation_context.GetFactManager()->FunctionIsLivesafe(
94 message_.callee_id())) {
95 return false;
96 }
97
98 // The ids must all match and have the right types and satisfy rules on
99 // pointers. If the block is not dead, pointers must be arbitrary.
100 for (uint32_t arg_index = 0;
101 arg_index < static_cast<uint32_t>(message_.argument_id().size());
102 arg_index++) {
103 opt::Instruction* arg_inst =
104 ir_context->get_def_use_mgr()->GetDef(message_.argument_id(arg_index));
105 if (!arg_inst) {
106 // The given argument does not correspond to an instruction.
107 return false;
108 }
109 if (!arg_inst->type_id()) {
110 // The given argument does not have a type; it is thus not suitable.
111 }
112 if (arg_inst->type_id() !=
113 callee_type_inst->GetSingleWordInOperand(arg_index + 1)) {
114 // Argument type mismatch.
115 return false;
116 }
117 opt::Instruction* arg_type_inst =
118 ir_context->get_def_use_mgr()->GetDef(arg_inst->type_id());
119 if (arg_type_inst->opcode() == SpvOpTypePointer) {
120 switch (arg_inst->opcode()) {
121 case SpvOpFunctionParameter:
122 case SpvOpVariable:
123 // These are OK
124 break;
125 default:
126 // Other pointer ids cannot be passed as parameters
127 return false;
128 }
129 if (!block_is_dead &&
130 !transformation_context.GetFactManager()->PointeeValueIsIrrelevant(
131 arg_inst->result_id())) {
132 // This is not a dead block, so pointer parameters passed to the called
133 // function might really have their contents modified. We thus require
134 // such pointers to be to arbitrary-valued variables, which this is not.
135 return false;
136 }
137 }
138
139 // The argument id needs to be available (according to dominance rules) at
140 // the point where the call will occur.
141 if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
142 arg_inst->result_id())) {
143 return false;
144 }
145 }
146
147 // Introducing the call must not lead to recursion.
148 if (message_.callee_id() == enclosing_function->result_id()) {
149 // This would be direct recursion.
150 return false;
151 }
152 // Ensure the call would not lead to indirect recursion.
153 return !CallGraph(ir_context)
154 .GetIndirectCallees(message_.callee_id())
155 .count(block->GetParent()->result_id());
156 }
157
Apply(opt::IRContext * ir_context,TransformationContext *) const158 void TransformationFunctionCall::Apply(
159 opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
160 // Update the module's bound to reflect the fresh id for the result of the
161 // function call.
162 fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
163 // Get the return type of the function being called.
164 uint32_t return_type =
165 ir_context->get_def_use_mgr()->GetDef(message_.callee_id())->type_id();
166 // Populate the operands to the call instruction, with the function id and the
167 // arguments.
168 opt::Instruction::OperandList operands;
169 operands.push_back({SPV_OPERAND_TYPE_ID, {message_.callee_id()}});
170 for (auto arg : message_.argument_id()) {
171 operands.push_back({SPV_OPERAND_TYPE_ID, {arg}});
172 }
173 // Insert the function call before the instruction specified in the message.
174 FindInstruction(message_.instruction_to_insert_before(), ir_context)
175 ->InsertBefore(MakeUnique<opt::Instruction>(
176 ir_context, SpvOpFunctionCall, return_type, message_.fresh_id(),
177 operands));
178 // Invalidate all analyses since we have changed the module.
179 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
180 }
181
ToMessage() const182 protobufs::Transformation TransformationFunctionCall::ToMessage() const {
183 protobufs::Transformation result;
184 *result.mutable_function_call() = message_;
185 return result;
186 }
187
GetFreshIds() const188 std::unordered_set<uint32_t> TransformationFunctionCall::GetFreshIds() const {
189 return {message_.fresh_id()};
190 }
191
192 } // namespace fuzz
193 } // namespace spvtools
194