| /* |
| * |
| * 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]; |
| } |
| |
| } |