/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "avmplus.h" //#define DOPROF #include "../vprof/vprof.h" namespace avmplus { #ifdef VMCFG_WORDCODE inline WordOpcode wordCode(AbcOpcode opcode) { return (WordOpcode)opcodeInfo[opcode].wordCode; } #endif Verifier::Verifier(MethodInfo* info, MethodSignaturep ms, Toplevel* toplevel, AbcEnv* abc_env #ifdef AVMPLUS_VERBOSE , bool secondTry #endif ) : tryFrom(NULL), tryTo(NULL) , info(info), ms(ms) , worklist(NULL) , blockStates(NULL) , handlerIsReachable(false) { #ifdef AVMPLUS_VERBOSE this->secondTry = secondTry; #endif this->core = info->pool()->core; this->pool = info->pool(); this->toplevel = toplevel; this->abc_env = abc_env; state = NULL; #ifdef VMCFG_RESTARG_OPTIMIZATION restArgAnalyzer.init(core, info, ms->frame_size()); #endif #ifdef AVMPLUS_VERBOSE verbose = pool->isVerbose(VB_verify, info); #endif pool->initPrecomputedMultinames(); } Verifier::~Verifier() { mmfx_delete( this->state ); if (blockStates) { for(int i = 0, n=blockStates->map.length(); i < n; i++) mmfx_delete( blockStates->map.at(i) ); delete blockStates; } } bool Verifier::hasReachableExceptions() const { // Valid during code generation only. AvmAssert(emitPass); return handlerIsReachable; } void Verifier::parseBodyHeader() { // note: reading of max_stack, etc (and validating the values) // is handled by MethodInfo::resolveSignature. const uint8_t* pos = info->abc_body_pos(); AvmCore::skipU32(pos, 4); code_length = AvmCore::readU32(pos); code_pos = pos; pos += code_length; } // ScopeWriter implements the scope-type-capturing functionality // for OP_newfunction and OP_newclass, as well as error checking // to handle illegally capturing incompatible scopes for the same // function. All other opcodes are ignored. Expected to be used // in the verifier's phase 2 when each bytecode is only visited once. class ScopeWriter: public NullWriter { MethodInfo* info; Toplevel* toplevel; Verifier *verifier; public: ScopeWriter(CodeWriter* coder, MethodInfo* info, Toplevel* toplevel, Verifier* verifier) : NullWriter(coder), info(info), toplevel(toplevel), verifier(verifier) {} void write(const FrameState* state, const uint8_t* pc, AbcOpcode opcode, Traits* type) { if (opcode == OP_newactivation) { // Capture activation scope the first time the method is verified. const ScopeTypeChain *scope = info->activationScope(); if (scope == NULL) { AvmAssert(type == info->activationTraits()); MMgc::GC* gc = info->pool()->core->GetGC(); scope = info->declaringScope()->cloneWithNewTraits(gc, type); type->setDeclaringScopes(scope); info->init_activationScope(scope); } } coder->write(state, pc, opcode, type); } void writeOp1(const FrameState* state, const uint8_t* pc, AbcOpcode opcode, uint32_t imm30, Traits* type) { if (opcode == OP_newfunction) { PoolObject* pool = info->pool(); AvmCore* core = pool->core; const ScopeTypeChain* scope = info->declaringScope(); MethodInfo* f = pool->getMethodInfo(imm30); Traits* ftraits = core->traits.function_itraits; const ScopeTypeChain* fscope = ScopeTypeChain::create(core->GetGC(), ftraits, scope, state, NULL, NULL); // Duplicate function definitions aren't strictly legal, but can occur // in otherwise "well formed" ABC due to old, buggy versions of ASC. // Specifically, code of the form // // public function simpleTest():void // { // var f:Function = function():void { } // f(); // f = function functwo (x):void { } // f(8); // } // // could cause the second interior function ("functwo") to include a bogus, unused OP_newfunction // call to itself inside the body of functwo. This caused the scope to get reinitialized // and generally caused havok. However, we want to allow existing code of this form to continue // to work, so check to see if we already have a declaringScope, and if so, require that // it match this one. const ScopeTypeChain* curScope = f->declaringScope(); if (curScope != NULL) { if (!curScope->equals(fscope)) { // if info->method_id() == imm30, f == info, and therefore // curScope == scope -- don't redefine, don't fail verification, // just accept it. see https://bugzilla.mozilla.org/show_bug.cgi?id=544370 if (info->method_id() != int32_t(imm30)) toplevel->throwVerifyError(kCorruptABCError); AvmAssert(type == core->traits.function_itraits); if (type != curScope->traits()) { #ifdef AVMPLUS_VERBOSE core->console << "ERROR in ScopeChain::create, mismatched traits " << type << " " << curScope->traits() << "\n"; #endif toplevel->throwVerifyError(kCorruptABCError); } AvmAssert(curScope->equals(scope)); } AvmAssert(f->isResolved()); } else { f->makeIntoPrototypeFunction(toplevel, fscope); } #ifdef AVMPLUS_VERBOSE if (verifier->verbose) verifier->printScope("function-scope", fscope); #endif } else if (opcode == OP_newclass) { PoolObject* pool = info->pool(); AvmCore* core = pool->core; const ScopeTypeChain* scope = info->declaringScope(); Traits* ctraits = type; // the actual result type will be the static traits of the new class. // make sure the traits came from this pool. they have to because // the class_index operand is resolved from the current pool. AvmAssert(ctraits->pool == pool); Traits *itraits = ctraits->itraits; // add a type constraint for the "this" scope of static methods const ScopeTypeChain* cscope = ScopeTypeChain::create(core->GetGC(), ctraits, scope, state, NULL, ctraits); if (state->scopeDepth > 0) { // innermost scope must be the base class object or else createInstance() // will malfunction because it will use the wrong [base] class object to // construct the instance. See ScriptObject::createInstance() Traits* baseCTraits = state->scopeValue(state->scopeDepth-1).traits; if (!baseCTraits || baseCTraits->itraits != itraits->base) verifier->verifyFailed(kCorruptABCError); } // add a type constraint for the "this" scope of instance methods const ScopeTypeChain* iscope = ScopeTypeChain::create(core->GetGC(), itraits, cscope, NULL, ctraits, itraits); ctraits->resolveSignatures(toplevel); itraits->resolveSignatures(toplevel); const ScopeTypeChain *cur_cscope = ctraits->declaringScope(); const ScopeTypeChain *cur_iscope = itraits->declaringScope(); if (!cur_cscope) { // first time we have seen this class, capture scope types ctraits->setDeclaringScopes(cscope); itraits->setDeclaringScopes(iscope); } else { // we have captured a scope already for this class. it better match! if (!cur_cscope->equals(cscope) || !cur_iscope || !cur_iscope->equals(iscope)) { toplevel->throwVerifyError(kCorruptABCError); } // use the old ScopeTypeChains, discard the new ones cscope = cur_cscope; iscope = cur_iscope; } #ifdef AVMPLUS_VERBOSE if (verifier->verbose) verifier->printScope("class-scope", cscope); #endif } coder->writeOp1(state, pc, opcode, imm30, type); } }; #ifdef VMCFG_RESTARG_OPTIMIZATION // Most interesting things happen in init() RestArgAnalyzer::RestArgAnalyzer() : NullWriter(NULL) , optimize(true) , core(NULL) , info(NULL) , pool(NULL) , frameSize(0) , restVar(0) , isRestArray(NULL) { } void RestArgAnalyzer::init(AvmCore* _core, MethodInfo* _info, uint32_t _frameSize) { core = _core; info = _info; pool = _info->pool(); frameSize = _frameSize; if (_info->needRest() || (_info->needArguments() && _info->onlyUntypedParameters())) restVar = _info->getMethodSignature()->param_count() + 1; else optimize = false; #ifdef DEBUGGER if (core->debugger() != NULL) optimize = false; #endif if (optimize) { isRestArray = mmfx_new_array(bool, frameSize); VMPI_memset(isRestArray, 0, frameSize*sizeof(bool)); } } RestArgAnalyzer::~RestArgAnalyzer() { mmfx_delete_array(isRestArray); isRestArray = NULL; } CodeWriter* RestArgAnalyzer::hookup(CodeWriter* next, bool pass2) { if (optimize) { if (pass2) info->setLazyRest(); coder = next; return this; } return next; } // Called when the analysis fails (but not during setup). Factored out to // allow easy breakpointing, etc. inline void RestArgAnalyzer::fail() { optimize = false; } void RestArgAnalyzer::endBlock() { if (!optimize) return; for ( uint32_t i=0 ; i < frameSize ; i++ ) if (isRestArray[i]) fail(); } void RestArgAnalyzer::write(const FrameState* state, const uint8_t* pc, AbcOpcode opcode, Traits *type) { if (optimize) operate(state, pc, opcode); coder->write(state, pc, opcode, type); } void RestArgAnalyzer::writeOp1(const FrameState* state, const uint8_t *pc, AbcOpcode opcode, uint32_t opd1, Traits* type) { if (optimize) operate(state, pc, opcode); coder->writeOp1(state, pc, opcode, opd1, type); } void RestArgAnalyzer::writeOp2(const FrameState* state, const uint8_t *pc, AbcOpcode opcode, uint32_t opd1, uint32_t opd2, Traits* type) { if (optimize) operate(state, pc, opcode); coder->writeOp2(state, pc, opcode, opd1, opd2, type); } void RestArgAnalyzer::writeMethodCall(const FrameState* state, const uint8_t *pc, AbcOpcode opcode, MethodInfo* m, uintptr_t disp_id, uint32_t argc, Traits* type) { if (optimize) operate(state, pc, opcode); coder->writeMethodCall(state, pc, opcode, m, disp_id, argc, type); } bool RestArgAnalyzer::getProperty(const FrameState* state, const Multiname& multiname, int obj_offset) { if (!optimize) return false; uint32_t sp = state->sp(); // Be sure the arguments to run-time names aren't the rest array. uint32_t numprops = (multiname.isRtname() != 0) + (multiname.isRtns() != 0); if (numprops > 0) { if (isRestArray[sp]) fail(); } if (numprops > 1) { if (isRestArray[sp-1]) fail(); } if (!optimize) return false; if (isRestArray[sp-obj_offset+1]) { isRestArray[sp-obj_offset+1] = false; if (multiname.isRtname() && multiname.containsAnyPublicNamespace()) { // OP_restarg candidate // containsAnyPublicNamespace should imply n==2 - the multiname must have ns or nsset AvmAssert(obj_offset == 2); } else if (multiname.getName() == core->klength && multiname.containsAnyPublicNamespace()) { // OP_restargc candidate AvmAssert(obj_offset == 1); } else { fail(); } return optimize; } else { return false; } } void RestArgAnalyzer::writeOpcodeVerified(const FrameState* state, const uint8_t *pc, AbcOpcode opcode) { if (optimize) { switch (opcode) { case OP_getlocal: { uint32_t imm30=0, imm30b=0; int32_t imm8=0, imm24=0; AvmCore::readOperands(pc, imm30, imm24, imm30b, imm8); if (restVar == imm30) isRestArray[state->sp()] = true; break; } case OP_getlocal0: case OP_getlocal1: case OP_getlocal2: case OP_getlocal3: { if (restVar == uint32_t(opcode-OP_getlocal0)) isRestArray[state->sp()] = true; break; } case OP_iflt: case OP_ifle: case OP_ifnlt: case OP_ifnle: case OP_ifgt: case OP_ifge: case OP_ifngt: case OP_ifnge: case OP_ifeq: case OP_ifstricteq: case OP_ifne: case OP_ifstrictne: case OP_iftrue: case OP_iffalse: case OP_jump: case OP_lookupswitch: case OP_throw: case OP_returnvalue: case OP_returnvoid: endBlock(); break; default: break; } } coder->writeOpcodeVerified(state, pc, opcode); } // This would be less painful if we had a table stating the number of operands read // by most instructions. void RestArgAnalyzer::operate(const FrameState* state, const uint8_t *pc, AbcOpcode opcode) { uint32_t imm30=0, imm30b=0; int32_t imm8=0, imm24=0; uint32_t numprops = 0; AvmCore::readOperands(pc, imm30, imm24, imm30b, imm8); switch (opcode) { // Ignored because they are generated post-analysis when the analysis succeeds. case OP_restarg: case OP_restargc: break; // Handled in writeOpcodeVerified case OP_getlocal: case OP_getlocal0: case OP_getlocal1: case OP_getlocal2: case OP_getlocal3: break; // Handled in getProperty case OP_getproperty: break; // Op0 case OP_bkpt: case OP_bkptline: case OP_timestamp: case OP_nop: case OP_pushbyte: case OP_pushshort: case OP_pushtrue: case OP_pushfalse: case OP_pushnan: case OP_pushnull: case OP_pushundefined: case OP_pushstring: case OP_pushint: case OP_pushuint: case OP_pushdouble: #ifdef VMCFG_FLOAT case OP_pushfloat: case OP_pushfloat4: #endif case OP_pushnamespace: case OP_dxns: case OP_label: case OP_jump: case OP_pop: // Does not 'read' the value case OP_popscope: case OP_newfunction: case OP_returnvoid: case OP_newactivation: case OP_newcatch: case OP_finddef: case OP_getlex: case OP_getglobalscope: case OP_getscopeobject: case OP_getouterscope: case OP_getglobalslot: case OP_debugline: case OP_debugfile: case OP_debug: case OP_findpropglobalstrict: // Internal, poorly documented case OP_findpropglobal: // Internal, poorly documented break; // Op1 case OP_throw: case OP_iftrue: case OP_iffalse: case OP_convert_i: case OP_convert_u: case OP_convert_d: #ifdef VMCFG_FLOAT case OP_convert_f: case OP_convert_f4: case OP_unplus: #endif case OP_convert_b: case OP_convert_o: case OP_convert_s: case OP_coerce_b: case OP_coerce_a: case OP_coerce_i: case OP_coerce_d: case OP_coerce_s: case OP_coerce_u: case OP_coerce_o: case OP_coerce: case OP_dxnslate: case OP_li8: case OP_li16: case OP_li32: case OP_lf32: case OP_lf64: case OP_sxi1: case OP_sxi8: case OP_sxi16: #ifdef VMCFG_FLOAT case OP_lf32x4: case OP_sf32x4: #endif case OP_pushwith: case OP_lookupswitch: case OP_increment: case OP_decrement: case OP_typeof: case OP_not: case OP_bitnot: case OP_increment_i: case OP_decrement_i: case OP_astype: case OP_pushscope: case OP_negate: case OP_negate_i: case OP_dup: case OP_checkfilter: case OP_esc_xelem: case OP_esc_xattr: case OP_returnvalue: case OP_newclass: case OP_getslot: case OP_setglobalslot: if (isRestArray[state->sp()]) fail(); break; // Op2 case OP_add: case OP_subtract: case OP_multiply: case OP_divide: case OP_modulo: case OP_lshift: case OP_rshift: case OP_urshift: case OP_bitand: case OP_bitor: case OP_bitxor: case OP_equals: case OP_strictequals: case OP_lessthan: case OP_lessequals: case OP_greaterthan: case OP_greaterequals: case OP_instanceof: case OP_istype: case OP_istypelate: case OP_in: case OP_ifnlt: case OP_ifnle: case OP_ifngt: case OP_ifnge: case OP_ifeq: case OP_ifne: case OP_iflt: case OP_ifle: case OP_ifgt: case OP_ifge: case OP_ifstricteq: case OP_ifstrictne: case OP_si8: case OP_si16: case OP_si32: case OP_sf32: case OP_sf64: case OP_add_i: case OP_subtract_i: case OP_multiply_i: case OP_astypelate: case OP_swap: case OP_nextname: case OP_nextvalue: case OP_hasnext: case OP_setslot: if (isRestArray[state->sp()] || isRestArray[state->sp()-1]) fail(); break; // Locals case OP_inclocal: case OP_declocal: case OP_inclocal_i: case OP_declocal_i: case OP_setlocal: case OP_kill: update_local: if (imm30 == restVar) fail(); break; case OP_setlocal0: case OP_setlocal1: case OP_setlocal2: case OP_setlocal3: imm30 = opcode-OP_setlocal0; goto update_local; // Locals case OP_hasnext2: if (imm30 == restVar || imm30b == restVar) fail(); break; // Workhorses for variable-number-of-operands instructions checkMultiname: { const Multiname* m = pool->precomputedMultiname(imm30); numprops += (m->isRtname() != 0) + (m->isRtns() != 0); } checkVariable: { uint32_t top = state->sp(); for ( uint32_t i=0 ; i < numprops ; i++ ) if (isRestArray[top-i]) fail(); break; } // 0 + 0/1/2 name components case OP_findpropstrict: case OP_findproperty: numprops = 0; goto checkMultiname; // 1 + 0/1/2 name components case OP_getsuper: case OP_getdescendants: case OP_deleteproperty: numprops = 1; goto checkMultiname; // 2 + 0/1/2 name components case OP_setsuper: case OP_setproperty: case OP_initproperty: numprops = 2; goto checkMultiname; case OP_call: numprops = 2 + imm30; goto checkVariable; case OP_construct: case OP_constructsuper: case OP_applytype: numprops = 1 + imm30; goto checkVariable; case OP_callmethod: case OP_callstatic: numprops = 1 + imm30b; goto checkVariable; case OP_callsuper: case OP_callsupervoid: case OP_callproperty: case OP_callproplex: case OP_callpropvoid: case OP_constructprop: numprops = 1 + imm30b; goto checkMultiname; case OP_newobject: numprops = 2 * imm30; goto checkMultiname; case OP_newarray: numprops = imm30; goto checkMultiname; default: AvmAssert(!"Can't happen"); } } #endif // VMCFG_RESTARG_OPTIMIZATION /** * (done) branches stay in code block * (done) branches end on even instr boundaries * (done) all local var operands stay inside [0..max_locals-1] * (done) no illegal opcodes * (done) cpool refs are inside [1..cpool_size-1] * (done) converging paths have same stack depth * (done) operand stack stays inside [0..max_stack-1] * (done) locals defined before use * (done) scope stack stays bounded * (done) getScopeObject never exceeds [0..info->maxScopeDepth()-1] * (done) global slots limits obeyed [0..var_count-1] * (done) callstatic method limits obeyed [0..method_count-1] * (done) cpool refs are correct type * (done) make sure we don't fall off end of function * (done) slot based ops are ok (slot must be legal) * (done) propref ops are ok: usage on actual type compatible with ref type. * dynamic lookup ops are ok (type must not have that binding & must be dynamic) * dont access superclass state in ctor until super ctor called. * * pipeline todos: * - early binding * - copy propagation * * @param pool * @param info */ void Verifier::verify(CodeWriter *emitter) { SAMPLE_FRAME("[verify]", core); PERFM_NVPROF("abc-bytes", code_length); if (!info->abc_body_pos()) { // no body was supplied in abc toplevel->throwVerifyError(kNotImplementedError, core->toErrorString(info)); } if (info->declaringTraits() == NULL) { // scope hasn't been captured yet. verifyFailed(kCannotVerifyUntilReferencedError); } // CodeWriter warning: Verify exceptions are thrown from here // and callees, so any CodeWriters declared with function scope // will not be destructed. Presently, only CodeWriter, VerifyallWriter, // and ScopeWriter are declared this way, and none require cleanup. // if you add a new one, or add allocations in one of these, // a TRY/CATCH will be required. #ifdef AVMPLUS_VERBOSE if (verbose) core->console << "\ntypecheck " << info << '\n'; secondTry = false; #endif #ifdef AVMPLUS_VERBOSE // Switch off Re-Queue messages bool saveVerbose = verbose; if (core->config.verifyquiet) { verbose = false; } #endif // Verify in two passes. Phase 1 does type modelling and // iterates to a fixed point to determine the types and nullability // of each frame variable at branch targets. Phase 2 includes the // emitter and ScopeWriter, and visits opcodes in linear order. // Errors detected by these additional CodeWriters can be reported // in phase 2. In each phase, the CodeWriter protocol is obeyed: // writePrologue(), visits to explicit and implicit operations using // other writeXXX() methods, then writeEpilogue(). emitPass = false; // phase 1 - iterate to a fixed point CodeWriter stubWriter; #ifdef VMCFG_RESTARG_OPTIMIZATION coder = restArgAnalyzer.hookup(&stubWriter); #else coder = &stubWriter; #endif parseBodyHeader(); // set code_pos & code_length checkFrameDefinition(); parseExceptionHandlers(); // resolve catch block types checkParams(); #ifdef AVMPLUS_VERBOSE if (verbose) { printScope("outer-scope", info->declaringScope()); StringBuffer buf(core); printState(buf, state); } #endif coder->writePrologue(state, code_pos, this); if (code_length > 0 && code_pos[0] == OP_label) { // a reachable block starts at code_pos; explicitly create it, // which puts it on the worklist. checkTarget(code_pos-1, code_pos); } else { // inital sequence of code is only reachable from procedure // entry, no block will be created, so verify it explicitly verifyBlock(code_pos); } for (FrameState* succ = worklist; succ != NULL; succ = worklist) { worklist = succ->wl_next; succ->wl_pending = false; verifyBlock(loadBlockState(succ)); } coder->writeEpilogue(state); // phase 2 - traverse code in abc order and emit mmfx_delete(state); #ifdef VMCFG_RESTARG_OPTIMIZATION coder = restArgAnalyzer.hookup(emitter, true); #else coder = emitter; #endif // save computed ScopeTypeChain for OP_newfunction and OP_newclass ScopeWriter scopeWriter(coder, info, toplevel, this); coder = &scopeWriter; #ifdef AVMPLUS_VERBOSE // Switch off Re-Queue messages if (core->config.verifyquiet) { verbose = saveVerbose; } #endif #ifdef AVMPLUS_VERBOSE if (verbose) core->console << "\nverify " << info << '\n'; #endif emitPass = true; parseBodyHeader(); // reset code_pos & code_length checkFrameDefinition(); checkParams(); coder->writePrologue(state, code_pos, this); const uint8_t* end_pos = code_pos; // typically, first block is not in blockStates: verify it explicitly if (!hasFrameState(code_pos)) end_pos = verifyBlock(code_pos); // visit blocks in linear order (blockStates is sorted by abc address) for (int i=0, n=getBlockCount(); i < n; i++) { const uint8_t* start_pos = loadBlockState(blockStates->map.at(i)); // overlapping blocks indicates a branch to the middle of an instruction if (start_pos < end_pos) verifyFailed(kInvalidBranchTargetError); end_pos = verifyBlock(start_pos); } state->abc_pc = code_pos + code_length; coder->writeEpilogue(state); } const uint8_t* Verifier::loadBlockState(FrameState* blk) { // now load the saved state at this block state->init(blk); state->abc_pc = blk->abc_pc; #ifdef AVMPLUS_VERBOSE if (verbose) { StringBuffer buf(core); buf << "B" << int(blk->abc_pc - code_pos) << ":"; printState(buf, state); } #endif // found the start of a new basic block coder->writeBlockStart(state); return blk->abc_pc; } static bool isU30(int32_t v) { return (v & 0xc0000000) == 0; } void Verifier::checkFrameDefinition() { // validate rational bounds for frame definition parameters read directly from the abc int32_t locals = ms->local_count(); int32_t scope = ms->max_scope(); int32_t stack = ms->max_stack(); if ( !(isU30(stack) && isU30(locals) && isU30(scope)) ) toplevel->throwVerifyError(kCorruptABCError); // also ensure that the computed size doesn't overflow (see MethodInfo._buildMethodSignature) int32_t frame = ms->frame_size(); if ( (uint32_t)frame > ( 0x7fffffff / sizeof(FrameValue) ) ) toplevel->throwVerifyError(kCorruptABCError); } void Verifier::checkParams() { const int param_count = ms->param_count(); if (ms->local_count() < param_count+1) { // must have enough locals to hold all parameters including this toplevel->throwVerifyError(kCorruptABCError); } // initial scope chain types if (info->declaringTraits()->init != info && info->declaringScope() == NULL) { // this can occur when an activation scope inside a class instance method // contains a nested getter, setter, or method. In that case the scope // is not captured when the containing function is verified. This isn't a // bug because such nested functions aren't supported by AS3. This // verify error is how we don't let those constructs run. verifyFailed(kNoScopeError, core->toErrorString(info)); } state = mmfx_new( FrameState(ms, info)); state->abc_pc = code_pos; // initialize method param types. // We already verified param_count is a legal register so // don't checkLocal(i) inside the loop. // MethodInfo::verify takes care of resolving param&return type // names to Traits pointers, and resolving optional param default values. for (int i=0; i <= param_count; i++) state->setType(i, ms->paramTraits(i), i == 0); int first_local = param_count+1; if (info->needRestOrArguments()) { // abcMethod_NEED_REST overrides abcMethod_NEED_ARGUMENTS when both are set checkLocal(first_local); // ensure param_count+1 <= max_reg state->setType(first_local, ARRAY_TYPE, true); first_local++; } else { checkLocal(param_count); // ensure param_count <= max_reg } for (int i=first_local, n = ms->local_count(); i < n; i++) state->setType(i, NULL); // void would be more precise. } // verify one superblock, return at the end. The end of the block is when // we reach a terminal opcode (jump, lookupswitch, returnvalue, returnvoid, // or throw), or when we fall into the beginning of another block. // returns the address of the next instruction after the block end. const uint8_t* Verifier::verifyBlock(const uint8_t* start_pos) { _nvprof("verify-block", 1); CodeWriter *coder = this->coder; // Load into local var for expediency. ExceptionHandlerTable* exTable = info->abc_exceptions(); bool isLoopHeader = state->targetOfBackwardsBranch; state->targetOfBackwardsBranch = false; state->targetOfExceptionBranch = false; const uint8_t* code_end = code_pos + code_length; for (const uint8_t *pc = start_pos, *nextpc = pc; pc < code_end; pc = nextpc) { // should we make a new sample frame in this method? // SAMPLE_CHECK(); PERFM_NVPROF("abc-verify", 1); coder->writeFixExceptionsAndLabels(state, pc); AbcOpcode opcode = (AbcOpcode) *pc; const AbcOpcodeInfo* opcodeInfoPtr = FLOAT_ONLY(!pool->hasFloatSupport() ? opcodeInfoNoFloats :) opcodeInfo; if (opcodeInfoPtr[opcode].operandCount == -1) verifyFailed(kIllegalOpcodeError, core->toErrorString(info), core->toErrorString(opcode), core->toErrorString((int)(pc-code_pos))); state->abc_pc = pc; // test for the start of a new block if (pc != start_pos && (opcode == OP_label || hasFrameState(pc))) { checkTarget(pc-1, pc); return pc; } int sp = state->sp(); if (pc < tryTo && pc >= tryFrom && (opcodeInfo[opcode].canThrow || (isLoopHeader && pc == start_pos))) { // If this instruction can throw exceptions, treat it as an edge to // each in-scope catch handler. The instruction can throw exceptions // if canThrow = true, or if this is the target of a backedge, where // the implicit interrupt check can throw an exception. for (int i=0, n=exTable->exception_count; i < n; i++) { ExceptionHandler* handler = &exTable->exceptions[i]; if (pc >= code_pos + handler->from && pc < code_pos + handler->to) { int saveStackDepth = state->stackDepth; int saveScopeDepth = state->scopeDepth; FrameValue stackEntryZero = saveStackDepth > 0 ? state->stackValue(0) : state->value(0); state->stackDepth = 0; state->scopeDepth = 0; // add edge from try statement to catch block const uint8_t* target = code_pos + handler->target; // The thrown value is received as an atom but we will coerce it to // the expected type before handing control to the catch block. state->push(handler->traits); checkTarget(pc, target, /*isExceptionEdge*/true); state->pop(); state->stackDepth = saveStackDepth; state->scopeDepth = saveScopeDepth; if (saveStackDepth > 0) state->stackValue(0) = stackEntryZero; // Note that an exception may be caught in this method. handlerIsReachable = true; } } } uint32_t imm30=0, imm30b=0; int32_t imm8=0, imm24=0; AvmCore::readOperands(nextpc, imm30, imm24, imm30b, imm8); // make sure U30 operands are within bounds, // except for OP_pushshort, whose operand is sign extended from 16 bits if (opcode != OP_pushshort && ((imm30|imm30b) & 0xc0000000)) verifyFailed(kCorruptABCError); if (nextpc > code_end) verifyFailed(kLastInstExceedsCodeSizeError); #ifdef AVMPLUS_VERBOSE if (verbose) printOpcode(pc, code_end); #endif _nvprof("verify-instr", 1); switch (opcode) { case OP_iflt: case OP_ifle: case OP_ifnlt: case OP_ifnle: case OP_ifgt: case OP_ifge: case OP_ifngt: case OP_ifnge: case OP_ifeq: case OP_ifstricteq: case OP_ifne: case OP_ifstrictne: checkStack(2,0); coder->writeOp1(state, pc, opcode, imm24); state->pop(2); checkTarget(pc, nextpc+imm24); break; case OP_iftrue: case OP_iffalse: checkStack(1,0); emitCoerce(BOOLEAN_TYPE, sp); coder->writeOp1(state, pc, opcode, imm24); state->pop(); checkTarget(pc, nextpc+imm24); break; case OP_jump: //checkStack(0,0) coder->writeOp1(state, pc, opcode, imm24); checkTarget(pc, nextpc+imm24); // target block; coder->writeOpcodeVerified(state, pc, opcode); return nextpc; case OP_lookupswitch: { checkStack(1,0); peekType(INT_TYPE); coder->write(state, pc, opcode); state->pop(); checkTarget(pc, pc+imm24); uint32_t case_count = 1 + imm30b; // case_count*3 can't overflow a U32... uint32_t const case_skip = case_count * 3; // ...but adding it to nextpc could wrap around, so check. if (uintptr_t(nextpc) > uintptr_t(-1) - case_skip || nextpc + case_skip > code_end) verifyFailed(kLastInstExceedsCodeSizeError); for (uint32_t i=0; i < case_count; i++) { int off = AvmCore::readS24(nextpc); checkTarget(pc, pc+off); nextpc += 3; } coder->writeOpcodeVerified(state, pc, opcode); return nextpc; } case OP_throw: checkStack(1,0); coder->write(state, pc, opcode); state->pop(); coder->writeOpcodeVerified(state, pc, opcode); return nextpc; case OP_returnvalue: checkStack(1,0); emitCoerce(ms->returnTraits(), sp); coder->write(state, pc, opcode); state->pop(); coder->writeOpcodeVerified(state, pc, opcode); return nextpc; case OP_returnvoid: //checkStack(0,0) coder->write(state, pc, opcode); coder->writeOpcodeVerified(state, pc, opcode); return nextpc; case OP_pushnull: checkStack(0,1); coder->write(state, pc, opcode, NULL_TYPE); state->push(NULL_TYPE); break; case OP_pushundefined: checkStack(0,1); coder->write(state, pc, opcode, VOID_TYPE); state->push(VOID_TYPE); break; case OP_pushtrue: checkStack(0,1); coder->write(state, pc, opcode, BOOLEAN_TYPE); state->push(BOOLEAN_TYPE, true); break; case OP_pushfalse: checkStack(0,1); coder->write(state, pc, opcode, BOOLEAN_TYPE); state->push(BOOLEAN_TYPE, true); break; case OP_pushnan: checkStack(0,1); coder->write(state, pc, opcode, NUMBER_TYPE); state->push(NUMBER_TYPE, true); break; case OP_pushshort: checkStack(0,1); coder->write(state, pc, opcode, INT_TYPE); state->push(INT_TYPE, true); break; case OP_pushbyte: checkStack(0,1); coder->write(state, pc, opcode, INT_TYPE); state->push(INT_TYPE, true); break; case OP_debugfile: //checkStack(0,0) // fixme: bugzilla 552988: remove ifdef. #if defined(DEBUGGER) || defined(VMCFG_VTUNE) checkStringOperand(imm30); #endif coder->write(state, pc, opcode); break; case OP_dxns: //checkStack(0,0) if (!info->setsDxns()) verifyFailed(kIllegalSetDxns, core->toErrorString(info)); checkStringOperand(imm30); coder->write(state, pc, opcode); break; case OP_dxnslate: checkStack(1,0); if (!info->setsDxns()) verifyFailed(kIllegalSetDxns, core->toErrorString(info)); // codgen will call intern on the input atom. coder->write(state, pc, opcode); state->pop(); break; case OP_pushstring: checkStack(0,1); checkStringOperand(imm30); coder->write(state, pc, opcode, STRING_TYPE); state->push(STRING_TYPE, pool->getString(imm30) != NULL); break; case OP_pushint: checkStack(0,1); // FIXME: zero check is incorrect but traditional if (imm30 == 0 || imm30 >= pool->constantIntCount) verifyFailed(kCpoolIndexRangeError, core->toErrorString(imm30), core->toErrorString(pool->constantIntCount)); coder->write(state, pc, opcode, INT_TYPE); state->push(INT_TYPE,true); break; case OP_pushuint: checkStack(0,1); // FIXME: zero check is incorrect but traditional if (imm30 == 0 || imm30 >= pool->constantUIntCount) verifyFailed(kCpoolIndexRangeError, core->toErrorString(imm30), core->toErrorString(pool->constantUIntCount)); coder->write(state, pc, opcode, UINT_TYPE); state->push(UINT_TYPE,true); break; case OP_pushdouble: checkStack(0,1); // FIXME: zero check is incorrect but traditional if (imm30 == 0 || imm30 >= pool->constantDoubleCount) verifyFailed(kCpoolIndexRangeError, core->toErrorString(imm30), core->toErrorString(pool->constantDoubleCount)); coder->write(state, pc, opcode, NUMBER_TYPE); state->push(NUMBER_TYPE, true); break; #ifdef VMCFG_FLOAT case OP_pushfloat: checkStack(0,1); // FIXME: zero check is incorrect but traditional if (imm30 == 0 || imm30 >= pool->constantFloatCount) verifyFailed(kCpoolIndexRangeError, core->toErrorString(imm30), core->toErrorString(pool->constantFloatCount)); coder->write(state, pc, opcode, FLOAT_TYPE); state->push(FLOAT_TYPE, true); break; case OP_pushfloat4: checkStack(0,1); // FIXME: zero check is incorrect but traditional if (imm30 == 0 || imm30 >= pool->constantFloat4Count) verifyFailed(kCpoolIndexRangeError, core->toErrorString(imm30), core->toErrorString(pool->constantFloat4Count)); coder->write(state, pc, opcode, FLOAT4_TYPE); state->push(FLOAT4_TYPE, true); break; #endif case OP_pushnamespace: checkStack(0,1); if (imm30 == 0 || imm30 >= pool->constantNsCount) verifyFailed(kCpoolIndexRangeError, core->toErrorString(imm30), core->toErrorString(pool->constantNsCount)); coder->write(state, pc, opcode, NAMESPACE_TYPE); state->push(NAMESPACE_TYPE, pool->cpool_ns[imm30] != NULL); break; case OP_setlocal: { checkStack(1,0); checkLocal(imm30); coder->write(state, pc, opcode); FrameValue &v = state->stackTop(); state->setType(imm30, v.traits, v.notNull); state->pop(); break; } case OP_setlocal0: case OP_setlocal1: case OP_setlocal2: case OP_setlocal3: { checkStack(1,0); int index = opcode-OP_setlocal0; checkLocal(index); coder->write(state, pc, opcode); FrameValue &v = state->stackTop(); state->setType(index, v.traits, v.notNull); state->pop(); break; } case OP_getlocal: { checkStack(0,1); FrameValue& v = checkLocal(imm30); coder->write(state, pc, opcode); state->push(v); break; } case OP_getlocal0: case OP_getlocal1: case OP_getlocal2: case OP_getlocal3: { checkStack(0,1); FrameValue& v = checkLocal(opcode-OP_getlocal0); coder->write(state, pc, opcode); state->push(v); break; } case OP_kill: { //checkStack(0,0) checkLocal(imm30); coder->write(state, pc, opcode, NULL); state->setType(imm30, NULL, false); break; } case OP_inclocal: case OP_declocal: { checkLocal(imm30); Traits* retType = NUMBER_TYPE; bool already_coerced = false; #ifdef VMCFG_FLOAT if(pool->hasFloatSupport()) { FrameValue& v = checkLocal(imm30); BuiltinType bt = Traits::getBuiltinType(v.traits); if(bt == BUILTIN_none || bt == BUILTIN_any || bt == BUILTIN_object) { emitCoerceToNumeric(imm30); retType = OBJECT_TYPE; already_coerced = true; } else if(bt == BUILTIN_float || bt == BUILTIN_float4) retType = v.traits; else retType = NUMBER_TYPE; } #endif // VMCFG_FLOAT if(!already_coerced) emitCoerce(retType, imm30); coder->write(state, pc, opcode, retType); break; } case OP_inclocal_i: case OP_declocal_i: //checkStack(0,0); checkLocal(imm30); emitCoerce(INT_TYPE, imm30); coder->write(state, pc, opcode); break; case OP_newfunction: { checkStack(0,1); checkMethodInfo(imm30); Traits* ftraits = core->traits.function_itraits; coder->writeOp1(state, pc, opcode, imm30, ftraits); state->push(ftraits, true); break; } case OP_getlex: { if (state->scopeDepth + info->declaringScope()->size == 0) verifyFailed(kFindVarWithNoScopeError); Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(0, 1, &multiname); if (multiname.isRuntime()) verifyFailed(kIllegalOpMultinameError, core->toErrorString(opcode), core->toErrorString(&multiname)); emitFindProperty(OP_findpropstrict, multiname, imm30, pc); emitGetProperty(multiname, 1, imm30, pc); break; } case OP_findpropstrict: case OP_findproperty: { if (state->scopeDepth + info->declaringScope()->size == 0) verifyFailed(kFindVarWithNoScopeError); Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(0, 1, &multiname); emitFindProperty(opcode, multiname, imm30, pc); break; } case OP_newclass: { checkStack(1, 1); // imm30 is the class_id of the class object to create. Traits* ctraits = checkClassInfo(imm30); emitCoerce(CLASS_TYPE, state->sp()); coder->writeOp1(state, pc, opcode, imm30, ctraits); state->pop_push(1, ctraits, true); break; } case OP_finddef: { // must be a CONSTANT_Multiname. Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(0, 1, &multiname); if (!multiname.isBinding()) { // error, def name must be CT constant, regular name verifyFailed(kIllegalOpMultinameError, core->toErrorString(opcode), core->toErrorString(&multiname)); } MethodInfo* script = core->domainMgr()->findScriptInPoolByMultiname(pool, multiname); Traits* resultType; if (script != (MethodInfo*)BIND_NONE && script != (MethodInfo*)BIND_AMBIGUOUS) { // found a single matching traits resultType = script->declaringTraits(); } else { // no traits, or ambiguous reference. use Object, anticipating // a runtime exception resultType = OBJECT_TYPE; } // If the script has already failed verification, it may // have a null declaringTraits. if (resultType == NULL) verifyFailed(kCorruptABCError); coder->writeOp1(state, pc, opcode, imm30, resultType); state->push(resultType, true); break; } case OP_setproperty: case OP_initproperty: { // stack in: object [ns] [name] value Multiname multiname; checkConstantMultiname(imm30, multiname); // CONSTANT_Multiname checkStackMulti(2, 0, &multiname); uint32_t n=2; checkPropertyMultiname(n, multiname); Traitsp declarer = NULL; FrameValue& obj = state->peek(n); Binding b = (opcode == OP_initproperty) ? toplevel->getBindingAndDeclarer(obj.traits, multiname, declarer) : toplevel->getBinding(obj.traits, &multiname); Traits* propTraits = readBinding(obj.traits, b); emitCheckNull(sp-(n-1)); if (AvmCore::isSlotBinding(b) && // it's a var, or a const being set from the init function (!AvmCore::isConstBinding(b) || (opcode == OP_initproperty && declarer->init == info))) { emitCoerce(propTraits, state->sp()); coder->writeOp2(state, pc, OP_setslot, (uint32_t)AvmCore::bindingToSlotId(b), sp-(n-1), propTraits); state->pop(n); break; } // else: setting const from illegal context, fall through // If it's an accessor that we can early bind, do so. // Note that this cannot be done on String or Namespace, // since those are represented by non-ScriptObjects if (AvmCore::hasSetterBinding(b)) { // invoke the setter int disp_id = AvmCore::bindingToSetterId(b); const TraitsBindingsp objtd = obj.traits->getTraitsBindings(); MethodInfo *f = objtd->getMethod(disp_id); AvmAssert(f != NULL); MethodSignaturep fms = f->getMethodSignature(); emitCoerceArgs(f, 1); Traits* propType = fms->returnTraits(); coder->writeOp2(state, pc, opcode, imm30, n, propType); state->pop(n); break; } if(obj.traits == VECTORINT_TYPE || obj.traits == VECTORUINT_TYPE || obj.traits == VECTORDOUBLE_TYPE #ifdef VMCFG_FLOAT || obj.traits == VECTORFLOAT_TYPE || obj.traits == VECTORFLOAT4_TYPE #endif ) { bool attr = multiname.isAttr(); Traits* indexType = state->value(state->sp()-1).traits; // NOTE a dynamic name should have the same version as the current pool bool maybeIntegerIndex = !attr && multiname.isRtname() && multiname.containsAnyPublicNamespace(); if( maybeIntegerIndex && (indexType == UINT_TYPE || indexType == INT_TYPE || indexType == NUMBER_TYPE #ifdef VMCFG_FLOAT || indexType == FLOAT_TYPE // float indices are always absorbed by the vector #endif ) ) { if(obj.traits == VECTORINT_TYPE) emitCoerce(INT_TYPE, state->sp()); else if(obj.traits == VECTORUINT_TYPE) emitCoerce(UINT_TYPE, state->sp()); else if(obj.traits == VECTORDOUBLE_TYPE) emitCoerce(NUMBER_TYPE, state->sp()); #ifdef VMCFG_FLOAT else if(obj.traits == VECTORFLOAT_TYPE) emitCoerce(FLOAT_TYPE, state->sp()); else if(obj.traits == VECTORFLOAT4_TYPE) emitCoerce(FLOAT4_TYPE, state->sp()); #endif } } // Default: do setproperty or initproperty at runtime. coder->writeOp2(state, pc, opcode, imm30, n, propTraits); state->pop(n); break; } case OP_getproperty: { // stack in: object [ns [name]] // stack out: value Multiname multiname; checkConstantMultiname(imm30, multiname); // CONSTANT_Multiname checkStackMulti(1, 1, &multiname); uint32_t n=1; checkPropertyMultiname(n, multiname); #ifdef VMCFG_RESTARG_OPTIMIZATION bool emitOptimizedRestArg = restArgAnalyzer.optimize; if (emitOptimizedRestArg) emitOptimizedRestArg = restArgAnalyzer.getProperty(state, multiname, n); if (emitPass && emitOptimizedRestArg) { FrameValue& obj = state->peek(n); if (multiname.isRtname()) { // restarg assumes the property name is an atom, so we must coerce it to an atom on input emitCoerce(NULL, state->sp()); coder->writeOp1(state, pc, OP_restarg, imm30, NULL); state->pop_push(n, NULL); } else if (multiname.getName() == core->klength) { Binding b = toplevel->getBinding(obj.traits, &multiname); Traits* propType = readBinding(obj.traits, b); coder->write(state, pc, OP_restargc, propType); state->pop_push(n, UINT_TYPE); // restargc produces an uint, so we must coerce to the target type on return emitCoerce(propType, sp); } else { AvmAssert(!"Can't happen"); } } else { emitGetProperty(multiname, n, imm30, pc); } #else emitGetProperty(multiname, n, imm30, pc); #endif break; } case OP_getdescendants: { // stack in: object [ns] [name] // stack out: value Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(1, 1, &multiname); uint32_t n=1; checkPropertyMultiname(n, multiname); emitCheckNull(sp-(n-1)); coder->write(state, pc, opcode); state->pop_push(n, NULL); break; } case OP_checkfilter: checkStack(1, 1); emitCheckNull(sp); coder->write(state, pc, opcode); break; case OP_deleteproperty: { Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(1, 1, &multiname); uint32_t n=1; checkPropertyMultiname(n, multiname); emitCheckNull(sp-(n-1)); coder->write(state, pc, opcode); state->pop_push(n, BOOLEAN_TYPE); break; } case OP_astype: { checkStack(1, 1); // resolve operand into a traits, and push that type. Traits *t = checkTypeName(imm30); // CONSTANT_Multiname int index = sp; Traits* rhs = state->value(index).traits; if (!Traits::canAssign(t, rhs)) { Traits* resultType = t; // result is typed value or null, so if type can't hold null, // then result type is Object. if (t && t->isMachineType()) resultType = OBJECT_TYPE; coder->write(state, pc, opcode, t); state->pop_push(1, resultType); } break; } case OP_astypelate: { checkStack(2,1); FrameValue& classValue = state->peek(1); // rhs - class Traits* ct = classValue.traits; Traits* t = NULL; if (ct && (t=ct->itraits) != 0) if (t->isMachineType()) t = OBJECT_TYPE; coder->write(state, pc, opcode, t); state->pop_push(2, t); break; } case OP_coerce: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = checkTypeName(imm30); coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_convert_b: case OP_coerce_b: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = BOOLEAN_TYPE; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_coerce_o: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = OBJECT_TYPE; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_coerce_a: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = NULL; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_convert_i: case OP_coerce_i: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = INT_TYPE; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_convert_u: case OP_coerce_u: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = UINT_TYPE; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_convert_d: case OP_coerce_d: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = NUMBER_TYPE; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } #ifdef VMCFG_FLOAT case OP_convert_f: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = FLOAT_TYPE; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_convert_f4: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = FLOAT4_TYPE; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_unplus: { checkStack(1,1); FrameValue &v = state->value(sp); BuiltinType bt = Traits::getBuiltinType(v.traits); Traits* type = v.traits; if(bt == BUILTIN_none || bt == BUILTIN_any || bt == BUILTIN_object) { emitCoerceToNumeric(sp); type = OBJECT_TYPE; } else if(bt != BUILTIN_float && bt != BUILTIN_float4) { emitCoerce(NUMBER_TYPE, sp); type = NUMBER_TYPE; } coder->write(state, pc, opcode, type); state->setType(sp, type, true); break; } #endif // VMCFG_FLOAT case OP_coerce_s: { checkStack(1,1); FrameValue &v = state->value(sp); Traits *type = STRING_TYPE; coder->write(state, pc, opcode, type); state->setType(sp, type, v.notNull); break; } case OP_istype: { checkStack(1,1); // resolve operand into a traits, and test if value is that type Traits* t = checkTypeName(imm30); // CONSTANT_Multiname coder->write(state, pc, opcode, t); state->pop(1); state->push(BOOLEAN_TYPE); break; } case OP_istypelate: checkStack(2,1); coder->write(state, pc, opcode); // TODO if the only common base type of lhs,rhs is Object, then result is always false state->pop_push(2, BOOLEAN_TYPE); break; case OP_convert_o: checkStack(1,1); // ISSUE should result be Object, laundering the type? // ToObject throws an exception on null and undefined, so after this runs we // know the value is safe to dereference. emitCheckNull(sp); coder->write(state, pc, opcode); break; case OP_convert_s: case OP_esc_xelem: case OP_esc_xattr: checkStack(1,1); // this is the ECMA ToString and ToXMLString operators, so the result must not be null // (ToXMLString is split into two variants - escaping elements and attributes) coder->write(state, pc, opcode); state->pop_push(1, STRING_TYPE, true); break; case OP_callstatic: { // Ensure that the method is eligible for callstatic. // Note: This fails when called by verifyEarly(), since the // data structures being checked have not been initialized. // Need to either rearrange the initialization sequence or // mark this verify pass as "needs late retry." if ( imm30 >= pool->methodCount() || ! abc_env->getMethod(imm30) ) verifyFailed(kCorruptABCError); MethodInfo* m = checkMethodInfo(imm30); const uint32_t argc = imm30b; checkStack(argc+1, 1); MethodSignaturep mms = m->getMethodSignature(); if (!mms->paramTraits(0)) { verifyFailed(kDanglingFunctionError, core->toErrorString(m), core->toErrorString(info)); } emitCheckNull(sp-argc); emitCoerceArgs(m, argc); Traits *resultType = mms->returnTraits(); coder->writeOp2(state, pc, OP_callstatic, (uint32_t)m->method_id(), argc, resultType); state->pop_push(argc+1, resultType); break; } case OP_call: { const uint32_t argc = imm30; checkStack(argc+2, 1); // don't need null check, AvmCore::call() uses toFunction() for null check. /* TODO optimizations - if this is a class closure for a non-native type, call == coerce - if this is a function closure, try early binding using the traits->call sig - optimize simple cases of casts to builtin types */ coder->writeOp1(state, pc, opcode, argc); state->pop_push(argc+2, NULL); break; } case OP_construct: { const uint32_t argc = imm30; checkStack(argc+1, 1); // don't need null check, AvmCore::construct() uses toFunction() for null check. Traits* ctraits = state->peek(argc+1).traits; Traits* itraits = ctraits ? (Traits*)(ctraits->itraits) : NULL; coder->writeOp1(state, pc, opcode, argc); state->pop_push(argc+1, itraits, true); break; } case OP_callmethod: { /* OP_callmethod will always throw a verify error. that's on purpose, it's a last minute change before we shipped FP9 and was necessary when we added methods to class Object. since then we realized that OP_callmethod need only have failed when used outside of the builtin abc, but it's a moot point now. We dont have to worry about it. code has since been simplified but existing failure modes preserved. */ const uint32_t argc = imm30b; checkStack(argc+1,1); const int disp_id = imm30-1; if (disp_id >= 0) { FrameValue& obj = state->peek(argc+1); if( !obj.traits ) verifyFailed(kCorruptABCError); else verifyFailed(kIllegalEarlyBindingError, core->toErrorString(obj.traits)); } else { verifyFailed(kZeroDispIdError); } break; } case OP_callproperty: case OP_callproplex: case OP_callpropvoid: { // stack in: obj [ns [name]] args // stack out: result const uint32_t argc = imm30b; Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(argc+1, 1, &multiname); checkCallMultiname(opcode, &multiname); uint32_t n = argc+1; // index of receiver checkPropertyMultiname(n, multiname); emitCallproperty(opcode, sp, multiname, imm30, imm30b, pc); break; } case OP_constructprop: { // stack in: obj [ns [name]] args const uint32_t argc = imm30b; Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(argc+1, 1, &multiname); checkCallMultiname(opcode, &multiname); uint32_t n = argc+1; // index of receiver checkPropertyMultiname(n, multiname); FrameValue& obj = state->peek(n); // make sure object is there Binding b = toplevel->getBinding(obj.traits, &multiname); Traits* ctraits = readBinding(obj.traits, b); emitCheckNull(sp-(n-1)); coder->writeOp2(state, pc, opcode, imm30, argc, ctraits); Traits* itraits = ctraits ? (Traits*)(ctraits->itraits) : NULL; state->pop_push(n, itraits, itraits==NULL?false:true); break; } case OP_applytype: { // in: factory arg1..N // out: type const uint32_t argc = imm30; checkStack(argc+1, 1); coder->write(state, pc, opcode); state->pop_push(argc+1, NULL, true); break; } case OP_callsuper: case OP_callsupervoid: { // stack in: obj [ns [name]] args const uint32_t argc = imm30b; Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(argc+1, 1, &multiname); if (multiname.isAttr()) verifyFailed(kIllegalOpMultinameError, core->toErrorString(&multiname)); uint32_t n = argc+1; // index of receiver checkPropertyMultiname(n, multiname); Traits* base = emitCoerceSuper(sp-(n-1)); const TraitsBindingsp basetd = base->getTraitsBindings(); Binding b = toplevel->getBinding(base, &multiname); Traits *resultType = NULL; if (AvmCore::isMethodBinding(b)) { int disp_id = AvmCore::bindingToMethodId(b); MethodInfo* m = basetd->getMethod(disp_id); if( !m ) verifyFailed(kCorruptABCError); MethodSignaturep mms = m->getMethodSignature(); resultType = mms->returnTraits(); } emitCheckNull(sp-(n-1)); coder->writeOp2(state, pc, opcode, imm30, argc, base); state->pop_push(n, resultType); if (opcode == OP_callsupervoid) state->pop(); break; } case OP_getsuper: { // stack in: obj [ns [name]] // stack out: value Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(1, 1, &multiname); uint32_t n=1; checkPropertyMultiname(n, multiname); if (multiname.isAttr()) verifyFailed(kIllegalOpMultinameError, core->toErrorString(&multiname)); Traits* base = emitCoerceSuper(sp-(n-1)); Binding b = toplevel->getBinding(base, &multiname); Traits* propType = readBinding(base, b); emitCheckNull(sp-(n-1)); coder->writeOp2(state, pc, opcode, imm30, n, base); if (AvmCore::hasGetterBinding(b)) { int disp_id = AvmCore::bindingToGetterId(b); const TraitsBindingsp basetd = base->getTraitsBindings(); MethodInfo *f = basetd->getMethod(disp_id); AvmAssert(f != NULL); MethodSignaturep fms = f->getMethodSignature(); Traits* resultType = fms->returnTraits(); state->pop_push(n, resultType); } else { state->pop_push(n, propType); } break; } case OP_setsuper: { // stack in: obj [ns [name]] value Multiname multiname; checkConstantMultiname(imm30, multiname); checkStackMulti(2, 0, &multiname); uint32_t n=2; checkPropertyMultiname(n, multiname); if (multiname.isAttr()) verifyFailed(kIllegalOpMultinameError, core->toErrorString(&multiname)); Traits* base = emitCoerceSuper(sp-(n-1)); emitCheckNull(sp-(n-1)); coder->writeOp2(state, pc, opcode, imm30, n, base); state->pop(n); break; } case OP_constructsuper: { // stack in: obj, args ... const uint32_t argc = imm30; checkStack(argc+1, 0); int32_t ptrIndex = sp-argc; Traits* baseTraits = emitCoerceSuper(ptrIndex); // check receiver MethodInfo *f = baseTraits->init; AvmAssert(f != NULL); emitCoerceArgs(f, argc); emitCheckNull(sp-argc); coder->writeOp2(state, pc, opcode, 0, argc, baseTraits); state->pop(argc+1); break; } case OP_newobject: { uint32_t argc = imm30; checkStack(2*argc, 1); int n=0; while (argc-- > 0) { n += 2; peekType(STRING_TYPE, n); // name; will call intern on it } coder->write(state, pc, opcode); state->pop_push(n, OBJECT_TYPE, true); break; } case OP_newarray: checkStack(imm30, 1); coder->write(state, pc, opcode); state->pop_push(imm30, ARRAY_TYPE, true); break; case OP_pushscope: { checkStack(1,0); if (state->scopeDepth + 1 > ms->max_scope()) verifyFailed(kScopeStackOverflowError); Traits* scopeTraits = state->peek().traits; const ScopeTypeChain* scope = info->declaringScope(); if (scope->fullsize > (scope->size+state->scopeDepth)) { // extra constraints on type of pushscope allowed Traits* requiredType = scope->getScopeTraitsAt(scope->size+state->scopeDepth); if (!scopeTraits || !scopeTraits->subtypeof(requiredType)) { verifyFailed(kIllegalOperandTypeError, core->toErrorString(scopeTraits), core->toErrorString(requiredType)); } } emitCheckNull(sp); coder->writeOp1(state, pc, opcode, ms->scope_base() + state->scopeDepth); state->pop(); state->setType(ms->scope_base() + state->scopeDepth, scopeTraits, true, false); state->scopeDepth++; break; } case OP_pushwith: { checkStack(1,0); if (state->scopeDepth + 1 > ms->max_scope()) verifyFailed(kScopeStackOverflowError); emitCheckNull(sp); coder->writeOp1(state, pc, opcode, ms->scope_base() + state->scopeDepth); Traits* scopeTraits = state->peek().traits; state->pop(); state->setType(ms->scope_base() + state->scopeDepth, scopeTraits, true, true); if (state->withBase == -1) state->withBase = state->scopeDepth; state->scopeDepth++; break; } case OP_newactivation: { checkStack(0, 1); if (!info->needActivation()) verifyFailed(kInvalidNewActivationError); Traits* atraits = info->activationTraits(); atraits->resolveSignatures(toplevel); coder->write(state, pc, opcode, atraits); state->push(atraits, true); break; } case OP_newcatch: { checkStack(0, 1); if (!info->abc_exceptions() || imm30 >= (uint32_t)info->abc_exceptions()->exception_count) verifyFailed(kInvalidNewActivationError); // FIXME better error msg ExceptionHandler* handler = &info->abc_exceptions()->exceptions[imm30]; coder->write(state, pc, opcode); state->push(handler->scopeTraits, true); break; } case OP_popscope: //checkStack(0,0) if (state->scopeDepth-- <= 0) verifyFailed(kScopeStackUnderflowError); coder->write(state, pc, opcode); if (state->withBase >= state->scopeDepth) state->withBase = -1; break; case OP_getscopeobject: checkStack(0,1); // local scope if (imm8 >= state->scopeDepth) verifyFailed(kGetScopeObjectBoundsError, core->toErrorString(imm8)); coder->writeOp1(state, pc, opcode, imm8); // this will copy type and all attributes too state->push(state->scopeValue(imm8)); break; case OP_getouterscope: { checkStack(0,1); const ScopeTypeChain* scope = info->declaringScope(); uint32_t index = imm30; int captured_depth = scope->size; if (captured_depth > 0) { // imm30 is unsigned, so can't be < 0 if (index >= uint32_t(scope->size)) toplevel->throwVerifyError(kGetScopeObjectBoundsError); // enclosing scope Traits* t = scope->getScopeTraitsAt(index); coder->writeOp1(state, pc, opcode, index, t); state->push(t, true); } else { #ifdef _DEBUG if (pool->isBuiltin) core->console << "getouterscope >= depth (" << index << " >= " << state->scopeDepth << ")\n"; #endif verifyFailed(kGetScopeObjectBoundsError, core->toErrorString(index)); } break; } case OP_getglobalscope: checkStack(0,1); coder->write(state, pc, OP_getglobalscope); checkGetGlobalScope(); // after coder->write because mutates stack that coder depends on break; case OP_getglobalslot: { checkStack(0,1); uint32_t slot = imm30-1; Traits* globalTraits = checkGetGlobalScope(); checkEarlySlotBinding(globalTraits); // sets state->value(sp).traits so CodeWriter can see type Traits* slotTraits = checkSlot(globalTraits, slot); coder->writeOp1(state, pc, OP_getglobalslot, slot, slotTraits); state->pop_push(1, slotTraits); break; } case OP_setglobalslot: { // FIXME need test case const ScopeTypeChain* scope = info->declaringScope(); if (!state->scopeDepth && !scope->size) verifyFailed(kNoGlobalScopeError); Traits *globalTraits = scope->size > 0 ? scope->getScopeTraitsAt(0) : state->scopeValue(0).traits; checkStack(1,0); checkEarlySlotBinding(globalTraits); Traits* slotTraits = checkSlot(globalTraits, imm30-1); emitCoerce(slotTraits, state->sp()); coder->writeOp1(state, pc, opcode, imm30-1, slotTraits); state->pop(); break; } case OP_getslot: { checkStack(1,1); FrameValue& obj = state->peek(); checkEarlySlotBinding(obj.traits); Traits* slotTraits = checkSlot(obj.traits, imm30-1); emitCheckNull(state->sp()); coder->write(state, pc, opcode); state->pop_push(1, slotTraits); break; } case OP_setslot: { checkStack(2,0); FrameValue& obj = state->peek(2); // object checkEarlySlotBinding(obj.traits); Traits* slotTraits = checkSlot(obj.traits, imm30-1); emitCoerce(slotTraits, state->sp()); emitCheckNull(state->sp()-1); coder->write(state, pc, opcode); state->pop(2); break; } case OP_pop: checkStack(1,0); coder->write(state, pc, opcode); state->pop(); break; case OP_dup: { checkStack(1, 2); FrameValue& v = state->peek(); coder->write(state, pc, opcode); state->push(v); break; } case OP_swap: { checkStack(2,2); FrameValue v1 = state->peek(1); FrameValue v2 = state->peek(2); coder->write(state, pc, opcode); state->pop(2); state->push(v1); state->push(v2); break; } case OP_lessthan: case OP_greaterthan: case OP_lessequals: case OP_greaterequals: { // if either the LHS or RHS is a number type, then we know // it will be a numeric comparison. checkStack(2,1); FrameValue& rhs = state->peek(1); FrameValue& lhs = state->peek(2); Traits *lhst = lhs.traits; Traits *rhst = rhs.traits; if(lhst && rhst ){ // We assume is that number-to-number comparison yields the same result as float-to-float comparison // Also, float4 will be coerced to number, and this is ok. if (!lhst->isNumeric() && rhst->isNumeric() ) { // convert lhs to Number emitCoerce(NUMBER_TYPE, state->sp()-1); } else if (lhst->isNumeric() && !rhst->isNumeric()) { // promote rhs to Number emitCoerce(NUMBER_TYPE, state->sp()); } } coder->write(state, pc, opcode, BOOLEAN_TYPE); state->pop_push(2, BOOLEAN_TYPE); break; } case OP_in: checkStack(2,1); emitCheckNull(sp); coder->write(state, pc, opcode); state->pop_push(2, BOOLEAN_TYPE); break; case OP_equals: case OP_strictequals: case OP_instanceof: checkStack(2,1); coder->write(state, pc, opcode); state->pop_push(2, BOOLEAN_TYPE); break; case OP_not: checkStack(1,1); emitCoerce(BOOLEAN_TYPE, sp); coder->write(state, pc, opcode, BOOLEAN_TYPE); state->pop_push(1, BOOLEAN_TYPE); break; case OP_add: { checkStack(2,1); FrameValue& rhs = state->peek(1); FrameValue& lhs = state->peek(2); Traits* lhst = lhs.traits; Traits* rhst = rhs.traits; if ((lhst == STRING_TYPE && lhs.notNull) || (rhst == STRING_TYPE && rhs.notNull)) { coder->write(state, pc, OP_add, STRING_TYPE); state->pop_push(2, STRING_TYPE, true); } else if (lhst && lhst->isNumeric() && rhst && rhst->isNumeric()) { Traits* type = NUMBER_TYPE; #ifdef VMCFG_FLOAT if(pool->hasFloatSupport()) { if(lhst == FLOAT4_TYPE || rhst == FLOAT4_TYPE) type = FLOAT4_TYPE; // float4 + anything numeric gives float4 else if(rhst->isNumberType() || lhst->isNumberType()) type = NUMBER_TYPE; else if(rhst == FLOAT_TYPE && lhst == FLOAT_TYPE) type = FLOAT_TYPE; else { // the result is "NUMERIC" because at least one is NUMERIC and the other is NUMERIC or FLOAT. emitCoerceToNumeric(sp); emitCoerceToNumeric(sp-1); type = OBJECT_TYPE; } } #endif // VMCFG_FLOAT coder->write(state, pc, OP_add, type); state->pop_push(2, type, true); } else { coder->write(state, pc, OP_add, OBJECT_TYPE); // NOTE don't know if it will return number or string, but // neither will be null state->pop_push(2, OBJECT_TYPE, true); } break; } case OP_modulo: case OP_subtract: case OP_divide: case OP_multiply: { checkStack(2,1); Traits* type = NUMBER_TYPE; bool already_coerced = false; #ifdef VMCFG_FLOAT if(pool->hasFloatSupport()) { FrameValue& rhs = state->peek(1); FrameValue& lhs = state->peek(2); BuiltinType lhst = Traits::getBuiltinType(lhs.traits); BuiltinType rhst = Traits::getBuiltinType(rhs.traits); /* If both are floats, operate on float. If any is float4, operate on float4. If both types are known (and not Object), operate on number. Otherwise - convert to numeric */ if(lhst == BUILTIN_float && rhst == BUILTIN_float) type = FLOAT_TYPE; else if(lhst == BUILTIN_float4 || rhst == BUILTIN_float4) type = FLOAT4_TYPE; else if(lhst != BUILTIN_none && lhst != BUILTIN_object && lhst != BUILTIN_any && rhst != BUILTIN_none && rhst != BUILTIN_object && rhst != BUILTIN_any) type = NUMBER_TYPE; else { emitCoerceToNumeric(sp-1); emitCoerceToNumeric(sp); already_coerced = true; type = OBJECT_TYPE; } } #endif if(!already_coerced) { emitCoerce(type, sp-1); emitCoerce(type, sp); } coder->write(state, pc, opcode, type); state->pop_push(2, type); break; } case OP_negate: case OP_increment: case OP_decrement: { checkStack(1,1); Traits* retType = NUMBER_TYPE; bool already_coerced = false; #ifdef VMCFG_FLOAT if(pool->hasFloatSupport()) { FrameValue& v = state->peek(1); BuiltinType bt = Traits::getBuiltinType(v.traits); if(bt == BUILTIN_none || bt == BUILTIN_any || bt == BUILTIN_object) { emitCoerceToNumeric(sp); already_coerced = true; retType = OBJECT_TYPE; } else if (bt == BUILTIN_float || bt == BUILTIN_float4) retType = v.traits; else retType = NUMBER_TYPE; } #endif if(!already_coerced) emitCoerce(retType, sp); coder->write(state, pc, opcode, retType); break; } case OP_increment_i: case OP_decrement_i: checkStack(1,1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); break; case OP_add_i: case OP_subtract_i: case OP_multiply_i: checkStack(2,1); emitCoerce(INT_TYPE, sp-1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop_push(2, INT_TYPE); break; case OP_negate_i: checkStack(1,1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); break; case OP_bitand: case OP_bitor: case OP_bitxor: checkStack(2,1); emitCoerce(INT_TYPE, sp-1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop_push(2, INT_TYPE); break; // ISSUE do we care if shift amount is signed or not? we mask // the result so maybe it doesn't matter. // CN says see tests e11.7.2, 11.7.3, 9.6 case OP_lshift: case OP_rshift: checkStack(2,1); emitCoerce(INT_TYPE, sp-1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop_push(2, INT_TYPE); break; case OP_urshift: checkStack(2,1); emitCoerce(INT_TYPE, sp-1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop_push(2, UINT_TYPE); break; case OP_bitnot: checkStack(1,1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); break; case OP_typeof: checkStack(1,1); coder->write(state, pc, opcode); state->pop_push(1, STRING_TYPE, true); break; case OP_nop: // those show up but will be ignored case OP_bkpt: case OP_bkptline: case OP_timestamp: coder->write(state, pc, OP_nop); break; case OP_debug: { #ifdef DEBUGGER // fixme: bugzilla 552988: remove ifdef. uint8_t type = (uint8_t)*(pc + 1); if (type == DI_LOCAL) { // see Debugger::scanCode const uint8_t* pc2 = pc + 2; uint32_t index = AvmCore::readU32(pc2); checkStringOperand(index); } #endif coder->write(state, pc, opcode); break; } case OP_label: coder->write(state, pc, opcode); break; case OP_debugline: coder->write(state, pc, opcode); break; case OP_nextvalue: case OP_nextname: checkStack(2,1); peekType(INT_TYPE, 1); coder->write(state, pc, opcode); state->pop_push(2, NULL); break; case OP_hasnext: checkStack(2,1); peekType(INT_TYPE,1); coder->write(state, pc, opcode); state->pop_push(2, INT_TYPE); break; case OP_hasnext2: { checkStack(0,1); checkLocal(imm30); FrameValue& v = checkLocal(imm30b); if (imm30 == imm30b) verifyFailed(kInvalidHasNextError); if (v.traits != INT_TYPE) verifyFailed(kIllegalOperandTypeError, core->toErrorString(v.traits), core->toErrorString(INT_TYPE)); coder->write(state, pc, opcode); state->setType(imm30, NULL, false); state->push(BOOLEAN_TYPE); break; } // sign extends case OP_sxi1: case OP_sxi8: case OP_sxi16: checkStack(1,1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop_push(1, INT_TYPE); break; // loads case OP_li8: case OP_li16: if (pc+1 < code_end && ((opcode == OP_li8 && pc[1] == OP_sxi8) || (opcode == OP_li16 && pc[1] == OP_sxi16))) { checkStack(1,1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, (opcode == OP_li8) ? OP_lix8 : OP_lix16); state->pop_push(1, INT_TYPE); // ++pc; // do not skip the sign-extend; if it's the target // of an implicit label, skipping it would cause verification failure. // instead, just emit it, and rely on LIR to ignore sxi instructions // in these situations. break; } // else fall thru case OP_li32: case OP_lf32: case OP_lf64: { Traits* result = (opcode == OP_lf32 || opcode == OP_lf64) ? NUMBER_TYPE : INT_TYPE; checkStack(1,1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop_push(1, result); break; } #ifdef VMCFG_FLOAT case OP_lf32x4: { checkStack(1,1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop_push(1, FLOAT4_TYPE); break; } #endif // stores case OP_si8: case OP_si16: case OP_si32: case OP_sf32: case OP_sf64: checkStack(2,0); emitCoerce((opcode == OP_sf32 || opcode == OP_sf64) ? NUMBER_TYPE : INT_TYPE, sp-1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop(2); break; #ifdef VMCFG_FLOAT case OP_sf32x4: { checkStack(2,0); emitCoerce(FLOAT4_TYPE, sp-1); emitCoerce(INT_TYPE, sp); coder->write(state, pc, opcode); state->pop(2); break; } #endif default: // size was nonzero, but no case handled the opcode. someone asleep at the wheel! AvmAssertMsg(false, "Unhandled opcode"); } coder->writeOpcodeVerified(state, pc, opcode); #ifdef AVMPLUS_VERBOSE if (verbose) { StringBuffer buf(core); printState(buf, state); } #endif } verifyFailed(kCannotFallOffMethodError); return code_end; } void Verifier::checkPropertyMultiname(uint32_t &depth, Multiname &multiname) { if (multiname.isRtname()) { if (multiname.isQName()) { // a.ns::@[name] or a.ns::[name] peekType(STRING_TYPE, depth++); } else { // a.@[name] or a[name] depth++; } } if (multiname.isRtns()) { peekType(NAMESPACE_TYPE, depth++); } } void Verifier::emitCallproperty(AbcOpcode opcode, int& sp, Multiname& multiname, uint32_t multiname_index, uint32_t argc, const uint8_t* pc) { uint32_t n = argc+1; checkPropertyMultiname(n, multiname); Traits* t = state->peek(n).traits; if (t) t->resolveSignatures(toplevel); Binding b = toplevel->getBinding(t, &multiname); emitCheckNull(sp-(n-1)); if (emitCallpropertyMethod(opcode, t, b, multiname, multiname_index, argc, pc)) return; if (emitCallpropertySlot(opcode, sp, t, b, argc, pc)) return; coder->writeOp2(state, pc, opcode, multiname_index, argc); // If early binding then the state will have been updated, so this will be skipped state->pop_push(n, NULL); if (opcode == OP_callpropvoid) state->pop(); } bool Verifier::emitCallpropertyMethod(AbcOpcode opcode, Traits* t, Binding b, Multiname& multiname, uint32_t multiname_index, uint32_t argc, const uint8_t* pc) { (void) multiname_index; // FIXME remove if (!AvmCore::isMethodBinding(b)) return false; uint32_t n = argc+1; const TraitsBindingsp tb = t->getTraitsBindings(); if (t == core->traits.math_ctraits) b = findMathFunction(tb, multiname, b, argc); else if (t == core->traits.string_itraits) b = findStringFunction(tb, multiname, b, argc); int disp_id = AvmCore::bindingToMethodId(b); MethodInfo* m = tb->getMethod(disp_id); MethodSignaturep mms = m->getMethodSignature(); if (!mms->argcOk(argc)) return false; Traits* resultType = mms->returnTraits(); emitCoerceArgs(m, argc); if (!t->isInterface()) { coder->writeMethodCall(state, pc, OP_callmethod, m, disp_id, argc, resultType); if (opcode == OP_callpropvoid) coder->write(state, pc, OP_pop); } else { // NOTE when the interpreter knows how to dispatch through an // interface, we can rewrite this call as a 'writeOp2'. coder->writeMethodCall(state, pc, opcode, m, 0, argc, resultType); } state->pop_push(n, resultType); if (opcode == OP_callpropvoid) { state->pop(); } return true; } bool Verifier::emitCallpropertySlot(AbcOpcode opcode, int& sp, Traits* t, Binding b, uint32_t argc, const uint8_t *pc) { if (!AvmCore::isSlotBinding(b)) return false; if ((argc != 1) && (argc != 4)) return false; const TraitsBindingsp tb = t->getTraitsBindings(); int slot_id = AvmCore::bindingToSlotId(b); Traits* slotType = tb->getSlotTraits(slot_id); bool argCheck = ((argc == 4) FLOAT_ONLY(&& (slotType != core->traits.float4_ctraits))); if (argCheck) return false; if (slotType == core->traits.int_ctraits) { coder->write(state, pc, OP_convert_i, INT_TYPE); state->setType(sp, INT_TYPE, true); } else if (slotType == core->traits.uint_ctraits) { coder->write(state, pc, OP_convert_u, UINT_TYPE); state->setType(sp, UINT_TYPE, true); } else if (slotType == core->traits.number_ctraits) { coder->write(state, pc, OP_convert_d, NUMBER_TYPE); state->setType(sp, NUMBER_TYPE, true); } else #ifdef VMCFG_FLOAT if (slotType == core->traits.float_ctraits) { coder->write(state, pc, OP_convert_f, FLOAT_TYPE); state->setType(sp, FLOAT_TYPE, true); } else if (slotType == core->traits.float4_ctraits) { if(argc == 1) { coder->write(state, pc, OP_convert_f4, FLOAT4_TYPE); state->setType(sp, FLOAT4_TYPE, true); } else { AvmAssert(argc == 4); AvmAssert(opcode != OP_callpropvoid); coder->writeCoerceToFloat4(state, sp); state->setType(sp, FLOAT4_TYPE, true); } } else #endif if (slotType == core->traits.boolean_ctraits) { coder->write(state, pc, OP_convert_b, BOOLEAN_TYPE); state->setType(sp, BOOLEAN_TYPE, true); } else if (slotType == core->traits.string_ctraits) { coder->write(state, pc, OP_convert_s, STRING_TYPE); state->setType(sp, STRING_TYPE, true); } else // NOTE the following has been refactored so that both lir and wc coerce. previously // wc would be skipped and fall back on the method call the the class converter if (slotType && slotType->base == CLASS_TYPE && slotType->getCreateClassClosureProc() == NULL) { // is this a user defined class? A(1+ args) means coerce to A AvmAssert(slotType->itraits != NULL); FrameValue &v = state->value(sp); coder->write(state, pc, OP_coerce, slotType->itraits); state->setType(sp, slotType->itraits, v.notNull); } else { return false; } if (opcode == OP_callpropvoid) { AvmAssert(argc == 1); coder->write(state, pc, OP_pop); // result coder->write(state, pc, OP_pop); // function state->pop(2); } else { FrameValue v = state->stackTop(); // NOTE writeNip is necessary until lir optimizes the "nip" // case to avoid the extra copies that result from swap+pop coder->writeNip(state, pc, argc); state->pop(argc + 1); state->push(v); } return true; } void Verifier::emitFindProperty(AbcOpcode opcode, Multiname& multiname, uint32_t imm30, const uint8_t *pc) { bool skip_translation = false; const ScopeTypeChain* scope = info->declaringScope(); if (multiname.isBinding()) { int scope_base = ms->scope_base(); int base = scope_base; int index = base + state->scopeDepth - 1; if (scope->size == 0) { // if scope->size = 0, then global is a local // scope, and we dont want to early bind to global. base++; } for (; index >= base; index--) { FrameValue& v = state->value(index); Binding b = toplevel->getBinding(v.traits, &multiname); if (b != BIND_NONE) { coder->writeOp1(state, pc, OP_getscopeobject, index - scope_base); state->push(v); return; } if (v.isWith) break; // with scope could have dynamic property } if (index < base) { // look at captured scope types for (index = scope->size-1; index > 0; index--) { Traits* t = scope->getScopeTraitsAt(index); Binding b = toplevel->getBinding(t, &multiname); if (b != BIND_NONE) { coder->writeOp1(state, pc, OP_getouterscope, index); state->push(t, true); return; } if (scope->getScopeIsWithAt(index)) break; // with scope could have dynamic property } if (index <= 0) { // look at import table for a suitable script MethodInfo* script = core->domainMgr()->findScriptInPoolByMultiname(pool, multiname); if (script != (MethodInfo*)BIND_NONE && script != (MethodInfo*)BIND_AMBIGUOUS) { // If the script has already failed verification, it may // have a null declaringTraits. if (script->declaringTraits() == NULL) verifyFailed(kCorruptABCError); if (script == info) { // ISSUE what if there is an ambiguity at runtime? is VT too early to bind? // its defined here, use getscopeobject 0 if (scope->size > 0) { coder->writeOp1(state, pc, OP_getouterscope, 0); } else { coder->write(state, pc, OP_getglobalscope); } } else // found a single matching traits { coder->writeOp1(state, pc, OP_finddef, imm30, script->declaringTraits()); } state->push(script->declaringTraits(), true); return; } else { switch (opcode) { case OP_findproperty: coder->writeOp1(state, pc, OP_findpropglobal, imm30); break; case OP_findpropstrict: coder->writeOp1(state, pc, OP_findpropglobalstrict, imm30); break; default: AvmAssert(false); break; } skip_translation = true; } } } } uint32_t n=1; checkPropertyMultiname(n, multiname); if (!skip_translation) coder->writeOp1(state, pc, opcode, imm30, OBJECT_TYPE); state->pop_push(n-1, OBJECT_TYPE, true); } void Verifier::emitGetProperty(Multiname &multiname, int n, uint32_t imm30, const uint8_t *pc) { FrameValue& obj = state->peek(n); Binding b = toplevel->getBinding(obj.traits, &multiname); Traits* propType = readBinding(obj.traits, b); emitCheckNull(state->sp()-(n-1)); // early bind slot if (AvmCore::isSlotBinding(b)) { // Bugzilla 705756: If we can mark known-notnull definitions as // notnull then finddef calls will be removed by the optimizer. // // *** DO NOT MERGE TO TAMARIN-REDUX WITHOUT CONSULTING FELIX *** bool notNull = false; if (obj.traits->pool == core->builtinPool) { // Global object holding the built-in types Stringp nm = multiname.getName(); if (FLOAT_ONLY(nm == core->kfloat4 || nm == core->kfloat ||) nm == core->kMath || nm == core->kNumber) { // We know those bindings not to be null values notNull = true; } } coder->writeOp1(state, pc, OP_getslot, AvmCore::bindingToSlotId(b), propType); state->pop_push(n, propType, notNull); return; } // early bind accessor if (AvmCore::hasGetterBinding(b)) { // Invoke the getter int disp_id = AvmCore::bindingToGetterId(b); const TraitsBindingsp objtd = obj.traits->getTraitsBindings(); MethodInfo *f = objtd->getMethod(disp_id); AvmAssert(f != NULL); emitCoerceArgs(f, 0); coder->writeOp2(state, pc, OP_getproperty, imm30, n, propType); AvmAssert(propType == f->getMethodSignature()->returnTraits()); state->pop_push(n, propType); return; } if( !propType ) { if (obj.traits == VECTORINT_TYPE || obj.traits == VECTORUINT_TYPE || obj.traits == VECTORDOUBLE_TYPE #ifdef VMCFG_FLOAT || obj.traits == VECTORFLOAT_TYPE || obj.traits == VECTORFLOAT4_TYPE #endif ) { bool attr = multiname.isAttr(); Traits* indexType = state->value(state->sp()).traits; // NOTE a dynamic name should have the same version as the current pool bool maybeIntegerIndex = !attr && multiname.isRtname() && multiname.containsAnyPublicNamespace(); if( maybeIntegerIndex && (indexType == UINT_TYPE || indexType == INT_TYPE || indexType == NUMBER_TYPE #ifdef VMCFG_FLOAT || indexType == FLOAT_TYPE #endif ) ) { if(obj.traits == VECTORINT_TYPE) propType = INT_TYPE; else if(obj.traits == VECTORUINT_TYPE) propType = UINT_TYPE; else if(obj.traits == VECTORDOUBLE_TYPE) propType = NUMBER_TYPE; #ifdef VMCFG_FLOAT else if(obj.traits == VECTORFLOAT_TYPE) propType = FLOAT_TYPE; else if(obj.traits == VECTORFLOAT4_TYPE) propType = FLOAT4_TYPE; #endif // VMCFG_FLOAT } } else if (obj.traits != NULL && obj.traits->subtypeof(VECTOROBJ_TYPE) && abc_env->codeContext()->bugCompatibility()->bugzilla678952) { // We version this because it affects verifier semantics, see comments // Bugzilla 678952. The versioning is static, however: we only want to // know the compatibility settings for the code, not for the calling context. // If the ABC was compiled for SWFn then it gets SWFn verifier treatment // in every context. Builtin ABC code is compiled with the latest SWF version // and always gets the optimization; that that is correct requires some // careful argumentation, again see Bugzilla 678952. bool attr = multiname.isAttr(); Traits* indexType = state->value(state->sp()).traits; // NOTE a dynamic name should have the same version as the current pool bool maybeIntegerIndex = !attr && multiname.isRtname() && multiname.containsAnyPublicNamespace(); if( maybeIntegerIndex && (indexType == UINT_TYPE || indexType == INT_TYPE || indexType == NUMBER_TYPE #ifdef VMCFG_FLOAT || indexType == FLOAT_TYPE #endif ) ) { propType = obj.traits->m_paramTraits; } } } // default - do getproperty at runtime coder->writeOp2(state, pc, OP_getproperty, imm30, n, propType); state->pop_push(n, propType); } Traits* Verifier::checkGetGlobalScope() { const ScopeTypeChain* scope = info->declaringScope(); int captured_depth = scope->size; if (captured_depth > 0) { // enclosing scope Traits* t = scope->getScopeTraitsAt(0); state->push(t, true); return t; } else { // local scope if (state->scopeDepth == 0) verifyFailed(kGetScopeObjectBoundsError, core->toErrorString(0)); Traits* t = state->scopeValue(0).traits; state->push(state->scopeValue(0)); return t; } } FrameState *Verifier::getFrameState(const uint8_t* pc) const { return blockStates ? blockStates->map.get(pc) : NULL; } bool Verifier::hasFrameState(const uint8_t* pc) const { return blockStates && blockStates->map.containsKey(pc); } int Verifier::getBlockCount() const { return blockStates ? blockStates->map.length() : 0; } const uint8_t* Verifier::getTryFrom() const { return tryFrom; } const uint8_t* Verifier::getTryTo() const { return tryTo; } void Verifier::emitCheckNull(int i) { FrameValue& value = state->value(i); if (!value.notNull) { coder->writeCheckNull(state, i); value.notNull = true; } } void Verifier::checkCallMultiname(AbcOpcode /*opcode*/, Multiname* name) const { if (name->isAttr()) { StringBuffer sb(core); sb << *name; verifyFailed(kIllegalOpMultinameError, core->toErrorString(name), sb.toString()); } } Traits* Verifier::emitCoerceSuper(int index) { Traits* base = info->declaringTraits()->base; if (base != NULL) { emitCoerce(base, index); } else { verifyFailed(kIllegalSuperCallError, core->toErrorString(info)); } return base; } void Verifier::emitCoerce(Traits* target, int index) { FrameValue &v = state->value(index); coder->writeCoerce(state, index, target); state->setType(index, target, v.notNull); } void Verifier::emitCoerceToNumeric(int index) { coder->writeCoerceToNumeric(state, index); state->setType(index, OBJECT_TYPE, true); } Traits* Verifier::peekType(Traits* requiredType, int n) { Traits* t = state->peek(n).traits; if (t != requiredType) { verifyFailed(kIllegalOperandTypeError, core->toErrorString(t), core->toErrorString(requiredType)); } return t; } void Verifier::checkEarlySlotBinding(Traits* t) { // early slot binding is only allowed if the method in question comes // from the same ABC as the Traits being accessed. if (!t || t->pool != info->pool() || !t->allowEarlyBinding()) verifyFailed(kIllegalEarlyBindingError, core->toErrorString(t)); } void Verifier::emitCoerceArgs(MethodInfo* m, int argc) { if (!m->isResolved()) m->resolveSignature(toplevel); MethodSignaturep mms = m->getMethodSignature(); if (!mms->argcOk(argc)) { verifyFailed(kWrongArgumentCountError, core->toErrorString(m), core->toErrorString(mms->requiredParamCount()), core->toErrorString(argc)); } // coerce parameter types int n=1; while (argc > 0) { Traits* target = (argc <= mms->param_count()) ? mms->paramTraits(argc) : NULL; emitCoerce(target, state->sp()-(n-1)); argc--; n++; } // coerce receiver type emitCoerce(mms->paramTraits(0), state->sp()-(n-1)); } void Verifier::checkStack(uint32_t pop, uint32_t push) { if (uint32_t(state->stackDepth) < pop) verifyFailed(kStackUnderflowError); if (state->stackDepth - pop + push > uint32_t(ms->max_stack())) verifyFailed(kStackOverflowError); } void Verifier::checkStackMulti(uint32_t pop, uint32_t push, Multiname* m) { if (m->isRtname()) pop++; if (m->isRtns()) pop++; checkStack(pop,push); } FrameValue& Verifier::checkLocal(int local) { if (local < 0 || local >= ms->local_count()) verifyFailed(kInvalidRegisterError, core->toErrorString(local)); return state->value(local); } Traits* Verifier::checkSlot(Traits *traits, int imm30) { uint32_t slot = imm30; if (traits) traits->resolveSignatures(toplevel); TraitsBindingsp td = traits ? traits->getTraitsBindings() : NULL; const uint32_t count = td ? td->slotCount : 0; if (!traits || slot >= count) { verifyFailed(kSlotExceedsCountError, core->toErrorString(slot+1), core->toErrorString(count), core->toErrorString(traits)); } return td->getSlotTraits(slot); } Traits* Verifier::readBinding(Traits* traits, Binding b) const { if (traits) { traits->resolveSignatures(toplevel); } else { AvmAssert(AvmCore::bindingKind(b) == BKIND_NONE); } return Traits::readBinding(traits, b); } MethodInfo* Verifier::checkMethodInfo(uint32_t id) { const uint32_t c = pool->methodCount(); if (id >= c) { verifyFailed(kMethodInfoExceedsCountError, core->toErrorString(id), core->toErrorString(c)); } return pool->getMethodInfo(id); } Traits* Verifier::checkClassInfo(uint32_t id) { const uint32_t c = pool->classCount(); if (id >= c) { verifyFailed(kClassInfoExceedsCountError, core->toErrorString(id), core->toErrorString(c)); } return pool->getClassTraits(id); } Traits* Verifier::checkTypeName(uint32_t index) { Multiname name; checkConstantMultiname(index, name); // CONSTANT_Multiname Traits* t = core->domainMgr()->findTraitsInPoolByMultiname(pool, name); if (t == NULL) verifyFailed(kClassNotFoundError, core->toErrorString(&name)); if (t == (Traits*)BIND_AMBIGUOUS) toplevel->throwReferenceError(kAmbiguousBindingError, name); if (name.isParameterizedType()) { core->stackCheck(toplevel); Traits* param_traits = name.getTypeParameter() ? checkTypeName(name.getTypeParameter()) : NULL ; t = pool->resolveParameterizedType(toplevel, t, param_traits); } return t; } void Verifier::verifyFailed(int errorID, Stringp arg1, Stringp arg2, Stringp arg3) const { #ifdef AVMPLUS_VERBOSE if (!secondTry && !verbose) { // capture the verify trace even if verbose is false. Verifier v2(info, ms, toplevel, abc_env, true); v2.verbose = true; v2.tryFrom = tryFrom; v2.tryTo = tryTo; CodeWriter stubWriter; // The second verification pass will presumably always throw an // error, which we ignore. But we /must/ catch it so that we can // clean up the verifier resources. Cleanup happens automatically // when execution reaches the end of the block. TRY(core, kCatchAction_Ignore) { v2.verify(&stubWriter); } CATCH(Exception *ignored) { (void)ignored; } END_CATCH END_TRY } #endif toplevel->throwVerifyError(errorID, arg1, arg2, arg3); // This function throws, and should never return. AvmAssert(false); } /** * Insert f into list in abc_pc order. Use a linear search for the * right position. Keeping the worklist sorted avoids pathologically * revisiting the same block too many times as long as predecessors * generally come before successors in ABC order. */ void requeue(FrameState* f, FrameState** list) { while (*list && (*list)->abc_pc < f->abc_pc) list = &(*list)->wl_next; f->wl_next = *list; *list = f; f->wl_pending = true; } // Merge the current FrameState (this->state) with the target // FrameState (getFrameState(target)), and report verify errors. // Fixme: Bug 558876 - |current| must not be dereferenced, it could point // outside the valid range of bytecodes. Its only for back-edge detection. void Verifier::checkTarget(const uint8_t* current, const uint8_t* target, bool isExceptionEdge) { if (emitPass) { AvmAssert(hasFrameState(target)); return; } // branches must stay inside code, and back edges must land on an OP_label, // or a location already known as a forward-branch target if (target < code_pos || target >= code_pos+code_length || (target <= current && !hasFrameState(target) && *target != OP_label)) { verifyFailed(kInvalidBranchTargetError); } FrameState *targetState = getFrameState(target); bool targetChanged; if (!targetState) { if (blockStates != NULL && target > current) { // If we're jumping forward to an instruction with no FrameState, // we need to re-verify the block that contains it to merge FrameStates. // This is a conservative approach: roughly, // // if (target is new) and (branch < nearest < target) and (nearest not queued) then queue(nearest) // // this is suboptimal in that we may guess wrong and requeue blocks that don't need // reverification, but this is theoretically harmless, just extra work. // int i = blockStates->map.findNear(target); // i too large should be impossible, but i < 0 is possible, if the insertion point // was before the first block. In that case, just ignore it, since we only // need to requeue if it's possible that an existing block has already been // verified once; in this case no such block could exist. AvmAssert(i < blockStates->map.length()); // (But let's check anyway...) if (i >= 0 && i < blockStates->map.length()) { // If the block we find in the table is in between the // position of the branch and the position of the target, // requeue. (And also, if it's already pending, don't bother.) FrameState* existingState = blockStates->map.at(i); if (current < existingState->abc_pc && existingState->abc_pc < target && !existingState->wl_pending) { #ifdef AVMPLUS_VERBOSE if (verbose) { core->console << "------------------------------------\n"; core->console << "RE-QUEUE B" << int(existingState->abc_pc - code_pos) << ":"; } #endif existingState->wl_pending = true; existingState->wl_next = worklist; worklist = existingState; // no return: we want to fall thru and create the new blockstate as well. } } } if (!blockStates) blockStates = new (core->GetGC()) BlockStatesType(core->GetGC()); targetState = mmfx_new(FrameState(ms, info)); targetState->abc_pc = target; blockStates->map.put(target, targetState); // first time visiting target block targetChanged = true; AvmAssert(!state->targetOfBackwardsBranch); AvmAssert(!state->targetOfExceptionBranch); targetState->init(state); #ifdef AVMPLUS_VERBOSE if (verbose) { core->console << "------------------------------------\n"; StringBuffer buf(core); buf << "MERGE FIRST B" << int(targetState->abc_pc - code_pos) << ":"; printState(buf, targetState); core->console << "------------------------------------\n"; } #endif } else { targetChanged = mergeState(targetState); } bool targetOfBackwardsBranch = targetState->targetOfBackwardsBranch || target <= current; if (targetOfBackwardsBranch != targetState->targetOfBackwardsBranch) targetChanged |= true; targetState->targetOfBackwardsBranch = targetOfBackwardsBranch; bool targetOfExceptionBranch = targetState->targetOfExceptionBranch || isExceptionEdge; if (targetOfExceptionBranch != targetState->targetOfExceptionBranch) targetChanged |= true; targetState->targetOfExceptionBranch = targetOfExceptionBranch; if (targetChanged && !targetState->wl_pending) requeue(targetState, &worklist); } bool Verifier::mergeState(FrameState* targetState) { #ifdef AVMPLUS_VERBOSE if (verbose) { core->console << "------------------------------------\n"; StringBuffer buf(core); buf << "MERGE CURRENT " << int(state->abc_pc - code_pos) << ":"; printState(buf, state); buf.reset(); buf << "MERGE TARGET B" << int(targetState->abc_pc - code_pos) << ":"; printState(buf, targetState); } #endif // check matching stack depth if (state->stackDepth != targetState->stackDepth) verifyFailed(kStackDepthUnbalancedError, core->toErrorString((int)state->stackDepth), core->toErrorString((int)targetState->stackDepth)); // check matching scope chain depth if (state->scopeDepth != targetState->scopeDepth) verifyFailed(kScopeDepthUnbalancedError, core->toErrorString(state->scopeDepth), core->toErrorString(targetState->scopeDepth)); // Merge types of locals, scopes, and operands. // Merge could preserve common interfaces even when // common supertype does not: // class A implements I {} // class B implements I {} // var i:I = b ? new A : new B // Doing so would require different specification for verify-time analysis, // essentially a differnet ABC spec, yet each abc version needs predictable // verifier semantics. // On the other hand, later optimization passes are free to be as accurate as // they like, if it produces better code. bool targetChanged = false; const int scopeTop = ms->scope_base() + targetState->scopeDepth; const int stackTop = ms->stack_base() + targetState->stackDepth; for (int i=0, n=stackTop; i < n; i++) { // ignore empty locations between scopeTop and stackBase if (i >= scopeTop && i < ms->stack_base()) continue; const FrameValue& curValue = state->value(i); FrameValue& targetValue = targetState->value(i); if (curValue.isWith != targetValue.isWith) { // failure: pushwith on one edge, pushscope on other edge, cannot merge. verifyFailed(kCannotMergeTypesError, core->toErrorString(targetValue.traits), core->toErrorString(curValue.traits)); } Traits* merged_traits = findCommonBase(targetValue.traits, curValue.traits); bool merged_notNull = targetValue.notNull && curValue.notNull; if (targetValue.traits != merged_traits || targetValue.notNull != merged_notNull) targetChanged = true; #ifdef VMCFG_NANOJIT uint16_t merged_sst = targetValue.sst_mask | curValue.sst_mask; // String+null and Namespace+null are legal but must be special-cased // here. The SlotStorageType for NULL_TYPE is SST_scriptobject, // which will leave two bits set in merged_sst. Erase the SST_scriptobject bit. // Any other type merged with null already works, as does String|Namespace // merged with any other type. const uint16_t str_or_obj = (1 << SST_string) | (1 << SST_scriptobject); const uint16_t ns_or_obj = (1 << SST_namespace) | (1 << SST_scriptobject); if ((merged_traits == STRING_TYPE && merged_sst == str_or_obj) || (merged_traits == NAMESPACE_TYPE && merged_sst == ns_or_obj)) { AvmAssert(targetValue.traits == NULL_TYPE || curValue.traits == NULL_TYPE); merged_sst &= ~(1 << SST_scriptobject); } if (targetValue.sst_mask != merged_sst) targetChanged = true; targetValue.sst_mask = merged_sst; #endif targetValue.traits = merged_traits; targetValue.notNull = merged_notNull; } #ifdef AVMPLUS_VERBOSE if (verbose) { StringBuffer buf(core); buf << "AFTER MERGE B" << int(targetState->abc_pc - code_pos) << ":"; printState(buf, targetState); core->console << "------------------------------------\n"; } #endif return targetChanged; } /** * find common base class of these two types */ Traits* Verifier::findCommonBase(Traits* t1, Traits* t2) { if (t1 == t2) return t1; if (t1 == NULL) { // assume t1 is always non-null Traits *temp = t1; t1 = t2; t2 = temp; } // okay to merge null with pointer type if (Traits::getBuiltinType(t1) == BUILTIN_null && t2 && !t2->isMachineType()) return t2; if (Traits::getBuiltinType(t2) == BUILTIN_null && t1 && !t1->isMachineType()) return t1; // all commonBase flags start out false. set the cb bits on // t1 and its ancestors. Traits* t = t1; do t->commonBase = true; while ((t = t->base) != NULL); // now search t2 and its ancestors looking for the first cb=true t = t2; while (t != NULL && !t->commonBase) t = t->base; Traits* common = t; // finally reset the cb bits to false for next time t = t1; do t->commonBase = false; while ((t = t->base) != NULL); return common; } void Verifier::checkStringOperand(uint32_t index) { if (!index || index >= pool->constantStringCount) verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->constantStringCount)); } void Verifier::checkNameOperand(uint32_t index) { if (!index || index >= pool->cpool_mn_offsets.length()) verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->cpool_mn_offsets.length())); } void Verifier::checkConstantMultiname(uint32_t index, Multiname& m) { checkNameOperand(index); pool->parseMultiname(m, index); } Binding Verifier::findMathFunction(TraitsBindingsp math, const Multiname& multiname, Binding b, int argc) { Stringp newname = core->internString(core->concatStrings(core->kUnderscore, multiname.getName())); Binding newb = math->findBinding(newname); if (AvmCore::isMethodBinding(newb)) { int disp_id = AvmCore::bindingToMethodId(newb); MethodInfo* newf = math->getMethod(disp_id); MethodSignaturep newfms = newf->getMethodSignature(); const int param_count = newfms->param_count(); if (argc == param_count) { for (int i=state->stackDepth-argc, n=state->stackDepth; i < n; i++) { Traits* t = state->stackValue(i).traits; if (!t || !t->isNumeric()) return b; } b = newb; } } return b; } Binding Verifier::findStringFunction(TraitsBindingsp str, const Multiname& multiname, Binding b, int argc) { Stringp newname = core->internString(core->concatStrings(core->kUnderscore, multiname.getName())); Binding newb = str->findBinding(newname); if (AvmCore::isMethodBinding(newb)) { int disp_id = AvmCore::bindingToMethodId(newb); MethodInfo* newf = str->getMethod(disp_id); // We have all required parameters but not more than required. MethodSignaturep newfms = newf->getMethodSignature(); const int param_count = newfms->param_count(); const int optional_count = newfms->optional_count(); if ((argc >= (param_count - optional_count)) && (argc <= param_count)) { for (int i=state->stackDepth-argc, k = 1, n=state->stackDepth; i < n; i++, k++) { Traits* t = state->stackValue(i).traits; if (t != newfms->paramTraits(k)) return b; } b = newb; } } return b; } #ifndef SIZE_T_MAX # ifdef SIZE_MAX # define SIZE_T_MAX SIZE_MAX # else # define SIZE_T_MAX UINT_MAX # endif #endif void Verifier::parseExceptionHandlers() { if (info->abc_exceptions()) { AvmAssert(tryFrom && tryTo); return; } const uint8_t* pos = code_pos + code_length; int exception_count = toplevel->readU30(pos); // will be nonnegative and less than 0xC0000000 if (exception_count != 0) { if (exception_count == 0 || (size_t)(exception_count-1) > SIZE_T_MAX / sizeof(ExceptionHandler)) verifyFailed(kIllegalExceptionHandlerError); ExceptionHandlerTable* table = ExceptionHandlerTable::create(core->GetGC(), exception_count); ExceptionHandler *handler = table->exceptions; for (int i=0; i < exception_count; i++, handler++) { handler->from = toplevel->readU30(pos); handler->to = toplevel->readU30(pos); handler->target = toplevel->readU30(pos); const uint8_t* const scopePosInPool = pos; int type_index = toplevel->readU30(pos); Traits* t = type_index ? checkTypeName(type_index) : NULL; Multiname qn; int name_index = pool->hasExceptionSupport() ? toplevel->readU30(pos) : 0; if (name_index != 0) { pool->parseMultiname(qn, name_index); if (!qn.isBinding()) { // abc docs specify that the name is a string but asc generates QNames. // Multinames with no namespaces could be supported, but Tamarin currently can't // handle them in this context. verifyFailed(kCorruptABCError); // the error code could be more precise } } #ifdef AVMPLUS_VERBOSE if (verbose) { core->console << " exception["<console << "\n"; } #endif if (handler->from < 0 || handler->to < handler->from || handler->target < handler->to || handler->target >= code_length) { // illegal range in handler record verifyFailed(kIllegalExceptionHandlerError); } // save maximum try range if (!tryFrom || (code_pos + handler->from) < tryFrom) tryFrom = code_pos + handler->from; if (code_pos + handler->to > tryTo) tryTo = code_pos + handler->to; // note: since we require (code_len > target >= to >= from >= 0), // all implicit exception edges are forward edges. // handler->traits = t WB(core->GetGC(), table, &handler->traits, t); Traits* scopeTraits = name_index == 0 ? OBJECT_TYPE : Traits::newCatchTraits(toplevel, pool, scopePosInPool, qn.getName(), qn.getNamespace()); // handler->scopeTraits = scopeTraits WB(core->GetGC(), table, &handler->scopeTraits, scopeTraits); } info->set_abc_exceptions(core->GetGC(), table); } else { info->set_abc_exceptions(core->GetGC(), NULL); } } #ifdef AVMPLUS_VERBOSE void Verifier::printOpcode(const uint8_t* pc, const uint8_t* code_end) { int offset = int(pc - code_pos); core->console << " " << offset << ':'; core->formatOpcode(core->console, pc, code_end, (AbcOpcode)*pc, offset, pool); core->console << '\n'; } /** * display contents of current stack frame only. */ void Verifier::printState(StringBuffer& prefix, FrameState *state) { PrintWriter& out = core->console; if (prefix.length() > 0) { char buf[80]; // plenty for currently known prefixes VMPI_sprintf(buf, "%-23s[", prefix.c_str()); out << buf; } else { out << " ["; } // locals int scope_base = ms->scope_base(); for (int i=0, n = scope_base; i < n; i++) { printValue(state->value(i)); if (i+1 < n) out << ' '; } out << "] {"; // scope chain for (int i = scope_base, n = scope_base + state->scopeDepth; i < n; i++) { printValue(state->value(i)); if (i+1 < n) out << ' '; } out << "} ("; // stack int stackStart; const int stackLimit = 20; // don't display more than this, to reduce verbosity if (state->stackDepth > stackLimit) { stackStart = ms->stack_base() + state->stackDepth - stackLimit; out << "..." << stackStart << ": "; } else { stackStart = ms->stack_base(); } for (int i = stackStart, n = ms->stack_base() + state->stackDepth; i < n; i++) { printValue(state->value(i)); if (i+1 < n) out << ' '; } out << ")"; // branch target info if (state->targetOfBackwardsBranch) out << " B"; if (state->targetOfExceptionBranch) out << " E"; out << "\n"; } /** display contents of a captured scope chain */ void Verifier::printScope(const char* title, const ScopeTypeChain* scope) { PrintWriter& out = core->console; out << " " << title << " = "; if (scope && scope->size > 0) { out << '['; for (int i=0, n=scope->size; i < n; i++) { Traits* t = scope->getScopeTraitsAt(i); if (!t) out << "*!"; else out << t; if (i+1 < n) out << ' '; } out << "]\n"; } else { out << "null\n"; } } void Verifier::printValue(FrameValue& v) { Traits* t = v.traits; PrintWriter& out = core->console; out << t; if (!t || (!t->isNumeric() && t != BOOLEAN_TYPE && t != NULL_TYPE && t != VOID_TYPE)) out << (v.notNull ? "~" : ""); #ifdef VMCFG_NANOJIT if (v.sst_mask) { out << '['; if (v.sst_mask & (1 << SST_atom)) out << 'A'; if (v.sst_mask & (1 << SST_string)) out << 'S'; if (v.sst_mask & (1 << SST_namespace)) out << 'N'; if (v.sst_mask & (1 << SST_scriptobject)) out << 'O'; if (v.sst_mask & (1 << SST_int32)) out << 'I'; if (v.sst_mask & (1 << SST_uint32)) out << 'U'; if (v.sst_mask & (1 << SST_bool32)) out << 'B'; if (v.sst_mask & (1 << SST_double)) out << 'D'; #ifdef VMCFG_FLOAT if (v.sst_mask & (1 << SST_float)) out << 'F'; if (v.sst_mask & (1 << SST_float4)) out << "F4"; #endif out << ']'; } #endif } #endif /* AVMPLUS_VERBOSE */ }