blob: fbacda68515cbf0d880ac2fb976fb724f4f8e6cb [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.flex.abc;
import java.io.*;
import java.util.*;
import org.apache.flex.abc.semantics.*;
import org.apache.flex.abc.visitors.IABCVisitor;
import org.apache.flex.abc.visitors.IClassVisitor;
import org.apache.flex.abc.visitors.IMetadataVisitor;
import org.apache.flex.abc.visitors.IMethodBodyVisitor;
import org.apache.flex.abc.visitors.IMethodVisitor;
import org.apache.flex.abc.visitors.IScriptVisitor;
import org.apache.flex.abc.visitors.ITraitVisitor;
import org.apache.flex.abc.visitors.ITraitsVisitor;
import org.apache.flex.abc.visitors.NilVisitors;
import static org.apache.flex.abc.ABCConstants.*;
/**
* ABCParser reads an ABC from a byte array or an input stream
* and translates it into a sequence of AET operations.
*/
public class ABCParser
{
/**
* The ABC as a byte array.
*/
byte[] abc;
/*
* Local copies of the pool data are maintained because the core's pools may
* contain data from several ABCs, and so the constant pool indices
* contained in this ABC won't necessarily match the indices in the core
* pools.
*/
/**
* Names defined by this ABC.
*/
private Name[] names;
/**
* Strings defined by this ABC.
*/
private String[] strings;
/**
* Namespaces defined by this ABC.
*/
private Namespace[] namespaces;
/**
* Namespace sets defined by this ABC.
*/
private Nsset[] namespace_sets;
/**
* Integer values defined by this ABC.
*/
private int[] ints;
/**
* Unsigned integer values defined by this ABC.
*/
private long[] uints;
/**
* Floating-point values defined by this ABC.
*/
private double[] doubles;
/**
* Metdata defined by this ABC.
*/
private Metadata[] metadata;
/**
* MethodInfos by id
*/
private MethodInfo[] methodInfos;
/**
* And associated visitors.
*/
private IMethodVisitor[] methodVisitors;
/**
* ClassInfos by id
*/
private ClassInfo[] classInfos;
/**
* InstanceInfos by id
*/
private InstanceInfo[] instanceInfos;
/**
* Construct a new ABC parser from a byte array.
*
* @param abc - the ABC in byte array form.
*/
public ABCParser(byte[] abc)
{
this.abc = abc;
}
/**
* Construct a new ABC parser from an input stream.
* @param input - the InputStream. Clients may want
* to buffer their input for best performance.
*/
public ABCParser(InputStream input)
throws IOException
{
ByteArrayOutputStream bufferedABC = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = input.read(buffer);
while (n != -1)
{
bufferedABC.write(buffer, 0, n);
n = input.read(buffer);
}
this.abc = bufferedABC.toByteArray();
}
/**
* Parse the ABC and send its information to the visitor.
*
* @param vabc - the top-level visitor.
*/
public void parseABC(IABCVisitor vabc)
{
int pool_size;
ABCReader p = new ABCReader(0, abc);
int minor = p.readU16();
int major = p.readU16();
vabc.visit(major, minor);
pool_size = p.readU30();
ints = new int[pool_size];
for (int i = 1; i < pool_size; i++)
{
ints[i] = p.readU30();
vabc.visitPooledInt(ints[i]);
}
pool_size = p.readU30();
uints = new long[pool_size];
for (int i = 1; i < pool_size; i++)
{
uints[i] = 0xffffffffL & p.readU30();
vabc.visitPooledUInt(uints[i]);
}
pool_size = p.readU30();
doubles = new double[pool_size];
for (int i = 1; i < pool_size; i++)
{
doubles[i] = p.readDouble();
vabc.visitPooledDouble(doubles[i]);
}
pool_size = p.readU30();
strings = new String[pool_size];
for (int i = 1; i < pool_size; i++)
{
int len = p.readU30();
try
{
strings[i] = new String(abc, p.pos, len, "UTF-8");
}
catch (UnsupportedEncodingException badEncoding)
{
// A well-formed ABC can't throw this exception.
throw new IllegalStateException(badEncoding);
}
vabc.visitPooledString(strings[i]);
p.pos += len;
}
pool_size = p.readU30();
namespaces = new Namespace[pool_size];
for (int i = 1; i < pool_size; i++)
{
int ns_kind = p.readU8();
int name_idx = p.readU30();
String ns_name = readPool(strings, name_idx, "string");
namespaces[i] = new Namespace(ns_kind, ns_name);
vabc.visitPooledNamespace(namespaces[i]);
}
pool_size = p.readU30();
namespace_sets = new Nsset[pool_size];
for (int i = 1; i < pool_size; i++)
{
int nsset_size = p.readU30();
Vector<Namespace> nsset_contents = new Vector<Namespace>(nsset_size);
for (int j = 0, m = nsset_size; j < m; j++)
nsset_contents.add(readPool(namespaces, p.readU30(), "namespace"));
namespace_sets[i] = new Nsset(nsset_contents);
vabc.visitPooledNsSet(namespace_sets[i]);
}
pool_size = p.readU30();
names = new Name[pool_size];
List<NameAndPos> forward_ref_names = null;
for (int i = 1; i < pool_size; i++)
{
Name name;
int name_pos = p.pos;
names[i] = name = readName(p);
if (name.isTypeName() &&
usesForwardReference(name))
{
// If this typename refers to names later in the table, we need to reprocess them later
// after the entire table has been read in
if (forward_ref_names == null)
forward_ref_names = new ArrayList<NameAndPos>();
forward_ref_names.add(new NameAndPos(i, name_pos));
}
else
{
// No forward ref, just visit the name now
vabc.visitPooledName(name);
}
}
if (forward_ref_names != null)
{
// save the current location
int orig_pos = p.pos;
for (NameAndPos nap : forward_ref_names)
{
p.pos = nap.pos;
Name newName = readName(p);
names[nap.nameIndex].initTypeName(newName.getTypeNameBase(), newName.getTypeNameParameter());
// visit the name now that it's been filled in correctly
vabc.visitPooledName(names[nap.nameIndex]);
}
// restore after we're done
p.pos = orig_pos;
}
int n_methods = p.readU30();
this.methodInfos = new MethodInfo[n_methods];
this.methodVisitors = new IMethodVisitor[n_methods];
for (int i = 0, n = n_methods; i < n; i++)
{
this.methodInfos[i] = readMethodInfo(p);
this.methodVisitors[i] = vabc.visitMethod(this.methodInfos[i]);
if (this.methodVisitors[i] != null)
this.methodVisitors[i].visit();
}
pool_size = p.readU30();
metadata = new Metadata[pool_size];
for (int i = 0; i < pool_size; i++)
{
metadata[i] = readMetadata(p);
vabc.visitPooledMetadata(metadata[i]);
}
// InstanceInfos and ClassInfos are stored in
// homogenous arrays in the ABC, but their
// IClassVisitor needs both in its constructor; so
// we read the arrays, remembering the position of
// the class and instance traits, and re-read the
// traits with the IClassVisitor's traits visitors.
int n_instances = p.readU30();
this.instanceInfos = new InstanceInfo[n_instances];
this.classInfos = new ClassInfo[n_instances];
for (int i = 0, n = n_instances; i < n; i++)
{
this.instanceInfos[i] = readInstanceInfo(p);
}
for (int i = 0, n = n_instances; i < n; i++)
{
this.classInfos[i] = readClassInfo(p);
}
for (int i = 0, n = n_instances; i < n; i++)
{
IClassVisitor cv = vabc.visitClass(this.instanceInfos[i], this.classInfos[i]);
if (cv != null)
{
ITraitsVisitor tv = cv.visitClassTraits();
readTraits(p, tv, this.classInfoToTraits.get(this.classInfos[i]));
tv.visitEnd();
tv = cv.visitInstanceTraits();
readTraits(p, tv, this.instanceInfoToTraits.get(this.instanceInfos[i]));
tv.visitEnd();
cv.visitEnd();
}
}
int n_scripts = p.readU30();
for (int i = 0; i < n_scripts; i++)
{
IScriptVisitor sv = vabc.visitScript();
if (sv != null)
{
readScript(p, sv);
}
else
{
p.readU30();
readTraits(p, NilVisitors.NIL_TRAITS_VISITOR);
}
}
int n_method_bodies = p.readU30();
for (int i = 0; i < n_method_bodies; i++)
{
readBody(vabc, p);
}
for (int i = 0; i < n_methods; ++i)
{
if (this.methodVisitors[i] != null)
this.methodVisitors[i].visitEnd();
}
vabc.visitEnd();
}
private boolean usesForwardReference(Name name)
{
Name nameBase = name.getTypeNameBase();
Name nameParam = name.getTypeNameParameter();
if (nameBase == null || nameParam == null)
return true;
if (nameBase != null && nameBase.isTypeName() &&
usesForwardReference(nameBase))
return true;
if (nameParam != null && nameParam.isTypeName() &&
usesForwardReference(nameParam))
return true;
return false;
}
/**
* Simple struct to keep track of Names, and where in the abc they come from
*/
private static class NameAndPos
{
// the index of the name in the name pool
public final int nameIndex;
// the pos of the name in the ABC
public final int pos;
public NameAndPos(int nameIndex, int pos)
{
this.nameIndex = nameIndex;
this.pos = pos;
}
}
/**
* Read MetadataInfo structures.
* <p>
* <b>Note:</b> Metadata entries can be "keyless". A "keyless" entry's key
* string index is "0". Although ABC file format defines string_pool[0] to
* be empty string "", this implementation use {@code null} to represent an
* "empty key" or "empty value". AET's implementation doesn't guarantee an
* empty string to have pool index of "0".
*/
Metadata readMetadata(ABCReader p)
{
final String metadata_name = readPool(strings, p.readU30(), "string");
final int value_count = p.readU30();
final String[] keys = new String[value_count];
for (int i = 0; i < value_count; i++)
{
final int key_index = p.readU30();
keys[i] = readPool(strings, key_index, "string");
}
final String[] values = new String[value_count];
for (int i = 0; i < value_count; i++)
{
final int value_index = p.readU30();
values[i] = readPool(strings, value_index, "string");
}
final Metadata metadata = new Metadata(metadata_name, keys, values);
return metadata;
}
/**
* Note that ABCParser does not insert a this pointer. The consumer of the
* ABC visitor should handle it.
*/
MethodInfo readMethodInfo(ABCReader p)
{
MethodInfo m = new MethodInfo();
final int param_count = p.readU30();
final int return_type_index = p.readU30();
final Name return_type = readPool(names, return_type_index, "name");
m.setReturnType(return_type);
final Vector<Name> param_types = new Vector<Name>(param_count);
for (int j = 0; j < param_count; j++)
{
final int param_index = p.readU30();
final Name param_type = readPool(names, param_index, "name");
param_types.add(param_type);
}
m.setParamTypes(param_types);
final String methodName = readPool(strings, p.readU30(), "string");
m.setMethodName(methodName);
m.setFlags((byte)p.readU8());
if (m.hasOptional())
{
int optional_count = p.readU30();
assert (optional_count > 0);
for (int j = 0; j < optional_count; j++)
{
m.addDefaultValue(readArgDefault(p));
}
}
if (m.hasParamNames())
{
for (int j = 0; j < param_count; j++)
{
final int param_name_index = p.readU30();
final String param_name = readPoolWithDefault(strings, param_name_index, MethodInfo.UNKNOWN_PARAM_NAME);
m.getParamNames().add(param_name);
}
}
return m;
}
Name readName(ABCReader p)
{
int kind = p.readU8();
switch (kind)
{
default:
{
throw new RuntimeException(String.format("Unknown name kind 0x%x", kind));
}
case CONSTANT_TypeName:
{
int index = p.readU30(); // Index to the Multiname type, i.e. Vector
int count = p.readU30(); // number of type parameter names
assert (count == 1); // all we support for now
int typeparm = p.readU30(); // Multinames for the type parameters, i.e. String for a Vector.<String>
Name mn = this.readPool(names, index, "name");
Name type_param = this.readPool(names, typeparm, "name");
return new Name(mn, type_param);
}
case CONSTANT_Qname:
case CONSTANT_QnameA:
{
int ns_idx = p.readU30();
Nsset nss = ns_idx != 0 ? new Nsset(readPool(namespaces, ns_idx, "namespace")) : null;
return new Name(kind, nss, readPool(strings, p.readU30(), "string"));
}
case CONSTANT_Multiname:
case CONSTANT_MultinameA:
{
int local_ix = p.readU30();
int nss_ix = p.readU30();
return new Name(kind, readPool(namespace_sets, nss_ix, "namespace_set"), readPool(strings, local_ix, "string"));
}
case CONSTANT_RTQname:
case CONSTANT_RTQnameA:
{
return new Name(kind, null, readPool(strings, p.readU30(), "string"));
}
case CONSTANT_MultinameL:
case CONSTANT_MultinameLA:
{
return new Name(kind, readPool(namespace_sets, p.readU30(), "namespace_set"), null);
}
case CONSTANT_RTQnameL:
case CONSTANT_RTQnameLA:
{
return new Name(kind, null, null);
}
}
}
void readTraits(ABCReader p, ITraitsVisitor traits_visitor)
{
assert traits_visitor != null : "Instead of passing null, pass NilVisitors.NIL_TRAITS_VISITOR";
traits_visitor.visit();
final int n_traits = p.readU30();
for (int i = 0; i < n_traits; i++)
{
final Name trait_name = readPool(names, p.readU30(), "name");
final int tag = p.readU8();
final int kind = tag & ABCConstants.TRAIT_KIND_MASK;
boolean is_method_getter_setter = false;
ITraitVisitor trait_visitor;
switch (kind)
{
case TRAIT_Var:
case TRAIT_Const:
{
int slot_id = p.readU30();
Name slot_type = readPool(names, p.readU30(), "name");
Object slot_value = readSlotDefault(p);
trait_visitor = traits_visitor.visitSlotTrait(kind, trait_name, slot_id, slot_type, slot_value);
break;
}
case TRAIT_Class:
{
trait_visitor = traits_visitor.visitClassTrait(
kind, trait_name, p.readU30(), this.readPool(classInfos, p.readU30(), "classInfo"));
break;
}
case TRAIT_Method:
case TRAIT_Getter:
case TRAIT_Setter:
{
is_method_getter_setter = true;
}
case TRAIT_Function:
{
trait_visitor = traits_visitor.visitMethodTrait(
kind, trait_name, p.readU30(), this.readPool(methodInfos, p.readU30(), "methodInfo"));
break;
}
default:
{
throw new IllegalArgumentException(String.format("illegal trait kind 0x%h at offset %d", kind, p.pos));
}
}
// provide a nil-visitor when trait visitor is null
if (trait_visitor == null)
trait_visitor = NilVisitors.NIL_TRAIT_VISITOR;
trait_visitor.visitStart();
// method, getter, setter has attributes in the high 4 bits of the kind byte
// 0x01: (1=final,0=virtual), 0x02: (1=override,0=new)
if (is_method_getter_setter)
{
trait_visitor.visitAttribute(Trait.TRAIT_FINAL, functionIsFinal(tag));
trait_visitor.visitAttribute(Trait.TRAIT_OVERRIDE, functionIsOverride(tag));
}
if (traitHasMetadata(tag))
{
final int n_entries = p.readU30();
final IMetadataVisitor mv = trait_visitor.visitMetadata(n_entries);
for (int j = 0; j < n_entries; j++)
{
final Metadata md = readPool(metadata, p.readU30(), "metadata");
if (mv != null)
mv.visit(md);
}
}
trait_visitor.visitEnd();
}
traits_visitor.visitEnd();
}
/**
* High 4 bits of the kind byte: 0x04: (1=has metadata,0=no metadata)
*/
private boolean traitHasMetadata(int kind)
{
return ((kind >> ABCConstants.TRAIT_KIND_SHIFT) & TRAIT_FLAG_metadata) != 0;
}
/**
* High 4 bits of the kind byte: 0x01: (1=final,0=virtual)
*/
private boolean functionIsFinal(int kind)
{
return ((kind >> ABCConstants.TRAIT_KIND_SHIFT) & ABCConstants.TRAIT_FLAG_final) != 0;
}
/**
* High 4 bits of the kind byte: 0x02: (1=override,0=new)
*/
private boolean functionIsOverride(int kind)
{
return ((kind >> ABCConstants.TRAIT_KIND_SHIFT) & ABCConstants.TRAIT_FLAG_override) != 0;
}
void readTraits(ABCReader p, ITraitsVisitor tv, Integer pos)
{
// Save current position, reposition as requested
int saved_pos = p.pos;
p.pos = pos;
readTraits(p, tv);
// Restore saved position
p.pos = saved_pos;
}
Object readSlotDefault(ABCReader p)
{
int i = p.readU30();
if (i != 0)
{
int kind = p.readU8();
return defaultValue(kind, i);
}
// changed from returning null to fix CMP-357. Note that this assumes
// that a 0 will be dumped by the ABCEmitter when it hits a
// ABCConstants.UNDEFINED_VALUE to match behavior
return ABCConstants.UNDEFINED_VALUE;
}
private PooledValue readArgDefault(ABCReader p)
{
int i = p.readU30();
int kind = p.readU8();
Object v = defaultValue(kind, i);
PooledValue result = new PooledValue(kind, v);
return result;
}
Object defaultValue(int kind, int i)
{
if (i == ABCConstants.ZERO_INDEX)
return ABCConstants.NULL_VALUE;
switch (kind)
{
case CONSTANT_False:
{
return Boolean.FALSE;
}
case CONSTANT_True:
{
return Boolean.TRUE;
}
case CONSTANT_Null:
{
return ABCConstants.NULL_VALUE;
}
case CONSTANT_Utf8:
{
return readPool(strings, i, "string");
}
case CONSTANT_Int:
{
return readIntPool(i);
}
case CONSTANT_UInt:
{
return readUintPool(i);
}
case CONSTANT_Double:
{
return readDoublePool(i);
}
case CONSTANT_Namespace:
{
return readPool(namespaces, i, "namespace");
}
case CONSTANT_PackageNs:
case CONSTANT_ProtectedNs:
case CONSTANT_PackageInternalNs:
case CONSTANT_StaticProtectedNs:
case CONSTANT_PrivateNs:
{
return readPool(namespaces, i, "namespace");
}
}
assert false : "Unknown value kind " + Integer.toString(i);
return null;
}
void readScript(ABCReader p, IScriptVisitor sv)
{
sv.visit();
sv.visitInit(this.readPool(methodInfos, p.readU30(), "methodInfo"));
ITraitsVisitor scriptTraitsVisitor = sv.visitTraits();
readTraits(p, scriptTraitsVisitor);
scriptTraitsVisitor.visitEnd();
sv.visitEnd();
}
Map<ClassInfo, Integer> classInfoToTraits = new HashMap<ClassInfo, Integer>();
ClassInfo readClassInfo(ABCReader p)
{
ClassInfo cinfo = new ClassInfo();
cinfo.cInit = this.readPool(methodInfos, p.readU30(), "methodInfo");
// Record the position of these traits, then skip past them.
// They'll be re-read when the class' IClassVisitor is initialized.
classInfoToTraits.put(cinfo, p.pos);
readTraits(p, NilVisitors.NIL_TRAITS_VISITOR);
return cinfo;
}
Map<InstanceInfo, Integer> instanceInfoToTraits = new HashMap<InstanceInfo, Integer>();
InstanceInfo readInstanceInfo(ABCReader p)
{
InstanceInfo iinfo = new InstanceInfo();
iinfo.name = readPool(names, p.readU30(), "name");
iinfo.superName = readPool(names, p.readU30(), "name");
iinfo.flags = p.readU8();
if (iinfo.hasProtectedNs())
iinfo.protectedNs = readPool(namespaces, p.readU30(), "namespace");
iinfo.interfaceNames = new Name[p.readU30()];
for (int j = 0, n = iinfo.interfaceNames.length; j < n; j++)
{
iinfo.interfaceNames[j] = readPool(names, p.readU30(), "name");
}
iinfo.iInit = this.readPool(methodInfos, p.readU30(), "methodInfo");
// Save the position of the instance traits
// for the IClassVisitor, which needs the
// InstanceInfo in its constructor, and read
// the traits with a nil visitor to skip them.
instanceInfoToTraits.put(iinfo, p.pos);
readTraits(p, NilVisitors.NIL_TRAITS_VISITOR);
return iinfo;
}
void readBody(IABCVisitor vabc, ABCReader p)
{
MethodBodyInfo mb = new MethodBodyInfo();
int method_id = p.readU30();
mb.max_stack = p.readU30();
mb.max_local = p.readU30();
mb.initial_scope = p.readU30();
mb.max_scope = p.readU30();
mb.code_len = p.readU30();
mb.setMethodInfo(this.readPool(methodInfos, method_id, "methodInfo"));
IMethodBodyVisitor mv = null;
ITraitsVisitor tv = NilVisitors.NIL_TRAITS_VISITOR;
if (this.readPool(methodVisitors, method_id, "methodVisitor") != null)
mv = this.readPool(methodVisitors, method_id, "methodVisitor").visitBody(mb);
if (mv != null)
{
mv.visit();
readCode(mb, mv, p);
tv = mv.visitTraits();
mv.visitEnd();
this.readPool(methodVisitors, method_id, "methodVisitor").visitEnd();
this.methodVisitors[method_id] = null;
}
else
{
p.pos += mb.code_len;
skipExceptions(p);
}
readTraits(p, tv);
tv.visitEnd();
}
void readCode(MethodBodyInfo mb, IMethodBodyVisitor m, ABCReader p)
{
int end_pos = p.pos + mb.code_len;
// Read the exception information to get label information
// that can't be deduced from the instructions themselves.
Map<Integer, Label> labels_by_pos = readExceptions(p, m, end_pos);
while (p.pos < end_pos)
{
int insn_pos = p.pos;
int op = p.readU8();
switch (op)
{
// Instructions with no operands.
case OP_add:
case OP_add_i:
case OP_astypelate:
case OP_bitand:
case OP_bitnot:
case OP_bitor:
case OP_bitxor:
case OP_checkfilter:
case OP_coerce_a:
case OP_coerce_b:
case OP_coerce_d:
case OP_coerce_i:
case OP_coerce_s:
case OP_convert_b:
case OP_convert_i:
case OP_convert_d:
case OP_convert_o:
case OP_convert_u:
case OP_convert_s:
case OP_decrement:
case OP_decrement_i:
case OP_divide:
case OP_dup:
case OP_dxnslate:
case OP_equals:
case OP_esc_xattr:
case OP_esc_xelem:
case OP_getglobalscope:
case OP_getlocal0:
case OP_getlocal1:
case OP_getlocal2:
case OP_getlocal3:
case OP_greaterequals:
case OP_greaterthan:
case OP_hasnext:
case OP_in:
case OP_increment:
case OP_increment_i:
case OP_instanceof:
case OP_istypelate:
case OP_lessequals:
case OP_lessthan:
case OP_lshift:
case OP_modulo:
case OP_multiply:
case OP_multiply_i:
case OP_negate:
case OP_negate_i:
case OP_newactivation:
case OP_nextname:
case OP_nextvalue:
case OP_nop:
case OP_not:
case OP_pop:
case OP_popscope:
case OP_pushfalse:
case OP_pushtrue:
case OP_pushnan:
case OP_pushnull:
case OP_pushscope:
case OP_pushundefined:
case OP_pushwith:
case OP_returnvalue:
case OP_returnvoid:
case OP_rshift:
case OP_setlocal0:
case OP_setlocal1:
case OP_setlocal2:
case OP_setlocal3:
case OP_strictequals:
case OP_subtract:
case OP_subtract_i:
case OP_swap:
case OP_throw:
case OP_typeof:
case OP_unplus:
case OP_urshift:
case OP_bkpt:
case OP_timestamp:
case OP_coerce_o:
case OP_li8:
case OP_li16:
case OP_li32:
case OP_lf32:
case OP_lf64:
case OP_si8:
case OP_si16:
case OP_si32:
case OP_sf32:
case OP_sf64:
case OP_sxi1:
case OP_sxi8:
case OP_sxi16:
{
m.visitInstruction(op);
break;
}
// Opcodes with two uint operands.
case OP_hasnext2:
{
m.visitInstruction(op, new Object[] {Integer.valueOf(p.readU30()), Integer.valueOf(p.readU30())});
break;
}
// Opcodes with a MethodInfo and an integer operand.
case OP_callstatic:
{
m.visitInstruction(op, new Object[] {this.readPool(methodInfos, p.readU30(), "methodInfo"), Integer.valueOf(p.readU30())});
break;
}
// Opcodes with one name operand.
case OP_findproperty:
case OP_findpropstrict:
case OP_getlex:
case OP_getsuper:
case OP_setsuper:
case OP_getproperty:
case OP_setproperty:
case OP_deleteproperty:
case OP_getdescendants:
case OP_initproperty:
case OP_istype:
case OP_coerce:
case OP_astype:
case OP_finddef:
{
m.visitInstruction(op, readPool(names, p.readU30(), "name"));
break;
}
// Opcodes with a name and an integer operand.
case OP_callproperty:
case OP_callproplex:
case OP_callpropvoid:
case OP_callsuper:
case OP_callsupervoid:
case OP_constructprop:
{
m.visitInstruction(op, new Object[] {readPool(names, p.readU30(), "name"), p.readU30()});
break;
}
// Opcodes with an unsigned immediate operand.
case OP_constructsuper:
case OP_call:
case OP_construct:
case OP_newarray:
case OP_newobject:
{
m.visitInstruction(op, p.readU30());
break;
}
// Opcodes with a branch operand (a signed immediate operand
// that designates a jump target).
case OP_ifnlt:
case OP_ifnle:
case OP_ifngt:
case OP_ifnge:
case OP_iftrue:
case OP_iffalse:
case OP_ifeq:
case OP_ifne:
case OP_iflt:
case OP_ifle:
case OP_ifgt:
case OP_ifge:
case OP_ifstricteq:
case OP_ifstrictne:
case OP_jump:
{
// Jump offset computed from the
// instruction following the branch.
int jump_target = p.readS24() + p.pos;
if (jump_target < insn_pos)
{
assert (labels_by_pos.containsKey(jump_target)) : "Unmapped backwards branch target " + jump_target + ", insn_pos " + insn_pos + ": " + labels_by_pos;
}
m.visitInstruction(op, addLabel(jump_target, labels_by_pos));
break;
}
// Lookupswitch, with a table of labels.
case OP_lookupswitch:
{
int default_case = p.readS24() + insn_pos;
Label default_label = addLabel(default_case, labels_by_pos);
int case_count = p.readU30() + 1;
Label[] switch_labels = new Label[case_count + 1];
switch_labels[case_count] = default_label;
for (int i = 0; i < case_count; i++)
{
int current_case = p.readS24() + insn_pos;
switch_labels[i] = addLabel(current_case, labels_by_pos);
}
m.visitInstruction(op, switch_labels);
break;
}
// OP_label, no operands generates a label as a side effect.
case OP_label:
{
addLabel(insn_pos, labels_by_pos);
m.visitInstruction(op);
break;
}
// Opcodes with an immediate integer operand.
case OP_getlocal:
case OP_setlocal:
case OP_getslot:
case OP_setslot:
case OP_kill:
case OP_inclocal:
case OP_declocal:
case OP_inclocal_i:
case OP_declocal_i:
case OP_newcatch:
case OP_getglobalslot:
case OP_setglobalslot:
case OP_applytype:
case OP_pushshort:
case OP_debugline:
case OP_bkptline:
{
m.visitInstruction(op, p.readU30());
break;
}
// Opcodes with an instance operand.
case OP_newclass:
{
m.visitInstruction(op, this.readPool(classInfos, p.readU30(), "classInfo"));
break;
}
// Opcodes with a byte operand.
case OP_pushbyte:
case OP_getscopeobject:
{
m.visitInstruction(op, p.readU8());
break;
}
// Opcodes with a string operand.
case OP_pushstring:
case OP_dxns:
case OP_debugfile:
{
m.visitInstruction(op, this.readPool(strings, p.readU30(), "string"));
break;
}
// Opcodes with a namespace operand.
case OP_pushnamespace:
{
m.visitInstruction(op, this.readPool(namespaces, p.readU30(), "namespace"));
break;
}
// Opcodes with a pooled operand.
case OP_pushint:
{
m.visitInstruction(op, Integer.valueOf(this.readIntPool(p.readU30())));
break;
}
case OP_pushuint:
{
m.visitInstruction(op, Long.valueOf(this.readUintPool(p.readU30())));
break;
}
case OP_pushdouble:
{
m.visitInstruction(op, Double.valueOf(this.readDoublePool(p.readU30())));
break;
}
// Opcodes with a function operand.
case OP_newfunction:
{
m.visitInstruction(op, this.readPool(methodInfos, p.readU30(), "methodInfo"));
break;
}
// Opcodes with a grab-bag of operands.
case OP_debug:
{
m.visitInstruction(op, new Object[] {p.readU8(), this.readPool(strings, p.readU30(), "string"), p.readU8(), p.readU30()});
break;
}
default:
{
throw new IllegalArgumentException(String.format("Unknown ABC bytecode 0x%x", op));
}
}
Label insn_label = labels_by_pos.get(insn_pos);
if (insn_label != null)
m.labelCurrent(insn_label);
}
// Skip over the exception table, it's already been read.
skipExceptions(p);
}
private Map<Integer, Label> readExceptions(ABCReader p, IMethodBodyVisitor mbv, int end_pos)
{
Map<Integer, Label> result = new HashMap<Integer, Label>();
int start_pos = p.pos;
p.pos = end_pos;
int n_exceptions = p.readU30();
for (int i = 0; i < n_exceptions; i++)
{
int from_pos = p.readU30() + start_pos;
int to_pos = p.readU30() + start_pos;
int catch_pos = p.readU30() + start_pos;
if (!result.containsKey(from_pos))
result.put(from_pos, new Label("try/from", Label.LabelKind.ANY_INSTRUCTION));
if (!result.containsKey(to_pos))
result.put(to_pos, new Label("try/to", Label.LabelKind.ANY_INSTRUCTION));
if (!result.containsKey(catch_pos))
result.put(catch_pos, new Label("catch"));
Name catch_type = readPool(names, p.readU30(), "name");
int catch_idx = p.readU30();
Name catch_var = readPool(names, catch_idx, "name");
if (mbv != null)
mbv.visitException(result.get(from_pos), result.get(to_pos), result.get(catch_pos), catch_type, catch_var);
}
p.pos = start_pos;
return result;
}
private void skipExceptions(ABCReader p)
{
int n_exceptions = p.readU30();
for (int i = 0; i < n_exceptions * 5; i++)
{
p.readU30();
}
}
private Label addLabel(int key, Map<Integer, Label> labels)
{
if (!labels.containsKey(key))
labels.put(key, new Label());
return labels.get(key);
}
/**
* Read a required value from a pool.
*
* @param pool - the pool of interest.
* @param idx - the proposed pool index.
* @param poolName - the name of the pool, used for diagnostics.
* @return the pool's value at the specified index.
* @throws IllegalStateException if the index is out of range.
*/
private <T> T readPool(T[] pool, final int idx, final String poolName)
{
if (idx < 0 || idx >= pool.length)
throw new IllegalStateException(String.format("%d is not a valid %s pool index.", idx, poolName));
return pool[idx];
}
/**
* Read an optional value from a pool.
*
* @param pool - the pool of interest.
* @param idx - the proposed pool index.
* @param defaultValue - the default value to use if the pool entry's not
* present.
* @return the pool's value at the specified index, or null if the index is
* out of range.
*/
private <T> T readPoolWithDefault(T[] pool, final int idx, final T defaultValue)
{
if (idx < 0)
throw new IllegalStateException(String.format("%d is not a valid pool index", idx));
else if (idx >= pool.length)
return defaultValue;
return pool[idx];
}
/**
* Read a required value from the int pool.
*
* @param idx - the proposed pool index.
* @return the pool's value at the specified index.
* @throws IllegalStateException if the index is out of range.
*/
private int readIntPool(final int idx)
{
if (idx < 0 || idx >= this.ints.length)
throw new IllegalStateException(String.format("%d is not a valid int pool index.", idx));
return this.ints[idx];
}
/**
* Read a required value from the uint pool.
*
* @param idx - the proposed pool index.
* @return the pool's value at the specified index.
* @throws IllegalStateException if the index is out of range.
*/
private long readUintPool(final int idx)
{
if (idx < 0 || idx >= this.uints.length)
throw new IllegalStateException(String.format("%d is not a valid uint pool index.", idx));
return this.uints[idx];
}
/**
* Read a required value from the double pool.
*
* @param idx - the proposed pool index.
* @return the pool's value at the specified index.
* @throws IllegalStateException if the index is out of range.
*/
private double readDoublePool(final int idx)
{
if (idx < 0 || idx >= this.doubles.length)
throw new IllegalStateException(String.format("%d is not a valid double pool index.", idx));
return this.doubles[idx];
}
}