Source code drop for the Actionscript virtual machine in the Adobe Flash Player for December 2013
1335 lines
31 KiB
HTML
1335 lines
31 KiB
HTML
<!-- 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/. -->
|
|
|
|
<h1>Notes on the AVM Bytecode Verifier</h1>
|
|
Jeff Dyer, Adobe Systems
|
|
|
|
<hr>
|
|
<pre>
|
|
Revision History:
|
|
November 26, 2008: second draft, reviewed by Tom and Lars
|
|
November 20, 2008: initial draft, not reviewed
|
|
|
|
Known Problems:
|
|
- certain abstract operations are yet to be defined
|
|
- handling of null as a type modifier
|
|
- dead code is not ignored as it is in AVM
|
|
- more and better examples of verification errors and their resolution
|
|
</pre>
|
|
<hr>
|
|
|
|
<h2>Introduction</h2>
|
|
|
|
<P><span class="pcounter"></span>ABC (originally for Action Block Code) is the language executed by the AVM (ActionScript
|
|
Virtual Machine of Tamarin). The full definition of ABC includes a specification of its
|
|
syntax, static semantics and runtime behavior. Within the static semantics are
|
|
constraints on the bytecode of each executed method (methods that are not
|
|
executed are not subject to these constraints.) A bytecode verifier is the mechanism
|
|
that enforces such static constraints on sequences of bytecode.
|
|
|
|
<P><span class="pcounter"></span>This document describes the AVM bytecode verifier using an abstract machine and
|
|
the translation of concrete ABC instructions into operations of that abstract machine.
|
|
The abstract machine described here accepts the same set of programs that the AVM verifier
|
|
accepts. This is the set of ABC programs that is allowed to be executed by the AVM.
|
|
|
|
<P><span class="pcounter"></span>This document does not describe the syntactic or structural constraints on ABC files
|
|
that do not involve ABC bytecode. Those rules are defined in the AVM2 Overview [3] and elsewhere.
|
|
|
|
<P><span class="pcounter"></span>The audience for this document includes developers who are working on and
|
|
compiler writers who are targeting the AVM. The reader might find helpful to think of
|
|
this as an alternative interpretation of ABC programs; that is, to not
|
|
confuse the meaning of types and control flow with that of a particular source
|
|
language or even the dynamic behavior of the ABC instructions.
|
|
|
|
<h2>Invariants</h2>
|
|
|
|
<P><span class="pcounter"></span>
|
|
The following invariants are checked during verification:
|
|
|
|
<ul>
|
|
<li> Operand stack does not underflow or overflow
|
|
<li> Scope stack does not underflow or overflow
|
|
<li> Storage locations always have an unambiguous state
|
|
<li> Instruction operands are valid references and have their expected types
|
|
<li> Instruction pointers point to the beginning of an instruction
|
|
<li> Constant pool indices point to valid constant pool entries
|
|
</ul>
|
|
|
|
<P><span class="pcounter"></span>There are many other constraints on the syntax and semantics of an ABC file. For
|
|
example it is essential that
|
|
|
|
<ul>
|
|
<li> Base classes exist and are not declared 'final'
|
|
<li> Class methods that are overridden are not declared 'final'
|
|
<li> ...more here
|
|
</ul>
|
|
|
|
<P><span class="pcounter"></span>Such constraints are assumed by this spec to have been checked before bytecode
|
|
verification.
|
|
|
|
<h2>Abstract Machine</h2>
|
|
|
|
<h3>Machine State</h3>
|
|
|
|
<P><span class="pcounter"></span>The abstract machine state includes the following components:
|
|
|
|
<ul>
|
|
<li> Operand stack of a maximum size containing zero or more types
|
|
<li> Scope stack of a maximum size containing one or more types
|
|
<li> Local registers of a maximum size, each containing a type
|
|
<li> Index of the current instruction
|
|
</ul>
|
|
|
|
<P><span class="pcounter"></span>We refer to stack elements and local registers as locations in the machine state.
|
|
A valid machine state is one whose stack sizes and type of each location are known
|
|
to be consistent when reached by all possible control flow paths. A valid bytecode
|
|
sequence is one with only valid machine states.
|
|
|
|
<P><span class="pcounter"></span>Accessible locations are initialized to the type Any and so are never in an undefined
|
|
(or killed) state.
|
|
|
|
<h3>Machine Context</h3>
|
|
|
|
<P><span class="pcounter"></span>
|
|
In addition to the state that is mutated in the course of method verification, there is
|
|
additional state that represents the context of the method. That environment
|
|
includes:
|
|
|
|
<ul>
|
|
<li> Method being verfied including method body and map of machine states for that method
|
|
<li> Lexically visible bindings (name to type) outside the method
|
|
<li> Constant pool
|
|
<li> Script domain (contains top-level fixed definitions)
|
|
</ul>
|
|
|
|
<h3>Machine Types</h3>
|
|
|
|
<P><span class="pcounter"></span>
|
|
The verifier type system is factored somewhat differently than that of ActionScript or
|
|
any other normal object oriented language, for that matter. The key difference is
|
|
that there are six root types, none of which are compatible with any of the others.
|
|
ActionScript, in contrast, has one root type Any with which all other types are
|
|
assignment compatible. The reason for this deviation is that the verifier type system
|
|
reflects the compatibility rules of the underlying machine values.
|
|
|
|
<P><span class="pcounter"></span>
|
|
The root types of the abstract machine are:
|
|
|
|
<ul>
|
|
<li> Atom* (consists of Any, Undefined, Object)
|
|
<li> Int*
|
|
<li> UInt*
|
|
<li> Boolean*
|
|
<li> Number*
|
|
<li> Reference* (consists of Null and any sub-class of Object, including String and
|
|
Namespace)
|
|
</ul>
|
|
|
|
<p class="note"><b>NOTE</b> String and Namespace are special in that there are instructions that expect
|
|
those types, but otherwise they are treated as all other Reference* types.
|
|
|
|
<P><span class="pcounter"></span>
|
|
Two types are considered consistent if they are the same machine type and they
|
|
merge to a concrete type that is the same machine type. We use the term 'type' to
|
|
refer to both a machine types (e.g. Atom*) and the concrete types (e.g. Any and
|
|
Object) without qualification unless the distinction is important and unclear.
|
|
|
|
<P><span class="pcounter"></span>
|
|
The rules for merging types are:
|
|
|
|
<ul>
|
|
<li> Two identical types merge as the same type
|
|
<li> Two Atom* types merge as type Any
|
|
<li> Two Reference* types merge as the type that is the most derived common
|
|
base class. Two Reference* types are consistent only if they merge to another
|
|
Reference* type (i.e. not Object)
|
|
<li> All other type combinations result in a merge error
|
|
</ul>
|
|
|
|
<h3>Abstract Operators</h3>
|
|
|
|
<P><span class="pcounter"></span>
|
|
The following operators are the primitives that define possible state transitions of the
|
|
abstract machine.
|
|
|
|
<ul>
|
|
<li> Push type
|
|
<li> Pop [count]
|
|
<li> Coerce type
|
|
<li> Check type [index]
|
|
<li> Peek symbol [index]
|
|
<li> Swap
|
|
<li> Load index
|
|
<li> Store index
|
|
<li> PushScope type
|
|
<li> PopScope
|
|
<li> Jump offset
|
|
<li> Throws
|
|
<li> PopName
|
|
<li> FindName
|
|
<li> FindDefName
|
|
<li> GetSlot
|
|
<li> GetProp
|
|
<li> SetSlot
|
|
<li> SetProp
|
|
</ul>
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Push <i>type</i></b>. Push the specified type onto the operand stack. Raises an error if the
|
|
number of elements on the operand stack is equal to the maximum stack size.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Pop <i>[count]</i></b>. Pop count types from the operand stack. If unspecified count is 1.
|
|
Raises an error if count is greater than the number of elements on the operand
|
|
stack.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Coerce <i>type</i></b>. Change the type on the top of the stack to the given type. Raises no
|
|
error.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Check <i>type [index]</i></b>. Check that the type at the location of index is of the given
|
|
type. If index is not provided, then check the type on the top of the stack. Raises an
|
|
error if index does not indicate a valid location or if the type at that location is not
|
|
compatible with the specified type.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Peek <i>symbol [index]</i></b>. Bind meta-name symbol to the type at the location of index.
|
|
If index is not provided, then binds the type on the top of the operand stack. Raises an
|
|
error if index does not indicate a valid location.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Swap</b>. Swap the two values on the top of the operand stack. Raises an error if fewer
|
|
than two elements are on the operand stack.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Load <i>index</i></b>. Load the type of local at index onto the stack. Raises an error if index
|
|
does not indicate a valid local register.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Store <i>index</i></b>. Store the type on the top of the stack into local <i>index</i>. Raises an
|
|
error if index does not indicate a valid local register.
|
|
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>PushScope <i>type</i></b>. Push the specified type onto the scope stack. Check that it
|
|
matches the expected type. [<b>FIXME</b> how is the pushed type constrainted?]. Raises an error if
|
|
the number of elements on the scope stack is equal to the maximum stack size.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>PopScope</b>. Pop the top type from the scope stack.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Jump <i>offset</i></b>. Merge the current machine state with the state associated with the
|
|
instruction indicated by offset. The meaning of merge depends on the direction of the
|
|
control flow causing the merge.
|
|
|
|
<P><span class="pcounter"></span>
|
|
Two machine states merge on a forward edge if each of the following conditions
|
|
hold:
|
|
|
|
<ul>
|
|
<li> operand stack is the same size
|
|
<li> scope stack is the same size
|
|
<li> scope type at each scope stack location is consistent
|
|
<li> type at each value location (operand stack and locals) are consistent
|
|
</ul>
|
|
|
|
<P><span class="pcounter"></span>
|
|
A merge of a backward edge is more constrained than a merge on a forward edge.
|
|
On a backward edge it is a verifier error if the merged type is different than the
|
|
target type. This includes the notNull attribute of that type. The reason for this
|
|
limitation is that by the time a backward edge is verified, the state of the machine
|
|
may have been used by a preceding instruction and so the type state cannot safely
|
|
be modified.
|
|
|
|
<P><span class="pcounter"></span>
|
|
Raises an error if offset does not refer to the beginning of an instruction or if the
|
|
current machine and the state associated with that offset are not consistent.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>Throws</b>. The Throws operation saves the current abstract machine state, configures
|
|
the scope and operand stacks for the exception handlers, jumps (as if by the Jump
|
|
operator) to each of the catch handlers, and then restores the saved machine state.
|
|
Raises no error.
|
|
PopName name. Pop a name from the operand stack. The structure of a name
|
|
value depends on the kind of name be referenced. Raise an error if the type(s) on
|
|
the operand stack are either too few or incompatible with the name kind specified by
|
|
the instruction operand.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>FindName <i>name</i></b>. Resolve the name constructed from the instruction operand and
|
|
element(s) on the operand stack to a type in the scope stack by searching the scope
|
|
stack for a type that contains the given name. If found push the found type onto the
|
|
operand stack. Otherwise push the global type onto the operand stack.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>FindDefName <i>name</i></b>. FIXME
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>GetSlot <i>index</i></b>. FIXME
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>GetName <i>name</i></b>. FIXME
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>SetSlot <i>index</i></b>. FIXME
|
|
|
|
<P><span class="pcounter"></span>
|
|
<b>SetName <i>name</i></b>. FIXME
|
|
|
|
<h3>Interpreter Loop</h3>
|
|
|
|
<P><span class="pcounter"></span>
|
|
The interpreter loop scans the bytecode in a single forward pass interpreting each
|
|
bytecode with the abstract meaning given below. After an instruction is interpreted,
|
|
the current machine state is merged with the input state of the next instruction as
|
|
though there is an implicit edge in the control flow graph to that instruction.
|
|
|
|
<P><span class="pcounter"></span>
|
|
If an invariant is violated during execution of an operator or the subsequent merge
|
|
with the input state of the next instruction, the bytecode sequence is considered
|
|
invalid and verification terminates.
|
|
|
|
<p class="note"><b>NOTE</b> Machine state is merged with the state of the next instruction even if there is
|
|
no direct path to that instruction. This allows the machine state to be approximated
|
|
without using an iterative data flow algorithm. The burden on the compiler that
|
|
generates the bytecode is to configure the types of live variables before entering the
|
|
loop through one of these imaginary edges.
|
|
|
|
<hr>
|
|
<PRE>
|
|
R1=0
|
|
<fixup>
|
|
jump L2
|
|
<dead>
|
|
L1: label
|
|
<body>
|
|
R1=R1+1
|
|
L2: if <condition> L1
|
|
</PRE>
|
|
<hr>
|
|
Example 1
|
|
|
|
<P><span class="pcounter"></span>
|
|
The machine state at 'jump L2' will be merged with the state at 'L1: label'. Any
|
|
intervening '<dead>' code will be ignored by the verifier and thus have no effect on
|
|
the interpreter state. This approximates reality where L1 is reached through the back
|
|
edge at L2. What is missing is an accounting of the effect that '<body>' has on the
|
|
machine state at L1. The code generator (or author) of this code must either insert
|
|
coerce and kill instructions in the region of '<fixup>', or reorganize the control flow
|
|
so that the condition comes before the body of the loop.
|
|
|
|
<hr>
|
|
<PRE>
|
|
R1=0
|
|
L2: if <condition> L1
|
|
<body>
|
|
R1=R1+1
|
|
jump L2
|
|
L1: label
|
|
</PRE>
|
|
<hr>
|
|
Example 2
|
|
|
|
<P><span class="pcounter"></span>
|
|
In this configuration every use is preceded by an actual definition of a locations
|
|
state. Any location that is used in <body> will have to be defined before <body> is
|
|
entered or inside of <body> but before it is used. A later redefinition of that location
|
|
will still be limited to the current restrictions on type merging on backedges.]
|
|
|
|
<P><span class="pcounter"></span>
|
|
<p class="fixme"><b>FIXME</b> the logic of dead code identification is not straightforward and needs to be
|
|
defined
|
|
|
|
<P><span class="pcounter"></span>
|
|
If the interpreter reaches the end of the bytecode, then the bytecode is valid.
|
|
|
|
<h4>Control Flow Graphs</h4>
|
|
|
|
<P><span class="pcounter"></span>
|
|
We have already seen that an implicit edge in the control flow graph (CFG) is created
|
|
to each instruction from the previous instruction. Here is the complete list of rules
|
|
used to create the CFG for a method.
|
|
|
|
<ul>
|
|
<li> to every instruction from the previous instruction
|
|
<li> to every exception handler from every in scope instruction that can throw an
|
|
exception
|
|
<li> from every jump to every jump target (including lookupswitch)
|
|
</ul>
|
|
|
|
<h3>ABC Instructions</h3>
|
|
|
|
<P><span class="pcounter"></span>
|
|
The abstract interpretation of ABC bytecode can be expressed in the abstract
|
|
machine operations defined above. The following is such a mapping from ABC
|
|
instruction to the abstract operators.
|
|
|
|
<P><span class="pcounter"></span>
|
|
<p class="fixme"><b>FIXME</b> this mapping is currently only approximate, and undoubtedly buggy. But is
|
|
good enough to get us started in the debugging process.]
|
|
|
|
<P><span class="pcounter"></span>
|
|
For the most part the notation is self-evident. Parens following an instruction name
|
|
indicate the type case for that particular definition. <code>!</code> (e.g. <code>!String</code>) indicates that <code>Null</code>
|
|
is excluded from the reference type being named.
|
|
|
|
<pre>
|
|
<b>abs_jump</b>:
|
|
; FIXME need definition
|
|
|
|
|
|
<b>add (!String, _)</b>: ; NOTE ! means nonNull
|
|
<b>add (_, !String)</b>:
|
|
Throws
|
|
Pop
|
|
Pop
|
|
Push String
|
|
|
|
|
|
<b>add (Numeric, Numeric)</b>: ; NOTE Numeric means converts to Number
|
|
Throws
|
|
Pop
|
|
Pop
|
|
Push Number
|
|
|
|
|
|
<b>add</b>:
|
|
Throws
|
|
Pop
|
|
Pop
|
|
Push Object
|
|
|
|
|
|
<b>add_d</b>:
|
|
; ERROR invalid instruction
|
|
|
|
|
|
<b>add_i</b>:
|
|
Throws
|
|
Pop
|
|
Pop
|
|
Push Int
|
|
|
|
|
|
<b>applytype argc</b>:
|
|
Throws
|
|
Pop argc+1
|
|
Push Any ; FIXME might be more specific if jitting
|
|
|
|
|
|
<b>astype type</b>:
|
|
Throws
|
|
Pop
|
|
Push type
|
|
|
|
|
|
<b>astypelate</b>:
|
|
Throws
|
|
Check Class
|
|
Peek c
|
|
Pop
|
|
Pop
|
|
Push instanceTypeOf(c)
|
|
; FIXME define instanceTypeOf
|
|
|
|
|
|
<b>bitand</b>:
|
|
Throws
|
|
Pop
|
|
Pop
|
|
Push Int
|
|
|
|
|
|
<b>bitnot</b>:
|
|
Throws
|
|
Pop
|
|
Push Int
|
|
|
|
|
|
<b>bitor</b>:
|
|
<b>bitxor</b>:
|
|
; REF bitand
|
|
|
|
|
|
<b>call argc</b>:
|
|
Throws
|
|
Pop argc+2
|
|
Push Any
|
|
|
|
|
|
<b>callinterface</b>:
|
|
<b>callmethod</b>:
|
|
; ERROR invalid instruction
|
|
|
|
|
|
<b>callproperty name</b>:
|
|
<b>callproplex name</b>:
|
|
<b>callpropvoid name</b>:
|
|
Throws
|
|
; CHECK name is not an attribute name
|
|
PopName name
|
|
|
|
|
|
<b>callstatic method argc</b>:
|
|
<b>callsuper method argc</b>:
|
|
<b>callsupervoid method argc</b>:
|
|
Throws
|
|
Pop argc+1
|
|
Push method.resultType
|
|
; ASSERT method has parameter types
|
|
CheckArgs ; FIXME needs definition
|
|
|
|
|
|
<b>checkfilter</b>:
|
|
Throws
|
|
Pop
|
|
Push Any
|
|
|
|
|
|
<b>coerce type</b>:
|
|
Throws
|
|
Pop
|
|
Push type
|
|
|
|
|
|
<b>coerce_a</b>:
|
|
Throws
|
|
Pop
|
|
Push Any
|
|
|
|
|
|
<b>coerce_b</b>:
|
|
Throws
|
|
Pop
|
|
Push Boolean
|
|
|
|
|
|
<b>coerce_d</b>:
|
|
Throws
|
|
Pop
|
|
Push Number
|
|
|
|
|
|
<b>coerce_i</b>:
|
|
Throws
|
|
Pop
|
|
Push Int
|
|
|
|
|
|
<b>coerce_o</b>:
|
|
Throws
|
|
Pop
|
|
Push Object
|
|
|
|
|
|
<b>coerce_s</b>:
|
|
Throws
|
|
Pop
|
|
Push String
|
|
|
|
|
|
<b>coerce_u</b>:
|
|
Throws
|
|
Pop
|
|
Push Uint
|
|
|
|
|
|
<b>concat</b>:
|
|
; ERROR invalid instruction (see add)
|
|
|
|
|
|
<b>construct name argc</b>:
|
|
Throws
|
|
Pop argc
|
|
Peek class
|
|
InstanceTypeOf(class) ; FIXME needs definition
|
|
|
|
|
|
<b>constructprop name argc</b>:
|
|
Throws
|
|
; CHECK name is not an attribute name
|
|
PopName name
|
|
|
|
|
|
<b>constructsuper argc</b>:
|
|
Throws
|
|
Pop argc+1
|
|
; CHECK base class is not null
|
|
|
|
|
|
<b>convert_b</b>:
|
|
; REF coerce_b
|
|
|
|
|
|
<b>convert_d</b>:
|
|
; REF coerce_d
|
|
|
|
|
|
<b>convert_i</b>:
|
|
; REF coerce_i
|
|
|
|
|
|
<b>convert_o</b>:
|
|
Peek t
|
|
Pop
|
|
Push t
|
|
|
|
<b>convert_s</b>:
|
|
Pop
|
|
Push String
|
|
|
|
|
|
<b>convert_u</b>:
|
|
; REF coerce_u
|
|
|
|
|
|
<b>debug</b>:
|
|
<b>debugfile</b>:
|
|
<b>debugline</b>:
|
|
Throws
|
|
|
|
|
|
<b>declocal</b>:
|
|
Throws
|
|
Load index
|
|
Coerce Number
|
|
Store index
|
|
|
|
|
|
<b>declocal_i</b>:
|
|
Throws
|
|
Load index
|
|
Coerce Int
|
|
Store index
|
|
|
|
|
|
<b>decrement</b>:
|
|
Throws
|
|
Pop
|
|
Push Number
|
|
|
|
|
|
<b>decrement_i</b>:
|
|
Throws
|
|
Pop
|
|
Push Int
|
|
|
|
|
|
<b>deleteproperty name</b>:
|
|
Throws
|
|
PopName name
|
|
|
|
|
|
<b>divide</b>:
|
|
Throws
|
|
Pop 2
|
|
Push Number
|
|
|
|
|
|
<b>dup</b>:
|
|
Peek t
|
|
Push t
|
|
|
|
|
|
<b>dxns</b>:
|
|
Throws
|
|
|
|
|
|
<b>dxnslate</b>:
|
|
Throws
|
|
Pop
|
|
|
|
|
|
<b>equals</b>:
|
|
Throws
|
|
Pop 2
|
|
Push Boolean
|
|
|
|
|
|
<b>esc_xattr</b>:
|
|
<b>esc_xelem</b>:
|
|
; REF convert_s
|
|
|
|
|
|
<b>finddef name</b>:
|
|
Throws
|
|
FindDefName name
|
|
|
|
|
|
<b>findproperty name</b>:
|
|
<b>findpropstrict name</b>:
|
|
Throws
|
|
; CHECK scope stack is not empty
|
|
FindName name
|
|
|
|
|
|
<b>getdescendants name</b>:
|
|
Throws
|
|
PopName name
|
|
Push Any
|
|
|
|
|
|
<b>getglobalscope</b>:
|
|
Push typeOfGlobalScope()
|
|
|
|
|
|
<b>getglobalslot index</b>:
|
|
Push typeOfGlobalSlot(index)
|
|
|
|
|
|
<b>getlex name</b>:
|
|
Throws
|
|
; CHECK scope stack is not empty
|
|
FindName name
|
|
GetProp name
|
|
|
|
|
|
<b>getlocal index</b>:
|
|
Load index
|
|
|
|
|
|
<b>getlocal0</b>:
|
|
Load 0
|
|
|
|
|
|
<b>getlocal1</b>:
|
|
Load 1
|
|
|
|
|
|
<b>getlocal2</b>:
|
|
Load 2
|
|
|
|
|
|
<b>getlocal3</b>:
|
|
Load 3
|
|
|
|
|
|
<b>getouterscope index</b>:
|
|
OuterScopeAt index ; FIXME needs definition
|
|
|
|
|
|
<b>getproperty name</b>:
|
|
Throws
|
|
GetProp name
|
|
|
|
|
|
<b>getscopeobject index</b>:
|
|
ScopeAt index ; FIXME needs definition
|
|
|
|
|
|
<b>getslot index</b>:
|
|
GetSlot index
|
|
|
|
|
|
<b>getsuper name</b>:
|
|
Throws
|
|
; CHECK name is not an attribute name
|
|
GetSuper
|
|
GetProp name
|
|
|
|
|
|
<b>greaterequals</b>:
|
|
<b>greaterthan</b>:
|
|
Throws
|
|
Pop 2
|
|
Push Boolean
|
|
|
|
|
|
<b>hasnext</b>:
|
|
Throws
|
|
Check Int
|
|
Peek t ; FIXME what this?
|
|
Pop 2
|
|
Push Int
|
|
|
|
|
|
<b>hasnext2 index1 index2</b>:
|
|
Throws
|
|
Check Any index1
|
|
Check Int index2
|
|
Push Boolean
|
|
|
|
|
|
<b>ifeq offset</b>:
|
|
Throws
|
|
Pop 2
|
|
Jump offset
|
|
|
|
|
|
<b>iffalse offset</b>:
|
|
Pop
|
|
Jump offset
|
|
|
|
|
|
<b>ifge offset</b>:
|
|
<b>ifgt offset</b>:
|
|
<b>ifle offset</b>:
|
|
<b>iflt offset</b>:
|
|
<b>ifne offset</b>:
|
|
<b>ifnge offset</b>:
|
|
<b>ifngt offset</b>:
|
|
<b>ifnle offset</b>:
|
|
<b>ifnlt offset</b>:
|
|
; REF ifeq
|
|
|
|
|
|
<b>ifstricteq offset</b>:
|
|
<b>ifstrictneq offset</b>:
|
|
Pop 2
|
|
Jump offset
|
|
|
|
|
|
<b>iftrue offset</b>:
|
|
; REF iffalse
|
|
|
|
|
|
<b>in</b>:
|
|
; REF equals
|
|
|
|
|
|
<b>inclocal index</b>:
|
|
; REF declocal
|
|
|
|
|
|
<b>inclocal_i index</b>:
|
|
; REF declocal_i
|
|
|
|
<b>increment</b>:
|
|
; REF decrement
|
|
|
|
|
|
<b>increment_i</b>:
|
|
; REF decrement_i
|
|
|
|
|
|
<b>initproperty name</b>:
|
|
Throws
|
|
Pop
|
|
PopName name
|
|
Pop
|
|
|
|
|
|
<b>instanceof</b>:
|
|
; REF equals
|
|
|
|
|
|
<b>istype name</b>:
|
|
Throws
|
|
Pop
|
|
Push Boolean
|
|
; FIXME the verifier code doesn't actually do this.
|
|
|
|
|
|
<b>istypelate</b>:
|
|
Throws
|
|
Pop 2
|
|
Push Boolean
|
|
|
|
|
|
<b>jump offset</b>:
|
|
Jump offset
|
|
|
|
|
|
<b>kill index</b>:
|
|
Push Undefined
|
|
Store index
|
|
|
|
|
|
<b>label</b>:
|
|
; NOTE do nothing
|
|
|
|
|
|
<b>lessequals</b>:
|
|
<b>lessthan</b>:
|
|
; REF greaterthan
|
|
|
|
|
|
<b>lf64</b>:
|
|
<b>lf32</b>:
|
|
Throws
|
|
Pop
|
|
Push Number
|
|
|
|
|
|
<b>li32</b>:
|
|
<b>li16</b>:
|
|
<b>li8</b>:
|
|
Throws
|
|
Pop
|
|
Push Int
|
|
|
|
|
|
<b>lookupswitch offset count targets[count]</b>:
|
|
Check Int
|
|
Pop
|
|
Jump offset
|
|
JumpAll targets ; FIXME needs definition
|
|
|
|
|
|
<b>lshift</b>:
|
|
Throws
|
|
Pop 2
|
|
Push Int
|
|
|
|
|
|
<b>modulo</b>:
|
|
<b>multiply</b>:
|
|
; REF divide
|
|
|
|
|
|
<b>multiply_i</b>:
|
|
; REF add_i
|
|
|
|
|
|
<b>negate</b>:
|
|
Pop
|
|
Push Number
|
|
|
|
|
|
<b>negate_i</b>:
|
|
Throws
|
|
Push Int
|
|
|
|
|
|
<b>newactivation</b>:
|
|
; CHECK need_activation flag is set in method info
|
|
Throws
|
|
GetActivationScope ; FIXME needs definition
|
|
|
|
|
|
<b>newarray argc</b>:
|
|
Throws
|
|
Pop argc
|
|
Push Array
|
|
|
|
|
|
<b>newcatch index</b>:
|
|
Throws
|
|
GetHandlerScope index
|
|
|
|
|
|
<b>newclass index</b>:
|
|
Throws
|
|
Pop
|
|
GetClassScope index ; FIXME needs definition
|
|
; FIXME to simplistic
|
|
|
|
|
|
<b>newfunction index</b>:
|
|
Throws
|
|
Pop
|
|
GetFunctionScope index ; FIXME needs definition
|
|
; FIXME what's missing?
|
|
|
|
|
|
<b>newobject argc</b>:
|
|
Throws
|
|
; CHECK every other operand < 2*argc is a String
|
|
Pop 2*argc
|
|
Push Object
|
|
|
|
|
|
<b>nextname</b>:
|
|
<b>nextvalue</b>:
|
|
Throws
|
|
Check Int
|
|
Pop 2
|
|
Push Any
|
|
|
|
<b>nop</b>:
|
|
; NOTE do nothing
|
|
|
|
|
|
<b>not</b>:
|
|
Pop
|
|
Push Boolean
|
|
|
|
|
|
<b>pop</b>:
|
|
Pop
|
|
|
|
|
|
<b>popscope</b>:
|
|
PopScope
|
|
|
|
|
|
<b>pushbyte b</b>:
|
|
Push Int
|
|
|
|
|
|
<b>pushdouble d</b>:
|
|
Push Number
|
|
|
|
|
|
<b>pushfalse</b>:
|
|
Push Boolean
|
|
|
|
|
|
<b>pushint v</b>:
|
|
Push Int
|
|
|
|
|
|
<b>pushnamespace index</b>:
|
|
Push Namespace
|
|
|
|
|
|
<b>pushnan</b>:
|
|
Push Number
|
|
|
|
|
|
<b>pushnull</b>:
|
|
Push Null
|
|
|
|
|
|
<b>pushscope</b>:
|
|
Peek s
|
|
Pop
|
|
PushScope s
|
|
|
|
|
|
<b>pushshort v</b>:
|
|
Push Int
|
|
|
|
|
|
<b>pushstring index</b>:
|
|
Push String
|
|
|
|
|
|
<b>pushtrue</b>:
|
|
Push Boolean
|
|
|
|
|
|
<b>pushuint</b>:
|
|
Push Uint
|
|
|
|
|
|
<b>pushundefined</b>:
|
|
Push Undefined
|
|
|
|
|
|
<b>pushwith</b>:
|
|
Peek s
|
|
Pop
|
|
PushScope s
|
|
|
|
|
|
<b>returnvalue</b>:
|
|
Pop
|
|
|
|
|
|
<b>returnvoid</b>:
|
|
; NOTE do nothing
|
|
|
|
|
|
<b>rshift</b>:
|
|
; REF lshift
|
|
|
|
|
|
<b>setglobalslot index</b>:
|
|
; CHECK scope stack is not empty
|
|
Pop
|
|
|
|
|
|
<b>setlocal index</b>:
|
|
Store index
|
|
|
|
|
|
<b>setlocal0</b>:
|
|
Store 0
|
|
|
|
|
|
<b>setlocal1</b>:
|
|
Store 1
|
|
|
|
|
|
<b>setlocal2</b>:
|
|
Store 2
|
|
|
|
|
|
<b>setlocal3</b>:
|
|
Store 3
|
|
|
|
|
|
<b>setproperty</b>:
|
|
; REF initproperty
|
|
|
|
|
|
<b>setslot index</b>:
|
|
Throws
|
|
Pop 2
|
|
|
|
|
|
<b>setsuper name</b>:
|
|
Throws
|
|
; CHECK name is not an attribute name
|
|
PopName
|
|
Pop 2
|
|
|
|
|
|
<b>sf32</b>:
|
|
<b>sf64</b>:
|
|
<b>si16</b>:
|
|
<b>si32</b>:
|
|
<b>si8</b>:
|
|
Throws
|
|
Pop 2
|
|
|
|
|
|
<b>strictequals</b>:
|
|
; REF equals
|
|
|
|
|
|
<b>subtract</b>:
|
|
; REF divide
|
|
|
|
|
|
<b>subtract_i</b>:
|
|
; REF add_i
|
|
|
|
|
|
<b>swap</b>:
|
|
Swap
|
|
|
|
|
|
<b>sxi1</b>:
|
|
<b>sxi8</b>:
|
|
<b>sxi16</b>:
|
|
Pop
|
|
Push Int
|
|
|
|
|
|
<b>throw</b>:
|
|
Pop
|
|
Throws
|
|
|
|
|
|
<b>timestamp</b>:
|
|
; ERROR invalid instruction
|
|
|
|
|
|
<b>typeof</b>:
|
|
Pop
|
|
Push String
|
|
|
|
|
|
<b>urshift</b>:
|
|
Throws
|
|
Pop 2
|
|
Push Uint
|
|
</pre>
|
|
|
|
<h2>Working Examples</h2>
|
|
|
|
In this section we review some common verify errors and how to avoid them.
|
|
|
|
<h3>Inconsistent Types on a Forward Edge</h3>
|
|
|
|
The following is an example of an unsuccessful merge between an Int and UInt
|
|
resulting in a verfication error. That error will be avoided if the integer values were
|
|
both coerced to the Atom* type Any before being stored in local slot 2.
|
|
|
|
<pre>
|
|
verify <anonymous>()
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * * *
|
|
0:pushbyte 0
|
|
stack: int
|
|
scope: [global]
|
|
locals: Object * * *
|
|
2:setlocal2 <b><-- Insert coerce_a</b>
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * int *
|
|
|
|
.
|
|
.
|
|
.
|
|
|
|
9:iffalse 16
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * int int
|
|
13:getlocal3
|
|
stack: int
|
|
scope: [global]
|
|
locals: Object * int int
|
|
14:convert_u
|
|
stack: uint
|
|
scope: [global]
|
|
locals: Object * int int
|
|
15:setlocal2 <b><-- Insert coerce_a</b>
|
|
VerifyError: Error #1068: int and uint cannot be reconciled.
|
|
</pre>
|
|
|
|
<h3>Inconsistent Type on a Backward Edge</h3>
|
|
|
|
<h4>Forward Edge</h4>
|
|
|
|
On a forward edge <code>null</code> and <code>String</code> merge to <code>String?</code>.
|
|
|
|
<pre>
|
|
function f (b) {
|
|
var x = null
|
|
if (b) {
|
|
x = "foo"
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
The above ActionScript code might be compiled as the following acceptable bytecode.
|
|
|
|
<pre>
|
|
verify <anonymous>()
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * *
|
|
0:pushnull
|
|
stack: null
|
|
scope: [global]
|
|
locals: Object * *
|
|
1:setlocal2
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * null
|
|
|
|
.
|
|
.
|
|
.
|
|
|
|
4:iffalse 11
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * null
|
|
8:pushstring <span class="literal">
|
|
stack: String
|
|
scope: [global]
|
|
locals: Object * null
|
|
10:setlocal2
|
|
B0:
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * String?
|
|
11:returnvoid
|
|
</pre>
|
|
|
|
<h4>Backward Edge</h4>
|
|
|
|
However, on a backward edge <code>String?</code> and <code>null</code> do not merge. This is for the simple
|
|
reason that the merge on the backward edge causes the type to
|
|
change: <code>null</code> -> <code>String?</code>.
|
|
|
|
<pre>
|
|
function f (b) {
|
|
var x = null
|
|
while (b) {
|
|
x = "foo"
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<P><span class="pcounter"></span>
|
|
A naive compiler might compile the above code as below. It would do well to
|
|
insert <code>coerce_s</code> as indicated.
|
|
|
|
|
|
<pre>
|
|
verify <anonymous>()
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * *
|
|
0:pushnull
|
|
stack: null
|
|
scope: [global]
|
|
locals: Object * *
|
|
1:setlocal2 <b><-- Insert coerce_s here</b>
|
|
stack:
|
|
scope: [global]
|
|
locals: Object * null
|
|
2:jump 10
|
|
B0:
|
|
stack:
|
|
scope: [global]
|
|
locals: Object? * null
|
|
6:label
|
|
stack:
|
|
scope: [global]
|
|
locals: Object? * null
|
|
7:pushstring </span>
|
|
stack: String
|
|
scope: [global]
|
|
locals: Object? * null
|
|
9:setlocal2
|
|
B1:
|
|
stack:
|
|
scope: [global]
|
|
locals: Object? * String?
|
|
10:getlocal1
|
|
stack: *
|
|
scope: [global]
|
|
locals: Object? * String?
|
|
11:convert_b
|
|
stack: Boolean
|
|
scope: [global]
|
|
locals: Object? * String?
|
|
12:iftrue 6
|
|
VerifyError: Error #1068: null and String cannot be reconciled.
|
|
</pre>
|
|
|
|
<h3>Unbalanced Stack</h3>
|
|
|
|
An unbalanced stack results when two or more paths that lead to the same control
|
|
flow point lead to different stack depths. Here is an example:
|
|
|
|
<pre>
|
|
verify <anonymous>()
|
|
stack:
|
|
scope: [global]
|
|
locals: Object *
|
|
0:getlocal1
|
|
stack: *
|
|
scope: [global]
|
|
locals: Object *
|
|
1:convert_b
|
|
stack: Boolean
|
|
scope: [global]
|
|
locals: Object *
|
|
2:iffalse 9
|
|
stack:
|
|
scope: [global]
|
|
locals: Object *
|
|
6:pushfalse
|
|
stack: Boolean
|
|
scope: [global]
|
|
locals: Object *
|
|
7:dup <b><-- either don't dup or move extra push outside of branch</b>
|
|
stack: Boolean Boolean
|
|
scope: [global]
|
|
locals: Object *
|
|
8:coerce_a
|
|
VerifyError: Error #1030: Stack depth is unbalanced. 2 != 0.
|
|
</pre>
|
|
|
|
|
|
<h2>References</h2>
|
|
|
|
<P><span class="pcounter"></span>
|
|
[1] <a href='http:<i>pauillac.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf'>
|
|
"Java bytecode verification: algorithms and formalizations", Xavier Leroy
|
|
</a>
|
|
|
|
<P><span class="pcounter"></span>
|
|
[2] <a href='ftp:</i>ftp.kestrel.edu/pub/papers/coglio/ccpe03.pdf'>
|
|
"Improving the official specification of Java bytecode verification", Alessandro Coglio
|
|
</a>
|
|
|
|
<P><span class="pcounter"></span>
|
|
[3] <a href=http://www.adobe.com/devnet/actionscript/articles/avm2overview.pdf>
|
|
"ActionScript Virtual Machine 2 Overview", Adobe Systems
|
|
</a>
|
|
|
|
|