|
| 1 | +#include "builtin/compiled_code.hpp" |
| 2 | +#include "builtin/exception.hpp" |
| 3 | +#include "builtin/fixnum.hpp" |
| 4 | +#include "builtin/iseq.hpp" |
| 5 | +#include "builtin/string.hpp" |
| 6 | +#include "builtin/symbol.hpp" |
| 7 | +#include "builtin/tuple.hpp" |
| 8 | + |
| 9 | +#include "bytecode_verifier.hpp" |
| 10 | +#include "configuration.hpp" |
| 11 | +#include "machine_code.hpp" |
| 12 | +#include "object_utils.hpp" |
| 13 | + |
| 14 | +#include "instruments/timing.hpp" |
| 15 | + |
| 16 | +namespace rubinius { |
| 17 | + BytecodeVerifier::BytecodeVerifier(CompiledCode* code) |
| 18 | + : method_(code) |
| 19 | + , ops_(NULL) |
| 20 | + , total_(0) |
| 21 | + , max_stack_allowed_(0) |
| 22 | + , max_stack_seen_(0) |
| 23 | + , stack_(0) |
| 24 | + , locals_(0) |
| 25 | + , max_stack_local_(-1) |
| 26 | + {} |
| 27 | + |
| 28 | + BytecodeVerifier::~BytecodeVerifier() { |
| 29 | + if(stack_) delete[] stack_; |
| 30 | + } |
| 31 | + |
| 32 | + void BytecodeVerifier::fail(STATE, const char* reason, int ip) { |
| 33 | + Exception::bytecode_error(state, method_, ip, reason); |
| 34 | + } |
| 35 | + |
| 36 | + void BytecodeVerifier::verify_width(STATE, int ip) { |
| 37 | + if(ip >= total_) { |
| 38 | + fail(state, "truncated instruction sequence", ip); |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + Fixnum* BytecodeVerifier::verify_opcode(STATE, int ip) { |
| 43 | + verify_width(state, ip); |
| 44 | + |
| 45 | + if(Fixnum* fix = try_as<Fixnum>(ops_->at(ip))) { |
| 46 | + return fix; |
| 47 | + } else { |
| 48 | + fail(state, "instruction is not a Fixnum", ip); |
| 49 | + } |
| 50 | + } |
| 51 | + |
| 52 | + Fixnum* BytecodeVerifier::verify_argument(STATE, int ip) { |
| 53 | + verify_width(state, ip); |
| 54 | + |
| 55 | + if(Fixnum* fix = try_as<Fixnum>(ops_->at(ip))) { |
| 56 | + return fix; |
| 57 | + } else { |
| 58 | + fail(state, "instruction argument is not a Fixnum", ip); |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + Object* BytecodeVerifier::verify_object(STATE, int index, int ip) { |
| 63 | + if(Object* obj = try_as<Object>(method_->literals()->at(index))) { |
| 64 | + return obj; |
| 65 | + } else { |
| 66 | + fail(state, "instruction argument is not an Object", ip); |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + Symbol* BytecodeVerifier::verify_symbol(STATE, int index, int ip) { |
| 71 | + if(Symbol* sym = try_as<Symbol>(method_->literals()->at(index))) { |
| 72 | + return sym; |
| 73 | + } else { |
| 74 | + fail(state, "instruction argument is not a Symbol", ip); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + Object* BytecodeVerifier::verify_symbol_or_nil(STATE, int index, int ip) { |
| 79 | + Object* val = method_->literals()->at(index); |
| 80 | + |
| 81 | + if(val->nil_p()) { |
| 82 | + return val; |
| 83 | + } else if(Symbol* sym = try_as<Symbol>(val)) { |
| 84 | + return sym; |
| 85 | + } else { |
| 86 | + fail(state, "instruction argument is not a Symbol or nil", ip); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + Object* BytecodeVerifier::verify_code(STATE, int index, int ip) { |
| 91 | + if(CompiledCode* code = try_as<CompiledCode>(method_->literals()->at(index))) { |
| 92 | + return force_as<Object>(code); |
| 93 | + } else if(String* str = try_as<String>(method_->literals()->at(index))) { |
| 94 | + return force_as<Object>(str); |
| 95 | + } else { |
| 96 | + fail(state, "instruction argument is not a String or CompiledCode", ip); |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + void BytecodeVerifier::verify(STATE) { |
| 101 | + // Do this setup here instead of the constructor so we can do |
| 102 | + // some validation of the CompiledCode's fields we read them. |
| 103 | + |
| 104 | + // Double check the method itself, since it might be a nil |
| 105 | + if(!kind_of<CompiledCode>(method_)) { |
| 106 | + fail(state, "not passed a CompiledCode object", -1); |
| 107 | + } |
| 108 | + |
| 109 | + if(Fixnum* fix = try_as<Fixnum>(method_->local_count())) { |
| 110 | + locals_ = fix->to_native(); |
| 111 | + } else { |
| 112 | + fail(state, "local_count is not a Fixnum", -1); |
| 113 | + } |
| 114 | + |
| 115 | + InstructionSequence* iseq = try_as<InstructionSequence>(method_->iseq()); |
| 116 | + if(!iseq) { |
| 117 | + fail(state, "iseq is not an InstructionSequence", -1); |
| 118 | + } |
| 119 | + |
| 120 | + if(Tuple* tup = try_as<Tuple>(iseq->opcodes())) { |
| 121 | + ops_ = tup; |
| 122 | + } else { |
| 123 | + fail(state, "opcodes is not a Tuple", -1); |
| 124 | + } |
| 125 | + |
| 126 | + if(Fixnum* fix = try_as<Fixnum>(method_->stack_size())) { |
| 127 | + max_stack_allowed_ = fix->to_native(); |
| 128 | + } else { |
| 129 | + fail(state, "stack_size is not a Fixnum", -1); |
| 130 | + } |
| 131 | + |
| 132 | + if(Fixnum* fix = try_as<Fixnum>(method_->splat())) { |
| 133 | + if(fix->to_native() >= locals_) { |
| 134 | + fail(state, "invalid splat position", -1); |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + Fixnum* tot = try_as<Fixnum>(method_->total_args()); |
| 139 | + if(!tot) { |
| 140 | + fail(state, "total_args is not a Fixnum", -1); |
| 141 | + } |
| 142 | + Fixnum* req = try_as<Fixnum>(method_->required_args()); |
| 143 | + if(!req) { |
| 144 | + fail(state, "required_args is not a Fixnum", -1); |
| 145 | + } |
| 146 | + Fixnum* post = try_as<Fixnum>(method_->post_args()); |
| 147 | + if(!post) { |
| 148 | + fail(state, "post_args is not a Fixnum", -1); |
| 149 | + } |
| 150 | + |
| 151 | + if(tot->to_native() > locals_) { |
| 152 | + fail(state, "more arguments than local slots", -1); |
| 153 | + } |
| 154 | + |
| 155 | + if(req->to_native() > tot->to_native()) { |
| 156 | + fail(state, "more required arguments than total", -1); |
| 157 | + } |
| 158 | + |
| 159 | + if(post->to_native() > req->to_native()) { |
| 160 | + fail(state, "more post arguments than required", -1); |
| 161 | + } |
| 162 | + |
| 163 | + if(post->to_native() > tot->to_native()) { |
| 164 | + fail(state, "more post arguments than total", -1); |
| 165 | + } |
| 166 | + |
| 167 | + total_ = ops_->num_fields(); |
| 168 | + stack_ = new int32_t[total_]; |
| 169 | + |
| 170 | + for(native_int i = 0; i < total_; i++) { |
| 171 | + stack_[i] = -1; |
| 172 | + } |
| 173 | + |
| 174 | + std::list<Section> ips; |
| 175 | + ips.push_back(Section(0, 0)); |
| 176 | + |
| 177 | + while(!ips.empty()) { |
| 178 | + Section& section = ips.front(); |
| 179 | + |
| 180 | + int ip = section.ip; |
| 181 | + int sp = section.sp; |
| 182 | + |
| 183 | + ips.pop_front(); |
| 184 | + |
| 185 | + verify_from(state, sp, ip, ips); |
| 186 | + } |
| 187 | + |
| 188 | + // Now, check there is a enough space for the stack locals. |
| 189 | + if(max_stack_seen_ + max_stack_local_ >= max_stack_allowed_) { |
| 190 | + fail(state, "not enough space for stack locals", -1); |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + namespace { |
| 195 | +#include "gen/instruction_effects.hpp" |
| 196 | + } |
| 197 | + |
| 198 | + void BytecodeVerifier::verify_from(STATE, int sp, int ip, std::list<Section>& ips) { |
| 199 | + int insn_ip = ip; |
| 200 | + |
| 201 | + if(sp < 0) { |
| 202 | + fail(state, "stack underflow error", ip); |
| 203 | + } |
| 204 | + |
| 205 | + for(;;) { |
| 206 | + int old_sp = stack_[ip]; |
| 207 | + if(old_sp < 0) { |
| 208 | + stack_[ip] = sp; |
| 209 | + } else if(old_sp != sp) { |
| 210 | + fail(state, "inconsistent stack depth", ip); |
| 211 | + } else { |
| 212 | + // Already been here and stack is consistent, done. |
| 213 | + return; |
| 214 | + } |
| 215 | + |
| 216 | + opcode op = static_cast<opcode>(verify_opcode(state, ip++)->to_native()); |
| 217 | + size_t width = InstructionSequence::instruction_width(op); |
| 218 | + |
| 219 | + opcode arg1 = 0, arg2 = 0, arg3 = 0; |
| 220 | + |
| 221 | + switch(width) { |
| 222 | + case 1: |
| 223 | + break; |
| 224 | + case 2: |
| 225 | + arg1 = static_cast<opcode>(verify_argument(state, ip++)->to_native()); |
| 226 | + break; |
| 227 | + case 3: |
| 228 | + arg1 = static_cast<opcode>(verify_argument(state, ip++)->to_native()); |
| 229 | + arg2 = static_cast<opcode>(verify_argument(state, ip++)->to_native()); |
| 230 | + break; |
| 231 | + case 4: |
| 232 | + arg1 = static_cast<opcode>(verify_argument(state, ip++)->to_native()); |
| 233 | + arg2 = static_cast<opcode>(verify_argument(state, ip++)->to_native()); |
| 234 | + arg3 = static_cast<opcode>(verify_argument(state, ip++)->to_native()); |
| 235 | + break; |
| 236 | + default: |
| 237 | + fail(state, "invalid instruction", insn_ip); |
| 238 | + } |
| 239 | + |
| 240 | + switch(op) { |
| 241 | + case InstructionSequence::insn_push_literal: |
| 242 | + case InstructionSequence::insn_push_memo: |
| 243 | + verify_object(state, arg1, insn_ip); |
| 244 | + break; |
| 245 | + case InstructionSequence::insn_create_block: |
| 246 | + verify_code(state, arg1, insn_ip); |
| 247 | + break; |
| 248 | + case InstructionSequence::insn_send_vcall: |
| 249 | + case InstructionSequence::insn_send_method: |
| 250 | + case InstructionSequence::insn_send_stack: |
| 251 | + case InstructionSequence::insn_send_stack_with_block: |
| 252 | + case InstructionSequence::insn_send_stack_with_splat: |
| 253 | + case InstructionSequence::insn_check_serial: |
| 254 | + case InstructionSequence::insn_check_serial_private: |
| 255 | + case InstructionSequence::insn_invoke_primitive: |
| 256 | + case InstructionSequence::insn_set_ivar: |
| 257 | + case InstructionSequence::insn_push_ivar: |
| 258 | + case InstructionSequence::insn_set_const: |
| 259 | + case InstructionSequence::insn_set_const_at: |
| 260 | + case InstructionSequence::insn_object_to_s: |
| 261 | + case InstructionSequence::insn_push_const: |
| 262 | + case InstructionSequence::insn_find_const: |
| 263 | + case InstructionSequence::insn_zsuper: |
| 264 | + verify_symbol(state, arg1, insn_ip); |
| 265 | + break; |
| 266 | + case InstructionSequence::insn_send_super_stack_with_block: |
| 267 | + case InstructionSequence::insn_send_super_stack_with_splat: |
| 268 | + verify_symbol_or_nil(state, arg1, insn_ip); |
| 269 | + break; |
| 270 | + } |
| 271 | + |
| 272 | + int read = 0, write = 0; |
| 273 | + |
| 274 | + int effect = stack_difference(op, arg1, arg2, arg3, &read, &write); |
| 275 | + |
| 276 | + // Check for under read |
| 277 | + if(sp - read < 0) { |
| 278 | + fail(state, "stack underflow on read", insn_ip); |
| 279 | + } |
| 280 | + |
| 281 | + // Apply the total effect to propagate it. |
| 282 | + sp += effect; |
| 283 | + if(sp < 0) { |
| 284 | + fail(state, "stack underflow on effect", insn_ip); |
| 285 | + } |
| 286 | + |
| 287 | + // Make sure we don't use more than the declared stack size. |
| 288 | + if(sp > max_stack_allowed_) { |
| 289 | + fail(state, "stack overflow", insn_ip); |
| 290 | + } |
| 291 | + |
| 292 | + // Keep track of the max stack depth seen |
| 293 | + if(sp > max_stack_seen_) max_stack_seen_ = sp; |
| 294 | + |
| 295 | + switch(op) { |
| 296 | + case InstructionSequence::insn_push_local: |
| 297 | + case InstructionSequence::insn_set_local: |
| 298 | + if((native_int)arg1 < 0 || (native_int)arg1 >= locals_) { |
| 299 | + fail(state, "invalid local variable access", insn_ip); |
| 300 | + } |
| 301 | + break; |
| 302 | + case InstructionSequence::insn_goto: |
| 303 | + if((native_int)arg1 < 0 || (native_int)arg1 >= total_) { |
| 304 | + fail(state, "invalid goto location", insn_ip); |
| 305 | + } |
| 306 | + |
| 307 | + // Only handle forward branches. |
| 308 | + if((int)arg1 > ip) { |
| 309 | + ip = (int)arg1; |
| 310 | + } else { |
| 311 | + return; |
| 312 | + } |
| 313 | + break; |
| 314 | + case InstructionSequence::insn_push_stack_local: |
| 315 | + case InstructionSequence::insn_set_stack_local: |
| 316 | + if((int)arg1 > max_stack_local_) { |
| 317 | + max_stack_local_ = (int)arg1; |
| 318 | + } |
| 319 | + break; |
| 320 | + case InstructionSequence::insn_goto_if_false: |
| 321 | + case InstructionSequence::insn_goto_if_true: |
| 322 | + case InstructionSequence::insn_setup_unwind: |
| 323 | + if((native_int)arg1 < 0 || (native_int)arg1 >= total_) { |
| 324 | + fail(state, "invalid goto location", insn_ip); |
| 325 | + } |
| 326 | + |
| 327 | + if((int)arg1 > ip) { |
| 328 | + ips.push_back(Section(sp, arg1)); |
| 329 | + } |
| 330 | + |
| 331 | + break; |
| 332 | + case InstructionSequence::insn_ret: |
| 333 | + case InstructionSequence::insn_raise_exc: |
| 334 | + case InstructionSequence::insn_raise_return: |
| 335 | + case InstructionSequence::insn_ensure_return: |
| 336 | + case InstructionSequence::insn_raise_break: |
| 337 | + case InstructionSequence::insn_reraise: |
| 338 | + return; |
| 339 | + } |
| 340 | + |
| 341 | + // Detect falling off the end of the stream |
| 342 | + if(ip >= total_) { |
| 343 | + fail(state, "unterminated instruction sequence", insn_ip); |
| 344 | + } |
| 345 | + } |
| 346 | + } |
| 347 | +} |
0 commit comments