--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/com/sun/java/util/jar/pack/Attribute.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1698 @@
+/*
+ * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.java.util.jar.pack;
+
+import com.sun.java.util.jar.pack.ConstantPool.Entry;
+import com.sun.java.util.jar.pack.ConstantPool.Index;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import static com.sun.java.util.jar.pack.Constants.*;
+
+/**
+ * Represents an attribute in a class-file.
+ * Takes care to remember where constant pool indexes occur.
+ * Implements the "little language" of Pack200 for describing
+ * attribute layouts.
+ * @author John Rose
+ */
+class Attribute implements Comparable<Attribute> {
+ // Attribute instance fields.
+
+ Layout def; // the name and format of this attr
+ byte[] bytes; // the actual bytes
+ Object fixups; // reference relocations, if any are required
+
+ public String name() { return def.name(); }
+ public Layout layout() { return def; }
+ public byte[] bytes() { return bytes; }
+ public int size() { return bytes.length; }
+ public Entry getNameRef() { return def.getNameRef(); }
+
+ private Attribute(Attribute old) {
+ this.def = old.def;
+ this.bytes = old.bytes;
+ this.fixups = old.fixups;
+ }
+
+ public Attribute(Layout def, byte[] bytes, Object fixups) {
+ this.def = def;
+ this.bytes = bytes;
+ this.fixups = fixups;
+ Fixups.setBytes(fixups, bytes);
+ }
+ public Attribute(Layout def, byte[] bytes) {
+ this(def, bytes, null);
+ }
+
+ public Attribute addContent(byte[] bytes, Object fixups) {
+ assert(isCanonical());
+ if (bytes.length == 0 && fixups == null)
+ return this;
+ Attribute res = new Attribute(this);
+ res.bytes = bytes;
+ res.fixups = fixups;
+ Fixups.setBytes(fixups, bytes);
+ return res;
+ }
+ public Attribute addContent(byte[] bytes) {
+ return addContent(bytes, null);
+ }
+
+ public void finishRefs(Index ix) {
+ if (fixups != null) {
+ Fixups.finishRefs(fixups, bytes, ix);
+ fixups = null;
+ }
+ }
+
+ public boolean isCanonical() {
+ return this == def.canon;
+ }
+
+ @Override
+ public int compareTo(Attribute that) {
+ return this.def.compareTo(that.def);
+ }
+
+ private static final Map<List<Attribute>, List<Attribute>> canonLists = new HashMap<>();
+ private static final Map<Layout, Attribute> attributes = new HashMap<>();
+ private static final Map<Layout, Attribute> standardDefs = new HashMap<>();
+
+ // Canonicalized lists of trivial attrs (Deprecated, etc.)
+ // are used by trimToSize, in order to reduce footprint
+ // of some common cases. (Note that Code attributes are
+ // always zero size.)
+ public static List<Attribute> getCanonList(List<Attribute> al) {
+ synchronized (canonLists) {
+ List<Attribute> cl = canonLists.get(al);
+ if (cl == null) {
+ cl = new ArrayList<>(al.size());
+ cl.addAll(al);
+ cl = Collections.unmodifiableList(cl);
+ canonLists.put(al, cl);
+ }
+ return cl;
+ }
+ }
+
+ // Find the canonical empty attribute with the given ctype, name, layout.
+ public static Attribute find(int ctype, String name, String layout) {
+ Layout key = Layout.makeKey(ctype, name, layout);
+ synchronized (attributes) {
+ Attribute a = attributes.get(key);
+ if (a == null) {
+ a = new Layout(ctype, name, layout).canonicalInstance();
+ attributes.put(key, a);
+ }
+ return a;
+ }
+ }
+
+ public static Layout keyForLookup(int ctype, String name) {
+ return Layout.makeKey(ctype, name);
+ }
+
+ // Find canonical empty attribute with given ctype and name,
+ // and with the standard layout.
+ public static Attribute lookup(Map<Layout, Attribute> defs, int ctype,
+ String name) {
+ if (defs == null) {
+ defs = standardDefs;
+ }
+ return defs.get(Layout.makeKey(ctype, name));
+ }
+
+ public static Attribute define(Map<Layout, Attribute> defs, int ctype,
+ String name, String layout) {
+ Attribute a = find(ctype, name, layout);
+ defs.put(Layout.makeKey(ctype, name), a);
+ return a;
+ }
+
+ static {
+ Map<Layout, Attribute> sd = standardDefs;
+ define(sd, ATTR_CONTEXT_CLASS, "Signature", "RSH");
+ define(sd, ATTR_CONTEXT_CLASS, "Synthetic", "");
+ define(sd, ATTR_CONTEXT_CLASS, "Deprecated", "");
+ define(sd, ATTR_CONTEXT_CLASS, "SourceFile", "RUH");
+ define(sd, ATTR_CONTEXT_CLASS, "EnclosingMethod", "RCHRDNH");
+ define(sd, ATTR_CONTEXT_CLASS, "InnerClasses", "NH[RCHRCNHRUNHFH]");
+ define(sd, ATTR_CONTEXT_CLASS, "BootstrapMethods", "NH[RMHNH[KLH]]");
+
+ define(sd, ATTR_CONTEXT_FIELD, "Signature", "RSH");
+ define(sd, ATTR_CONTEXT_FIELD, "Synthetic", "");
+ define(sd, ATTR_CONTEXT_FIELD, "Deprecated", "");
+ define(sd, ATTR_CONTEXT_FIELD, "ConstantValue", "KQH");
+
+ define(sd, ATTR_CONTEXT_METHOD, "Signature", "RSH");
+ define(sd, ATTR_CONTEXT_METHOD, "Synthetic", "");
+ define(sd, ATTR_CONTEXT_METHOD, "Deprecated", "");
+ define(sd, ATTR_CONTEXT_METHOD, "Exceptions", "NH[RCH]");
+ define(sd, ATTR_CONTEXT_METHOD, "MethodParameters", "NB[RUNHFH]");
+ //define(sd, ATTR_CONTEXT_METHOD, "Code", "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]");
+
+ define(sd, ATTR_CONTEXT_CODE, "StackMapTable",
+ ("[NH[(1)]]" +
+ "[TB" +
+ "(64-127)[(2)]" +
+ "(247)[(1)(2)]" +
+ "(248-251)[(1)]" +
+ "(252)[(1)(2)]" +
+ "(253)[(1)(2)(2)]" +
+ "(254)[(1)(2)(2)(2)]" +
+ "(255)[(1)NH[(2)]NH[(2)]]" +
+ "()[]" +
+ "]" +
+ "[H]" +
+ "[TB(7)[RCH](8)[PH]()[]]"));
+
+ define(sd, ATTR_CONTEXT_CODE, "LineNumberTable", "NH[PHH]");
+ define(sd, ATTR_CONTEXT_CODE, "LocalVariableTable", "NH[PHOHRUHRSHH]");
+ define(sd, ATTR_CONTEXT_CODE, "LocalVariableTypeTable", "NH[PHOHRUHRSHH]");
+ //define(sd, ATTR_CONTEXT_CODE, "CharacterRangeTable", "NH[PHPOHIIH]");
+ //define(sd, ATTR_CONTEXT_CODE, "CoverageTable", "NH[PHHII]");
+
+ // Note: Code and InnerClasses are special-cased elsewhere.
+ // Their layout specs. are given here for completeness.
+ // The Code spec is incomplete, in that it does not distinguish
+ // bytecode bytes or locate CP references.
+ // The BootstrapMethods attribute is also special-cased
+ // elsewhere as an appendix to the local constant pool.
+ }
+
+ // Metadata.
+ //
+ // We define metadata using similar layouts
+ // for all five kinds of metadata attributes and 2 type metadata attributes
+ //
+ // Regular annotations are a counted list of [RSHNH[RUH(1)]][...]
+ // pack.method.attribute.RuntimeVisibleAnnotations=[NH[(1)]][RSHNH[RUH(1)]][TB...]
+ //
+ // Parameter annotations are a counted list of regular annotations.
+ // pack.method.attribute.RuntimeVisibleParameterAnnotations=[NB[(1)]][NH[(1)]][RSHNH[RUH(1)]][TB...]
+ //
+ // RuntimeInvisible annotations are defined similarly...
+ // Non-method annotations are defined similarly...
+ //
+ // Annotation are a simple tagged value [TB...]
+ // pack.attribute.method.AnnotationDefault=[TB...]
+
+ static {
+ String mdLayouts[] = {
+ Attribute.normalizeLayoutString
+ (""
+ +"\n # parameter_annotations :="
+ +"\n [ NB[(1)] ] # forward call to annotations"
+ ),
+ Attribute.normalizeLayoutString
+ (""
+ +"\n # annotations :="
+ +"\n [ NH[(1)] ] # forward call to annotation"
+ +"\n "
+ ),
+ Attribute.normalizeLayoutString
+ (""
+ +"\n # annotation :="
+ +"\n [RSH"
+ +"\n NH[RUH (1)] # forward call to value"
+ +"\n ]"
+ ),
+ Attribute.normalizeLayoutString
+ (""
+ +"\n # value :="
+ +"\n [TB # Callable 2 encodes one tagged value."
+ +"\n (\\B,\\C,\\I,\\S,\\Z)[KIH]"
+ +"\n (\\D)[KDH]"
+ +"\n (\\F)[KFH]"
+ +"\n (\\J)[KJH]"
+ +"\n (\\c)[RSH]"
+ +"\n (\\e)[RSH RUH]"
+ +"\n (\\s)[RUH]"
+ +"\n (\\[)[NH[(0)]] # backward self-call to value"
+ +"\n (\\@)[RSH NH[RUH (0)]] # backward self-call to value"
+ +"\n ()[] ]"
+ )
+ };
+ /*
+ * RuntimeVisibleTypeAnnotation and RuntimeInvisibleTypeAnnotatation are
+ * similar to RuntimeVisibleAnnotation and RuntimeInvisibleAnnotation,
+ * a type-annotation union and a type-path structure precedes the
+ * annotation structure
+ */
+ String typeLayouts[] = {
+ Attribute.normalizeLayoutString
+ (""
+ +"\n # type-annotations :="
+ +"\n [ NH[(1)(2)(3)] ] # forward call to type-annotations"
+ ),
+ Attribute.normalizeLayoutString
+ ( ""
+ +"\n # type-annotation :="
+ +"\n [TB"
+ +"\n (0-1) [B] # {CLASS, METHOD}_TYPE_PARAMETER"
+ +"\n (16) [FH] # CLASS_EXTENDS"
+ +"\n (17-18) [BB] # {CLASS, METHOD}_TYPE_PARAMETER_BOUND"
+ +"\n (19-21) [] # FIELD, METHOD_RETURN, METHOD_RECEIVER"
+ +"\n (22) [B] # METHOD_FORMAL_PARAMETER"
+ +"\n (23) [H] # THROWS"
+ +"\n (64-65) [NH[PHOHH]] # LOCAL_VARIABLE, RESOURCE_VARIABLE"
+ +"\n (66) [H] # EXCEPTION_PARAMETER"
+ +"\n (67-70) [PH] # INSTANCEOF, NEW, {CONSTRUCTOR, METHOD}_REFERENCE_RECEIVER"
+ +"\n (71-75) [PHB] # CAST, {CONSTRUCTOR,METHOD}_INVOCATION_TYPE_ARGUMENT, {CONSTRUCTOR, METHOD}_REFERENCE_TYPE_ARGUMENT"
+ +"\n ()[] ]"
+ ),
+ Attribute.normalizeLayoutString
+ (""
+ +"\n # type-path"
+ +"\n [ NB[BB] ]"
+ )
+ };
+ Map<Layout, Attribute> sd = standardDefs;
+ String defaultLayout = mdLayouts[3];
+ String annotationsLayout = mdLayouts[1] + mdLayouts[2] + mdLayouts[3];
+ String paramsLayout = mdLayouts[0] + annotationsLayout;
+ String typesLayout = typeLayouts[0] + typeLayouts[1] +
+ typeLayouts[2] + mdLayouts[2] + mdLayouts[3];
+
+ for (int ctype = 0; ctype < ATTR_CONTEXT_LIMIT; ctype++) {
+ if (ctype != ATTR_CONTEXT_CODE) {
+ define(sd, ctype,
+ "RuntimeVisibleAnnotations", annotationsLayout);
+ define(sd, ctype,
+ "RuntimeInvisibleAnnotations", annotationsLayout);
+
+ if (ctype == ATTR_CONTEXT_METHOD) {
+ define(sd, ctype,
+ "RuntimeVisibleParameterAnnotations", paramsLayout);
+ define(sd, ctype,
+ "RuntimeInvisibleParameterAnnotations", paramsLayout);
+ define(sd, ctype,
+ "AnnotationDefault", defaultLayout);
+ }
+ }
+ define(sd, ctype,
+ "RuntimeVisibleTypeAnnotations", typesLayout);
+ define(sd, ctype,
+ "RuntimeInvisibleTypeAnnotations", typesLayout);
+ }
+ }
+
+ public static String contextName(int ctype) {
+ switch (ctype) {
+ case ATTR_CONTEXT_CLASS: return "class";
+ case ATTR_CONTEXT_FIELD: return "field";
+ case ATTR_CONTEXT_METHOD: return "method";
+ case ATTR_CONTEXT_CODE: return "code";
+ }
+ return null;
+ }
+
+ /** Base class for any attributed object (Class, Field, Method, Code).
+ * Flags are included because they are used to help transmit the
+ * presence of attributes. That is, flags are a mix of modifier
+ * bits and attribute indicators.
+ */
+ public abstract static
+ class Holder {
+
+ // We need this abstract method to interpret embedded CP refs.
+ protected abstract Entry[] getCPMap();
+
+ protected int flags; // defined here for convenience
+ protected List<Attribute> attributes;
+
+ public int attributeSize() {
+ return (attributes == null) ? 0 : attributes.size();
+ }
+
+ public void trimToSize() {
+ if (attributes == null) {
+ return;
+ }
+ if (attributes.isEmpty()) {
+ attributes = null;
+ return;
+ }
+ if (attributes instanceof ArrayList) {
+ ArrayList<Attribute> al = (ArrayList<Attribute>)attributes;
+ al.trimToSize();
+ boolean allCanon = true;
+ for (Attribute a : al) {
+ if (!a.isCanonical()) {
+ allCanon = false;
+ }
+ if (a.fixups != null) {
+ assert(!a.isCanonical());
+ a.fixups = Fixups.trimToSize(a.fixups);
+ }
+ }
+ if (allCanon) {
+ // Replace private writable attribute list
+ // with only trivial entries by public unique
+ // immutable attribute list with the same entries.
+ attributes = getCanonList(al);
+ }
+ }
+ }
+
+ public void addAttribute(Attribute a) {
+ if (attributes == null)
+ attributes = new ArrayList<>(3);
+ else if (!(attributes instanceof ArrayList))
+ attributes = new ArrayList<>(attributes); // unfreeze it
+ attributes.add(a);
+ }
+
+ public Attribute removeAttribute(Attribute a) {
+ if (attributes == null) return null;
+ if (!attributes.contains(a)) return null;
+ if (!(attributes instanceof ArrayList))
+ attributes = new ArrayList<>(attributes); // unfreeze it
+ attributes.remove(a);
+ return a;
+ }
+
+ public Attribute getAttribute(int n) {
+ return attributes.get(n);
+ }
+
+ protected void visitRefs(int mode, Collection<Entry> refs) {
+ if (attributes == null) return;
+ for (Attribute a : attributes) {
+ a.visitRefs(this, mode, refs);
+ }
+ }
+
+ static final List<Attribute> noAttributes = Arrays.asList(new Attribute[0]);
+
+ public List<Attribute> getAttributes() {
+ if (attributes == null)
+ return noAttributes;
+ return attributes;
+ }
+
+ public void setAttributes(List<Attribute> attrList) {
+ if (attrList.isEmpty())
+ attributes = null;
+ else
+ attributes = attrList;
+ }
+
+ public Attribute getAttribute(String attrName) {
+ if (attributes == null) return null;
+ for (Attribute a : attributes) {
+ if (a.name().equals(attrName))
+ return a;
+ }
+ return null;
+ }
+
+ public Attribute getAttribute(Layout attrDef) {
+ if (attributes == null) return null;
+ for (Attribute a : attributes) {
+ if (a.layout() == attrDef)
+ return a;
+ }
+ return null;
+ }
+
+ public Attribute removeAttribute(String attrName) {
+ return removeAttribute(getAttribute(attrName));
+ }
+
+ public Attribute removeAttribute(Layout attrDef) {
+ return removeAttribute(getAttribute(attrDef));
+ }
+
+ public void strip(String attrName) {
+ removeAttribute(getAttribute(attrName));
+ }
+ }
+
+ // Lightweight interface to hide details of band structure.
+ // Also used for testing.
+ public abstract static
+ class ValueStream {
+ public int getInt(int bandIndex) { throw undef(); }
+ public void putInt(int bandIndex, int value) { throw undef(); }
+ public Entry getRef(int bandIndex) { throw undef(); }
+ public void putRef(int bandIndex, Entry ref) { throw undef(); }
+ // Note: decodeBCI goes w/ getInt/Ref; encodeBCI goes w/ putInt/Ref
+ public int decodeBCI(int bciCode) { throw undef(); }
+ public int encodeBCI(int bci) { throw undef(); }
+ public void noteBackCall(int whichCallable) { /* ignore by default */ }
+ private RuntimeException undef() {
+ return new UnsupportedOperationException("ValueStream method");
+ }
+ }
+
+ // Element kinds:
+ static final byte EK_INT = 1; // B H I SH etc.
+ static final byte EK_BCI = 2; // PH POH etc.
+ static final byte EK_BCO = 3; // OH etc.
+ static final byte EK_FLAG = 4; // FH etc.
+ static final byte EK_REPL = 5; // NH[...] etc.
+ static final byte EK_REF = 6; // RUH, RUNH, KQH, etc.
+ static final byte EK_UN = 7; // TB(...)[...] etc.
+ static final byte EK_CASE = 8; // (...)[...] etc.
+ static final byte EK_CALL = 9; // (0), (1), etc.
+ static final byte EK_CBLE = 10; // [...][...] etc.
+ static final byte EF_SIGN = 1<<0; // INT is signed
+ static final byte EF_DELTA = 1<<1; // BCI/BCI value is diff'ed w/ previous
+ static final byte EF_NULL = 1<<2; // null REF is expected/allowed
+ static final byte EF_BACK = 1<<3; // call, callable, case is backward
+ static final int NO_BAND_INDEX = -1;
+
+ /** A "class" of attributes, characterized by a context-type, name
+ * and format. The formats are specified in a "little language".
+ */
+ public static
+ class Layout implements Comparable<Layout> {
+ int ctype; // attribute context type, e.g., ATTR_CONTEXT_CODE
+ String name; // name of attribute
+ boolean hasRefs; // this kind of attr contains CP refs?
+ String layout; // layout specification
+ int bandCount; // total number of elems
+ Element[] elems; // tokenization of layout
+ Attribute canon; // canonical instance of this layout
+
+ public int ctype() { return ctype; }
+ public String name() { return name; }
+ public String layout() { return layout; }
+ public Attribute canonicalInstance() { return canon; }
+
+ public Entry getNameRef() {
+ return ConstantPool.getUtf8Entry(name());
+ }
+
+ public boolean isEmpty() {
+ return layout.isEmpty();
+ }
+
+ public Layout(int ctype, String name, String layout) {
+ this.ctype = ctype;
+ this.name = name.intern();
+ this.layout = layout.intern();
+ assert(ctype < ATTR_CONTEXT_LIMIT);
+ boolean hasCallables = layout.startsWith("[");
+ try {
+ if (!hasCallables) {
+ this.elems = tokenizeLayout(this, -1, layout);
+ } else {
+ String[] bodies = splitBodies(layout);
+ // Make the callables now, so they can be linked immediately.
+ Element[] lelems = new Element[bodies.length];
+ this.elems = lelems;
+ for (int i = 0; i < lelems.length; i++) {
+ Element ce = this.new Element();
+ ce.kind = EK_CBLE;
+ ce.removeBand();
+ ce.bandIndex = NO_BAND_INDEX;
+ ce.layout = bodies[i];
+ lelems[i] = ce;
+ }
+ // Next fill them in.
+ for (int i = 0; i < lelems.length; i++) {
+ Element ce = lelems[i];
+ ce.body = tokenizeLayout(this, i, bodies[i]);
+ }
+ //System.out.println(Arrays.asList(elems));
+ }
+ } catch (StringIndexOutOfBoundsException ee) {
+ // simplest way to catch syntax errors...
+ throw new RuntimeException("Bad attribute layout: "+layout, ee);
+ }
+ // Some uses do not make a fresh one for each occurrence.
+ // For example, if layout == "", we only need one attr to share.
+ canon = new Attribute(this, noBytes);
+ }
+ private Layout() {}
+ static Layout makeKey(int ctype, String name, String layout) {
+ Layout def = new Layout();
+ def.ctype = ctype;
+ def.name = name.intern();
+ def.layout = layout.intern();
+ assert(ctype < ATTR_CONTEXT_LIMIT);
+ return def;
+ }
+ static Layout makeKey(int ctype, String name) {
+ return makeKey(ctype, name, "");
+ }
+
+ public Attribute addContent(byte[] bytes, Object fixups) {
+ return canon.addContent(bytes, fixups);
+ }
+ public Attribute addContent(byte[] bytes) {
+ return canon.addContent(bytes, null);
+ }
+
+ @Override
+ public boolean equals(Object x) {
+ return ( x != null) && ( x.getClass() == Layout.class ) &&
+ equals((Layout)x);
+ }
+ public boolean equals(Layout that) {
+ return this.name.equals(that.name)
+ && this.layout.equals(that.layout)
+ && this.ctype == that.ctype;
+ }
+ @Override
+ public int hashCode() {
+ return (((17 + name.hashCode())
+ * 37 + layout.hashCode())
+ * 37 + ctype);
+ }
+ @Override
+ public int compareTo(Layout that) {
+ int r;
+ r = this.name.compareTo(that.name);
+ if (r != 0) return r;
+ r = this.layout.compareTo(that.layout);
+ if (r != 0) return r;
+ return this.ctype - that.ctype;
+ }
+ @Override
+ public String toString() {
+ String str = contextName(ctype)+"."+name+"["+layout+"]";
+ // If -ea, print out more informative strings!
+ assert((str = stringForDebug()) != null);
+ return str;
+ }
+ private String stringForDebug() {
+ return contextName(ctype)+"."+name+Arrays.asList(elems);
+ }
+
+ public
+ class Element {
+ String layout; // spelling in the little language
+ byte flags; // EF_SIGN, etc.
+ byte kind; // EK_UINT, etc.
+ byte len; // scalar length of element
+ byte refKind; // CONSTANT_String, etc.
+ int bandIndex; // which band does this element govern?
+ int value; // extra parameter
+ Element[] body; // extra data (for replications, unions, calls)
+
+ boolean flagTest(byte mask) { return (flags & mask) != 0; }
+
+ Element() {
+ bandIndex = bandCount++;
+ }
+
+ void removeBand() {
+ --bandCount;
+ assert(bandIndex == bandCount);
+ bandIndex = NO_BAND_INDEX;
+ }
+
+ public boolean hasBand() {
+ return bandIndex >= 0;
+ }
+ public String toString() {
+ String str = layout;
+ // If -ea, print out more informative strings!
+ assert((str = stringForDebug()) != null);
+ return str;
+ }
+ private String stringForDebug() {
+ Element[] lbody = this.body;
+ switch (kind) {
+ case EK_CALL:
+ lbody = null;
+ break;
+ case EK_CASE:
+ if (flagTest(EF_BACK))
+ lbody = null;
+ break;
+ }
+ return layout
+ + (!hasBand()?"":"#"+bandIndex)
+ + "<"+ (flags==0?"":""+flags)+kind+len
+ + (refKind==0?"":""+refKind) + ">"
+ + (value==0?"":"("+value+")")
+ + (lbody==null?"": ""+Arrays.asList(lbody));
+ }
+ }
+
+ public boolean hasCallables() {
+ return (elems.length > 0 && elems[0].kind == EK_CBLE);
+ }
+ private static final Element[] noElems = {};
+ public Element[] getCallables() {
+ if (hasCallables()) {
+ Element[] nelems = Arrays.copyOf(elems, elems.length);
+ return nelems;
+ } else
+ return noElems; // no callables at all
+ }
+ public Element[] getEntryPoint() {
+ if (hasCallables())
+ return elems[0].body; // body of first callable
+ else {
+ Element[] nelems = Arrays.copyOf(elems, elems.length);
+ return nelems; // no callables; whole body
+ }
+ }
+
+ /** Return a sequence of tokens from the given attribute bytes.
+ * Sequence elements will be 1-1 correspondent with my layout tokens.
+ */
+ public void parse(Holder holder,
+ byte[] bytes, int pos, int len, ValueStream out) {
+ int end = parseUsing(getEntryPoint(),
+ holder, bytes, pos, len, out);
+ if (end != pos + len)
+ throw new InternalError("layout parsed "+(end-pos)+" out of "+len+" bytes");
+ }
+ /** Given a sequence of tokens, return the attribute bytes.
+ * Sequence elements must be 1-1 correspondent with my layout tokens.
+ * The returned object is a cookie for Fixups.finishRefs, which
+ * must be used to harden any references into integer indexes.
+ */
+ public Object unparse(ValueStream in, ByteArrayOutputStream out) {
+ Object[] fixups = { null };
+ unparseUsing(getEntryPoint(), fixups, in, out);
+ return fixups[0]; // return ref-bearing cookie, if any
+ }
+
+ public String layoutForClassVersion(Package.Version vers) {
+ if (vers.lessThan(JAVA6_MAX_CLASS_VERSION)) {
+ // Disallow layout syntax in the oldest protocol version.
+ return expandCaseDashNotation(layout);
+ }
+ return layout;
+ }
+ }
+
+ public static
+ class FormatException extends IOException {
+ private static final long serialVersionUID = -2542243830788066513L;
+
+ private int ctype;
+ private String name;
+ String layout;
+ public FormatException(String message,
+ int ctype, String name, String layout) {
+ super(ATTR_CONTEXT_NAME[ctype]+ " attribute \"" + name + "\"" +
+ (message == null? "" : (": " + message)));
+ this.ctype = ctype;
+ this.name = name;
+ this.layout = layout;
+ }
+ public FormatException(String message,
+ int ctype, String name) {
+ this(message, ctype, name, null);
+ }
+ }
+
+ void visitRefs(Holder holder, int mode, final Collection<Entry> refs) {
+ if (mode == VRM_CLASSIC) {
+ refs.add(getNameRef());
+ }
+ // else the name is owned by the layout, and is processed elsewhere
+ if (bytes.length == 0) return; // quick exit
+ if (!def.hasRefs) return; // quick exit
+ if (fixups != null) {
+ Fixups.visitRefs(fixups, refs);
+ return;
+ }
+ // References (to a local cpMap) are embedded in the bytes.
+ def.parse(holder, bytes, 0, bytes.length,
+ new ValueStream() {
+ @Override
+ public void putInt(int bandIndex, int value) {
+ }
+ @Override
+ public void putRef(int bandIndex, Entry ref) {
+ refs.add(ref);
+ }
+ @Override
+ public int encodeBCI(int bci) {
+ return bci;
+ }
+ });
+ }
+
+ public void parse(Holder holder, byte[] bytes, int pos, int len, ValueStream out) {
+ def.parse(holder, bytes, pos, len, out);
+ }
+ public Object unparse(ValueStream in, ByteArrayOutputStream out) {
+ return def.unparse(in, out);
+ }
+
+ @Override
+ public String toString() {
+ return def
+ +"{"+(bytes == null ? -1 : size())+"}"
+ +(fixups == null? "": fixups.toString());
+ }
+
+ /** Remove any informal "pretty printing" from the layout string.
+ * Removes blanks and control chars.
+ * Removes '#' comments (to end of line).
+ * Replaces '\c' by the decimal code of the character c.
+ * Replaces '0xNNN' by the decimal code of the hex number NNN.
+ */
+ public static
+ String normalizeLayoutString(String layout) {
+ StringBuilder buf = new StringBuilder();
+ for (int i = 0, len = layout.length(); i < len; ) {
+ char ch = layout.charAt(i++);
+ if (ch <= ' ') {
+ // Skip whitespace and control chars
+ continue;
+ } else if (ch == '#') {
+ // Skip to end of line.
+ int end1 = layout.indexOf('\n', i);
+ int end2 = layout.indexOf('\r', i);
+ if (end1 < 0) end1 = len;
+ if (end2 < 0) end2 = len;
+ i = Math.min(end1, end2);
+ } else if (ch == '\\') {
+ // Map a character reference to its decimal code.
+ buf.append((int) layout.charAt(i++));
+ } else if (ch == '0' && layout.startsWith("0x", i-1)) {
+ // Map a hex numeral to its decimal code.
+ int start = i-1;
+ int end = start+2;
+ while (end < len) {
+ int dig = layout.charAt(end);
+ if ((dig >= '0' && dig <= '9') ||
+ (dig >= 'a' && dig <= 'f'))
+ ++end;
+ else
+ break;
+ }
+ if (end > start) {
+ String num = layout.substring(start, end);
+ buf.append(Integer.decode(num));
+ i = end;
+ } else {
+ buf.append(ch);
+ }
+ } else {
+ buf.append(ch);
+ }
+ }
+ String result = buf.toString();
+ if (false && !result.equals(layout)) {
+ Utils.log.info("Normalizing layout string");
+ Utils.log.info(" From: "+layout);
+ Utils.log.info(" To: "+result);
+ }
+ return result;
+ }
+
+ /// Subroutines for parsing and unparsing:
+
+ /** Parse the attribute layout language.
+<pre>
+ attribute_layout:
+ ( layout_element )* | ( callable )+
+ layout_element:
+ ( integral | replication | union | call | reference )
+
+ callable:
+ '[' body ']'
+ body:
+ ( layout_element )+
+
+ integral:
+ ( unsigned_int | signed_int | bc_index | bc_offset | flag )
+ unsigned_int:
+ uint_type
+ signed_int:
+ 'S' uint_type
+ any_int:
+ ( unsigned_int | signed_int )
+ bc_index:
+ ( 'P' uint_type | 'PO' uint_type )
+ bc_offset:
+ 'O' any_int
+ flag:
+ 'F' uint_type
+ uint_type:
+ ( 'B' | 'H' | 'I' | 'V' )
+
+ replication:
+ 'N' uint_type '[' body ']'
+
+ union:
+ 'T' any_int (union_case)* '(' ')' '[' (body)? ']'
+ union_case:
+ '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']'
+ union_case_tag:
+ ( numeral | numeral '-' numeral )
+ call:
+ '(' numeral ')'
+
+ reference:
+ reference_type ( 'N' )? uint_type
+ reference_type:
+ ( constant_ref | schema_ref | utf8_ref | untyped_ref )
+ constant_ref:
+ ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' | 'KM' | 'KT' | 'KL' )
+ schema_ref:
+ ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' | 'RY' | 'RB' | 'RN' )
+ utf8_ref:
+ 'RU'
+ untyped_ref:
+ 'RQ'
+
+ numeral:
+ '(' ('-')? (digit)+ ')'
+ digit:
+ ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' )
+ </pre>
+ */
+ static //private
+ Layout.Element[] tokenizeLayout(Layout self, int curCble, String layout) {
+ List<Layout.Element> col = new ArrayList<>(layout.length());
+ tokenizeLayout(self, curCble, layout, col);
+ Layout.Element[] res = new Layout.Element[col.size()];
+ col.toArray(res);
+ return res;
+ }
+ static //private
+ void tokenizeLayout(Layout self, int curCble, String layout, List<Layout.Element> col) {
+ boolean prevBCI = false;
+ for (int len = layout.length(), i = 0; i < len; ) {
+ int start = i;
+ int body;
+ Layout.Element e = self.new Element();
+ byte kind;
+ //System.out.println("at "+i+": ..."+layout.substring(i));
+ // strip a prefix
+ switch (layout.charAt(i++)) {
+ /// layout_element: integral
+ case 'B': case 'H': case 'I': case 'V': // unsigned_int
+ kind = EK_INT;
+ --i; // reparse
+ i = tokenizeUInt(e, layout, i);
+ break;
+ case 'S': // signed_int
+ kind = EK_INT;
+ --i; // reparse
+ i = tokenizeSInt(e, layout, i);
+ break;
+ case 'P': // bc_index
+ kind = EK_BCI;
+ if (layout.charAt(i++) == 'O') {
+ // bc_index: 'PO' tokenizeUInt
+ e.flags |= EF_DELTA;
+ // must follow P or PO:
+ if (!prevBCI)
+ { i = -i; continue; } // fail
+ i++; // move forward
+ }
+ --i; // reparse
+ i = tokenizeUInt(e, layout, i);
+ break;
+ case 'O': // bc_offset
+ kind = EK_BCO;
+ e.flags |= EF_DELTA;
+ // must follow P or PO:
+ if (!prevBCI)
+ { i = -i; continue; } // fail
+ i = tokenizeSInt(e, layout, i);
+ break;
+ case 'F': // flag
+ kind = EK_FLAG;
+ i = tokenizeUInt(e, layout, i);
+ break;
+ case 'N': // replication: 'N' uint '[' elem ... ']'
+ kind = EK_REPL;
+ i = tokenizeUInt(e, layout, i);
+ if (layout.charAt(i++) != '[')
+ { i = -i; continue; } // fail
+ i = skipBody(layout, body = i);
+ e.body = tokenizeLayout(self, curCble,
+ layout.substring(body, i++));
+ break;
+ case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']'
+ kind = EK_UN;
+ i = tokenizeSInt(e, layout, i);
+ List<Layout.Element> cases = new ArrayList<>();
+ for (;;) {
+ // Keep parsing cases until we hit the default case.
+ if (layout.charAt(i++) != '(')
+ { i = -i; break; } // fail
+ int beg = i;
+ i = layout.indexOf(')', i);
+ String cstr = layout.substring(beg, i++);
+ int cstrlen = cstr.length();
+ if (layout.charAt(i++) != '[')
+ { i = -i; break; } // fail
+ // Check for duplication.
+ if (layout.charAt(i) == ']')
+ body = i; // missing body, which is legal here
+ else
+ i = skipBody(layout, body = i);
+ Layout.Element[] cbody
+ = tokenizeLayout(self, curCble,
+ layout.substring(body, i++));
+ if (cstrlen == 0) {
+ Layout.Element ce = self.new Element();
+ ce.body = cbody;
+ ce.kind = EK_CASE;
+ ce.removeBand();
+ cases.add(ce);
+ break; // done with the whole union
+ } else {
+ // Parse a case string.
+ boolean firstCaseNum = true;
+ for (int cp = 0, endp;; cp = endp+1) {
+ // Look for multiple case tags:
+ endp = cstr.indexOf(',', cp);
+ if (endp < 0) endp = cstrlen;
+ String cstr1 = cstr.substring(cp, endp);
+ if (cstr1.length() == 0)
+ cstr1 = "empty"; // will fail parse
+ int value0, value1;
+ // Check for a case range (new in 1.6).
+ int dash = findCaseDash(cstr1, 0);
+ if (dash >= 0) {
+ value0 = parseIntBefore(cstr1, dash);
+ value1 = parseIntAfter(cstr1, dash);
+ if (value0 >= value1)
+ { i = -i; break; } // fail
+ } else {
+ value0 = value1 = Integer.parseInt(cstr1);
+ }
+ // Add a case for each value in value0..value1
+ for (;; value0++) {
+ Layout.Element ce = self.new Element();
+ ce.body = cbody; // all cases share one body
+ ce.kind = EK_CASE;
+ ce.removeBand();
+ if (!firstCaseNum)
+ // "backward case" repeats a body
+ ce.flags |= EF_BACK;
+ firstCaseNum = false;
+ ce.value = value0;
+ cases.add(ce);
+ if (value0 == value1) break;
+ }
+ if (endp == cstrlen) {
+ break; // done with this case
+ }
+ }
+ }
+ }
+ e.body = new Layout.Element[cases.size()];
+ cases.toArray(e.body);
+ e.kind = kind;
+ for (int j = 0; j < e.body.length-1; j++) {
+ Layout.Element ce = e.body[j];
+ if (matchCase(e, ce.value) != ce) {
+ // Duplicate tag.
+ { i = -i; break; } // fail
+ }
+ }
+ break;
+ case '(': // call: '(' '-'? digit+ ')'
+ kind = EK_CALL;
+ e.removeBand();
+ i = layout.indexOf(')', i);
+ String cstr = layout.substring(start+1, i++);
+ int offset = Integer.parseInt(cstr);
+ int target = curCble + offset;
+ if (!(offset+"").equals(cstr) ||
+ self.elems == null ||
+ target < 0 ||
+ target >= self.elems.length)
+ { i = -i; continue; } // fail
+ Layout.Element ce = self.elems[target];
+ assert(ce.kind == EK_CBLE);
+ e.value = target;
+ e.body = new Layout.Element[]{ ce };
+ // Is it a (recursive) backward call?
+ if (offset <= 0) {
+ // Yes. Mark both caller and callee backward.
+ e.flags |= EF_BACK;
+ ce.flags |= EF_BACK;
+ }
+ break;
+ case 'K': // reference_type: constant_ref
+ kind = EK_REF;
+ switch (layout.charAt(i++)) {
+ case 'I': e.refKind = CONSTANT_Integer; break;
+ case 'J': e.refKind = CONSTANT_Long; break;
+ case 'F': e.refKind = CONSTANT_Float; break;
+ case 'D': e.refKind = CONSTANT_Double; break;
+ case 'S': e.refKind = CONSTANT_String; break;
+ case 'Q': e.refKind = CONSTANT_FieldSpecific; break;
+
+ // new in 1.7:
+ case 'M': e.refKind = CONSTANT_MethodHandle; break;
+ case 'T': e.refKind = CONSTANT_MethodType; break;
+ case 'L': e.refKind = CONSTANT_LoadableValue; break;
+ default: { i = -i; continue; } // fail
+ }
+ break;
+ case 'R': // schema_ref
+ kind = EK_REF;
+ switch (layout.charAt(i++)) {
+ case 'C': e.refKind = CONSTANT_Class; break;
+ case 'S': e.refKind = CONSTANT_Signature; break;
+ case 'D': e.refKind = CONSTANT_NameandType; break;
+ case 'F': e.refKind = CONSTANT_Fieldref; break;
+ case 'M': e.refKind = CONSTANT_Methodref; break;
+ case 'I': e.refKind = CONSTANT_InterfaceMethodref; break;
+
+ case 'U': e.refKind = CONSTANT_Utf8; break; //utf8_ref
+ case 'Q': e.refKind = CONSTANT_All; break; //untyped_ref
+
+ // new in 1.7:
+ case 'Y': e.refKind = CONSTANT_InvokeDynamic; break;
+ case 'B': e.refKind = CONSTANT_BootstrapMethod; break;
+ case 'N': e.refKind = CONSTANT_AnyMember; break;
+
+ default: { i = -i; continue; } // fail
+ }
+ break;
+ default: { i = -i; continue; } // fail
+ }
+
+ // further parsing of refs
+ if (kind == EK_REF) {
+ // reference: reference_type -><- ( 'N' )? tokenizeUInt
+ if (layout.charAt(i++) == 'N') {
+ e.flags |= EF_NULL;
+ i++; // move forward
+ }
+ --i; // reparse
+ i = tokenizeUInt(e, layout, i);
+ self.hasRefs = true;
+ }
+
+ prevBCI = (kind == EK_BCI);
+
+ // store the new element
+ e.kind = kind;
+ e.layout = layout.substring(start, i);
+ col.add(e);
+ }
+ }
+ static //private
+ String[] splitBodies(String layout) {
+ List<String> bodies = new ArrayList<>();
+ // Parse several independent layout bodies: "[foo][bar]...[baz]"
+ for (int i = 0; i < layout.length(); i++) {
+ if (layout.charAt(i++) != '[')
+ layout.charAt(-i); // throw error
+ int body;
+ i = skipBody(layout, body = i);
+ bodies.add(layout.substring(body, i));
+ }
+ String[] res = new String[bodies.size()];
+ bodies.toArray(res);
+ return res;
+ }
+ private static
+ int skipBody(String layout, int i) {
+ assert(layout.charAt(i-1) == '[');
+ if (layout.charAt(i) == ']')
+ // No empty bodies, please.
+ return -i;
+ // skip balanced [...[...]...]
+ for (int depth = 1; depth > 0; ) {
+ switch (layout.charAt(i++)) {
+ case '[': depth++; break;
+ case ']': depth--; break;
+ }
+ }
+ --i; // get before bracket
+ assert(layout.charAt(i) == ']');
+ return i; // return closing bracket
+ }
+ private static
+ int tokenizeUInt(Layout.Element e, String layout, int i) {
+ switch (layout.charAt(i++)) {
+ case 'V': e.len = 0; break;
+ case 'B': e.len = 1; break;
+ case 'H': e.len = 2; break;
+ case 'I': e.len = 4; break;
+ default: return -i;
+ }
+ return i;
+ }
+ private static
+ int tokenizeSInt(Layout.Element e, String layout, int i) {
+ if (layout.charAt(i) == 'S') {
+ e.flags |= EF_SIGN;
+ ++i;
+ }
+ return tokenizeUInt(e, layout, i);
+ }
+
+ private static
+ boolean isDigit(char c) {
+ return c >= '0' && c <= '9';
+ }
+
+ /** Find an occurrence of hyphen '-' between two numerals. */
+ static //private
+ int findCaseDash(String layout, int fromIndex) {
+ if (fromIndex <= 0) fromIndex = 1; // minimum dash pos
+ int lastDash = layout.length() - 2; // maximum dash pos
+ for (;;) {
+ int dash = layout.indexOf('-', fromIndex);
+ if (dash < 0 || dash > lastDash) return -1;
+ if (isDigit(layout.charAt(dash-1))) {
+ char afterDash = layout.charAt(dash+1);
+ if (afterDash == '-' && dash+2 < layout.length())
+ afterDash = layout.charAt(dash+2);
+ if (isDigit(afterDash)) {
+ // matched /[0-9]--?[0-9]/; return position of dash
+ return dash;
+ }
+ }
+ fromIndex = dash+1;
+ }
+ }
+ static
+ int parseIntBefore(String layout, int dash) {
+ int end = dash;
+ int beg = end;
+ while (beg > 0 && isDigit(layout.charAt(beg-1))) {
+ --beg;
+ }
+ if (beg == end) return Integer.parseInt("empty");
+ // skip backward over a sign
+ if (beg >= 1 && layout.charAt(beg-1) == '-') --beg;
+ assert(beg == 0 || !isDigit(layout.charAt(beg-1)));
+ return Integer.parseInt(layout.substring(beg, end));
+ }
+ static
+ int parseIntAfter(String layout, int dash) {
+ int beg = dash+1;
+ int end = beg;
+ int limit = layout.length();
+ if (end < limit && layout.charAt(end) == '-') ++end;
+ while (end < limit && isDigit(layout.charAt(end))) {
+ ++end;
+ }
+ if (beg == end) return Integer.parseInt("empty");
+ return Integer.parseInt(layout.substring(beg, end));
+ }
+ /** For compatibility with 1.5 pack, expand 1-5 into 1,2,3,4,5. */
+ static
+ String expandCaseDashNotation(String layout) {
+ int dash = findCaseDash(layout, 0);
+ if (dash < 0) return layout; // no dashes (the common case)
+ StringBuilder result = new StringBuilder(layout.length() * 3);
+ int sofar = 0; // how far have we processed the layout?
+ for (;;) {
+ // for each dash, collect everything up to the dash
+ result.append(layout, sofar, dash);
+ sofar = dash+1; // skip the dash
+ // then collect intermediate values
+ int value0 = parseIntBefore(layout, dash);
+ int value1 = parseIntAfter(layout, dash);
+ assert(value0 < value1);
+ result.append(","); // close off value0 numeral
+ for (int i = value0+1; i < value1; i++) {
+ result.append(i);
+ result.append(","); // close off i numeral
+ }
+ dash = findCaseDash(layout, sofar);
+ if (dash < 0) break;
+ }
+ result.append(layout, sofar, layout.length()); // collect the rest
+ return result.toString();
+ }
+ static {
+ assert(expandCaseDashNotation("1-5").equals("1,2,3,4,5"));
+ assert(expandCaseDashNotation("-2--1").equals("-2,-1"));
+ assert(expandCaseDashNotation("-2-1").equals("-2,-1,0,1"));
+ assert(expandCaseDashNotation("-1-0").equals("-1,0"));
+ }
+
+ // Parse attribute bytes, putting values into bands. Returns new pos.
+ // Used when reading a class file (local refs resolved with local cpMap).
+ // Also used for ad hoc scanning.
+ static
+ int parseUsing(Layout.Element[] elems, Holder holder,
+ byte[] bytes, int pos, int len, ValueStream out) {
+ int prevBCI = 0;
+ int prevRBCI = 0;
+ int end = pos + len;
+ int[] buf = { 0 }; // for calls to parseInt, holds 2nd result
+ for (int i = 0; i < elems.length; i++) {
+ Layout.Element e = elems[i];
+ int bandIndex = e.bandIndex;
+ int value;
+ int BCI, RBCI;
+ switch (e.kind) {
+ case EK_INT:
+ pos = parseInt(e, bytes, pos, buf);
+ value = buf[0];
+ out.putInt(bandIndex, value);
+ break;
+ case EK_BCI: // PH, POH
+ pos = parseInt(e, bytes, pos, buf);
+ BCI = buf[0];
+ RBCI = out.encodeBCI(BCI);
+ if (!e.flagTest(EF_DELTA)) {
+ // PH: transmit R(bci), store bci
+ value = RBCI;
+ } else {
+ // POH: transmit D(R(bci)), store bci
+ value = RBCI - prevRBCI;
+ }
+ prevBCI = BCI;
+ prevRBCI = RBCI;
+ out.putInt(bandIndex, value);
+ break;
+ case EK_BCO: // OH
+ assert(e.flagTest(EF_DELTA));
+ // OH: transmit D(R(bci)), store D(bci)
+ pos = parseInt(e, bytes, pos, buf);
+ BCI = prevBCI + buf[0];
+ RBCI = out.encodeBCI(BCI);
+ value = RBCI - prevRBCI;
+ prevBCI = BCI;
+ prevRBCI = RBCI;
+ out.putInt(bandIndex, value);
+ break;
+ case EK_FLAG:
+ pos = parseInt(e, bytes, pos, buf);
+ value = buf[0];
+ out.putInt(bandIndex, value);
+ break;
+ case EK_REPL:
+ pos = parseInt(e, bytes, pos, buf);
+ value = buf[0];
+ out.putInt(bandIndex, value);
+ for (int j = 0; j < value; j++) {
+ pos = parseUsing(e.body, holder, bytes, pos, end-pos, out);
+ }
+ break; // already transmitted the scalar value
+ case EK_UN:
+ pos = parseInt(e, bytes, pos, buf);
+ value = buf[0];
+ out.putInt(bandIndex, value);
+ Layout.Element ce = matchCase(e, value);
+ pos = parseUsing(ce.body, holder, bytes, pos, end-pos, out);
+
+ break; // already transmitted the scalar value
+ case EK_CALL:
+ // Adjust band offset if it is a backward call.
+ assert(e.body.length == 1);
+ assert(e.body[0].kind == EK_CBLE);
+ if (e.flagTest(EF_BACK))
+ out.noteBackCall(e.value);
+ pos = parseUsing(e.body[0].body, holder, bytes, pos, end-pos, out);
+ break; // no additional scalar value to transmit
+ case EK_REF:
+ pos = parseInt(e, bytes, pos, buf);
+ int localRef = buf[0];
+ Entry globalRef;
+ if (localRef == 0) {
+ globalRef = null; // N.B. global null reference is -1
+ } else {
+ Entry[] cpMap = holder.getCPMap();
+ globalRef = (localRef >= 0 && localRef < cpMap.length
+ ? cpMap[localRef]
+ : null);
+ byte tag = e.refKind;
+ if (globalRef != null && tag == CONSTANT_Signature
+ && globalRef.getTag() == CONSTANT_Utf8) {
+ // Cf. ClassReader.readSignatureRef.
+ String typeName = globalRef.stringValue();
+ globalRef = ConstantPool.getSignatureEntry(typeName);
+ }
+ String got = (globalRef == null
+ ? "invalid CP index"
+ : "type=" + ConstantPool.tagName(globalRef.tag));
+ if (globalRef == null || !globalRef.tagMatches(tag)) {
+ throw new IllegalArgumentException(
+ "Bad constant, expected type=" +
+ ConstantPool.tagName(tag) + " got " + got);
+ }
+ }
+ out.putRef(bandIndex, globalRef);
+ break;
+ default: assert(false);
+ }
+ }
+ return pos;
+ }
+
+ static
+ Layout.Element matchCase(Layout.Element e, int value) {
+ assert(e.kind == EK_UN);
+ int lastj = e.body.length-1;
+ for (int j = 0; j < lastj; j++) {
+ Layout.Element ce = e.body[j];
+ assert(ce.kind == EK_CASE);
+ if (value == ce.value)
+ return ce;
+ }
+ return e.body[lastj];
+ }
+
+ private static
+ int parseInt(Layout.Element e, byte[] bytes, int pos, int[] buf) {
+ int value = 0;
+ int loBits = e.len * 8;
+ // Read in big-endian order:
+ for (int bitPos = loBits; (bitPos -= 8) >= 0; ) {
+ value += (bytes[pos++] & 0xFF) << bitPos;
+ }
+ if (loBits < 32 && e.flagTest(EF_SIGN)) {
+ // sign-extend subword value
+ int hiBits = 32 - loBits;
+ value = (value << hiBits) >> hiBits;
+ }
+ buf[0] = value;
+ return pos;
+ }
+
+ // Format attribute bytes, drawing values from bands.
+ // Used when emptying attribute bands into a package model.
+ // (At that point CP refs. are not yet assigned indexes.)
+ static
+ void unparseUsing(Layout.Element[] elems, Object[] fixups,
+ ValueStream in, ByteArrayOutputStream out) {
+ int prevBCI = 0;
+ int prevRBCI = 0;
+ for (int i = 0; i < elems.length; i++) {
+ Layout.Element e = elems[i];
+ int bandIndex = e.bandIndex;
+ int value;
+ int BCI, RBCI; // "RBCI" is R(BCI), BCI's coded representation
+ switch (e.kind) {
+ case EK_INT:
+ value = in.getInt(bandIndex);
+ unparseInt(e, value, out);
+ break;
+ case EK_BCI: // PH, POH
+ value = in.getInt(bandIndex);
+ if (!e.flagTest(EF_DELTA)) {
+ // PH: transmit R(bci), store bci
+ RBCI = value;
+ } else {
+ // POH: transmit D(R(bci)), store bci
+ RBCI = prevRBCI + value;
+ }
+ assert(prevBCI == in.decodeBCI(prevRBCI));
+ BCI = in.decodeBCI(RBCI);
+ unparseInt(e, BCI, out);
+ prevBCI = BCI;
+ prevRBCI = RBCI;
+ break;
+ case EK_BCO: // OH
+ value = in.getInt(bandIndex);
+ assert(e.flagTest(EF_DELTA));
+ // OH: transmit D(R(bci)), store D(bci)
+ assert(prevBCI == in.decodeBCI(prevRBCI));
+ RBCI = prevRBCI + value;
+ BCI = in.decodeBCI(RBCI);
+ unparseInt(e, BCI - prevBCI, out);
+ prevBCI = BCI;
+ prevRBCI = RBCI;
+ break;
+ case EK_FLAG:
+ value = in.getInt(bandIndex);
+ unparseInt(e, value, out);
+ break;
+ case EK_REPL:
+ value = in.getInt(bandIndex);
+ unparseInt(e, value, out);
+ for (int j = 0; j < value; j++) {
+ unparseUsing(e.body, fixups, in, out);
+ }
+ break;
+ case EK_UN:
+ value = in.getInt(bandIndex);
+ unparseInt(e, value, out);
+ Layout.Element ce = matchCase(e, value);
+ unparseUsing(ce.body, fixups, in, out);
+ break;
+ case EK_CALL:
+ assert(e.body.length == 1);
+ assert(e.body[0].kind == EK_CBLE);
+ unparseUsing(e.body[0].body, fixups, in, out);
+ break;
+ case EK_REF:
+ Entry globalRef = in.getRef(bandIndex);
+ int localRef;
+ if (globalRef != null) {
+ // It's a one-element array, really an lvalue.
+ fixups[0] = Fixups.addRefWithLoc(fixups[0], out.size(), globalRef);
+ localRef = 0; // placeholder for fixups
+ } else {
+ localRef = 0; // fixed null value
+ }
+ unparseInt(e, localRef, out);
+ break;
+ default: assert(false); continue;
+ }
+ }
+ }
+
+ private static
+ void unparseInt(Layout.Element e, int value, ByteArrayOutputStream out) {
+ int loBits = e.len * 8;
+ if (loBits == 0) {
+ // It is not stored at all ('V' layout).
+ return;
+ }
+ if (loBits < 32) {
+ int hiBits = 32 - loBits;
+ int codedValue;
+ if (e.flagTest(EF_SIGN))
+ codedValue = (value << hiBits) >> hiBits;
+ else
+ codedValue = (value << hiBits) >>> hiBits;
+ if (codedValue != value)
+ throw new InternalError("cannot code in "+e.len+" bytes: "+value);
+ }
+ // Write in big-endian order:
+ for (int bitPos = loBits; (bitPos -= 8) >= 0; ) {
+ out.write((byte)(value >>> bitPos));
+ }
+ }
+
+/*
+ /// Testing.
+ public static void main(String av[]) {
+ int maxVal = 12;
+ int iters = 0;
+ boolean verbose;
+ int ap = 0;
+ while (ap < av.length) {
+ if (!av[ap].startsWith("-")) break;
+ if (av[ap].startsWith("-m"))
+ maxVal = Integer.parseInt(av[ap].substring(2));
+ else if (av[ap].startsWith("-i"))
+ iters = Integer.parseInt(av[ap].substring(2));
+ else
+ throw new RuntimeException("Bad option: "+av[ap]);
+ ap++;
+ }
+ verbose = (iters == 0);
+ if (iters <= 0) iters = 1;
+ if (ap == av.length) {
+ av = new String[] {
+ "HH", // ClassFile.version
+ "RUH", // SourceFile
+ "RCHRDNH", // EnclosingMethod
+ "KQH", // ConstantValue
+ "NH[RCH]", // Exceptions
+ "NH[PHH]", // LineNumberTable
+ "NH[PHOHRUHRSHH]", // LocalVariableTable
+ "NH[PHPOHIIH]", // CharacterRangeTable
+ "NH[PHHII]", // CoverageTable
+ "NH[RCHRCNHRUNHFH]", // InnerClasses
+ "NH[RMHNH[KLH]]", // BootstrapMethods
+ "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]", // Code
+ "=AnnotationDefault",
+ // Like metadata, but with a compact tag set:
+ "[NH[(1)]]"
+ +"[NH[(1)]]"
+ +"[RSHNH[RUH(1)]]"
+ +"[TB(0,1,3)[KIH](2)[KDH](5)[KFH](4)[KJH](7)[RSH](8)[RSHRUH](9)[RUH](10)[(-1)](6)[NH[(0)]]()[]]",
+ ""
+ };
+ ap = 0;
+ }
+ Utils.currentInstance.set(new PackerImpl());
+ final int[][] counts = new int[2][3]; // int bci ref
+ final Entry[] cpMap = new Entry[maxVal+1];
+ for (int i = 0; i < cpMap.length; i++) {
+ if (i == 0) continue; // 0 => null
+ cpMap[i] = ConstantPool.getLiteralEntry(new Integer(i));
+ }
+ Package.Class cls = new Package().new Class("");
+ cls.cpMap = cpMap;
+ class TestValueStream extends ValueStream {
+ java.util.Random rand = new java.util.Random(0);
+ ArrayList history = new ArrayList();
+ int ckidx = 0;
+ int maxVal;
+ boolean verbose;
+ void reset() { history.clear(); ckidx = 0; }
+ public int getInt(int bandIndex) {
+ counts[0][0]++;
+ int value = rand.nextInt(maxVal+1);
+ history.add(new Integer(bandIndex));
+ history.add(new Integer(value));
+ return value;
+ }
+ public void putInt(int bandIndex, int token) {
+ counts[1][0]++;
+ if (verbose)
+ System.out.print(" "+bandIndex+":"+token);
+ // Make sure this put parallels a previous get:
+ int check0 = ((Integer)history.get(ckidx+0)).intValue();
+ int check1 = ((Integer)history.get(ckidx+1)).intValue();
+ if (check0 != bandIndex || check1 != token) {
+ if (!verbose)
+ System.out.println(history.subList(0, ckidx));
+ System.out.println(" *** Should be "+check0+":"+check1);
+ throw new RuntimeException("Failed test!");
+ }
+ ckidx += 2;
+ }
+ public Entry getRef(int bandIndex) {
+ counts[0][2]++;
+ int value = getInt(bandIndex);
+ if (value < 0 || value > maxVal) {
+ System.out.println(" *** Unexpected ref code "+value);
+ return ConstantPool.getLiteralEntry(new Integer(value));
+ }
+ return cpMap[value];
+ }
+ public void putRef(int bandIndex, Entry ref) {
+ counts[1][2]++;
+ if (ref == null) {
+ putInt(bandIndex, 0);
+ return;
+ }
+ Number refValue = null;
+ if (ref instanceof ConstantPool.NumberEntry)
+ refValue = ((ConstantPool.NumberEntry)ref).numberValue();
+ int value;
+ if (!(refValue instanceof Integer)) {
+ System.out.println(" *** Unexpected ref "+ref);
+ value = -1;
+ } else {
+ value = ((Integer)refValue).intValue();
+ }
+ putInt(bandIndex, value);
+ }
+ public int encodeBCI(int bci) {
+ counts[1][1]++;
+ // move LSB to MSB of low byte
+ int code = (bci >> 8) << 8; // keep high bits
+ code += (bci & 0xFE) >> 1;
+ code += (bci & 0x01) << 7;
+ return code ^ (8<<8); // mark it clearly as coded
+ }
+ public int decodeBCI(int bciCode) {
+ counts[0][1]++;
+ bciCode ^= (8<<8); // remove extra mark
+ int bci = (bciCode >> 8) << 8; // keep high bits
+ bci += (bciCode & 0x7F) << 1;
+ bci += (bciCode & 0x80) >> 7;
+ return bci;
+ }
+ }
+ TestValueStream tts = new TestValueStream();
+ tts.maxVal = maxVal;
+ tts.verbose = verbose;
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ for (int i = 0; i < (1 << 30); i = (i + 1) * 5) {
+ int ei = tts.encodeBCI(i);
+ int di = tts.decodeBCI(ei);
+ if (di != i) System.out.println("i="+Integer.toHexString(i)+
+ " ei="+Integer.toHexString(ei)+
+ " di="+Integer.toHexString(di));
+ }
+ while (iters-- > 0) {
+ for (int i = ap; i < av.length; i++) {
+ String layout = av[i];
+ if (layout.startsWith("=")) {
+ String name = layout.substring(1);
+ for (Attribute a : standardDefs.values()) {
+ if (a.name().equals(name)) {
+ layout = a.layout().layout();
+ break;
+ }
+ }
+ if (layout.startsWith("=")) {
+ System.out.println("Could not find "+name+" in "+standardDefs.values());
+ }
+ }
+ Layout self = new Layout(0, "Foo", layout);
+ if (verbose) {
+ System.out.print("/"+layout+"/ => ");
+ System.out.println(Arrays.asList(self.elems));
+ }
+ buf.reset();
+ tts.reset();
+ Object fixups = self.unparse(tts, buf);
+ byte[] bytes = buf.toByteArray();
+ // Attach the references to the byte array.
+ Fixups.setBytes(fixups, bytes);
+ // Patch the references to their frozen values.
+ Fixups.finishRefs(fixups, bytes, new Index("test", cpMap));
+ if (verbose) {
+ System.out.print(" bytes: {");
+ for (int j = 0; j < bytes.length; j++) {
+ System.out.print(" "+bytes[j]);
+ }
+ System.out.println("}");
+ }
+ if (verbose) {
+ System.out.print(" parse: {");
+ }
+ self.parse(cls, bytes, 0, bytes.length, tts);
+ if (verbose) {
+ System.out.println("}");
+ }
+ }
+ }
+ for (int j = 0; j <= 1; j++) {
+ System.out.print("values "+(j==0?"read":"written")+": {");
+ for (int k = 0; k < counts[j].length; k++) {
+ System.out.print(" "+counts[j][k]);
+ }
+ System.out.println(" }");
+ }
+ }
+//*/
+}