Mozilla/mozilla/js/tamarin/core/Verifier.cpp
wsharp%adobe.com 039e5a464d bug 375561. stejohns+ review. Various fixes from Flash player codebase
git-svn-id: svn://10.0.0.236/trunk@222472 18797224-902f-48f8-a5cc-f745e15eee43
2007-03-27 18:37:45 +00:00

3337 lines
84 KiB
C++

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is [Open Source Virtual Machine.].
*
* The Initial Developer of the Original Code is
* Adobe System Incorporated.
* Portions created by the Initial Developer are Copyright (C) 2004-2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Adobe AS3 Team
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "avmplus.h"
namespace avmplus
{
#undef DEBUG_EARLY_BINDING
Verifier::Verifier(MethodInfo* info, Toplevel* toplevel
#ifdef AVMPLUS_VERBOSE
, bool secondTry
#endif
)
{
#ifdef AVMPLUS_VERBOSE
this->secondTry = secondTry;
#endif
this->info = info;
this->core = info->core();
this->pool = info->pool;
this->toplevel = toplevel;
#ifdef FEATURE_BUFFER_GUARD
#ifdef AVMPLUS_MIR
this->growthGuard = 0;
#endif //AVMPLUS_MIR
#endif /* FEATURE_BUFFER_GUARD */
#ifdef AVMPLUS_VERBOSE
this->verbose = pool->verbose || (info->flags & AbstractFunction::VERBOSE_VERIFY);
#endif
#ifdef AVMPLUS_PROFILE
if (core->dprof.dprofile)
core->dprof.mark(OP_verifyop);
#endif
const byte* pos = info->body_pos;
max_stack = AvmCore::readU30(pos);
local_count = AvmCore::readU30(pos);
int init_scope_depth = AvmCore::readU30(pos);
int max_scope_depth = AvmCore::readU30(pos);
max_scope = MethodInfo::maxScopeDepth(info, max_scope_depth - init_scope_depth);
stackBase = scopeBase + max_scope;
frameSize = stackBase + max_stack;
if ((init_scope_depth < 0) || (max_scope_depth < 0) || (max_stack < 0) ||
(max_scope < 0) || (local_count < 0) || (frameSize < 0) || (stackBase < 0))
verifyFailed(kCorruptABCError);
code_length = AvmCore::readU30(pos);
code_pos = pos;
pos += code_length;
exceptions_pos = pos;
blockStates = NULL;
state = NULL;
labelCount = 0;
if (info->declaringTraits == NULL)
{
// scope hasn't been captured yet.
verifyFailed(kCannotVerifyUntilReferencedError);
}
}
Verifier::~Verifier()
{
if (blockStates)
{
MMgc::GC* gc = core->GetGC();
for(int i=0, n=blockStates->size(); i<n; i++)
{
FrameState* state = blockStates->at(i);
if (state)
{
gc->Free(state);
}
}
blockStates->clear();
delete blockStates;
}
}
/**
* (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.
* @param pool
* @param info
*/
void Verifier::verify(CodegenMIR *mir)
{
SAMPLE_FRAME("[verify]", core);
#ifdef AVMPLUS_VERBOSE
if (verbose)
core->console << "verify " << info << '\n';
secondTry = false;
#endif
this->mir = mir;
if ( (state = newFrameState()) == 0 ){
verifyFailed(kCorruptABCError);
}
for (int i=0, n=frameSize; i < n; i++)
state->setType(i, NULL);
if (info->flags & AbstractFunction::NEED_REST)
{
// param_count+1 <= max_reg
checkLocal(info->param_count+1);
state->setType(info->param_count+1, ARRAY_TYPE, true);
#ifdef AVMPLUS_VERBOSE
if (verbose && (info->flags & AbstractFunction::NEED_ARGUMENTS))
{
// warning, NEED_ARGUMENTS wins
core->console << "WARNING, NEED_REST overrides NEED_ARGUMENTS when both are set\n";
}
#endif
}
else if (info->flags & AbstractFunction::NEED_ARGUMENTS)
{
// param_count+1 <= max_reg
checkLocal(info->param_count+1);
// E3 style arguments array is an Object, E4 says Array, E4 wins...
state->setType(info->param_count+1, ARRAY_TYPE, true);
}
else
{
// param_count <= max_reg
checkLocal(info->param_count);
}
// initialize method param types.
// We already verified param_count is a legal register so
// don't checkLocal(i) inside the loop.
// AbstractFunction::verify takes care of resolving param&return type
// names to Traits pointers, and resolving optional param default values.
for (int i=0, n=info->param_count; i <= n; i++)
{
bool notNull = (i==0); // this is not null, but other params could be
state->setType(i, info->paramTraits(i), notNull);
}
// initial scope chain types
int outer_depth = 0;
ScopeTypeChain* scope = info->declaringTraits->scope;
if (!scope && info->declaringTraits->init != info)
{
// 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 suppported by AS3. This
// verify error is how we don't let those constructs run.
verifyFailed(kNoScopeError, core->toErrorString(info));
}
state->scopeDepth = outer_depth;
// resolve catch block types
parseExceptionHandlers();
// Save initial state
FrameState* initialState = newFrameState();
if( !initialState->init(state) )
verifyFailed(kCorruptABCError);
#ifdef AVMPLUS_PROFILE
if (core->dprof.dprofile)
core->dprof.mark(OP_verifypass);
#endif
int actualCount = 0;
bool blockEnd = false; // extended basic block ending
// always load the entry state, no merge on second pass
state->init(initialState);
#ifdef FEATURE_BUFFER_GUARD
#ifdef AVMPLUS_MIR
// allow the mir buffer to grow dynamically
GrowthGuard guard(mir ? mir->mirBuffer : NULL);
this->growthGuard = &guard;
#endif //AVMPLUS_MIR
#endif /* FEATURE_BUFFER_GUARD */
TRY(core, kCatchAction_Rethrow){
#ifdef AVMPLUS_INTERP
if (mir)
#endif
{
#ifdef AVMPLUS_MIR
if( !mir->prologue(state) )
{
if (!mir->overflow)
verifyFailed(kCorruptABCError);
// we're out of code memory so try to carry on with interpreter
mir = 0;
this->mir = 0;
}
#endif //AVMPLUS_MIR
}
int size;
for (const byte* pc = code_pos, *code_end=code_pos+code_length; pc < code_end; pc += size)
{
SAMPLE_CHECK();
#ifdef AVMPLUS_PROFILE
if (core->dprof.dprofile)
core->dprof.mark(OP_verifyop);
#endif
if (mir && mir->overflow)
{
mir = 0;
this->mir = 0;
}
AbcOpcode opcode = (AbcOpcode) *pc;
if (opOperandCount[opcode] == -1)
verifyFailed(kIllegalOpcodeError, core->toErrorString(info), core->toErrorString(opcode), core->toErrorString(pc-code_pos));
if (opcode == OP_label)
{
// insert a label here
getFrameState(pc-code_pos)->targetOfBackwardsBranch = true;
}
bool unreachable = false;
FrameState* blockState;
if ( blockStates && (blockState = blockStates->get((uintptr)pc)) != 0 )
{
// send a bbend prior to the merge
if (mir) mir->emitBlockEnd(state);
if (!blockEnd || !blockState->initialized)
{
checkTarget(pc);
}
#ifdef AVMPLUS_VERBOSE
if (verbose)
core->console << "B" << actualCount << ":\n";
#endif
// now load the merged state
state->init(blockState);
state->targetOfBackwardsBranch = blockState->targetOfBackwardsBranch;
state->pc = pc - code_pos;
// found the start of a new basic block
if (mir) mir->emitBlockStart(state);
state->targetOfBackwardsBranch = false;
actualCount++;
blockEnd = false;
if (!blockState->targetOfBackwardsBranch)
{
blockStates->remove((uintptr)pc);
core->GetGC()->Free(blockState);
}
}
else
{
if (blockEnd)
{
// the next instruction is not reachable because it comes after
// a jump, throw, or return, and there are no branch targets
// inbetween the branch/jump/return and this instruction.
// So, don't verify it.
unreachable = true;
}
}
state->pc = pc - code_pos;
int sp = state->sp();
if (info->exceptions)
{
bool mirSavedState = false;
for (int i=0, n=info->exceptions->exception_count; i < n; i++)
{
ExceptionHandler* handler = &info->exceptions->exceptions[i];
if (pc >= code_pos + handler->from && pc <= code_pos + handler->to)
{
// Set the insideTryBlock flag, so the codegen can
// react appropriately.
state->insideTryBlock = true;
// If this instruction can throw exceptions, add an edge to
// the catch handler.
if (opCanThrow[opcode] || pc == code_pos + handler->from)
{
// TODO check stack is empty because catch handlers assume so.
int saveStackDepth = state->stackDepth;
int saveScopeDepth = state->scopeDepth;
state->stackDepth = 0;
state->scopeDepth = outer_depth;
Value stackEntryZero = state->stackValue(0);
#ifdef AVMPLUS_MIR
if (mir && !mirSavedState) {
mir->emitBlockEnd(state);
mirSavedState = true;
}
#endif
// add edge from try statement to catch block
const byte* target = code_pos + handler->target;
// atom received as *, will coerce to correct type in catch handler.
state->push(NULL);
#ifdef AVMPLUS_MIR
if (mir)
mir->localSet(stackBase, mir->exAtom);
#endif
checkTarget(target);
state->pop();
state->stackDepth = saveStackDepth;
state->scopeDepth = saveScopeDepth;
state->stackValue(0) = stackEntryZero;
// Search forward for OP_killreg in the try block
// Set these registers to killed in the catch state
if (pc == code_pos + handler->from)
{
FrameState *catchState = blockStates->get((uintptr)target);
AvmAssert(catchState != 0);
for (const byte *temp = pc; temp <= code_pos + handler->to; )
{
if( *temp == OP_lookupswitch )
{
// Variable length instruction
const byte *temp2 = temp+4;
const int case_count = toplevel->readU30(temp2)+1;
temp += AvmCore::calculateInstructionWidth(temp) + 3*case_count;
}
else
{
// If this instruction is OP_killreg, kill the register
// in the catch block state too
if (*temp == OP_kill)
{
const byte* temp2 = temp+1;
int local = toplevel->readU30(temp2);
if (local >= local_count)
verifyFailed(kInvalidRegisterError, core->toErrorString(local));
Value& v = catchState->value(local);
v.notNull = false;
v.traits = NULL;
v.killed = true;
}
temp += AvmCore::calculateInstructionWidth(temp);
}
}
}
}
}
if (pc == code_pos + handler->target)
{
emitCoerce(handler->traits, sp);
}
}
}
unsigned int imm30=0, imm30b=0;
int imm8=0, imm24=0;
const byte* nextpc = pc;
AvmCore::readOperands(nextpc, imm30, imm24, imm30b, imm8);
// make sure U30 operands are within bounds.
if (opcode != OP_pushshort && ((imm30|imm30b) & 0xc0000000))
{
// don't report error if first operand of abs_jump (pc) is large.
if (opcode != OP_abs_jump || (imm30b & 0xc0000000))
{
verifyFailed(kCorruptABCError);
}
}
size = int(nextpc-pc);
if (pc+size > code_end)
verifyFailed(kLastInstExceedsCodeSizeError);
#ifdef AVMPLUS_PROFILE
if (core->sprof.sprofile)
core->sprof.tally(opcode, size);
#endif
#ifdef AVMPLUS_VERBOSE
if (verbose)
{
showState(state, pc, unreachable);
}
#endif
if (unreachable)
{
// Even if unreachable, we have to properly advance
// past the variable-sized OP_lookupswitch
if (opcode == OP_lookupswitch)
size += 3*(imm30b+1);
continue;
}
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);
int lhs = sp-1;
state->pop();
state->pop();
if (mir)
mir->emitIf(state, opcode, state->pc+size+imm24, lhs, lhs+1);
checkTarget(nextpc+imm24);
break;
}
case OP_iftrue:
case OP_iffalse:
{
checkStack(1,0);
int cond = sp;
emitCoerce(BOOLEAN_TYPE, cond);
state->pop();
if (mir)
mir->emitIf(state, opcode, state->pc+size+imm24, cond, 0);
checkTarget(nextpc+imm24);
break;
}
case OP_jump:
{
//checkStack(0,0)
if (mir) mir->emit(state, opcode, state->pc+size+imm24);
checkTarget(nextpc+imm24); // target block;
blockEnd = true;
break;
}
case OP_lookupswitch:
{
checkStack(1,0);
peekType(INT_TYPE);
const uint32 count = imm30b;
if (mir) mir->emit(state, opcode, state->pc+imm24, count);
state->pop();
checkTarget(pc+imm24);
uint32 case_count = 1 + count;
if (pc + size + 3*case_count > code_end)
verifyFailed(kLastInstExceedsCodeSizeError);
for (uint32 i=0; i < case_count; i++) {
int off = AvmCore::readS24(pc+size);
checkTarget(pc+off);
size += 3;
}
blockEnd = true;
break;
}
case OP_throw:
{
checkStack(1,0);
// [ggrossman] it is legal to throw anything at all; don't check
if (mir) mir->emit(state, opcode, sp);
state->pop();
blockEnd = true;
break;
}
case OP_returnvalue:
{
checkStack(1,0);
if (mir)
{
Traits* returnTraits = info->returnTraits();
emitCoerce(returnTraits, sp);
mir->emit(state, opcode, sp);
}
// make sure stack state is updated, since verifier scans
// straight through to the next block.
state->pop();
blockEnd = true;
break;
}
case OP_returnvoid:
{
//checkStack(1,0)
if (mir) mir->emit(state, opcode);
blockEnd = true;
break;
}
case OP_pushnull:
checkStack(0,1);
if (mir) mir->emitIntConst(state, sp+1, 0);
state->push(NULL_TYPE);
break;
case OP_pushundefined:
checkStack(0,1);
if (mir) mir->emitIntConst(state, sp+1, undefinedAtom);
state->push(VOID_TYPE);
break;
case OP_pushtrue:
checkStack(0,1);
if (mir) mir->emitIntConst(state, sp+1, 1);
state->push(BOOLEAN_TYPE, true);
break;
case OP_pushfalse:
checkStack(0,1);
if (mir) mir->emitIntConst(state, sp+1, 0);
state->push(BOOLEAN_TYPE, true);
break;
case OP_pushnan:
checkStack(0,1);
if (mir) mir->emitDoubleConst(state, sp+1, (double*)(core->kNaN & ~7));
state->push(NUMBER_TYPE, true);
break;
case OP_pushshort:
checkStack(0,1);
if (mir) mir->emitIntConst(state, sp+1, (signed short)imm30);
state->push(INT_TYPE, true);
break;
case OP_pushbyte:
checkStack(0,1);
if (mir) mir->emitIntConst(state, sp+1, (signed char)imm8);
state->push(INT_TYPE, true);
break;
case OP_debugfile:
{
//checkStack(0,0)
#ifdef DEBUGGER
Atom filename = checkCpoolOperand(imm30, kStringType);
if (mir) mir->emit(state, opcode, (uintptr)AvmCore::atomToString(filename));
#endif
break;
}
case OP_dxns:
{
//checkStack(0,0)
if (!info->isFlagSet(AbstractFunction::SETS_DXNS))
verifyFailed(kIllegalSetDxns, core->toErrorString(info));
Atom uri = checkCpoolOperand(imm30, kStringType);
if (mir) mir->emit(state, opcode, (uintptr)AvmCore::atomToString(uri));
break;
}
case OP_dxnslate:
{
checkStack(1,0);
if (!info->isFlagSet(AbstractFunction::SETS_DXNS))
verifyFailed(kIllegalSetDxns, core->toErrorString(info));
// codgeen will call intern on the input atom.
if (mir) mir->emit(state, opcode, sp);
state->pop();
break;
}
case OP_pushstring:
{
checkStack(0,1);
uint32 index = imm30;
if (index > 0 && index < pool->constantStringCount)
{
Stringp value = pool->cpool_string[index];
if (mir)mir->emitIntConst(state, sp+1, (uintptr)value);
state->push(STRING_TYPE, value != NULL);
}
else
{
verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->constantStringCount));
}
break;
}
case OP_pushint:
{
checkStack(0,1);
uint32 index = imm30;
if (index > 0 && index < pool->constantIntCount)
{
if (mir)
{
int value = pool->cpool_int[index];
mir->emitIntConst(state, sp+1, value);
}
state->push(INT_TYPE,true);
}
else
{
verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->constantIntCount));
}
break;
}
case OP_pushuint:
{
checkStack(0,1);
uint32 index = imm30;
if (index > 0 && index < pool->constantUIntCount)
{
if (mir)
{
uint32 value = pool->cpool_uint[index];
mir->emitIntConst(state, sp+1, value);
}
state->push(UINT_TYPE,true);
}
else
{
verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->constantUIntCount));
}
break;
}
case OP_pushdouble:
{
checkStack(0,1);
uint32 index = imm30;
if (index > 0 && index < pool->constantDoubleCount)
{
if (mir)
{
double* p = pool->cpool_double[index];
mir->emitDoubleConst(state, sp+1, p);
}
state->push(NUMBER_TYPE, true);
}
else
{
verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->constantDoubleCount));
}
break;
}
case OP_pushnamespace:
{
checkStack(0,1);
uint32 index = imm30;
if (index > 0 && index < pool->constantNsCount)
{
Namespace* value = pool->cpool_ns[index];
if (mir)mir->emitIntConst(state, sp+1, (uintptr)value);
state->push(NAMESPACE_TYPE, value != NULL);
}
else
{
verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->constantNsCount));
}
break;
}
case OP_setlocal:
{
checkStack(1,0);
checkLocal(imm30);
if (mir) mir->emitCopy(state, sp, imm30);
Value &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:
{
int localno = opcode-OP_setlocal0;
checkStack(1,0);
checkLocal(localno);
if (mir) mir->emitCopy(state, sp, localno);
Value &v = state->stackTop();
state->setType(localno, v.traits, v.notNull);
state->pop();
break;
}
case OP_getlocal:
{
checkStack(0,1);
Value& v = checkLocal(imm30);
if (mir) mir->emitCopy(state, imm30, sp+1);
state->push(v);
break;
}
case OP_getlocal0:
case OP_getlocal1:
case OP_getlocal2:
case OP_getlocal3:
{
int localno = opcode-OP_getlocal0;
checkStack(0,1);
Value& v = checkLocal(localno);
if (mir) mir->emitCopy(state, localno, sp+1);
state->push(v);
break;
}
case OP_kill:
{
//checkStack(0,0)
Value &v = checkLocal(imm30);
if (mir) mir->emitKill(state, imm30);
v.notNull = false;
v.traits = NULL;
break;
}
case OP_inclocal:
case OP_declocal:
{
//checkStack(0,0);
checkLocal(imm30);
emitCoerce(NUMBER_TYPE, imm30);
if (mir)
{
mir->emit(state, opcode, imm30, opcode==OP_inclocal ? 1 : -1, NUMBER_TYPE);
}
break;
}
case OP_inclocal_i:
case OP_declocal_i:
{
//checkStack(0,0);
checkLocal(imm30);
emitCoerce(INT_TYPE, imm30);
if (mir)
{
mir->emit(state, opcode, imm30, opcode==OP_inclocal_i ? 1 : -1, INT_TYPE);
}
break;
}
case OP_newfunction:
{
checkStack(0,1);
AbstractFunction* f = checkMethodInfo(imm30);
f->setParamType(0, NULL);
f->makeIntoPrototypeFunction(toplevel);
Traits* ftraits = f->declaringTraits;
// make sure the traits of the base vtable matches the base traits
// This is also caught by an assert in VTable.cpp, however that turns
// out to be too late and may cause crashes
if( ftraits->base != toplevel->functionClass->ivtable()->traits ){
AvmAssertMsg(0, "Verify failed:OP_newfunction");
if (toplevel->verifyErrorClass() != NULL)
verifyFailed(kInvalidBaseClassError);
}
int extraScopes = state->scopeDepth;
ftraits->scope = ScopeTypeChain::create(core->GetGC(), scope, extraScopes);
for (int i=0,j=scope->size, n=state->scopeDepth; i < n; i++, j++)
{
ftraits->scope->scopes[j].traits = state->scopeValue(i).traits;
ftraits->scope->scopes[j].isWith = state->scopeValue(i).isWith;
}
if (f->activationTraits)
{
// ISSUE - if nested functions, need to capture scope, not make a copy
f->activationTraits->scope = ftraits->scope;
}
#ifdef AVMPLUS_VERIFYALL
if (core->verifyall)
pool->enq(f);
#endif
if (mir)
{
mir->emitSetDxns(state);
mir->emit(state, opcode, imm30, sp+1, ftraits);
}
state->push(ftraits, true);
break;
}
case OP_getlex:
{
if (state->scopeDepth + scope->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);
emitGetProperty(multiname, 1);
break;
}
case OP_findpropstrict:
case OP_findproperty:
{
if (state->scopeDepth + scope->size == 0)
{
verifyFailed(kFindVarWithNoScopeError);
}
Multiname multiname;
checkConstantMultiname(imm30, multiname);
checkStackMulti(0, 1, &multiname);
emitFindProperty(opcode, multiname);
break;
}
case OP_newclass:
{
checkStack(1, 1);
// must be a CONSTANT_Multiname
Traits* ctraits = checkClassInfo(imm30);
// 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;
int captureScopes = state->scopeDepth;
ScopeTypeChain* cscope = ScopeTypeChain::create(core->GetGC(), scope, captureScopes, 1);
int j=scope->size;
for (int i=0, n=state->scopeDepth; i < n; i++, j++)
{
cscope->scopes[j].traits = state->scopeValue(i).traits;
cscope->scopes[j].isWith = state->scopeValue(i).isWith;
}
// add a type constraint for the "this" scope of static methods
cscope->scopes[state->scopeDepth].traits = 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)
verifyFailed(kCorruptABCError);
}
ScopeTypeChain* iscope = ScopeTypeChain::create(core->GetGC(), cscope, 1, 1);
iscope->scopes[iscope->size-1].traits = ctraits;
// add a type constraint for the "this" scope of instance methods
iscope->scopes[iscope->size].traits = itraits;
ctraits->scope = cscope;
itraits->scope = iscope;
ctraits->resolveSignatures(toplevel);
itraits->resolveSignatures(toplevel);
#ifdef AVMPLUS_VERIFYALL
if (core->verifyall)
{
pool->enq(ctraits);
pool->enq(itraits);
}
#endif
// make sure base class is really a class
if (mir)
{
mir->emitSetDxns(state);
emitCoerce(CLASS_TYPE, state->sp());
mir->emit(state, opcode, (uintptr)(void*)pool->cinits[imm30], sp, 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));
}
AbstractFunction* script = (AbstractFunction*)pool->getNamedScript(&multiname);
if (script != (AbstractFunction*)BIND_NONE && script != (AbstractFunction*)BIND_AMBIGUOUS)
{
// found a single matching traits
if (mir) mir->emit(state, opcode, (uintptr)&multiname, sp+1, script->declaringTraits);
state->push(script->declaringTraits, true);
}
else
{
// no traits, or ambiguous reference. use Object, anticipating
// a runtime exception
if (mir) mir->emit(state, opcode, (uintptr)&multiname, sp+1, OBJECT_TYPE);
state->push(OBJECT_TYPE, 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 n=2;
checkPropertyMultiname(n, multiname);
Value& obj = state->peek(n);
int ptrIndex = sp-(n-1);
if (mir) emitCheckNull(ptrIndex);
Binding b = toplevel->getBinding(obj.traits, &multiname);
Traits* propTraits = readBinding(obj.traits, b);
if (AvmCore::isSlotBinding(b) && mir &&
// it's a var, or a const being set from the init function
(!AvmCore::isConstBinding(b) ||
obj.traits->init == info && opcode == OP_initproperty))
{
emitCoerce(propTraits, state->sp());
mir->emit(state, OP_setslot, AvmCore::bindingToSlotId(b), ptrIndex, 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 (mir && AvmCore::hasSetterBinding(b))
{
// early bind to the setter
int disp_id = AvmCore::bindingToSetterId(b);
AbstractFunction *f = obj.traits->getMethod(disp_id);
AvmAssert(f != NULL);
emitCoerceArgs(f, 1);
mir->emitSetContext(state, f);
Traits* result = f->returnTraits();
if (!obj.traits->isInterface)
mir->emitCall(state, OP_callmethod, disp_id, 1, result);
else
mir->emitCall(state, OP_callinterface, f->iid(), 1, result);
state->pop(n);
break;
}
#ifdef DEBUG_EARLY_BINDING
core->console << "verify setproperty " << obj.traits << " " << &multiname->getName() << " from within " << info << "\n";
#endif
// not a var binding or early bindable accessor
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, opcode, (uintptr)&multiname);
}
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 n=1;
checkPropertyMultiname(n, multiname);
emitGetProperty(multiname, n);
break;
}
case OP_getdescendants:
{
// stack in: object [ns] [name]
// stack out: value
Multiname multiname;
checkConstantMultiname(imm30, multiname);
checkStackMulti(1, 1, &multiname);
uint32 n=1;
checkPropertyMultiname(n, multiname);
if (mir)
{
emitCheckNull(sp-(n-1));
mir->emit(state, opcode, (uintptr)&multiname, 0, NULL);
}
state->pop_push(n, NULL);
break;
}
case OP_checkfilter:
{
// stack in: object
// stack out: object
checkStack(1, 1);
if (mir)
{
emitCheckNull(state->sp());
mir->emit(state, opcode, state->sp(), 0, NULL);
}
break;
}
case OP_deleteproperty:
{
Multiname multiname;
checkConstantMultiname(imm30, multiname);
checkStackMulti(1, 1, &multiname);
uint32 n=1;
checkPropertyMultiname(n, multiname);
if (mir)
{
emitCheckNull(sp-(n-1));
mir->emit(state, opcode, (uintptr)&multiname, 0, BOOLEAN_TYPE);
}
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 (!canAssign(t, rhs) || !Traits::isMachineCompatible(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;
if (mir)
mir->emit(state, OP_astype, (uintptr)t, index, resultType);
state->pop_push(1, t);
}
break;
}
case OP_astypelate:
{
checkStack(2,1);
Value& 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;
if (mir) mir->emit(state, opcode, 0, 0, t);
state->pop_push(2, t);
break;
}
case OP_coerce:
{
checkStack(1,1);
// resolve operand into a traits, and push that type.
emitCoerce(checkTypeName(imm30), sp);
break;
}
case OP_convert_b:
case OP_coerce_b:
{
checkStack(1,1);
emitCoerce(BOOLEAN_TYPE, sp);
break;
}
case OP_coerce_o:
{
checkStack(1,1);
emitCoerce(OBJECT_TYPE, sp);
break;
}
case OP_coerce_a:
{
checkStack(1,1);
emitCoerce(NULL, sp);
break;
}
case OP_convert_i:
case OP_coerce_i:
{
checkStack(1,1);
emitCoerce(INT_TYPE, sp);
break;
}
case OP_convert_u:
case OP_coerce_u:
{
checkStack(1,1);
emitCoerce(UINT_TYPE, sp);
break;
}
case OP_convert_d:
case OP_coerce_d:
{
checkStack(1,1);
emitCoerce(NUMBER_TYPE, sp);
break;
}
case OP_coerce_s:
{
checkStack(1,1);
emitCoerce(STRING_TYPE, sp);
break;
}
case OP_istype:
{
checkStack(1,1);
// resolve operand into a traits, and test if value is that type
Traits* itraits = checkTypeName(imm30); // CONSTANT_Multiname
if (mir) mir->emit(state, opcode, (uintptr)itraits, sp, BOOLEAN_TYPE);
state->pop();
state->pop();
state->push(OBJECT_TYPE);
state->push(INT_TYPE);
break;
}
case OP_istypelate:
{
checkStack(2,1);
if (mir) mir->emit(state, opcode, 0, 0, BOOLEAN_TYPE);
// 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.
if (mir)
emitCheckNull(sp);
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)
emitToString(opcode, sp);
break;
}
case OP_callstatic:
{
AbstractFunction* m = checkMethodInfo(imm30);
int method_id = m->method_id;
const uint32 argc = imm30b;
checkStack(argc+1, 1);
if (!m->paramTraits(0))
{
verifyFailed(kDanglingFunctionError, core->toErrorString(m), core->toErrorString(info));
}
#ifdef AVMPLUS_VERIFYALL
if (core->verifyall)
pool->enq(m);
#endif
emitCoerceArgs(m, argc);
Traits* resultType = m->returnTraits();
if (mir)
{
emitCheckNull(sp-argc);
mir->emitSetContext(state, m);
mir->emitCall(state, OP_callstatic, method_id, argc, resultType);
}
state->pop_push(argc+1, resultType);
break;
}
case OP_call:
{
const uint32 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
*/
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, opcode, argc, 0, NULL);
}
state->pop_push(argc+2, NULL);
break;
}
case OP_construct:
{
// in: ctor arg1..N
// out: obj
const uint32 argc = imm30;
checkStack(argc+1, 1);
Traits* ctraits = state->peek(argc+1).traits;
// don't need null check, AvmCore::construct() uses toFunction() for null check.
Traits* itraits = ctraits ? ctraits->itraits : NULL;
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, opcode, argc, 0, itraits);
}
state->pop_push(argc+1, itraits, true);
break;
}
case OP_callmethod:
{
const uint32 argc = imm30b;
const int disp_id = imm30-1;
checkStack(argc+1,1);
if (disp_id >= 0)
{
Value& obj = state->peek(argc+1);
if( !obj.traits ) verifyFailed(kCorruptABCError);
checkEarlyMethodBinding(obj.traits);
AbstractFunction* m = checkDispId(obj.traits, disp_id);
Traits *resultType = m->returnTraits();
if (mir)
{
emitCheckNull(sp-argc);
emitCoerceArgs(m, argc);
mir->emitSetContext(state, m);
if (!obj.traits->isInterface)
mir->emitCall(state, OP_callmethod, disp_id, argc, resultType);
else
mir->emitCall(state, OP_callinterface, m->iid(), argc, resultType);
}
state->pop_push(argc+1, resultType);
}
else
{
verifyFailed(kZeroDispIdError);
}
break;
}
case OP_callproperty:
case OP_callproplex:
case OP_callpropvoid:
{
// stack in: obj [ns [name]] args
// stack out: result
const uint32 argc = imm30b;
Multiname multiname;
checkConstantMultiname(imm30, multiname);
checkStackMulti(argc+1, 1, &multiname);
checkCallMultiname(opcode, &multiname);
uint32 n = argc+1; // index of receiver
checkPropertyMultiname(n, multiname);
Traits* t = state->peek(n).traits;
if (mir) emitCheckNull(sp-(n-1));
Binding b = toplevel->getBinding(t, &multiname);
if (t)
t->resolveSignatures(toplevel);
if (AvmCore::isMethodBinding(b))
{
if (mir)
{
if (t == core->traits.math_ctraits)
{
b = findMathFunction(t, &multiname, b, argc);
}
else if (t == core->traits.string_itraits)
{
b = findStringFunction(t, &multiname, b, argc);
}
}
int disp_id = AvmCore::bindingToMethodId(b);
AbstractFunction* m = t->getMethod(disp_id);
if (m->argcOk(argc))
{
Traits* resultType = m->returnTraits();
if (mir)
{
emitCoerceArgs(m, argc);
mir->emitSetContext(state, m);
if (!t->isInterface)
mir->emitCall(state, OP_callmethod, disp_id, argc, resultType);
else
mir->emitCall(state, OP_callinterface, m->iid(), argc, resultType);
}
state->pop_push(n, resultType);
if (opcode == OP_callpropvoid)
state->pop();
break;
}
}
else if (mir && AvmCore::isSlotBinding(b) && argc == 1)
{
// is it int, number, or uint?
int slot_id = AvmCore::bindingToSlotId(b);
Traits* slotType = t->getSlotTraits(slot_id);
if (slotType == core->traits.int_ctraits)
{
emitCoerce(INT_TYPE, sp);
Value v = state->stackTop();
state->pop();
state->stackTop() = v;
if (opcode == OP_callpropvoid)
state->pop();
break;
}
else if (slotType == core->traits.uint_ctraits)
{
emitCoerce(UINT_TYPE, sp);
Value v = state->stackTop();
state->pop();
state->stackTop() = v;
if (opcode == OP_callpropvoid)
state->pop();
break;
}
else if (slotType == core->traits.number_ctraits)
{
emitCoerce(NUMBER_TYPE, sp);
Value v = state->stackTop();
state->pop();
state->stackTop() = v;
if (opcode == OP_callpropvoid)
state->pop();
break;
}
else if (slotType == core->traits.boolean_ctraits)
{
emitCoerce(BOOLEAN_TYPE, sp);
Value v = state->stackTop();
state->pop();
state->stackTop() = v;
if (opcode == OP_callpropvoid)
state->pop();
break;
}
else if (slotType == core->traits.string_ctraits)
{
emitToString(OP_convert_s, sp);
Value v = state->stackTop();
state->pop();
state->stackTop() = v;
if (opcode == OP_callpropvoid)
state->pop();
break;
}
// is this a user defined class? A(1+ args) means coerce to A
if (slotType && slotType->base == CLASS_TYPE && slotType->getNativeClassInfo() == NULL)
{
AvmAssert(slotType->itraits != NULL);
emitCoerce(slotType->itraits, state->sp());
Value v = state->stackTop();
state->pop();
state->stackTop() = v;
if (opcode == OP_callpropvoid)
state->pop();
break;
}
}
#ifdef DEBUG_EARLY_BINDING
core->console << "verify callproperty " << t << " " << multiname->getName() << " from within " << info << "\n";
#endif
// don't know the binding now, resolve at runtime
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, opcode, (uintptr)&multiname, argc, NULL);
}
state->pop_push(n, NULL);
if (opcode == OP_callpropvoid)
state->pop();
break;
}
case OP_constructprop:
{
// stack in: obj [ns [name]] args
const uint32 argc = imm30b;
Multiname multiname;
checkConstantMultiname(imm30, multiname);
checkStackMulti(argc+1, 1, &multiname);
checkCallMultiname(opcode, &multiname);
uint32 n = argc+1; // index of receiver
checkPropertyMultiname(n, multiname);
Value& obj = state->peek(n); // make sure object is there
if (mir) emitCheckNull(sp-(n-1));
#ifdef DEBUG_EARLY_BINDING
//core->console << "verify constructprop " << t << " " << multiname->getName() << " from within " << info << "\n";
#endif
Binding b = toplevel->getBinding(obj.traits, &multiname);
if (AvmCore::isSlotBinding(b))
{
int slot_id = AvmCore::bindingToSlotId(b);
Traits* ctraits = readBinding(obj.traits, b);
if (mir) mir->emit(state, OP_getslot, slot_id, sp-(n-1), ctraits);
obj.notNull = false;
obj.traits = ctraits;
Traits* itraits = ctraits ? ctraits->itraits : NULL;
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, OP_construct, argc, 0, itraits);
}
state->pop_push(argc+1, itraits, true);
break;
}
// don't know the binding now, resolve at runtime
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, opcode, (uintptr)&multiname, argc, NULL);
}
state->pop_push(n, NULL);
break;
}
case OP_callsuper:
case OP_callsupervoid:
{
// stack in: obj [ns [name]] args
const uint32 argc = imm30b;
Multiname multiname;
checkConstantMultiname(imm30, multiname);
checkStackMulti(argc+1, 1, &multiname);
if (multiname.isAttr())
verifyFailed(kIllegalOpMultinameError, core->toErrorString(&multiname));
uint32 n = argc+1; // index of receiver
checkPropertyMultiname(n, multiname);
if (mir)
emitCheckNull(sp-(n-1));
Traits* base = emitCoerceSuper(sp-(n-1));
Binding b = toplevel->getBinding(base, &multiname);
if (AvmCore::isMethodBinding(b))
{
int disp_id = AvmCore::bindingToMethodId(b);
AbstractFunction* m = base->getMethod(disp_id);
if( !m ) verifyFailed(kCorruptABCError);
emitCoerceArgs(m, argc);
Traits* resultType = m->returnTraits();
if (mir)
{
mir->emitSetContext(state, m);
mir->emitCall(state, OP_callsuperid, disp_id, argc, resultType);
}
state->pop_push(n, resultType);
if (opcode == OP_callsupervoid)
state->pop();
break;
}
#ifdef DEBUG_EARLY_BINDING
core->console << "verify callsuper " << base << " " << multiname->getName() << " from within " << info << "\n";
#endif
// TODO optimize other cases
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, opcode, (uintptr)&multiname, argc, NULL);
}
state->pop_push(n, NULL);
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 n=1;
checkPropertyMultiname(n, multiname);
if (multiname.isAttr())
verifyFailed(kIllegalOpMultinameError, core->toErrorString(&multiname));
int ptrIndex = sp-(n-1);
Traits* base = emitCoerceSuper(ptrIndex);
Binding b = toplevel->getBinding(base, &multiname);
Traits* propType = readBinding(base, b);
if (mir)
{
emitCheckNull(ptrIndex);
if (AvmCore::isSlotBinding(b))
{
int slot_id = AvmCore::bindingToSlotId(b);
if (mir) mir->emit(state, OP_getslot, slot_id, ptrIndex, propType);
state->pop_push(n, propType);
break;
}
if (AvmCore::hasGetterBinding(b))
{
// Invoke the getter
int disp_id = AvmCore::bindingToGetterId(b);
AbstractFunction *f = base->getMethod(disp_id);
AvmAssert(f != NULL);
emitCoerceArgs(f, 0);
Traits* resultType = f->returnTraits();
if (mir)
{
mir->emitSetContext(state, f);
mir->emitCall(state, OP_callsuperid, disp_id, 0, resultType);
}
state->pop_push(n, resultType);
break;
}
}
#ifdef DEBUG_EARLY_BINDING
core->console << "verify getsuper " << base << " " << multiname.getName() << " from within " << info << "\n";
#endif
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, opcode, (uintptr)&multiname, 0, propType);
}
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 n=2;
checkPropertyMultiname(n, multiname);
if (multiname.isAttr())
verifyFailed(kIllegalOpMultinameError, core->toErrorString(&multiname));
int ptrIndex = sp-(n-1);
Traits* base = emitCoerceSuper(ptrIndex);
if (mir)
{
emitCheckNull(ptrIndex);
Binding b = toplevel->getBinding(base, &multiname);
Traits* propType = readBinding(base, b);
if (AvmCore::isSlotBinding(b))
{
if (AvmCore::isVarBinding(b))
{
int slot_id = AvmCore::bindingToSlotId(b);
emitCoerce(propType, sp);
mir->emit(state, OP_setslot, slot_id, ptrIndex);
}
// else, it's a readonly slot so ignore
state->pop(n);
break;
}
if (AvmCore::isAccessorBinding(b))
{
if (AvmCore::hasSetterBinding(b))
{
// Invoke the setter
int disp_id = AvmCore::bindingToSetterId(b);
AbstractFunction *f = base->getMethod(disp_id);
AvmAssert(f != NULL);
emitCoerceArgs(f, 1);
mir->emitSetContext(state, f);
mir->emitCall(state, OP_callsuperid, disp_id, 1, f->returnTraits());
}
// else, ignore write to readonly accessor
state->pop(n);
break;
}
#ifdef DEBUG_EARLY_BINDING
core->console << "verify setsuper " << base << " " << multiname.getName() << " from within " << info << "\n";
#endif
mir->emitSetContext(state, NULL);
mir->emit(state, opcode, (uintptr)&multiname);
}
state->pop(n);
break;
}
case OP_constructsuper:
{
// stack in: obj, args ...
const uint32 argc = imm30;
checkStack(argc+1, 0);
int ptrIndex = sp-argc;
Traits* baseTraits = emitCoerceSuper(ptrIndex); // check receiver
AbstractFunction *f = baseTraits->init;
AvmAssert(f != NULL);
emitCoerceArgs(f, argc);
if (mir)
{
mir->emitSetContext(state, f);
emitCheckNull(ptrIndex);
mir->emitCall(state, opcode, 0, argc, VOID_TYPE);
}
state->pop(argc+1);
// this is a true void call, no result to push.
break;
}
case OP_newobject:
{
uint32 argc = imm30;
checkStack(2*argc, 1);
int n=0;
while (argc-- > 0)
{
n += 2;
peekType(STRING_TYPE, n); // name; will call intern on it
}
if (mir) mir->emit(state, opcode, imm30, 0, OBJECT_TYPE);
state->pop_push(n, OBJECT_TYPE, true);
break;
}
case OP_newarray:
{
const uint32 argc = imm30;
checkStack(argc, 1);
if (mir) mir->emit(state, opcode, argc, 0, ARRAY_TYPE);
state->pop_push(argc, ARRAY_TYPE, true);
break;
}
case OP_pushscope:
{
checkStack(1,0);
Traits* scopeTraits = state->peek().traits;
if (state->scopeDepth+1 <= max_scope)
{
if (scope->fullsize > (scope->size+state->scopeDepth))
{
// extra constraints on type of pushscope allowed
Traits* requiredType = scope->scopes[scope->size+state->scopeDepth].traits;
if (!scopeTraits || !scopeTraits->containsInterface(requiredType))
{
verifyFailed(kIllegalOperandTypeError, core->toErrorString(scopeTraits), core->toErrorString(requiredType));
}
}
if (mir)
{
emitCheckNull(sp);
mir->emitCopy(state, sp, scopeBase+state->scopeDepth);
}
state->pop();
state->setType(scopeBase+state->scopeDepth, scopeTraits, true, false);
state->scopeDepth++;
}
else
{
verifyFailed(kScopeStackOverflowError);
}
break;
}
case OP_pushwith:
{
checkStack(1,0);
Traits* scopeTraits = state->peek().traits;
if (state->scopeDepth+1 <= max_scope)
{
if (mir)
{
emitCheckNull(sp);
mir->emitCopy(state, sp, scopeBase+state->scopeDepth);
}
state->pop();
state->setType(scopeBase+state->scopeDepth, scopeTraits, true, true);
if (state->withBase == -1)
{
state->withBase = state->scopeDepth;
}
state->scopeDepth++;
}
else
{
verifyFailed(kScopeStackOverflowError);
}
break;
}
case OP_newactivation:
{
if (!(info->flags & AbstractFunction::NEED_ACTIVATION))
{
verifyFailed(kInvalidNewActivationError);
}
//AvmAssert(!info->activationTraits->dynamic);
// [ed] does the vm really care if an activation object is dynamic or not?
checkStack(0, 1);
if (mir) mir->emit(state, opcode, 0, 0, info->activationTraits);
state->push(info->activationTraits, true);
break;
}
case OP_newcatch:
{
const int index = imm30;
/*if (!(info->flags & AbstractFunction::NEED_ACTIVATION))
{
verifyFailed(kInvalidNewActivationError);
}*/
if (index < 0 || !info->exceptions || index >= info->exceptions->exception_count)
{
// todo better error msg
verifyFailed(kInvalidNewActivationError);
}
ExceptionHandler* handler = &info->exceptions->exceptions[index];
checkStack(0, 1);
if (mir) mir->emit(state, opcode, 0, 0, handler->scopeTraits);
state->push(handler->scopeTraits, true);
break;
}
case OP_popscope:
{
//checkStack(0,0)
// no code to emit
if (state->scopeDepth-- <= outer_depth)
{
verifyFailed(kScopeStackUnderflowError);
}
#ifdef DEBUGGER
if (mir) mir->emitKill(state, scopeBase + state->scopeDepth);
#endif
if (state->withBase >= state->scopeDepth)
{
state->withBase = -1;
}
break;
}
case OP_getscopeobject:
{
checkStack(0,1);
int scope_index = imm8;
// local scope
if (scope_index < state->scopeDepth)
{
if (mir) mir->emitCopy(state, scopeBase+scope_index, sp+1);
// this will copy type and all attributes too
state->push(state->scopeValue(scope_index));
}
else
{
verifyFailed(kGetScopeObjectBoundsError, core->toErrorString(imm8));
}
break;
}
case OP_getglobalscope:
{
checkStack(0,1);
emitGetGlobalScope();
break;
}
case OP_getglobalslot:
{
checkStack(0,1);
emitGetGlobalScope();
emitGetSlot(imm30-1);
break;
}
case OP_setglobalslot:
{
if (!state->scopeDepth && !scope->size)
{
verifyFailed(kNoGlobalScopeError);
}
Traits *globalTraits = scope->size > 0 ? scope->scopes[0].traits : state->scopeValue(0).traits;
checkStack(1,0);
checkEarlySlotBinding(globalTraits);
Traits* slotTraits = checkSlot(globalTraits, imm30-1);
if (mir)
{
emitCoerce(slotTraits, state->sp());
mir->emit(state, opcode, imm30-1, sp, slotTraits);
}
state->pop();
break;
}
case OP_getslot:
{
checkStack(1,1);
emitGetSlot(imm30-1);
break;
}
case OP_setslot:
{
checkStack(2,0);
emitSetSlot(imm30-1);
break;
}
case OP_pop:
{
checkStack(1,0);
state->pop();
break;
}
case OP_dup:
{
checkStack(1, 2);
Value& v = state->peek();
if (mir) mir->emitCopy(state, sp, sp+1);
state->push(v);
break;
}
case OP_swap:
{
checkStack(2,2);
emitSwap();
break;
}
case OP_lessthan:
case OP_greaterthan:
case OP_lessequals:
case OP_greaterequals:
{
checkStack(2,1);
emitCompare(opcode);
break;
}
case OP_equals:
case OP_strictequals:
case OP_instanceof:
case OP_in:
{
checkStack(2,1);
if (mir) mir->emit(state, opcode, 0, 0, BOOLEAN_TYPE);
state->pop_push(2, BOOLEAN_TYPE);
break;
}
case OP_not:
checkStack(1,1);
if (mir)
{
emitCoerce(BOOLEAN_TYPE, sp);
mir->emit(state, opcode, sp);
}
state->pop_push(1, BOOLEAN_TYPE);
break;
case OP_add:
{
checkStack(2,1);
Value& rhs = state->peek(1);
Value& lhs = state->peek(2);
Traits* lhst = lhs.traits;
Traits* rhst = rhs.traits;
if (lhst == STRING_TYPE && lhs.notNull || rhst == STRING_TYPE && rhs.notNull)
{
if (mir)
{
emitToString(OP_convert_s, sp-1);
emitToString(OP_convert_s, sp);
mir->emit(state, OP_concat, 0, 0, STRING_TYPE);
}
state->pop_push(2, STRING_TYPE, true);
}
else if (lhst && lhst->isNumeric && rhst && rhst->isNumeric)
{
if (mir)
{
emitCoerce(NUMBER_TYPE, sp-1);
emitCoerce(NUMBER_TYPE, sp);
mir->emit(state, OP_add_d, 0, 0, NUMBER_TYPE);
}
state->pop_push(2, NUMBER_TYPE);
}
else
{
if (mir) mir->emit(state, opcode, 0, 0, OBJECT_TYPE);
// dont 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);
if (mir)
{
emitCoerce(NUMBER_TYPE, sp-1); // convert LHS to number
emitCoerce(NUMBER_TYPE, sp); // convert RHS to number
mir->emit(state, opcode, 0, 0, NUMBER_TYPE);
}
state->pop_push(2, NUMBER_TYPE);
break;
case OP_negate:
checkStack(1,1);
emitCoerce(NUMBER_TYPE, sp);
if (mir) mir->emit(state, opcode, sp, 0, NUMBER_TYPE);
break;
case OP_increment:
case OP_decrement:
checkStack(1,1);
emitCoerce(NUMBER_TYPE, sp);
if (mir) mir->emit(state, opcode, sp, opcode == OP_increment ? 1 : -1, NUMBER_TYPE);
break;
case OP_increment_i:
case OP_decrement_i:
checkStack(1,1);
emitCoerce(INT_TYPE, sp);
if (mir) mir->emit(state, opcode, state->sp(), opcode == OP_increment_i ? 1 : -1, INT_TYPE);
break;
case OP_add_i:
case OP_subtract_i:
case OP_multiply_i:
checkStack(2,1);
if (mir)
{
emitCoerce(INT_TYPE, sp-1);
emitCoerce(INT_TYPE, sp);
mir->emit(state, opcode, 0, 0, INT_TYPE);
}
state->pop_push(2, INT_TYPE);
break;
case OP_negate_i:
checkStack(1,1);
emitCoerce(INT_TYPE, sp);
if (mir) mir->emit(state, opcode, sp, 0, INT_TYPE);
break;
case OP_bitand:
case OP_bitor:
case OP_bitxor:
checkStack(2,1);
if (mir)
{
emitCoerce(INT_TYPE, sp-1);
emitCoerce(INT_TYPE, sp);
mir->emit(state, opcode, 0, 0, INT_TYPE);
}
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);
if (mir)
{
emitCoerce(INT_TYPE, sp-1); // lhs
emitCoerce(UINT_TYPE, sp); // rhs
mir->emit(state, opcode, 0, 0, INT_TYPE);
}
state->pop_push(2, INT_TYPE);
break;
case OP_urshift:
checkStack(2,1);
if (mir)
{
emitCoerce(UINT_TYPE, sp-1); // lhs
emitCoerce(UINT_TYPE, sp); // rhs
mir->emit(state, opcode, 0, 0, UINT_TYPE);
}
state->pop_push(2, UINT_TYPE);
break;
case OP_bitnot:
checkStack(1,1);
emitCoerce(INT_TYPE, sp); // lhs
if (mir) mir->emit(state, opcode, sp, 0, INT_TYPE);
break;
case OP_typeof:
{
checkStack(1,1);
if (mir) mir->emit(state, opcode, sp, 0, STRING_TYPE);
state->pop_push(1, STRING_TYPE, true);
break;
}
case OP_bkpt:
case OP_bkptline:
case OP_nop:
case OP_label:
case OP_debug:
break;
case OP_debugline:
#ifdef DEBUGGER
// we actually do generate code for these, in debugger mode
if (mir) mir->emit(state, opcode, imm30);
#endif
break;
case OP_nextvalue:
case OP_nextname:
{
checkStack(2,1);
peekType(INT_TYPE,1);
if (mir) mir->emit(state, opcode, 0, 0, NULL);
state->pop_push(2, NULL);
break;
}
case OP_hasnext:
{
checkStack(2,1);
peekType(INT_TYPE,1);
if (mir) mir->emit(state, opcode, 0, 0, INT_TYPE);
state->pop_push(2, INT_TYPE);
break;
}
case OP_hasnext2:
{
checkStack(0,1);
checkLocal(imm30);
Value& v = checkLocal(imm30b);
if (imm30 == imm30b)
{
verifyFailed(kInvalidHasNextError);
}
if (v.traits != INT_TYPE)
{
verifyFailed(kIllegalOperandTypeError, core->toErrorString(v.traits), core->toErrorString(INT_TYPE));
}
if (mir) mir->emit(state, opcode, imm30, imm30b, BOOLEAN_TYPE);
state->setType(imm30, NULL, false);
state->push(BOOLEAN_TYPE);
break;
}
case OP_abs_jump:
{
// first ensure the executing code isn't user code (only VM generated abc can use this op)
if(pool->isCodePointer(pc))
{
verifyFailed(kIllegalOpcodeError, core->toErrorString(info), core->toErrorString(OP_abs_jump), core->toErrorString(pc-code_pos));
}
const byte* new_pc = (const byte*) imm30;
#ifdef AVMPLUS_64BIT
new_pc = (const byte *) (uintptr(new_pc) | (((uintptr) imm30b) << 32));
const byte* new_code_end = new_pc + AvmCore::readU30 (nextpc);
#else
const byte* new_code_end = new_pc + imm30b;
#endif
// now ensure target points to within pool's script buffer
if(!pool->isCodePointer(new_pc))
{
verifyFailed(kIllegalOpcodeError, core->toErrorString(info), core->toErrorString(OP_abs_jump), core->toErrorString(pc-code_pos));
}
// FIXME: what other verification steps should we do here?
pc = code_pos = new_pc;
code_end = new_code_end;
size = 0;
//set mir abcStart/End
if(mir) {
mir->abcStart = pc;
mir->abcEnd = code_end;
//core->GetGC()->Free((void*) info->body_pos);
//info->body_pos = NULL;
}
exceptions_pos = code_end;
code_length = int(code_end - pc);
parseExceptionHandlers();
break;
}
default:
// size was nonzero, but no case handled the opcode. someone asleep at the wheel!
AvmAssert(false);
}
}
if (!blockEnd)
{
verifyFailed(kCannotFallOffMethodError);
}
if (blockStates && actualCount != labelCount)
{
verifyFailed(kInvalidBranchTargetError);
}
#ifdef AVMPLUS_INTERP
if (!mir || mir->overflow)
{
if (info->returnTraits() == NUMBER_TYPE)
info->implN = Interpreter::interpN;
else
info->impl32 = Interpreter::interp32;
}
else
#endif //AVMPLUS_INTERP
{
mir->epilogue(state);
}
#ifdef FEATURE_BUFFER_GUARD
#ifdef AVMPLUS_MIR
growthGuard = NULL;
#endif
#endif
} CATCH (Exception *exception) {
// clean up growthGuard
#ifdef FEATURE_BUFFER_GUARD
#ifdef AVMPLUS_MIR
// Make sure the GrowthGuard is unregistered
if (growthGuard)
{
growthGuard->~GrowthGuard();
// growthGuard = NULL;
}
#endif
#endif
// re-throw exception
core->throwException(exception);
}
END_CATCH
END_TRY
}
void Verifier::checkPropertyMultiname(uint32 &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::emitCompare(AbcOpcode opcode)
{
// if either the LHS or RHS is a number type, then we know
// it will be a numeric comparison.
Value& rhs = state->peek(1);
Value& lhs = state->peek(2);
Traits *lhst = lhs.traits;
Traits *rhst = rhs.traits;
if (mir)
{
if (rhst && rhst->isNumeric && lhst && !lhst->isNumeric)
{
// convert lhs to Number
emitCoerce(NUMBER_TYPE, state->sp()-1);
}
else if (lhst && lhst->isNumeric && rhst && !rhst->isNumeric)
{
// promote rhs to Number
emitCoerce(NUMBER_TYPE, state->sp());
}
mir->emit(state, opcode, 0, 0, BOOLEAN_TYPE);
}
state->pop_push(2, BOOLEAN_TYPE);
}
void Verifier::emitFindProperty(AbcOpcode opcode, Multiname& multiname)
{
ScopeTypeChain* scope = info->declaringTraits->scope;
bool early = false;
if (multiname.isBinding())
{
int index = scopeBase + state->scopeDepth - 1;
int base = scopeBase;
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--)
{
Value& v = state->value(index);
Binding b = toplevel->getBinding(v.traits, &multiname);
if (b != BIND_NONE)
{
if (mir) mir->emitCopy(state, index, state->sp()+1);
state->push(v);
early = true;
break;
}
else if (v.isWith)
{
// with scope could have dynamic property
break;
}
}
if (!early && index < base)
{
// look at captured scope types
for (index = scope->size-1; index > 0; index--)
{
Traits* t = scope->scopes[index].traits;
Binding b = toplevel->getBinding(t, &multiname);
if (b != BIND_NONE)
{
if (mir) mir->emitGetscope(state, index, state->sp()+1);
state->push(t, true);
early = true;
break;
}
else if (scope->scopes[index].isWith)
{
// with scope could have dynamic property
break;
}
}
if (!early && index <= 0)
{
// look at import table for a suitable script
AbstractFunction* script = (AbstractFunction*)pool->getNamedScript(&multiname);
if (script != (AbstractFunction*)BIND_NONE && script != (AbstractFunction*)BIND_AMBIGUOUS)
{
if (mir)
{
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)
{
mir->emitGetscope(state, 0, state->sp()+1);
}
else
{
mir->emitCopy(state, scopeBase, state->sp()+1);
}
}
else
{
// found a single matching traits
mir->emit(state, OP_finddef, (uintptr)&multiname, state->sp()+1, script->declaringTraits);
}
}
state->push(script->declaringTraits, true);
early = true;
return;
}
}
}
}
if (!early)
{
uint32 n=1;
checkPropertyMultiname(n, multiname);
if (mir) mir->emit(state, opcode, (uintptr)&multiname, 0, OBJECT_TYPE);
state->pop_push(n-1, OBJECT_TYPE, true);
}
}
void Verifier::emitGetProperty(Multiname &multiname, int n)
{
Value& obj = state->peek(n); // object
if (mir) emitCheckNull(state->sp()-(n-1));
Binding b = toplevel->getBinding(obj.traits, &multiname);
Traits* propType = readBinding(obj.traits, b);
if (AvmCore::isSlotBinding(b))
{
// early bind to slot
if (mir) mir->emit(state, OP_getslot, AvmCore::bindingToSlotId(b), state->sp(), propType);
state->pop_push(n, propType);
return;
}
// Do early binding to accessors.
if (AvmCore::hasGetterBinding(b))
{
// Invoke the getter
int disp_id = AvmCore::bindingToGetterId(b);
AbstractFunction *f = obj.traits->getMethod(disp_id);
AvmAssert(f != NULL);
if (mir)
{
emitCoerceArgs(f, 0);
mir->emitSetContext(state, f);
if (!obj.traits->isInterface)
mir->emitCall(state, OP_callmethod, disp_id, 0, propType);
else
mir->emitCall(state, OP_callinterface, f->iid(), 0, propType);
}
AvmAssert(propType == f->returnTraits());
state->pop_push(n, propType);
return;
}
#ifdef DEBUG_EARLY_BINDING
core->console << "verify getproperty " << obj.traits << " " << multiname->getName() << " from within " << info << "\n";
#endif
// default - do getproperty at runtime
if (mir)
{
mir->emitSetContext(state, NULL);
mir->emit(state, OP_getproperty, (uintptr)&multiname, 0, propType);
}
state->pop_push(n, propType);
}
void Verifier::emitGetGlobalScope()
{
ScopeTypeChain* scope = info->declaringTraits->scope;
int captured_depth = scope->size;
if (captured_depth > 0)
{
// enclosing scope
if (mir) mir->emitGetscope(state, 0, state->sp()+1);
state->push(scope->scopes[0].traits, true);
}
else
{
// local scope
if (state->scopeDepth > 0)
{
if (mir) mir->emitCopy(state, scopeBase, state->sp()+1);
// this will copy type and all attributes too
state->push(state->scopeValue(0));
}
else
{
#ifdef _DEBUG
if (pool->isBuiltin)
core->console << "getglobalscope >= depth (0) "<< state->scopeDepth << "\n";
#endif
verifyFailed(kGetScopeObjectBoundsError, core->toErrorString(0));
}
}
}
void Verifier::emitGetSlot(int slot)
{
Value& obj = state->peek();
checkEarlySlotBinding(obj.traits);
Traits* slotTraits = checkSlot(obj.traits, slot);
if (mir)
{
emitCheckNull(state->sp());
mir->emit(state, OP_getslot, slot, state->sp(), slotTraits);
}
state->pop_push(1, slotTraits);
}
void Verifier::emitSetSlot(int slot)
{
Value& obj = state->peek(2); // object
// if code isn't in pool, its our generated init function which we always
// allow early binding on
if(pool->isCodePointer(info->body_pos))
checkEarlySlotBinding(obj.traits);
Traits* slotTraits = checkSlot(obj.traits, slot);
if (mir)
{
emitCoerce(slotTraits, state->sp());
emitCheckNull(state->sp()-1);
mir->emit(state, OP_setslot, slot, state->sp()-1, slotTraits);
}
state->pop(2);
}
void Verifier::emitSwap()
{
if (mir) mir->emitSwap(state, state->sp(), state->sp()-1);
Value v1 = state->peek(1);
Value v2 = state->peek(2);
state->pop(2);
state->push(v1);
state->push(v2);
}
FrameState *Verifier::getFrameState(sintptr targetpc)
{
const byte *target = code_pos + targetpc;
FrameState *targetState;
// get state for target address or create a new one if none exists
if (!blockStates)
{
blockStates = new (core->GetGC()) SortedIntMap<FrameState*>(core->GetGC(), 128);
}
if ( (targetState = blockStates->get((uintptr)target)) == 0 )
{
targetState = newFrameState();
targetState->pc = target - code_pos;
blockStates->put((uintptr)target, targetState);
labelCount++;
}
return targetState;
}
void Verifier::emitToString(AbcOpcode opcode, int i)
{
Traits *st = STRING_TYPE;
Value& value = state->value(i);
Traits *in = value.traits;
if (in != st || !value.notNull || opcode != OP_convert_s)
{
if (mir)
{
if (opcode == OP_convert_s && in &&
(value.notNull || in->isNumeric || in == BOOLEAN_TYPE))
{
mir->emitCoerce(state, i, st);
}
else
{
mir->emit(state, opcode, i, 0, st);
}
}
value.traits = st;
value.notNull = true;
}
}
void Verifier::emitCheckNull(int i)
{
Value& value = state->value(i);
if (!value.notNull)
{
mir->emitCheckNull(state, i);
for (int j=0, n = frameSize; j < n; j++)
{
// also mark all copies of value.ins as non-null
Value &v2 = state->value(j);
if (v2.ins == value.ins)
v2.notNull = true;
}
}
}
void Verifier::checkCallMultiname(AbcOpcode /*opcode*/, Multiname* name) const
{
if (name->isAttr())
{
verifyFailed(kIllegalOpMultinameError, core->toErrorString(name));
}
}
Traits* Verifier::emitCoerceSuper(int index)
{
Traits* base = info->declaringTraits->base;
if (base != NULL)
{
if (mir)
emitCoerce(base, index);
}
else
{
verifyFailed(kIllegalSuperCallError, core->toErrorString(info));
}
return base;
}
void Verifier::emitCoerce(Traits* target, int index)
{
Value &v = state->value(index);
Traits* rhs = v.traits;
if (mir && (!canAssign(target, rhs) || !Traits::isMachineCompatible(target,rhs)))
mir->emitCoerce(state, index, target);
state->setType(index, target, v.notNull);
}
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)
{
bool slotAllow;
pool->allowEarlyBinding(t, slotAllow);
if (!slotAllow)
{
// illegal disp_id or slot_id access since dispatch table layout and instance layout
// are not known at compile time
verifyFailed(kIllegalEarlyBindingError, core->toErrorString(t));
}
}
void Verifier::checkEarlyMethodBinding(Traits* t)
{
bool slotAllow;
pool->allowEarlyBinding(t, slotAllow);
if (true)
{
// illegal disp_id or slot_id access since dispatch table layout and instance layout
// are not known at compile time
verifyFailed(kIllegalEarlyBindingError, core->toErrorString(t));
}
}
void Verifier::emitCoerceArgs(AbstractFunction* m, int argc)
{
if (!m->argcOk(argc))
{
verifyFailed(kWrongArgumentCountError, core->toErrorString(m), core->toErrorString(m->requiredParamCount()), core->toErrorString(argc));
}
m->resolveSignature(toplevel);
// coerce parameter types
int n=1;
while (argc > 0)
{
if (mir)
{
Traits* target = (argc <= m->param_count) ? m->paramTraits(argc) : NULL;
emitCoerce(target, state->sp()-(n-1));
}
argc--;
n++;
}
// coerce receiver type
if (mir)
emitCoerce(m->paramTraits(0), state->sp()-(n-1));
}
bool Verifier::canAssign(Traits* lhs, Traits* rhs) const
{
if (!Traits::isMachineCompatible(lhs,rhs))
{
// no machine type is compatible with any other
return false;
}
if (!lhs)
return true;
// type on right must be same class or subclass of type on left.
Traits* t = rhs;
while (t != lhs && t != NULL)
t = t->base;
return t != NULL;
}
void Verifier::checkStack(uint32 pop, uint32 push)
{
if (uint32(state->stackDepth) < pop)
verifyFailed(kStackUnderflowError);
if (state->stackDepth-pop+push > uint32(max_stack))
verifyFailed(kStackOverflowError);
}
void Verifier::checkStackMulti(uint32 pop, uint32 push, Multiname* m)
{
if (m->isRtname()) pop++;
if (m->isRtns()) pop++;
checkStack(pop,push);
}
Value& Verifier::checkLocal(int local)
{
if (local < 0 || local >= local_count)
verifyFailed(kInvalidRegisterError, core->toErrorString(local));
return state->value(local);
}
Traits* Verifier::checkSlot(Traits *traits, int imm30)
{
uint32 slot = imm30;
if (!traits || slot >= traits->slotCount)
{
verifyFailed(kSlotExceedsCountError, core->toErrorString(slot+1), core->toErrorString(traits?traits->slotCount:0), core->toErrorString(traits));
}
traits->resolveSignatures(toplevel);
return traits->getSlotTraits(slot);
}
Traits* Verifier::readBinding(Traits* traits, Binding b)
{
if (traits)
traits->resolveSignatures(toplevel);
switch (b&7)
{
default:
AvmAssert(false); // internal error - illegal binding type
case BIND_GET:
case BIND_GETSET:
{
int m = AvmCore::bindingToGetterId(b);
AbstractFunction *f = traits->getMethod(m);
return f->returnTraits();
}
case BIND_SET:
// TODO lookup type here. get/set must have same type.
case BIND_NONE:
// dont know what this is
// fall through
case BIND_METHOD:
// extracted method or dynamic data, don't know which
return NULL;
case BIND_VAR:
case BIND_CONST:
return traits->getSlotTraits(AvmCore::bindingToSlotId(b));
}
}
AbstractFunction* Verifier::checkMethodInfo(uint32 id)
{
if (id >= pool->methodCount)
{
verifyFailed(kMethodInfoExceedsCountError, core->toErrorString(id), core->toErrorString(pool->methodCount));
return NULL;
}
else
{
return pool->methods[id];
}
}
Traits* Verifier::checkClassInfo(uint32 id)
{
if (id >= pool->classCount)
{
verifyFailed(kClassInfoExceedsCountError, core->toErrorString(id), core->toErrorString(pool->classCount));
return NULL;
}
else
{
return pool->cinits[id]->declaringTraits;
}
}
Traits* Verifier::checkTypeName(uint32 index)
{
Multiname name;
checkConstantMultiname(index, name); // CONSTANT_Multiname
Traits *t = pool->getTraits(&name, toplevel);
if (t == NULL)
verifyFailed(kClassNotFoundError, core->toErrorString(&name));
return t;
}
AbstractFunction* Verifier::checkDispId(Traits* traits, uint32 disp_id)
{
if (disp_id > traits->methodCount)
{
verifyFailed(kDispIdExceedsCountError, core->toErrorString(disp_id), core->toErrorString(traits->methodCount), core->toErrorString(traits));
return NULL;
}
else
{
if (!traits->getMethod(disp_id))
{
verifyFailed(kDispIdUndefinedError, core->toErrorString(disp_id), core->toErrorString(traits));
}
return traits->getMethod(disp_id);
}
}
void Verifier::verifyFailed(int errorID, Stringp arg1, Stringp arg2, Stringp arg3) const
{
#ifdef FEATURE_BUFFER_GUARD
#ifdef AVMPLUS_MIR
// Make sure the GrowthGuard is unregistered
if (growthGuard)
{
growthGuard->~GrowthGuard();
}
#endif
#endif
#ifdef AVMPLUS_VERBOSE
if (!secondTry && !verbose)
{
// capture the verify trace even if verbose is false.
Verifier v2(info, toplevel, true);
v2.verbose = true;
v2.verify(NULL);
}
#endif
core->throwErrorV(toplevel->verifyErrorClass(), errorID, arg1, arg2, arg3);
// This function throws, and should never return.
AvmAssert(false);
}
void Verifier::checkTarget(const byte* target)
{
FrameState *targetState = getFrameState(target-code_pos);
AvmAssert(targetState != 0);
if (!targetState->initialized)
{
//if (verbose)
// core->console << "merge first target=" << targetState->pc << "\n";
// first time visiting target block
targetState->init(state);
targetState->initialized = true;
// if this label is a loop header then clear the notNull flag for
// any state entry that might become null in the loop body. this
// prevents us from needing to re-verify the loop, at a cost of a
// few more null pointer checks.
if (targetState->targetOfBackwardsBranch)
{
// null check on all locals
for (int i=0, n=local_count; i < n; i++)
targetState->value(i).notNull = false;
// and all stack entries
for (int i=stackBase, n=i+state->stackDepth; i < n; i++)
targetState->value(i).notNull = false;
// we don't have to clear notNull on scope stack entries because we
// check for null in op_pushscope/pushwith
}
//if (verbose)
// showState(targetState, targetState->pc+code_pos, false);
}
else
{
/*if (verbose)
{
core->console << "merge current=" << state->pc << "\n";
showState(state, code_pos+state->pc, false);
core->console << "merge target=" << targetState->pc << "\n";
showState(targetState, code_pos+targetState->pc, false);
}*/
// 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
// ISSUE merge should 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
const int scopeTop = scopeBase + targetState->scopeDepth;
const int stackTop = stackBase + targetState->stackDepth;
Value* targetValues = &targetState->value(0);
Value* curValues = &state->value(0);
for (int i=0, n=stackTop; i < n; i++)
{
if (i >= scopeTop && i < stackBase)
{
// invalid location, ignore it.
continue;
}
Value& curValue = curValues[i];
Value& targetValue = targetValues[i];
if (curValue.killed || targetValue.killed)
{
// this reg has been killed in one or both states;
// ignore it.
continue;
}
Traits* t1 = targetValue.traits;
Traits* t2 = curValue.traits;
bool isWith = curValue.isWith;
if (isWith != targetValue.isWith)
{
// failure: pushwith on one edge, pushscope on other edge, cannot merge.
verifyFailed(kCannotMergeTypesError, core->toErrorString(t1), core->toErrorString(t2));
}
Traits* t3 = (t1 == t2) ? t1 : findCommonBase(t1, t2);
#ifdef AVMPLUS_MIR
if (mir)
mir->merge(curValue, targetValue);
#endif // AVMPLUS_MIR
bool notNull = targetValue.notNull && curValue.notNull;
if (targetState->pc < state->pc &&
(t3 != t1 || t1 && !t1->isNumeric && notNull != targetValue.notNull))
{
// failure: merge on back-edge
verifyFailed(kCannotMergeTypesError, core->toErrorString(t1), core->toErrorString(t3));
}
// if we're targeting a label we can't propagate notNull since we don't yet know
// the state of all the other possible branches. Another possible fix would be to
// enforce a null check at each branch to the target.
if (targetState->targetOfBackwardsBranch)
notNull = false;
targetState->setType(i, t3, notNull, isWith);
}
/*if (verbose) {
core->console << "after merge\n";
showState(targetState, code_pos+targetState->pc, false);
}*/
}
}
/**
* find common base class of these two types
*/
Traits* Verifier::findCommonBase(Traits* t1, Traits* t2)
{
AvmAssert(t1 != t2);
if (t1 == NULL) {
// assume t1 is always non-null
Traits *temp = t1;
t1 = t2;
t2 = temp;
}
if (!Traits::isMachineCompatible(t1,t2))
{
// these two types are incompatible machine types that require
// coersions before the join node.
verifyFailed(kCannotMergeTypesError, core->toErrorString(t1), core->toErrorString(t2));
}
if (t1 == NULL_TYPE && t2 && !t2->isMachineType)
{
// okay to merge null with pointer type
return t2;
}
if (t2 == NULL_TYPE && t1 && !t1->isMachineType)
{
// okay to merge null with pointer type
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);
// found common base, possibly *
if (!Traits::isMachineCompatible(t1,common) || !Traits::isMachineCompatible(t2,common))
{
// these two types are incompatible types that require
// coersions before the join node.
verifyFailed(kCannotMergeTypesError, core->toErrorString(t1), core->toErrorString(t2));
}
return common;
}
Atom Verifier::checkCpoolOperand(uint32 index, int requiredAtomType)
{
switch( requiredAtomType )
{
case kStringType:
if( !index || index >= pool->constantStringCount )
{
verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->constantStringCount));
return undefinedAtom;
}
return pool->cpool_string[index]->atom();
case kObjectType:
if( !index || index >= pool->constantMnCount )
{
verifyFailed(kCpoolIndexRangeError, core->toErrorString(index), core->toErrorString(pool->constantMnCount));
return undefinedAtom;
}
return pool->cpool_mn[index];
default:
verifyFailed(kCpoolEntryWrongTypeError, core->toErrorString(index));
return undefinedAtom;
}
}
void Verifier::checkConstantMultiname(uint32 index, Multiname& m)
{
checkCpoolOperand(index, kObjectType);
pool->parseMultiname(m, index);
}
Binding Verifier::findMathFunction(Traits* math, Multiname* multiname, Binding b, int argc)
{
Stringp newname = core->internString(core->concatStrings(core->constantString("_"), multiname->getName()));
Binding newb = math->getName(newname);
if (AvmCore::isMethodBinding(newb))
{
int disp_id = AvmCore::bindingToMethodId(newb);
AbstractFunction* newf = math->getMethod(disp_id);
if (argc == newf->param_count)
{
for (int i=state->stackDepth-argc, n=state->stackDepth; i < n; i++)
{
Traits* t = state->stackValue(i).traits;
if (t != NUMBER_TYPE && t != INT_TYPE && t != UINT_TYPE)
return b;
}
b = newb;
}
}
return b;
}
Binding Verifier::findStringFunction(Traits* str, Multiname* multiname, Binding b, int argc)
{
Stringp newname = core->internString(core->concatStrings(core->constantString("_"), multiname->getName()));
Binding newb = str->getName(newname);
if (AvmCore::isMethodBinding(newb))
{
int disp_id = AvmCore::bindingToMethodId(newb);
AbstractFunction* newf = str->getMethod(disp_id);
// We have all required parameters but not more than required.
if ((argc >= (newf->param_count - newf->optional_count)) && (argc <= newf->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 != newf->paramTraits(k))
return b;
}
b = newb;
}
}
return b;
}
void Verifier::parseExceptionHandlers()
{
const byte* pos = exceptions_pos;
int exception_count = toplevel->readU30(pos);
if (exception_count != 0)
{
size_t extra = sizeof(ExceptionHandler)*(exception_count-1);
ExceptionHandlerTable* table = new (core->GetGC(), extra) ExceptionHandlerTable(exception_count);
ExceptionHandler *handler = table->exceptions;
for (int i=0; i<exception_count; i++)
{
handler->from = toplevel->readU30(pos);
handler->to = toplevel->readU30(pos);
handler->target = toplevel->readU30(pos);
int type_index = toplevel->readU30(pos);
Traits* t = type_index ? checkTypeName(type_index) : NULL;
Multiname qn;
int name_index = (pool->version != (46<<16|15)) ? toplevel->readU30(pos) : 0;
if (name_index != 0)
{
pool->parseMultiname(qn, name_index);
}
#ifdef AVMPLUS_VERBOSE
if (verbose)
{
core->console << " exception["<<i<<"] from="<< handler->from
<< " to=" << handler->to
<< " target=" << handler->target
<< " type=" << t
<< " name=";
if (name_index != 0)
core->console << &qn;
else
core->console << "(none)";
core->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);
}
// handler->traits = t
WB(core->GetGC(), table, &handler->traits, t);
Traits* scopeTraits;
if (name_index == 0)
{
scopeTraits = OBJECT_TYPE;
}
else
{
scopeTraits = core->newTraits(NULL, 1, 0, sizeof(ScriptObject));
scopeTraits->pool = pool;
scopeTraits->final = true;
scopeTraits->defineSlot(qn.getName(), qn.getNamespace(), 0, BIND_VAR);
scopeTraits->slotCount = 1;
scopeTraits->initTables(toplevel);
AbcGen gen(core->GetGC());
scopeTraits->setSlotInfo(0, 0, toplevel, t, scopeTraits->sizeofInstance, CPoolKind(0), gen);
scopeTraits->setTotalSize(scopeTraits->sizeofInstance + 16);
scopeTraits->linked = true;
}
// handler->scopeTraits = scopeTraits
WB(core->GetGC(), table, &handler->scopeTraits, scopeTraits);
getFrameState(handler->target)->targetOfBackwardsBranch = true;
handler++;
}
//info->exceptions = table;
WB(core->GetGC(), info, &info->exceptions, table);
}
else
{
info->exceptions = NULL;
}
}
#ifdef AVMPLUS_VERBOSE
/**
* display contents of current stack frame only.
*/
void Verifier::showState(FrameState *state, const byte* pc, bool unreachable)
{
// stack
core->console << " stack:";
for (int i=0, n=state->stackDepth; i < n; i++) {
core->console << " ";
printValue(state->stackValue(i));
}
core->console << '\n';
// scope chain
core->console << " scope: ";
if (info->declaringTraits->scope && info->declaringTraits->scope->size > 0)
{
core->console << "[";
for (int i=0, n=info->declaringTraits->scope->size; i < n; i++)
{
Value v;
v.traits = info->declaringTraits->scope->scopes[i].traits;
v.isWith = info->declaringTraits->scope->scopes[i].isWith;
v.killed = false;
v.notNull = true;
#ifdef AVMPLUS_MIR
v.ins = 0;
v.stored = false;
#endif
printValue(v);
if (i+1 < n)
core->console << " ";
}
core->console << "] ";
}
for (int i=0, n=state->scopeDepth; i < n; i++)
{
printValue(state->scopeValue(i));
core->console << " ";
}
core->console << '\n';
// locals
core->console << " locals: ";
for (int i=0, n=scopeBase; i < n; i++) {
printValue(state->value(i));
core->console << " ";
}
core->console << '\n';
// opcode
if (unreachable)
core->console << "- ";
else
core->console << " ";
core->console << state->pc << ':';
core->formatOpcode(core->console, pc, (AbcOpcode)*pc, state->pc, pool);
core->console << '\n';
}
void Verifier::printValue(Value& v)
{
Traits* t = v.traits;
if (!t)
{
core->console << "*";
}
else
{
core->console << t->format(core);
if (!t->isNumeric && !v.notNull && t != BOOLEAN_TYPE && t != NULL_TYPE)
core->console << "?";
}
#ifdef AVMPLUS_MIR
if (mir && v.ins)
mir->formatOperand(core->console, v.ins, mir->ipStart);
#endif
}
#endif /* AVMPLUS_VERBOSE */
FrameState* Verifier::newFrameState()
{
size_t extra = (frameSize-1)*sizeof(Value);
return new (core->GetGC(), extra) FrameState(this);
}
}