aboutsummaryrefslogtreecommitdiff
path: root/source/fuzz/transformation_function_call.cpp
blob: 0f88ce51adc87c0d51f144b7cf13ae809fa9670f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Copyright (c) 2020 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_function_call.h"

#include "source/fuzz/call_graph.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"

namespace spvtools {
namespace fuzz {

TransformationFunctionCall::TransformationFunctionCall(
    protobufs::TransformationFunctionCall message)
    : message_(std::move(message)) {}

TransformationFunctionCall::TransformationFunctionCall(
    uint32_t fresh_id, uint32_t callee_id,
    const std::vector<uint32_t>& argument_id,
    const protobufs::InstructionDescriptor& instruction_to_insert_before) {
  message_.set_fresh_id(fresh_id);
  message_.set_callee_id(callee_id);
  for (auto argument : argument_id) {
    message_.add_argument_id(argument);
  }
  *message_.mutable_instruction_to_insert_before() =
      instruction_to_insert_before;
}

bool TransformationFunctionCall::IsApplicable(
    opt::IRContext* ir_context,
    const TransformationContext& transformation_context) const {
  // The result id must be fresh
  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
    return false;
  }

  // The function must exist
  auto callee_inst =
      ir_context->get_def_use_mgr()->GetDef(message_.callee_id());
  if (!callee_inst || callee_inst->opcode() != SpvOpFunction) {
    return false;
  }

  // The function must not be an entry point
  if (fuzzerutil::FunctionIsEntryPoint(ir_context, message_.callee_id())) {
    return false;
  }

  auto callee_type_inst = ir_context->get_def_use_mgr()->GetDef(
      callee_inst->GetSingleWordInOperand(1));
  assert(callee_type_inst->opcode() == SpvOpTypeFunction &&
         "Bad function type.");

  // The number of expected function arguments must match the number of given
  // arguments.  The number of expected arguments is one less than the function
  // type's number of input operands, as one operand is for the return type.
  if (callee_type_inst->NumInOperands() - 1 !=
      static_cast<uint32_t>(message_.argument_id().size())) {
    return false;
  }

  // The instruction descriptor must refer to a position where it is valid to
  // insert the call
  auto insert_before =
      FindInstruction(message_.instruction_to_insert_before(), ir_context);
  if (!insert_before) {
    return false;
  }
  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpFunctionCall,
                                                    insert_before)) {
    return false;
  }

  auto block = ir_context->get_instr_block(insert_before);
  auto enclosing_function = block->GetParent();

  // If the block is not dead, the function must be livesafe
  bool block_is_dead =
      transformation_context.GetFactManager()->BlockIsDead(block->id());
  if (!block_is_dead &&
      !transformation_context.GetFactManager()->FunctionIsLivesafe(
          message_.callee_id())) {
    return false;
  }

  // The ids must all match and have the right types and satisfy rules on
  // pointers.  If the block is not dead, pointers must be arbitrary.
  for (uint32_t arg_index = 0;
       arg_index < static_cast<uint32_t>(message_.argument_id().size());
       arg_index++) {
    opt::Instruction* arg_inst =
        ir_context->get_def_use_mgr()->GetDef(message_.argument_id(arg_index));
    if (!arg_inst) {
      // The given argument does not correspond to an instruction.
      return false;
    }
    if (!arg_inst->type_id()) {
      // The given argument does not have a type; it is thus not suitable.
    }
    if (arg_inst->type_id() !=
        callee_type_inst->GetSingleWordInOperand(arg_index + 1)) {
      // Argument type mismatch.
      return false;
    }
    opt::Instruction* arg_type_inst =
        ir_context->get_def_use_mgr()->GetDef(arg_inst->type_id());
    if (arg_type_inst->opcode() == SpvOpTypePointer) {
      switch (arg_inst->opcode()) {
        case SpvOpFunctionParameter:
        case SpvOpVariable:
          // These are OK
          break;
        default:
          // Other pointer ids cannot be passed as parameters
          return false;
      }
      if (!block_is_dead &&
          !transformation_context.GetFactManager()->PointeeValueIsIrrelevant(
              arg_inst->result_id())) {
        // This is not a dead block, so pointer parameters passed to the called
        // function might really have their contents modified. We thus require
        // such pointers to be to arbitrary-valued variables, which this is not.
        return false;
      }
    }

    // The argument id needs to be available (according to dominance rules) at
    // the point where the call will occur.
    if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
                                                    arg_inst->result_id())) {
      return false;
    }
  }

  // Introducing the call must not lead to recursion.
  if (message_.callee_id() == enclosing_function->result_id()) {
    // This would be direct recursion.
    return false;
  }
  // Ensure the call would not lead to indirect recursion.
  return !CallGraph(ir_context)
              .GetIndirectCallees(message_.callee_id())
              .count(block->GetParent()->result_id());
}

void TransformationFunctionCall::Apply(
    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
  // Update the module's bound to reflect the fresh id for the result of the
  // function call.
  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
  // Get the return type of the function being called.
  uint32_t return_type =
      ir_context->get_def_use_mgr()->GetDef(message_.callee_id())->type_id();
  // Populate the operands to the call instruction, with the function id and the
  // arguments.
  opt::Instruction::OperandList operands;
  operands.push_back({SPV_OPERAND_TYPE_ID, {message_.callee_id()}});
  for (auto arg : message_.argument_id()) {
    operands.push_back({SPV_OPERAND_TYPE_ID, {arg}});
  }
  // Insert the function call before the instruction specified in the message.
  FindInstruction(message_.instruction_to_insert_before(), ir_context)
      ->InsertBefore(MakeUnique<opt::Instruction>(
          ir_context, SpvOpFunctionCall, return_type, message_.fresh_id(),
          operands));
  // Invalidate all analyses since we have changed the module.
  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}

protobufs::Transformation TransformationFunctionCall::ToMessage() const {
  protobufs::Transformation result;
  *result.mutable_function_call() = message_;
  return result;
}

std::unordered_set<uint32_t> TransformationFunctionCall::GetFreshIds() const {
  return {message_.fresh_id()};
}

}  // namespace fuzz
}  // namespace spvtools