> 1;
if (db.meetTraceTargetClassMethod(classAndMethod)) {
- Object fv = value;
db.info(buildMessage(s, "#%d pushVObject %s, tos=%d midx=%d sp=%d idx=%d v=%s", s.verifyInfo.vid, classAndMethod, s.methodTOS, midx, s.curMethodSP, idx, takeValueWithoutRealizeIt(value)));
}
s.checkClassAndMethod(midx, "pushV", classAndMethod);
@@ -395,12 +405,12 @@ public final int getIntV(int idx, String classAndMethod) {
}else if (ort instanceof Character) {
prt = ((Character)ort).charValue();
}else {
- printErrorMessage(this.db, this,"#%d getIntV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, ort);
+ printErrorMessage(db, this,"#%d getIntV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, ort);
return rt;
}
if (rt != prt) {
- printErrorMessage(this.db, this ,"#%d getIntV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, prt);
+ printErrorMessage(db, this ,"#%d getIntV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, prt);
}
return rt;
}
@@ -417,11 +427,11 @@ public final float getFloatV(int idx, String classAndMethod) {
if (ort instanceof Float) {
prt = (Float)ort;
}else {
- printErrorMessage(this.db, this,"#%d getFloatV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx,curMethodSP, idx, rt, ort);
+ printErrorMessage(db, this,"#%d getFloatV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx,curMethodSP, idx, rt, ort);
return rt;
}
if (rt != prt) {
- printErrorMessage(this.db, this ,"#%d getFloatV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt);
+ printErrorMessage(db, this ,"#%d getFloatV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt);
}
return rt;
}
@@ -438,11 +448,11 @@ public final long getLongV(int idx, String classAndMethod) {
if (ort instanceof Long) {
prt = (Long)ort;
}else {
- printErrorMessage(this.db, this ,"#%d getLongV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, ort);
+ printErrorMessage(db, this ,"#%d getLongV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, rt, ort);
return rt;
}
if (rt != prt) {
- printErrorMessage(this.db, this ,"#%d getLongV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt);
+ printErrorMessage(db, this ,"#%d getLongV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt);
}
return rt;
}
@@ -459,11 +469,11 @@ public final double getDoubleV(int idx, String classAndMethod) {
if (ort instanceof Double) {
prt = (Double)ort;
}else {
- printErrorMessage(this.db, this ,"#%d getDoubleV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS,midx, curMethodSP, idx, rt, ort);
+ printErrorMessage(db, this ,"#%d getDoubleV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS,midx, curMethodSP, idx, rt, ort);
return rt;
}
if (rt != prt) {
- printErrorMessage(this.db, this ,"#%d getDoubleV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt);
+ printErrorMessage(db, this ,"#%d getDoubleV %s tos=%d midx=%d, sp=%d idx=%d %s != %s", verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx,rt, prt);
}
return rt;
}
@@ -476,7 +486,7 @@ public final Object getObjectV(int idx, String classAndMethod) {
}
Object prt = verifyInfo.methodIdxInfos[midx].vars[idx].value;
if (rt != prt) {
- printErrorMessage(this.db, this ,"#%d getObjectV %s tos=%d midx=%d, sp=%d idx=%d %s != %s" , verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, takeValueWithoutRealizeIt(rt), takeValueWithoutRealizeIt(prt));
+ printErrorMessage(db, this ,"#%d getObjectV %s tos=%d midx=%d, sp=%d idx=%d %s != %s" , verifyInfo.vid, classAndMethod, methodTOS, midx, curMethodSP, idx, takeValueWithoutRealizeIt(rt), takeValueWithoutRealizeIt(prt));
}
return rt;
}
@@ -538,12 +548,18 @@ public boolean allObjsAreNull() {
}
protected void release() {
+
+ if (Coroutine.isUseNative()) {
+ return;
+ }
+
methodTOS = -1;
if (verifyInfo != null) {
verifyInfo.tracerStacks.clear();
fillNull(verifyInfo.methodIdxInfos, 0, verifyInfo.methodIdxInfos.length);
}
- fillNull(dataObject, 0, dataObject.length);
+
+ fillNull(dataObject, 0, dataObject.length);
}
public static void fillNull(Object[] array, int s, int len) {
diff --git a/src/java/nginx/clojure/SuspendExecution.java b/src/java/nginx/clojure/SuspendExecution.java
index 3199b622..63929e3c 100644
--- a/src/java/nginx/clojure/SuspendExecution.java
+++ b/src/java/nginx/clojure/SuspendExecution.java
@@ -45,7 +45,9 @@
*/
public final class SuspendExecution extends RuntimeException {
- static final SuspendExecution instance = new SuspendExecution();
+ private static final long serialVersionUID = 1L;
+
+ static final SuspendExecution instance = new SuspendExecution();
private SuspendExecution() {
}
diff --git a/src/java/nginx/clojure/SuspendableConstructorUtilStack.java b/src/java/nginx/clojure/SuspendableConstructorUtilStack.java
index 1906435a..de067f23 100644
--- a/src/java/nginx/clojure/SuspendableConstructorUtilStack.java
+++ b/src/java/nginx/clojure/SuspendableConstructorUtilStack.java
@@ -19,11 +19,11 @@ public final class SuspendableConstructorUtilStack implements Serializable {
private long[] dataLong;
private Object[] dataObject;
+ private int[] methodSlotNumbers;
private int sp = -1;
private int top = 0;
private int refs = 0;
- private int desp = 0;
SuspendableConstructorUtilStack(int stackSize) {
if(stackSize <= 0) {
@@ -31,6 +31,7 @@ public final class SuspendableConstructorUtilStack implements Serializable {
}
this.dataLong = new long[stackSize];
this.dataObject = new Object[stackSize];
+ this.methodSlotNumbers = new int[2];
}
public static SuspendableConstructorUtilStack getStack() {
@@ -63,55 +64,71 @@ public final void incRefsAndReserveSpace(int numSlots) {
if(dataTOS > dataObject.length) {
growDataStack(dataTOS);
}
+
+ if (refs > methodSlotNumbers.length) {
+ methodSlotNumbers = Util.copyOf(methodSlotNumbers, methodSlotNumbers.length + 2);
+ }
+
+ methodSlotNumbers[refs -1] = numSlots;
}
- public static void push(int value, SuspendableConstructorUtilStack s, int idx) {
- s.dataLong[s.sp + idx] = value;
- }
- public static void push(float value, SuspendableConstructorUtilStack s, int idx) {
- s.dataLong[s.sp + idx] = Float.floatToRawIntBits(value);
- }
- public static void push(long value, SuspendableConstructorUtilStack s, int idx) {
- s.dataLong[s.sp + idx] = value;
- }
- public static void push(double value, SuspendableConstructorUtilStack s, int idx) {
- s.dataLong[s.sp + idx] = Double.doubleToRawLongBits(value);
- }
- public static void push(Object value, SuspendableConstructorUtilStack s, int idx) {
- s.dataObject[s.sp + idx] = value;
- }
+ public static void push(int value, SuspendableConstructorUtilStack s, int idx) {
+ s.dataLong[s.sp + idx] = value;
+ }
- public final int getInt(int idx) {
- return (int)dataLong[desp + idx];
- }
- public final float getFloat(int idx) {
- return Float.intBitsToFloat((int)dataLong[desp + idx]);
- }
- public final long getLong(int idx) {
- return dataLong[desp + idx];
- }
- public final double getDouble(int idx) {
- return Double.longBitsToDouble(dataLong[desp + idx]);
- }
- public final Object getObject(int idx) {
- return dataObject[desp + idx];
- }
+ public static void push(float value, SuspendableConstructorUtilStack s, int idx) {
+ s.dataLong[s.sp + idx] = Float.floatToRawIntBits(value);
+ }
+
+ public static void push(long value, SuspendableConstructorUtilStack s, int idx) {
+ s.dataLong[s.sp + idx] = value;
+ }
+
+ public static void push(double value, SuspendableConstructorUtilStack s, int idx) {
+ s.dataLong[s.sp + idx] = Double.doubleToRawLongBits(value);
+ }
+
+ public static void push(Object value, SuspendableConstructorUtilStack s, int idx) {
+ s.dataObject[s.sp + idx] = value;
+ }
+
+ public final int getInt(int idx) {
+ return (int) dataLong[sp + idx];
+ }
+
+ public final float getFloat(int idx) {
+ return Float.intBitsToFloat((int) dataLong[sp + idx]);
+ }
+
+ public final long getLong(int idx) {
+ return dataLong[sp + idx];
+ }
+
+ public final double getDouble(int idx) {
+ return Double.longBitsToDouble(dataLong[sp + idx]);
+ }
+
+ public final Object getObject(int idx) {
+ return dataObject[sp + idx];
+ }
public final void release(int c) {
- desp += c;
if (--refs == 0) {
Stack.fillNull(dataObject, 0, top);
sp = -1;
top = 0;
- desp = 0;
+ } else {
+ sp -= methodSlotNumbers[refs - 1];
+ top -= c;
+ Stack.fillNull(dataObject, top, c);
}
}
private void growDataStack(int required) {
int newSize = dataObject.length;
- do {
- newSize *= 2;
- } while(newSize < required);
+ do {
+ newSize *= 2;
+ } while (newSize < required);
dataLong = Util.copyOf(dataLong, newSize);
dataObject = Util.copyOf(dataObject, newSize);
diff --git a/src/java/nginx/clojure/TableEltHeaderHolder.java b/src/java/nginx/clojure/TableEltHeaderHolder.java
index c358fd00..38e20b2d 100644
--- a/src/java/nginx/clojure/TableEltHeaderHolder.java
+++ b/src/java/nginx/clojure/TableEltHeaderHolder.java
@@ -9,6 +9,9 @@
import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_HASH_OFFSET;
import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_KEY_OFFSET;
import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET;
+import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET;
+import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_STR_LEN_OFFSET;
+import static nginx.clojure.MiniConstants.NGINX_VER;
import static nginx.clojure.NginxClojureRT.UNSAFE;
import static nginx.clojure.NginxClojureRT.fetchNGXString;
import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_shadow_copy_ngx_str;
@@ -42,6 +45,11 @@ public void push(long h, long pool, Object v) {
ngx_http_clojure_mem_shadow_copy_ngx_str(HEADERS_NAMES.get(name), p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET);
}
pushNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, pickString(v), DEFAULT_ENCODING, pool);
+
+ if (NGINX_VER >= 1023000) {
+ UNSAFE.putAddress(p + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET, 0);
+ }
+
UNSAFE.putAddress(h + offset, p);
}
@@ -51,7 +59,8 @@ public void clear(long h) {
long p = UNSAFE.getAddress(h + offset);
if (p != 0) {
NginxClojureRT.pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 0);
- UNSAFE.putAddress(h+offset, 0);
+ NginxClojureRT.pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET + NGX_HTTP_CLOJURE_STR_LEN_OFFSET, 0);
+ UNSAFE.putAddress(h + offset, 0);
}
}
@Override
@@ -60,7 +69,7 @@ public Object fetch(long h) {
if (p == 0) {
return null;
}
- return fetchNGXString(p +NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET , DEFAULT_ENCODING);
+ return fetchNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET , DEFAULT_ENCODING);
}
@Override
diff --git a/src/java/nginx/clojure/UnknownHeaderHolder.java b/src/java/nginx/clojure/UnknownHeaderHolder.java
index b8816b30..a1995b17 100644
--- a/src/java/nginx/clojure/UnknownHeaderHolder.java
+++ b/src/java/nginx/clojure/UnknownHeaderHolder.java
@@ -11,7 +11,7 @@
import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_HASH_OFFSET;
import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_KEY_OFFSET;
import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET;
-import static nginx.clojure.NginxClojureRT.UNSAFE;
+import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_STR_LEN_OFFSET;
import static nginx.clojure.NginxClojureRT.fetchNGXString;
import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_get_header;
import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_shadow_copy_ngx_str;
@@ -48,6 +48,7 @@ public long knownOffset() {
return -1;
}
+ @SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public void push(long h, long pool, Object v) {
@@ -85,7 +86,7 @@ public void push(long h, long pool, Object v) {
ngx_http_clojure_mem_shadow_copy_ngx_str(lpname, p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET);
}else {
pushNGXString(p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET, name, DEFAULT_ENCODING, pool);
- lpname = UNSAFE.getAddress(p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET);
+ lpname = p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET;
}
pushNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, val.toString(), DEFAULT_ENCODING, pool);
}
@@ -102,10 +103,12 @@ public void clear(long h) {
int c = (int)ngx_http_clojure_mem_get_header(h, array, BYTE_ARRAY_OFFSET , nameLen, valuesOffset, kbb.capacity());
kbb.clear();
kbb.position(NGINX_CLOJURE_CORE_CLIENT_HEADER_MAX_LINE_SIZE);
- LongBuffer lbb =kbb.order(ByteOrder.nativeOrder()).asLongBuffer();
+ LongBuffer lbb = kbb.order(ByteOrder.nativeOrder()).asLongBuffer();
for (; c > 0; c--) {
- pushNGXInt(lbb.get() + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 0);
+ long p = lbb.get();
+ pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 0);
+ pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET + NGX_HTTP_CLOJURE_STR_LEN_OFFSET, 0);
}
}
diff --git a/src/java/nginx/clojure/anno/Suspendable.java b/src/java/nginx/clojure/anno/Suspendable.java
index 2caa4ead..ad84181c 100644
--- a/src/java/nginx/clojure/anno/Suspendable.java
+++ b/src/java/nginx/clojure/anno/Suspendable.java
@@ -9,7 +9,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-@Target({ElementType.TYPE, ElementType.METHOD})
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface Suspendable {
diff --git a/src/java/nginx/clojure/asm/AnnotationVisitor.java b/src/java/nginx/clojure/asm/AnnotationVisitor.java
index 94b298d1..ae76fb12 100644
--- a/src/java/nginx/clojure/asm/AnnotationVisitor.java
+++ b/src/java/nginx/clojure/asm/AnnotationVisitor.java
@@ -1,169 +1,168 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A visitor to visit a Java annotation. The methods of this class must be
- * called in the following order: ( visit | visitEnum |
- * visitAnnotation | visitArray )* visitEnd.
- *
+ * A visitor to visit a Java annotation. The methods of this class must be called in the following
+ * order: ( {@code visit} | {@code visitEnum} | {@code visitAnnotation} | {@code visitArray} )*
+ * {@code visitEnd}.
+ *
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public abstract class AnnotationVisitor {
- /**
- * The ASM API version implemented by this visitor. The value of this field
- * must be one of {@link Opcodes#ASM4}.
- */
- protected final int api;
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
+ */
+ protected final int api;
- /**
- * The annotation visitor to which this visitor must delegate method calls.
- * May be null.
- */
- protected AnnotationVisitor av;
+ /**
+ * The annotation visitor to which this visitor must delegate method calls. May be {@literal
+ * null}.
+ */
+ protected AnnotationVisitor av;
- /**
- * Constructs a new {@link AnnotationVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- */
- public AnnotationVisitor(final int api) {
- this(api, null);
- }
+ /**
+ * Constructs a new {@link AnnotationVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ */
+ protected AnnotationVisitor(final int api) {
+ this(api, null);
+ }
- /**
- * Constructs a new {@link AnnotationVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- * @param av
- * the annotation visitor to which this visitor must delegate
- * method calls. May be null.
- */
- public AnnotationVisitor(final int api, final AnnotationVisitor av) {
- if (api != Opcodes.ASM4) {
- throw new IllegalArgumentException();
- }
- this.api = api;
- this.av = av;
+ /**
+ * Constructs a new {@link AnnotationVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ * @param annotationVisitor the annotation visitor to which this visitor must delegate method
+ * calls. May be {@literal null}.
+ */
+ protected AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
}
+ this.api = api;
+ this.av = annotationVisitor;
+ }
+
+ /**
+ * The annotation visitor to which this visitor must delegate method calls. May be {@literal
+ * null}.
+ *
+ * @return the annotation visitor to which this visitor must delegate method calls, or {@literal
+ * null}.
+ */
+ public AnnotationVisitor getDelegate() {
+ return av;
+ }
- /**
- * Visits a primitive value of the annotation.
- *
- * @param name
- * the value name.
- * @param value
- * the actual value, whose type must be {@link Byte},
- * {@link Boolean}, {@link Character}, {@link Short},
- * {@link Integer} , {@link Long}, {@link Float}, {@link Double},
- * {@link String} or {@link Type} or OBJECT or ARRAY sort. This
- * value can also be an array of byte, boolean, short, char, int,
- * long, float or double values (this is equivalent to using
- * {@link #visitArray visitArray} and visiting each array element
- * in turn, but is more convenient).
- */
- public void visit(String name, Object value) {
- if (av != null) {
- av.visit(name, value);
- }
+ /**
+ * Visits a primitive value of the annotation.
+ *
+ * @param name the value name.
+ * @param value the actual value, whose type must be {@link Byte}, {@link Boolean}, {@link
+ * Character}, {@link Short}, {@link Integer} , {@link Long}, {@link Float}, {@link Double},
+ * {@link String} or {@link Type} of {@link Type#OBJECT} or {@link Type#ARRAY} sort. This
+ * value can also be an array of byte, boolean, short, char, int, long, float or double values
+ * (this is equivalent to using {@link #visitArray} and visiting each array element in turn,
+ * but is more convenient).
+ */
+ public void visit(final String name, final Object value) {
+ if (av != null) {
+ av.visit(name, value);
}
+ }
- /**
- * Visits an enumeration value of the annotation.
- *
- * @param name
- * the value name.
- * @param desc
- * the class descriptor of the enumeration class.
- * @param value
- * the actual enumeration value.
- */
- public void visitEnum(String name, String desc, String value) {
- if (av != null) {
- av.visitEnum(name, desc, value);
- }
+ /**
+ * Visits an enumeration value of the annotation.
+ *
+ * @param name the value name.
+ * @param descriptor the class descriptor of the enumeration class.
+ * @param value the actual enumeration value.
+ */
+ public void visitEnum(final String name, final String descriptor, final String value) {
+ if (av != null) {
+ av.visitEnum(name, descriptor, value);
}
+ }
- /**
- * Visits a nested annotation value of the annotation.
- *
- * @param name
- * the value name.
- * @param desc
- * the class descriptor of the nested annotation class.
- * @return a visitor to visit the actual nested annotation value, or
- * null if this visitor is not interested in visiting this
- * nested annotation. The nested annotation value must be fully
- * visited before calling other methods on this annotation
- * visitor.
- */
- public AnnotationVisitor visitAnnotation(String name, String desc) {
- if (av != null) {
- return av.visitAnnotation(name, desc);
- }
- return null;
+ /**
+ * Visits a nested annotation value of the annotation.
+ *
+ * @param name the value name.
+ * @param descriptor the class descriptor of the nested annotation class.
+ * @return a visitor to visit the actual nested annotation value, or {@literal null} if this
+ * visitor is not interested in visiting this nested annotation. The nested annotation
+ * value must be fully visited before calling other methods on this annotation visitor.
+ */
+ public AnnotationVisitor visitAnnotation(final String name, final String descriptor) {
+ if (av != null) {
+ return av.visitAnnotation(name, descriptor);
}
+ return null;
+ }
- /**
- * Visits an array value of the annotation. Note that arrays of primitive
- * types (such as byte, boolean, short, char, int, long, float or double)
- * can be passed as value to {@link #visit visit}. This is what
- * {@link ClassReader} does.
- *
- * @param name
- * the value name.
- * @return a visitor to visit the actual array value elements, or
- * null if this visitor is not interested in visiting these
- * values. The 'name' parameters passed to the methods of this
- * visitor are ignored. All the array values must be visited
- * before calling other methods on this annotation visitor.
- */
- public AnnotationVisitor visitArray(String name) {
- if (av != null) {
- return av.visitArray(name);
- }
- return null;
+ /**
+ * Visits an array value of the annotation. Note that arrays of primitive values (such as byte,
+ * boolean, short, char, int, long, float or double) can be passed as value to {@link #visit
+ * visit}. This is what {@link ClassReader} does for non empty arrays of primitive values.
+ *
+ * @param name the value name.
+ * @return a visitor to visit the actual array value elements, or {@literal null} if this visitor
+ * is not interested in visiting these values. The 'name' parameters passed to the methods of
+ * this visitor are ignored. All the array values must be visited before calling other
+ * methods on this annotation visitor.
+ */
+ public AnnotationVisitor visitArray(final String name) {
+ if (av != null) {
+ return av.visitArray(name);
}
+ return null;
+ }
- /**
- * Visits the end of the annotation.
- */
- public void visitEnd() {
- if (av != null) {
- av.visitEnd();
- }
+ /** Visits the end of the annotation. */
+ public void visitEnd() {
+ if (av != null) {
+ av.visitEnd();
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/AnnotationWriter.java b/src/java/nginx/clojure/asm/AnnotationWriter.java
index 67827fa1..d85252bf 100644
--- a/src/java/nginx/clojure/asm/AnnotationWriter.java
+++ b/src/java/nginx/clojure/asm/AnnotationWriter.java
@@ -1,318 +1,553 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * An {@link AnnotationVisitor} that generates annotations in bytecode form.
- *
+ * An {@link AnnotationVisitor} that generates a corresponding 'annotation' or 'type_annotation'
+ * structure, as defined in the Java Virtual Machine Specification (JVMS). AnnotationWriter
+ * instances can be chained in a doubly linked list, from which Runtime[In]Visible[Type]Annotations
+ * attributes can be generated with the {@link #putAnnotations} method. Similarly, arrays of such
+ * lists can be used to generate Runtime[In]VisibleParameterAnnotations attributes.
+ *
+ * @see JVMS
+ * 4.7.16
+ * @see JVMS
+ * 4.7.20
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
final class AnnotationWriter extends AnnotationVisitor {
- /**
- * The class writer to which this annotation must be added.
- */
- private final ClassWriter cw;
+ /** Where the constants used in this AnnotationWriter must be stored. */
+ private final SymbolTable symbolTable;
+
+ /**
+ * Whether values are named or not. AnnotationWriter instances used for annotation default and
+ * annotation arrays use unnamed values (i.e. they generate an 'element_value' structure for each
+ * value, instead of an element_name_index followed by an element_value).
+ */
+ private final boolean useNamedValues;
- /**
- * The number of values in this annotation.
- */
- private int size;
+ /**
+ * The 'annotation' or 'type_annotation' JVMS structure corresponding to the annotation values
+ * visited so far. All the fields of these structures, except the last one - the
+ * element_value_pairs array, must be set before this ByteVector is passed to the constructor
+ * (num_element_value_pairs can be set to 0, it is reset to the correct value in {@link
+ * #visitEnd()}). The element_value_pairs array is filled incrementally in the various visit()
+ * methods.
+ *
+ * Note: as an exception to the above rules, for AnnotationDefault attributes (which contain a
+ * single element_value by definition), this ByteVector is initially empty when passed to the
+ * constructor, and {@link #numElementValuePairsOffset} is set to -1.
+ */
+ private final ByteVector annotation;
- /**
- * true if values are named, false otherwise. Annotation
- * writers used for annotation default and annotation arrays use unnamed
- * values.
- */
- private final boolean named;
+ /**
+ * The offset in {@link #annotation} where {@link #numElementValuePairs} must be stored (or -1 for
+ * the case of AnnotationDefault attributes).
+ */
+ private final int numElementValuePairsOffset;
- /**
- * The annotation values in bytecode form. This byte vector only contains
- * the values themselves, i.e. the number of values must be stored as a
- * unsigned short just before these bytes.
- */
- private final ByteVector bv;
+ /** The number of element value pairs visited so far. */
+ private int numElementValuePairs;
- /**
- * The byte vector to be used to store the number of values of this
- * annotation. See {@link #bv}.
- */
- private final ByteVector parent;
+ /**
+ * The previous AnnotationWriter. This field is used to store the list of annotations of a
+ * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations
+ * (annotation values of annotation type), or for AnnotationDefault attributes.
+ */
+ private final AnnotationWriter previousAnnotation;
- /**
- * Where the number of values of this annotation must be stored in
- * {@link #parent}.
- */
- private final int offset;
+ /**
+ * The next AnnotationWriter. This field is used to store the list of annotations of a
+ * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations
+ * (annotation values of annotation type), or for AnnotationDefault attributes.
+ */
+ private AnnotationWriter nextAnnotation;
- /**
- * Next annotation writer. This field is used to store annotation lists.
- */
- AnnotationWriter next;
+ // -----------------------------------------------------------------------------------------------
+ // Constructors and factories
+ // -----------------------------------------------------------------------------------------------
- /**
- * Previous annotation writer. This field is used to store annotation lists.
- */
- AnnotationWriter prev;
+ /**
+ * Constructs a new {@link AnnotationWriter}.
+ *
+ * @param symbolTable where the constants used in this AnnotationWriter must be stored.
+ * @param useNamedValues whether values are named or not. AnnotationDefault and annotation arrays
+ * use unnamed values.
+ * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to
+ * the visited content must be stored. This ByteVector must already contain all the fields of
+ * the structure except the last one (the element_value_pairs array).
+ * @param previousAnnotation the previously visited annotation of the
+ * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or
+ * {@literal null} in other cases (e.g. nested or array annotations).
+ */
+ AnnotationWriter(
+ final SymbolTable symbolTable,
+ final boolean useNamedValues,
+ final ByteVector annotation,
+ final AnnotationWriter previousAnnotation) {
+ super(/* latest api = */ Opcodes.ASM9);
+ this.symbolTable = symbolTable;
+ this.useNamedValues = useNamedValues;
+ this.annotation = annotation;
+ // By hypothesis, num_element_value_pairs is stored in the last unsigned short of 'annotation'.
+ this.numElementValuePairsOffset = annotation.length == 0 ? -1 : annotation.length - 2;
+ this.previousAnnotation = previousAnnotation;
+ if (previousAnnotation != null) {
+ previousAnnotation.nextAnnotation = this;
+ }
+ }
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
+ /**
+ * Creates a new {@link AnnotationWriter} using named values.
+ *
+ * @param symbolTable where the constants used in this AnnotationWriter must be stored.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param previousAnnotation the previously visited annotation of the
+ * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or
+ * {@literal null} in other cases (e.g. nested or array annotations).
+ * @return a new {@link AnnotationWriter} for the given annotation descriptor.
+ */
+ static AnnotationWriter create(
+ final SymbolTable symbolTable,
+ final String descriptor,
+ final AnnotationWriter previousAnnotation) {
+ // Create a ByteVector to hold an 'annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.
+ ByteVector annotation = new ByteVector();
+ // Write type_index and reserve space for num_element_value_pairs.
+ annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ return new AnnotationWriter(
+ symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation);
+ }
- /**
- * Constructs a new {@link AnnotationWriter}.
- *
- * @param cw
- * the class writer to which this annotation must be added.
- * @param named
- * true if values are named, false otherwise.
- * @param bv
- * where the annotation values must be stored.
- * @param parent
- * where the number of annotation values must be stored.
- * @param offset
- * where in parent the number of annotation values must
- * be stored.
- */
- AnnotationWriter(final ClassWriter cw, final boolean named,
- final ByteVector bv, final ByteVector parent, final int offset) {
- super(Opcodes.ASM4);
- this.cw = cw;
- this.named = named;
- this.bv = bv;
- this.parent = parent;
- this.offset = offset;
+ /**
+ * Creates a new {@link AnnotationWriter} using named values.
+ *
+ * @param symbolTable where the constants used in this AnnotationWriter must be stored.
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link
+ * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See
+ * {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param previousAnnotation the previously visited annotation of the
+ * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or
+ * {@literal null} in other cases (e.g. nested or array annotations).
+ * @return a new {@link AnnotationWriter} for the given type annotation reference and descriptor.
+ */
+ static AnnotationWriter create(
+ final SymbolTable symbolTable,
+ final int typeRef,
+ final TypePath typePath,
+ final String descriptor,
+ final AnnotationWriter previousAnnotation) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ TypeReference.putTarget(typeRef, typeAnnotation);
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ return new AnnotationWriter(
+ symbolTable, /* useNamedValues = */ true, typeAnnotation, previousAnnotation);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the AnnotationVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
+
+ @Override
+ public void visit(final String name, final Object value) {
+ // Case of an element_value with a const_value_index, class_info_index or array_index field.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
+ ++numElementValuePairs;
+ if (useNamedValues) {
+ annotation.putShort(symbolTable.addConstantUtf8(name));
+ }
+ if (value instanceof String) {
+ annotation.put12('s', symbolTable.addConstantUtf8((String) value));
+ } else if (value instanceof Byte) {
+ annotation.put12('B', symbolTable.addConstantInteger(((Byte) value).byteValue()).index);
+ } else if (value instanceof Boolean) {
+ int booleanValue = ((Boolean) value).booleanValue() ? 1 : 0;
+ annotation.put12('Z', symbolTable.addConstantInteger(booleanValue).index);
+ } else if (value instanceof Character) {
+ annotation.put12('C', symbolTable.addConstantInteger(((Character) value).charValue()).index);
+ } else if (value instanceof Short) {
+ annotation.put12('S', symbolTable.addConstantInteger(((Short) value).shortValue()).index);
+ } else if (value instanceof Type) {
+ annotation.put12('c', symbolTable.addConstantUtf8(((Type) value).getDescriptor()));
+ } else if (value instanceof byte[]) {
+ byte[] byteArray = (byte[]) value;
+ annotation.put12('[', byteArray.length);
+ for (byte byteValue : byteArray) {
+ annotation.put12('B', symbolTable.addConstantInteger(byteValue).index);
+ }
+ } else if (value instanceof boolean[]) {
+ boolean[] booleanArray = (boolean[]) value;
+ annotation.put12('[', booleanArray.length);
+ for (boolean booleanValue : booleanArray) {
+ annotation.put12('Z', symbolTable.addConstantInteger(booleanValue ? 1 : 0).index);
+ }
+ } else if (value instanceof short[]) {
+ short[] shortArray = (short[]) value;
+ annotation.put12('[', shortArray.length);
+ for (short shortValue : shortArray) {
+ annotation.put12('S', symbolTable.addConstantInteger(shortValue).index);
+ }
+ } else if (value instanceof char[]) {
+ char[] charArray = (char[]) value;
+ annotation.put12('[', charArray.length);
+ for (char charValue : charArray) {
+ annotation.put12('C', symbolTable.addConstantInteger(charValue).index);
+ }
+ } else if (value instanceof int[]) {
+ int[] intArray = (int[]) value;
+ annotation.put12('[', intArray.length);
+ for (int intValue : intArray) {
+ annotation.put12('I', symbolTable.addConstantInteger(intValue).index);
+ }
+ } else if (value instanceof long[]) {
+ long[] longArray = (long[]) value;
+ annotation.put12('[', longArray.length);
+ for (long longValue : longArray) {
+ annotation.put12('J', symbolTable.addConstantLong(longValue).index);
+ }
+ } else if (value instanceof float[]) {
+ float[] floatArray = (float[]) value;
+ annotation.put12('[', floatArray.length);
+ for (float floatValue : floatArray) {
+ annotation.put12('F', symbolTable.addConstantFloat(floatValue).index);
+ }
+ } else if (value instanceof double[]) {
+ double[] doubleArray = (double[]) value;
+ annotation.put12('[', doubleArray.length);
+ for (double doubleValue : doubleArray) {
+ annotation.put12('D', symbolTable.addConstantDouble(doubleValue).index);
+ }
+ } else {
+ Symbol symbol = symbolTable.addConstant(value);
+ annotation.put12(".s.IFJDCS".charAt(symbol.tag), symbol.index);
}
+ }
- // ------------------------------------------------------------------------
- // Implementation of the AnnotationVisitor abstract class
- // ------------------------------------------------------------------------
+ @Override
+ public void visitEnum(final String name, final String descriptor, final String value) {
+ // Case of an element_value with an enum_const_value field.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
+ ++numElementValuePairs;
+ if (useNamedValues) {
+ annotation.putShort(symbolTable.addConstantUtf8(name));
+ }
+ annotation
+ .put12('e', symbolTable.addConstantUtf8(descriptor))
+ .putShort(symbolTable.addConstantUtf8(value));
+ }
- @Override
- public void visit(final String name, final Object value) {
- ++size;
- if (named) {
- bv.putShort(cw.newUTF8(name));
- }
- if (value instanceof String) {
- bv.put12('s', cw.newUTF8((String) value));
- } else if (value instanceof Byte) {
- bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index);
- } else if (value instanceof Boolean) {
- int v = ((Boolean) value).booleanValue() ? 1 : 0;
- bv.put12('Z', cw.newInteger(v).index);
- } else if (value instanceof Character) {
- bv.put12('C', cw.newInteger(((Character) value).charValue()).index);
- } else if (value instanceof Short) {
- bv.put12('S', cw.newInteger(((Short) value).shortValue()).index);
- } else if (value instanceof Type) {
- bv.put12('c', cw.newUTF8(((Type) value).getDescriptor()));
- } else if (value instanceof byte[]) {
- byte[] v = (byte[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('B', cw.newInteger(v[i]).index);
- }
- } else if (value instanceof boolean[]) {
- boolean[] v = (boolean[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index);
- }
- } else if (value instanceof short[]) {
- short[] v = (short[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('S', cw.newInteger(v[i]).index);
- }
- } else if (value instanceof char[]) {
- char[] v = (char[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('C', cw.newInteger(v[i]).index);
- }
- } else if (value instanceof int[]) {
- int[] v = (int[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('I', cw.newInteger(v[i]).index);
- }
- } else if (value instanceof long[]) {
- long[] v = (long[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('J', cw.newLong(v[i]).index);
- }
- } else if (value instanceof float[]) {
- float[] v = (float[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('F', cw.newFloat(v[i]).index);
- }
- } else if (value instanceof double[]) {
- double[] v = (double[]) value;
- bv.put12('[', v.length);
- for (int i = 0; i < v.length; i++) {
- bv.put12('D', cw.newDouble(v[i]).index);
- }
- } else {
- Item i = cw.newConstItem(value);
- bv.put12(".s.IFJDCS".charAt(i.type), i.index);
- }
+ @Override
+ public AnnotationVisitor visitAnnotation(final String name, final String descriptor) {
+ // Case of an element_value with an annotation_value field.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
+ ++numElementValuePairs;
+ if (useNamedValues) {
+ annotation.putShort(symbolTable.addConstantUtf8(name));
}
+ // Write tag and type_index, and reserve 2 bytes for num_element_value_pairs.
+ annotation.put12('@', symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ return new AnnotationWriter(symbolTable, /* useNamedValues = */ true, annotation, null);
+ }
- @Override
- public void visitEnum(final String name, final String desc,
- final String value) {
- ++size;
- if (named) {
- bv.putShort(cw.newUTF8(name));
- }
- bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value));
+ @Override
+ public AnnotationVisitor visitArray(final String name) {
+ // Case of an element_value with an array_value field.
+ // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1
+ ++numElementValuePairs;
+ if (useNamedValues) {
+ annotation.putShort(symbolTable.addConstantUtf8(name));
}
+ // Write tag, and reserve 2 bytes for num_values. Here we take advantage of the fact that the
+ // end of an element_value of array type is similar to the end of an 'annotation' structure: an
+ // unsigned short num_values followed by num_values element_value, versus an unsigned short
+ // num_element_value_pairs, followed by num_element_value_pairs { element_name_index,
+ // element_value } tuples. This allows us to use an AnnotationWriter with unnamed values to
+ // visit the array elements. Its num_element_value_pairs will correspond to the number of array
+ // elements and will be stored in what is in fact num_values.
+ annotation.put12('[', 0);
+ return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, annotation, null);
+ }
- @Override
- public AnnotationVisitor visitAnnotation(final String name,
- final String desc) {
- ++size;
- if (named) {
- bv.putShort(cw.newUTF8(name));
- }
- // write tag and type, and reserve space for values count
- bv.put12('@', cw.newUTF8(desc)).putShort(0);
- return new AnnotationWriter(cw, true, bv, bv, bv.length - 2);
+ @Override
+ public void visitEnd() {
+ if (numElementValuePairsOffset != -1) {
+ byte[] data = annotation.data;
+ data[numElementValuePairsOffset] = (byte) (numElementValuePairs >>> 8);
+ data[numElementValuePairsOffset + 1] = (byte) numElementValuePairs;
}
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods
+ // -----------------------------------------------------------------------------------------------
- @Override
- public AnnotationVisitor visitArray(final String name) {
- ++size;
- if (named) {
- bv.putShort(cw.newUTF8(name));
- }
- // write tag, and reserve space for array size
- bv.put12('[', 0);
- return new AnnotationWriter(cw, false, bv, bv, bv.length - 2);
+ /**
+ * Returns the size of a Runtime[In]Visible[Type]Annotations attribute containing this annotation
+ * and all its predecessors (see {@link #previousAnnotation}. Also adds the attribute name
+ * to the constant pool of the class (if not null).
+ *
+ * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or {@literal null}.
+ * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this
+ * annotation and all its predecessors. This includes the size of the attribute_name_index and
+ * attribute_length fields.
+ */
+ int computeAnnotationsSize(final String attributeName) {
+ if (attributeName != null) {
+ symbolTable.addConstantUtf8(attributeName);
}
+ // The attribute_name_index, attribute_length and num_annotations fields use 8 bytes.
+ int attributeSize = 8;
+ AnnotationWriter annotationWriter = this;
+ while (annotationWriter != null) {
+ attributeSize += annotationWriter.annotation.length;
+ annotationWriter = annotationWriter.previousAnnotation;
+ }
+ return attributeSize;
+ }
- @Override
- public void visitEnd() {
- if (parent != null) {
- byte[] data = parent.data;
- data[offset] = (byte) (size >>> 8);
- data[offset + 1] = (byte) size;
- }
+ /**
+ * Returns the size of the Runtime[In]Visible[Type]Annotations attributes containing the given
+ * annotations and all their predecessors (see {@link #previousAnnotation}. Also adds the
+ * attribute names to the constant pool of the class (if not null).
+ *
+ * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or
+ * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be
+ * {@literal null}.
+ * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field,
+ * method or class. The previous ones can be accessed with the {@link #previousAnnotation}
+ * field. May be {@literal null}.
+ * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a
+ * field, method or class. The previous ones can be accessed with the {@link
+ * #previousAnnotation} field. May be {@literal null}.
+ * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a
+ * field, method or class field. The previous ones can be accessed with the {@link
+ * #previousAnnotation} field. May be {@literal null}.
+ * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing the
+ * given annotations and all their predecessors. This includes the size of the
+ * attribute_name_index and attribute_length fields.
+ */
+ static int computeAnnotationsSize(
+ final AnnotationWriter lastRuntimeVisibleAnnotation,
+ final AnnotationWriter lastRuntimeInvisibleAnnotation,
+ final AnnotationWriter lastRuntimeVisibleTypeAnnotation,
+ final AnnotationWriter lastRuntimeInvisibleTypeAnnotation) {
+ int size = 0;
+ if (lastRuntimeVisibleAnnotation != null) {
+ size +=
+ lastRuntimeVisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ size +=
+ lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ size +=
+ lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
}
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ size +=
+ lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
+ return size;
+ }
- // ------------------------------------------------------------------------
- // Utility methods
- // ------------------------------------------------------------------------
+ /**
+ * Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its
+ * predecessors (see {@link #previousAnnotation} in the given ByteVector. Annotations are
+ * put in the same order they have been visited.
+ *
+ * @param attributeNameIndex the constant pool index of the attribute name (one of
+ * "Runtime[In]Visible[Type]Annotations").
+ * @param output where the attribute must be put.
+ */
+ void putAnnotations(final int attributeNameIndex, final ByteVector output) {
+ int attributeLength = 2; // For num_annotations.
+ int numAnnotations = 0;
+ AnnotationWriter annotationWriter = this;
+ AnnotationWriter firstAnnotation = null;
+ while (annotationWriter != null) {
+ // In case the user forgot to call visitEnd().
+ annotationWriter.visitEnd();
+ attributeLength += annotationWriter.annotation.length;
+ numAnnotations++;
+ firstAnnotation = annotationWriter;
+ annotationWriter = annotationWriter.previousAnnotation;
+ }
+ output.putShort(attributeNameIndex);
+ output.putInt(attributeLength);
+ output.putShort(numAnnotations);
+ annotationWriter = firstAnnotation;
+ while (annotationWriter != null) {
+ output.putByteArray(annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
+ annotationWriter = annotationWriter.nextAnnotation;
+ }
+ }
- /**
- * Returns the size of this annotation writer list.
- *
- * @return the size of this annotation writer list.
- */
- int getSize() {
- int size = 0;
- AnnotationWriter aw = this;
- while (aw != null) {
- size += aw.bv.length;
- aw = aw.next;
- }
- return size;
+ /**
+ * Puts the Runtime[In]Visible[Type]Annotations attributes containing the given annotations and
+ * all their predecessors (see {@link #previousAnnotation} in the given ByteVector.
+ * Annotations are put in the same order they have been visited.
+ *
+ * @param symbolTable where the constants used in the AnnotationWriter instances are stored.
+ * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or
+ * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be
+ * {@literal null}.
+ * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field,
+ * method or class. The previous ones can be accessed with the {@link #previousAnnotation}
+ * field. May be {@literal null}.
+ * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a
+ * field, method or class. The previous ones can be accessed with the {@link
+ * #previousAnnotation} field. May be {@literal null}.
+ * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a
+ * field, method or class field. The previous ones can be accessed with the {@link
+ * #previousAnnotation} field. May be {@literal null}.
+ * @param output where the attributes must be put.
+ */
+ static void putAnnotations(
+ final SymbolTable symbolTable,
+ final AnnotationWriter lastRuntimeVisibleAnnotation,
+ final AnnotationWriter lastRuntimeInvisibleAnnotation,
+ final AnnotationWriter lastRuntimeVisibleTypeAnnotation,
+ final AnnotationWriter lastRuntimeInvisibleTypeAnnotation,
+ final ByteVector output) {
+ if (lastRuntimeVisibleAnnotation != null) {
+ lastRuntimeVisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ lastRuntimeInvisibleAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output);
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ lastRuntimeVisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
}
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ lastRuntimeInvisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ }
- /**
- * Puts the annotations of this annotation writer list into the given byte
- * vector.
- *
- * @param out
- * where the annotations must be put.
- */
- void put(final ByteVector out) {
- int n = 0;
- int size = 2;
- AnnotationWriter aw = this;
- AnnotationWriter last = null;
- while (aw != null) {
- ++n;
- size += aw.bv.length;
- aw.visitEnd(); // in case user forgot to call visitEnd
- aw.prev = last;
- last = aw;
- aw = aw.next;
- }
- out.putInt(size);
- out.putShort(n);
- aw = last;
- while (aw != null) {
- out.putByteArray(aw.bv.data, 0, aw.bv.length);
- aw = aw.prev;
- }
+ /**
+ * Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the
+ * annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the
+ * constant pool of the class.
+ *
+ * @param attributeName one of "Runtime[In]VisibleParameterAnnotations".
+ * @param annotationWriters an array of AnnotationWriter lists (designated by their last
+ * element).
+ * @param annotableParameterCount the number of elements in annotationWriters to take into account
+ * (elements [0..annotableParameterCount[ are taken into account).
+ * @return the size in bytes of a Runtime[In]VisibleParameterAnnotations attribute corresponding
+ * to the given sub-array of AnnotationWriter lists. This includes the size of the
+ * attribute_name_index and attribute_length fields.
+ */
+ static int computeParameterAnnotationsSize(
+ final String attributeName,
+ final AnnotationWriter[] annotationWriters,
+ final int annotableParameterCount) {
+ // Note: attributeName is added to the constant pool by the call to computeAnnotationsSize
+ // below. This assumes that there is at least one non-null element in the annotationWriters
+ // sub-array (which is ensured by the lazy instantiation of this array in MethodWriter).
+ // The attribute_name_index, attribute_length and num_parameters fields use 7 bytes, and each
+ // element of the parameter_annotations array uses 2 bytes for its num_annotations field.
+ int attributeSize = 7 + 2 * annotableParameterCount;
+ for (int i = 0; i < annotableParameterCount; ++i) {
+ AnnotationWriter annotationWriter = annotationWriters[i];
+ attributeSize +=
+ annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8;
}
+ return attributeSize;
+ }
- /**
- * Puts the given annotation lists into the given byte vector.
- *
- * @param panns
- * an array of annotation writer lists.
- * @param off
- * index of the first annotation to be written.
- * @param out
- * where the annotations must be put.
- */
- static void put(final AnnotationWriter[] panns, final int off,
- final ByteVector out) {
- int size = 1 + 2 * (panns.length - off);
- for (int i = off; i < panns.length; ++i) {
- size += panns[i] == null ? 0 : panns[i].getSize();
- }
- out.putInt(size).putByte(panns.length - off);
- for (int i = off; i < panns.length; ++i) {
- AnnotationWriter aw = panns[i];
- AnnotationWriter last = null;
- int n = 0;
- while (aw != null) {
- ++n;
- aw.visitEnd(); // in case user forgot to call visitEnd
- aw.prev = last;
- last = aw;
- aw = aw.next;
- }
- out.putShort(n);
- aw = last;
- while (aw != null) {
- out.putByteArray(aw.bv.data, 0, aw.bv.length);
- aw = aw.prev;
- }
- }
+ /**
+ * Puts a Runtime[In]VisibleParameterAnnotations attribute containing all the annotation lists
+ * from the given AnnotationWriter sub-array in the given ByteVector.
+ *
+ * @param attributeNameIndex constant pool index of the attribute name (one of
+ * Runtime[In]VisibleParameterAnnotations).
+ * @param annotationWriters an array of AnnotationWriter lists (designated by their last
+ * element).
+ * @param annotableParameterCount the number of elements in annotationWriters to put (elements
+ * [0..annotableParameterCount[ are put).
+ * @param output where the attribute must be put.
+ */
+ static void putParameterAnnotations(
+ final int attributeNameIndex,
+ final AnnotationWriter[] annotationWriters,
+ final int annotableParameterCount,
+ final ByteVector output) {
+ // The num_parameters field uses 1 byte, and each element of the parameter_annotations array
+ // uses 2 bytes for its num_annotations field.
+ int attributeLength = 1 + 2 * annotableParameterCount;
+ for (int i = 0; i < annotableParameterCount; ++i) {
+ AnnotationWriter annotationWriter = annotationWriters[i];
+ attributeLength +=
+ annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(null) - 8;
+ }
+ output.putShort(attributeNameIndex);
+ output.putInt(attributeLength);
+ output.putByte(annotableParameterCount);
+ for (int i = 0; i < annotableParameterCount; ++i) {
+ AnnotationWriter annotationWriter = annotationWriters[i];
+ AnnotationWriter firstAnnotation = null;
+ int numAnnotations = 0;
+ while (annotationWriter != null) {
+ // In case user the forgot to call visitEnd().
+ annotationWriter.visitEnd();
+ numAnnotations++;
+ firstAnnotation = annotationWriter;
+ annotationWriter = annotationWriter.previousAnnotation;
+ }
+ output.putShort(numAnnotations);
+ annotationWriter = firstAnnotation;
+ while (annotationWriter != null) {
+ output.putByteArray(
+ annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
+ annotationWriter = annotationWriter.nextAnnotation;
+ }
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/Attribute.java b/src/java/nginx/clojure/asm/Attribute.java
index 1548f06d..06b168bf 100644
--- a/src/java/nginx/clojure/asm/Attribute.java
+++ b/src/java/nginx/clojure/asm/Attribute.java
@@ -1,255 +1,392 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A non standard class, field, method or code attribute.
- *
+ * A non standard class, field, method or Code attribute, as defined in the Java Virtual Machine
+ * Specification (JVMS).
+ *
+ * @see JVMS
+ * 4.7
+ * @see JVMS
+ * 4.7.3
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public class Attribute {
- /**
- * The type of this attribute.
- */
- public final String type;
-
- /**
- * The raw value of this attribute, used only for unknown attributes.
- */
- byte[] value;
-
- /**
- * The next attribute in this attribute list. May be null.
- */
- Attribute next;
-
- /**
- * Constructs a new empty attribute.
- *
- * @param type
- * the type of the attribute.
- */
- protected Attribute(final String type) {
- this.type = type;
- }
+ /** The type of this attribute, also called its name in the JVMS. */
+ public final String type;
+
+ /**
+ * The raw content of this attribute, only used for unknown attributes (see {@link #isUnknown()}).
+ * The 6 header bytes of the attribute (attribute_name_index and attribute_length) are not
+ * included.
+ */
+ private byte[] content;
+
+ /**
+ * The next attribute in this attribute list (Attribute instances can be linked via this field to
+ * store a list of class, field, method or Code attributes). May be {@literal null}.
+ */
+ Attribute nextAttribute;
+
+ /**
+ * Constructs a new empty attribute.
+ *
+ * @param type the type of the attribute.
+ */
+ protected Attribute(final String type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns {@literal true} if this type of attribute is unknown. This means that the attribute
+ * content can't be parsed to extract constant pool references, labels, etc. Instead, the
+ * attribute content is read as an opaque byte array, and written back as is. This can lead to
+ * invalid attributes, if the content actually contains constant pool references, labels, or other
+ * symbolic references that need to be updated when there are changes to the constant pool, the
+ * method bytecode, etc. The default implementation of this method always returns {@literal true}.
+ *
+ * @return {@literal true} if this type of attribute is unknown.
+ */
+ public boolean isUnknown() {
+ return true;
+ }
+
+ /**
+ * Returns {@literal true} if this type of attribute is a Code attribute.
+ *
+ * @return {@literal true} if this type of attribute is a Code attribute.
+ */
+ public boolean isCodeAttribute() {
+ return false;
+ }
+
+ /**
+ * Returns the labels corresponding to this attribute.
+ *
+ * @return the labels corresponding to this attribute, or {@literal null} if this attribute is not
+ * a Code attribute that contains labels.
+ */
+ protected Label[] getLabels() {
+ return new Label[0];
+ }
+
+ /**
+ * Reads a {@link #type} attribute. This method must return a new {@link Attribute} object,
+ * of type {@link #type}, corresponding to the 'length' bytes starting at 'offset', in the given
+ * ClassReader.
+ *
+ * @param classReader the class that contains the attribute to be read.
+ * @param offset index of the first byte of the attribute's content in {@link ClassReader}. The 6
+ * attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param length the length of the attribute's content (excluding the 6 attribute header bytes).
+ * @param charBuffer the buffer to be used to call the ClassReader methods requiring a
+ * 'charBuffer' parameter.
+ * @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute
+ * in {@link ClassReader}, or -1 if the attribute to be read is not a Code attribute. The 6
+ * attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param labels the labels of the method's code, or {@literal null} if the attribute to be read
+ * is not a Code attribute.
+ * @return a new {@link Attribute} object corresponding to the specified bytes.
+ */
+ protected Attribute read(
+ final ClassReader classReader,
+ final int offset,
+ final int length,
+ final char[] charBuffer,
+ final int codeAttributeOffset,
+ final Label[] labels) {
+ Attribute attribute = new Attribute(type);
+ attribute.content = new byte[length];
+ System.arraycopy(classReader.classFileBuffer, offset, attribute.content, 0, length);
+ return attribute;
+ }
+
+ /**
+ * Returns the byte array form of the content of this attribute. The 6 header bytes
+ * (attribute_name_index and attribute_length) must not be added in the returned
+ * ByteVector.
+ *
+ * @param classWriter the class to which this attribute must be added. This parameter can be used
+ * to add the items that corresponds to this attribute to the constant pool of this class.
+ * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null}
+ * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code
+ * attribute.
+ * @param codeLength the length of the bytecode of the method corresponding to this code
+ * attribute, or 0 if this attribute is not a Code attribute. Corresponds to the 'code_length'
+ * field of the Code attribute.
+ * @param maxStack the maximum stack size of the method corresponding to this Code attribute, or
+ * -1 if this attribute is not a Code attribute.
+ * @param maxLocals the maximum number of local variables of the method corresponding to this code
+ * attribute, or -1 if this attribute is not a Code attribute.
+ * @return the byte array form of this attribute.
+ */
+ protected ByteVector write(
+ final ClassWriter classWriter,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals) {
+ return new ByteVector(content);
+ }
- /**
- * Returns true if this type of attribute is unknown. The default
- * implementation of this method always returns true.
- *
- * @return true if this type of attribute is unknown.
- */
- public boolean isUnknown() {
- return true;
+ /**
+ * Returns the number of attributes of the attribute list that begins with this attribute.
+ *
+ * @return the number of attributes of the attribute list that begins with this attribute.
+ */
+ final int getAttributeCount() {
+ int count = 0;
+ Attribute attribute = this;
+ while (attribute != null) {
+ count += 1;
+ attribute = attribute.nextAttribute;
}
+ return count;
+ }
- /**
- * Returns true if this type of attribute is a code attribute.
- *
- * @return true if this type of attribute is a code attribute.
- */
- public boolean isCodeAttribute() {
- return false;
+ /**
+ * Returns the total size in bytes of all the attributes in the attribute list that begins with
+ * this attribute. This size includes the 6 header bytes (attribute_name_index and
+ * attribute_length) per attribute. Also adds the attribute type names to the constant pool.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @return the size of all the attributes in this attribute list. This size includes the size of
+ * the attribute headers.
+ */
+ final int computeAttributesSize(final SymbolTable symbolTable) {
+ final byte[] code = null;
+ final int codeLength = 0;
+ final int maxStack = -1;
+ final int maxLocals = -1;
+ return computeAttributesSize(symbolTable, code, codeLength, maxStack, maxLocals);
+ }
+
+ /**
+ * Returns the total size in bytes of all the attributes in the attribute list that begins with
+ * this attribute. This size includes the 6 header bytes (attribute_name_index and
+ * attribute_length) per attribute. Also adds the attribute type names to the constant pool.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param code the bytecode of the method corresponding to these Code attributes, or {@literal
+ * null} if they are not Code attributes. Corresponds to the 'code' field of the Code
+ * attribute.
+ * @param codeLength the length of the bytecode of the method corresponding to these code
+ * attributes, or 0 if they are not Code attributes. Corresponds to the 'code_length' field of
+ * the Code attribute.
+ * @param maxStack the maximum stack size of the method corresponding to these Code attributes, or
+ * -1 if they are not Code attributes.
+ * @param maxLocals the maximum number of local variables of the method corresponding to these
+ * Code attributes, or -1 if they are not Code attribute.
+ * @return the size of all the attributes in this attribute list. This size includes the size of
+ * the attribute headers.
+ */
+ final int computeAttributesSize(
+ final SymbolTable symbolTable,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals) {
+ final ClassWriter classWriter = symbolTable.classWriter;
+ int size = 0;
+ Attribute attribute = this;
+ while (attribute != null) {
+ symbolTable.addConstantUtf8(attribute.type);
+ size += 6 + attribute.write(classWriter, code, codeLength, maxStack, maxLocals).length;
+ attribute = attribute.nextAttribute;
}
+ return size;
+ }
- /**
- * Returns the labels corresponding to this attribute.
- *
- * @return the labels corresponding to this attribute, or null if
- * this attribute is not a code attribute that contains labels.
- */
- protected Label[] getLabels() {
- return null;
+ /**
+ * Returns the total size in bytes of all the attributes that correspond to the given field,
+ * method or class access flags and signature. This size includes the 6 header bytes
+ * (attribute_name_index and attribute_length) per attribute. Also adds the attribute type names
+ * to the constant pool.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param accessFlags some field, method or class access flags.
+ * @param signatureIndex the constant pool index of a field, method of class signature.
+ * @return the size of all the attributes in bytes. This size includes the size of the attribute
+ * headers.
+ */
+ static int computeAttributesSize(
+ final SymbolTable symbolTable, final int accessFlags, final int signatureIndex) {
+ int size = 0;
+ // Before Java 1.5, synthetic fields are represented with a Synthetic attribute.
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0
+ && symbolTable.getMajorVersion() < Opcodes.V1_5) {
+ // Synthetic attributes always use 6 bytes.
+ symbolTable.addConstantUtf8(Constants.SYNTHETIC);
+ size += 6;
+ }
+ if (signatureIndex != 0) {
+ // Signature attributes always use 8 bytes.
+ symbolTable.addConstantUtf8(Constants.SIGNATURE);
+ size += 8;
+ }
+ // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead.
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ // Deprecated attributes always use 6 bytes.
+ symbolTable.addConstantUtf8(Constants.DEPRECATED);
+ size += 6;
}
+ return size;
+ }
- /**
- * Reads a {@link #type type} attribute. This method must return a
- * new {@link Attribute} object, of type {@link #type type},
- * corresponding to the len bytes starting at the given offset, in
- * the given class reader.
- *
- * @param cr
- * the class that contains the attribute to be read.
- * @param off
- * index of the first byte of the attribute's content in
- * {@link ClassReader#b cr.b}. The 6 attribute header bytes,
- * containing the type and the length of the attribute, are not
- * taken into account here.
- * @param len
- * the length of the attribute's content.
- * @param buf
- * buffer to be used to call {@link ClassReader#readUTF8
- * readUTF8}, {@link ClassReader#readClass(int,char[]) readClass}
- * or {@link ClassReader#readConst readConst}.
- * @param codeOff
- * index of the first byte of code's attribute content in
- * {@link ClassReader#b cr.b}, or -1 if the attribute to be read
- * is not a code attribute. The 6 attribute header bytes,
- * containing the type and the length of the attribute, are not
- * taken into account here.
- * @param labels
- * the labels of the method's code, or null if the
- * attribute to be read is not a code attribute.
- * @return a new {@link Attribute} object corresponding to the given
- * bytes.
- */
- protected Attribute read(final ClassReader cr, final int off,
- final int len, final char[] buf, final int codeOff,
- final Label[] labels) {
- Attribute attr = new Attribute(type);
- attr.value = new byte[len];
- System.arraycopy(cr.b, off, attr.value, 0, len);
- return attr;
+ /**
+ * Puts all the attributes of the attribute list that begins with this attribute, in the given
+ * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per
+ * attribute.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param output where the attributes must be written.
+ */
+ final void putAttributes(final SymbolTable symbolTable, final ByteVector output) {
+ final byte[] code = null;
+ final int codeLength = 0;
+ final int maxStack = -1;
+ final int maxLocals = -1;
+ putAttributes(symbolTable, code, codeLength, maxStack, maxLocals, output);
+ }
+
+ /**
+ * Puts all the attributes of the attribute list that begins with this attribute, in the given
+ * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per
+ * attribute.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param code the bytecode of the method corresponding to these Code attributes, or {@literal
+ * null} if they are not Code attributes. Corresponds to the 'code' field of the Code
+ * attribute.
+ * @param codeLength the length of the bytecode of the method corresponding to these code
+ * attributes, or 0 if they are not Code attributes. Corresponds to the 'code_length' field of
+ * the Code attribute.
+ * @param maxStack the maximum stack size of the method corresponding to these Code attributes, or
+ * -1 if they are not Code attributes.
+ * @param maxLocals the maximum number of local variables of the method corresponding to these
+ * Code attributes, or -1 if they are not Code attribute.
+ * @param output where the attributes must be written.
+ */
+ final void putAttributes(
+ final SymbolTable symbolTable,
+ final byte[] code,
+ final int codeLength,
+ final int maxStack,
+ final int maxLocals,
+ final ByteVector output) {
+ final ClassWriter classWriter = symbolTable.classWriter;
+ Attribute attribute = this;
+ while (attribute != null) {
+ ByteVector attributeContent =
+ attribute.write(classWriter, code, codeLength, maxStack, maxLocals);
+ // Put attribute_name_index and attribute_length.
+ output.putShort(symbolTable.addConstantUtf8(attribute.type)).putInt(attributeContent.length);
+ output.putByteArray(attributeContent.data, 0, attributeContent.length);
+ attribute = attribute.nextAttribute;
}
+ }
- /**
- * Returns the byte array form of this attribute.
- *
- * @param cw
- * the class to which this attribute must be added. This
- * parameter can be used to add to the constant pool of this
- * class the items that corresponds to this attribute.
- * @param code
- * the bytecode of the method corresponding to this code
- * attribute, or null if this attribute is not a code
- * attributes.
- * @param len
- * the length of the bytecode of the method corresponding to this
- * code attribute, or null if this attribute is not a
- * code attribute.
- * @param maxStack
- * the maximum stack size of the method corresponding to this
- * code attribute, or -1 if this attribute is not a code
- * attribute.
- * @param maxLocals
- * the maximum number of local variables of the method
- * corresponding to this code attribute, or -1 if this attribute
- * is not a code attribute.
- * @return the byte array form of this attribute.
- */
- protected ByteVector write(final ClassWriter cw, final byte[] code,
- final int len, final int maxStack, final int maxLocals) {
- ByteVector v = new ByteVector();
- v.data = value;
- v.length = value.length;
- return v;
+ /**
+ * Puts all the attributes that correspond to the given field, method or class access flags and
+ * signature, in the given byte vector. This includes the 6 header bytes (attribute_name_index and
+ * attribute_length) per attribute.
+ *
+ * @param symbolTable where the constants used in the attributes must be stored.
+ * @param accessFlags some field, method or class access flags.
+ * @param signatureIndex the constant pool index of a field, method of class signature.
+ * @param output where the attributes must be written.
+ */
+ static void putAttributes(
+ final SymbolTable symbolTable,
+ final int accessFlags,
+ final int signatureIndex,
+ final ByteVector output) {
+ // Before Java 1.5, synthetic fields are represented with a Synthetic attribute.
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0
+ && symbolTable.getMajorVersion() < Opcodes.V1_5) {
+ output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
+ }
+ if (signatureIndex != 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE))
+ .putInt(2)
+ .putShort(signatureIndex);
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
}
+ }
- /**
- * Returns the length of the attribute list that begins with this attribute.
- *
- * @return the length of the attribute list that begins with this attribute.
- */
- final int getCount() {
- int count = 0;
- Attribute attr = this;
- while (attr != null) {
- count += 1;
- attr = attr.next;
+ /** A set of attribute prototypes (attributes with the same type are considered equal). */
+ static final class Set {
+
+ private static final int SIZE_INCREMENT = 6;
+
+ private int size;
+ private Attribute[] data = new Attribute[SIZE_INCREMENT];
+
+ void addAttributes(final Attribute attributeList) {
+ Attribute attribute = attributeList;
+ while (attribute != null) {
+ if (!contains(attribute)) {
+ add(attribute);
}
- return count;
+ attribute = attribute.nextAttribute;
+ }
}
- /**
- * Returns the size of all the attributes in this attribute list.
- *
- * @param cw
- * the class writer to be used to convert the attributes into
- * byte arrays, with the {@link #write write} method.
- * @param code
- * the bytecode of the method corresponding to these code
- * attributes, or null if these attributes are not code
- * attributes.
- * @param len
- * the length of the bytecode of the method corresponding to
- * these code attributes, or null if these attributes
- * are not code attributes.
- * @param maxStack
- * the maximum stack size of the method corresponding to these
- * code attributes, or -1 if these attributes are not code
- * attributes.
- * @param maxLocals
- * the maximum number of local variables of the method
- * corresponding to these code attributes, or -1 if these
- * attributes are not code attributes.
- * @return the size of all the attributes in this attribute list. This size
- * includes the size of the attribute headers.
- */
- final int getSize(final ClassWriter cw, final byte[] code, final int len,
- final int maxStack, final int maxLocals) {
- Attribute attr = this;
- int size = 0;
- while (attr != null) {
- cw.newUTF8(attr.type);
- size += attr.write(cw, code, len, maxStack, maxLocals).length + 6;
- attr = attr.next;
- }
- return size;
+ Attribute[] toArray() {
+ Attribute[] result = new Attribute[size];
+ System.arraycopy(data, 0, result, 0, size);
+ return result;
}
- /**
- * Writes all the attributes of this attribute list in the given byte
- * vector.
- *
- * @param cw
- * the class writer to be used to convert the attributes into
- * byte arrays, with the {@link #write write} method.
- * @param code
- * the bytecode of the method corresponding to these code
- * attributes, or null if these attributes are not code
- * attributes.
- * @param len
- * the length of the bytecode of the method corresponding to
- * these code attributes, or null if these attributes
- * are not code attributes.
- * @param maxStack
- * the maximum stack size of the method corresponding to these
- * code attributes, or -1 if these attributes are not code
- * attributes.
- * @param maxLocals
- * the maximum number of local variables of the method
- * corresponding to these code attributes, or -1 if these
- * attributes are not code attributes.
- * @param out
- * where the attributes must be written.
- */
- final void put(final ClassWriter cw, final byte[] code, final int len,
- final int maxStack, final int maxLocals, final ByteVector out) {
- Attribute attr = this;
- while (attr != null) {
- ByteVector b = attr.write(cw, code, len, maxStack, maxLocals);
- out.putShort(cw.newUTF8(attr.type)).putInt(b.length);
- out.putByteArray(b.data, 0, b.length);
- attr = attr.next;
+ private boolean contains(final Attribute attribute) {
+ for (int i = 0; i < size; ++i) {
+ if (data[i].type.equals(attribute.type)) {
+ return true;
}
+ }
+ return false;
+ }
+
+ private void add(final Attribute attribute) {
+ if (size >= data.length) {
+ Attribute[] newData = new Attribute[data.length + SIZE_INCREMENT];
+ System.arraycopy(data, 0, newData, 0, size);
+ data = newData;
+ }
+ data[size++] = attribute;
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/ByteVector.java b/src/java/nginx/clojure/asm/ByteVector.java
index 2eaf5c9d..f06678c8 100644
--- a/src/java/nginx/clojure/asm/ByteVector.java
+++ b/src/java/nginx/clojure/asm/ByteVector.java
@@ -1,312 +1,373 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A dynamically extensible vector of bytes. This class is roughly equivalent to
- * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient.
- *
+ * A dynamically extensible vector of bytes. This class is roughly equivalent to a DataOutputStream
+ * on top of a ByteArrayOutputStream, but is more efficient.
+ *
* @author Eric Bruneton
*/
public class ByteVector {
- /**
- * The content of this vector.
- */
- byte[] data;
+ /** The content of this vector. Only the first {@link #length} bytes contain real data. */
+ byte[] data;
+
+ /** The actual number of bytes in this vector. */
+ int length;
- /**
- * Actual number of bytes in this vector.
- */
- int length;
+ /** Constructs a new {@link ByteVector} with a default initial capacity. */
+ public ByteVector() {
+ data = new byte[64];
+ }
- /**
- * Constructs a new {@link ByteVector ByteVector} with a default initial
- * size.
- */
- public ByteVector() {
- data = new byte[64];
+ /**
+ * Constructs a new {@link ByteVector} with the given initial capacity.
+ *
+ * @param initialCapacity the initial capacity of the byte vector to be constructed.
+ */
+ public ByteVector(final int initialCapacity) {
+ data = new byte[initialCapacity];
+ }
+
+ /**
+ * Constructs a new {@link ByteVector} from the given initial data.
+ *
+ * @param data the initial data of the new byte vector.
+ */
+ ByteVector(final byte[] data) {
+ this.data = data;
+ this.length = data.length;
+ }
+
+ /**
+ * Returns the actual number of bytes in this vector.
+ *
+ * @return the actual number of bytes in this vector.
+ */
+ public int size() {
+ return length;
+ }
+
+ /**
+ * Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param byteValue a byte.
+ * @return this byte vector.
+ */
+ public ByteVector putByte(final int byteValue) {
+ int currentLength = length;
+ if (currentLength + 1 > data.length) {
+ enlarge(1);
}
+ data[currentLength++] = (byte) byteValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Constructs a new {@link ByteVector ByteVector} with the given initial
- * size.
- *
- * @param initialSize
- * the initial size of the byte vector to be constructed.
- */
- public ByteVector(final int initialSize) {
- data = new byte[initialSize];
+ /**
+ * Puts two bytes into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param byteValue1 a byte.
+ * @param byteValue2 another byte.
+ * @return this byte vector.
+ */
+ final ByteVector put11(final int byteValue1, final int byteValue2) {
+ int currentLength = length;
+ if (currentLength + 2 > data.length) {
+ enlarge(2);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) byteValue1;
+ currentData[currentLength++] = (byte) byteValue2;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts a byte into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param b
- * a byte.
- * @return this byte vector.
- */
- public ByteVector putByte(final int b) {
- int length = this.length;
- if (length + 1 > data.length) {
- enlarge(1);
- }
- data[length++] = (byte) b;
- this.length = length;
- return this;
+ /**
+ * Puts a short into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param shortValue a short.
+ * @return this byte vector.
+ */
+ public ByteVector putShort(final int shortValue) {
+ int currentLength = length;
+ if (currentLength + 2 > data.length) {
+ enlarge(2);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) (shortValue >>> 8);
+ currentData[currentLength++] = (byte) shortValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts two bytes into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param b1
- * a byte.
- * @param b2
- * another byte.
- * @return this byte vector.
- */
- ByteVector put11(final int b1, final int b2) {
- int length = this.length;
- if (length + 2 > data.length) {
- enlarge(2);
- }
- byte[] data = this.data;
- data[length++] = (byte) b1;
- data[length++] = (byte) b2;
- this.length = length;
- return this;
+ /**
+ * Puts a byte and a short into this byte vector. The byte vector is automatically enlarged if
+ * necessary.
+ *
+ * @param byteValue a byte.
+ * @param shortValue a short.
+ * @return this byte vector.
+ */
+ final ByteVector put12(final int byteValue, final int shortValue) {
+ int currentLength = length;
+ if (currentLength + 3 > data.length) {
+ enlarge(3);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) byteValue;
+ currentData[currentLength++] = (byte) (shortValue >>> 8);
+ currentData[currentLength++] = (byte) shortValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts a short into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param s
- * a short.
- * @return this byte vector.
- */
- public ByteVector putShort(final int s) {
- int length = this.length;
- if (length + 2 > data.length) {
- enlarge(2);
- }
- byte[] data = this.data;
- data[length++] = (byte) (s >>> 8);
- data[length++] = (byte) s;
- this.length = length;
- return this;
+ /**
+ * Puts two bytes and a short into this byte vector. The byte vector is automatically enlarged if
+ * necessary.
+ *
+ * @param byteValue1 a byte.
+ * @param byteValue2 another byte.
+ * @param shortValue a short.
+ * @return this byte vector.
+ */
+ final ByteVector put112(final int byteValue1, final int byteValue2, final int shortValue) {
+ int currentLength = length;
+ if (currentLength + 4 > data.length) {
+ enlarge(4);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) byteValue1;
+ currentData[currentLength++] = (byte) byteValue2;
+ currentData[currentLength++] = (byte) (shortValue >>> 8);
+ currentData[currentLength++] = (byte) shortValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts a byte and a short into this byte vector. The byte vector is
- * automatically enlarged if necessary.
- *
- * @param b
- * a byte.
- * @param s
- * a short.
- * @return this byte vector.
- */
- ByteVector put12(final int b, final int s) {
- int length = this.length;
- if (length + 3 > data.length) {
- enlarge(3);
- }
- byte[] data = this.data;
- data[length++] = (byte) b;
- data[length++] = (byte) (s >>> 8);
- data[length++] = (byte) s;
- this.length = length;
- return this;
+ /**
+ * Puts an int into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param intValue an int.
+ * @return this byte vector.
+ */
+ public ByteVector putInt(final int intValue) {
+ int currentLength = length;
+ if (currentLength + 4 > data.length) {
+ enlarge(4);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) (intValue >>> 24);
+ currentData[currentLength++] = (byte) (intValue >>> 16);
+ currentData[currentLength++] = (byte) (intValue >>> 8);
+ currentData[currentLength++] = (byte) intValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts an int into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param i
- * an int.
- * @return this byte vector.
- */
- public ByteVector putInt(final int i) {
- int length = this.length;
- if (length + 4 > data.length) {
- enlarge(4);
- }
- byte[] data = this.data;
- data[length++] = (byte) (i >>> 24);
- data[length++] = (byte) (i >>> 16);
- data[length++] = (byte) (i >>> 8);
- data[length++] = (byte) i;
- this.length = length;
- return this;
+ /**
+ * Puts one byte and two shorts into this byte vector. The byte vector is automatically enlarged
+ * if necessary.
+ *
+ * @param byteValue a byte.
+ * @param shortValue1 a short.
+ * @param shortValue2 another short.
+ * @return this byte vector.
+ */
+ final ByteVector put122(final int byteValue, final int shortValue1, final int shortValue2) {
+ int currentLength = length;
+ if (currentLength + 5 > data.length) {
+ enlarge(5);
}
+ byte[] currentData = data;
+ currentData[currentLength++] = (byte) byteValue;
+ currentData[currentLength++] = (byte) (shortValue1 >>> 8);
+ currentData[currentLength++] = (byte) shortValue1;
+ currentData[currentLength++] = (byte) (shortValue2 >>> 8);
+ currentData[currentLength++] = (byte) shortValue2;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts a long into this byte vector. The byte vector is automatically
- * enlarged if necessary.
- *
- * @param l
- * a long.
- * @return this byte vector.
- */
- public ByteVector putLong(final long l) {
- int length = this.length;
- if (length + 8 > data.length) {
- enlarge(8);
- }
- byte[] data = this.data;
- int i = (int) (l >>> 32);
- data[length++] = (byte) (i >>> 24);
- data[length++] = (byte) (i >>> 16);
- data[length++] = (byte) (i >>> 8);
- data[length++] = (byte) i;
- i = (int) l;
- data[length++] = (byte) (i >>> 24);
- data[length++] = (byte) (i >>> 16);
- data[length++] = (byte) (i >>> 8);
- data[length++] = (byte) i;
- this.length = length;
- return this;
+ /**
+ * Puts a long into this byte vector. The byte vector is automatically enlarged if necessary.
+ *
+ * @param longValue a long.
+ * @return this byte vector.
+ */
+ public ByteVector putLong(final long longValue) {
+ int currentLength = length;
+ if (currentLength + 8 > data.length) {
+ enlarge(8);
}
+ byte[] currentData = data;
+ int intValue = (int) (longValue >>> 32);
+ currentData[currentLength++] = (byte) (intValue >>> 24);
+ currentData[currentLength++] = (byte) (intValue >>> 16);
+ currentData[currentLength++] = (byte) (intValue >>> 8);
+ currentData[currentLength++] = (byte) intValue;
+ intValue = (int) longValue;
+ currentData[currentLength++] = (byte) (intValue >>> 24);
+ currentData[currentLength++] = (byte) (intValue >>> 16);
+ currentData[currentLength++] = (byte) (intValue >>> 8);
+ currentData[currentLength++] = (byte) intValue;
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts an UTF8 string into this byte vector. The byte vector is
- * automatically enlarged if necessary.
- *
- * @param s
- * a String whose UTF8 encoded length must be less than 65536.
- * @return this byte vector.
- */
- public ByteVector putUTF8(final String s) {
- int charLength = s.length();
- if (charLength > 65535) {
- throw new IllegalArgumentException();
- }
- int len = length;
- if (len + 2 + charLength > data.length) {
- enlarge(2 + charLength);
- }
- byte[] data = this.data;
- // optimistic algorithm: instead of computing the byte length and then
- // serializing the string (which requires two loops), we assume the byte
- // length is equal to char length (which is the most frequent case), and
- // we start serializing the string right away. During the serialization,
- // if we find that this assumption is wrong, we continue with the
- // general method.
- data[len++] = (byte) (charLength >>> 8);
- data[len++] = (byte) charLength;
- for (int i = 0; i < charLength; ++i) {
- char c = s.charAt(i);
- if (c >= '\001' && c <= '\177') {
- data[len++] = (byte) c;
- } else {
- int byteLength = i;
- for (int j = i; j < charLength; ++j) {
- c = s.charAt(j);
- if (c >= '\001' && c <= '\177') {
- byteLength++;
- } else if (c > '\u07FF') {
- byteLength += 3;
- } else {
- byteLength += 2;
- }
- }
- if (byteLength > 65535) {
- throw new IllegalArgumentException();
- }
- data[length] = (byte) (byteLength >>> 8);
- data[length + 1] = (byte) byteLength;
- if (length + 2 + byteLength > data.length) {
- length = len;
- enlarge(2 + byteLength);
- data = this.data;
- }
- for (int j = i; j < charLength; ++j) {
- c = s.charAt(j);
- if (c >= '\001' && c <= '\177') {
- data[len++] = (byte) c;
- } else if (c > '\u07FF') {
- data[len++] = (byte) (0xE0 | c >> 12 & 0xF);
- data[len++] = (byte) (0x80 | c >> 6 & 0x3F);
- data[len++] = (byte) (0x80 | c & 0x3F);
- } else {
- data[len++] = (byte) (0xC0 | c >> 6 & 0x1F);
- data[len++] = (byte) (0x80 | c & 0x3F);
- }
- }
- break;
- }
- }
- length = len;
- return this;
+ /**
+ * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if
+ * necessary.
+ *
+ * @param stringValue a String whose UTF8 encoded length must be less than 65536.
+ * @return this byte vector.
+ */
+ // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
+ public ByteVector putUTF8(final String stringValue) {
+ int charLength = stringValue.length();
+ if (charLength > 65535) {
+ throw new IllegalArgumentException("UTF8 string too large");
+ }
+ int currentLength = length;
+ if (currentLength + 2 + charLength > data.length) {
+ enlarge(2 + charLength);
}
+ byte[] currentData = data;
+ // Optimistic algorithm: instead of computing the byte length and then serializing the string
+ // (which requires two loops), we assume the byte length is equal to char length (which is the
+ // most frequent case), and we start serializing the string right away. During the
+ // serialization, if we find that this assumption is wrong, we continue with the general method.
+ currentData[currentLength++] = (byte) (charLength >>> 8);
+ currentData[currentLength++] = (byte) charLength;
+ for (int i = 0; i < charLength; ++i) {
+ char charValue = stringValue.charAt(i);
+ if (charValue >= '\u0001' && charValue <= '\u007F') {
+ currentData[currentLength++] = (byte) charValue;
+ } else {
+ length = currentLength;
+ return encodeUtf8(stringValue, i, 65535);
+ }
+ }
+ length = currentLength;
+ return this;
+ }
+
+ /**
+ * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if
+ * necessary. The string length is encoded in two bytes before the encoded characters, if there is
+ * space for that (i.e. if this.length - offset - 2 >= 0).
+ *
+ * @param stringValue the String to encode.
+ * @param offset the index of the first character to encode. The previous characters are supposed
+ * to have already been encoded, using only one byte per character.
+ * @param maxByteLength the maximum byte length of the encoded string, including the already
+ * encoded characters.
+ * @return this byte vector.
+ */
+ final ByteVector encodeUtf8(final String stringValue, final int offset, final int maxByteLength) {
+ int charLength = stringValue.length();
+ int byteLength = offset;
+ for (int i = offset; i < charLength; ++i) {
+ char charValue = stringValue.charAt(i);
+ if (charValue >= 0x0001 && charValue <= 0x007F) {
+ byteLength++;
+ } else if (charValue <= 0x07FF) {
+ byteLength += 2;
+ } else {
+ byteLength += 3;
+ }
+ }
+ if (byteLength > maxByteLength) {
+ throw new IllegalArgumentException("UTF8 string too large");
+ }
+ // Compute where 'byteLength' must be stored in 'data', and store it at this location.
+ int byteLengthOffset = length - offset - 2;
+ if (byteLengthOffset >= 0) {
+ data[byteLengthOffset] = (byte) (byteLength >>> 8);
+ data[byteLengthOffset + 1] = (byte) byteLength;
+ }
+ if (length + byteLength - offset > data.length) {
+ enlarge(byteLength - offset);
+ }
+ int currentLength = length;
+ for (int i = offset; i < charLength; ++i) {
+ char charValue = stringValue.charAt(i);
+ if (charValue >= 0x0001 && charValue <= 0x007F) {
+ data[currentLength++] = (byte) charValue;
+ } else if (charValue <= 0x07FF) {
+ data[currentLength++] = (byte) (0xC0 | charValue >> 6 & 0x1F);
+ data[currentLength++] = (byte) (0x80 | charValue & 0x3F);
+ } else {
+ data[currentLength++] = (byte) (0xE0 | charValue >> 12 & 0xF);
+ data[currentLength++] = (byte) (0x80 | charValue >> 6 & 0x3F);
+ data[currentLength++] = (byte) (0x80 | charValue & 0x3F);
+ }
+ }
+ length = currentLength;
+ return this;
+ }
- /**
- * Puts an array of bytes into this byte vector. The byte vector is
- * automatically enlarged if necessary.
- *
- * @param b
- * an array of bytes. May be null to put len
- * null bytes into this byte vector.
- * @param off
- * index of the fist byte of b that must be copied.
- * @param len
- * number of bytes of b that must be copied.
- * @return this byte vector.
- */
- public ByteVector putByteArray(final byte[] b, final int off, final int len) {
- if (length + len > data.length) {
- enlarge(len);
- }
- if (b != null) {
- System.arraycopy(b, off, data, length, len);
- }
- length += len;
- return this;
+ /**
+ * Puts an array of bytes into this byte vector. The byte vector is automatically enlarged if
+ * necessary.
+ *
+ * @param byteArrayValue an array of bytes. May be {@literal null} to put {@code byteLength} null
+ * bytes into this byte vector.
+ * @param byteOffset index of the first byte of byteArrayValue that must be copied.
+ * @param byteLength number of bytes of byteArrayValue that must be copied.
+ * @return this byte vector.
+ */
+ public ByteVector putByteArray(
+ final byte[] byteArrayValue, final int byteOffset, final int byteLength) {
+ if (length + byteLength > data.length) {
+ enlarge(byteLength);
+ }
+ if (byteArrayValue != null) {
+ System.arraycopy(byteArrayValue, byteOffset, data, length, byteLength);
}
+ length += byteLength;
+ return this;
+ }
- /**
- * Enlarge this byte vector so that it can receive n more bytes.
- *
- * @param size
- * number of additional bytes that this byte vector should be
- * able to receive.
- */
- private void enlarge(final int size) {
- int length1 = 2 * data.length;
- int length2 = length + size;
- byte[] newData = new byte[length1 > length2 ? length1 : length2];
- System.arraycopy(data, 0, newData, 0, length);
- data = newData;
+ /**
+ * Enlarges this byte vector so that it can receive 'size' more bytes.
+ *
+ * @param size number of additional bytes that this byte vector should be able to receive.
+ */
+ private void enlarge(final int size) {
+ if (length > data.length) {
+ throw new AssertionError("Internal error");
}
+ int doubleCapacity = 2 * data.length;
+ int minimalCapacity = length + size;
+ byte[] newData = new byte[doubleCapacity > minimalCapacity ? doubleCapacity : minimalCapacity];
+ System.arraycopy(data, 0, newData, 0, length);
+ data = newData;
+ }
}
diff --git a/src/java/nginx/clojure/asm/ClassReader.java b/src/java/nginx/clojure/asm/ClassReader.java
index 96a49860..8a3aeb20 100644
--- a/src/java/nginx/clojure/asm/ClassReader.java
+++ b/src/java/nginx/clojure/asm/ClassReader.java
@@ -1,2202 +1,3852 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
- * A Java class parser to make a {@link ClassVisitor} visit an existing class.
- * This class parses a byte array conforming to the Java class file format and
- * calls the appropriate visit methods of a given class visitor for each field,
- * method and bytecode instruction encountered.
- *
+ * A parser to make a {@link ClassVisitor} visit a ClassFile structure, as defined in the Java
+ * Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the
+ * appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode
+ * instruction encountered.
+ *
+ * @see JVMS 4
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public class ClassReader {
- /**
- * True to enable signatures support.
+ /**
+ * A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed
+ * nor visited.
+ */
+ public static final int SKIP_CODE = 1;
+
+ /**
+ * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable,
+ * LocalVariableTypeTable, LineNumberTable and MethodParameters attributes. If this flag is set
+ * these attributes are neither parsed nor visited (i.e. {@link ClassVisitor#visitSource}, {@link
+ * MethodVisitor#visitLocalVariable}, {@link MethodVisitor#visitLineNumber} and {@link
+ * MethodVisitor#visitParameter} are not called).
+ */
+ public static final int SKIP_DEBUG = 2;
+
+ /**
+ * A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes
+ * are neither parsed nor visited (i.e. {@link MethodVisitor#visitFrame} is not called). This flag
+ * is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames
+ * that will be ignored and recomputed from scratch.
+ */
+ public static final int SKIP_FRAMES = 4;
+
+ /**
+ * A flag to expand the stack map frames. By default stack map frames are visited in their
+ * original format (i.e. "expanded" for classes whose version is less than V1_6, and "compressed"
+ * for the other classes). If this flag is set, stack map frames are always visited in expanded
+ * format (this option adds a decompression/compression step in ClassReader and ClassWriter which
+ * degrades performance quite a lot).
+ */
+ public static final int EXPAND_FRAMES = 8;
+
+ /**
+ * A flag to expand the ASM specific instructions into an equivalent sequence of standard bytecode
+ * instructions. When resolving a forward jump it may happen that the signed 2 bytes offset
+ * reserved for it is not sufficient to store the bytecode offset. In this case the jump
+ * instruction is replaced with a temporary ASM specific instruction using an unsigned 2 bytes
+ * offset (see {@link Label#resolve}). This internal flag is used to re-read classes containing
+ * such instructions, in order to replace them with standard instructions. In addition, when this
+ * flag is used, goto_w and jsr_w are not converted into goto and jsr, to make sure that
+ * infinite loops where a goto_w is replaced with a goto in ClassReader and converted back to a
+ * goto_w in ClassWriter cannot occur.
+ */
+ static final int EXPAND_ASM_INSNS = 256;
+
+ /** The maximum size of array to allocate. */
+ private static final int MAX_BUFFER_SIZE = 1024 * 1024;
+
+ /** The size of the temporary byte array used to read class input streams chunk by chunk. */
+ private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
+
+ /**
+ * A byte array containing the JVMS ClassFile structure to be parsed.
+ *
+ * @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will
+ * eventually be deleted.
+ */
+ @Deprecated
+ // DontCheck(MemberName): can't be renamed (for backward binary compatibility).
+ public final byte[] b;
+
+ /** The offset in bytes of the ClassFile's access_flags field. */
+ public final int header;
+
+ /**
+ * A byte array containing the JVMS ClassFile structure to be parsed. The content of this array
+ * must not be modified. This field is intended for {@link Attribute} sub classes, and is normally
+ * not needed by class visitors.
+ *
+ *
NOTE: the ClassFile structure can start at any offset within this array, i.e. it does not
+ * necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct
+ * ClassFile element offsets within this byte array.
+ */
+ final byte[] classFileBuffer;
+
+ /**
+ * The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's
+ * constant_pool array, plus one. In other words, the offset of constant pool entry i is
+ * given by cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] -
+ * 1].
+ */
+ private final int[] cpInfoOffsets;
+
+ /**
+ * The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids
+ * multiple parsing of a given CONSTANT_Utf8 constant pool item.
+ */
+ private final String[] constantUtf8Values;
+
+ /**
+ * The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This
+ * cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item.
+ */
+ private final ConstantDynamic[] constantDynamicValues;
+
+ /**
+ * The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array
+ * (in the BootstrapMethods attribute).
+ *
+ * @see JVMS
+ * 4.7.23
+ */
+ private final int[] bootstrapMethodOffsets;
+
+ /**
+ * A conservative estimate of the maximum length of the strings contained in the constant pool of
+ * the class.
+ */
+ private final int maxStringLength;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param classFile the JVMS ClassFile structure to be read.
+ */
+ public ClassReader(final byte[] classFile) {
+ this(classFile, 0, classFile.length);
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read.
+ * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read.
+ * @param classFileLength the length in bytes of the ClassFile to be read.
+ */
+ public ClassReader(
+ final byte[] classFileBuffer,
+ final int classFileOffset,
+ final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility.
+ this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true);
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object. This internal constructor must not be exposed
+ * as a public API.
+ *
+ * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read.
+ * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read.
+ * @param checkClassVersion whether to check the class version or not.
+ */
+ ClassReader(
+ final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) {
+ this.classFileBuffer = classFileBuffer;
+ this.b = classFileBuffer;
+ // Check the class' major_version. This field is after the magic and minor_version fields, which
+ // use 4 and 2 bytes respectively.
+ if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V20) {
+ throw new IllegalArgumentException(
+ "Unsupported class file major version " + readShort(classFileOffset + 6));
+ }
+ // Create the constant pool arrays. The constant_pool_count field is after the magic,
+ // minor_version and major_version fields, which use 4, 2 and 2 bytes respectively.
+ int constantPoolCount = readUnsignedShort(classFileOffset + 8);
+ cpInfoOffsets = new int[constantPoolCount];
+ constantUtf8Values = new String[constantPoolCount];
+ // Compute the offset of each constant pool entry, as well as a conservative estimate of the
+ // maximum length of the constant pool strings. The first constant pool entry is after the
+ // magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2
+ // bytes respectively.
+ int currentCpInfoIndex = 1;
+ int currentCpInfoOffset = classFileOffset + 10;
+ int currentMaxStringLength = 0;
+ boolean hasBootstrapMethods = false;
+ boolean hasConstantDynamic = false;
+ // The offset of the other entries depend on the total size of all the previous entries.
+ while (currentCpInfoIndex < constantPoolCount) {
+ cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1;
+ int cpInfoSize;
+ switch (classFileBuffer[currentCpInfoOffset]) {
+ case Symbol.CONSTANT_FIELDREF_TAG:
+ case Symbol.CONSTANT_METHODREF_TAG:
+ case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
+ case Symbol.CONSTANT_INTEGER_TAG:
+ case Symbol.CONSTANT_FLOAT_TAG:
+ case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
+ cpInfoSize = 5;
+ break;
+ case Symbol.CONSTANT_DYNAMIC_TAG:
+ cpInfoSize = 5;
+ hasBootstrapMethods = true;
+ hasConstantDynamic = true;
+ break;
+ case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
+ cpInfoSize = 5;
+ hasBootstrapMethods = true;
+ break;
+ case Symbol.CONSTANT_LONG_TAG:
+ case Symbol.CONSTANT_DOUBLE_TAG:
+ cpInfoSize = 9;
+ currentCpInfoIndex++;
+ break;
+ case Symbol.CONSTANT_UTF8_TAG:
+ cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1);
+ if (cpInfoSize > currentMaxStringLength) {
+ // The size in bytes of this CONSTANT_Utf8 structure provides a conservative estimate
+ // of the length in characters of the corresponding string, and is much cheaper to
+ // compute than this exact length.
+ currentMaxStringLength = cpInfoSize;
+ }
+ break;
+ case Symbol.CONSTANT_METHOD_HANDLE_TAG:
+ cpInfoSize = 4;
+ break;
+ case Symbol.CONSTANT_CLASS_TAG:
+ case Symbol.CONSTANT_STRING_TAG:
+ case Symbol.CONSTANT_METHOD_TYPE_TAG:
+ case Symbol.CONSTANT_PACKAGE_TAG:
+ case Symbol.CONSTANT_MODULE_TAG:
+ cpInfoSize = 3;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ currentCpInfoOffset += cpInfoSize;
+ }
+ maxStringLength = currentMaxStringLength;
+ // The Classfile's access_flags field is just after the last constant pool entry.
+ header = currentCpInfoOffset;
+
+ // Allocate the cache of ConstantDynamic values, if there is at least one.
+ constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null;
+
+ // Read the BootstrapMethods attribute, if any (only get the offset of each method).
+ bootstrapMethodOffsets =
+ hasBootstrapMethods ? readBootstrapMethodsAttribute(currentMaxStringLength) : null;
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param inputStream an input stream of the JVMS ClassFile structure to be read. This input
+ * stream must contain nothing more than the ClassFile structure itself. It is read from its
+ * current position to its end.
+ * @throws IOException if a problem occurs during reading.
+ */
+ public ClassReader(final InputStream inputStream) throws IOException {
+ this(readStream(inputStream, false));
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param className the fully qualified name of the class to be read. The ClassFile structure is
+ * retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}.
+ * @throws IOException if an exception occurs during reading.
+ */
+ public ClassReader(final String className) throws IOException {
+ this(
+ readStream(
+ ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true));
+ }
+
+ /**
+ * Reads the given input stream and returns its content as a byte array.
+ *
+ * @param inputStream an input stream.
+ * @param close true to close the input stream after reading.
+ * @return the content of the given input stream.
+ * @throws IOException if a problem occurs during reading.
+ */
+ @SuppressWarnings("PMD.UseTryWithResources")
+ private static byte[] readStream(final InputStream inputStream, final boolean close)
+ throws IOException {
+ if (inputStream == null) {
+ throw new IOException("Class not found");
+ }
+ int bufferSize = computeBufferSize(inputStream);
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ byte[] data = new byte[bufferSize];
+ int bytesRead;
+ int readCount = 0;
+ while ((bytesRead = inputStream.read(data, 0, bufferSize)) != -1) {
+ outputStream.write(data, 0, bytesRead);
+ readCount++;
+ }
+ outputStream.flush();
+ if (readCount == 1) {
+ return data;
+ }
+ return outputStream.toByteArray();
+ } finally {
+ if (close) {
+ inputStream.close();
+ }
+ }
+ }
+
+ private static int computeBufferSize(final InputStream inputStream) throws IOException {
+ int expectedLength = inputStream.available();
+ /*
+ * Some implementations can return 0 while holding available data (e.g. new
+ * FileInputStream("/proc/a_file")). Also in some pathological cases a very small number might
+ * be returned, and in this case we use a default size.
*/
- static final boolean SIGNATURES = true;
+ if (expectedLength < 256) {
+ return INPUT_STREAM_DATA_CHUNK_SIZE;
+ }
+ return Math.min(expectedLength, MAX_BUFFER_SIZE);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the class's access flags (see {@link Opcodes}). This value may not reflect Deprecated
+ * and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes.
+ *
+ * @return the class access flags.
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public int getAccess() {
+ return readUnsignedShort(header);
+ }
+
+ /**
+ * Returns the internal name of the class (see {@link Type#getInternalName()}).
+ *
+ * @return the internal class name.
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String getClassName() {
+ // this_class is just after the access_flags field (using 2 bytes).
+ return readClass(header + 2, new char[maxStringLength]);
+ }
+
+ /**
+ * Returns the internal name of the super class (see {@link Type#getInternalName()}). For
+ * interfaces, the super class is {@link Object}.
+ *
+ * @return the internal name of the super class, or {@literal null} for {@link Object} class.
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String getSuperName() {
+ // super_class is after the access_flags and this_class fields (2 bytes each).
+ return readClass(header + 4, new char[maxStringLength]);
+ }
+
+ /**
+ * Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}).
+ *
+ * @return the internal names of the directly implemented interfaces. Inherited implemented
+ * interfaces are not returned.
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String[] getInterfaces() {
+ // interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each).
+ int currentOffset = header + 6;
+ int interfacesCount = readUnsignedShort(currentOffset);
+ String[] interfaces = new String[interfacesCount];
+ if (interfacesCount > 0) {
+ char[] charBuffer = new char[maxStringLength];
+ for (int i = 0; i < interfacesCount; ++i) {
+ currentOffset += 2;
+ interfaces[i] = readClass(currentOffset, charBuffer);
+ }
+ }
+ return interfaces;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Public methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
+ * {@link ClassReader}.
+ *
+ * @param classVisitor the visitor that must visit this class.
+ * @param parsingOptions the options to use to parse this class. One or more of {@link
+ * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
+ */
+ public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
+ accept(classVisitor, new Attribute[0], parsingOptions);
+ }
+
+ /**
+ * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this
+ * {@link ClassReader}.
+ *
+ * @param classVisitor the visitor that must visit this class.
+ * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of
+ * the class. Any attribute whose type is not equal to the type of one the prototypes will not
+ * be parsed: its byte array value will be passed unchanged to the ClassWriter. This may
+ * corrupt it if this value contains references to the constant pool, or has syntactic or
+ * semantic links with a class element that has been transformed by a class adapter between
+ * the reader and the writer.
+ * @param parsingOptions the options to use to parse this class. One or more of {@link
+ * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}.
+ */
+ public void accept(
+ final ClassVisitor classVisitor,
+ final Attribute[] attributePrototypes,
+ final int parsingOptions) {
+ Context context = new Context();
+ context.attributePrototypes = attributePrototypes;
+ context.parsingOptions = parsingOptions;
+ context.charBuffer = new char[maxStringLength];
+
+ // Read the access_flags, this_class, super_class, interface_count and interfaces fields.
+ char[] charBuffer = context.charBuffer;
+ int currentOffset = header;
+ int accessFlags = readUnsignedShort(currentOffset);
+ String thisClass = readClass(currentOffset + 2, charBuffer);
+ String superClass = readClass(currentOffset + 4, charBuffer);
+ String[] interfaces = new String[readUnsignedShort(currentOffset + 6)];
+ currentOffset += 8;
+ for (int i = 0; i < interfaces.length; ++i) {
+ interfaces[i] = readClass(currentOffset, charBuffer);
+ currentOffset += 2;
+ }
- /**
- * True to enable annotations support.
- */
- static final boolean ANNOTATIONS = true;
+ // Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS).
+ // Attribute offsets exclude the attribute_name_index and attribute_length fields.
+ // - The offset of the InnerClasses attribute, or 0.
+ int innerClassesOffset = 0;
+ // - The offset of the EnclosingMethod attribute, or 0.
+ int enclosingMethodOffset = 0;
+ // - The string corresponding to the Signature attribute, or null.
+ String signature = null;
+ // - The string corresponding to the SourceFile attribute, or null.
+ String sourceFile = null;
+ // - The string corresponding to the SourceDebugExtension attribute, or null.
+ String sourceDebugExtension = null;
+ // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
+ int runtimeVisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
+ int runtimeInvisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
+ int runtimeVisibleTypeAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
+ int runtimeInvisibleTypeAnnotationsOffset = 0;
+ // - The offset of the Module attribute, or 0.
+ int moduleOffset = 0;
+ // - The offset of the ModulePackages attribute, or 0.
+ int modulePackagesOffset = 0;
+ // - The string corresponding to the ModuleMainClass attribute, or null.
+ String moduleMainClass = null;
+ // - The string corresponding to the NestHost attribute, or null.
+ String nestHostClass = null;
+ // - The offset of the NestMembers attribute, or 0.
+ int nestMembersOffset = 0;
+ // - The offset of the PermittedSubclasses attribute, or 0
+ int permittedSubclassesOffset = 0;
+ // - The offset of the Record attribute, or 0.
+ int recordOffset = 0;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int currentAttributeOffset = getFirstAttributeOffset();
+ for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentAttributeOffset, charBuffer);
+ int attributeLength = readInt(currentAttributeOffset + 2);
+ currentAttributeOffset += 6;
+ // The tests are sorted in decreasing frequency order (based on frequencies observed on
+ // typical classes).
+ if (Constants.SOURCE_FILE.equals(attributeName)) {
+ sourceFile = readUTF8(currentAttributeOffset, charBuffer);
+ } else if (Constants.INNER_CLASSES.equals(attributeName)) {
+ innerClassesOffset = currentAttributeOffset;
+ } else if (Constants.ENCLOSING_METHOD.equals(attributeName)) {
+ enclosingMethodOffset = currentAttributeOffset;
+ } else if (Constants.NEST_HOST.equals(attributeName)) {
+ nestHostClass = readClass(currentAttributeOffset, charBuffer);
+ } else if (Constants.NEST_MEMBERS.equals(attributeName)) {
+ nestMembersOffset = currentAttributeOffset;
+ } else if (Constants.PERMITTED_SUBCLASSES.equals(attributeName)) {
+ permittedSubclassesOffset = currentAttributeOffset;
+ } else if (Constants.SIGNATURE.equals(attributeName)) {
+ signature = readUTF8(currentAttributeOffset, charBuffer);
+ } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.DEPRECATED.equals(attributeName)) {
+ accessFlags |= Opcodes.ACC_DEPRECATED;
+ } else if (Constants.SYNTHETIC.equals(attributeName)) {
+ accessFlags |= Opcodes.ACC_SYNTHETIC;
+ } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
+ if (attributeLength > classFileBuffer.length - currentAttributeOffset) {
+ throw new IllegalArgumentException();
+ }
+ sourceDebugExtension =
+ readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
+ } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset;
+ } else if (Constants.RECORD.equals(attributeName)) {
+ recordOffset = currentAttributeOffset;
+ accessFlags |= Opcodes.ACC_RECORD;
+ } else if (Constants.MODULE.equals(attributeName)) {
+ moduleOffset = currentAttributeOffset;
+ } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) {
+ moduleMainClass = readClass(currentAttributeOffset, charBuffer);
+ } else if (Constants.MODULE_PACKAGES.equals(attributeName)) {
+ modulePackagesOffset = currentAttributeOffset;
+ } else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
+ // The BootstrapMethods attribute is read in the constructor.
+ Attribute attribute =
+ readAttribute(
+ attributePrototypes,
+ attributeName,
+ currentAttributeOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentAttributeOffset += attributeLength;
+ }
- /**
- * True to enable stack map frames support.
- */
- static final boolean FRAMES = true;
+ // Visit the class declaration. The minor_version and major_version fields start 6 bytes before
+ // the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition).
+ classVisitor.visit(
+ readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces);
- /**
- * True to enable bytecode writing support.
- */
- static final boolean WRITER = true;
+ // Visit the SourceFile and SourceDebugExtenstion attributes.
+ if ((parsingOptions & SKIP_DEBUG) == 0
+ && (sourceFile != null || sourceDebugExtension != null)) {
+ classVisitor.visitSource(sourceFile, sourceDebugExtension);
+ }
- /**
- * True to enable JSR_W and GOTO_W support.
- */
- static final boolean RESIZE = true;
+ // Visit the Module, ModulePackages and ModuleMainClass attributes.
+ if (moduleOffset != 0) {
+ readModuleAttributes(
+ classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass);
+ }
- /**
- * Flag to skip method code. If this class is set CODE
- * attribute won't be visited. This can be used, for example, to retrieve
- * annotations for methods and method parameters.
- */
- public static final int SKIP_CODE = 1;
-
- /**
- * Flag to skip the debug information in the class. If this flag is set the
- * debug information of the class is not visited, i.e. the
- * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
- * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
- * called.
- */
- public static final int SKIP_DEBUG = 2;
-
- /**
- * Flag to skip the stack map frames in the class. If this flag is set the
- * stack map frames of the class is not visited, i.e. the
- * {@link MethodVisitor#visitFrame visitFrame} method will not be called.
- * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is
- * used: it avoids visiting frames that will be ignored and recomputed from
- * scratch in the class writer.
- */
- public static final int SKIP_FRAMES = 4;
-
- /**
- * Flag to expand the stack map frames. By default stack map frames are
- * visited in their original format (i.e. "expanded" for classes whose
- * version is less than V1_6, and "compressed" for the other classes). If
- * this flag is set, stack map frames are always visited in expanded format
- * (this option adds a decompression/recompression step in ClassReader and
- * ClassWriter which degrades performances quite a lot).
- */
- public static final int EXPAND_FRAMES = 8;
+ // Visit the NestHost attribute.
+ if (nestHostClass != null) {
+ classVisitor.visitNestHost(nestHostClass);
+ }
- /**
- * The class to be parsed. The content of this array must not be
- * modified. This field is intended for {@link Attribute} sub classes, and
- * is normally not needed by class generators or adapters.
- */
- public final byte[] b;
+ // Visit the EnclosingMethod attribute.
+ if (enclosingMethodOffset != 0) {
+ String className = readClass(enclosingMethodOffset, charBuffer);
+ int methodIndex = readUnsignedShort(enclosingMethodOffset + 2);
+ String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer);
+ String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer);
+ classVisitor.visitOuterClass(className, name, type);
+ }
- /**
- * The start index of each constant pool item in {@link #b b}, plus one. The
- * one byte offset skips the constant pool item tag that indicates its type.
- */
- private final int[] items;
-
- /**
- * The String objects corresponding to the CONSTANT_Utf8 items. This cache
- * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
- * which GREATLY improves performances (by a factor 2 to 3). This caching
- * strategy could be extended to all constant pool items, but its benefit
- * would not be so great for these items (because they are much less
- * expensive to parse than CONSTANT_Utf8 items).
- */
- private final String[] strings;
+ // Visit the RuntimeVisibleAnnotations attribute.
+ if (runtimeVisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- /**
- * Maximum length of the strings contained in the constant pool of the
- * class.
- */
- private final int maxStringLength;
+ // Visit the RuntimeInvisibleAnnotations attribute.
+ if (runtimeInvisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- /**
- * Start index of the class header information (access, name...) in
- * {@link #b b}.
- */
- public final int header;
+ // Visit the RuntimeVisibleTypeAnnotations attribute.
+ if (runtimeVisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ classVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // ------------------------------------------------------------------------
- // Constructors
- // ------------------------------------------------------------------------
+ // Visit the RuntimeInvisibleTypeAnnotations attribute.
+ if (runtimeInvisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ classVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- /**
- * Constructs a new {@link ClassReader} object.
- *
- * @param b
- * the bytecode of the class to be read.
- */
- public ClassReader(final byte[] b) {
- this(b, 0, b.length);
- }
-
- /**
- * Constructs a new {@link ClassReader} object.
- *
- * @param b
- * the bytecode of the class to be read.
- * @param off
- * the start offset of the class data.
- * @param len
- * the length of the class data.
- */
- public ClassReader(final byte[] b, final int off, final int len) {
- this.b = b;
- // checks the class version
- if (readShort(off + 6) > Opcodes.V1_8) {
- throw new IllegalArgumentException();
- }
- // parses the constant pool
- items = new int[readUnsignedShort(off + 8)];
- int n = items.length;
- strings = new String[n];
- int max = 0;
- int index = off + 10;
- for (int i = 1; i < n; ++i) {
- items[i] = index + 1;
- int size;
- switch (b[index]) {
- case ClassWriter.FIELD:
- case ClassWriter.METH:
- case ClassWriter.IMETH:
- case ClassWriter.INT:
- case ClassWriter.FLOAT:
- case ClassWriter.NAME_TYPE:
- case ClassWriter.INDY:
- size = 5;
- break;
- case ClassWriter.LONG:
- case ClassWriter.DOUBLE:
- size = 9;
- ++i;
- break;
- case ClassWriter.UTF8:
- size = 3 + readUnsignedShort(index + 1);
- if (size > max) {
- max = size;
- }
- break;
- case ClassWriter.HANDLE:
- size = 4;
- break;
- // case ClassWriter.CLASS:
- // case ClassWriter.STR:
- // case ClassWriter.MTYPE
- default:
- size = 3;
- break;
- }
- index += size;
- }
- maxStringLength = max;
- // the class header information starts just after the constant pool
- header = index;
- }
-
- /**
- * Returns the class's access flags (see {@link Opcodes}). This value may
- * not reflect Deprecated and Synthetic flags when bytecode is before 1.5
- * and those flags are represented by attributes.
- *
- * @return the class access flags
- *
- * @see ClassVisitor#visit(int, int, String, String, String, String[])
- */
- public int getAccess() {
- return readUnsignedShort(header);
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in ClassWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ classVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
}
- /**
- * Returns the internal name of the class (see
- * {@link Type#getInternalName() getInternalName}).
- *
- * @return the internal class name
- *
- * @see ClassVisitor#visit(int, int, String, String, String, String[])
- */
- public String getClassName() {
- return readClass(header + 2, new char[maxStringLength]);
- }
-
- /**
- * Returns the internal of name of the super class (see
- * {@link Type#getInternalName() getInternalName}). For interfaces, the
- * super class is {@link Object}.
- *
- * @return the internal name of super class, or null for
- * {@link Object} class.
- *
- * @see ClassVisitor#visit(int, int, String, String, String, String[])
- */
- public String getSuperName() {
- return readClass(header + 4, new char[maxStringLength]);
- }
-
- /**
- * Returns the internal names of the class's interfaces (see
- * {@link Type#getInternalName() getInternalName}).
- *
- * @return the array of internal names for all implemented interfaces or
- * null.
- *
- * @see ClassVisitor#visit(int, int, String, String, String, String[])
- */
- public String[] getInterfaces() {
- int index = header + 6;
- int n = readUnsignedShort(index);
- String[] interfaces = new String[n];
- if (n > 0) {
- char[] buf = new char[maxStringLength];
- for (int i = 0; i < n; ++i) {
- index += 2;
- interfaces[i] = readClass(index, buf);
- }
- }
- return interfaces;
+ // Visit the NestedMembers attribute.
+ if (nestMembersOffset != 0) {
+ int numberOfNestMembers = readUnsignedShort(nestMembersOffset);
+ int currentNestMemberOffset = nestMembersOffset + 2;
+ while (numberOfNestMembers-- > 0) {
+ classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer));
+ currentNestMemberOffset += 2;
+ }
}
- /**
- * Copies the constant pool data into the given {@link ClassWriter}. Should
- * be called before the {@link #accept(ClassVisitor,int)} method.
- *
- * @param classWriter
- * the {@link ClassWriter} to copy constant pool into.
- */
- void copyPool(final ClassWriter classWriter) {
- char[] buf = new char[maxStringLength];
- int ll = items.length;
- Item[] items2 = new Item[ll];
- for (int i = 1; i < ll; i++) {
- int index = items[i];
- int tag = b[index - 1];
- Item item = new Item(i);
- int nameType;
- switch (tag) {
- case ClassWriter.FIELD:
- case ClassWriter.METH:
- case ClassWriter.IMETH:
- nameType = items[readUnsignedShort(index + 2)];
- item.set(tag, readClass(index, buf), readUTF8(nameType, buf),
- readUTF8(nameType + 2, buf));
- break;
- case ClassWriter.INT:
- item.set(readInt(index));
- break;
- case ClassWriter.FLOAT:
- item.set(Float.intBitsToFloat(readInt(index)));
- break;
- case ClassWriter.NAME_TYPE:
- item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf),
- null);
- break;
- case ClassWriter.LONG:
- item.set(readLong(index));
- ++i;
- break;
- case ClassWriter.DOUBLE:
- item.set(Double.longBitsToDouble(readLong(index)));
- ++i;
- break;
- case ClassWriter.UTF8: {
- String s = strings[i];
- if (s == null) {
- index = items[i];
- s = strings[i] = readUTF(index + 2,
- readUnsignedShort(index), buf);
- }
- item.set(tag, s, null, null);
- break;
- }
- case ClassWriter.HANDLE: {
- int fieldOrMethodRef = items[readUnsignedShort(index + 1)];
- nameType = items[readUnsignedShort(fieldOrMethodRef + 2)];
- item.set(ClassWriter.HANDLE_BASE + readByte(index),
- readClass(fieldOrMethodRef, buf),
- readUTF8(nameType, buf), readUTF8(nameType + 2, buf));
- break;
- }
- case ClassWriter.INDY:
- if (classWriter.bootstrapMethods == null) {
- copyBootstrapMethods(classWriter, items2, buf);
- }
- nameType = items[readUnsignedShort(index + 2)];
- item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf),
- readUnsignedShort(index));
- break;
- // case ClassWriter.STR:
- // case ClassWriter.CLASS:
- // case ClassWriter.MTYPE
- default:
- item.set(tag, readUTF8(index, buf), null, null);
- break;
- }
+ // Visit the PermittedSubclasses attribute.
+ if (permittedSubclassesOffset != 0) {
+ int numberOfPermittedSubclasses = readUnsignedShort(permittedSubclassesOffset);
+ int currentPermittedSubclassesOffset = permittedSubclassesOffset + 2;
+ while (numberOfPermittedSubclasses-- > 0) {
+ classVisitor.visitPermittedSubclass(
+ readClass(currentPermittedSubclassesOffset, charBuffer));
+ currentPermittedSubclassesOffset += 2;
+ }
+ }
- int index2 = item.hashCode % items2.length;
- item.next = items2[index2];
- items2[index2] = item;
- }
+ // Visit the InnerClasses attribute.
+ if (innerClassesOffset != 0) {
+ int numberOfClasses = readUnsignedShort(innerClassesOffset);
+ int currentClassesOffset = innerClassesOffset + 2;
+ while (numberOfClasses-- > 0) {
+ classVisitor.visitInnerClass(
+ readClass(currentClassesOffset, charBuffer),
+ readClass(currentClassesOffset + 2, charBuffer),
+ readUTF8(currentClassesOffset + 4, charBuffer),
+ readUnsignedShort(currentClassesOffset + 6));
+ currentClassesOffset += 8;
+ }
+ }
- int off = items[1] - 1;
- classWriter.pool.putByteArray(b, off, header - off);
- classWriter.items = items2;
- classWriter.threshold = (int) (0.75d * ll);
- classWriter.index = ll;
+ // Visit Record components.
+ if (recordOffset != 0) {
+ int recordComponentsCount = readUnsignedShort(recordOffset);
+ recordOffset += 2;
+ while (recordComponentsCount-- > 0) {
+ recordOffset = readRecordComponent(classVisitor, context, recordOffset);
+ }
}
- /**
- * Copies the bootstrap method data into the given {@link ClassWriter}.
- * Should be called before the {@link #accept(ClassVisitor,int)} method.
- *
- * @param classWriter
- * the {@link ClassWriter} to copy bootstrap methods into.
- */
- private void copyBootstrapMethods(final ClassWriter classWriter,
- final Item[] items, final char[] c) {
- // finds the "BootstrapMethods" attribute
- int u = getAttributes();
- boolean found = false;
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- if ("BootstrapMethods".equals(attrName)) {
- found = true;
- break;
- }
- u += 6 + readInt(u + 4);
- }
- if (!found) {
- return;
- }
- // copies the bootstrap methods in the class writer
- int boostrapMethodCount = readUnsignedShort(u + 8);
- for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
- int position = v - u - 10;
- int hashCode = readConst(readUnsignedShort(v), c).hashCode();
- for (int k = readUnsignedShort(v + 2); k > 0; --k) {
- hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
- v += 2;
- }
- v += 4;
- Item item = new Item(j);
- item.set(position, hashCode & 0x7FFFFFFF);
- int index = item.hashCode % items.length;
- item.next = items[index];
- items[index] = item;
- }
- int attrSize = readInt(u + 4);
- ByteVector bootstrapMethods = new ByteVector(attrSize + 62);
- bootstrapMethods.putByteArray(b, u + 10, attrSize - 2);
- classWriter.bootstrapMethodsCount = boostrapMethodCount;
- classWriter.bootstrapMethods = bootstrapMethods;
- }
-
- /**
- * Constructs a new {@link ClassReader} object.
- *
- * @param is
- * an input stream from which to read the class.
- * @throws IOException
- * if a problem occurs during reading.
- */
- public ClassReader(final InputStream is) throws IOException {
- this(readClass(is, false));
+ // Visit the fields and methods.
+ int fieldsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (fieldsCount-- > 0) {
+ currentOffset = readField(classVisitor, context, currentOffset);
+ }
+ int methodsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (methodsCount-- > 0) {
+ currentOffset = readMethod(classVisitor, context, currentOffset);
}
- /**
- * Constructs a new {@link ClassReader} object.
- *
- * @param name
- * the binary qualified name of the class to be read.
- * @throws IOException
- * if an exception occurs during reading.
- */
- public ClassReader(final String name) throws IOException {
- this(readClass(
- ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
- + ".class"), true));
- }
-
- /**
- * Reads the bytecode of a class.
- *
- * @param is
- * an input stream from which to read the class.
- * @param close
- * true to close the input stream after reading.
- * @return the bytecode read from the given input stream.
- * @throws IOException
- * if a problem occurs during reading.
- */
- private static byte[] readClass(final InputStream is, boolean close)
- throws IOException {
- if (is == null) {
- throw new IOException("Class not found");
- }
- try {
- byte[] b = new byte[is.available()];
- int len = 0;
- while (true) {
- int n = is.read(b, len, b.length - len);
- if (n == -1) {
- if (len < b.length) {
- byte[] c = new byte[len];
- System.arraycopy(b, 0, c, 0, len);
- b = c;
- }
- return b;
- }
- len += n;
- if (len == b.length) {
- int last = is.read();
- if (last < 0) {
- return b;
- }
- byte[] c = new byte[b.length + 1000];
- System.arraycopy(b, 0, c, 0, len);
- c[len++] = (byte) last;
- b = c;
- }
- }
- } finally {
- if (close) {
- is.close();
- }
- }
+ // Visit the end of the class.
+ classVisitor.visitEnd();
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse modules, fields and methods
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Reads the Module, ModulePackages and ModuleMainClass attributes and visit them.
+ *
+ * @param classVisitor the current class visitor
+ * @param context information about the class being parsed.
+ * @param moduleOffset the offset of the Module attribute (excluding the attribute_info's
+ * attribute_name_index and attribute_length fields).
+ * @param modulePackagesOffset the offset of the ModulePackages attribute (excluding the
+ * attribute_info's attribute_name_index and attribute_length fields), or 0.
+ * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or {@literal
+ * null}.
+ */
+ private void readModuleAttributes(
+ final ClassVisitor classVisitor,
+ final Context context,
+ final int moduleOffset,
+ final int modulePackagesOffset,
+ final String moduleMainClass) {
+ char[] buffer = context.charBuffer;
+
+ // Read the module_name_index, module_flags and module_version_index fields and visit them.
+ int currentOffset = moduleOffset;
+ String moduleName = readModule(currentOffset, buffer);
+ int moduleFlags = readUnsignedShort(currentOffset + 2);
+ String moduleVersion = readUTF8(currentOffset + 4, buffer);
+ currentOffset += 6;
+ ModuleVisitor moduleVisitor = classVisitor.visitModule(moduleName, moduleFlags, moduleVersion);
+ if (moduleVisitor == null) {
+ return;
}
- // ------------------------------------------------------------------------
- // Public methods
- // ------------------------------------------------------------------------
-
- /**
- * Makes the given visitor visit the Java class of this {@link ClassReader}
- * . This class is the one specified in the constructor (see
- * {@link #ClassReader(byte[]) ClassReader}).
- *
- * @param classVisitor
- * the visitor that must visit this class.
- * @param flags
- * option flags that can be used to modify the default behavior
- * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
- * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
- */
- public void accept(final ClassVisitor classVisitor, final int flags) {
- accept(classVisitor, new Attribute[0], flags);
- }
-
- /**
- * Makes the given visitor visit the Java class of this {@link ClassReader}.
- * This class is the one specified in the constructor (see
- * {@link #ClassReader(byte[]) ClassReader}).
- *
- * @param classVisitor
- * the visitor that must visit this class.
- * @param attrs
- * prototypes of the attributes that must be parsed during the
- * visit of the class. Any attribute whose type is not equal to
- * the type of one the prototypes will not be parsed: its byte
- * array value will be passed unchanged to the ClassWriter.
- * This may corrupt it if this value contains references to
- * the constant pool, or has syntactic or semantic links with a
- * class element that has been transformed by a class adapter
- * between the reader and the writer.
- * @param flags
- * option flags that can be used to modify the default behavior
- * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
- * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
- */
- public void accept(final ClassVisitor classVisitor,
- final Attribute[] attrs, final int flags) {
- int u = header; // current offset in the class file
- char[] c = new char[maxStringLength]; // buffer used to read strings
-
- Context context = new Context();
- context.attrs = attrs;
- context.flags = flags;
- context.buffer = c;
-
- // reads the class declaration
- int access = readUnsignedShort(u);
- String name = readClass(u + 2, c);
- String superClass = readClass(u + 4, c);
- String[] interfaces = new String[readUnsignedShort(u + 6)];
- u += 8;
- for (int i = 0; i < interfaces.length; ++i) {
- interfaces[i] = readClass(u, c);
- u += 2;
- }
+ // Visit the ModuleMainClass attribute.
+ if (moduleMainClass != null) {
+ moduleVisitor.visitMainClass(moduleMainClass);
+ }
- // reads the class attributes
- String signature = null;
- String sourceFile = null;
- String sourceDebug = null;
- String enclosingOwner = null;
- String enclosingName = null;
- String enclosingDesc = null;
- int anns = 0;
- int ianns = 0;
- int innerClasses = 0;
- Attribute attributes = null;
-
- u = getAttributes();
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- // tests are sorted in decreasing frequency order
- // (based on frequencies observed on typical classes)
- if ("SourceFile".equals(attrName)) {
- sourceFile = readUTF8(u + 8, c);
- } else if ("InnerClasses".equals(attrName)) {
- innerClasses = u + 8;
- } else if ("EnclosingMethod".equals(attrName)) {
- enclosingOwner = readClass(u + 8, c);
- int item = readUnsignedShort(u + 10);
- if (item != 0) {
- enclosingName = readUTF8(items[item], c);
- enclosingDesc = readUTF8(items[item] + 2, c);
- }
- } else if (SIGNATURES && "Signature".equals(attrName)) {
- signature = readUTF8(u + 8, c);
- } else if (ANNOTATIONS
- && "RuntimeVisibleAnnotations".equals(attrName)) {
- anns = u + 8;
- } else if ("Deprecated".equals(attrName)) {
- access |= Opcodes.ACC_DEPRECATED;
- } else if ("Synthetic".equals(attrName)) {
- access |= Opcodes.ACC_SYNTHETIC
- | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
- } else if ("SourceDebugExtension".equals(attrName)) {
- int len = readInt(u + 4);
- sourceDebug = readUTF(u + 8, len, new char[len]);
- } else if (ANNOTATIONS
- && "RuntimeInvisibleAnnotations".equals(attrName)) {
- ianns = u + 8;
- } else if ("BootstrapMethods".equals(attrName)) {
- int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
- for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
- bootstrapMethods[j] = v;
- v += 2 + readUnsignedShort(v + 2) << 1;
- }
- context.bootstrapMethods = bootstrapMethods;
- } else {
- Attribute attr = readAttribute(attrs, attrName, u + 8,
- readInt(u + 4), c, -1, null);
- if (attr != null) {
- attr.next = attributes;
- attributes = attr;
- }
- }
- u += 6 + readInt(u + 4);
- }
+ // Visit the ModulePackages attribute.
+ if (modulePackagesOffset != 0) {
+ int packageCount = readUnsignedShort(modulePackagesOffset);
+ int currentPackageOffset = modulePackagesOffset + 2;
+ while (packageCount-- > 0) {
+ moduleVisitor.visitPackage(readPackage(currentPackageOffset, buffer));
+ currentPackageOffset += 2;
+ }
+ }
- // visits the class declaration
- classVisitor.visit(readInt(items[1] - 7), access, name, signature,
- superClass, interfaces);
+ // Read the 'requires_count' and 'requires' fields.
+ int requiresCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (requiresCount-- > 0) {
+ // Read the requires_index, requires_flags and requires_version fields and visit them.
+ String requires = readModule(currentOffset, buffer);
+ int requiresFlags = readUnsignedShort(currentOffset + 2);
+ String requiresVersion = readUTF8(currentOffset + 4, buffer);
+ currentOffset += 6;
+ moduleVisitor.visitRequire(requires, requiresFlags, requiresVersion);
+ }
- // visits the source and debug info
- if ((flags & SKIP_DEBUG) == 0
- && (sourceFile != null || sourceDebug != null)) {
- classVisitor.visitSource(sourceFile, sourceDebug);
+ // Read the 'exports_count' and 'exports' fields.
+ int exportsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (exportsCount-- > 0) {
+ // Read the exports_index, exports_flags, exports_to_count and exports_to_index fields
+ // and visit them.
+ String exports = readPackage(currentOffset, buffer);
+ int exportsFlags = readUnsignedShort(currentOffset + 2);
+ int exportsToCount = readUnsignedShort(currentOffset + 4);
+ currentOffset += 6;
+ String[] exportsTo = null;
+ if (exportsToCount != 0) {
+ exportsTo = new String[exportsToCount];
+ for (int i = 0; i < exportsToCount; ++i) {
+ exportsTo[i] = readModule(currentOffset, buffer);
+ currentOffset += 2;
}
+ }
+ moduleVisitor.visitExport(exports, exportsFlags, exportsTo);
+ }
- // visits the outer class
- if (enclosingOwner != null) {
- classVisitor.visitOuterClass(enclosingOwner, enclosingName,
- enclosingDesc);
+ // Reads the 'opens_count' and 'opens' fields.
+ int opensCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (opensCount-- > 0) {
+ // Read the opens_index, opens_flags, opens_to_count and opens_to_index fields and visit them.
+ String opens = readPackage(currentOffset, buffer);
+ int opensFlags = readUnsignedShort(currentOffset + 2);
+ int opensToCount = readUnsignedShort(currentOffset + 4);
+ currentOffset += 6;
+ String[] opensTo = null;
+ if (opensToCount != 0) {
+ opensTo = new String[opensToCount];
+ for (int i = 0; i < opensToCount; ++i) {
+ opensTo[i] = readModule(currentOffset, buffer);
+ currentOffset += 2;
}
+ }
+ moduleVisitor.visitOpen(opens, opensFlags, opensTo);
+ }
- // visits the class annotations
- if (ANNOTATIONS && anns != 0) {
- for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- classVisitor.visitAnnotation(readUTF8(v, c), true));
- }
- }
- if (ANNOTATIONS && ianns != 0) {
- for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- classVisitor.visitAnnotation(readUTF8(v, c), false));
- }
- }
+ // Read the 'uses_count' and 'uses' fields.
+ int usesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (usesCount-- > 0) {
+ moduleVisitor.visitUse(readClass(currentOffset, buffer));
+ currentOffset += 2;
+ }
- // visits the attributes
- while (attributes != null) {
- Attribute attr = attributes.next;
- attributes.next = null;
- classVisitor.visitAttribute(attributes);
- attributes = attr;
- }
+ // Read the 'provides_count' and 'provides' fields.
+ int providesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (providesCount-- > 0) {
+ // Read the provides_index, provides_with_count and provides_with_index fields and visit them.
+ String provides = readClass(currentOffset, buffer);
+ int providesWithCount = readUnsignedShort(currentOffset + 2);
+ currentOffset += 4;
+ String[] providesWith = new String[providesWithCount];
+ for (int i = 0; i < providesWithCount; ++i) {
+ providesWith[i] = readClass(currentOffset, buffer);
+ currentOffset += 2;
+ }
+ moduleVisitor.visitProvide(provides, providesWith);
+ }
- // visits the inner classes
- if (innerClasses != 0) {
- int v = innerClasses + 2;
- for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
- classVisitor.visitInnerClass(readClass(v, c),
- readClass(v + 2, c), readUTF8(v + 4, c),
- readUnsignedShort(v + 6));
- v += 8;
- }
- }
+ // Visit the end of the module attributes.
+ moduleVisitor.visitEnd();
+ }
+
+ /**
+ * Reads a record component and visit it.
+ *
+ * @param classVisitor the current class visitor
+ * @param context information about the class being parsed.
+ * @param recordComponentOffset the offset of the current record component.
+ * @return the offset of the first byte following the record component.
+ */
+ private int readRecordComponent(
+ final ClassVisitor classVisitor, final Context context, final int recordComponentOffset) {
+ char[] charBuffer = context.charBuffer;
+
+ int currentOffset = recordComponentOffset;
+ String name = readUTF8(currentOffset, charBuffer);
+ String descriptor = readUTF8(currentOffset + 2, charBuffer);
+ currentOffset += 4;
+
+ // Read the record component attributes (the variables are ordered as in Section 4.7 of the
+ // JVMS).
+
+ // Attribute offsets exclude the attribute_name_index and attribute_length fields.
+ // - The string corresponding to the Signature attribute, or null.
+ String signature = null;
+ // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
+ int runtimeVisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
+ int runtimeInvisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
+ int runtimeVisibleTypeAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
+ int runtimeInvisibleTypeAnnotationsOffset = 0;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int attributesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (attributesCount-- > 0) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentOffset, charBuffer);
+ int attributeLength = readInt(currentOffset + 2);
+ currentOffset += 6;
+ // The tests are sorted in decreasing frequency order (based on frequencies observed on
+ // typical classes).
+ if (Constants.SIGNATURE.equals(attributeName)) {
+ signature = readUTF8(currentOffset, charBuffer);
+ } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleTypeAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleTypeAnnotationsOffset = currentOffset;
+ } else {
+ Attribute attribute =
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentOffset += attributeLength;
+ }
- // visits the fields and methods
- u = header + 10 + 2 * interfaces.length;
- for (int i = readUnsignedShort(u - 2); i > 0; --i) {
- u = readField(classVisitor, context, u);
- }
- u += 2;
- for (int i = readUnsignedShort(u - 2); i > 0; --i) {
- u = readMethod(classVisitor, context, u);
- }
+ RecordComponentVisitor recordComponentVisitor =
+ classVisitor.visitRecordComponent(name, descriptor, signature);
+ if (recordComponentVisitor == null) {
+ return currentOffset;
+ }
- // visits the end of the class
- classVisitor.visitEnd();
+ // Visit the RuntimeVisibleAnnotations attribute.
+ if (runtimeVisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
}
- /**
- * Reads a field and makes the given visitor visit it.
- *
- * @param classVisitor
- * the visitor that must visit the field.
- * @param context
- * information about the class being parsed.
- * @param u
- * the start offset of the field in the class file.
- * @return the offset of the first byte following the field in the class.
- */
- private int readField(final ClassVisitor classVisitor,
- final Context context, int u) {
- // reads the field declaration
- char[] c = context.buffer;
- int access = readUnsignedShort(u);
- String name = readUTF8(u + 2, c);
- String desc = readUTF8(u + 4, c);
- u += 6;
-
- // reads the field attributes
- String signature = null;
- int anns = 0;
- int ianns = 0;
- Object value = null;
- Attribute attributes = null;
-
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- // tests are sorted in decreasing frequency order
- // (based on frequencies observed on typical classes)
- if ("ConstantValue".equals(attrName)) {
- int item = readUnsignedShort(u + 8);
- value = item == 0 ? null : readConst(item, c);
- } else if (SIGNATURES && "Signature".equals(attrName)) {
- signature = readUTF8(u + 8, c);
- } else if ("Deprecated".equals(attrName)) {
- access |= Opcodes.ACC_DEPRECATED;
- } else if ("Synthetic".equals(attrName)) {
- access |= Opcodes.ACC_SYNTHETIC
- | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
- } else if (ANNOTATIONS
- && "RuntimeVisibleAnnotations".equals(attrName)) {
- anns = u + 8;
- } else if (ANNOTATIONS
- && "RuntimeInvisibleAnnotations".equals(attrName)) {
- ianns = u + 8;
- } else {
- Attribute attr = readAttribute(context.attrs, attrName, u + 8,
- readInt(u + 4), c, -1, null);
- if (attr != null) {
- attr.next = attributes;
- attributes = attr;
- }
- }
- u += 6 + readInt(u + 4);
- }
- u += 2;
+ // Visit the RuntimeInvisibleAnnotations attribute.
+ if (runtimeInvisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the field declaration
- FieldVisitor fv = classVisitor.visitField(access, name, desc,
- signature, value);
- if (fv == null) {
- return u;
- }
+ // Visit the RuntimeVisibleTypeAnnotations attribute.
+ if (runtimeVisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ recordComponentVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the field annotations
- if (ANNOTATIONS && anns != 0) {
- for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- fv.visitAnnotation(readUTF8(v, c), true));
- }
- }
- if (ANNOTATIONS && ianns != 0) {
- for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- fv.visitAnnotation(readUTF8(v, c), false));
- }
- }
+ // Visit the RuntimeInvisibleTypeAnnotations attribute.
+ if (runtimeInvisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ recordComponentVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the field attributes
- while (attributes != null) {
- Attribute attr = attributes.next;
- attributes.next = null;
- fv.visitAttribute(attributes);
- attributes = attr;
- }
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in FieldWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ recordComponentVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
- // visits the end of the field
- fv.visitEnd();
+ // Visit the end of the field.
+ recordComponentVisitor.visitEnd();
+ return currentOffset;
+ }
+
+ /**
+ * Reads a JVMS field_info structure and makes the given visitor visit it.
+ *
+ * @param classVisitor the visitor that must visit the field.
+ * @param context information about the class being parsed.
+ * @param fieldInfoOffset the start offset of the field_info structure.
+ * @return the offset of the first byte following the field_info structure.
+ */
+ private int readField(
+ final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) {
+ char[] charBuffer = context.charBuffer;
+
+ // Read the access_flags, name_index and descriptor_index fields.
+ int currentOffset = fieldInfoOffset;
+ int accessFlags = readUnsignedShort(currentOffset);
+ String name = readUTF8(currentOffset + 2, charBuffer);
+ String descriptor = readUTF8(currentOffset + 4, charBuffer);
+ currentOffset += 6;
+
+ // Read the field attributes (the variables are ordered as in Section 4.7 of the JVMS).
+ // Attribute offsets exclude the attribute_name_index and attribute_length fields.
+ // - The value corresponding to the ConstantValue attribute, or null.
+ Object constantValue = null;
+ // - The string corresponding to the Signature attribute, or null.
+ String signature = null;
+ // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
+ int runtimeVisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
+ int runtimeInvisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
+ int runtimeVisibleTypeAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
+ int runtimeInvisibleTypeAnnotationsOffset = 0;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int attributesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (attributesCount-- > 0) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentOffset, charBuffer);
+ int attributeLength = readInt(currentOffset + 2);
+ currentOffset += 6;
+ // The tests are sorted in decreasing frequency order (based on frequencies observed on
+ // typical classes).
+ if (Constants.CONSTANT_VALUE.equals(attributeName)) {
+ int constantvalueIndex = readUnsignedShort(currentOffset);
+ constantValue = constantvalueIndex == 0 ? null : readConst(constantvalueIndex, charBuffer);
+ } else if (Constants.SIGNATURE.equals(attributeName)) {
+ signature = readUTF8(currentOffset, charBuffer);
+ } else if (Constants.DEPRECATED.equals(attributeName)) {
+ accessFlags |= Opcodes.ACC_DEPRECATED;
+ } else if (Constants.SYNTHETIC.equals(attributeName)) {
+ accessFlags |= Opcodes.ACC_SYNTHETIC;
+ } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleTypeAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleTypeAnnotationsOffset = currentOffset;
+ } else {
+ Attribute attribute =
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentOffset += attributeLength;
+ }
- return u;
+ // Visit the field declaration.
+ FieldVisitor fieldVisitor =
+ classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue);
+ if (fieldVisitor == null) {
+ return currentOffset;
}
- /**
- * Reads a method and makes the given visitor visit it.
- *
- * @param classVisitor
- * the visitor that must visit the method.
- * @param context
- * information about the class being parsed.
- * @param u
- * the start offset of the method in the class file.
- * @return the offset of the first byte following the method in the class.
- */
- private int readMethod(final ClassVisitor classVisitor,
- final Context context, int u) {
- // reads the method declaration
- char[] c = context.buffer;
- int access = readUnsignedShort(u);
- String name = readUTF8(u + 2, c);
- String desc = readUTF8(u + 4, c);
- u += 6;
-
- // reads the method attributes
- int code = 0;
- int exception = 0;
- String[] exceptions = null;
- String signature = null;
- int anns = 0;
- int ianns = 0;
- int dann = 0;
- int mpanns = 0;
- int impanns = 0;
- int firstAttribute = u;
- Attribute attributes = null;
-
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- // tests are sorted in decreasing frequency order
- // (based on frequencies observed on typical classes)
- if ("Code".equals(attrName)) {
- if ((context.flags & SKIP_CODE) == 0) {
- code = u + 8;
- }
- } else if ("Exceptions".equals(attrName)) {
- exceptions = new String[readUnsignedShort(u + 8)];
- exception = u + 10;
- for (int j = 0; j < exceptions.length; ++j) {
- exceptions[j] = readClass(exception, c);
- exception += 2;
- }
- } else if (SIGNATURES && "Signature".equals(attrName)) {
- signature = readUTF8(u + 8, c);
- } else if ("Deprecated".equals(attrName)) {
- access |= Opcodes.ACC_DEPRECATED;
- } else if (ANNOTATIONS
- && "RuntimeVisibleAnnotations".equals(attrName)) {
- anns = u + 8;
- } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
- dann = u + 8;
- } else if ("Synthetic".equals(attrName)) {
- access |= Opcodes.ACC_SYNTHETIC
- | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
- } else if (ANNOTATIONS
- && "RuntimeInvisibleAnnotations".equals(attrName)) {
- ianns = u + 8;
- } else if (ANNOTATIONS
- && "RuntimeVisibleParameterAnnotations".equals(attrName)) {
- mpanns = u + 8;
- } else if (ANNOTATIONS
- && "RuntimeInvisibleParameterAnnotations".equals(attrName)) {
- impanns = u + 8;
- } else {
- Attribute attr = readAttribute(context.attrs, attrName, u + 8,
- readInt(u + 4), c, -1, null);
- if (attr != null) {
- attr.next = attributes;
- attributes = attr;
- }
- }
- u += 6 + readInt(u + 4);
- }
- u += 2;
+ // Visit the RuntimeVisibleAnnotations attribute.
+ if (runtimeVisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the method declaration
- MethodVisitor mv = classVisitor.visitMethod(access, name, desc,
- signature, exceptions);
- if (mv == null) {
- return u;
- }
+ // Visit the RuntimeInvisibleAnnotations attribute.
+ if (runtimeInvisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- /*
- * if the returned MethodVisitor is in fact a MethodWriter, it means
- * there is no method adapter between the reader and the writer. If, in
- * addition, the writer's constant pool was copied from this reader
- * (mw.cw.cr == this), and the signature and exceptions of the method
- * have not been changed, then it is possible to skip all visit events
- * and just copy the original code of the method to the writer (the
- * access, name and descriptor can have been changed, this is not
- * important since they are not copied as is from the reader).
- */
- if (WRITER && mv instanceof MethodWriter) {
- MethodWriter mw = (MethodWriter) mv;
- if (mw.cw.cr == this && signature == mw.signature) {
- boolean sameExceptions = false;
- if (exceptions == null) {
- sameExceptions = mw.exceptionCount == 0;
- } else if (exceptions.length == mw.exceptionCount) {
- sameExceptions = true;
- for (int j = exceptions.length - 1; j >= 0; --j) {
- exception -= 2;
- if (mw.exceptions[j] != readUnsignedShort(exception)) {
- sameExceptions = false;
- break;
- }
- }
- }
- if (sameExceptions) {
- /*
- * we do not copy directly the code into MethodWriter to
- * save a byte array copy operation. The real copy will be
- * done in ClassWriter.toByteArray().
- */
- mw.classReaderOffset = firstAttribute;
- mw.classReaderLength = u - firstAttribute;
- return u;
- }
- }
- }
+ // Visit the RuntimeVisibleTypeAnnotations attribute.
+ if (runtimeVisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ fieldVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
- // visits the method annotations
- if (ANNOTATIONS && dann != 0) {
- AnnotationVisitor dv = mv.visitAnnotationDefault();
- readAnnotationValue(dann, c, null, dv);
- if (dv != null) {
- dv.visitEnd();
- }
- }
- if (ANNOTATIONS && anns != 0) {
- for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- mv.visitAnnotation(readUTF8(v, c), true));
- }
- }
- if (ANNOTATIONS && ianns != 0) {
- for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
- v = readAnnotationValues(v + 2, c, true,
- mv.visitAnnotation(readUTF8(v, c), false));
- }
- }
- if (ANNOTATIONS && mpanns != 0) {
- readParameterAnnotations(mpanns, desc, c, true, mv);
+ // Visit the RuntimeInvisibleTypeAnnotations attribute.
+ if (runtimeInvisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ fieldVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in FieldWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ fieldVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
+
+ // Visit the end of the field.
+ fieldVisitor.visitEnd();
+ return currentOffset;
+ }
+
+ /**
+ * Reads a JVMS method_info structure and makes the given visitor visit it.
+ *
+ * @param classVisitor the visitor that must visit the method.
+ * @param context information about the class being parsed.
+ * @param methodInfoOffset the start offset of the method_info structure.
+ * @return the offset of the first byte following the method_info structure.
+ */
+ private int readMethod(
+ final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) {
+ char[] charBuffer = context.charBuffer;
+
+ // Read the access_flags, name_index and descriptor_index fields.
+ int currentOffset = methodInfoOffset;
+ context.currentMethodAccessFlags = readUnsignedShort(currentOffset);
+ context.currentMethodName = readUTF8(currentOffset + 2, charBuffer);
+ context.currentMethodDescriptor = readUTF8(currentOffset + 4, charBuffer);
+ currentOffset += 6;
+
+ // Read the method attributes (the variables are ordered as in Section 4.7 of the JVMS).
+ // Attribute offsets exclude the attribute_name_index and attribute_length fields.
+ // - The offset of the Code attribute, or 0.
+ int codeOffset = 0;
+ // - The offset of the Exceptions attribute, or 0.
+ int exceptionsOffset = 0;
+ // - The strings corresponding to the Exceptions attribute, or null.
+ String[] exceptions = null;
+ // - Whether the method has a Synthetic attribute.
+ boolean synthetic = false;
+ // - The constant pool index contained in the Signature attribute, or 0.
+ int signatureIndex = 0;
+ // - The offset of the RuntimeVisibleAnnotations attribute, or 0.
+ int runtimeVisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleAnnotations attribute, or 0.
+ int runtimeInvisibleAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleParameterAnnotations attribute, or 0.
+ int runtimeVisibleParameterAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleParameterAnnotations attribute, or 0.
+ int runtimeInvisibleParameterAnnotationsOffset = 0;
+ // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0.
+ int runtimeVisibleTypeAnnotationsOffset = 0;
+ // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0.
+ int runtimeInvisibleTypeAnnotationsOffset = 0;
+ // - The offset of the AnnotationDefault attribute, or 0.
+ int annotationDefaultOffset = 0;
+ // - The offset of the MethodParameters attribute, or 0.
+ int methodParametersOffset = 0;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int attributesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (attributesCount-- > 0) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentOffset, charBuffer);
+ int attributeLength = readInt(currentOffset + 2);
+ currentOffset += 6;
+ // The tests are sorted in decreasing frequency order (based on frequencies observed on
+ // typical classes).
+ if (Constants.CODE.equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_CODE) == 0) {
+ codeOffset = currentOffset;
}
- if (ANNOTATIONS && impanns != 0) {
- readParameterAnnotations(impanns, desc, c, false, mv);
+ } else if (Constants.EXCEPTIONS.equals(attributeName)) {
+ exceptionsOffset = currentOffset;
+ exceptions = new String[readUnsignedShort(exceptionsOffset)];
+ int currentExceptionOffset = exceptionsOffset + 2;
+ for (int i = 0; i < exceptions.length; ++i) {
+ exceptions[i] = readClass(currentExceptionOffset, charBuffer);
+ currentExceptionOffset += 2;
}
+ } else if (Constants.SIGNATURE.equals(attributeName)) {
+ signatureIndex = readUnsignedShort(currentOffset);
+ } else if (Constants.DEPRECATED.equals(attributeName)) {
+ context.currentMethodAccessFlags |= Opcodes.ACC_DEPRECATED;
+ } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleTypeAnnotationsOffset = currentOffset;
+ } else if (Constants.ANNOTATION_DEFAULT.equals(attributeName)) {
+ annotationDefaultOffset = currentOffset;
+ } else if (Constants.SYNTHETIC.equals(attributeName)) {
+ synthetic = true;
+ context.currentMethodAccessFlags |= Opcodes.ACC_SYNTHETIC;
+ } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleTypeAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) {
+ runtimeVisibleParameterAnnotationsOffset = currentOffset;
+ } else if (Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) {
+ runtimeInvisibleParameterAnnotationsOffset = currentOffset;
+ } else if (Constants.METHOD_PARAMETERS.equals(attributeName)) {
+ methodParametersOffset = currentOffset;
+ } else {
+ Attribute attribute =
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ -1,
+ null);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentOffset += attributeLength;
+ }
- // visits the method attributes
- while (attributes != null) {
- Attribute attr = attributes.next;
- attributes.next = null;
- mv.visitAttribute(attributes);
- attributes = attr;
- }
+ // Visit the method declaration.
+ MethodVisitor methodVisitor =
+ classVisitor.visitMethod(
+ context.currentMethodAccessFlags,
+ context.currentMethodName,
+ context.currentMethodDescriptor,
+ signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer),
+ exceptions);
+ if (methodVisitor == null) {
+ return currentOffset;
+ }
- // visits the method code
- if (code != 0) {
- context.access = access;
- context.name = name;
- context.desc = desc;
- mv.visitCode();
- readCode(mv, context, code);
- }
+ // If the returned MethodVisitor is in fact a MethodWriter, it means there is no method
+ // adapter between the reader and the writer. In this case, it might be possible to copy
+ // the method attributes directly into the writer. If so, return early without visiting
+ // the content of these attributes.
+ if (methodVisitor instanceof MethodWriter) {
+ MethodWriter methodWriter = (MethodWriter) methodVisitor;
+ if (methodWriter.canCopyMethodAttributes(
+ this,
+ synthetic,
+ (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0,
+ readUnsignedShort(methodInfoOffset + 4),
+ signatureIndex,
+ exceptionsOffset)) {
+ methodWriter.setMethodAttributesSource(methodInfoOffset, currentOffset - methodInfoOffset);
+ return currentOffset;
+ }
+ }
- // visits the end of the method
- mv.visitEnd();
+ // Visit the MethodParameters attribute.
+ if (methodParametersOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) {
+ int parametersCount = readByte(methodParametersOffset);
+ int currentParameterOffset = methodParametersOffset + 1;
+ while (parametersCount-- > 0) {
+ // Read the name_index and access_flags fields and visit them.
+ methodVisitor.visitParameter(
+ readUTF8(currentParameterOffset, charBuffer),
+ readUnsignedShort(currentParameterOffset + 2));
+ currentParameterOffset += 4;
+ }
+ }
- return u;
+ // Visit the AnnotationDefault attribute.
+ if (annotationDefaultOffset != 0) {
+ AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
+ readElementValue(annotationVisitor, annotationDefaultOffset, null, charBuffer);
+ if (annotationVisitor != null) {
+ annotationVisitor.visitEnd();
+ }
}
- /**
- * Reads the bytecode of a method and makes the given visitor visit it.
- *
- * @param mv
- * the visitor that must visit the method's code.
- * @param context
- * information about the class being parsed.
- * @param u
- * the start offset of the code attribute in the class file.
- */
- private void readCode(final MethodVisitor mv, final Context context, int u) {
- // reads the header
- byte[] b = this.b;
- char[] c = context.buffer;
- int maxStack = readUnsignedShort(u);
- int maxLocals = readUnsignedShort(u + 2);
- int codeLength = readInt(u + 4);
- u += 8;
-
- // reads the bytecode to find the labels
- int codeStart = u;
- int codeEnd = u + codeLength;
- Label[] labels = new Label[codeLength + 2];
- readLabel(codeLength + 1, labels);
- while (u < codeEnd) {
- int offset = u - codeStart;
- int opcode = b[u] & 0xFF;
- switch (ClassWriter.TYPE[opcode]) {
- case ClassWriter.NOARG_INSN:
- case ClassWriter.IMPLVAR_INSN:
- u += 1;
- break;
- case ClassWriter.LABEL_INSN:
- readLabel(offset + readShort(u + 1), labels);
- u += 3;
- break;
- case ClassWriter.LABELW_INSN:
- readLabel(offset + readInt(u + 1), labels);
- u += 5;
- break;
- case ClassWriter.WIDE_INSN:
- opcode = b[u + 1] & 0xFF;
- if (opcode == Opcodes.IINC) {
- u += 6;
- } else {
- u += 4;
- }
- break;
- case ClassWriter.TABL_INSN:
- // skips 0 to 3 padding bytes
- u = u + 4 - (offset & 3);
- // reads instruction
- readLabel(offset + readInt(u), labels);
- for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) {
- readLabel(offset + readInt(u + 12), labels);
- u += 4;
- }
- u += 12;
- break;
- case ClassWriter.LOOK_INSN:
- // skips 0 to 3 padding bytes
- u = u + 4 - (offset & 3);
- // reads instruction
- readLabel(offset + readInt(u), labels);
- for (int i = readInt(u + 4); i > 0; --i) {
- readLabel(offset + readInt(u + 12), labels);
- u += 8;
- }
- u += 8;
- break;
- case ClassWriter.VAR_INSN:
- case ClassWriter.SBYTE_INSN:
- case ClassWriter.LDC_INSN:
- u += 2;
- break;
- case ClassWriter.SHORT_INSN:
- case ClassWriter.LDCW_INSN:
- case ClassWriter.FIELDORMETH_INSN:
- case ClassWriter.TYPE_INSN:
- case ClassWriter.IINC_INSN:
- u += 3;
- break;
- case ClassWriter.ITFMETH_INSN:
- case ClassWriter.INDYMETH_INSN:
- u += 5;
- break;
- // case MANA_INSN:
+ // Visit the RuntimeVisibleAnnotations attribute.
+ if (runtimeVisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeInvisibleAnnotations attribute.
+ if (runtimeInvisibleAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeVisibleTypeAnnotations attribute.
+ if (runtimeVisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ methodVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeInvisibleTypeAnnotations attribute.
+ if (runtimeInvisibleTypeAnnotationsOffset != 0) {
+ int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset);
+ int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2;
+ while (numAnnotations-- > 0) {
+ // Parse the target_type, target_info and target_path fields.
+ currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentAnnotationOffset =
+ readElementValues(
+ methodVisitor.visitTypeAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+
+ // Visit the RuntimeVisibleParameterAnnotations attribute.
+ if (runtimeVisibleParameterAnnotationsOffset != 0) {
+ readParameterAnnotations(
+ methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true);
+ }
+
+ // Visit the RuntimeInvisibleParameterAnnotations attribute.
+ if (runtimeInvisibleParameterAnnotationsOffset != 0) {
+ readParameterAnnotations(
+ methodVisitor,
+ context,
+ runtimeInvisibleParameterAnnotationsOffset,
+ /* visible = */ false);
+ }
+
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in MethodWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ methodVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
+
+ // Visit the Code attribute.
+ if (codeOffset != 0) {
+ methodVisitor.visitCode();
+ readCode(methodVisitor, context, codeOffset);
+ }
+
+ // Visit the end of the method.
+ methodVisitor.visitEnd();
+ return currentOffset;
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse a Code attribute
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Reads a JVMS 'Code' attribute and makes the given visitor visit it.
+ *
+ * @param methodVisitor the visitor that must visit the Code attribute.
+ * @param context information about the class being parsed.
+ * @param codeOffset the start offset in {@link #classFileBuffer} of the Code attribute, excluding
+ * its attribute_name_index and attribute_length fields.
+ */
+ private void readCode(
+ final MethodVisitor methodVisitor, final Context context, final int codeOffset) {
+ int currentOffset = codeOffset;
+
+ // Read the max_stack, max_locals and code_length fields.
+ final byte[] classBuffer = classFileBuffer;
+ final char[] charBuffer = context.charBuffer;
+ final int maxStack = readUnsignedShort(currentOffset);
+ final int maxLocals = readUnsignedShort(currentOffset + 2);
+ final int codeLength = readInt(currentOffset + 4);
+ currentOffset += 8;
+ if (codeLength > classFileBuffer.length - currentOffset) {
+ throw new IllegalArgumentException();
+ }
+
+ // Read the bytecode 'code' array to create a label for each referenced instruction.
+ final int bytecodeStartOffset = currentOffset;
+ final int bytecodeEndOffset = currentOffset + codeLength;
+ final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1];
+ while (currentOffset < bytecodeEndOffset) {
+ final int bytecodeOffset = currentOffset - bytecodeStartOffset;
+ final int opcode = classBuffer[currentOffset] & 0xFF;
+ switch (opcode) {
+ case Opcodes.NOP:
+ case Opcodes.ACONST_NULL:
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ case Opcodes.IALOAD:
+ case Opcodes.LALOAD:
+ case Opcodes.FALOAD:
+ case Opcodes.DALOAD:
+ case Opcodes.AALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ case Opcodes.IASTORE:
+ case Opcodes.LASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.DASTORE:
+ case Opcodes.AASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.POP:
+ case Opcodes.POP2:
+ case Opcodes.DUP:
+ case Opcodes.DUP_X1:
+ case Opcodes.DUP_X2:
+ case Opcodes.DUP2:
+ case Opcodes.DUP2_X1:
+ case Opcodes.DUP2_X2:
+ case Opcodes.SWAP:
+ case Opcodes.IADD:
+ case Opcodes.LADD:
+ case Opcodes.FADD:
+ case Opcodes.DADD:
+ case Opcodes.ISUB:
+ case Opcodes.LSUB:
+ case Opcodes.FSUB:
+ case Opcodes.DSUB:
+ case Opcodes.IMUL:
+ case Opcodes.LMUL:
+ case Opcodes.FMUL:
+ case Opcodes.DMUL:
+ case Opcodes.IDIV:
+ case Opcodes.LDIV:
+ case Opcodes.FDIV:
+ case Opcodes.DDIV:
+ case Opcodes.IREM:
+ case Opcodes.LREM:
+ case Opcodes.FREM:
+ case Opcodes.DREM:
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ case Opcodes.ISHL:
+ case Opcodes.LSHL:
+ case Opcodes.ISHR:
+ case Opcodes.LSHR:
+ case Opcodes.IUSHR:
+ case Opcodes.LUSHR:
+ case Opcodes.IAND:
+ case Opcodes.LAND:
+ case Opcodes.IOR:
+ case Opcodes.LOR:
+ case Opcodes.IXOR:
+ case Opcodes.LXOR:
+ case Opcodes.I2L:
+ case Opcodes.I2F:
+ case Opcodes.I2D:
+ case Opcodes.L2I:
+ case Opcodes.L2F:
+ case Opcodes.L2D:
+ case Opcodes.F2I:
+ case Opcodes.F2L:
+ case Opcodes.F2D:
+ case Opcodes.D2I:
+ case Opcodes.D2L:
+ case Opcodes.D2F:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ case Opcodes.LCMP:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ case Opcodes.IRETURN:
+ case Opcodes.LRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.RETURN:
+ case Opcodes.ARRAYLENGTH:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
+ case Constants.ILOAD_0:
+ case Constants.ILOAD_1:
+ case Constants.ILOAD_2:
+ case Constants.ILOAD_3:
+ case Constants.LLOAD_0:
+ case Constants.LLOAD_1:
+ case Constants.LLOAD_2:
+ case Constants.LLOAD_3:
+ case Constants.FLOAD_0:
+ case Constants.FLOAD_1:
+ case Constants.FLOAD_2:
+ case Constants.FLOAD_3:
+ case Constants.DLOAD_0:
+ case Constants.DLOAD_1:
+ case Constants.DLOAD_2:
+ case Constants.DLOAD_3:
+ case Constants.ALOAD_0:
+ case Constants.ALOAD_1:
+ case Constants.ALOAD_2:
+ case Constants.ALOAD_3:
+ case Constants.ISTORE_0:
+ case Constants.ISTORE_1:
+ case Constants.ISTORE_2:
+ case Constants.ISTORE_3:
+ case Constants.LSTORE_0:
+ case Constants.LSTORE_1:
+ case Constants.LSTORE_2:
+ case Constants.LSTORE_3:
+ case Constants.FSTORE_0:
+ case Constants.FSTORE_1:
+ case Constants.FSTORE_2:
+ case Constants.FSTORE_3:
+ case Constants.DSTORE_0:
+ case Constants.DSTORE_1:
+ case Constants.DSTORE_2:
+ case Constants.DSTORE_3:
+ case Constants.ASTORE_0:
+ case Constants.ASTORE_1:
+ case Constants.ASTORE_2:
+ case Constants.ASTORE_3:
+ currentOffset += 1;
+ break;
+ case Opcodes.IFEQ:
+ case Opcodes.IFNE:
+ case Opcodes.IFLT:
+ case Opcodes.IFGE:
+ case Opcodes.IFGT:
+ case Opcodes.IFLE:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ICMPLT:
+ case Opcodes.IF_ICMPGE:
+ case Opcodes.IF_ICMPGT:
+ case Opcodes.IF_ICMPLE:
+ case Opcodes.IF_ACMPEQ:
+ case Opcodes.IF_ACMPNE:
+ case Opcodes.GOTO:
+ case Opcodes.JSR:
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
+ createLabel(bytecodeOffset + readShort(currentOffset + 1), labels);
+ currentOffset += 3;
+ break;
+ case Constants.ASM_IFEQ:
+ case Constants.ASM_IFNE:
+ case Constants.ASM_IFLT:
+ case Constants.ASM_IFGE:
+ case Constants.ASM_IFGT:
+ case Constants.ASM_IFLE:
+ case Constants.ASM_IF_ICMPEQ:
+ case Constants.ASM_IF_ICMPNE:
+ case Constants.ASM_IF_ICMPLT:
+ case Constants.ASM_IF_ICMPGE:
+ case Constants.ASM_IF_ICMPGT:
+ case Constants.ASM_IF_ICMPLE:
+ case Constants.ASM_IF_ACMPEQ:
+ case Constants.ASM_IF_ACMPNE:
+ case Constants.ASM_GOTO:
+ case Constants.ASM_JSR:
+ case Constants.ASM_IFNULL:
+ case Constants.ASM_IFNONNULL:
+ createLabel(bytecodeOffset + readUnsignedShort(currentOffset + 1), labels);
+ currentOffset += 3;
+ break;
+ case Constants.GOTO_W:
+ case Constants.JSR_W:
+ case Constants.ASM_GOTO_W:
+ createLabel(bytecodeOffset + readInt(currentOffset + 1), labels);
+ currentOffset += 5;
+ break;
+ case Constants.WIDE:
+ switch (classBuffer[currentOffset + 1] & 0xFF) {
+ case Opcodes.ILOAD:
+ case Opcodes.FLOAD:
+ case Opcodes.ALOAD:
+ case Opcodes.LLOAD:
+ case Opcodes.DLOAD:
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.ASTORE:
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ case Opcodes.RET:
+ currentOffset += 4;
+ break;
+ case Opcodes.IINC:
+ currentOffset += 6;
+ break;
default:
- u += 4;
- break;
- }
- }
+ throw new IllegalArgumentException();
+ }
+ break;
+ case Opcodes.TABLESWITCH:
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (bytecodeOffset & 3);
+ // Read the default label and the number of table entries.
+ createLabel(bytecodeOffset + readInt(currentOffset), labels);
+ int numTableEntries = readInt(currentOffset + 8) - readInt(currentOffset + 4) + 1;
+ currentOffset += 12;
+ // Read the table labels.
+ while (numTableEntries-- > 0) {
+ createLabel(bytecodeOffset + readInt(currentOffset), labels);
+ currentOffset += 4;
+ }
+ break;
+ case Opcodes.LOOKUPSWITCH:
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (bytecodeOffset & 3);
+ // Read the default label and the number of switch cases.
+ createLabel(bytecodeOffset + readInt(currentOffset), labels);
+ int numSwitchCases = readInt(currentOffset + 4);
+ currentOffset += 8;
+ // Read the switch labels.
+ while (numSwitchCases-- > 0) {
+ createLabel(bytecodeOffset + readInt(currentOffset + 4), labels);
+ currentOffset += 8;
+ }
+ break;
+ case Opcodes.ILOAD:
+ case Opcodes.LLOAD:
+ case Opcodes.FLOAD:
+ case Opcodes.DLOAD:
+ case Opcodes.ALOAD:
+ case Opcodes.ISTORE:
+ case Opcodes.LSTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.DSTORE:
+ case Opcodes.ASTORE:
+ case Opcodes.RET:
+ case Opcodes.BIPUSH:
+ case Opcodes.NEWARRAY:
+ case Opcodes.LDC:
+ currentOffset += 2;
+ break;
+ case Opcodes.SIPUSH:
+ case Constants.LDC_W:
+ case Constants.LDC2_W:
+ case Opcodes.GETSTATIC:
+ case Opcodes.PUTSTATIC:
+ case Opcodes.GETFIELD:
+ case Opcodes.PUTFIELD:
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKESPECIAL:
+ case Opcodes.INVOKESTATIC:
+ case Opcodes.NEW:
+ case Opcodes.ANEWARRAY:
+ case Opcodes.CHECKCAST:
+ case Opcodes.INSTANCEOF:
+ case Opcodes.IINC:
+ currentOffset += 3;
+ break;
+ case Opcodes.INVOKEINTERFACE:
+ case Opcodes.INVOKEDYNAMIC:
+ currentOffset += 5;
+ break;
+ case Opcodes.MULTIANEWARRAY:
+ currentOffset += 4;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ // Read the 'exception_table_length' and 'exception_table' field to create a label for each
+ // referenced instruction, and to make methodVisitor visit the corresponding try catch blocks.
+ int exceptionTableLength = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (exceptionTableLength-- > 0) {
+ Label start = createLabel(readUnsignedShort(currentOffset), labels);
+ Label end = createLabel(readUnsignedShort(currentOffset + 2), labels);
+ Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels);
+ String catchType = readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer);
+ currentOffset += 8;
+ methodVisitor.visitTryCatchBlock(start, end, handler, catchType);
+ }
- // reads the try catch entries to find the labels, and also visits them
- for (int i = readUnsignedShort(u); i > 0; --i) {
- Label start = readLabel(readUnsignedShort(u + 2), labels);
- Label end = readLabel(readUnsignedShort(u + 4), labels);
- Label handler = readLabel(readUnsignedShort(u + 6), labels);
- String type = readUTF8(items[readUnsignedShort(u + 8)], c);
- mv.visitTryCatchBlock(start, end, handler, type);
- u += 8;
+ // Read the Code attributes to create a label for each referenced instruction (the variables
+ // are ordered as in Section 4.7 of the JVMS). Attribute offsets exclude the
+ // attribute_name_index and attribute_length fields.
+ // - The offset of the current 'stack_map_frame' in the StackMap[Table] attribute, or 0.
+ // Initially, this is the offset of the first 'stack_map_frame' entry. Then this offset is
+ // updated after each stack_map_frame is read.
+ int stackMapFrameOffset = 0;
+ // - The end offset of the StackMap[Table] attribute, or 0.
+ int stackMapTableEndOffset = 0;
+ // - Whether the stack map frames are compressed (i.e. in a StackMapTable) or not.
+ boolean compressedFrames = true;
+ // - The offset of the LocalVariableTable attribute, or 0.
+ int localVariableTableOffset = 0;
+ // - The offset of the LocalVariableTypeTable attribute, or 0.
+ int localVariableTypeTableOffset = 0;
+ // - The offset of each 'type_annotation' entry in the RuntimeVisibleTypeAnnotations
+ // attribute, or null.
+ int[] visibleTypeAnnotationOffsets = null;
+ // - The offset of each 'type_annotation' entry in the RuntimeInvisibleTypeAnnotations
+ // attribute, or null.
+ int[] invisibleTypeAnnotationOffsets = null;
+ // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field).
+ // This list in the reverse order or their order in the ClassFile structure.
+ Attribute attributes = null;
+
+ int attributesCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (attributesCount-- > 0) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentOffset, charBuffer);
+ int attributeLength = readInt(currentOffset + 2);
+ currentOffset += 6;
+ if (Constants.LOCAL_VARIABLE_TABLE.equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_DEBUG) == 0) {
+ localVariableTableOffset = currentOffset;
+ // Parse the attribute to find the corresponding (debug only) labels.
+ int currentLocalVariableTableOffset = currentOffset;
+ int localVariableTableLength = readUnsignedShort(currentLocalVariableTableOffset);
+ currentLocalVariableTableOffset += 2;
+ while (localVariableTableLength-- > 0) {
+ int startPc = readUnsignedShort(currentLocalVariableTableOffset);
+ createDebugLabel(startPc, labels);
+ int length = readUnsignedShort(currentLocalVariableTableOffset + 2);
+ createDebugLabel(startPc + length, labels);
+ // Skip the name_index, descriptor_index and index fields (2 bytes each).
+ currentLocalVariableTableOffset += 10;
+ }
}
- u += 2;
-
- // reads the code attributes
- int varTable = 0;
- int varTypeTable = 0;
- boolean zip = true;
- boolean unzip = (context.flags & EXPAND_FRAMES) != 0;
- int stackMap = 0;
- int stackMapSize = 0;
- int frameCount = 0;
- Context frame = null;
- Attribute attributes = null;
-
- for (int i = readUnsignedShort(u); i > 0; --i) {
- String attrName = readUTF8(u + 2, c);
- if ("LocalVariableTable".equals(attrName)) {
- if ((context.flags & SKIP_DEBUG) == 0) {
- varTable = u + 8;
- for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
- int label = readUnsignedShort(v + 10);
- if (labels[label] == null) {
- readLabel(label, labels).status |= Label.DEBUG;
- }
- label += readUnsignedShort(v + 12);
- if (labels[label] == null) {
- readLabel(label, labels).status |= Label.DEBUG;
- }
- v += 10;
- }
- }
- } else if ("LocalVariableTypeTable".equals(attrName)) {
- varTypeTable = u + 8;
- } else if ("LineNumberTable".equals(attrName)) {
- if ((context.flags & SKIP_DEBUG) == 0) {
- for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
- int label = readUnsignedShort(v + 10);
- if (labels[label] == null) {
- readLabel(label, labels).status |= Label.DEBUG;
- }
- labels[label].line = readUnsignedShort(v + 12);
- v += 4;
- }
- }
- } else if (FRAMES && "StackMapTable".equals(attrName)) {
- if ((context.flags & SKIP_FRAMES) == 0) {
- stackMap = u + 10;
- stackMapSize = readInt(u + 4);
- frameCount = readUnsignedShort(u + 8);
- }
- /*
- * here we do not extract the labels corresponding to the
- * attribute content. This would require a full parsing of the
- * attribute, which would need to be repeated in the second
- * phase (see below). Instead the content of the attribute is
- * read one frame at a time (i.e. after a frame has been
- * visited, the next frame is read), and the labels it contains
- * are also extracted one frame at a time. Thanks to the
- * ordering of frames, having only a "one frame lookahead" is
- * not a problem, i.e. it is not possible to see an offset
- * smaller than the offset of the current insn and for which no
- * Label exist.
- */
- /*
- * This is not true for UNINITIALIZED type offsets. We solve
- * this by parsing the stack map table without a full decoding
- * (see below).
- */
- } else if (FRAMES && "StackMap".equals(attrName)) {
- if ((context.flags & SKIP_FRAMES) == 0) {
- zip = false;
- stackMap = u + 10;
- stackMapSize = readInt(u + 4);
- frameCount = readUnsignedShort(u + 8);
- }
- /*
- * IMPORTANT! here we assume that the frames are ordered, as in
- * the StackMapTable attribute, although this is not guaranteed
- * by the attribute format.
- */
- } else {
- for (int j = 0; j < context.attrs.length; ++j) {
- if (context.attrs[j].type.equals(attrName)) {
- Attribute attr = context.attrs[j].read(this, u + 8,
- readInt(u + 4), c, codeStart - 8, labels);
- if (attr != null) {
- attr.next = attributes;
- attributes = attr;
- }
- }
- }
- }
- u += 6 + readInt(u + 4);
+ } else if (Constants.LOCAL_VARIABLE_TYPE_TABLE.equals(attributeName)) {
+ localVariableTypeTableOffset = currentOffset;
+ // Here we do not extract the labels corresponding to the attribute content. We assume they
+ // are the same or a subset of those of the LocalVariableTable attribute.
+ } else if (Constants.LINE_NUMBER_TABLE.equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_DEBUG) == 0) {
+ // Parse the attribute to find the corresponding (debug only) labels.
+ int currentLineNumberTableOffset = currentOffset;
+ int lineNumberTableLength = readUnsignedShort(currentLineNumberTableOffset);
+ currentLineNumberTableOffset += 2;
+ while (lineNumberTableLength-- > 0) {
+ int startPc = readUnsignedShort(currentLineNumberTableOffset);
+ int lineNumber = readUnsignedShort(currentLineNumberTableOffset + 2);
+ currentLineNumberTableOffset += 4;
+ createDebugLabel(startPc, labels);
+ labels[startPc].addLineNumber(lineNumber);
+ }
}
- u += 2;
-
- // generates the first (implicit) stack map frame
- if (FRAMES && stackMap != 0) {
- /*
- * for the first explicit frame the offset is not offset_delta + 1
- * but only offset_delta; setting the implicit frame offset to -1
- * allow the use of the "offset_delta + 1" rule in all cases
- */
- frame = context;
- frame.offset = -1;
- frame.mode = 0;
- frame.localCount = 0;
- frame.localDiff = 0;
- frame.stackCount = 0;
- frame.local = new Object[maxLocals];
- frame.stack = new Object[maxStack];
- if (unzip) {
- getImplicitFrame(context);
- }
- /*
- * Finds labels for UNINITIALIZED frame types. Instead of decoding
- * each element of the stack map table, we look for 3 consecutive
- * bytes that "look like" an UNINITIALIZED type (tag 8, offset
- * within code bounds, NEW instruction at this offset). We may find
- * false positives (i.e. not real UNINITIALIZED types), but this
- * should be rare, and the only consequence will be the creation of
- * an unneeded label. This is better than creating a label for each
- * NEW instruction, and faster than fully decoding the whole stack
- * map table.
- */
- for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) {
- if (b[i] == 8) { // UNINITIALIZED FRAME TYPE
- int v = readUnsignedShort(i + 1);
- if (v >= 0 && v < codeLength) {
- if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) {
- readLabel(v, labels);
- }
- }
- }
- }
+ } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ visibleTypeAnnotationOffsets =
+ readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true);
+ // Here we do not extract the labels corresponding to the attribute content. This would
+ // require a full parsing of the attribute, which would need to be repeated when parsing
+ // the bytecode instructions (see below). Instead, the content of the attribute is read one
+ // type annotation at a time (i.e. after a type annotation has been visited, the next type
+ // annotation is read), and the labels it contains are also extracted one annotation at a
+ // time. This assumes that type annotations are ordered by increasing bytecode offset.
+ } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) {
+ invisibleTypeAnnotationOffsets =
+ readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ false);
+ // Same comment as above for the RuntimeVisibleTypeAnnotations attribute.
+ } else if (Constants.STACK_MAP_TABLE.equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_FRAMES) == 0) {
+ stackMapFrameOffset = currentOffset + 2;
+ stackMapTableEndOffset = currentOffset + attributeLength;
+ }
+ // Here we do not extract the labels corresponding to the attribute content. This would
+ // require a full parsing of the attribute, which would need to be repeated when parsing
+ // the bytecode instructions (see below). Instead, the content of the attribute is read one
+ // frame at a time (i.e. after a frame has been visited, the next frame is read), and the
+ // labels it contains are also extracted one frame at a time. Thanks to the ordering of
+ // frames, having only a "one frame lookahead" is not a problem, i.e. it is not possible to
+ // see an offset smaller than the offset of the current instruction and for which no Label
+ // exist. Except for UNINITIALIZED type offsets. We solve this by parsing the stack map
+ // table without a full decoding (see below).
+ } else if ("StackMap".equals(attributeName)) {
+ if ((context.parsingOptions & SKIP_FRAMES) == 0) {
+ stackMapFrameOffset = currentOffset + 2;
+ stackMapTableEndOffset = currentOffset + attributeLength;
+ compressedFrames = false;
}
+ // IMPORTANT! Here we assume that the frames are ordered, as in the StackMapTable attribute,
+ // although this is not guaranteed by the attribute format. This allows an incremental
+ // extraction of the labels corresponding to this attribute (see the comment above for the
+ // StackMapTable attribute).
+ } else {
+ Attribute attribute =
+ readAttribute(
+ context.attributePrototypes,
+ attributeName,
+ currentOffset,
+ attributeLength,
+ charBuffer,
+ codeOffset,
+ labels);
+ attribute.nextAttribute = attributes;
+ attributes = attribute;
+ }
+ currentOffset += attributeLength;
+ }
- // visits the instructions
- u = codeStart;
- while (u < codeEnd) {
- int offset = u - codeStart;
-
- // visits the label and line number for this offset, if any
- Label l = labels[offset];
- if (l != null) {
- mv.visitLabel(l);
- if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) {
- mv.visitLineNumber(l.line, l);
- }
- }
+ // Initialize the context fields related to stack map frames, and generate the first
+ // (implicit) stack map frame, if needed.
+ final boolean expandFrames = (context.parsingOptions & EXPAND_FRAMES) != 0;
+ if (stackMapFrameOffset != 0) {
+ // The bytecode offset of the first explicit frame is not offset_delta + 1 but only
+ // offset_delta. Setting the implicit frame offset to -1 allows us to use of the
+ // "offset_delta + 1" rule in all cases.
+ context.currentFrameOffset = -1;
+ context.currentFrameType = 0;
+ context.currentFrameLocalCount = 0;
+ context.currentFrameLocalCountDelta = 0;
+ context.currentFrameLocalTypes = new Object[maxLocals];
+ context.currentFrameStackCount = 0;
+ context.currentFrameStackTypes = new Object[maxStack];
+ if (expandFrames) {
+ computeImplicitFrame(context);
+ }
+ // Find the labels for UNINITIALIZED frame types. Instead of decoding each element of the
+ // stack map table, we look for 3 consecutive bytes that "look like" an UNINITIALIZED type
+ // (tag ITEM_Uninitialized, offset within bytecode bounds, NEW instruction at this offset).
+ // We may find false positives (i.e. not real UNINITIALIZED types), but this should be rare,
+ // and the only consequence will be the creation of an unneeded label. This is better than
+ // creating a label for each NEW instruction, and faster than fully decoding the whole stack
+ // map table.
+ for (int offset = stackMapFrameOffset; offset < stackMapTableEndOffset - 2; ++offset) {
+ if (classBuffer[offset] == Frame.ITEM_UNINITIALIZED) {
+ int potentialBytecodeOffset = readUnsignedShort(offset + 1);
+ if (potentialBytecodeOffset >= 0
+ && potentialBytecodeOffset < codeLength
+ && (classBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF)
+ == Opcodes.NEW) {
+ createLabel(potentialBytecodeOffset, labels);
+ }
+ }
+ }
+ }
+ if (expandFrames && (context.parsingOptions & EXPAND_ASM_INSNS) != 0) {
+ // Expanding the ASM specific instructions can introduce F_INSERT frames, even if the method
+ // does not currently have any frame. These inserted frames must be computed by simulating the
+ // effect of the bytecode instructions, one by one, starting from the implicit first frame.
+ // For this, MethodWriter needs to know maxLocals before the first instruction is visited. To
+ // ensure this, we visit the implicit first frame here (passing only maxLocals - the rest is
+ // computed in MethodWriter).
+ methodVisitor.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null);
+ }
- // visits the frame for this offset, if any
- while (FRAMES && frame != null
- && (frame.offset == offset || frame.offset == -1)) {
- // if there is a frame for this offset, makes the visitor visit
- // it, and reads the next frame if there is one.
- if (frame.offset != -1) {
- if (!zip || unzip) {
- mv.visitFrame(Opcodes.F_NEW, frame.localCount,
- frame.local, frame.stackCount, frame.stack);
- } else {
- mv.visitFrame(frame.mode, frame.localDiff, frame.local,
- frame.stackCount, frame.stack);
- }
- }
- if (frameCount > 0) {
- stackMap = readFrame(stackMap, zip, unzip, labels, frame);
- --frameCount;
- } else {
- frame = null;
- }
- }
+ // Visit the bytecode instructions. First, introduce state variables for the incremental parsing
+ // of the type annotations.
+
+ // Index of the next runtime visible type annotation to read (in the
+ // visibleTypeAnnotationOffsets array).
+ int currentVisibleTypeAnnotationIndex = 0;
+ // The bytecode offset of the next runtime visible type annotation to read, or -1.
+ int currentVisibleTypeAnnotationBytecodeOffset =
+ getTypeAnnotationBytecodeOffset(visibleTypeAnnotationOffsets, 0);
+ // Index of the next runtime invisible type annotation to read (in the
+ // invisibleTypeAnnotationOffsets array).
+ int currentInvisibleTypeAnnotationIndex = 0;
+ // The bytecode offset of the next runtime invisible type annotation to read, or -1.
+ int currentInvisibleTypeAnnotationBytecodeOffset =
+ getTypeAnnotationBytecodeOffset(invisibleTypeAnnotationOffsets, 0);
+
+ // Whether a F_INSERT stack map frame must be inserted before the current instruction.
+ boolean insertFrame = false;
+
+ // The delta to subtract from a goto_w or jsr_w opcode to get the corresponding goto or jsr
+ // opcode, or 0 if goto_w and jsr_w must be left unchanged (i.e. when expanding ASM specific
+ // instructions).
+ final int wideJumpOpcodeDelta =
+ (context.parsingOptions & EXPAND_ASM_INSNS) == 0 ? Constants.WIDE_JUMP_OPCODE_DELTA : 0;
+
+ currentOffset = bytecodeStartOffset;
+ while (currentOffset < bytecodeEndOffset) {
+ final int currentBytecodeOffset = currentOffset - bytecodeStartOffset;
+
+ // Visit the label and the line number(s) for this bytecode offset, if any.
+ Label currentLabel = labels[currentBytecodeOffset];
+ if (currentLabel != null) {
+ currentLabel.accept(methodVisitor, (context.parsingOptions & SKIP_DEBUG) == 0);
+ }
+
+ // Visit the stack map frame for this bytecode offset, if any.
+ while (stackMapFrameOffset != 0
+ && (context.currentFrameOffset == currentBytecodeOffset
+ || context.currentFrameOffset == -1)) {
+ // If there is a stack map frame for this offset, make methodVisitor visit it, and read the
+ // next stack map frame if there is one.
+ if (context.currentFrameOffset != -1) {
+ if (!compressedFrames || expandFrames) {
+ methodVisitor.visitFrame(
+ Opcodes.F_NEW,
+ context.currentFrameLocalCount,
+ context.currentFrameLocalTypes,
+ context.currentFrameStackCount,
+ context.currentFrameStackTypes);
+ } else {
+ methodVisitor.visitFrame(
+ context.currentFrameType,
+ context.currentFrameLocalCountDelta,
+ context.currentFrameLocalTypes,
+ context.currentFrameStackCount,
+ context.currentFrameStackTypes);
+ }
+ // Since there is already a stack map frame for this bytecode offset, there is no need to
+ // insert a new one.
+ insertFrame = false;
+ }
+ if (stackMapFrameOffset < stackMapTableEndOffset) {
+ stackMapFrameOffset =
+ readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context);
+ } else {
+ stackMapFrameOffset = 0;
+ }
+ }
- // visits the instruction at this offset
- int opcode = b[u] & 0xFF;
- switch (ClassWriter.TYPE[opcode]) {
- case ClassWriter.NOARG_INSN:
- mv.visitInsn(opcode);
- u += 1;
- break;
- case ClassWriter.IMPLVAR_INSN:
- if (opcode > Opcodes.ISTORE) {
- opcode -= 59; // ISTORE_0
- mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2),
- opcode & 0x3);
- } else {
- opcode -= 26; // ILOAD_0
- mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
- }
- u += 1;
- break;
- case ClassWriter.LABEL_INSN:
- mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]);
- u += 3;
- break;
- case ClassWriter.LABELW_INSN:
- mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]);
- u += 5;
- break;
- case ClassWriter.WIDE_INSN:
- opcode = b[u + 1] & 0xFF;
- if (opcode == Opcodes.IINC) {
- mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4));
- u += 6;
- } else {
- mv.visitVarInsn(opcode, readUnsignedShort(u + 2));
- u += 4;
- }
- break;
- case ClassWriter.TABL_INSN: {
- // skips 0 to 3 padding bytes
- u = u + 4 - (offset & 3);
- // reads instruction
- int label = offset + readInt(u);
- int min = readInt(u + 4);
- int max = readInt(u + 8);
- Label[] table = new Label[max - min + 1];
- u += 12;
- for (int i = 0; i < table.length; ++i) {
- table[i] = labels[offset + readInt(u)];
- u += 4;
- }
- mv.visitTableSwitchInsn(min, max, labels[label], table);
- break;
- }
- case ClassWriter.LOOK_INSN: {
- // skips 0 to 3 padding bytes
- u = u + 4 - (offset & 3);
- // reads instruction
- int label = offset + readInt(u);
- int len = readInt(u + 4);
- int[] keys = new int[len];
- Label[] values = new Label[len];
- u += 8;
- for (int i = 0; i < len; ++i) {
- keys[i] = readInt(u);
- values[i] = labels[offset + readInt(u + 4)];
- u += 8;
- }
- mv.visitLookupSwitchInsn(labels[label], keys, values);
- break;
+ // Insert a stack map frame for this bytecode offset, if requested by setting insertFrame to
+ // true during the previous iteration. The actual frame content is computed in MethodWriter.
+ if (insertFrame) {
+ if ((context.parsingOptions & EXPAND_FRAMES) != 0) {
+ methodVisitor.visitFrame(Constants.F_INSERT, 0, null, 0, null);
+ }
+ insertFrame = false;
+ }
+
+ // Visit the instruction at this bytecode offset.
+ int opcode = classBuffer[currentOffset] & 0xFF;
+ switch (opcode) {
+ case Opcodes.NOP:
+ case Opcodes.ACONST_NULL:
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ case Opcodes.IALOAD:
+ case Opcodes.LALOAD:
+ case Opcodes.FALOAD:
+ case Opcodes.DALOAD:
+ case Opcodes.AALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ case Opcodes.IASTORE:
+ case Opcodes.LASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.DASTORE:
+ case Opcodes.AASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.POP:
+ case Opcodes.POP2:
+ case Opcodes.DUP:
+ case Opcodes.DUP_X1:
+ case Opcodes.DUP_X2:
+ case Opcodes.DUP2:
+ case Opcodes.DUP2_X1:
+ case Opcodes.DUP2_X2:
+ case Opcodes.SWAP:
+ case Opcodes.IADD:
+ case Opcodes.LADD:
+ case Opcodes.FADD:
+ case Opcodes.DADD:
+ case Opcodes.ISUB:
+ case Opcodes.LSUB:
+ case Opcodes.FSUB:
+ case Opcodes.DSUB:
+ case Opcodes.IMUL:
+ case Opcodes.LMUL:
+ case Opcodes.FMUL:
+ case Opcodes.DMUL:
+ case Opcodes.IDIV:
+ case Opcodes.LDIV:
+ case Opcodes.FDIV:
+ case Opcodes.DDIV:
+ case Opcodes.IREM:
+ case Opcodes.LREM:
+ case Opcodes.FREM:
+ case Opcodes.DREM:
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ case Opcodes.ISHL:
+ case Opcodes.LSHL:
+ case Opcodes.ISHR:
+ case Opcodes.LSHR:
+ case Opcodes.IUSHR:
+ case Opcodes.LUSHR:
+ case Opcodes.IAND:
+ case Opcodes.LAND:
+ case Opcodes.IOR:
+ case Opcodes.LOR:
+ case Opcodes.IXOR:
+ case Opcodes.LXOR:
+ case Opcodes.I2L:
+ case Opcodes.I2F:
+ case Opcodes.I2D:
+ case Opcodes.L2I:
+ case Opcodes.L2F:
+ case Opcodes.L2D:
+ case Opcodes.F2I:
+ case Opcodes.F2L:
+ case Opcodes.F2D:
+ case Opcodes.D2I:
+ case Opcodes.D2L:
+ case Opcodes.D2F:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ case Opcodes.LCMP:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ case Opcodes.IRETURN:
+ case Opcodes.LRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.RETURN:
+ case Opcodes.ARRAYLENGTH:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
+ methodVisitor.visitInsn(opcode);
+ currentOffset += 1;
+ break;
+ case Constants.ILOAD_0:
+ case Constants.ILOAD_1:
+ case Constants.ILOAD_2:
+ case Constants.ILOAD_3:
+ case Constants.LLOAD_0:
+ case Constants.LLOAD_1:
+ case Constants.LLOAD_2:
+ case Constants.LLOAD_3:
+ case Constants.FLOAD_0:
+ case Constants.FLOAD_1:
+ case Constants.FLOAD_2:
+ case Constants.FLOAD_3:
+ case Constants.DLOAD_0:
+ case Constants.DLOAD_1:
+ case Constants.DLOAD_2:
+ case Constants.DLOAD_3:
+ case Constants.ALOAD_0:
+ case Constants.ALOAD_1:
+ case Constants.ALOAD_2:
+ case Constants.ALOAD_3:
+ opcode -= Constants.ILOAD_0;
+ methodVisitor.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
+ currentOffset += 1;
+ break;
+ case Constants.ISTORE_0:
+ case Constants.ISTORE_1:
+ case Constants.ISTORE_2:
+ case Constants.ISTORE_3:
+ case Constants.LSTORE_0:
+ case Constants.LSTORE_1:
+ case Constants.LSTORE_2:
+ case Constants.LSTORE_3:
+ case Constants.FSTORE_0:
+ case Constants.FSTORE_1:
+ case Constants.FSTORE_2:
+ case Constants.FSTORE_3:
+ case Constants.DSTORE_0:
+ case Constants.DSTORE_1:
+ case Constants.DSTORE_2:
+ case Constants.DSTORE_3:
+ case Constants.ASTORE_0:
+ case Constants.ASTORE_1:
+ case Constants.ASTORE_2:
+ case Constants.ASTORE_3:
+ opcode -= Constants.ISTORE_0;
+ methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3);
+ currentOffset += 1;
+ break;
+ case Opcodes.IFEQ:
+ case Opcodes.IFNE:
+ case Opcodes.IFLT:
+ case Opcodes.IFGE:
+ case Opcodes.IFGT:
+ case Opcodes.IFLE:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ICMPLT:
+ case Opcodes.IF_ICMPGE:
+ case Opcodes.IF_ICMPGT:
+ case Opcodes.IF_ICMPLE:
+ case Opcodes.IF_ACMPEQ:
+ case Opcodes.IF_ACMPNE:
+ case Opcodes.GOTO:
+ case Opcodes.JSR:
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
+ methodVisitor.visitJumpInsn(
+ opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]);
+ currentOffset += 3;
+ break;
+ case Constants.GOTO_W:
+ case Constants.JSR_W:
+ methodVisitor.visitJumpInsn(
+ opcode - wideJumpOpcodeDelta,
+ labels[currentBytecodeOffset + readInt(currentOffset + 1)]);
+ currentOffset += 5;
+ break;
+ case Constants.ASM_IFEQ:
+ case Constants.ASM_IFNE:
+ case Constants.ASM_IFLT:
+ case Constants.ASM_IFGE:
+ case Constants.ASM_IFGT:
+ case Constants.ASM_IFLE:
+ case Constants.ASM_IF_ICMPEQ:
+ case Constants.ASM_IF_ICMPNE:
+ case Constants.ASM_IF_ICMPLT:
+ case Constants.ASM_IF_ICMPGE:
+ case Constants.ASM_IF_ICMPGT:
+ case Constants.ASM_IF_ICMPLE:
+ case Constants.ASM_IF_ACMPEQ:
+ case Constants.ASM_IF_ACMPNE:
+ case Constants.ASM_GOTO:
+ case Constants.ASM_JSR:
+ case Constants.ASM_IFNULL:
+ case Constants.ASM_IFNONNULL:
+ {
+ // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO
+ // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:...,
+ // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and
+ // where designates the instruction just after the GOTO_W.
+ // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and
+ // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL.
+ opcode =
+ opcode < Constants.ASM_IFNULL
+ ? opcode - Constants.ASM_OPCODE_DELTA
+ : opcode - Constants.ASM_IFNULL_OPCODE_DELTA;
+ Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)];
+ if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
+ // Replace GOTO with GOTO_W and JSR with JSR_W.
+ methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target);
+ } else {
+ // Compute the "opposite" of opcode. This can be done by flipping the least
+ // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ
+ // (with a pre and post offset by 1).
+ opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1;
+ Label endif = createLabel(currentBytecodeOffset + 3, labels);
+ methodVisitor.visitJumpInsn(opcode, endif);
+ methodVisitor.visitJumpInsn(Constants.GOTO_W, target);
+ // endif designates the instruction just after GOTO_W, and is visited as part of the
+ // next instruction. Since it is a jump target, we need to insert a frame here.
+ insertFrame = true;
}
- case ClassWriter.VAR_INSN:
- mv.visitVarInsn(opcode, b[u + 1] & 0xFF);
- u += 2;
- break;
- case ClassWriter.SBYTE_INSN:
- mv.visitIntInsn(opcode, b[u + 1]);
- u += 2;
- break;
- case ClassWriter.SHORT_INSN:
- mv.visitIntInsn(opcode, readShort(u + 1));
- u += 3;
- break;
- case ClassWriter.LDC_INSN:
- mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c));
- u += 2;
- break;
- case ClassWriter.LDCW_INSN:
- mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c));
- u += 3;
- break;
- case ClassWriter.FIELDORMETH_INSN:
- case ClassWriter.ITFMETH_INSN: {
- int cpIndex = items[readUnsignedShort(u + 1)];
- String iowner = readClass(cpIndex, c);
- cpIndex = items[readUnsignedShort(cpIndex + 2)];
- String iname = readUTF8(cpIndex, c);
- String idesc = readUTF8(cpIndex + 2, c);
- if (opcode < Opcodes.INVOKEVIRTUAL) {
- mv.visitFieldInsn(opcode, iowner, iname, idesc);
- } else {
- mv.visitMethodInsn(opcode, iowner, iname, idesc);
- }
- if (opcode == Opcodes.INVOKEINTERFACE) {
- u += 5;
- } else {
- u += 3;
- }
- break;
+ currentOffset += 3;
+ break;
+ }
+ case Constants.ASM_GOTO_W:
+ // Replace ASM_GOTO_W with GOTO_W.
+ methodVisitor.visitJumpInsn(
+ Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]);
+ // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns
+ // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame
+ // here.
+ insertFrame = true;
+ currentOffset += 5;
+ break;
+ case Constants.WIDE:
+ opcode = classBuffer[currentOffset + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ methodVisitor.visitIincInsn(
+ readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4));
+ currentOffset += 6;
+ } else {
+ methodVisitor.visitVarInsn(opcode, readUnsignedShort(currentOffset + 2));
+ currentOffset += 4;
+ }
+ break;
+ case Opcodes.TABLESWITCH:
+ {
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (currentBytecodeOffset & 3);
+ // Read the instruction.
+ Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)];
+ int low = readInt(currentOffset + 4);
+ int high = readInt(currentOffset + 8);
+ currentOffset += 12;
+ Label[] table = new Label[high - low + 1];
+ for (int i = 0; i < table.length; ++i) {
+ table[i] = labels[currentBytecodeOffset + readInt(currentOffset)];
+ currentOffset += 4;
}
- case ClassWriter.INDYMETH_INSN: {
- int cpIndex = items[readUnsignedShort(u + 1)];
- int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)];
- Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
- int bsmArgCount = readUnsignedShort(bsmIndex + 2);
- Object[] bsmArgs = new Object[bsmArgCount];
- bsmIndex += 4;
- for (int i = 0; i < bsmArgCount; i++) {
- bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c);
- bsmIndex += 2;
- }
- cpIndex = items[readUnsignedShort(cpIndex + 2)];
- String iname = readUTF8(cpIndex, c);
- String idesc = readUTF8(cpIndex + 2, c);
- mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs);
- u += 5;
- break;
+ methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table);
+ break;
+ }
+ case Opcodes.LOOKUPSWITCH:
+ {
+ // Skip 0 to 3 padding bytes.
+ currentOffset += 4 - (currentBytecodeOffset & 3);
+ // Read the instruction.
+ Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)];
+ int numPairs = readInt(currentOffset + 4);
+ currentOffset += 8;
+ int[] keys = new int[numPairs];
+ Label[] values = new Label[numPairs];
+ for (int i = 0; i < numPairs; ++i) {
+ keys[i] = readInt(currentOffset);
+ values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)];
+ currentOffset += 8;
}
- case ClassWriter.TYPE_INSN:
- mv.visitTypeInsn(opcode, readClass(u + 1, c));
- u += 3;
- break;
- case ClassWriter.IINC_INSN:
- mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]);
- u += 3;
- break;
- // case MANA_INSN:
- default:
- mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF);
- u += 4;
- break;
+ methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values);
+ break;
+ }
+ case Opcodes.ILOAD:
+ case Opcodes.LLOAD:
+ case Opcodes.FLOAD:
+ case Opcodes.DLOAD:
+ case Opcodes.ALOAD:
+ case Opcodes.ISTORE:
+ case Opcodes.LSTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.DSTORE:
+ case Opcodes.ASTORE:
+ case Opcodes.RET:
+ methodVisitor.visitVarInsn(opcode, classBuffer[currentOffset + 1] & 0xFF);
+ currentOffset += 2;
+ break;
+ case Opcodes.BIPUSH:
+ case Opcodes.NEWARRAY:
+ methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]);
+ currentOffset += 2;
+ break;
+ case Opcodes.SIPUSH:
+ methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1));
+ currentOffset += 3;
+ break;
+ case Opcodes.LDC:
+ methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer));
+ currentOffset += 2;
+ break;
+ case Constants.LDC_W:
+ case Constants.LDC2_W:
+ methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer));
+ currentOffset += 3;
+ break;
+ case Opcodes.GETSTATIC:
+ case Opcodes.PUTSTATIC:
+ case Opcodes.GETFIELD:
+ case Opcodes.PUTFIELD:
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKESPECIAL:
+ case Opcodes.INVOKESTATIC:
+ case Opcodes.INVOKEINTERFACE:
+ {
+ int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
+ String owner = readClass(cpInfoOffset, charBuffer);
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ if (opcode < Opcodes.INVOKEVIRTUAL) {
+ methodVisitor.visitFieldInsn(opcode, owner, name, descriptor);
+ } else {
+ boolean isInterface =
+ classBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
+ methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
- }
- if (labels[codeLength] != null) {
- mv.visitLabel(labels[codeLength]);
- }
-
- // visits the local variable tables
- if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) {
- int[] typeTable = null;
- if (varTypeTable != 0) {
- u = varTypeTable + 2;
- typeTable = new int[readUnsignedShort(varTypeTable) * 3];
- for (int i = typeTable.length; i > 0;) {
- typeTable[--i] = u + 6; // signature
- typeTable[--i] = readUnsignedShort(u + 8); // index
- typeTable[--i] = readUnsignedShort(u); // start
- u += 10;
- }
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ currentOffset += 5;
+ } else {
+ currentOffset += 3;
}
- u = varTable + 2;
- for (int i = readUnsignedShort(varTable); i > 0; --i) {
- int start = readUnsignedShort(u);
- int length = readUnsignedShort(u + 2);
- int index = readUnsignedShort(u + 8);
- String vsignature = null;
- if (typeTable != null) {
- for (int j = 0; j < typeTable.length; j += 3) {
- if (typeTable[j] == start && typeTable[j + 1] == index) {
- vsignature = readUTF8(typeTable[j + 2], c);
- break;
- }
- }
- }
- mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c),
- vsignature, labels[start], labels[start + length],
- index);
- u += 10;
+ break;
+ }
+ case Opcodes.INVOKEDYNAMIC:
+ {
+ int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)];
+ Handle handle =
+ (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ Object[] bootstrapMethodArguments =
+ new Object[readUnsignedShort(bootstrapMethodOffset + 2)];
+ bootstrapMethodOffset += 4;
+ for (int i = 0; i < bootstrapMethodArguments.length; i++) {
+ bootstrapMethodArguments[i] =
+ readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ bootstrapMethodOffset += 2;
}
+ methodVisitor.visitInvokeDynamicInsn(
+ name, descriptor, handle, bootstrapMethodArguments);
+ currentOffset += 5;
+ break;
+ }
+ case Opcodes.NEW:
+ case Opcodes.ANEWARRAY:
+ case Opcodes.CHECKCAST:
+ case Opcodes.INSTANCEOF:
+ methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer));
+ currentOffset += 3;
+ break;
+ case Opcodes.IINC:
+ methodVisitor.visitIincInsn(
+ classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]);
+ currentOffset += 3;
+ break;
+ case Opcodes.MULTIANEWARRAY:
+ methodVisitor.visitMultiANewArrayInsn(
+ readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF);
+ currentOffset += 4;
+ break;
+ default:
+ throw new AssertionError();
+ }
+
+ // Visit the runtime visible instruction annotations, if any.
+ while (visibleTypeAnnotationOffsets != null
+ && currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length
+ && currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
+ if (currentVisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) {
+ // Parse the target_type, target_info and target_path fields.
+ int currentAnnotationOffset =
+ readTypeAnnotationTarget(
+ context, visibleTypeAnnotationOffsets[currentVisibleTypeAnnotationIndex]);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ readElementValues(
+ methodVisitor.visitInsnAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
}
-
- // visits the code attributes
- while (attributes != null) {
- Attribute attr = attributes.next;
- attributes.next = null;
- mv.visitAttribute(attributes);
- attributes = attr;
+ currentVisibleTypeAnnotationBytecodeOffset =
+ getTypeAnnotationBytecodeOffset(
+ visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex);
+ }
+
+ // Visit the runtime invisible instruction annotations, if any.
+ while (invisibleTypeAnnotationOffsets != null
+ && currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length
+ && currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) {
+ if (currentInvisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) {
+ // Parse the target_type, target_info and target_path fields.
+ int currentAnnotationOffset =
+ readTypeAnnotationTarget(
+ context, invisibleTypeAnnotationOffsets[currentInvisibleTypeAnnotationIndex]);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer);
+ currentAnnotationOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ readElementValues(
+ methodVisitor.visitInsnAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentAnnotationOffset,
+ /* named = */ true,
+ charBuffer);
}
+ currentInvisibleTypeAnnotationBytecodeOffset =
+ getTypeAnnotationBytecodeOffset(
+ invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex);
+ }
+ }
+ if (labels[codeLength] != null) {
+ methodVisitor.visitLabel(labels[codeLength]);
+ }
- // visits the max stack and max locals values
- mv.visitMaxs(maxStack, maxLocals);
- }
-
- /**
- * Reads parameter annotations and makes the given visitor visit them.
- *
- * @param v
- * start offset in {@link #b b} of the annotations to be read.
- * @param desc
- * the method descriptor.
- * @param buf
- * buffer to be used to call {@link #readUTF8 readUTF8},
- * {@link #readClass(int,char[]) readClass} or {@link #readConst
- * readConst}.
- * @param visible
- * true if the annotations to be read are visible at
- * runtime.
- * @param mv
- * the visitor that must visit the annotations.
- */
- private void readParameterAnnotations(int v, final String desc,
- final char[] buf, final boolean visible, final MethodVisitor mv) {
- int i;
- int n = b[v++] & 0xFF;
- // workaround for a bug in javac (javac compiler generates a parameter
- // annotation array whose size is equal to the number of parameters in
- // the Java source file, while it should generate an array whose size is
- // equal to the number of parameters in the method descriptor - which
- // includes the synthetic parameters added by the compiler). This work-
- // around supposes that the synthetic parameters are the first ones.
- int synthetics = Type.getArgumentTypes(desc).length - n;
- AnnotationVisitor av;
- for (i = 0; i < synthetics; ++i) {
- // virtual annotation to detect synthetic parameters in MethodWriter
- av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false);
- if (av != null) {
- av.visitEnd();
- }
+ // Visit LocalVariableTable and LocalVariableTypeTable attributes.
+ if (localVariableTableOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) {
+ // The (start_pc, index, signature_index) fields of each entry of the LocalVariableTypeTable.
+ int[] typeTable = null;
+ if (localVariableTypeTableOffset != 0) {
+ typeTable = new int[readUnsignedShort(localVariableTypeTableOffset) * 3];
+ currentOffset = localVariableTypeTableOffset + 2;
+ int typeTableIndex = typeTable.length;
+ while (typeTableIndex > 0) {
+ // Store the offset of 'signature_index', and the value of 'index' and 'start_pc'.
+ typeTable[--typeTableIndex] = currentOffset + 6;
+ typeTable[--typeTableIndex] = readUnsignedShort(currentOffset + 8);
+ typeTable[--typeTableIndex] = readUnsignedShort(currentOffset);
+ currentOffset += 10;
}
- for (; i < n + synthetics; ++i) {
- int j = readUnsignedShort(v);
- v += 2;
- for (; j > 0; --j) {
- av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible);
- v = readAnnotationValues(v + 2, buf, true, av);
+ }
+ int localVariableTableLength = readUnsignedShort(localVariableTableOffset);
+ currentOffset = localVariableTableOffset + 2;
+ while (localVariableTableLength-- > 0) {
+ int startPc = readUnsignedShort(currentOffset);
+ int length = readUnsignedShort(currentOffset + 2);
+ String name = readUTF8(currentOffset + 4, charBuffer);
+ String descriptor = readUTF8(currentOffset + 6, charBuffer);
+ int index = readUnsignedShort(currentOffset + 8);
+ currentOffset += 10;
+ String signature = null;
+ if (typeTable != null) {
+ for (int i = 0; i < typeTable.length; i += 3) {
+ if (typeTable[i] == startPc && typeTable[i + 1] == index) {
+ signature = readUTF8(typeTable[i + 2], charBuffer);
+ break;
}
+ }
}
+ methodVisitor.visitLocalVariable(
+ name, descriptor, signature, labels[startPc], labels[startPc + length], index);
+ }
}
- /**
- * Reads the values of an annotation and makes the given visitor visit them.
- *
- * @param v
- * the start offset in {@link #b b} of the values to be read
- * (including the unsigned short that gives the number of
- * values).
- * @param buf
- * buffer to be used to call {@link #readUTF8 readUTF8},
- * {@link #readClass(int,char[]) readClass} or {@link #readConst
- * readConst}.
- * @param named
- * if the annotation values are named or not.
- * @param av
- * the visitor that must visit the values.
- * @return the end offset of the annotation values.
- */
- private int readAnnotationValues(int v, final char[] buf,
- final boolean named, final AnnotationVisitor av) {
- int i = readUnsignedShort(v);
- v += 2;
- if (named) {
- for (; i > 0; --i) {
- v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
- }
- } else {
- for (; i > 0; --i) {
- v = readAnnotationValue(v, buf, null, av);
- }
+ // Visit the local variable type annotations of the RuntimeVisibleTypeAnnotations attribute.
+ if (visibleTypeAnnotationOffsets != null) {
+ for (int typeAnnotationOffset : visibleTypeAnnotationOffsets) {
+ int targetType = readByte(typeAnnotationOffset);
+ if (targetType == TypeReference.LOCAL_VARIABLE
+ || targetType == TypeReference.RESOURCE_VARIABLE) {
+ // Parse the target_type, target_info and target_path fields.
+ currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentOffset, charBuffer);
+ currentOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ readElementValues(
+ methodVisitor.visitLocalVariableAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ context.currentLocalVariableAnnotationRangeStarts,
+ context.currentLocalVariableAnnotationRangeEnds,
+ context.currentLocalVariableAnnotationRangeIndices,
+ annotationDescriptor,
+ /* visible = */ true),
+ currentOffset,
+ /* named = */ true,
+ charBuffer);
}
- if (av != null) {
- av.visitEnd();
+ }
+ }
+
+ // Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute.
+ if (invisibleTypeAnnotationOffsets != null) {
+ for (int typeAnnotationOffset : invisibleTypeAnnotationOffsets) {
+ int targetType = readByte(typeAnnotationOffset);
+ if (targetType == TypeReference.LOCAL_VARIABLE
+ || targetType == TypeReference.RESOURCE_VARIABLE) {
+ // Parse the target_type, target_info and target_path fields.
+ currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset);
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentOffset, charBuffer);
+ currentOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ readElementValues(
+ methodVisitor.visitLocalVariableAnnotation(
+ context.currentTypeAnnotationTarget,
+ context.currentTypeAnnotationTargetPath,
+ context.currentLocalVariableAnnotationRangeStarts,
+ context.currentLocalVariableAnnotationRangeEnds,
+ context.currentLocalVariableAnnotationRangeIndices,
+ annotationDescriptor,
+ /* visible = */ false),
+ currentOffset,
+ /* named = */ true,
+ charBuffer);
}
- return v;
- }
-
- /**
- * Reads a value of an annotation and makes the given visitor visit it.
- *
- * @param v
- * the start offset in {@link #b b} of the value to be read
- * (not including the value name constant pool index).
- * @param buf
- * buffer to be used to call {@link #readUTF8 readUTF8},
- * {@link #readClass(int,char[]) readClass} or {@link #readConst
- * readConst}.
- * @param name
- * the name of the value to be read.
- * @param av
- * the visitor that must visit the value.
- * @return the end offset of the annotation value.
- */
- private int readAnnotationValue(int v, final char[] buf, final String name,
- final AnnotationVisitor av) {
- int i;
- if (av == null) {
- switch (b[v] & 0xFF) {
- case 'e': // enum_const_value
- return v + 5;
- case '@': // annotation_value
- return readAnnotationValues(v + 3, buf, true, null);
- case '[': // array_value
- return readAnnotationValues(v + 1, buf, false, null);
- default:
- return v + 3;
- }
+ }
+ }
+
+ // Visit the non standard attributes.
+ while (attributes != null) {
+ // Copy and reset the nextAttribute field so that it can also be used in MethodWriter.
+ Attribute nextAttribute = attributes.nextAttribute;
+ attributes.nextAttribute = null;
+ methodVisitor.visitAttribute(attributes);
+ attributes = nextAttribute;
+ }
+
+ // Visit the max stack and max locals values.
+ methodVisitor.visitMaxs(maxStack, maxLocals);
+ }
+
+ /**
+ * Returns the label corresponding to the given bytecode offset. The default implementation of
+ * this method creates a label for the given offset if it has not been already created.
+ *
+ * @param bytecodeOffset a bytecode offset in a method.
+ * @param labels the already created labels, indexed by their offset. If a label already exists
+ * for bytecodeOffset this method must not create a new one. Otherwise it must store the new
+ * label in this array.
+ * @return a non null Label, which must be equal to labels[bytecodeOffset].
+ */
+ protected Label readLabel(final int bytecodeOffset, final Label[] labels) {
+ if (labels[bytecodeOffset] == null) {
+ labels[bytecodeOffset] = new Label();
+ }
+ return labels[bytecodeOffset];
+ }
+
+ /**
+ * Creates a label without the {@link Label#FLAG_DEBUG_ONLY} flag set, for the given bytecode
+ * offset. The label is created with a call to {@link #readLabel} and its {@link
+ * Label#FLAG_DEBUG_ONLY} flag is cleared.
+ *
+ * @param bytecodeOffset a bytecode offset in a method.
+ * @param labels the already created labels, indexed by their offset.
+ * @return a Label without the {@link Label#FLAG_DEBUG_ONLY} flag set.
+ */
+ private Label createLabel(final int bytecodeOffset, final Label[] labels) {
+ Label label = readLabel(bytecodeOffset, labels);
+ label.flags &= ~Label.FLAG_DEBUG_ONLY;
+ return label;
+ }
+
+ /**
+ * Creates a label with the {@link Label#FLAG_DEBUG_ONLY} flag set, if there is no already
+ * existing label for the given bytecode offset (otherwise does nothing). The label is created
+ * with a call to {@link #readLabel}.
+ *
+ * @param bytecodeOffset a bytecode offset in a method.
+ * @param labels the already created labels, indexed by their offset.
+ */
+ private void createDebugLabel(final int bytecodeOffset, final Label[] labels) {
+ if (labels[bytecodeOffset] == null) {
+ readLabel(bytecodeOffset, labels).flags |= Label.FLAG_DEBUG_ONLY;
+ }
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse annotations, type annotations and parameter annotations
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Parses a Runtime[In]VisibleTypeAnnotations attribute to find the offset of each type_annotation
+ * entry it contains, to find the corresponding labels, and to visit the try catch block
+ * annotations.
+ *
+ * @param methodVisitor the method visitor to be used to visit the try catch block annotations.
+ * @param context information about the class being parsed.
+ * @param runtimeTypeAnnotationsOffset the start offset of a Runtime[In]VisibleTypeAnnotations
+ * attribute, excluding the attribute_info's attribute_name_index and attribute_length fields.
+ * @param visible true if the attribute to parse is a RuntimeVisibleTypeAnnotations attribute,
+ * false it is a RuntimeInvisibleTypeAnnotations attribute.
+ * @return the start offset of each entry of the Runtime[In]VisibleTypeAnnotations_attribute's
+ * 'annotations' array field.
+ */
+ private int[] readTypeAnnotations(
+ final MethodVisitor methodVisitor,
+ final Context context,
+ final int runtimeTypeAnnotationsOffset,
+ final boolean visible) {
+ char[] charBuffer = context.charBuffer;
+ int currentOffset = runtimeTypeAnnotationsOffset;
+ // Read the num_annotations field and create an array to store the type_annotation offsets.
+ int[] typeAnnotationsOffsets = new int[readUnsignedShort(currentOffset)];
+ currentOffset += 2;
+ // Parse the 'annotations' array field.
+ for (int i = 0; i < typeAnnotationsOffsets.length; ++i) {
+ typeAnnotationsOffsets[i] = currentOffset;
+ // Parse the type_annotation's target_type and the target_info fields. The size of the
+ // target_info field depends on the value of target_type.
+ int targetType = readInt(currentOffset);
+ switch (targetType >>> 24) {
+ case TypeReference.LOCAL_VARIABLE:
+ case TypeReference.RESOURCE_VARIABLE:
+ // A localvar_target has a variable size, which depends on the value of their table_length
+ // field. It also references bytecode offsets, for which we need labels.
+ int tableLength = readUnsignedShort(currentOffset + 1);
+ currentOffset += 3;
+ while (tableLength-- > 0) {
+ int startPc = readUnsignedShort(currentOffset);
+ int length = readUnsignedShort(currentOffset + 2);
+ // Skip the index field (2 bytes).
+ currentOffset += 6;
+ createLabel(startPc, context.currentMethodLabels);
+ createLabel(startPc + length, context.currentMethodLabels);
+ }
+ break;
+ case TypeReference.CAST:
+ case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
+ case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT:
+ case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
+ case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT:
+ currentOffset += 4;
+ break;
+ case TypeReference.CLASS_EXTENDS:
+ case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
+ case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
+ case TypeReference.THROWS:
+ case TypeReference.EXCEPTION_PARAMETER:
+ case TypeReference.INSTANCEOF:
+ case TypeReference.NEW:
+ case TypeReference.CONSTRUCTOR_REFERENCE:
+ case TypeReference.METHOD_REFERENCE:
+ currentOffset += 3;
+ break;
+ case TypeReference.CLASS_TYPE_PARAMETER:
+ case TypeReference.METHOD_TYPE_PARAMETER:
+ case TypeReference.METHOD_FORMAL_PARAMETER:
+ case TypeReference.FIELD:
+ case TypeReference.METHOD_RETURN:
+ case TypeReference.METHOD_RECEIVER:
+ default:
+ // TypeReference type which can't be used in Code attribute, or which is unknown.
+ throw new IllegalArgumentException();
+ }
+ // Parse the rest of the type_annotation structure, starting with the target_path structure
+ // (whose size depends on its path_length field).
+ int pathLength = readByte(currentOffset);
+ if ((targetType >>> 24) == TypeReference.EXCEPTION_PARAMETER) {
+ // Parse the target_path structure and create a corresponding TypePath.
+ TypePath path = pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset);
+ currentOffset += 1 + 2 * pathLength;
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentOffset, charBuffer);
+ currentOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentOffset =
+ readElementValues(
+ methodVisitor.visitTryCatchAnnotation(
+ targetType & 0xFFFFFF00, path, annotationDescriptor, visible),
+ currentOffset,
+ /* named = */ true,
+ charBuffer);
+ } else {
+ // We don't want to visit the other target_type annotations, so we just skip them (which
+ // requires some parsing because the element_value_pairs array has a variable size). First,
+ // skip the target_path structure:
+ currentOffset += 3 + 2 * pathLength;
+ // Then skip the num_element_value_pairs and element_value_pairs fields (by reading them
+ // with a null AnnotationVisitor).
+ currentOffset =
+ readElementValues(
+ /* annotationVisitor = */ null, currentOffset, /* named = */ true, charBuffer);
+ }
+ }
+ return typeAnnotationsOffsets;
+ }
+
+ /**
+ * Returns the bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or
+ * -1 if there is no such type_annotation of if it does not have a bytecode offset.
+ *
+ * @param typeAnnotationOffsets the offset of each 'type_annotation' entry in a
+ * Runtime[In]VisibleTypeAnnotations attribute, or {@literal null}.
+ * @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets.
+ * @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1
+ * if there is no such type_annotation of if it does not have a bytecode offset.
+ */
+ private int getTypeAnnotationBytecodeOffset(
+ final int[] typeAnnotationOffsets, final int typeAnnotationIndex) {
+ if (typeAnnotationOffsets == null
+ || typeAnnotationIndex >= typeAnnotationOffsets.length
+ || readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) {
+ return -1;
+ }
+ return readUnsignedShort(typeAnnotationOffsets[typeAnnotationIndex] + 1);
+ }
+
+ /**
+ * Parses the header of a JVMS type_annotation structure to extract its target_type, target_info
+ * and target_path (the result is stored in the given context), and returns the start offset of
+ * the rest of the type_annotation structure.
+ *
+ * @param context information about the class being parsed. This is where the extracted
+ * target_type and target_path must be stored.
+ * @param typeAnnotationOffset the start offset of a type_annotation structure.
+ * @return the start offset of the rest of the type_annotation structure.
+ */
+ private int readTypeAnnotationTarget(final Context context, final int typeAnnotationOffset) {
+ int currentOffset = typeAnnotationOffset;
+ // Parse and store the target_type structure.
+ int targetType = readInt(typeAnnotationOffset);
+ switch (targetType >>> 24) {
+ case TypeReference.CLASS_TYPE_PARAMETER:
+ case TypeReference.METHOD_TYPE_PARAMETER:
+ case TypeReference.METHOD_FORMAL_PARAMETER:
+ targetType &= 0xFFFF0000;
+ currentOffset += 2;
+ break;
+ case TypeReference.FIELD:
+ case TypeReference.METHOD_RETURN:
+ case TypeReference.METHOD_RECEIVER:
+ targetType &= 0xFF000000;
+ currentOffset += 1;
+ break;
+ case TypeReference.LOCAL_VARIABLE:
+ case TypeReference.RESOURCE_VARIABLE:
+ targetType &= 0xFF000000;
+ int tableLength = readUnsignedShort(currentOffset + 1);
+ currentOffset += 3;
+ context.currentLocalVariableAnnotationRangeStarts = new Label[tableLength];
+ context.currentLocalVariableAnnotationRangeEnds = new Label[tableLength];
+ context.currentLocalVariableAnnotationRangeIndices = new int[tableLength];
+ for (int i = 0; i < tableLength; ++i) {
+ int startPc = readUnsignedShort(currentOffset);
+ int length = readUnsignedShort(currentOffset + 2);
+ int index = readUnsignedShort(currentOffset + 4);
+ currentOffset += 6;
+ context.currentLocalVariableAnnotationRangeStarts[i] =
+ createLabel(startPc, context.currentMethodLabels);
+ context.currentLocalVariableAnnotationRangeEnds[i] =
+ createLabel(startPc + length, context.currentMethodLabels);
+ context.currentLocalVariableAnnotationRangeIndices[i] = index;
}
- switch (b[v++] & 0xFF) {
- case 'I': // pointer to CONSTANT_Integer
- case 'J': // pointer to CONSTANT_Long
- case 'F': // pointer to CONSTANT_Float
- case 'D': // pointer to CONSTANT_Double
- av.visit(name, readConst(readUnsignedShort(v), buf));
- v += 2;
- break;
- case 'B': // pointer to CONSTANT_Byte
- av.visit(name,
- new Byte((byte) readInt(items[readUnsignedShort(v)])));
- v += 2;
- break;
- case 'Z': // pointer to CONSTANT_Boolean
- av.visit(name,
- readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
- : Boolean.TRUE);
- v += 2;
- break;
- case 'S': // pointer to CONSTANT_Short
- av.visit(name, new Short(
- (short) readInt(items[readUnsignedShort(v)])));
- v += 2;
- break;
- case 'C': // pointer to CONSTANT_Char
- av.visit(name, new Character(
- (char) readInt(items[readUnsignedShort(v)])));
- v += 2;
- break;
- case 's': // pointer to CONSTANT_Utf8
- av.visit(name, readUTF8(v, buf));
- v += 2;
- break;
+ break;
+ case TypeReference.CAST:
+ case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
+ case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT:
+ case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
+ case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT:
+ targetType &= 0xFF0000FF;
+ currentOffset += 4;
+ break;
+ case TypeReference.CLASS_EXTENDS:
+ case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
+ case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
+ case TypeReference.THROWS:
+ case TypeReference.EXCEPTION_PARAMETER:
+ targetType &= 0xFFFFFF00;
+ currentOffset += 3;
+ break;
+ case TypeReference.INSTANCEOF:
+ case TypeReference.NEW:
+ case TypeReference.CONSTRUCTOR_REFERENCE:
+ case TypeReference.METHOD_REFERENCE:
+ targetType &= 0xFF000000;
+ currentOffset += 3;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ context.currentTypeAnnotationTarget = targetType;
+ // Parse and store the target_path structure.
+ int pathLength = readByte(currentOffset);
+ context.currentTypeAnnotationTargetPath =
+ pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset);
+ // Return the start offset of the rest of the type_annotation structure.
+ return currentOffset + 1 + 2 * pathLength;
+ }
+
+ /**
+ * Reads a Runtime[In]VisibleParameterAnnotations attribute and makes the given visitor visit it.
+ *
+ * @param methodVisitor the visitor that must visit the parameter annotations.
+ * @param context information about the class being parsed.
+ * @param runtimeParameterAnnotationsOffset the start offset of a
+ * Runtime[In]VisibleParameterAnnotations attribute, excluding the attribute_info's
+ * attribute_name_index and attribute_length fields.
+ * @param visible true if the attribute to parse is a RuntimeVisibleParameterAnnotations
+ * attribute, false it is a RuntimeInvisibleParameterAnnotations attribute.
+ */
+ private void readParameterAnnotations(
+ final MethodVisitor methodVisitor,
+ final Context context,
+ final int runtimeParameterAnnotationsOffset,
+ final boolean visible) {
+ int currentOffset = runtimeParameterAnnotationsOffset;
+ int numParameters = classFileBuffer[currentOffset++] & 0xFF;
+ methodVisitor.visitAnnotableParameterCount(numParameters, visible);
+ char[] charBuffer = context.charBuffer;
+ for (int i = 0; i < numParameters; ++i) {
+ int numAnnotations = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (numAnnotations-- > 0) {
+ // Parse the type_index field.
+ String annotationDescriptor = readUTF8(currentOffset, charBuffer);
+ currentOffset += 2;
+ // Parse num_element_value_pairs and element_value_pairs and visit these values.
+ currentOffset =
+ readElementValues(
+ methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible),
+ currentOffset,
+ /* named = */ true,
+ charBuffer);
+ }
+ }
+ }
+
+ /**
+ * Reads the element values of a JVMS 'annotation' structure and makes the given visitor visit
+ * them. This method can also be used to read the values of the JVMS 'array_value' field of an
+ * annotation's 'element_value'.
+ *
+ * @param annotationVisitor the visitor that must visit the values.
+ * @param annotationOffset the start offset of an 'annotation' structure (excluding its type_index
+ * field) or of an 'array_value' structure.
+ * @param named if the annotation values are named or not. This should be true to parse the values
+ * of a JVMS 'annotation' structure, and false to parse the JVMS 'array_value' of an
+ * annotation's element_value.
+ * @param charBuffer the buffer used to read strings in the constant pool.
+ * @return the end offset of the JVMS 'annotation' or 'array_value' structure.
+ */
+ private int readElementValues(
+ final AnnotationVisitor annotationVisitor,
+ final int annotationOffset,
+ final boolean named,
+ final char[] charBuffer) {
+ int currentOffset = annotationOffset;
+ // Read the num_element_value_pairs field (or num_values field for an array_value).
+ int numElementValuePairs = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ if (named) {
+ // Parse the element_value_pairs array.
+ while (numElementValuePairs-- > 0) {
+ String elementName = readUTF8(currentOffset, charBuffer);
+ currentOffset =
+ readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer);
+ }
+ } else {
+ // Parse the array_value array.
+ while (numElementValuePairs-- > 0) {
+ currentOffset =
+ readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer);
+ }
+ }
+ if (annotationVisitor != null) {
+ annotationVisitor.visitEnd();
+ }
+ return currentOffset;
+ }
+
+ /**
+ * Reads a JVMS 'element_value' structure and makes the given visitor visit it.
+ *
+ * @param annotationVisitor the visitor that must visit the element_value structure.
+ * @param elementValueOffset the start offset in {@link #classFileBuffer} of the element_value
+ * structure to be read.
+ * @param elementName the name of the element_value structure to be read, or {@literal null}.
+ * @param charBuffer the buffer used to read strings in the constant pool.
+ * @return the end offset of the JVMS 'element_value' structure.
+ */
+ private int readElementValue(
+ final AnnotationVisitor annotationVisitor,
+ final int elementValueOffset,
+ final String elementName,
+ final char[] charBuffer) {
+ int currentOffset = elementValueOffset;
+ if (annotationVisitor == null) {
+ switch (classFileBuffer[currentOffset] & 0xFF) {
case 'e': // enum_const_value
- av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
- v += 4;
- break;
- case 'c': // class_info
- av.visit(name, Type.getType(readUTF8(v, buf)));
- v += 2;
- break;
+ return currentOffset + 5;
case '@': // annotation_value
- v = readAnnotationValues(v + 2, buf, true,
- av.visitAnnotation(name, readUTF8(v, buf)));
- break;
+ return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer);
case '[': // array_value
- int size = readUnsignedShort(v);
- v += 2;
- if (size == 0) {
- return readAnnotationValues(v - 2, buf, false,
- av.visitArray(name));
- }
- switch (this.b[v++] & 0xFF) {
- case 'B':
- byte[] bv = new byte[size];
- for (i = 0; i < size; i++) {
- bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, bv);
- --v;
- break;
- case 'Z':
- boolean[] zv = new boolean[size];
- for (i = 0; i < size; i++) {
- zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
- v += 3;
- }
- av.visit(name, zv);
- --v;
- break;
- case 'S':
- short[] sv = new short[size];
- for (i = 0; i < size; i++) {
- sv[i] = (short) readInt(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, sv);
- --v;
- break;
- case 'C':
- char[] cv = new char[size];
- for (i = 0; i < size; i++) {
- cv[i] = (char) readInt(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, cv);
- --v;
- break;
- case 'I':
- int[] iv = new int[size];
- for (i = 0; i < size; i++) {
- iv[i] = readInt(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, iv);
- --v;
- break;
- case 'J':
- long[] lv = new long[size];
- for (i = 0; i < size; i++) {
- lv[i] = readLong(items[readUnsignedShort(v)]);
- v += 3;
- }
- av.visit(name, lv);
- --v;
- break;
- case 'F':
- float[] fv = new float[size];
- for (i = 0; i < size; i++) {
- fv[i] = Float
- .intBitsToFloat(readInt(items[readUnsignedShort(v)]));
- v += 3;
- }
- av.visit(name, fv);
- --v;
- break;
- case 'D':
- double[] dv = new double[size];
- for (i = 0; i < size; i++) {
- dv[i] = Double
- .longBitsToDouble(readLong(items[readUnsignedShort(v)]));
- v += 3;
- }
- av.visit(name, dv);
- --v;
- break;
- default:
- v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
- }
- }
- return v;
+ return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer);
+ default:
+ return currentOffset + 3;
+ }
}
-
- /**
- * Computes the implicit frame of the method currently being parsed (as
- * defined in the given {@link Context}) and stores it in the given context.
- *
- * @param frame
- * information about the class being parsed.
- */
- private void getImplicitFrame(final Context frame) {
- String desc = frame.desc;
- Object[] locals = frame.local;
- int local = 0;
- if ((frame.access & Opcodes.ACC_STATIC) == 0) {
- if ("".equals(frame.name)) {
- locals[local++] = Opcodes.UNINITIALIZED_THIS;
- } else {
- locals[local++] = readClass(header + 2, frame.buffer);
- }
+ switch (classFileBuffer[currentOffset++] & 0xFF) {
+ case 'B': // const_value_index, CONSTANT_Integer
+ annotationVisitor.visit(
+ elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ currentOffset += 2;
+ break;
+ case 'C': // const_value_index, CONSTANT_Integer
+ annotationVisitor.visit(
+ elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ currentOffset += 2;
+ break;
+ case 'D': // const_value_index, CONSTANT_Double
+ case 'F': // const_value_index, CONSTANT_Float
+ case 'I': // const_value_index, CONSTANT_Integer
+ case 'J': // const_value_index, CONSTANT_Long
+ annotationVisitor.visit(
+ elementName, readConst(readUnsignedShort(currentOffset), charBuffer));
+ currentOffset += 2;
+ break;
+ case 'S': // const_value_index, CONSTANT_Integer
+ annotationVisitor.visit(
+ elementName, (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]));
+ currentOffset += 2;
+ break;
+
+ case 'Z': // const_value_index, CONSTANT_Integer
+ annotationVisitor.visit(
+ elementName,
+ readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]) == 0
+ ? Boolean.FALSE
+ : Boolean.TRUE);
+ currentOffset += 2;
+ break;
+ case 's': // const_value_index, CONSTANT_Utf8
+ annotationVisitor.visit(elementName, readUTF8(currentOffset, charBuffer));
+ currentOffset += 2;
+ break;
+ case 'e': // enum_const_value
+ annotationVisitor.visitEnum(
+ elementName,
+ readUTF8(currentOffset, charBuffer),
+ readUTF8(currentOffset + 2, charBuffer));
+ currentOffset += 4;
+ break;
+ case 'c': // class_info
+ annotationVisitor.visit(elementName, Type.getType(readUTF8(currentOffset, charBuffer)));
+ currentOffset += 2;
+ break;
+ case '@': // annotation_value
+ currentOffset =
+ readElementValues(
+ annotationVisitor.visitAnnotation(elementName, readUTF8(currentOffset, charBuffer)),
+ currentOffset + 2,
+ true,
+ charBuffer);
+ break;
+ case '[': // array_value
+ int numValues = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ if (numValues == 0) {
+ return readElementValues(
+ annotationVisitor.visitArray(elementName),
+ currentOffset - 2,
+ /* named = */ false,
+ charBuffer);
}
- int i = 1;
- loop: while (true) {
- int j = i;
- switch (desc.charAt(i++)) {
- case 'Z':
- case 'C':
- case 'B':
- case 'S':
- case 'I':
- locals[local++] = Opcodes.INTEGER;
- break;
- case 'F':
- locals[local++] = Opcodes.FLOAT;
- break;
- case 'J':
- locals[local++] = Opcodes.LONG;
- break;
- case 'D':
- locals[local++] = Opcodes.DOUBLE;
- break;
- case '[':
- while (desc.charAt(i) == '[') {
- ++i;
- }
- if (desc.charAt(i) == 'L') {
- ++i;
- while (desc.charAt(i) != ';') {
- ++i;
- }
- }
- locals[local++] = desc.substring(j, ++i);
- break;
- case 'L':
- while (desc.charAt(i) != ';') {
- ++i;
- }
- locals[local++] = desc.substring(j + 1, i++);
- break;
- default:
- break loop;
+ switch (classFileBuffer[currentOffset] & 0xFF) {
+ case 'B':
+ byte[] byteValues = new byte[numValues];
+ for (int i = 0; i < numValues; i++) {
+ byteValues[i] = (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
}
- }
- frame.localCount = local;
- }
-
- /**
- * Reads a stack map frame and stores the result in the given
- * {@link Context} object.
- *
- * @param stackMap
- * the start offset of a stack map frame in the class file.
- * @param zip
- * if the stack map frame at stackMap is compressed or not.
- * @param unzip
- * if the stack map frame must be uncompressed.
- * @param labels
- * the labels of the method currently being parsed, indexed by
- * their offset. A new label for the parsed stack map frame is
- * stored in this array if it does not already exist.
- * @param frame
- * where the parsed stack map frame must be stored.
- * @return the offset of the first byte following the parsed frame.
- */
- private int readFrame(int stackMap, boolean zip, boolean unzip,
- Label[] labels, Context frame) {
- char[] c = frame.buffer;
- int tag;
- int delta;
- if (zip) {
- tag = b[stackMap++] & 0xFF;
- } else {
- tag = MethodWriter.FULL_FRAME;
- frame.offset = -1;
- }
- frame.localDiff = 0;
- if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) {
- delta = tag;
- frame.mode = Opcodes.F_SAME;
- frame.stackCount = 0;
- } else if (tag < MethodWriter.RESERVED) {
- delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
- stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
- frame.mode = Opcodes.F_SAME1;
- frame.stackCount = 1;
- } else {
- delta = readUnsignedShort(stackMap);
- stackMap += 2;
- if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
- stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
- frame.mode = Opcodes.F_SAME1;
- frame.stackCount = 1;
- } else if (tag >= MethodWriter.CHOP_FRAME
- && tag < MethodWriter.SAME_FRAME_EXTENDED) {
- frame.mode = Opcodes.F_CHOP;
- frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag;
- frame.localCount -= frame.localDiff;
- frame.stackCount = 0;
- } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) {
- frame.mode = Opcodes.F_SAME;
- frame.stackCount = 0;
- } else if (tag < MethodWriter.FULL_FRAME) {
- int local = unzip ? frame.localCount : 0;
- for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) {
- stackMap = readFrameType(frame.local, local++, stackMap, c,
- labels);
- }
- frame.mode = Opcodes.F_APPEND;
- frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED;
- frame.localCount += frame.localDiff;
- frame.stackCount = 0;
- } else { // if (tag == FULL_FRAME) {
- frame.mode = Opcodes.F_FULL;
- int n = readUnsignedShort(stackMap);
- stackMap += 2;
- frame.localDiff = n;
- frame.localCount = n;
- for (int local = 0; n > 0; n--) {
- stackMap = readFrameType(frame.local, local++, stackMap, c,
- labels);
- }
- n = readUnsignedShort(stackMap);
- stackMap += 2;
- frame.stackCount = n;
- for (int stack = 0; n > 0; n--) {
- stackMap = readFrameType(frame.stack, stack++, stackMap, c,
- labels);
- }
+ annotationVisitor.visit(elementName, byteValues);
+ break;
+ case 'Z':
+ boolean[] booleanValues = new boolean[numValues];
+ for (int i = 0; i < numValues; i++) {
+ booleanValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]) != 0;
+ currentOffset += 3;
}
- }
- frame.offset += delta + 1;
- readLabel(frame.offset, labels);
- return stackMap;
- }
-
- /**
- * Reads a stack map frame type and stores it at the given index in the
- * given array.
- *
- * @param frame
- * the array where the parsed type must be stored.
- * @param index
- * the index in 'frame' where the parsed type must be stored.
- * @param v
- * the start offset of the stack map frame type to read.
- * @param buf
- * a buffer to read strings.
- * @param labels
- * the labels of the method currently being parsed, indexed by
- * their offset. If the parsed type is an Uninitialized type, a
- * new label for the corresponding NEW instruction is stored in
- * this array if it does not already exist.
- * @return the offset of the first byte after the parsed type.
- */
- private int readFrameType(final Object[] frame, final int index, int v,
- final char[] buf, final Label[] labels) {
- int type = b[v++] & 0xFF;
- switch (type) {
- case 0:
- frame[index] = Opcodes.TOP;
+ annotationVisitor.visit(elementName, booleanValues);
break;
- case 1:
- frame[index] = Opcodes.INTEGER;
+ case 'S':
+ short[] shortValues = new short[numValues];
+ for (int i = 0; i < numValues; i++) {
+ shortValues[i] = (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, shortValues);
break;
- case 2:
- frame[index] = Opcodes.FLOAT;
+ case 'C':
+ char[] charValues = new char[numValues];
+ for (int i = 0; i < numValues; i++) {
+ charValues[i] = (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, charValues);
break;
- case 3:
- frame[index] = Opcodes.DOUBLE;
+ case 'I':
+ int[] intValues = new int[numValues];
+ for (int i = 0; i < numValues; i++) {
+ intValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, intValues);
break;
- case 4:
- frame[index] = Opcodes.LONG;
+ case 'J':
+ long[] longValues = new long[numValues];
+ for (int i = 0; i < numValues; i++) {
+ longValues[i] = readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]);
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, longValues);
break;
- case 5:
- frame[index] = Opcodes.NULL;
+ case 'F':
+ float[] floatValues = new float[numValues];
+ for (int i = 0; i < numValues; i++) {
+ floatValues[i] =
+ Float.intBitsToFloat(
+ readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, floatValues);
break;
- case 6:
- frame[index] = Opcodes.UNINITIALIZED_THIS;
+ case 'D':
+ double[] doubleValues = new double[numValues];
+ for (int i = 0; i < numValues; i++) {
+ doubleValues[i] =
+ Double.longBitsToDouble(
+ readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]));
+ currentOffset += 3;
+ }
+ annotationVisitor.visit(elementName, doubleValues);
break;
- case 7: // Object
- frame[index] = readClass(v, buf);
- v += 2;
+ default:
+ currentOffset =
+ readElementValues(
+ annotationVisitor.visitArray(elementName),
+ currentOffset - 2,
+ /* named = */ false,
+ charBuffer);
break;
- default: // Uninitialized
- frame[index] = readLabel(readUnsignedShort(v), labels);
- v += 2;
- }
- return v;
- }
-
- /**
- * Returns the label corresponding to the given offset. The default
- * implementation of this method creates a label for the given offset if it
- * has not been already created.
- *
- * @param offset
- * a bytecode offset in a method.
- * @param labels
- * the already created labels, indexed by their offset. If a
- * label already exists for offset this method must not create a
- * new one. Otherwise it must store the new label in this array.
- * @return a non null Label, which must be equal to labels[offset].
- */
- protected Label readLabel(int offset, Label[] labels) {
- if (labels[offset] == null) {
- labels[offset] = new Label();
}
- return labels[offset];
+ break;
+ default:
+ throw new IllegalArgumentException();
}
-
- /**
- * Returns the start index of the attribute_info structure of this class.
- *
- * @return the start index of the attribute_info structure of this class.
- */
- private int getAttributes() {
- // skips the header
- int u = header + 8 + readUnsignedShort(header + 6) * 2;
- // skips fields and methods
- for (int i = readUnsignedShort(u); i > 0; --i) {
- for (int j = readUnsignedShort(u + 8); j > 0; --j) {
- u += 6 + readInt(u + 12);
+ return currentOffset;
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse stack map frames
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Computes the implicit frame of the method currently being parsed (as defined in the given
+ * {@link Context}) and stores it in the given context.
+ *
+ * @param context information about the class being parsed.
+ */
+ private void computeImplicitFrame(final Context context) {
+ String methodDescriptor = context.currentMethodDescriptor;
+ Object[] locals = context.currentFrameLocalTypes;
+ int numLocal = 0;
+ if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) {
+ if ("".equals(context.currentMethodName)) {
+ locals[numLocal++] = Opcodes.UNINITIALIZED_THIS;
+ } else {
+ locals[numLocal++] = readClass(header + 2, context.charBuffer);
+ }
+ }
+ // Parse the method descriptor, one argument type descriptor at each iteration. Start by
+ // skipping the first method descriptor character, which is always '('.
+ int currentMethodDescritorOffset = 1;
+ while (true) {
+ int currentArgumentDescriptorStartOffset = currentMethodDescritorOffset;
+ switch (methodDescriptor.charAt(currentMethodDescritorOffset++)) {
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ locals[numLocal++] = Opcodes.INTEGER;
+ break;
+ case 'F':
+ locals[numLocal++] = Opcodes.FLOAT;
+ break;
+ case 'J':
+ locals[numLocal++] = Opcodes.LONG;
+ break;
+ case 'D':
+ locals[numLocal++] = Opcodes.DOUBLE;
+ break;
+ case '[':
+ while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') {
+ ++currentMethodDescritorOffset;
+ }
+ if (methodDescriptor.charAt(currentMethodDescritorOffset) == 'L') {
+ ++currentMethodDescritorOffset;
+ while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') {
+ ++currentMethodDescritorOffset;
}
- u += 8;
+ }
+ locals[numLocal++] =
+ methodDescriptor.substring(
+ currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset);
+ break;
+ case 'L':
+ while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') {
+ ++currentMethodDescritorOffset;
+ }
+ locals[numLocal++] =
+ methodDescriptor.substring(
+ currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++);
+ break;
+ default:
+ context.currentFrameLocalCount = numLocal;
+ return;
+ }
+ }
+ }
+
+ /**
+ * Reads a JVMS 'stack_map_frame' structure and stores the result in the given {@link Context}
+ * object. This method can also be used to read a full_frame structure, excluding its frame_type
+ * field (this is used to parse the legacy StackMap attributes).
+ *
+ * @param stackMapFrameOffset the start offset in {@link #classFileBuffer} of the
+ * stack_map_frame_value structure to be read, or the start offset of a full_frame structure
+ * (excluding its frame_type field).
+ * @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame'
+ * structure without its frame_type field.
+ * @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}.
+ * @param context where the parsed stack map frame must be stored.
+ * @return the end offset of the JVMS 'stack_map_frame' or 'full_frame' structure.
+ */
+ private int readStackMapFrame(
+ final int stackMapFrameOffset,
+ final boolean compressed,
+ final boolean expand,
+ final Context context) {
+ int currentOffset = stackMapFrameOffset;
+ final char[] charBuffer = context.charBuffer;
+ final Label[] labels = context.currentMethodLabels;
+ int frameType;
+ if (compressed) {
+ // Read the frame_type field.
+ frameType = classFileBuffer[currentOffset++] & 0xFF;
+ } else {
+ frameType = Frame.FULL_FRAME;
+ context.currentFrameOffset = -1;
+ }
+ int offsetDelta;
+ context.currentFrameLocalCountDelta = 0;
+ if (frameType < Frame.SAME_LOCALS_1_STACK_ITEM_FRAME) {
+ offsetDelta = frameType;
+ context.currentFrameType = Opcodes.F_SAME;
+ context.currentFrameStackCount = 0;
+ } else if (frameType < Frame.RESERVED) {
+ offsetDelta = frameType - Frame.SAME_LOCALS_1_STACK_ITEM_FRAME;
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels);
+ context.currentFrameType = Opcodes.F_SAME1;
+ context.currentFrameStackCount = 1;
+ } else if (frameType >= Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+ offsetDelta = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ if (frameType == Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels);
+ context.currentFrameType = Opcodes.F_SAME1;
+ context.currentFrameStackCount = 1;
+ } else if (frameType >= Frame.CHOP_FRAME && frameType < Frame.SAME_FRAME_EXTENDED) {
+ context.currentFrameType = Opcodes.F_CHOP;
+ context.currentFrameLocalCountDelta = Frame.SAME_FRAME_EXTENDED - frameType;
+ context.currentFrameLocalCount -= context.currentFrameLocalCountDelta;
+ context.currentFrameStackCount = 0;
+ } else if (frameType == Frame.SAME_FRAME_EXTENDED) {
+ context.currentFrameType = Opcodes.F_SAME;
+ context.currentFrameStackCount = 0;
+ } else if (frameType < Frame.FULL_FRAME) {
+ int local = expand ? context.currentFrameLocalCount : 0;
+ for (int k = frameType - Frame.SAME_FRAME_EXTENDED; k > 0; k--) {
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameLocalTypes, local++, charBuffer, labels);
}
- u += 2;
- for (int i = readUnsignedShort(u); i > 0; --i) {
- for (int j = readUnsignedShort(u + 8); j > 0; --j) {
- u += 6 + readInt(u + 12);
- }
- u += 8;
+ context.currentFrameType = Opcodes.F_APPEND;
+ context.currentFrameLocalCountDelta = frameType - Frame.SAME_FRAME_EXTENDED;
+ context.currentFrameLocalCount += context.currentFrameLocalCountDelta;
+ context.currentFrameStackCount = 0;
+ } else {
+ final int numberOfLocals = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ context.currentFrameType = Opcodes.F_FULL;
+ context.currentFrameLocalCountDelta = numberOfLocals;
+ context.currentFrameLocalCount = numberOfLocals;
+ for (int local = 0; local < numberOfLocals; ++local) {
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels);
}
- // the attribute_info structure starts just after the methods
- return u + 2;
- }
-
- /**
- * Reads an attribute in {@link #b b}.
- *
- * @param attrs
- * prototypes of the attributes that must be parsed during the
- * visit of the class. Any attribute whose type is not equal to
- * the type of one the prototypes is ignored (i.e. an empty
- * {@link Attribute} instance is returned).
- * @param type
- * the type of the attribute.
- * @param off
- * index of the first byte of the attribute's content in
- * {@link #b b}. The 6 attribute header bytes, containing the
- * type and the length of the attribute, are not taken into
- * account here (they have already been read).
- * @param len
- * the length of the attribute's content.
- * @param buf
- * buffer to be used to call {@link #readUTF8 readUTF8},
- * {@link #readClass(int,char[]) readClass} or {@link #readConst
- * readConst}.
- * @param codeOff
- * index of the first byte of code's attribute content in
- * {@link #b b}, or -1 if the attribute to be read is not a code
- * attribute. The 6 attribute header bytes, containing the type
- * and the length of the attribute, are not taken into account
- * here.
- * @param labels
- * the labels of the method's code, or null if the
- * attribute to be read is not a code attribute.
- * @return the attribute that has been read, or null to skip this
- * attribute.
- */
- private Attribute readAttribute(final Attribute[] attrs, final String type,
- final int off, final int len, final char[] buf, final int codeOff,
- final Label[] labels) {
- for (int i = 0; i < attrs.length; ++i) {
- if (attrs[i].type.equals(type)) {
- return attrs[i].read(this, off, len, buf, codeOff, labels);
- }
+ final int numberOfStackItems = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ context.currentFrameStackCount = numberOfStackItems;
+ for (int stack = 0; stack < numberOfStackItems; ++stack) {
+ currentOffset =
+ readVerificationTypeInfo(
+ currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels);
}
- return new Attribute(type).read(this, off, len, null, -1, null);
+ }
+ } else {
+ throw new IllegalArgumentException();
+ }
+ context.currentFrameOffset += offsetDelta + 1;
+ createLabel(context.currentFrameOffset, labels);
+ return currentOffset;
+ }
+
+ /**
+ * Reads a JVMS 'verification_type_info' structure and stores it at the given index in the given
+ * array.
+ *
+ * @param verificationTypeInfoOffset the start offset of the 'verification_type_info' structure to
+ * read.
+ * @param frame the array where the parsed type must be stored.
+ * @param index the index in 'frame' where the parsed type must be stored.
+ * @param charBuffer the buffer used to read strings in the constant pool.
+ * @param labels the labels of the method currently being parsed, indexed by their offset. If the
+ * parsed type is an ITEM_Uninitialized, a new label for the corresponding NEW instruction is
+ * stored in this array if it does not already exist.
+ * @return the end offset of the JVMS 'verification_type_info' structure.
+ */
+ private int readVerificationTypeInfo(
+ final int verificationTypeInfoOffset,
+ final Object[] frame,
+ final int index,
+ final char[] charBuffer,
+ final Label[] labels) {
+ int currentOffset = verificationTypeInfoOffset;
+ int tag = classFileBuffer[currentOffset++] & 0xFF;
+ switch (tag) {
+ case Frame.ITEM_TOP:
+ frame[index] = Opcodes.TOP;
+ break;
+ case Frame.ITEM_INTEGER:
+ frame[index] = Opcodes.INTEGER;
+ break;
+ case Frame.ITEM_FLOAT:
+ frame[index] = Opcodes.FLOAT;
+ break;
+ case Frame.ITEM_DOUBLE:
+ frame[index] = Opcodes.DOUBLE;
+ break;
+ case Frame.ITEM_LONG:
+ frame[index] = Opcodes.LONG;
+ break;
+ case Frame.ITEM_NULL:
+ frame[index] = Opcodes.NULL;
+ break;
+ case Frame.ITEM_UNINITIALIZED_THIS:
+ frame[index] = Opcodes.UNINITIALIZED_THIS;
+ break;
+ case Frame.ITEM_OBJECT:
+ frame[index] = readClass(currentOffset, charBuffer);
+ currentOffset += 2;
+ break;
+ case Frame.ITEM_UNINITIALIZED:
+ frame[index] = createLabel(readUnsignedShort(currentOffset), labels);
+ currentOffset += 2;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ return currentOffset;
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Methods to parse attributes
+ // ----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array
+ * field entry.
+ *
+ * @return the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array
+ * field entry.
+ */
+ final int getFirstAttributeOffset() {
+ // Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes
+ // each), as well as the interfaces array field (2 bytes per interface).
+ int currentOffset = header + 8 + readUnsignedShort(header + 6) * 2;
+
+ // Read the fields_count field.
+ int fieldsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ // Skip the 'fields' array field.
+ while (fieldsCount-- > 0) {
+ // Invariant: currentOffset is the offset of a field_info structure.
+ // Skip the access_flags, name_index and descriptor_index fields (2 bytes each), and read the
+ // attributes_count field.
+ int attributesCount = readUnsignedShort(currentOffset + 6);
+ currentOffset += 8;
+ // Skip the 'attributes' array field.
+ while (attributesCount-- > 0) {
+ // Invariant: currentOffset is the offset of an attribute_info structure.
+ // Read the attribute_length field (2 bytes after the start of the attribute_info) and skip
+ // this many bytes, plus 6 for the attribute_name_index and attribute_length fields
+ // (yielding the total size of the attribute_info structure).
+ currentOffset += 6 + readInt(currentOffset + 2);
+ }
}
- // ------------------------------------------------------------------------
- // Utility methods: low level parsing
- // ------------------------------------------------------------------------
-
- /**
- * Returns the number of constant pool items in {@link #b b}.
- *
- * @return the number of constant pool items in {@link #b b}.
- */
- public int getItemCount() {
- return items.length;
- }
-
- /**
- * Returns the start index of the constant pool item in {@link #b b}, plus
- * one. This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param item
- * the index a constant pool item.
- * @return the start index of the constant pool item in {@link #b b}, plus
- * one.
- */
- public int getItem(final int item) {
- return items[item];
+ // Skip the methods_count and 'methods' fields, using the same method as above.
+ int methodsCount = readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ while (methodsCount-- > 0) {
+ int attributesCount = readUnsignedShort(currentOffset + 6);
+ currentOffset += 8;
+ while (attributesCount-- > 0) {
+ currentOffset += 6 + readInt(currentOffset + 2);
+ }
}
- /**
- * Returns the maximum length of the strings contained in the constant pool
- * of the class.
- *
- * @return the maximum length of the strings contained in the constant pool
- * of the class.
- */
- public int getMaxStringLength() {
- return maxStringLength;
- }
-
- /**
- * Reads a byte value in {@link #b b}. This method is intended for
- * {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public int readByte(final int index) {
- return b[index] & 0xFF;
- }
-
- /**
- * Reads an unsigned short value in {@link #b b}. This method is intended
- * for {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public int readUnsignedShort(final int index) {
- byte[] b = this.b;
- return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
- }
-
- /**
- * Reads a signed short value in {@link #b b}. This method is intended
- * for {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public short readShort(final int index) {
- byte[] b = this.b;
- return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
- }
-
- /**
- * Reads a signed int value in {@link #b b}. This method is intended for
- * {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public int readInt(final int index) {
- byte[] b = this.b;
- return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
- | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
- }
-
- /**
- * Reads a signed long value in {@link #b b}. This method is intended for
- * {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @param index
- * the start index of the value to be read in {@link #b b}.
- * @return the read value.
- */
- public long readLong(final int index) {
- long l1 = readInt(index);
- long l0 = readInt(index + 4) & 0xFFFFFFFFL;
- return (l1 << 32) | l0;
- }
-
- /**
- * Reads an UTF8 string constant pool item in {@link #b b}. This method
- * is intended for {@link Attribute} sub classes, and is normally not needed
- * by class generators or adapters.
- *
- * @param index
- * the start index of an unsigned short value in {@link #b b},
- * whose value is the index of an UTF8 constant pool item.
- * @param buf
- * buffer to be used to read the item. This buffer must be
- * sufficiently large. It is not automatically resized.
- * @return the String corresponding to the specified UTF8 item.
- */
- public String readUTF8(int index, final char[] buf) {
- int item = readUnsignedShort(index);
- if (index == 0 || item == 0) {
- return null;
- }
- String s = strings[item];
- if (s != null) {
- return s;
- }
- index = items[item];
- return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
- }
-
- /**
- * Reads UTF8 string in {@link #b b}.
- *
- * @param index
- * start offset of the UTF8 string to be read.
- * @param utfLen
- * length of the UTF8 string to be read.
- * @param buf
- * buffer to be used to read the string. This buffer must be
- * sufficiently large. It is not automatically resized.
- * @return the String corresponding to the specified UTF8 string.
- */
- private String readUTF(int index, final int utfLen, final char[] buf) {
- int endIndex = index + utfLen;
- byte[] b = this.b;
- int strLen = 0;
- int c;
- int st = 0;
- char cc = 0;
- while (index < endIndex) {
- c = b[index++];
- switch (st) {
- case 0:
- c = c & 0xFF;
- if (c < 0x80) { // 0xxxxxxx
- buf[strLen++] = (char) c;
- } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
- cc = (char) (c & 0x1F);
- st = 1;
- } else { // 1110 xxxx 10xx xxxx 10xx xxxx
- cc = (char) (c & 0x0F);
- st = 2;
- }
- break;
-
- case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
- buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
- st = 0;
- break;
-
- case 2: // byte 2 of 3-byte char
- cc = (char) ((cc << 6) | (c & 0x3F));
- st = 1;
- break;
- }
- }
- return new String(buf, 0, strLen);
- }
-
- /**
- * Reads a class constant pool item in {@link #b b}. This method is
- * intended for {@link Attribute} sub classes, and is normally not needed by
- * class generators or adapters.
- *
- * @param index
- * the start index of an unsigned short value in {@link #b b},
- * whose value is the index of a class constant pool item.
- * @param buf
- * buffer to be used to read the item. This buffer must be
- * sufficiently large. It is not automatically resized.
- * @return the String corresponding to the specified class item.
- */
- public String readClass(final int index, final char[] buf) {
- // computes the start index of the CONSTANT_Class item in b
- // and reads the CONSTANT_Utf8 item designated by
- // the first two bytes of this CONSTANT_Class item
- return readUTF8(items[readUnsignedShort(index)], buf);
- }
-
- /**
- * Reads a numeric or string constant pool item in {@link #b b}. This
- * method is intended for {@link Attribute} sub classes, and is normally not
- * needed by class generators or adapters.
- *
- * @param item
- * the index of a constant pool item.
- * @param buf
- * buffer to be used to read the item. This buffer must be
- * sufficiently large. It is not automatically resized.
- * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double},
- * {@link String}, {@link Type} or {@link Handle} corresponding to
- * the given constant pool item.
- */
- public Object readConst(final int item, final char[] buf) {
- int index = items[item];
- switch (b[index - 1]) {
- case ClassWriter.INT:
- return new Integer(readInt(index));
- case ClassWriter.FLOAT:
- return new Float(Float.intBitsToFloat(readInt(index)));
- case ClassWriter.LONG:
- return new Long(readLong(index));
- case ClassWriter.DOUBLE:
- return new Double(Double.longBitsToDouble(readLong(index)));
- case ClassWriter.CLASS:
- return Type.getObjectType(readUTF8(index, buf));
- case ClassWriter.STR:
- return readUTF8(index, buf);
- case ClassWriter.MTYPE:
- return Type.getMethodType(readUTF8(index, buf));
- default: // case ClassWriter.HANDLE_BASE + [1..9]:
- int tag = readByte(index);
- int[] items = this.items;
- int cpIndex = items[readUnsignedShort(index + 1)];
- String owner = readClass(cpIndex, buf);
- cpIndex = items[readUnsignedShort(cpIndex + 2)];
- String name = readUTF8(cpIndex, buf);
- String desc = readUTF8(cpIndex + 2, buf);
- return new Handle(tag, owner, name, desc);
+ // Skip the ClassFile's attributes_count field.
+ return currentOffset + 2;
+ }
+
+ /**
+ * Reads the BootstrapMethods attribute to compute the offset of each bootstrap method.
+ *
+ * @param maxStringLength a conservative estimate of the maximum length of the strings contained
+ * in the constant pool of the class.
+ * @return the offsets of the bootstrap methods.
+ */
+ private int[] readBootstrapMethodsAttribute(final int maxStringLength) {
+ char[] charBuffer = new char[maxStringLength];
+ int currentAttributeOffset = getFirstAttributeOffset();
+ for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
+ // Read the attribute_info's attribute_name and attribute_length fields.
+ String attributeName = readUTF8(currentAttributeOffset, charBuffer);
+ int attributeLength = readInt(currentAttributeOffset + 2);
+ currentAttributeOffset += 6;
+ if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
+ // Read the num_bootstrap_methods field and create an array of this size.
+ int[] result = new int[readUnsignedShort(currentAttributeOffset)];
+ // Compute and store the offset of each 'bootstrap_methods' array field entry.
+ int currentBootstrapMethodOffset = currentAttributeOffset + 2;
+ for (int j = 0; j < result.length; ++j) {
+ result[j] = currentBootstrapMethodOffset;
+ // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each),
+ // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2).
+ currentBootstrapMethodOffset +=
+ 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2;
}
+ return result;
+ }
+ currentAttributeOffset += attributeLength;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Reads a non standard JVMS 'attribute' structure in {@link #classFileBuffer}.
+ *
+ * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of
+ * the class. Any attribute whose type is not equal to the type of one the prototypes will not
+ * be parsed: its byte array value will be passed unchanged to the ClassWriter.
+ * @param type the type of the attribute.
+ * @param offset the start offset of the JVMS 'attribute' structure in {@link #classFileBuffer}.
+ * The 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param length the length of the attribute's content (excluding the 6 attribute header bytes).
+ * @param charBuffer the buffer to be used to read strings in the constant pool.
+ * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link
+ * #classFileBuffer}, or -1 if the attribute to be read is not a code attribute. The 6
+ * attribute header bytes (attribute_name_index and attribute_length) are not taken into
+ * account here.
+ * @param labels the labels of the method's code, or {@literal null} if the attribute to be read
+ * is not a code attribute.
+ * @return the attribute that has been read.
+ */
+ private Attribute readAttribute(
+ final Attribute[] attributePrototypes,
+ final String type,
+ final int offset,
+ final int length,
+ final char[] charBuffer,
+ final int codeAttributeOffset,
+ final Label[] labels) {
+ for (Attribute attributePrototype : attributePrototypes) {
+ if (attributePrototype.type.equals(type)) {
+ return attributePrototype.read(
+ this, offset, length, charBuffer, codeAttributeOffset, labels);
+ }
+ }
+ return new Attribute(type).read(this, offset, length, null, -1, null);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods: low level parsing
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the number of entries in the class's constant pool table.
+ *
+ * @return the number of entries in the class's constant pool table.
+ */
+ public int getItemCount() {
+ return cpInfoOffsets.length;
+ }
+
+ /**
+ * Returns the start offset in this {@link ClassReader} of a JVMS 'cp_info' structure (i.e. a
+ * constant pool entry), plus one. This method is intended for {@link Attribute} sub classes,
+ * and is normally not needed by class generators or adapters.
+ *
+ * @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool
+ * table.
+ * @return the start offset in this {@link ClassReader} of the corresponding JVMS 'cp_info'
+ * structure, plus one.
+ */
+ public int getItem(final int constantPoolEntryIndex) {
+ return cpInfoOffsets[constantPoolEntryIndex];
+ }
+
+ /**
+ * Returns a conservative estimate of the maximum length of the strings contained in the class's
+ * constant pool table.
+ *
+ * @return a conservative estimate of the maximum length of the strings contained in the class's
+ * constant pool table.
+ */
+ public int getMaxStringLength() {
+ return maxStringLength;
+ }
+
+ /**
+ * Reads a byte value in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the value to be read in this {@link ClassReader}.
+ * @return the read value.
+ */
+ public int readByte(final int offset) {
+ return classFileBuffer[offset] & 0xFF;
+ }
+
+ /**
+ * Reads an unsigned short value in this {@link ClassReader}. This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start index of the value to be read in this {@link ClassReader}.
+ * @return the read value.
+ */
+ public int readUnsignedShort(final int offset) {
+ byte[] classBuffer = classFileBuffer;
+ return ((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF);
+ }
+
+ /**
+ * Reads a signed short value in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the value to be read in this {@link ClassReader}.
+ * @return the read value.
+ */
+ public short readShort(final int offset) {
+ byte[] classBuffer = classFileBuffer;
+ return (short) (((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF));
+ }
+
+ /**
+ * Reads a signed int value in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the value to be read in this {@link ClassReader}.
+ * @return the read value.
+ */
+ public int readInt(final int offset) {
+ byte[] classBuffer = classFileBuffer;
+ return ((classBuffer[offset] & 0xFF) << 24)
+ | ((classBuffer[offset + 1] & 0xFF) << 16)
+ | ((classBuffer[offset + 2] & 0xFF) << 8)
+ | (classBuffer[offset + 3] & 0xFF);
+ }
+
+ /**
+ * Reads a signed long value in this {@link ClassReader}. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param offset the start offset of the value to be read in this {@link ClassReader}.
+ * @return the read value.
+ */
+ public long readLong(final int offset) {
+ long l1 = readInt(offset);
+ long l0 = readInt(offset + 4) & 0xFFFFFFFFL;
+ return (l1 << 32) | l0;
+ }
+
+ /**
+ * Reads a CONSTANT_Utf8 constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
+ *
+ * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose
+ * value is the index of a CONSTANT_Utf8 entry in the class's constant pool table.
+ * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Utf8 entry.
+ */
+ // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
+ public String readUTF8(final int offset, final char[] charBuffer) {
+ int constantPoolEntryIndex = readUnsignedShort(offset);
+ if (offset == 0 || constantPoolEntryIndex == 0) {
+ return null;
+ }
+ return readUtf(constantPoolEntryIndex, charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Utf8 constant pool entry in {@link #classFileBuffer}.
+ *
+ * @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool
+ * table.
+ * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Utf8 entry.
+ */
+ final String readUtf(final int constantPoolEntryIndex, final char[] charBuffer) {
+ String value = constantUtf8Values[constantPoolEntryIndex];
+ if (value != null) {
+ return value;
+ }
+ int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
+ return constantUtf8Values[constantPoolEntryIndex] =
+ readUtf(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer);
+ }
+
+ /**
+ * Reads an UTF8 string in {@link #classFileBuffer}.
+ *
+ * @param utfOffset the start offset of the UTF8 string to be read.
+ * @param utfLength the length of the UTF8 string to be read.
+ * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified UTF8 string.
+ */
+ private String readUtf(final int utfOffset, final int utfLength, final char[] charBuffer) {
+ int currentOffset = utfOffset;
+ int endOffset = currentOffset + utfLength;
+ int strLength = 0;
+ byte[] classBuffer = classFileBuffer;
+ while (currentOffset < endOffset) {
+ int currentByte = classBuffer[currentOffset++];
+ if ((currentByte & 0x80) == 0) {
+ charBuffer[strLength++] = (char) (currentByte & 0x7F);
+ } else if ((currentByte & 0xE0) == 0xC0) {
+ charBuffer[strLength++] =
+ (char) (((currentByte & 0x1F) << 6) + (classBuffer[currentOffset++] & 0x3F));
+ } else {
+ charBuffer[strLength++] =
+ (char)
+ (((currentByte & 0xF) << 12)
+ + ((classBuffer[currentOffset++] & 0x3F) << 6)
+ + (classBuffer[currentOffset++] & 0x3F));
+ }
+ }
+ return new String(charBuffer, 0, strLength);
+ }
+
+ /**
+ * Reads a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or
+ * CONSTANT_Package constant pool entry in {@link #classFileBuffer}. This method is intended
+ * for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
+ *
+ * @param offset the start offset of an unsigned short value in {@link #classFileBuffer}, whose
+ * value is the index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType,
+ * CONSTANT_Module or CONSTANT_Package entry in class's constant pool table.
+ * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified constant pool entry.
+ */
+ private String readStringish(final int offset, final char[] charBuffer) {
+ // Get the start offset of the cp_info structure (plus one), and read the CONSTANT_Utf8 entry
+ // designated by the first two bytes of this cp_info.
+ return readUTF8(cpInfoOffsets[readUnsignedShort(offset)], charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Class constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
+ *
+ * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose
+ * value is the index of a CONSTANT_Class entry in class's constant pool table.
+ * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Class entry.
+ */
+ public String readClass(final int offset, final char[] charBuffer) {
+ return readStringish(offset, charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Module constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
+ *
+ * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose
+ * value is the index of a CONSTANT_Module entry in class's constant pool table.
+ * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Module entry.
+ */
+ public String readModule(final int offset, final char[] charBuffer) {
+ return readStringish(offset, charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Package constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
+ *
+ * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose
+ * value is the index of a CONSTANT_Package entry in class's constant pool table.
+ * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the String corresponding to the specified CONSTANT_Package entry.
+ */
+ public String readPackage(final int offset, final char[] charBuffer) {
+ return readStringish(offset, charBuffer);
+ }
+
+ /**
+ * Reads a CONSTANT_Dynamic constant pool entry in {@link #classFileBuffer}.
+ *
+ * @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant
+ * pool table.
+ * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the ConstantDynamic corresponding to the specified CONSTANT_Dynamic entry.
+ */
+ private ConstantDynamic readConstantDynamic(
+ final int constantPoolEntryIndex, final char[] charBuffer) {
+ ConstantDynamic constantDynamic = constantDynamicValues[constantPoolEntryIndex];
+ if (constantDynamic != null) {
+ return constantDynamic;
+ }
+ int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)];
+ Handle handle = (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ Object[] bootstrapMethodArguments = new Object[readUnsignedShort(bootstrapMethodOffset + 2)];
+ bootstrapMethodOffset += 4;
+ for (int i = 0; i < bootstrapMethodArguments.length; i++) {
+ bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer);
+ bootstrapMethodOffset += 2;
+ }
+ return constantDynamicValues[constantPoolEntryIndex] =
+ new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments);
+ }
+
+ /**
+ * Reads a numeric or string constant pool entry in this {@link ClassReader}. This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by class generators or
+ * adapters.
+ *
+ * @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long,
+ * CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType,
+ * CONSTANT_MethodHandle or CONSTANT_Dynamic entry in the class's constant pool.
+ * @param charBuffer the buffer to be used to read strings. This buffer must be sufficiently
+ * large. It is not automatically resized.
+ * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String},
+ * {@link Type}, {@link Handle} or {@link ConstantDynamic} corresponding to the specified
+ * constant pool entry.
+ */
+ public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) {
+ int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex];
+ switch (classFileBuffer[cpInfoOffset - 1]) {
+ case Symbol.CONSTANT_INTEGER_TAG:
+ return readInt(cpInfoOffset);
+ case Symbol.CONSTANT_FLOAT_TAG:
+ return Float.intBitsToFloat(readInt(cpInfoOffset));
+ case Symbol.CONSTANT_LONG_TAG:
+ return readLong(cpInfoOffset);
+ case Symbol.CONSTANT_DOUBLE_TAG:
+ return Double.longBitsToDouble(readLong(cpInfoOffset));
+ case Symbol.CONSTANT_CLASS_TAG:
+ return Type.getObjectType(readUTF8(cpInfoOffset, charBuffer));
+ case Symbol.CONSTANT_STRING_TAG:
+ return readUTF8(cpInfoOffset, charBuffer);
+ case Symbol.CONSTANT_METHOD_TYPE_TAG:
+ return Type.getMethodType(readUTF8(cpInfoOffset, charBuffer));
+ case Symbol.CONSTANT_METHOD_HANDLE_TAG:
+ int referenceKind = readByte(cpInfoOffset);
+ int referenceCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 1)];
+ int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(referenceCpInfoOffset + 2)];
+ String owner = readClass(referenceCpInfoOffset, charBuffer);
+ String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer);
+ String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer);
+ boolean isInterface =
+ classFileBuffer[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG;
+ return new Handle(referenceKind, owner, name, descriptor, isInterface);
+ case Symbol.CONSTANT_DYNAMIC_TAG:
+ return readConstantDynamic(constantPoolEntryIndex, charBuffer);
+ default:
+ throw new IllegalArgumentException();
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/ClassTooLargeException.java b/src/java/nginx/clojure/asm/ClassTooLargeException.java
new file mode 100644
index 00000000..517a6771
--- /dev/null
+++ b/src/java/nginx/clojure/asm/ClassTooLargeException.java
@@ -0,0 +1,72 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+/**
+ * Exception thrown when the constant pool of a class produced by a {@link ClassWriter} is too
+ * large.
+ *
+ * @author Jason Zaugg
+ */
+public final class ClassTooLargeException extends IndexOutOfBoundsException {
+ private static final long serialVersionUID = 160715609518896765L;
+
+ private final String className;
+ private final int constantPoolCount;
+
+ /**
+ * Constructs a new {@link ClassTooLargeException}.
+ *
+ * @param className the internal name of the class (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}).
+ * @param constantPoolCount the number of constant pool items of the class.
+ */
+ public ClassTooLargeException(final String className, final int constantPoolCount) {
+ super("Class too large: " + className);
+ this.className = className;
+ this.constantPoolCount = constantPoolCount;
+ }
+
+ /**
+ * Returns the internal name of the class (see {@link nginx.clojure.asm.Type#getInternalName()}).
+ *
+ * @return the internal name of the class.
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ /**
+ * Returns the number of constant pool items of the class.
+ *
+ * @return the number of constant pool items of the class.
+ */
+ public int getConstantPoolCount() {
+ return constantPoolCount;
+ }
+}
diff --git a/src/java/nginx/clojure/asm/ClassVisitor.java b/src/java/nginx/clojure/asm/ClassVisitor.java
index 0c294864..8cf6ad1d 100644
--- a/src/java/nginx/clojure/asm/ClassVisitor.java
+++ b/src/java/nginx/clojure/asm/ClassVisitor.java
@@ -1,286 +1,398 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A visitor to visit a Java class. The methods of this class must be called in
- * the following order: visit [ visitSource ] [
- * visitOuterClass ] ( visitAnnotation |
- * visitAttribute )* ( visitInnerClass | visitField |
- * visitMethod )* visitEnd.
- *
+ * A visitor to visit a Java class. The methods of this class must be called in the following order:
+ * {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code
+ * visitOuterClass} ] ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code
+ * visitAttribute} )* ( {@code visitNestMember} | [ {@code * visitPermittedSubclass} ] | {@code
+ * visitInnerClass} | {@code visitRecordComponent} | {@code visitField} | {@code visitMethod} )*
+ * {@code visitEnd}.
+ *
* @author Eric Bruneton
*/
public abstract class ClassVisitor {
- /**
- * The ASM API version implemented by this visitor. The value of this field
- * must be one of {@link Opcodes#ASM4}.
- */
- protected final int api;
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
+ */
+ protected final int api;
+
+ /** The class visitor to which this visitor must delegate method calls. May be {@literal null}. */
+ protected ClassVisitor cv;
+
+ /**
+ * Constructs a new {@link ClassVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ */
+ protected ClassVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link ClassVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ * @param classVisitor the class visitor to which this visitor must delegate method calls. May be
+ * null.
+ */
+ protected ClassVisitor(final int api, final ClassVisitor classVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
+ }
+ this.api = api;
+ this.cv = classVisitor;
+ }
+
+ /**
+ * The class visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the class visitor to which this visitor must delegate method calls, or {@literal null}.
+ */
+ public ClassVisitor getDelegate() {
+ return cv;
+ }
+
+ /**
+ * Visits the header of the class.
+ *
+ * @param version the class version. The minor version is stored in the 16 most significant bits,
+ * and the major version in the 16 least significant bits.
+ * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if
+ * the class is deprecated {@link Opcodes#ACC_DEPRECATED} or a record {@link
+ * Opcodes#ACC_RECORD}.
+ * @param name the internal name of the class (see {@link Type#getInternalName()}).
+ * @param signature the signature of this class. May be {@literal null} if the class is not a
+ * generic one, and does not extend or implement generic classes or interfaces.
+ * @param superName the internal of name of the super class (see {@link Type#getInternalName()}).
+ * For interfaces, the super class is {@link Object}. May be {@literal null}, but only for the
+ * {@link Object} class.
+ * @param interfaces the internal names of the class's interfaces (see {@link
+ * Type#getInternalName()}). May be {@literal null}.
+ */
+ public void visit(
+ final int version,
+ final int access,
+ final String name,
+ final String signature,
+ final String superName,
+ final String[] interfaces) {
+ if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) {
+ throw new UnsupportedOperationException("Records requires ASM8");
+ }
+ if (cv != null) {
+ cv.visit(version, access, name, signature, superName, interfaces);
+ }
+ }
+
+ /**
+ * Visits the source of the class.
+ *
+ * @param source the name of the source file from which the class was compiled. May be {@literal
+ * null}.
+ * @param debug additional debug information to compute the correspondence between source and
+ * compiled elements of the class. May be {@literal null}.
+ */
+ public void visitSource(final String source, final String debug) {
+ if (cv != null) {
+ cv.visitSource(source, debug);
+ }
+ }
+
+ /**
+ * Visit the module corresponding to the class.
+ *
+ * @param name the fully qualified name (using dots) of the module.
+ * @param access the module access flags, among {@code ACC_OPEN}, {@code ACC_SYNTHETIC} and {@code
+ * ACC_MANDATED}.
+ * @param version the module version, or {@literal null}.
+ * @return a visitor to visit the module values, or {@literal null} if this visitor is not
+ * interested in visiting this module.
+ */
+ public ModuleVisitor visitModule(final String name, final int access, final String version) {
+ if (api < Opcodes.ASM6) {
+ throw new UnsupportedOperationException("Module requires ASM6");
+ }
+ if (cv != null) {
+ return cv.visitModule(name, access, version);
+ }
+ return null;
+ }
- /**
- * The class visitor to which this visitor must delegate method calls. May
- * be null.
- */
- protected ClassVisitor cv;
+ /**
+ * Visits the nest host class of the class. A nest is a set of classes of the same package that
+ * share access to their private members. One of these classes, called the host, lists the other
+ * members of the nest, which in turn should link to the host of their nest. This method must be
+ * called only once and only if the visited class is a non-host member of a nest. A class is
+ * implicitly its own nest, so it's invalid to call this method with the visited class name as
+ * argument.
+ *
+ * @param nestHost the internal name of the host class of the nest (see {@link
+ * Type#getInternalName()}).
+ */
+ public void visitNestHost(final String nestHost) {
+ if (api < Opcodes.ASM7) {
+ throw new UnsupportedOperationException("NestHost requires ASM7");
+ }
+ if (cv != null) {
+ cv.visitNestHost(nestHost);
+ }
+ }
- /**
- * Constructs a new {@link ClassVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- */
- public ClassVisitor(final int api) {
- this(api, null);
+ /**
+ * Visits the enclosing class of the class. This method must be called only if this class is a
+ * local or anonymous class. See the JVMS 4.7.7 section for more details.
+ *
+ * @param owner internal name of the enclosing class of the class (see {@link
+ * Type#getInternalName()}).
+ * @param name the name of the method that contains the class, or {@literal null} if the class is
+ * not enclosed in a method or constructor of its enclosing class (e.g. if it is enclosed in
+ * an instance initializer, static initializer, instance variable initializer, or class
+ * variable initializer).
+ * @param descriptor the descriptor of the method that contains the class, or {@literal null} if
+ * the class is not enclosed in a method or constructor of its enclosing class (e.g. if it is
+ * enclosed in an instance initializer, static initializer, instance variable initializer, or
+ * class variable initializer).
+ */
+ public void visitOuterClass(final String owner, final String name, final String descriptor) {
+ if (cv != null) {
+ cv.visitOuterClass(owner, name, descriptor);
}
+ }
- /**
- * Constructs a new {@link ClassVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- * @param cv
- * the class visitor to which this visitor must delegate method
- * calls. May be null.
- */
- public ClassVisitor(final int api, final ClassVisitor cv) {
- if (api != Opcodes.ASM4) {
- throw new IllegalArgumentException();
- }
- this.api = api;
- this.cv = cv;
+ /**
+ * Visits an annotation of the class.
+ *
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (cv != null) {
+ return cv.visitAnnotation(descriptor, visible);
}
+ return null;
+ }
- /**
- * Visits the header of the class.
- *
- * @param version
- * the class version.
- * @param access
- * the class's access flags (see {@link Opcodes}). This parameter
- * also indicates if the class is deprecated.
- * @param name
- * the internal name of the class (see
- * {@link Type#getInternalName() getInternalName}).
- * @param signature
- * the signature of this class. May be null if the class
- * is not a generic one, and does not extend or implement generic
- * classes or interfaces.
- * @param superName
- * the internal of name of the super class (see
- * {@link Type#getInternalName() getInternalName}). For
- * interfaces, the super class is {@link Object}. May be
- * null, but only for the {@link Object} class.
- * @param interfaces
- * the internal names of the class's interfaces (see
- * {@link Type#getInternalName() getInternalName}). May be
- * null.
- */
- public void visit(int version, int access, String name, String signature,
- String superName, String[] interfaces) {
- if (cv != null) {
- cv.visit(version, access, name, signature, superName, interfaces);
- }
+ /**
+ * Visits an annotation on a type in the class signature.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link
+ * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See
+ * {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException("TypeAnnotation requires ASM5");
+ }
+ if (cv != null) {
+ return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
}
+ return null;
+ }
- /**
- * Visits the source of the class.
- *
- * @param source
- * the name of the source file from which the class was compiled.
- * May be null.
- * @param debug
- * additional debug information to compute the correspondance
- * between source and compiled elements of the class. May be
- * null.
- */
- public void visitSource(String source, String debug) {
- if (cv != null) {
- cv.visitSource(source, debug);
- }
+ /**
+ * Visits a non standard attribute of the class.
+ *
+ * @param attribute an attribute.
+ */
+ public void visitAttribute(final Attribute attribute) {
+ if (cv != null) {
+ cv.visitAttribute(attribute);
}
+ }
- /**
- * Visits the enclosing class of the class. This method must be called only
- * if the class has an enclosing class.
- *
- * @param owner
- * internal name of the enclosing class of the class.
- * @param name
- * the name of the method that contains the class, or
- * null if the class is not enclosed in a method of its
- * enclosing class.
- * @param desc
- * the descriptor of the method that contains the class, or
- * null if the class is not enclosed in a method of its
- * enclosing class.
- */
- public void visitOuterClass(String owner, String name, String desc) {
- if (cv != null) {
- cv.visitOuterClass(owner, name, desc);
- }
+ /**
+ * Visits a member of the nest. A nest is a set of classes of the same package that share access
+ * to their private members. One of these classes, called the host, lists the other members of the
+ * nest, which in turn should link to the host of their nest. This method must be called only if
+ * the visited class is the host of a nest. A nest host is implicitly a member of its own nest, so
+ * it's invalid to call this method with the visited class name as argument.
+ *
+ * @param nestMember the internal name of a nest member (see {@link Type#getInternalName()}).
+ */
+ public void visitNestMember(final String nestMember) {
+ if (api < Opcodes.ASM7) {
+ throw new UnsupportedOperationException("NestMember requires ASM7");
}
+ if (cv != null) {
+ cv.visitNestMember(nestMember);
+ }
+ }
- /**
- * Visits an annotation of the class.
- *
- * @param desc
- * the class descriptor of the annotation class.
- * @param visible
- * true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if
- * this visitor is not interested in visiting this annotation.
- */
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (cv != null) {
- return cv.visitAnnotation(desc, visible);
- }
- return null;
+ /**
+ * Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the
+ * current class.
+ *
+ * @param permittedSubclass the internal name of a permitted subclass (see {@link
+ * Type#getInternalName()}).
+ */
+ public void visitPermittedSubclass(final String permittedSubclass) {
+ if (api < Opcodes.ASM9) {
+ throw new UnsupportedOperationException("PermittedSubclasses requires ASM9");
+ }
+ if (cv != null) {
+ cv.visitPermittedSubclass(permittedSubclass);
}
+ }
- /**
- * Visits a non standard attribute of the class.
- *
- * @param attr
- * an attribute.
- */
- public void visitAttribute(Attribute attr) {
- if (cv != null) {
- cv.visitAttribute(attr);
- }
+ /**
+ * Visits information about an inner class. This inner class is not necessarily a member of the
+ * class being visited. More precisely, every class or interface C which is referenced by this
+ * class and which is not a package member must be visited with this method. This class must
+ * reference its nested class or interface members, and its enclosing class, if any. See the JVMS
+ * 4.7.6 section for more details.
+ *
+ * @param name the internal name of C (see {@link Type#getInternalName()}).
+ * @param outerName the internal name of the class or interface C is a member of (see {@link
+ * Type#getInternalName()}). Must be {@literal null} if C is not the member of a class or
+ * interface (e.g. for local or anonymous classes).
+ * @param innerName the (simple) name of C. Must be {@literal null} for anonymous inner classes.
+ * @param access the access flags of C originally declared in the source code from which this
+ * class was compiled.
+ */
+ public void visitInnerClass(
+ final String name, final String outerName, final String innerName, final int access) {
+ if (cv != null) {
+ cv.visitInnerClass(name, outerName, innerName, access);
}
+ }
- /**
- * Visits information about an inner class. This inner class is not
- * necessarily a member of the class being visited.
- *
- * @param name
- * the internal name of an inner class (see
- * {@link Type#getInternalName() getInternalName}).
- * @param outerName
- * the internal name of the class to which the inner class
- * belongs (see {@link Type#getInternalName() getInternalName}).
- * May be null for not member classes.
- * @param innerName
- * the (simple) name of the inner class inside its enclosing
- * class. May be null for anonymous inner classes.
- * @param access
- * the access flags of the inner class as originally declared in
- * the enclosing class.
- */
- public void visitInnerClass(String name, String outerName,
- String innerName, int access) {
- if (cv != null) {
- cv.visitInnerClass(name, outerName, innerName, access);
- }
+ /**
+ * Visits a record component of the class.
+ *
+ * @param name the record component name.
+ * @param descriptor the record component descriptor (see {@link Type}).
+ * @param signature the record component signature. May be {@literal null} if the record component
+ * type does not use generic types.
+ * @return a visitor to visit this record component annotations and attributes, or {@literal null}
+ * if this class visitor is not interested in visiting these annotations and attributes.
+ */
+ public RecordComponentVisitor visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ if (api < Opcodes.ASM8) {
+ throw new UnsupportedOperationException("Record requires ASM8");
+ }
+ if (cv != null) {
+ return cv.visitRecordComponent(name, descriptor, signature);
}
+ return null;
+ }
- /**
- * Visits a field of the class.
- *
- * @param access
- * the field's access flags (see {@link Opcodes}). This parameter
- * also indicates if the field is synthetic and/or deprecated.
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor (see {@link Type Type}).
- * @param signature
- * the field's signature. May be null if the field's
- * type does not use generic types.
- * @param value
- * the field's initial value. This parameter, which may be
- * null if the field does not have an initial value,
- * must be an {@link Integer}, a {@link Float}, a {@link Long}, a
- * {@link Double} or a {@link String} (for int,
- * float, long or String fields
- * respectively). This parameter is only used for static
- * fields. Its value is ignored for non static fields, which
- * must be initialized through bytecode instructions in
- * constructors or methods.
- * @return a visitor to visit field annotations and attributes, or
- * null if this class visitor is not interested in visiting
- * these annotations and attributes.
- */
- public FieldVisitor visitField(int access, String name, String desc,
- String signature, Object value) {
- if (cv != null) {
- return cv.visitField(access, name, desc, signature, value);
- }
- return null;
+ /**
+ * Visits a field of the class.
+ *
+ * @param access the field's access flags (see {@link Opcodes}). This parameter also indicates if
+ * the field is synthetic and/or deprecated.
+ * @param name the field's name.
+ * @param descriptor the field's descriptor (see {@link Type}).
+ * @param signature the field's signature. May be {@literal null} if the field's type does not use
+ * generic types.
+ * @param value the field's initial value. This parameter, which may be {@literal null} if the
+ * field does not have an initial value, must be an {@link Integer}, a {@link Float}, a {@link
+ * Long}, a {@link Double} or a {@link String} (for {@code int}, {@code float}, {@code long}
+ * or {@code String} fields respectively). This parameter is only used for static
+ * fields. Its value is ignored for non static fields, which must be initialized through
+ * bytecode instructions in constructors or methods.
+ * @return a visitor to visit field annotations and attributes, or {@literal null} if this class
+ * visitor is not interested in visiting these annotations and attributes.
+ */
+ public FieldVisitor visitField(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Object value) {
+ if (cv != null) {
+ return cv.visitField(access, name, descriptor, signature, value);
}
+ return null;
+ }
- /**
- * Visits a method of the class. This method must return a new
- * {@link MethodVisitor} instance (or null) each time it is called,
- * i.e., it should not return a previously returned visitor.
- *
- * @param access
- * the method's access flags (see {@link Opcodes}). This
- * parameter also indicates if the method is synthetic and/or
- * deprecated.
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor (see {@link Type Type}).
- * @param signature
- * the method's signature. May be null if the method
- * parameters, return type and exceptions do not use generic
- * types.
- * @param exceptions
- * the internal names of the method's exception classes (see
- * {@link Type#getInternalName() getInternalName}). May be
- * null.
- * @return an object to visit the byte code of the method, or null
- * if this class visitor is not interested in visiting the code of
- * this method.
- */
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
- if (cv != null) {
- return cv.visitMethod(access, name, desc, signature, exceptions);
- }
- return null;
+ /**
+ * Visits a method of the class. This method must return a new {@link MethodVisitor}
+ * instance (or {@literal null}) each time it is called, i.e., it should not return a previously
+ * returned visitor.
+ *
+ * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if
+ * the method is synthetic and/or deprecated.
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @param signature the method's signature. May be {@literal null} if the method parameters,
+ * return type and exceptions do not use generic types.
+ * @param exceptions the internal names of the method's exception classes (see {@link
+ * Type#getInternalName()}). May be {@literal null}.
+ * @return an object to visit the byte code of the method, or {@literal null} if this class
+ * visitor is not interested in visiting the code of this method.
+ */
+ public MethodVisitor visitMethod(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final String[] exceptions) {
+ if (cv != null) {
+ return cv.visitMethod(access, name, descriptor, signature, exceptions);
}
+ return null;
+ }
- /**
- * Visits the end of the class. This method, which is the last one to be
- * called, is used to inform the visitor that all the fields and methods of
- * the class have been visited.
- */
- public void visitEnd() {
- if (cv != null) {
- cv.visitEnd();
- }
+ /**
+ * Visits the end of the class. This method, which is the last one to be called, is used to inform
+ * the visitor that all the fields and methods of the class have been visited.
+ */
+ public void visitEnd() {
+ if (cv != null) {
+ cv.visitEnd();
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/ClassWriter.java b/src/java/nginx/clojure/asm/ClassWriter.java
index b921e0ee..17273461 100644
--- a/src/java/nginx/clojure/asm/ClassWriter.java
+++ b/src/java/nginx/clojure/asm/ClassWriter.java
@@ -1,1706 +1,1079 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A {@link ClassVisitor} that generates classes in bytecode form. More
- * precisely this visitor generates a byte array conforming to the Java class
- * file format. It can be used alone, to generate a Java class "from scratch",
- * or with one or more {@link ClassReader ClassReader} and adapter class visitor
- * to generate a modified class from one or more existing Java classes.
- *
+ * A {@link ClassVisitor} that generates a corresponding ClassFile structure, as defined in the Java
+ * Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from
+ * scratch", or with one or more {@link ClassReader} and adapter {@link ClassVisitor} to generate a
+ * modified class from one or more existing Java classes.
+ *
+ * @see JVMS 4
* @author Eric Bruneton
*/
public class ClassWriter extends ClassVisitor {
- /**
- * Flag to automatically compute the maximum stack size and the maximum
- * number of local variables of methods. If this flag is set, then the
- * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the
- * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod}
- * method will be ignored, and computed automatically from the signature and
- * the bytecode of each method.
- *
- * @see #ClassWriter(int)
- */
- public static final int COMPUTE_MAXS = 1;
-
- /**
- * Flag to automatically compute the stack map frames of methods from
- * scratch. If this flag is set, then the calls to the
- * {@link MethodVisitor#visitFrame} method are ignored, and the stack map
- * frames are recomputed from the methods bytecode. The arguments of the
- * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and
- * recomputed from the bytecode. In other words, computeFrames implies
- * computeMaxs.
- *
- * @see #ClassWriter(int)
- */
- public static final int COMPUTE_FRAMES = 2;
-
- /**
- * Pseudo access flag to distinguish between the synthetic attribute and the
- * synthetic access flag.
- */
- static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
-
- /**
- * Factor to convert from ACC_SYNTHETIC_ATTRIBUTE to Opcode.ACC_SYNTHETIC.
- */
- static final int TO_ACC_SYNTHETIC = ACC_SYNTHETIC_ATTRIBUTE
- / Opcodes.ACC_SYNTHETIC;
-
- /**
- * The type of instructions without any argument.
- */
- static final int NOARG_INSN = 0;
-
- /**
- * The type of instructions with an signed byte argument.
- */
- static final int SBYTE_INSN = 1;
-
- /**
- * The type of instructions with an signed short argument.
- */
- static final int SHORT_INSN = 2;
-
- /**
- * The type of instructions with a local variable index argument.
- */
- static final int VAR_INSN = 3;
-
- /**
- * The type of instructions with an implicit local variable index argument.
- */
- static final int IMPLVAR_INSN = 4;
-
- /**
- * The type of instructions with a type descriptor argument.
- */
- static final int TYPE_INSN = 5;
-
- /**
- * The type of field and method invocations instructions.
- */
- static final int FIELDORMETH_INSN = 6;
-
- /**
- * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction.
- */
- static final int ITFMETH_INSN = 7;
-
- /**
- * The type of the INVOKEDYNAMIC instruction.
- */
- static final int INDYMETH_INSN = 8;
-
- /**
- * The type of instructions with a 2 bytes bytecode offset label.
- */
- static final int LABEL_INSN = 9;
-
- /**
- * The type of instructions with a 4 bytes bytecode offset label.
- */
- static final int LABELW_INSN = 10;
-
- /**
- * The type of the LDC instruction.
- */
- static final int LDC_INSN = 11;
-
- /**
- * The type of the LDC_W and LDC2_W instructions.
- */
- static final int LDCW_INSN = 12;
-
- /**
- * The type of the IINC instruction.
- */
- static final int IINC_INSN = 13;
-
- /**
- * The type of the TABLESWITCH instruction.
- */
- static final int TABL_INSN = 14;
-
- /**
- * The type of the LOOKUPSWITCH instruction.
- */
- static final int LOOK_INSN = 15;
-
- /**
- * The type of the MULTIANEWARRAY instruction.
- */
- static final int MANA_INSN = 16;
-
- /**
- * The type of the WIDE instruction.
- */
- static final int WIDE_INSN = 17;
-
- /**
- * The instruction types of all JVM opcodes.
- */
- static final byte[] TYPE;
-
- /**
- * The type of CONSTANT_Class constant pool items.
- */
- static final int CLASS = 7;
-
- /**
- * The type of CONSTANT_Fieldref constant pool items.
- */
- static final int FIELD = 9;
-
- /**
- * The type of CONSTANT_Methodref constant pool items.
- */
- static final int METH = 10;
-
- /**
- * The type of CONSTANT_InterfaceMethodref constant pool items.
- */
- static final int IMETH = 11;
-
- /**
- * The type of CONSTANT_String constant pool items.
- */
- static final int STR = 8;
-
- /**
- * The type of CONSTANT_Integer constant pool items.
- */
- static final int INT = 3;
-
- /**
- * The type of CONSTANT_Float constant pool items.
- */
- static final int FLOAT = 4;
-
- /**
- * The type of CONSTANT_Long constant pool items.
- */
- static final int LONG = 5;
-
- /**
- * The type of CONSTANT_Double constant pool items.
- */
- static final int DOUBLE = 6;
-
- /**
- * The type of CONSTANT_NameAndType constant pool items.
- */
- static final int NAME_TYPE = 12;
-
- /**
- * The type of CONSTANT_Utf8 constant pool items.
- */
- static final int UTF8 = 1;
-
- /**
- * The type of CONSTANT_MethodType constant pool items.
- */
- static final int MTYPE = 16;
-
- /**
- * The type of CONSTANT_MethodHandle constant pool items.
- */
- static final int HANDLE = 15;
-
- /**
- * The type of CONSTANT_InvokeDynamic constant pool items.
- */
- static final int INDY = 18;
-
- /**
- * The base value for all CONSTANT_MethodHandle constant pool items.
- * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into 9
- * different items.
- */
- static final int HANDLE_BASE = 20;
-
- /**
- * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable},
- * instead of the constant pool, in order to avoid clashes with normal
- * constant pool items in the ClassWriter constant pool's hash table.
- */
- static final int TYPE_NORMAL = 30;
-
- /**
- * Uninitialized type Item stored in the ClassWriter
- * {@link ClassWriter#typeTable}, instead of the constant pool, in order to
- * avoid clashes with normal constant pool items in the ClassWriter constant
- * pool's hash table.
- */
- static final int TYPE_UNINIT = 31;
-
- /**
- * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable},
- * instead of the constant pool, in order to avoid clashes with normal
- * constant pool items in the ClassWriter constant pool's hash table.
- */
- static final int TYPE_MERGED = 32;
-
- /**
- * The type of BootstrapMethods items. These items are stored in a special
- * class attribute named BootstrapMethods and not in the constant pool.
- */
- static final int BSM = 33;
-
- /**
- * The class reader from which this class writer was constructed, if any.
- */
- ClassReader cr;
-
- /**
- * Minor and major version numbers of the class to be generated.
- */
- int version;
-
- /**
- * Index of the next item to be added in the constant pool.
- */
- int index;
-
- /**
- * The constant pool of this class.
- */
- final ByteVector pool;
-
- /**
- * The constant pool's hash table data.
- */
- Item[] items;
-
- /**
- * The threshold of the constant pool's hash table.
- */
- int threshold;
-
- /**
- * A reusable key used to look for items in the {@link #items} hash table.
- */
- final Item key;
-
- /**
- * A reusable key used to look for items in the {@link #items} hash table.
- */
- final Item key2;
-
- /**
- * A reusable key used to look for items in the {@link #items} hash table.
- */
- final Item key3;
-
- /**
- * A reusable key used to look for items in the {@link #items} hash table.
- */
- final Item key4;
-
- /**
- * A type table used to temporarily store internal names that will not
- * necessarily be stored in the constant pool. This type table is used by
- * the control flow and data flow analysis algorithm used to compute stack
- * map frames from scratch. This array associates to each index i
- * the Item whose index is i. All Item objects stored in this array
- * are also stored in the {@link #items} hash table. These two arrays allow
- * to retrieve an Item from its index or, conversely, to get the index of an
- * Item from its value. Each Item stores an internal name in its
- * {@link Item#strVal1} field.
- */
- Item[] typeTable;
-
- /**
- * Number of elements in the {@link #typeTable} array.
- */
- private short typeCount;
-
- /**
- * The access flags of this class.
- */
- private int access;
-
- /**
- * The constant pool item that contains the internal name of this class.
- */
- private int name;
-
- /**
- * The internal name of this class.
- */
- String thisName;
-
- /**
- * The constant pool item that contains the signature of this class.
- */
- private int signature;
-
- /**
- * The constant pool item that contains the internal name of the super class
- * of this class.
- */
- private int superName;
-
- /**
- * Number of interfaces implemented or extended by this class or interface.
- */
- private int interfaceCount;
-
- /**
- * The interfaces implemented or extended by this class or interface. More
- * precisely, this array contains the indexes of the constant pool items
- * that contain the internal names of these interfaces.
- */
- private int[] interfaces;
-
- /**
- * The index of the constant pool item that contains the name of the source
- * file from which this class was compiled.
- */
- private int sourceFile;
-
- /**
- * The SourceDebug attribute of this class.
- */
- private ByteVector sourceDebug;
-
- /**
- * The constant pool item that contains the name of the enclosing class of
- * this class.
- */
- private int enclosingMethodOwner;
-
- /**
- * The constant pool item that contains the name and descriptor of the
- * enclosing method of this class.
- */
- private int enclosingMethod;
-
- /**
- * The runtime visible annotations of this class.
- */
- private AnnotationWriter anns;
-
- /**
- * The runtime invisible annotations of this class.
- */
- private AnnotationWriter ianns;
-
- /**
- * The non standard attributes of this class.
- */
- private Attribute attrs;
-
- /**
- * The number of entries in the InnerClasses attribute.
- */
- private int innerClassesCount;
-
- /**
- * The InnerClasses attribute.
- */
- private ByteVector innerClasses;
-
- /**
- * The number of entries in the BootstrapMethods attribute.
- */
- int bootstrapMethodsCount;
-
- /**
- * The BootstrapMethods attribute.
- */
- ByteVector bootstrapMethods;
-
- /**
- * The fields of this class. These fields are stored in a linked list of
- * {@link FieldWriter} objects, linked to each other by their
- * {@link FieldWriter#fv} field. This field stores the first element of this
- * list.
- */
- FieldWriter firstField;
-
- /**
- * The fields of this class. These fields are stored in a linked list of
- * {@link FieldWriter} objects, linked to each other by their
- * {@link FieldWriter#fv} field. This field stores the last element of this
- * list.
- */
- FieldWriter lastField;
-
- /**
- * The methods of this class. These methods are stored in a linked list of
- * {@link MethodWriter} objects, linked to each other by their
- * {@link MethodWriter#mv} field. This field stores the first element of
- * this list.
- */
- MethodWriter firstMethod;
-
- /**
- * The methods of this class. These methods are stored in a linked list of
- * {@link MethodWriter} objects, linked to each other by their
- * {@link MethodWriter#mv} field. This field stores the last element of this
- * list.
- */
- MethodWriter lastMethod;
-
- /**
- * true if the maximum stack size and number of local variables
- * must be automatically computed.
- */
- private boolean computeMaxs;
-
- /**
- * true if the stack map frames must be recomputed from scratch.
- */
- private boolean computeFrames;
-
- /**
- * true if the stack map tables of this class are invalid. The
- * {@link MethodWriter#resizeInstructions} method cannot transform existing
- * stack map tables, and so produces potentially invalid classes when it is
- * executed. In this case the class is reread and rewritten with the
- * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize
- * stack map tables when this option is used).
- */
- boolean invalidFrames;
-
- // ------------------------------------------------------------------------
- // Static initializer
- // ------------------------------------------------------------------------
-
- /**
- * Computes the instruction types of JVM opcodes.
- */
- static {
- int i;
- byte[] b = new byte[220];
- String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD"
- + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
- + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA"
- + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ";
- for (i = 0; i < b.length; ++i) {
- b[i] = (byte) (s.charAt(i) - 'A');
- }
- TYPE = b;
-
- // code to generate the above string
- //
- // // SBYTE_INSN instructions
- // b[Constants.NEWARRAY] = SBYTE_INSN;
- // b[Constants.BIPUSH] = SBYTE_INSN;
- //
- // // SHORT_INSN instructions
- // b[Constants.SIPUSH] = SHORT_INSN;
- //
- // // (IMPL)VAR_INSN instructions
- // b[Constants.RET] = VAR_INSN;
- // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) {
- // b[i] = VAR_INSN;
- // }
- // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) {
- // b[i] = VAR_INSN;
- // }
- // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3
- // b[i] = IMPLVAR_INSN;
- // }
- // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3
- // b[i] = IMPLVAR_INSN;
- // }
- //
- // // TYPE_INSN instructions
- // b[Constants.NEW] = TYPE_INSN;
- // b[Constants.ANEWARRAY] = TYPE_INSN;
- // b[Constants.CHECKCAST] = TYPE_INSN;
- // b[Constants.INSTANCEOF] = TYPE_INSN;
- //
- // // (Set)FIELDORMETH_INSN instructions
- // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) {
- // b[i] = FIELDORMETH_INSN;
- // }
- // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN;
- // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN;
- //
- // // LABEL(W)_INSN instructions
- // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) {
- // b[i] = LABEL_INSN;
- // }
- // b[Constants.IFNULL] = LABEL_INSN;
- // b[Constants.IFNONNULL] = LABEL_INSN;
- // b[200] = LABELW_INSN; // GOTO_W
- // b[201] = LABELW_INSN; // JSR_W
- // // temporary opcodes used internally by ASM - see Label and
- // MethodWriter
- // for (i = 202; i < 220; ++i) {
- // b[i] = LABEL_INSN;
- // }
- //
- // // LDC(_W) instructions
- // b[Constants.LDC] = LDC_INSN;
- // b[19] = LDCW_INSN; // LDC_W
- // b[20] = LDCW_INSN; // LDC2_W
- //
- // // special instructions
- // b[Constants.IINC] = IINC_INSN;
- // b[Constants.TABLESWITCH] = TABL_INSN;
- // b[Constants.LOOKUPSWITCH] = LOOK_INSN;
- // b[Constants.MULTIANEWARRAY] = MANA_INSN;
- // b[196] = WIDE_INSN; // WIDE
- //
- // for (i = 0; i < b.length; ++i) {
- // System.err.print((char)('A' + b[i]));
- // }
- // System.err.println();
+ /**
+ * A flag to automatically compute the maximum stack size and the maximum number of local
+ * variables of methods. If this flag is set, then the arguments of the {@link
+ * MethodVisitor#visitMaxs} method of the {@link MethodVisitor} returned by the {@link
+ * #visitMethod} method will be ignored, and computed automatically from the signature and the
+ * bytecode of each method.
+ *
+ * Note: for classes whose version is {@link Opcodes#V1_7} of more, this option requires
+ * valid stack map frames. The maximum stack size is then computed from these frames, and from the
+ * bytecode instructions in between. If stack map frames are not present or must be recomputed,
+ * used {@link #COMPUTE_FRAMES} instead.
+ *
+ * @see #ClassWriter(int)
+ */
+ public static final int COMPUTE_MAXS = 1;
+
+ /**
+ * A flag to automatically compute the stack map frames of methods from scratch. If this flag is
+ * set, then the calls to the {@link MethodVisitor#visitFrame} method are ignored, and the stack
+ * map frames are recomputed from the methods bytecode. The arguments of the {@link
+ * MethodVisitor#visitMaxs} method are also ignored and recomputed from the bytecode. In other
+ * words, {@link #COMPUTE_FRAMES} implies {@link #COMPUTE_MAXS}.
+ *
+ * @see #ClassWriter(int)
+ */
+ public static final int COMPUTE_FRAMES = 2;
+
+ /**
+ * The flags passed to the constructor. Must be zero or more of {@link #COMPUTE_MAXS} and {@link
+ * #COMPUTE_FRAMES}.
+ */
+ private final int flags;
+
+ // Note: fields are ordered as in the ClassFile structure, and those related to attributes are
+ // ordered as in Section 4.7 of the JVMS.
+
+ /**
+ * The minor_version and major_version fields of the JVMS ClassFile structure. minor_version is
+ * stored in the 16 most significant bits, and major_version in the 16 least significant bits.
+ */
+ private int version;
+
+ /** The symbol table for this class (contains the constant_pool and the BootstrapMethods). */
+ private final SymbolTable symbolTable;
+
+ /**
+ * The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific
+ * access flags, such as {@link Opcodes#ACC_DEPRECATED} or {@link Opcodes#ACC_RECORD}, which are
+ * removed when generating the ClassFile structure.
+ */
+ private int accessFlags;
+
+ /** The this_class field of the JVMS ClassFile structure. */
+ private int thisClass;
+
+ /** The super_class field of the JVMS ClassFile structure. */
+ private int superClass;
+
+ /** The interface_count field of the JVMS ClassFile structure. */
+ private int interfaceCount;
+
+ /** The 'interfaces' array of the JVMS ClassFile structure. */
+ private int[] interfaces;
+
+ /**
+ * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their
+ * {@link FieldWriter#fv} field. This field stores the first element of this list.
+ */
+ private FieldWriter firstField;
+
+ /**
+ * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their
+ * {@link FieldWriter#fv} field. This field stores the last element of this list.
+ */
+ private FieldWriter lastField;
+
+ /**
+ * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their
+ * {@link MethodWriter#mv} field. This field stores the first element of this list.
+ */
+ private MethodWriter firstMethod;
+
+ /**
+ * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their
+ * {@link MethodWriter#mv} field. This field stores the last element of this list.
+ */
+ private MethodWriter lastMethod;
+
+ /** The number_of_classes field of the InnerClasses attribute, or 0. */
+ private int numberOfInnerClasses;
+
+ /** The 'classes' array of the InnerClasses attribute, or {@literal null}. */
+ private ByteVector innerClasses;
+
+ /** The class_index field of the EnclosingMethod attribute, or 0. */
+ private int enclosingClassIndex;
+
+ /** The method_index field of the EnclosingMethod attribute. */
+ private int enclosingMethodIndex;
+
+ /** The signature_index field of the Signature attribute, or 0. */
+ private int signatureIndex;
+
+ /** The source_file_index field of the SourceFile attribute, or 0. */
+ private int sourceFileIndex;
+
+ /** The debug_extension field of the SourceDebugExtension attribute, or {@literal null}. */
+ private ByteVector debugExtension;
+
+ /**
+ * The last runtime visible annotation of this class. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleAnnotation;
+
+ /**
+ * The last runtime invisible annotation of this class. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleAnnotation;
+
+ /**
+ * The last runtime visible type annotation of this class. The previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
+
+ /**
+ * The last runtime invisible type annotation of this class. The previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
+
+ /** The Module attribute of this class, or {@literal null}. */
+ private ModuleWriter moduleWriter;
+
+ /** The host_class_index field of the NestHost attribute, or 0. */
+ private int nestHostClassIndex;
+
+ /** The number_of_classes field of the NestMembers attribute, or 0. */
+ private int numberOfNestMemberClasses;
+
+ /** The 'classes' array of the NestMembers attribute, or {@literal null}. */
+ private ByteVector nestMemberClasses;
+
+ /** The number_of_classes field of the PermittedSubclasses attribute, or 0. */
+ private int numberOfPermittedSubclasses;
+
+ /** The 'classes' array of the PermittedSubclasses attribute, or {@literal null}. */
+ private ByteVector permittedSubclasses;
+
+ /**
+ * The record components of this class, stored in a linked list of {@link RecordComponentWriter}
+ * linked via their {@link RecordComponentWriter#delegate} field. This field stores the first
+ * element of this list.
+ */
+ private RecordComponentWriter firstRecordComponent;
+
+ /**
+ * The record components of this class, stored in a linked list of {@link RecordComponentWriter}
+ * linked via their {@link RecordComponentWriter#delegate} field. This field stores the last
+ * element of this list.
+ */
+ private RecordComponentWriter lastRecordComponent;
+
+ /**
+ * The first non standard attribute of this class. The next ones can be accessed with the {@link
+ * Attribute#nextAttribute} field. May be {@literal null}.
+ *
+ *
WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
+ * #toByteArray} method writes the attributes in the order defined by this list, i.e. in the
+ * reverse order specified by the user.
+ */
+ private Attribute firstAttribute;
+
+ /**
+ * Indicates what must be automatically computed in {@link MethodWriter}. Must be one of {@link
+ * MethodWriter#COMPUTE_NOTHING}, {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL}, {@link
+ * MethodWriter#COMPUTE_INSERTED_FRAMES}, or {@link MethodWriter#COMPUTE_ALL_FRAMES}.
+ */
+ private int compute;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructor
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ClassWriter} object.
+ *
+ * @param flags option flags that can be used to modify the default behavior of this class. Must
+ * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}.
+ */
+ public ClassWriter(final int flags) {
+ this(null, flags);
+ }
+
+ /**
+ * Constructs a new {@link ClassWriter} object and enables optimizations for "mostly add" bytecode
+ * transformations. These optimizations are the following:
+ *
+ *
+ * - The constant pool and bootstrap methods from the original class are copied as is in the
+ * new class, which saves time. New constant pool entries and new bootstrap methods will be
+ * added at the end if necessary, but unused constant pool entries or bootstrap methods
+ * won't be removed.
+ *
- Methods that are not transformed are copied as is in the new class, directly from the
+ * original class bytecode (i.e. without emitting visit events for all the method
+ * instructions), which saves a lot of time. Untransformed methods are detected by
+ * the fact that the {@link ClassReader} receives {@link MethodVisitor} objects that come
+ * from a {@link ClassWriter} (and not from any other {@link ClassVisitor} instance).
+ *
+ *
+ * @param classReader the {@link ClassReader} used to read the original class. It will be used to
+ * copy the entire constant pool and bootstrap methods from the original class and also to
+ * copy other fragments of original bytecode where applicable.
+ * @param flags option flags that can be used to modify the default behavior of this class. Must
+ * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. These option flags
+ * do not affect methods that are copied as is in the new class. This means that neither the
+ * maximum stack size nor the stack frames will be computed for these methods.
+ */
+ public ClassWriter(final ClassReader classReader, final int flags) {
+ super(/* latest api = */ Opcodes.ASM9);
+ this.flags = flags;
+ symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader);
+ if ((flags & COMPUTE_FRAMES) != 0) {
+ compute = MethodWriter.COMPUTE_ALL_FRAMES;
+ } else if ((flags & COMPUTE_MAXS) != 0) {
+ compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL;
+ } else {
+ compute = MethodWriter.COMPUTE_NOTHING;
}
-
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a new {@link ClassWriter} object.
- *
- * @param flags
- * option flags that can be used to modify the default behavior
- * of this class. See {@link #COMPUTE_MAXS},
- * {@link #COMPUTE_FRAMES}.
- */
- public ClassWriter(final int flags) {
- super(Opcodes.ASM4);
- index = 1;
- pool = new ByteVector();
- items = new Item[256];
- threshold = (int) (0.75d * items.length);
- key = new Item();
- key2 = new Item();
- key3 = new Item();
- key4 = new Item();
- this.computeMaxs = (flags & COMPUTE_MAXS) != 0;
- this.computeFrames = (flags & COMPUTE_FRAMES) != 0;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns true if all the given flags were passed to the constructor.
+ *
+ * @param flags some option flags. Must be zero or more of {@link #COMPUTE_MAXS} and {@link
+ * #COMPUTE_FRAMES}.
+ * @return true if all the given flags, or more, were passed to the constructor.
+ */
+ public boolean hasFlags(final int flags) {
+ return (this.flags & flags) == flags;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the ClassVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
+
+ @Override
+ public final void visit(
+ final int version,
+ final int access,
+ final String name,
+ final String signature,
+ final String superName,
+ final String[] interfaces) {
+ this.version = version;
+ this.accessFlags = access;
+ this.thisClass = symbolTable.setMajorVersionAndClassName(version & 0xFFFF, name);
+ if (signature != null) {
+ this.signatureIndex = symbolTable.addConstantUtf8(signature);
}
-
- /**
- * Constructs a new {@link ClassWriter} object and enables optimizations for
- * "mostly add" bytecode transformations. These optimizations are the
- * following:
- *
- *
- * - The constant pool from the original class is copied as is in the new
- * class, which saves time. New constant pool entries will be added at the
- * end if necessary, but unused constant pool entries won't be
- * removed.
- * - Methods that are not transformed are copied as is in the new class,
- * directly from the original class bytecode (i.e. without emitting visit
- * events for all the method instructions), which saves a lot of
- * time. Untransformed methods are detected by the fact that the
- * {@link ClassReader} receives {@link MethodVisitor} objects that come from
- * a {@link ClassWriter} (and not from any other {@link ClassVisitor}
- * instance).
- *
- *
- * @param classReader
- * the {@link ClassReader} used to read the original class. It
- * will be used to copy the entire constant pool from the
- * original class and also to copy other fragments of original
- * bytecode where applicable.
- * @param flags
- * option flags that can be used to modify the default behavior
- * of this class. These option flags do not affect methods
- * that are copied as is in the new class. This means that the
- * maximum stack size nor the stack frames will be computed for
- * these methods. See {@link #COMPUTE_MAXS},
- * {@link #COMPUTE_FRAMES}.
- */
- public ClassWriter(final ClassReader classReader, final int flags) {
- this(flags);
- classReader.copyPool(this);
- this.cr = classReader;
+ this.superClass = superName == null ? 0 : symbolTable.addConstantClass(superName).index;
+ if (interfaces != null && interfaces.length > 0) {
+ interfaceCount = interfaces.length;
+ this.interfaces = new int[interfaceCount];
+ for (int i = 0; i < interfaceCount; ++i) {
+ this.interfaces[i] = symbolTable.addConstantClass(interfaces[i]).index;
+ }
}
-
- // ------------------------------------------------------------------------
- // Implementation of the ClassVisitor abstract class
- // ------------------------------------------------------------------------
-
- @Override
- public final void visit(final int version, final int access,
- final String name, final String signature, final String superName,
- final String[] interfaces) {
- this.version = version;
- this.access = access;
- this.name = newClass(name);
- thisName = name;
- if (ClassReader.SIGNATURES && signature != null) {
- this.signature = newUTF8(signature);
- }
- this.superName = superName == null ? 0 : newClass(superName);
- if (interfaces != null && interfaces.length > 0) {
- interfaceCount = interfaces.length;
- this.interfaces = new int[interfaceCount];
- for (int i = 0; i < interfaceCount; ++i) {
- this.interfaces[i] = newClass(interfaces[i]);
- }
- }
+ if (compute == MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL && (version & 0xFFFF) >= Opcodes.V1_7) {
+ compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES;
}
+ }
- @Override
- public final void visitSource(final String file, final String debug) {
- if (file != null) {
- sourceFile = newUTF8(file);
- }
- if (debug != null) {
- sourceDebug = new ByteVector().putUTF8(debug);
- }
+ @Override
+ public final void visitSource(final String file, final String debug) {
+ if (file != null) {
+ sourceFileIndex = symbolTable.addConstantUtf8(file);
}
-
- @Override
- public final void visitOuterClass(final String owner, final String name,
- final String desc) {
- enclosingMethodOwner = newClass(owner);
- if (name != null && desc != null) {
- enclosingMethod = newNameType(name, desc);
- }
+ if (debug != null) {
+ debugExtension = new ByteVector().encodeUtf8(debug, 0, Integer.MAX_VALUE);
}
-
- @Override
- public final AnnotationVisitor visitAnnotation(final String desc,
- final boolean visible) {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- ByteVector bv = new ByteVector();
- // write type, and reserve space for values count
- bv.putShort(newUTF8(desc)).putShort(0);
- AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2);
- if (visible) {
- aw.next = anns;
- anns = aw;
- } else {
- aw.next = ianns;
- ianns = aw;
- }
- return aw;
+ }
+
+ @Override
+ public final ModuleVisitor visitModule(
+ final String name, final int access, final String version) {
+ return moduleWriter =
+ new ModuleWriter(
+ symbolTable,
+ symbolTable.addConstantModule(name).index,
+ access,
+ version == null ? 0 : symbolTable.addConstantUtf8(version));
+ }
+
+ @Override
+ public final void visitNestHost(final String nestHost) {
+ nestHostClassIndex = symbolTable.addConstantClass(nestHost).index;
+ }
+
+ @Override
+ public final void visitOuterClass(
+ final String owner, final String name, final String descriptor) {
+ enclosingClassIndex = symbolTable.addConstantClass(owner).index;
+ if (name != null && descriptor != null) {
+ enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor);
}
-
- @Override
- public final void visitAttribute(final Attribute attr) {
- attr.next = attrs;
- attrs = attr;
+ }
+
+ @Override
+ public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
+ } else {
+ return lastRuntimeInvisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
}
-
- @Override
- public final void visitInnerClass(final String name,
- final String outerName, final String innerName, final int access) {
- if (innerClasses == null) {
- innerClasses = new ByteVector();
- }
- ++innerClassesCount;
- innerClasses.putShort(name == null ? 0 : newClass(name));
- innerClasses.putShort(outerName == null ? 0 : newClass(outerName));
- innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName));
- innerClasses.putShort(access);
+ }
+
+ @Override
+ public final AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastRuntimeInvisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
}
-
- @Override
- public final FieldVisitor visitField(final int access, final String name,
- final String desc, final String signature, final Object value) {
- return new FieldWriter(this, access, name, desc, signature, value);
+ }
+
+ @Override
+ public final void visitAttribute(final Attribute attribute) {
+ // Store the attributes in the reverse order of their visit by this method.
+ attribute.nextAttribute = firstAttribute;
+ firstAttribute = attribute;
+ }
+
+ @Override
+ public final void visitNestMember(final String nestMember) {
+ if (nestMemberClasses == null) {
+ nestMemberClasses = new ByteVector();
}
-
- @Override
- public final MethodVisitor visitMethod(final int access, final String name,
- final String desc, final String signature, final String[] exceptions) {
- return new MethodWriter(this, access, name, desc, signature,
- exceptions, computeMaxs, computeFrames);
+ ++numberOfNestMemberClasses;
+ nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index);
+ }
+
+ @Override
+ public final void visitPermittedSubclass(final String permittedSubclass) {
+ if (permittedSubclasses == null) {
+ permittedSubclasses = new ByteVector();
}
-
- @Override
- public final void visitEnd() {
+ ++numberOfPermittedSubclasses;
+ permittedSubclasses.putShort(symbolTable.addConstantClass(permittedSubclass).index);
+ }
+
+ @Override
+ public final void visitInnerClass(
+ final String name, final String outerName, final String innerName, final int access) {
+ if (innerClasses == null) {
+ innerClasses = new ByteVector();
}
-
- // ------------------------------------------------------------------------
- // Other public methods
- // ------------------------------------------------------------------------
-
- /**
- * Returns the bytecode of the class that was build with this class writer.
- *
- * @return the bytecode of the class that was build with this class writer.
- */
- public byte[] toByteArray() {
- if (index > 0xFFFF) {
- throw new RuntimeException("Class file too large!");
- }
- // computes the real size of the bytecode of this class
- int size = 24 + 2 * interfaceCount;
- int nbFields = 0;
- FieldWriter fb = firstField;
- while (fb != null) {
- ++nbFields;
- size += fb.getSize();
- fb = (FieldWriter) fb.fv;
- }
- int nbMethods = 0;
- MethodWriter mb = firstMethod;
- while (mb != null) {
- ++nbMethods;
- size += mb.getSize();
- mb = (MethodWriter) mb.mv;
- }
- int attributeCount = 0;
- if (bootstrapMethods != null) {
- // we put it as first attribute in order to improve a bit
- // ClassReader.copyBootstrapMethods
- ++attributeCount;
- size += 8 + bootstrapMethods.length;
- newUTF8("BootstrapMethods");
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- ++attributeCount;
- size += 8;
- newUTF8("Signature");
- }
- if (sourceFile != 0) {
- ++attributeCount;
- size += 8;
- newUTF8("SourceFile");
- }
- if (sourceDebug != null) {
- ++attributeCount;
- size += sourceDebug.length + 4;
- newUTF8("SourceDebugExtension");
- }
- if (enclosingMethodOwner != 0) {
- ++attributeCount;
- size += 10;
- newUTF8("EnclosingMethod");
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- ++attributeCount;
- size += 6;
- newUTF8("Deprecated");
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((version & 0xFFFF) < Opcodes.V1_5
- || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- ++attributeCount;
- size += 6;
- newUTF8("Synthetic");
- }
- }
- if (innerClasses != null) {
- ++attributeCount;
- size += 8 + innerClasses.length;
- newUTF8("InnerClasses");
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- ++attributeCount;
- size += 8 + anns.getSize();
- newUTF8("RuntimeVisibleAnnotations");
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- ++attributeCount;
- size += 8 + ianns.getSize();
- newUTF8("RuntimeInvisibleAnnotations");
- }
- if (attrs != null) {
- attributeCount += attrs.getCount();
- size += attrs.getSize(this, null, 0, -1, -1);
- }
- size += pool.length;
- // allocates a byte vector of this size, in order to avoid unnecessary
- // arraycopy operations in the ByteVector.enlarge() method
- ByteVector out = new ByteVector(size);
- out.putInt(0xCAFEBABE).putInt(version);
- out.putShort(index).putByteArray(pool.data, 0, pool.length);
- int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE
- | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC);
- out.putShort(access & ~mask).putShort(name).putShort(superName);
- out.putShort(interfaceCount);
- for (int i = 0; i < interfaceCount; ++i) {
- out.putShort(interfaces[i]);
- }
- out.putShort(nbFields);
- fb = firstField;
- while (fb != null) {
- fb.put(out);
- fb = (FieldWriter) fb.fv;
- }
- out.putShort(nbMethods);
- mb = firstMethod;
- while (mb != null) {
- mb.put(out);
- mb = (MethodWriter) mb.mv;
- }
- out.putShort(attributeCount);
- if (bootstrapMethods != null) {
- out.putShort(newUTF8("BootstrapMethods"));
- out.putInt(bootstrapMethods.length + 2).putShort(
- bootstrapMethodsCount);
- out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- out.putShort(newUTF8("Signature")).putInt(2).putShort(signature);
- }
- if (sourceFile != 0) {
- out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile);
- }
- if (sourceDebug != null) {
- int len = sourceDebug.length - 2;
- out.putShort(newUTF8("SourceDebugExtension")).putInt(len);
- out.putByteArray(sourceDebug.data, 2, len);
- }
- if (enclosingMethodOwner != 0) {
- out.putShort(newUTF8("EnclosingMethod")).putInt(4);
- out.putShort(enclosingMethodOwner).putShort(enclosingMethod);
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- out.putShort(newUTF8("Deprecated")).putInt(0);
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((version & 0xFFFF) < Opcodes.V1_5
- || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- out.putShort(newUTF8("Synthetic")).putInt(0);
- }
- }
- if (innerClasses != null) {
- out.putShort(newUTF8("InnerClasses"));
- out.putInt(innerClasses.length + 2).putShort(innerClassesCount);
- out.putByteArray(innerClasses.data, 0, innerClasses.length);
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- out.putShort(newUTF8("RuntimeVisibleAnnotations"));
- anns.put(out);
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- out.putShort(newUTF8("RuntimeInvisibleAnnotations"));
- ianns.put(out);
- }
- if (attrs != null) {
- attrs.put(this, null, 0, -1, -1, out);
- }
- if (invalidFrames) {
- anns = null;
- ianns = null;
- attrs = null;
- innerClassesCount = 0;
- innerClasses = null;
- bootstrapMethodsCount = 0;
- bootstrapMethods = null;
- firstField = null;
- lastField = null;
- firstMethod = null;
- lastMethod = null;
- computeMaxs = false;
- computeFrames = true;
- invalidFrames = false;
- new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES);
- return toByteArray();
- }
- return out.data;
+ // Section 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the constant_pool table
+ // which represents a class or interface C that is not a package member must have exactly one
+ // corresponding entry in the classes array". To avoid duplicates we keep track in the info
+ // field of the Symbol of each CONSTANT_Class_info entry C whether an inner class entry has
+ // already been added for C. If so, we store the index of this inner class entry (plus one) in
+ // the info field. This trick allows duplicate detection in O(1) time.
+ Symbol nameSymbol = symbolTable.addConstantClass(name);
+ if (nameSymbol.info == 0) {
+ ++numberOfInnerClasses;
+ innerClasses.putShort(nameSymbol.index);
+ innerClasses.putShort(outerName == null ? 0 : symbolTable.addConstantClass(outerName).index);
+ innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName));
+ innerClasses.putShort(access);
+ nameSymbol.info = numberOfInnerClasses;
}
-
- // ------------------------------------------------------------------------
- // Utility methods: constant pool management
- // ------------------------------------------------------------------------
-
- /**
- * Adds a number or string constant to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- *
- * @param cst
- * the value of the constant to be added to the constant pool.
- * This parameter must be an {@link Integer}, a {@link Float}, a
- * {@link Long}, a {@link Double}, a {@link String} or a
- * {@link Type}.
- * @return a new or already existing constant item with the given value.
- */
- Item newConstItem(final Object cst) {
- if (cst instanceof Integer) {
- int val = ((Integer) cst).intValue();
- return newInteger(val);
- } else if (cst instanceof Byte) {
- int val = ((Byte) cst).intValue();
- return newInteger(val);
- } else if (cst instanceof Character) {
- int val = ((Character) cst).charValue();
- return newInteger(val);
- } else if (cst instanceof Short) {
- int val = ((Short) cst).intValue();
- return newInteger(val);
- } else if (cst instanceof Boolean) {
- int val = ((Boolean) cst).booleanValue() ? 1 : 0;
- return newInteger(val);
- } else if (cst instanceof Float) {
- float val = ((Float) cst).floatValue();
- return newFloat(val);
- } else if (cst instanceof Long) {
- long val = ((Long) cst).longValue();
- return newLong(val);
- } else if (cst instanceof Double) {
- double val = ((Double) cst).doubleValue();
- return newDouble(val);
- } else if (cst instanceof String) {
- return newString((String) cst);
- } else if (cst instanceof Type) {
- Type t = (Type) cst;
- int s = t.getSort();
- if (s == Type.OBJECT) {
- return newClassItem(t.getInternalName());
- } else if (s == Type.METHOD) {
- return newMethodTypeItem(t.getDescriptor());
- } else { // s == primitive type or array
- return newClassItem(t.getDescriptor());
- }
- } else if (cst instanceof Handle) {
- Handle h = (Handle) cst;
- return newHandleItem(h.tag, h.owner, h.name, h.desc);
- } else {
- throw new IllegalArgumentException("value " + cst);
- }
+ // Else, compare the inner classes entry nameSymbol.info - 1 with the arguments of this method
+ // and throw an exception if there is a difference?
+ }
+
+ @Override
+ public final RecordComponentVisitor visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ RecordComponentWriter recordComponentWriter =
+ new RecordComponentWriter(symbolTable, name, descriptor, signature);
+ if (firstRecordComponent == null) {
+ firstRecordComponent = recordComponentWriter;
+ } else {
+ lastRecordComponent.delegate = recordComponentWriter;
}
-
- /**
- * Adds a number or string constant to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param cst
- * the value of the constant to be added to the constant pool.
- * This parameter must be an {@link Integer}, a {@link Float}, a
- * {@link Long}, a {@link Double} or a {@link String}.
- * @return the index of a new or already existing constant item with the
- * given value.
- */
- public int newConst(final Object cst) {
- return newConstItem(cst).index;
+ return lastRecordComponent = recordComponentWriter;
+ }
+
+ @Override
+ public final FieldVisitor visitField(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Object value) {
+ FieldWriter fieldWriter =
+ new FieldWriter(symbolTable, access, name, descriptor, signature, value);
+ if (firstField == null) {
+ firstField = fieldWriter;
+ } else {
+ lastField.fv = fieldWriter;
}
-
- /**
- * Adds an UTF8 string to the constant pool of the class being build. Does
- * nothing if the constant pool already contains a similar item. This
- * method is intended for {@link Attribute} sub classes, and is normally not
- * needed by class generators or adapters.
- *
- * @param value
- * the String value.
- * @return the index of a new or already existing UTF8 item.
- */
- public int newUTF8(final String value) {
- key.set(UTF8, value, null, null);
- Item result = get(key);
- if (result == null) {
- pool.putByte(UTF8).putUTF8(value);
- result = new Item(index++, key);
- put(result);
- }
- return result.index;
+ return lastField = fieldWriter;
+ }
+
+ @Override
+ public final MethodVisitor visitMethod(
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final String[] exceptions) {
+ MethodWriter methodWriter =
+ new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute);
+ if (firstMethod == null) {
+ firstMethod = methodWriter;
+ } else {
+ lastMethod.mv = methodWriter;
}
-
- /**
- * Adds a class reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param value
- * the internal name of the class.
- * @return a new or already existing class reference item.
- */
- Item newClassItem(final String value) {
- key2.set(CLASS, value, null, null);
- Item result = get(key2);
- if (result == null) {
- pool.put12(CLASS, newUTF8(value));
- result = new Item(index++, key2);
- put(result);
- }
- return result;
+ return lastMethod = methodWriter;
+ }
+
+ @Override
+ public final void visitEnd() {
+ // Nothing to do.
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Other public methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the content of the class file that was built by this ClassWriter.
+ *
+ * @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter.
+ * @throws ClassTooLargeException if the constant pool of the class is too large.
+ * @throws MethodTooLargeException if the Code attribute of a method is too large.
+ */
+ public byte[] toByteArray() {
+ // First step: compute the size in bytes of the ClassFile structure.
+ // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version,
+ // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count,
+ // methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too.
+ int size = 24 + 2 * interfaceCount;
+ int fieldsCount = 0;
+ FieldWriter fieldWriter = firstField;
+ while (fieldWriter != null) {
+ ++fieldsCount;
+ size += fieldWriter.computeFieldInfoSize();
+ fieldWriter = (FieldWriter) fieldWriter.fv;
}
-
- /**
- * Adds a class reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param value
- * the internal name of the class.
- * @return the index of a new or already existing class reference item.
- */
- public int newClass(final String value) {
- return newClassItem(value).index;
+ int methodsCount = 0;
+ MethodWriter methodWriter = firstMethod;
+ while (methodWriter != null) {
+ ++methodsCount;
+ size += methodWriter.computeMethodInfoSize();
+ methodWriter = (MethodWriter) methodWriter.mv;
}
- /**
- * Adds a method type reference to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param methodDesc
- * method descriptor of the method type.
- * @return a new or already existing method type reference item.
- */
- Item newMethodTypeItem(final String methodDesc) {
- key2.set(MTYPE, methodDesc, null, null);
- Item result = get(key2);
- if (result == null) {
- pool.put12(MTYPE, newUTF8(methodDesc));
- result = new Item(index++, key2);
- put(result);
- }
- return result;
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ int attributesCount = 0;
+ if (innerClasses != null) {
+ ++attributesCount;
+ size += 8 + innerClasses.length;
+ symbolTable.addConstantUtf8(Constants.INNER_CLASSES);
}
-
- /**
- * Adds a method type reference to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param methodDesc
- * method descriptor of the method type.
- * @return the index of a new or already existing method type reference
- * item.
- */
- public int newMethodType(final String methodDesc) {
- return newMethodTypeItem(methodDesc).index;
+ if (enclosingClassIndex != 0) {
+ ++attributesCount;
+ size += 10;
+ symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD);
}
-
- /**
- * Adds a handle to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item. This method is
- * intended for {@link Attribute} sub classes, and is normally not needed by
- * class generators or adapters.
- *
- * @param tag
- * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
- * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
- * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
- * {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL},
- * {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- * @param owner
- * the internal name of the field or method owner class.
- * @param name
- * the name of the field or method.
- * @param desc
- * the descriptor of the field or method.
- * @return a new or an already existing method type reference item.
- */
- Item newHandleItem(final int tag, final String owner, final String name,
- final String desc) {
- key4.set(HANDLE_BASE + tag, owner, name, desc);
- Item result = get(key4);
- if (result == null) {
- if (tag <= Opcodes.H_PUTSTATIC) {
- put112(HANDLE, tag, newField(owner, name, desc));
- } else {
- put112(HANDLE,
- tag,
- newMethod(owner, name, desc,
- tag == Opcodes.H_INVOKEINTERFACE));
- }
- result = new Item(index++, key4);
- put(result);
- }
- return result;
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) {
+ ++attributesCount;
+ size += 6;
+ symbolTable.addConstantUtf8(Constants.SYNTHETIC);
}
-
- /**
- * Adds a handle to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item. This method is
- * intended for {@link Attribute} sub classes, and is normally not needed by
- * class generators or adapters.
- *
- * @param tag
- * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
- * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
- * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
- * {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL},
- * {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- * @param owner
- * the internal name of the field or method owner class.
- * @param name
- * the name of the field or method.
- * @param desc
- * the descriptor of the field or method.
- * @return the index of a new or already existing method type reference
- * item.
- */
- public int newHandle(final int tag, final String owner, final String name,
- final String desc) {
- return newHandleItem(tag, owner, name, desc).index;
+ if (signatureIndex != 0) {
+ ++attributesCount;
+ size += 8;
+ symbolTable.addConstantUtf8(Constants.SIGNATURE);
}
-
- /**
- * Adds an invokedynamic reference to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param name
- * name of the invoked method.
- * @param desc
- * descriptor of the invoke method.
- * @param bsm
- * the bootstrap method.
- * @param bsmArgs
- * the bootstrap method constant arguments.
- *
- * @return a new or an already existing invokedynamic type reference item.
- */
- Item newInvokeDynamicItem(final String name, final String desc,
- final Handle bsm, final Object... bsmArgs) {
- // cache for performance
- ByteVector bootstrapMethods = this.bootstrapMethods;
- if (bootstrapMethods == null) {
- bootstrapMethods = this.bootstrapMethods = new ByteVector();
- }
-
- int position = bootstrapMethods.length; // record current position
-
- int hashCode = bsm.hashCode();
- bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name,
- bsm.desc));
-
- int argsLength = bsmArgs.length;
- bootstrapMethods.putShort(argsLength);
-
- for (int i = 0; i < argsLength; i++) {
- Object bsmArg = bsmArgs[i];
- hashCode ^= bsmArg.hashCode();
- bootstrapMethods.putShort(newConst(bsmArg));
- }
-
- byte[] data = bootstrapMethods.data;
- int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments)
- hashCode &= 0x7FFFFFFF;
- Item result = items[hashCode % items.length];
- loop: while (result != null) {
- if (result.type != BSM || result.hashCode != hashCode) {
- result = result.next;
- continue;
- }
-
- // because the data encode the size of the argument
- // we don't need to test if these size are equals
- int resultPosition = result.intVal;
- for (int p = 0; p < length; p++) {
- if (data[position + p] != data[resultPosition + p]) {
- result = result.next;
- continue loop;
- }
- }
- break;
- }
-
- int bootstrapMethodIndex;
- if (result != null) {
- bootstrapMethodIndex = result.index;
- bootstrapMethods.length = position; // revert to old position
- } else {
- bootstrapMethodIndex = bootstrapMethodsCount++;
- result = new Item(bootstrapMethodIndex);
- result.set(position, hashCode);
- put(result);
- }
-
- // now, create the InvokeDynamic constant
- key3.set(name, desc, bootstrapMethodIndex);
- result = get(key3);
- if (result == null) {
- put122(INDY, bootstrapMethodIndex, newNameType(name, desc));
- result = new Item(index++, key3);
- put(result);
- }
- return result;
+ if (sourceFileIndex != 0) {
+ ++attributesCount;
+ size += 8;
+ symbolTable.addConstantUtf8(Constants.SOURCE_FILE);
}
-
- /**
- * Adds an invokedynamic reference to the constant pool of the class being
- * build. Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param name
- * name of the invoked method.
- * @param desc
- * descriptor of the invoke method.
- * @param bsm
- * the bootstrap method.
- * @param bsmArgs
- * the bootstrap method constant arguments.
- *
- * @return the index of a new or already existing invokedynamic reference
- * item.
- */
- public int newInvokeDynamic(final String name, final String desc,
- final Handle bsm, final Object... bsmArgs) {
- return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index;
+ if (debugExtension != null) {
+ ++attributesCount;
+ size += 6 + debugExtension.length;
+ symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION);
}
-
- /**
- * Adds a field reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- *
- * @param owner
- * the internal name of the field's owner class.
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor.
- * @return a new or already existing field reference item.
- */
- Item newFieldItem(final String owner, final String name, final String desc) {
- key3.set(FIELD, owner, name, desc);
- Item result = get(key3);
- if (result == null) {
- put122(FIELD, newClass(owner), newNameType(name, desc));
- result = new Item(index++, key3);
- put(result);
- }
- return result;
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributesCount;
+ size += 6;
+ symbolTable.addConstantUtf8(Constants.DEPRECATED);
}
-
- /**
- * Adds a field reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param owner
- * the internal name of the field's owner class.
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor.
- * @return the index of a new or already existing field reference item.
- */
- public int newField(final String owner, final String name, final String desc) {
- return newFieldItem(owner, name, desc).index;
+ if (lastRuntimeVisibleAnnotation != null) {
+ ++attributesCount;
+ size +=
+ lastRuntimeVisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_ANNOTATIONS);
}
-
- /**
- * Adds a method reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- *
- * @param owner
- * the internal name of the method's owner class.
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor.
- * @param itf
- * true if owner is an interface.
- * @return a new or already existing method reference item.
- */
- Item newMethodItem(final String owner, final String name,
- final String desc, final boolean itf) {
- int type = itf ? IMETH : METH;
- key3.set(type, owner, name, desc);
- Item result = get(key3);
- if (result == null) {
- put122(type, newClass(owner), newNameType(name, desc));
- result = new Item(index++, key3);
- put(result);
- }
- return result;
+ if (lastRuntimeInvisibleAnnotation != null) {
+ ++attributesCount;
+ size +=
+ lastRuntimeInvisibleAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_ANNOTATIONS);
}
-
- /**
- * Adds a method reference to the constant pool of the class being build.
- * Does nothing if the constant pool already contains a similar item.
- * This method is intended for {@link Attribute} sub classes, and is
- * normally not needed by class generators or adapters.
- *
- * @param owner
- * the internal name of the method's owner class.
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor.
- * @param itf
- * true if owner is an interface.
- * @return the index of a new or already existing method reference item.
- */
- public int newMethod(final String owner, final String name,
- final String desc, final boolean itf) {
- return newMethodItem(owner, name, desc, itf).index;
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ ++attributesCount;
+ size +=
+ lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
}
-
- /**
- * Adds an integer to the constant pool of the class being build. Does
- * nothing if the constant pool already contains a similar item.
- *
- * @param value
- * the int value.
- * @return a new or already existing int item.
- */
- Item newInteger(final int value) {
- key.set(value);
- Item result = get(key);
- if (result == null) {
- pool.putByte(INT).putInt(value);
- result = new Item(index++, key);
- put(result);
- }
- return result;
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ ++attributesCount;
+ size +=
+ lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
}
-
- /**
- * Adds a float to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item.
- *
- * @param value
- * the float value.
- * @return a new or already existing float item.
- */
- Item newFloat(final float value) {
- key.set(value);
- Item result = get(key);
- if (result == null) {
- pool.putByte(FLOAT).putInt(key.intVal);
- result = new Item(index++, key);
- put(result);
- }
- return result;
+ if (symbolTable.computeBootstrapMethodsSize() > 0) {
+ ++attributesCount;
+ size += symbolTable.computeBootstrapMethodsSize();
}
-
- /**
- * Adds a long to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item.
- *
- * @param value
- * the long value.
- * @return a new or already existing long item.
- */
- Item newLong(final long value) {
- key.set(value);
- Item result = get(key);
- if (result == null) {
- pool.putByte(LONG).putLong(value);
- result = new Item(index, key);
- index += 2;
- put(result);
- }
- return result;
+ if (moduleWriter != null) {
+ attributesCount += moduleWriter.getAttributeCount();
+ size += moduleWriter.computeAttributesSize();
}
-
- /**
- * Adds a double to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item.
- *
- * @param value
- * the double value.
- * @return a new or already existing double item.
- */
- Item newDouble(final double value) {
- key.set(value);
- Item result = get(key);
- if (result == null) {
- pool.putByte(DOUBLE).putLong(key.longVal);
- result = new Item(index, key);
- index += 2;
- put(result);
- }
- return result;
+ if (nestHostClassIndex != 0) {
+ ++attributesCount;
+ size += 8;
+ symbolTable.addConstantUtf8(Constants.NEST_HOST);
}
-
- /**
- * Adds a string to the constant pool of the class being build. Does nothing
- * if the constant pool already contains a similar item.
- *
- * @param value
- * the String value.
- * @return a new or already existing string item.
- */
- private Item newString(final String value) {
- key2.set(STR, value, null, null);
- Item result = get(key2);
- if (result == null) {
- pool.put12(STR, newUTF8(value));
- result = new Item(index++, key2);
- put(result);
- }
- return result;
+ if (nestMemberClasses != null) {
+ ++attributesCount;
+ size += 8 + nestMemberClasses.length;
+ symbolTable.addConstantUtf8(Constants.NEST_MEMBERS);
}
-
- /**
- * Adds a name and type to the constant pool of the class being build. Does
- * nothing if the constant pool already contains a similar item. This
- * method is intended for {@link Attribute} sub classes, and is normally not
- * needed by class generators or adapters.
- *
- * @param name
- * a name.
- * @param desc
- * a type descriptor.
- * @return the index of a new or already existing name and type item.
- */
- public int newNameType(final String name, final String desc) {
- return newNameTypeItem(name, desc).index;
+ if (permittedSubclasses != null) {
+ ++attributesCount;
+ size += 8 + permittedSubclasses.length;
+ symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES);
}
-
- /**
- * Adds a name and type to the constant pool of the class being build. Does
- * nothing if the constant pool already contains a similar item.
- *
- * @param name
- * a name.
- * @param desc
- * a type descriptor.
- * @return a new or already existing name and type item.
- */
- Item newNameTypeItem(final String name, final String desc) {
- key2.set(NAME_TYPE, name, desc, null);
- Item result = get(key2);
- if (result == null) {
- put122(NAME_TYPE, newUTF8(name), newUTF8(desc));
- result = new Item(index++, key2);
- put(result);
- }
- return result;
+ int recordComponentCount = 0;
+ int recordSize = 0;
+ if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) {
+ RecordComponentWriter recordComponentWriter = firstRecordComponent;
+ while (recordComponentWriter != null) {
+ ++recordComponentCount;
+ recordSize += recordComponentWriter.computeRecordComponentInfoSize();
+ recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
+ }
+ ++attributesCount;
+ size += 8 + recordSize;
+ symbolTable.addConstantUtf8(Constants.RECORD);
}
-
- /**
- * Adds the given internal name to {@link #typeTable} and returns its index.
- * Does nothing if the type table already contains this internal name.
- *
- * @param type
- * the internal name to be added to the type table.
- * @return the index of this internal name in the type table.
- */
- int addType(final String type) {
- key.set(TYPE_NORMAL, type, null, null);
- Item result = get(key);
- if (result == null) {
- result = addType(key);
- }
- return result.index;
+ if (firstAttribute != null) {
+ attributesCount += firstAttribute.getAttributeCount();
+ size += firstAttribute.computeAttributesSize(symbolTable);
}
-
- /**
- * Adds the given "uninitialized" type to {@link #typeTable} and returns its
- * index. This method is used for UNINITIALIZED types, made of an internal
- * name and a bytecode offset.
- *
- * @param type
- * the internal name to be added to the type table.
- * @param offset
- * the bytecode offset of the NEW instruction that created this
- * UNINITIALIZED type value.
- * @return the index of this internal name in the type table.
- */
- int addUninitializedType(final String type, final int offset) {
- key.type = TYPE_UNINIT;
- key.intVal = offset;
- key.strVal1 = type;
- key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset);
- Item result = get(key);
- if (result == null) {
- result = addType(key);
- }
- return result.index;
+ // IMPORTANT: this must be the last part of the ClassFile size computation, because the previous
+ // statements can add attribute names to the constant pool, thereby changing its size!
+ size += symbolTable.getConstantPoolLength();
+ int constantPoolCount = symbolTable.getConstantPoolCount();
+ if (constantPoolCount > 0xFFFF) {
+ throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount);
}
- /**
- * Adds the given Item to {@link #typeTable}.
- *
- * @param item
- * the value to be added to the type table.
- * @return the added Item, which a new Item instance with the same value as
- * the given Item.
- */
- private Item addType(final Item item) {
- ++typeCount;
- Item result = new Item(typeCount, key);
- put(result);
- if (typeTable == null) {
- typeTable = new Item[16];
- }
- if (typeCount == typeTable.length) {
- Item[] newTable = new Item[2 * typeTable.length];
- System.arraycopy(typeTable, 0, newTable, 0, typeTable.length);
- typeTable = newTable;
- }
- typeTable[typeCount] = result;
- return result;
+ // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in
+ // dynamic resizes) and fill it with the ClassFile content.
+ ByteVector result = new ByteVector(size);
+ result.putInt(0xCAFEBABE).putInt(version);
+ symbolTable.putConstantPool(result);
+ int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0;
+ result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass);
+ result.putShort(interfaceCount);
+ for (int i = 0; i < interfaceCount; ++i) {
+ result.putShort(interfaces[i]);
}
-
- /**
- * Returns the index of the common super type of the two given types. This
- * method calls {@link #getCommonSuperClass} and caches the result in the
- * {@link #items} hash table to speedup future calls with the same
- * parameters.
- *
- * @param type1
- * index of an internal name in {@link #typeTable}.
- * @param type2
- * index of an internal name in {@link #typeTable}.
- * @return the index of the common super type of the two given types.
- */
- int getMergedType(final int type1, final int type2) {
- key2.type = TYPE_MERGED;
- key2.longVal = type1 | (((long) type2) << 32);
- key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2);
- Item result = get(key2);
- if (result == null) {
- String t = typeTable[type1].strVal1;
- String u = typeTable[type2].strVal1;
- key2.intVal = addType(getCommonSuperClass(t, u));
- result = new Item((short) 0, key2);
- put(result);
- }
- return result.intVal;
+ result.putShort(fieldsCount);
+ fieldWriter = firstField;
+ while (fieldWriter != null) {
+ fieldWriter.putFieldInfo(result);
+ fieldWriter = (FieldWriter) fieldWriter.fv;
}
-
- /**
- * Returns the common super type of the two given types. The default
- * implementation of this method loads the two given classes and uses
- * the java.lang.Class methods to find the common super class. It can be
- * overridden to compute this common super type in other ways, in particular
- * without actually loading any class, or to take into account the class
- * that is currently being generated by this ClassWriter, which can of
- * course not be loaded since it is under construction.
- *
- * @param type1
- * the internal name of a class.
- * @param type2
- * the internal name of another class.
- * @return the internal name of the common super class of the two given
- * classes.
- */
- protected String getCommonSuperClass(final String type1, final String type2) {
- Class> c, d;
- ClassLoader classLoader = getClass().getClassLoader();
- try {
- c = Class.forName(type1.replace('/', '.'), false, classLoader);
- d = Class.forName(type2.replace('/', '.'), false, classLoader);
- } catch (Exception e) {
- throw new RuntimeException(e.toString());
- }
- if (c.isAssignableFrom(d)) {
- return type1;
- }
- if (d.isAssignableFrom(c)) {
- return type2;
- }
- if (c.isInterface() || d.isInterface()) {
- return "java/lang/Object";
- } else {
- do {
- c = c.getSuperclass();
- } while (!c.isAssignableFrom(d));
- return c.getName().replace('.', '/');
- }
+ result.putShort(methodsCount);
+ boolean hasFrames = false;
+ boolean hasAsmInstructions = false;
+ methodWriter = firstMethod;
+ while (methodWriter != null) {
+ hasFrames |= methodWriter.hasFrames();
+ hasAsmInstructions |= methodWriter.hasAsmInstructions();
+ methodWriter.putMethodInfo(result);
+ methodWriter = (MethodWriter) methodWriter.mv;
}
-
- /**
- * Returns the constant pool's hash table item which is equal to the given
- * item.
- *
- * @param key
- * a constant pool item.
- * @return the constant pool's hash table item which is equal to the given
- * item, or null if there is no such item.
- */
- private Item get(final Item key) {
- Item i = items[key.hashCode % items.length];
- while (i != null && (i.type != key.type || !key.isEqualTo(i))) {
- i = i.next;
- }
- return i;
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ result.putShort(attributesCount);
+ if (innerClasses != null) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.INNER_CLASSES))
+ .putInt(innerClasses.length + 2)
+ .putShort(numberOfInnerClasses)
+ .putByteArray(innerClasses.data, 0, innerClasses.length);
}
-
- /**
- * Puts the given item in the constant pool's hash table. The hash table
- * must not already contains this item.
- *
- * @param i
- * the item to be added to the constant pool's hash table.
- */
- private void put(final Item i) {
- if (index + typeCount > threshold) {
- int ll = items.length;
- int nl = ll * 2 + 1;
- Item[] newItems = new Item[nl];
- for (int l = ll - 1; l >= 0; --l) {
- Item j = items[l];
- while (j != null) {
- int index = j.hashCode % newItems.length;
- Item k = j.next;
- j.next = newItems[index];
- newItems[index] = j;
- j = k;
- }
- }
- items = newItems;
- threshold = (int) (nl * 0.75);
- }
- int index = i.hashCode % items.length;
- i.next = items[index];
- items[index] = i;
+ if (enclosingClassIndex != 0) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD))
+ .putInt(4)
+ .putShort(enclosingClassIndex)
+ .putShort(enclosingMethodIndex);
}
-
- /**
- * Puts one byte and two shorts into the constant pool.
- *
- * @param b
- * a byte.
- * @param s1
- * a short.
- * @param s2
- * another short.
- */
- private void put122(final int b, final int s1, final int s2) {
- pool.put12(b, s1).putShort(s2);
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) {
+ result.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0);
+ }
+ if (signatureIndex != 0) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE))
+ .putInt(2)
+ .putShort(signatureIndex);
+ }
+ if (sourceFileIndex != 0) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE))
+ .putInt(2)
+ .putShort(sourceFileIndex);
+ }
+ if (debugExtension != null) {
+ int length = debugExtension.length;
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION))
+ .putInt(length)
+ .putByteArray(debugExtension.data, 0, length);
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0);
+ }
+ AnnotationWriter.putAnnotations(
+ symbolTable,
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation,
+ result);
+ symbolTable.putBootstrapMethods(result);
+ if (moduleWriter != null) {
+ moduleWriter.putAttributes(result);
+ }
+ if (nestHostClassIndex != 0) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST))
+ .putInt(2)
+ .putShort(nestHostClassIndex);
+ }
+ if (nestMemberClasses != null) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS))
+ .putInt(nestMemberClasses.length + 2)
+ .putShort(numberOfNestMemberClasses)
+ .putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length);
+ }
+ if (permittedSubclasses != null) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES))
+ .putInt(permittedSubclasses.length + 2)
+ .putShort(numberOfPermittedSubclasses)
+ .putByteArray(permittedSubclasses.data, 0, permittedSubclasses.length);
+ }
+ if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) {
+ result
+ .putShort(symbolTable.addConstantUtf8(Constants.RECORD))
+ .putInt(recordSize + 2)
+ .putShort(recordComponentCount);
+ RecordComponentWriter recordComponentWriter = firstRecordComponent;
+ while (recordComponentWriter != null) {
+ recordComponentWriter.putRecordComponentInfo(result);
+ recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
+ }
+ }
+ if (firstAttribute != null) {
+ firstAttribute.putAttributes(symbolTable, result);
}
- /**
- * Puts two bytes and one short into the constant pool.
- *
- * @param b1
- * a byte.
- * @param b2
- * another byte.
- * @param s
- * a short.
- */
- private void put112(final int b1, final int b2, final int s) {
- pool.put11(b1, b2).putShort(s);
+ // Third step: replace the ASM specific instructions, if any.
+ if (hasAsmInstructions) {
+ return replaceAsmInstructions(result.data, hasFrames);
+ } else {
+ return result.data;
+ }
+ }
+
+ /**
+ * Returns the equivalent of the given class file, with the ASM specific instructions replaced
+ * with standard ones. This is done with a ClassReader -> ClassWriter round trip.
+ *
+ * @param classFile a class file containing ASM specific instructions, generated by this
+ * ClassWriter.
+ * @param hasFrames whether there is at least one stack map frames in 'classFile'.
+ * @return an equivalent of 'classFile', with the ASM specific instructions replaced with standard
+ * ones.
+ */
+ private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) {
+ final Attribute[] attributes = getAttributePrototypes();
+ firstField = null;
+ lastField = null;
+ firstMethod = null;
+ lastMethod = null;
+ lastRuntimeVisibleAnnotation = null;
+ lastRuntimeInvisibleAnnotation = null;
+ lastRuntimeVisibleTypeAnnotation = null;
+ lastRuntimeInvisibleTypeAnnotation = null;
+ moduleWriter = null;
+ nestHostClassIndex = 0;
+ numberOfNestMemberClasses = 0;
+ nestMemberClasses = null;
+ numberOfPermittedSubclasses = 0;
+ permittedSubclasses = null;
+ firstRecordComponent = null;
+ lastRecordComponent = null;
+ firstAttribute = null;
+ compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING;
+ new ClassReader(classFile, 0, /* checkClassVersion = */ false)
+ .accept(
+ this,
+ attributes,
+ (hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS);
+ return toByteArray();
+ }
+
+ /**
+ * Returns the prototypes of the attributes used by this class, its fields and its methods.
+ *
+ * @return the prototypes of the attributes used by this class, its fields and its methods.
+ */
+ private Attribute[] getAttributePrototypes() {
+ Attribute.Set attributePrototypes = new Attribute.Set();
+ attributePrototypes.addAttributes(firstAttribute);
+ FieldWriter fieldWriter = firstField;
+ while (fieldWriter != null) {
+ fieldWriter.collectAttributePrototypes(attributePrototypes);
+ fieldWriter = (FieldWriter) fieldWriter.fv;
+ }
+ MethodWriter methodWriter = firstMethod;
+ while (methodWriter != null) {
+ methodWriter.collectAttributePrototypes(attributePrototypes);
+ methodWriter = (MethodWriter) methodWriter.mv;
+ }
+ RecordComponentWriter recordComponentWriter = firstRecordComponent;
+ while (recordComponentWriter != null) {
+ recordComponentWriter.collectAttributePrototypes(attributePrototypes);
+ recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
+ }
+ return attributePrototypes.toArray();
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods: constant pool management for Attribute sub classes
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a number or string constant to the constant pool of the class being build. Does nothing if
+ * the constant pool already contains a similar item. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param value the value of the constant to be added to the constant pool. This parameter must be
+ * an {@link Integer}, a {@link Float}, a {@link Long}, a {@link Double} or a {@link String}.
+ * @return the index of a new or already existing constant item with the given value.
+ */
+ public int newConst(final Object value) {
+ return symbolTable.addConstant(value).index;
+ }
+
+ /**
+ * Adds an UTF8 string to the constant pool of the class being build. Does nothing if the constant
+ * pool already contains a similar item. This method is intended for {@link Attribute} sub
+ * classes, and is normally not needed by class generators or adapters.
+ *
+ * @param value the String value.
+ * @return the index of a new or already existing UTF8 item.
+ */
+ // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility).
+ public int newUTF8(final String value) {
+ return symbolTable.addConstantUtf8(value);
+ }
+
+ /**
+ * Adds a class reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param value the internal name of the class (see {@link Type#getInternalName()}).
+ * @return the index of a new or already existing class reference item.
+ */
+ public int newClass(final String value) {
+ return symbolTable.addConstantClass(value).index;
+ }
+
+ /**
+ * Adds a method type reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param methodDescriptor method descriptor of the method type.
+ * @return the index of a new or already existing method type reference item.
+ */
+ public int newMethodType(final String methodDescriptor) {
+ return symbolTable.addConstantMethodType(methodDescriptor).index;
+ }
+
+ /**
+ * Adds a module reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param moduleName name of the module.
+ * @return the index of a new or already existing module reference item.
+ */
+ public int newModule(final String moduleName) {
+ return symbolTable.addConstantModule(moduleName).index;
+ }
+
+ /**
+ * Adds a package reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param packageName name of the package in its internal form.
+ * @return the index of a new or already existing module reference item.
+ */
+ public int newPackage(final String packageName) {
+ return symbolTable.addConstantPackage(packageName).index;
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool
+ * already contains a similar item. This method is intended for {@link Attribute} sub classes,
+ * and is normally not needed by class generators or adapters.
+ *
+ * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link
+ * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
+ * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of the field or method owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the name of the field or method.
+ * @param descriptor the descriptor of the field or method.
+ * @return the index of a new or already existing method type reference item.
+ * @deprecated this method is superseded by {@link #newHandle(int, String, String, String,
+ * boolean)}.
+ */
+ @Deprecated
+ public int newHandle(
+ final int tag, final String owner, final String name, final String descriptor) {
+ return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool
+ * already contains a similar item. This method is intended for {@link Attribute} sub classes,
+ * and is normally not needed by class generators or adapters.
+ *
+ * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link
+ * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
+ * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of the field or method owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the name of the field or method.
+ * @param descriptor the descriptor of the field or method.
+ * @param isInterface true if the owner is an interface.
+ * @return the index of a new or already existing method type reference item.
+ */
+ public int newHandle(
+ final int tag,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index;
+ }
+
+ /**
+ * Adds a dynamic constant reference to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param name name of the invoked method.
+ * @param descriptor field descriptor of the constant type.
+ * @param bootstrapMethodHandle the bootstrap method.
+ * @param bootstrapMethodArguments the bootstrap method constant arguments.
+ * @return the index of a new or already existing dynamic constant reference item.
+ */
+ public int newConstantDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ return symbolTable.addConstantDynamic(
+ name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments)
+ .index;
+ }
+
+ /**
+ * Adds an invokedynamic reference to the constant pool of the class being build. Does nothing if
+ * the constant pool already contains a similar item. This method is intended for {@link
+ * Attribute} sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param name name of the invoked method.
+ * @param descriptor descriptor of the invoke method.
+ * @param bootstrapMethodHandle the bootstrap method.
+ * @param bootstrapMethodArguments the bootstrap method constant arguments.
+ * @return the index of a new or already existing invokedynamic reference item.
+ */
+ public int newInvokeDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ return symbolTable.addConstantInvokeDynamic(
+ name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments)
+ .index;
+ }
+
+ /**
+ * Adds a field reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}).
+ * @param name the field's name.
+ * @param descriptor the field's descriptor.
+ * @return the index of a new or already existing field reference item.
+ */
+ public int newField(final String owner, final String name, final String descriptor) {
+ return symbolTable.addConstantFieldref(owner, name, descriptor).index;
+ }
+
+ /**
+ * Adds a method reference to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param owner the internal name of the method's owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor.
+ * @param isInterface {@literal true} if {@code owner} is an interface.
+ * @return the index of a new or already existing method reference item.
+ */
+ public int newMethod(
+ final String owner, final String name, final String descriptor, final boolean isInterface) {
+ return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index;
+ }
+
+ /**
+ * Adds a name and type to the constant pool of the class being build. Does nothing if the
+ * constant pool already contains a similar item. This method is intended for {@link Attribute}
+ * sub classes, and is normally not needed by class generators or adapters.
+ *
+ * @param name a name.
+ * @param descriptor a type descriptor.
+ * @return the index of a new or already existing name and type item.
+ */
+ public int newNameType(final String name, final String descriptor) {
+ return symbolTable.addConstantNameAndType(name, descriptor);
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Default method to compute common super classes when computing stack map frames
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the common super type of the two given types. The default implementation of this method
+ * loads the two given classes and uses the java.lang.Class methods to find the common
+ * super class. It can be overridden to compute this common super type in other ways, in
+ * particular without actually loading any class, or to take into account the class that is
+ * currently being generated by this ClassWriter, which can of course not be loaded since it is
+ * under construction.
+ *
+ * @param type1 the internal name of a class (see {@link Type#getInternalName()}).
+ * @param type2 the internal name of another class (see {@link Type#getInternalName()}).
+ * @return the internal name of the common super class of the two given classes (see {@link
+ * Type#getInternalName()}).
+ */
+ protected String getCommonSuperClass(final String type1, final String type2) {
+ ClassLoader classLoader = getClassLoader();
+ Class> class1;
+ try {
+ class1 = Class.forName(type1.replace('/', '.'), false, classLoader);
+ } catch (ClassNotFoundException e) {
+ throw new TypeNotPresentException(type1, e);
+ }
+ Class> class2;
+ try {
+ class2 = Class.forName(type2.replace('/', '.'), false, classLoader);
+ } catch (ClassNotFoundException e) {
+ throw new TypeNotPresentException(type2, e);
+ }
+ if (class1.isAssignableFrom(class2)) {
+ return type1;
+ }
+ if (class2.isAssignableFrom(class1)) {
+ return type2;
+ }
+ if (class1.isInterface() || class2.isInterface()) {
+ return "java/lang/Object";
+ } else {
+ do {
+ class1 = class1.getSuperclass();
+ } while (!class1.isAssignableFrom(class2));
+ return class1.getName().replace('.', '/');
}
+ }
+
+ /**
+ * Returns the {@link ClassLoader} to be used by the default implementation of {@link
+ * #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by
+ * default.
+ *
+ * @return ClassLoader
+ */
+ protected ClassLoader getClassLoader() {
+ return getClass().getClassLoader();
+ }
}
diff --git a/src/java/nginx/clojure/asm/ConstantDynamic.java b/src/java/nginx/clojure/asm/ConstantDynamic.java
new file mode 100644
index 00000000..5c673ca2
--- /dev/null
+++ b/src/java/nginx/clojure/asm/ConstantDynamic.java
@@ -0,0 +1,178 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+import java.util.Arrays;
+
+/**
+ * A constant whose value is computed at runtime, with a bootstrap method.
+ *
+ * @author Remi Forax
+ */
+public final class ConstantDynamic {
+
+ /** The constant name (can be arbitrary). */
+ private final String name;
+
+ /** The constant type (must be a field descriptor). */
+ private final String descriptor;
+
+ /** The bootstrap method to use to compute the constant value at runtime. */
+ private final Handle bootstrapMethod;
+
+ /**
+ * The arguments to pass to the bootstrap method, in order to compute the constant value at
+ * runtime.
+ */
+ private final Object[] bootstrapMethodArguments;
+
+ /**
+ * Constructs a new {@link ConstantDynamic}.
+ *
+ * @param name the constant name (can be arbitrary).
+ * @param descriptor the constant type (must be a field descriptor).
+ * @param bootstrapMethod the bootstrap method to use to compute the constant value at runtime.
+ * @param bootstrapMethodArguments the arguments to pass to the bootstrap method, in order to
+ * compute the constant value at runtime.
+ */
+ public ConstantDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethod,
+ final Object... bootstrapMethodArguments) {
+ this.name = name;
+ this.descriptor = descriptor;
+ this.bootstrapMethod = bootstrapMethod;
+ this.bootstrapMethodArguments = bootstrapMethodArguments;
+ }
+
+ /**
+ * Returns the name of this constant.
+ *
+ * @return the name of this constant.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the type of this constant.
+ *
+ * @return the type of this constant, as a field descriptor.
+ */
+ public String getDescriptor() {
+ return descriptor;
+ }
+
+ /**
+ * Returns the bootstrap method used to compute the value of this constant.
+ *
+ * @return the bootstrap method used to compute the value of this constant.
+ */
+ public Handle getBootstrapMethod() {
+ return bootstrapMethod;
+ }
+
+ /**
+ * Returns the number of arguments passed to the bootstrap method, in order to compute the value
+ * of this constant.
+ *
+ * @return the number of arguments passed to the bootstrap method, in order to compute the value
+ * of this constant.
+ */
+ public int getBootstrapMethodArgumentCount() {
+ return bootstrapMethodArguments.length;
+ }
+
+ /**
+ * Returns an argument passed to the bootstrap method, in order to compute the value of this
+ * constant.
+ *
+ * @param index an argument index, between 0 and {@link #getBootstrapMethodArgumentCount()}
+ * (exclusive).
+ * @return the argument passed to the bootstrap method, with the given index.
+ */
+ public Object getBootstrapMethodArgument(final int index) {
+ return bootstrapMethodArguments[index];
+ }
+
+ /**
+ * Returns the arguments to pass to the bootstrap method, in order to compute the value of this
+ * constant. WARNING: this array must not be modified, and must not be returned to the user.
+ *
+ * @return the arguments to pass to the bootstrap method, in order to compute the value of this
+ * constant.
+ */
+ Object[] getBootstrapMethodArgumentsUnsafe() {
+ return bootstrapMethodArguments;
+ }
+
+ /**
+ * Returns the size of this constant.
+ *
+ * @return the size of this constant, i.e., 2 for {@code long} and {@code double}, 1 otherwise.
+ */
+ public int getSize() {
+ char firstCharOfDescriptor = descriptor.charAt(0);
+ return (firstCharOfDescriptor == 'J' || firstCharOfDescriptor == 'D') ? 2 : 1;
+ }
+
+ @Override
+ public boolean equals(final Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (!(object instanceof ConstantDynamic)) {
+ return false;
+ }
+ ConstantDynamic constantDynamic = (ConstantDynamic) object;
+ return name.equals(constantDynamic.name)
+ && descriptor.equals(constantDynamic.descriptor)
+ && bootstrapMethod.equals(constantDynamic.bootstrapMethod)
+ && Arrays.equals(bootstrapMethodArguments, constantDynamic.bootstrapMethodArguments);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode()
+ ^ Integer.rotateLeft(descriptor.hashCode(), 8)
+ ^ Integer.rotateLeft(bootstrapMethod.hashCode(), 16)
+ ^ Integer.rotateLeft(Arrays.hashCode(bootstrapMethodArguments), 24);
+ }
+
+ @Override
+ public String toString() {
+ return name
+ + " : "
+ + descriptor
+ + ' '
+ + bootstrapMethod
+ + ' '
+ + Arrays.toString(bootstrapMethodArguments);
+ }
+}
diff --git a/src/java/nginx/clojure/asm/Constants.java b/src/java/nginx/clojure/asm/Constants.java
new file mode 100644
index 00000000..fe402185
--- /dev/null
+++ b/src/java/nginx/clojure/asm/Constants.java
@@ -0,0 +1,221 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Pattern;
+
+/**
+ * Defines additional JVM opcodes, access flags and constants which are not part of the ASM public
+ * API.
+ *
+ * @see JVMS 6
+ * @author Eric Bruneton
+ */
+final class Constants {
+
+ // The ClassFile attribute names, in the order they are defined in
+ // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7-300.
+
+ static final String CONSTANT_VALUE = "ConstantValue";
+ static final String CODE = "Code";
+ static final String STACK_MAP_TABLE = "StackMapTable";
+ static final String EXCEPTIONS = "Exceptions";
+ static final String INNER_CLASSES = "InnerClasses";
+ static final String ENCLOSING_METHOD = "EnclosingMethod";
+ static final String SYNTHETIC = "Synthetic";
+ static final String SIGNATURE = "Signature";
+ static final String SOURCE_FILE = "SourceFile";
+ static final String SOURCE_DEBUG_EXTENSION = "SourceDebugExtension";
+ static final String LINE_NUMBER_TABLE = "LineNumberTable";
+ static final String LOCAL_VARIABLE_TABLE = "LocalVariableTable";
+ static final String LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable";
+ static final String DEPRECATED = "Deprecated";
+ static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations";
+ static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations";
+ static final String RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations";
+ static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS =
+ "RuntimeInvisibleParameterAnnotations";
+ static final String RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations";
+ static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = "RuntimeInvisibleTypeAnnotations";
+ static final String ANNOTATION_DEFAULT = "AnnotationDefault";
+ static final String BOOTSTRAP_METHODS = "BootstrapMethods";
+ static final String METHOD_PARAMETERS = "MethodParameters";
+ static final String MODULE = "Module";
+ static final String MODULE_PACKAGES = "ModulePackages";
+ static final String MODULE_MAIN_CLASS = "ModuleMainClass";
+ static final String NEST_HOST = "NestHost";
+ static final String NEST_MEMBERS = "NestMembers";
+ static final String PERMITTED_SUBCLASSES = "PermittedSubclasses";
+ static final String RECORD = "Record";
+
+ // ASM specific access flags.
+ // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard
+ // access flags, and also to make sure that these flags are automatically filtered out when
+ // written in class files (because access flags are stored using 16 bits only).
+
+ static final int ACC_CONSTRUCTOR = 0x40000; // method access flag.
+
+ // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}.
+
+ /**
+ * A frame inserted between already existing frames. This internal stack map frame type (in
+ * addition to the ones declared in {@link Opcodes}) can only be used if the frame content can be
+ * computed from the previous existing frame and from the instructions between this existing frame
+ * and the inserted one, without any knowledge of the type hierarchy. This kind of frame is only
+ * used when an unconditional jump is inserted in a method while expanding an ASM specific
+ * instruction. Keep in sync with Opcodes.java.
+ */
+ static final int F_INSERT = 256;
+
+ // The JVM opcode values which are not part of the ASM public API.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html.
+
+ static final int LDC_W = 19;
+ static final int LDC2_W = 20;
+ static final int ILOAD_0 = 26;
+ static final int ILOAD_1 = 27;
+ static final int ILOAD_2 = 28;
+ static final int ILOAD_3 = 29;
+ static final int LLOAD_0 = 30;
+ static final int LLOAD_1 = 31;
+ static final int LLOAD_2 = 32;
+ static final int LLOAD_3 = 33;
+ static final int FLOAD_0 = 34;
+ static final int FLOAD_1 = 35;
+ static final int FLOAD_2 = 36;
+ static final int FLOAD_3 = 37;
+ static final int DLOAD_0 = 38;
+ static final int DLOAD_1 = 39;
+ static final int DLOAD_2 = 40;
+ static final int DLOAD_3 = 41;
+ static final int ALOAD_0 = 42;
+ static final int ALOAD_1 = 43;
+ static final int ALOAD_2 = 44;
+ static final int ALOAD_3 = 45;
+ static final int ISTORE_0 = 59;
+ static final int ISTORE_1 = 60;
+ static final int ISTORE_2 = 61;
+ static final int ISTORE_3 = 62;
+ static final int LSTORE_0 = 63;
+ static final int LSTORE_1 = 64;
+ static final int LSTORE_2 = 65;
+ static final int LSTORE_3 = 66;
+ static final int FSTORE_0 = 67;
+ static final int FSTORE_1 = 68;
+ static final int FSTORE_2 = 69;
+ static final int FSTORE_3 = 70;
+ static final int DSTORE_0 = 71;
+ static final int DSTORE_1 = 72;
+ static final int DSTORE_2 = 73;
+ static final int DSTORE_3 = 74;
+ static final int ASTORE_0 = 75;
+ static final int ASTORE_1 = 76;
+ static final int ASTORE_2 = 77;
+ static final int ASTORE_3 = 78;
+ static final int WIDE = 196;
+ static final int GOTO_W = 200;
+ static final int JSR_W = 201;
+
+ // Constants to convert between normal and wide jump instructions.
+
+ // The delta between the GOTO_W and JSR_W opcodes and GOTO and JUMP.
+ static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - Opcodes.GOTO;
+
+ // Constants to convert JVM opcodes to the equivalent ASM specific opcodes, and vice versa.
+
+ // The delta between the ASM_IFEQ, ..., ASM_IF_ACMPNE, ASM_GOTO and ASM_JSR opcodes
+ // and IFEQ, ..., IF_ACMPNE, GOTO and JSR.
+ static final int ASM_OPCODE_DELTA = 49;
+
+ // The delta between the ASM_IFNULL and ASM_IFNONNULL opcodes and IFNULL and IFNONNULL.
+ static final int ASM_IFNULL_OPCODE_DELTA = 20;
+
+ // ASM specific opcodes, used for long forward jump instructions.
+
+ static final int ASM_IFEQ = Opcodes.IFEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IFNE = Opcodes.IFNE + ASM_OPCODE_DELTA;
+ static final int ASM_IFLT = Opcodes.IFLT + ASM_OPCODE_DELTA;
+ static final int ASM_IFGE = Opcodes.IFGE + ASM_OPCODE_DELTA;
+ static final int ASM_IFGT = Opcodes.IFGT + ASM_OPCODE_DELTA;
+ static final int ASM_IFLE = Opcodes.IFLE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPEQ = Opcodes.IF_ICMPEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPNE = Opcodes.IF_ICMPNE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPLT = Opcodes.IF_ICMPLT + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPGE = Opcodes.IF_ICMPGE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPGT = Opcodes.IF_ICMPGT + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ICMPLE = Opcodes.IF_ICMPLE + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ACMPEQ = Opcodes.IF_ACMPEQ + ASM_OPCODE_DELTA;
+ static final int ASM_IF_ACMPNE = Opcodes.IF_ACMPNE + ASM_OPCODE_DELTA;
+ static final int ASM_GOTO = Opcodes.GOTO + ASM_OPCODE_DELTA;
+ static final int ASM_JSR = Opcodes.JSR + ASM_OPCODE_DELTA;
+ static final int ASM_IFNULL = Opcodes.IFNULL + ASM_IFNULL_OPCODE_DELTA;
+ static final int ASM_IFNONNULL = Opcodes.IFNONNULL + ASM_IFNULL_OPCODE_DELTA;
+ static final int ASM_GOTO_W = 220;
+
+ private Constants() {}
+
+ static void checkAsmExperimental(final Object caller) {
+ Class> callerClass = caller.getClass();
+ String internalName = callerClass.getName().replace('.', '/');
+ if (!isWhitelisted(internalName)) {
+ checkIsPreview(callerClass.getClassLoader().getResourceAsStream(internalName + ".class"));
+ }
+ }
+
+ static boolean isWhitelisted(final String internalName) {
+ if (!internalName.startsWith("org/objectweb/asm/")) {
+ return false;
+ }
+ String member = "(Annotation|Class|Field|Method|Module|RecordComponent|Signature)";
+ return internalName.contains("Test$")
+ || Pattern.matches(
+ "org/objectweb/asm/util/Trace" + member + "Visitor(\\$.*)?", internalName)
+ || Pattern.matches(
+ "org/objectweb/asm/util/Check" + member + "Adapter(\\$.*)?", internalName);
+ }
+
+ static void checkIsPreview(final InputStream classInputStream) {
+ if (classInputStream == null) {
+ throw new IllegalStateException("Bytecode not available, can't check class version");
+ }
+ int minorVersion;
+ try (DataInputStream callerClassStream = new DataInputStream(classInputStream); ) {
+ callerClassStream.readInt();
+ minorVersion = callerClassStream.readUnsignedShort();
+ } catch (IOException ioe) {
+ throw new IllegalStateException("I/O error, can't check class version", ioe);
+ }
+ if (minorVersion != 0xFFFF) {
+ throw new IllegalStateException(
+ "ASM9_EXPERIMENTAL can only be used by classes compiled with --enable-preview");
+ }
+ }
+}
diff --git a/src/java/nginx/clojure/asm/Context.java b/src/java/nginx/clojure/asm/Context.java
index f0e446ba..4547330a 100644
--- a/src/java/nginx/clojure/asm/Context.java
+++ b/src/java/nginx/clojure/asm/Context.java
@@ -1,110 +1,137 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package nginx.clojure.asm;
-
-/**
- * Information about a class being parsed in a {@link ClassReader}.
- *
- * @author Eric Bruneton
- */
-class Context {
-
- /**
- * Prototypes of the attributes that must be parsed for this class.
- */
- Attribute[] attrs;
-
- /**
- * The {@link ClassReader} option flags for the parsing of this class.
- */
- int flags;
-
- /**
- * The buffer used to read strings.
- */
- char[] buffer;
-
- /**
- * The start index of each bootstrap method.
- */
- int[] bootstrapMethods;
-
- /**
- * The access flags of the method currently being parsed.
- */
- int access;
-
- /**
- * The name of the method currently being parsed.
- */
- String name;
-
- /**
- * The descriptor of the method currently being parsed.
- */
- String desc;
-
- /**
- * The offset of the latest stack map frame that has been parsed.
- */
- int offset;
-
- /**
- * The encoding of the latest stack map frame that has been parsed.
- */
- int mode;
-
- /**
- * The number of locals in the latest stack map frame that has been parsed.
- */
- int localCount;
-
- /**
- * The number locals in the latest stack map frame that has been parsed,
- * minus the number of locals in the previous frame.
- */
- int localDiff;
-
- /**
- * The local values of the latest stack map frame that has been parsed.
- */
- Object[] local;
-
- /**
- * The stack size of the latest stack map frame that has been parsed.
- */
- int stackCount;
-
- /**
- * The stack values of the latest stack map frame that has been parsed.
- */
- Object[] stack;
-}
\ No newline at end of file
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+
+package nginx.clojure.asm;
+
+/**
+ * Information about a class being parsed in a {@link ClassReader}.
+ *
+ * @author Eric Bruneton
+ */
+final class Context {
+
+ /** The prototypes of the attributes that must be parsed in this class. */
+ Attribute[] attributePrototypes;
+
+ /**
+ * The options used to parse this class. One or more of {@link ClassReader#SKIP_CODE}, {@link
+ * ClassReader#SKIP_DEBUG}, {@link ClassReader#SKIP_FRAMES}, {@link ClassReader#EXPAND_FRAMES} or
+ * {@link ClassReader#EXPAND_ASM_INSNS}.
+ */
+ int parsingOptions;
+
+ /** The buffer used to read strings in the constant pool. */
+ char[] charBuffer;
+
+ // Information about the current method, i.e. the one read in the current (or latest) call
+ // to {@link ClassReader#readMethod()}.
+
+ /** The access flags of the current method. */
+ int currentMethodAccessFlags;
+
+ /** The name of the current method. */
+ String currentMethodName;
+
+ /** The descriptor of the current method. */
+ String currentMethodDescriptor;
+
+ /**
+ * The labels of the current method, indexed by bytecode offset (only bytecode offsets for which a
+ * label is needed have a non null associated Label).
+ */
+ Label[] currentMethodLabels;
+
+ // Information about the current type annotation target, i.e. the one read in the current
+ // (or latest) call to {@link ClassReader#readAnnotationTarget()}.
+
+ /**
+ * The target_type and target_info of the current type annotation target, encoded as described in
+ * {@link TypeReference}.
+ */
+ int currentTypeAnnotationTarget;
+
+ /** The target_path of the current type annotation target. */
+ TypePath currentTypeAnnotationTargetPath;
+
+ /** The start of each local variable range in the current local variable annotation. */
+ Label[] currentLocalVariableAnnotationRangeStarts;
+
+ /** The end of each local variable range in the current local variable annotation. */
+ Label[] currentLocalVariableAnnotationRangeEnds;
+
+ /**
+ * The local variable index of each local variable range in the current local variable annotation.
+ */
+ int[] currentLocalVariableAnnotationRangeIndices;
+
+ // Information about the current stack map frame, i.e. the one read in the current (or latest)
+ // call to {@link ClassReader#readFrame()}.
+
+ /** The bytecode offset of the current stack map frame. */
+ int currentFrameOffset;
+
+ /**
+ * The type of the current stack map frame. One of {@link Opcodes#F_FULL}, {@link
+ * Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or {@link Opcodes#F_SAME1}.
+ */
+ int currentFrameType;
+
+ /**
+ * The number of local variable types in the current stack map frame. Each type is represented
+ * with a single array element (even long and double).
+ */
+ int currentFrameLocalCount;
+
+ /**
+ * The delta number of local variable types in the current stack map frame (each type is
+ * represented with a single array element - even long and double). This is the number of local
+ * variable types in this frame, minus the number of local variable types in the previous frame.
+ */
+ int currentFrameLocalCountDelta;
+
+ /**
+ * The types of the local variables in the current stack map frame. Each type is represented with
+ * a single array element (even long and double), using the format described in {@link
+ * MethodVisitor#visitFrame}. Depending on {@link #currentFrameType}, this contains the types of
+ * all the local variables, or only those of the additional ones (compared to the previous frame).
+ */
+ Object[] currentFrameLocalTypes;
+
+ /**
+ * The number stack element types in the current stack map frame. Each type is represented with a
+ * single array element (even long and double).
+ */
+ int currentFrameStackCount;
+
+ /**
+ * The types of the stack elements in the current stack map frame. Each type is represented with a
+ * single array element (even long and double), using the format described in {@link
+ * MethodVisitor#visitFrame}.
+ */
+ Object[] currentFrameStackTypes;
+}
diff --git a/src/java/nginx/clojure/asm/CurrentFrame.java b/src/java/nginx/clojure/asm/CurrentFrame.java
new file mode 100644
index 00000000..e7c0aab8
--- /dev/null
+++ b/src/java/nginx/clojure/asm/CurrentFrame.java
@@ -0,0 +1,56 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+
+package nginx.clojure.asm;
+
+/**
+ * Information about the input stack map frame at the "current" instruction of a method. This is
+ * implemented as a Frame subclass for a "basic block" containing only one instruction.
+ *
+ * @author Eric Bruneton
+ */
+final class CurrentFrame extends Frame {
+
+ CurrentFrame(final Label owner) {
+ super(owner);
+ }
+
+ /**
+ * Sets this CurrentFrame to the input stack map frame of the next "current" instruction, i.e. the
+ * instruction just after the given one. It is assumed that the value of this object when this
+ * method is called is the stack map frame status just before the given instruction is executed.
+ */
+ @Override
+ void execute(
+ final int opcode, final int arg, final Symbol symbolArg, final SymbolTable symbolTable) {
+ super.execute(opcode, arg, symbolArg, symbolTable);
+ Frame successor = new Frame(null);
+ merge(symbolTable, successor, 0);
+ copyFrom(successor);
+ }
+}
diff --git a/src/java/nginx/clojure/asm/Edge.java b/src/java/nginx/clojure/asm/Edge.java
index eaf8e454..2863b231 100644
--- a/src/java/nginx/clojure/asm/Edge.java
+++ b/src/java/nginx/clojure/asm/Edge.java
@@ -1,75 +1,91 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * An edge in the control flow graph of a method body. See {@link Label Label}.
- *
+ * An edge in the control flow graph of a method. Each node of this graph is a basic block,
+ * represented with the Label corresponding to its first instruction. Each edge goes from one node
+ * to another, i.e. from one basic block to another (called the predecessor and successor blocks,
+ * respectively). An edge corresponds either to a jump or ret instruction or to an exception
+ * handler.
+ *
+ * @see Label
* @author Eric Bruneton
*/
-class Edge {
+final class Edge {
+
+ /**
+ * A control flow graph edge corresponding to a jump or ret instruction. Only used with {@link
+ * ClassWriter#COMPUTE_FRAMES}.
+ */
+ static final int JUMP = 0;
- /**
- * Denotes a normal control flow graph edge.
- */
- static final int NORMAL = 0;
+ /**
+ * A control flow graph edge corresponding to an exception handler. Only used with {@link
+ * ClassWriter#COMPUTE_MAXS}.
+ */
+ static final int EXCEPTION = 0x7FFFFFFF;
- /**
- * Denotes a control flow graph edge corresponding to an exception handler.
- * More precisely any {@link Edge} whose {@link #info} is strictly positive
- * corresponds to an exception handler. The actual value of {@link #info} is
- * the index, in the {@link ClassWriter} type table, of the exception that
- * is catched.
- */
- static final int EXCEPTION = 0x7FFFFFFF;
+ /**
+ * Information about this control flow graph edge.
+ *
+ *
+ * - If {@link ClassWriter#COMPUTE_MAXS} is used, this field contains either a stack size
+ * delta (for an edge corresponding to a jump instruction), or the value EXCEPTION (for an
+ * edge corresponding to an exception handler). The stack size delta is the stack size just
+ * after the jump instruction, minus the stack size at the beginning of the predecessor
+ * basic block, i.e. the one containing the jump instruction.
+ *
- If {@link ClassWriter#COMPUTE_FRAMES} is used, this field contains either the value JUMP
+ * (for an edge corresponding to a jump instruction), or the index, in the {@link
+ * ClassWriter} type table, of the exception type that is handled (for an edge corresponding
+ * to an exception handler).
+ *
+ */
+ final int info;
- /**
- * Information about this control flow graph edge. If
- * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative)
- * stack size in the basic block from which this edge originates. This size
- * is equal to the stack size at the "jump" instruction to which this edge
- * corresponds, relatively to the stack size at the beginning of the
- * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used,
- * this field is the kind of this control flow graph edge (i.e. NORMAL or
- * EXCEPTION).
- */
- int info;
+ /** The successor block of this control flow graph edge. */
+ final Label successor;
- /**
- * The successor block of the basic block from which this edge originates.
- */
- Label successor;
+ /**
+ * The next edge in the list of outgoing edges of a basic block. See {@link Label#outgoingEdges}.
+ */
+ Edge nextEdge;
- /**
- * The next edge in the list of successors of the originating basic block.
- * See {@link Label#successors successors}.
- */
- Edge next;
+ /**
+ * Constructs a new Edge.
+ *
+ * @param info see {@link #info}.
+ * @param successor see {@link #successor}.
+ * @param nextEdge see {@link #nextEdge}.
+ */
+ Edge(final int info, final Label successor, final Edge nextEdge) {
+ this.info = info;
+ this.successor = successor;
+ this.nextEdge = nextEdge;
+ }
}
diff --git a/src/java/nginx/clojure/asm/FieldVisitor.java b/src/java/nginx/clojure/asm/FieldVisitor.java
index a2d39981..468aada8 100644
--- a/src/java/nginx/clojure/asm/FieldVisitor.java
+++ b/src/java/nginx/clojure/asm/FieldVisitor.java
@@ -1,121 +1,151 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A visitor to visit a Java field. The methods of this class must be called in
- * the following order: ( visitAnnotation | visitAttribute )*
- * visitEnd.
- *
+ * A visitor to visit a Java field. The methods of this class must be called in the following order:
+ * ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code
+ * visitEnd}.
+ *
* @author Eric Bruneton
*/
public abstract class FieldVisitor {
- /**
- * The ASM API version implemented by this visitor. The value of this field
- * must be one of {@link Opcodes#ASM4}.
- */
- protected final int api;
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
+ */
+ protected final int api;
- /**
- * The field visitor to which this visitor must delegate method calls. May
- * be null.
- */
- protected FieldVisitor fv;
+ /** The field visitor to which this visitor must delegate method calls. May be {@literal null}. */
+ protected FieldVisitor fv;
- /**
- * Constructs a new {@link FieldVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- */
- public FieldVisitor(final int api) {
- this(api, null);
+ /**
+ * Constructs a new {@link FieldVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ */
+ protected FieldVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link FieldVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ * @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be
+ * null.
+ */
+ protected FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
}
+ this.api = api;
+ this.fv = fieldVisitor;
+ }
- /**
- * Constructs a new {@link FieldVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- * @param fv
- * the field visitor to which this visitor must delegate method
- * calls. May be null.
- */
- public FieldVisitor(final int api, final FieldVisitor fv) {
- if (api != Opcodes.ASM4) {
- throw new IllegalArgumentException();
- }
- this.api = api;
- this.fv = fv;
+ /**
+ * The field visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the field visitor to which this visitor must delegate method calls, or {@literal null}.
+ */
+ public FieldVisitor getDelegate() {
+ return fv;
+ }
+
+ /**
+ * Visits an annotation of the field.
+ *
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (fv != null) {
+ return fv.visitAnnotation(descriptor, visible);
}
+ return null;
+ }
- /**
- * Visits an annotation of the field.
- *
- * @param desc
- * the class descriptor of the annotation class.
- * @param visible
- * true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if
- * this visitor is not interested in visiting this annotation.
- */
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (fv != null) {
- return fv.visitAnnotation(desc, visible);
- }
- return null;
+ /**
+ * Visits an annotation on the type of the field.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#FIELD}. See {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException("This feature requires ASM5");
+ }
+ if (fv != null) {
+ return fv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
}
+ return null;
+ }
- /**
- * Visits a non standard attribute of the field.
- *
- * @param attr
- * an attribute.
- */
- public void visitAttribute(Attribute attr) {
- if (fv != null) {
- fv.visitAttribute(attr);
- }
+ /**
+ * Visits a non standard attribute of the field.
+ *
+ * @param attribute an attribute.
+ */
+ public void visitAttribute(final Attribute attribute) {
+ if (fv != null) {
+ fv.visitAttribute(attribute);
}
+ }
- /**
- * Visits the end of the field. This method, which is the last one to be
- * called, is used to inform the visitor that all the annotations and
- * attributes of the field have been visited.
- */
- public void visitEnd() {
- if (fv != null) {
- fv.visitEnd();
- }
+ /**
+ * Visits the end of the field. This method, which is the last one to be called, is used to inform
+ * the visitor that all the annotations and attributes of the field have been visited.
+ */
+ public void visitEnd() {
+ if (fv != null) {
+ fv.visitEnd();
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/FieldWriter.java b/src/java/nginx/clojure/asm/FieldWriter.java
index 6a0b706b..4447a2b2 100644
--- a/src/java/nginx/clojure/asm/FieldWriter.java
+++ b/src/java/nginx/clojure/asm/FieldWriter.java
@@ -1,273 +1,284 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * An {@link FieldVisitor} that generates Java fields in bytecode form.
- *
+ * A {@link FieldVisitor} that generates a corresponding 'field_info' structure, as defined in the
+ * Java Virtual Machine Specification (JVMS).
+ *
+ * @see JVMS
+ * 4.5
* @author Eric Bruneton
*/
final class FieldWriter extends FieldVisitor {
- /**
- * The class writer to which this field must be added.
- */
- private final ClassWriter cw;
+ /** Where the constants used in this FieldWriter must be stored. */
+ private final SymbolTable symbolTable;
- /**
- * Access flags of this field.
- */
- private final int access;
+ // Note: fields are ordered as in the field_info structure, and those related to attributes are
+ // ordered as in Section 4.7 of the JVMS.
- /**
- * The index of the constant pool item that contains the name of this
- * method.
- */
- private final int name;
+ /**
+ * The access_flags field of the field_info JVMS structure. This field can contain ASM specific
+ * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
+ * ClassFile structure.
+ */
+ private final int accessFlags;
- /**
- * The index of the constant pool item that contains the descriptor of this
- * field.
- */
- private final int desc;
+ /** The name_index field of the field_info JVMS structure. */
+ private final int nameIndex;
- /**
- * The index of the constant pool item that contains the signature of this
- * field.
- */
- private int signature;
+ /** The descriptor_index field of the field_info JVMS structure. */
+ private final int descriptorIndex;
- /**
- * The index of the constant pool item that contains the constant value of
- * this field.
- */
- private int value;
+ /**
+ * The signature_index field of the Signature attribute of this field_info, or 0 if there is no
+ * Signature attribute.
+ */
+ private int signatureIndex;
- /**
- * The runtime visible annotations of this field. May be null.
- */
- private AnnotationWriter anns;
+ /**
+ * The constantvalue_index field of the ConstantValue attribute of this field_info, or 0 if there
+ * is no ConstantValue attribute.
+ */
+ private int constantValueIndex;
- /**
- * The runtime invisible annotations of this field. May be null.
- */
- private AnnotationWriter ianns;
+ /**
+ * The last runtime visible annotation of this field. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleAnnotation;
- /**
- * The non standard attributes of this field. May be null.
- */
- private Attribute attrs;
+ /**
+ * The last runtime invisible annotation of this field. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleAnnotation;
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
+ /**
+ * The last runtime visible type annotation of this field. The previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
- /**
- * Constructs a new {@link FieldWriter}.
- *
- * @param cw
- * the class writer to which this field must be added.
- * @param access
- * the field's access flags (see {@link Opcodes}).
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor (see {@link Type}).
- * @param signature
- * the field's signature. May be null.
- * @param value
- * the field's constant value. May be null.
- */
- FieldWriter(final ClassWriter cw, final int access, final String name,
- final String desc, final String signature, final Object value) {
- super(Opcodes.ASM4);
- if (cw.firstField == null) {
- cw.firstField = this;
- } else {
- cw.lastField.fv = this;
- }
- cw.lastField = this;
- this.cw = cw;
- this.access = access;
- this.name = cw.newUTF8(name);
- this.desc = cw.newUTF8(desc);
- if (ClassReader.SIGNATURES && signature != null) {
- this.signature = cw.newUTF8(signature);
- }
- if (value != null) {
- this.value = cw.newConstItem(value).index;
- }
- }
+ /**
+ * The last runtime invisible type annotation of this field. The previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
+
+ /**
+ * The first non standard attribute of this field. The next ones can be accessed with the {@link
+ * Attribute#nextAttribute} field. May be {@literal null}.
+ *
+ * WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
+ * #putFieldInfo} method writes the attributes in the order defined by this list, i.e. in the
+ * reverse order specified by the user.
+ */
+ private Attribute firstAttribute;
- // ------------------------------------------------------------------------
- // Implementation of the FieldVisitor abstract class
- // ------------------------------------------------------------------------
+ // -----------------------------------------------------------------------------------------------
+ // Constructor
+ // -----------------------------------------------------------------------------------------------
- @Override
- public AnnotationVisitor visitAnnotation(final String desc,
- final boolean visible) {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- ByteVector bv = new ByteVector();
- // write type, and reserve space for values count
- bv.putShort(cw.newUTF8(desc)).putShort(0);
- AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
- if (visible) {
- aw.next = anns;
- anns = aw;
- } else {
- aw.next = ianns;
- ianns = aw;
- }
- return aw;
+ /**
+ * Constructs a new {@link FieldWriter}.
+ *
+ * @param symbolTable where the constants used in this FieldWriter must be stored.
+ * @param access the field's access flags (see {@link Opcodes}).
+ * @param name the field's name.
+ * @param descriptor the field's descriptor (see {@link Type}).
+ * @param signature the field's signature. May be {@literal null}.
+ * @param constantValue the field's constant value. May be {@literal null}.
+ */
+ FieldWriter(
+ final SymbolTable symbolTable,
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Object constantValue) {
+ super(/* latest api = */ Opcodes.ASM9);
+ this.symbolTable = symbolTable;
+ this.accessFlags = access;
+ this.nameIndex = symbolTable.addConstantUtf8(name);
+ this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
+ if (signature != null) {
+ this.signatureIndex = symbolTable.addConstantUtf8(signature);
}
+ if (constantValue != null) {
+ this.constantValueIndex = symbolTable.addConstant(constantValue).index;
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the FieldVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
- @Override
- public void visitAttribute(final Attribute attr) {
- attr.next = attrs;
- attrs = attr;
+ @Override
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
+ } else {
+ return lastRuntimeInvisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
}
+ }
- @Override
- public void visitEnd() {
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastRuntimeInvisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
}
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attribute) {
+ // Store the attributes in the reverse order of their visit by this method.
+ attribute.nextAttribute = firstAttribute;
+ firstAttribute = attribute;
+ }
- // ------------------------------------------------------------------------
- // Utility methods
- // ------------------------------------------------------------------------
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
- /**
- * Returns the size of this field.
- *
- * @return the size of this field.
- */
- int getSize() {
- int size = 8;
- if (value != 0) {
- cw.newUTF8("ConstantValue");
- size += 8;
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- cw.newUTF8("Synthetic");
- size += 6;
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- cw.newUTF8("Deprecated");
- size += 6;
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- cw.newUTF8("Signature");
- size += 8;
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- cw.newUTF8("RuntimeVisibleAnnotations");
- size += 8 + anns.getSize();
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- cw.newUTF8("RuntimeInvisibleAnnotations");
- size += 8 + ianns.getSize();
- }
- if (attrs != null) {
- size += attrs.getSize(cw, null, 0, -1, -1);
- }
- return size;
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the size of the field_info JVMS structure generated by this FieldWriter. Also adds the
+ * names of the attributes of this field in the constant pool.
+ *
+ * @return the size in bytes of the field_info JVMS structure.
+ */
+ int computeFieldInfoSize() {
+ // The access_flags, name_index, descriptor_index and attributes_count fields use 8 bytes.
+ int size = 8;
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ if (constantValueIndex != 0) {
+ // ConstantValue attributes always use 8 bytes.
+ symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE);
+ size += 8;
+ }
+ size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex);
+ size +=
+ AnnotationWriter.computeAnnotationsSize(
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation);
+ if (firstAttribute != null) {
+ size += firstAttribute.computeAttributesSize(symbolTable);
}
+ return size;
+ }
- /**
- * Puts the content of this field into the given byte vector.
- *
- * @param out
- * where the content of this field must be put.
- */
- void put(final ByteVector out) {
- final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC;
- int mask = Opcodes.ACC_DEPRECATED | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
- | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR);
- out.putShort(access & ~mask).putShort(name).putShort(desc);
- int attributeCount = 0;
- if (value != 0) {
- ++attributeCount;
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- ++attributeCount;
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- ++attributeCount;
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- ++attributeCount;
- }
- if (attrs != null) {
- attributeCount += attrs.getCount();
- }
- out.putShort(attributeCount);
- if (value != 0) {
- out.putShort(cw.newUTF8("ConstantValue"));
- out.putInt(2).putShort(value);
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- out.putShort(cw.newUTF8("Synthetic")).putInt(0);
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- out.putShort(cw.newUTF8("Deprecated")).putInt(0);
- }
- if (ClassReader.SIGNATURES && signature != 0) {
- out.putShort(cw.newUTF8("Signature"));
- out.putInt(2).putShort(signature);
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
- anns.put(out);
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
- ianns.put(out);
- }
- if (attrs != null) {
- attrs.put(cw, null, 0, -1, -1, out);
- }
+ /**
+ * Puts the content of the field_info JVMS structure generated by this FieldWriter into the given
+ * ByteVector.
+ *
+ * @param output where the field_info structure must be put.
+ */
+ void putFieldInfo(final ByteVector output) {
+ boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
+ // Put the access_flags, name_index and descriptor_index fields.
+ int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
+ output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
+ // Compute and put the attributes_count field.
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ int attributesCount = 0;
+ if (constantValueIndex != 0) {
+ ++attributesCount;
}
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
+ ++attributesCount;
+ }
+ if (signatureIndex != 0) {
+ ++attributesCount;
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributesCount;
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ ++attributesCount;
+ }
+ if (firstAttribute != null) {
+ attributesCount += firstAttribute.getAttributeCount();
+ }
+ output.putShort(attributesCount);
+ // Put the field_info attributes.
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ if (constantValueIndex != 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE))
+ .putInt(2)
+ .putShort(constantValueIndex);
+ }
+ Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output);
+ AnnotationWriter.putAnnotations(
+ symbolTable,
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation,
+ output);
+ if (firstAttribute != null) {
+ firstAttribute.putAttributes(symbolTable, output);
+ }
+ }
+
+ /**
+ * Collects the attributes of this field into the given set of attribute prototypes.
+ *
+ * @param attributePrototypes a set of attribute prototypes.
+ */
+ final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
+ attributePrototypes.addAttributes(firstAttribute);
+ }
}
diff --git a/src/java/nginx/clojure/asm/Frame.java b/src/java/nginx/clojure/asm/Frame.java
index 1d5378ee..d6868d62 100644
--- a/src/java/nginx/clojure/asm/Frame.java
+++ b/src/java/nginx/clojure/asm/Frame.java
@@ -1,1453 +1,1473 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * Information about the input and output stack map frames of a basic block.
- *
+ * The input and output stack map frames of a basic block.
+ *
+ *
Stack map frames are computed in two steps:
+ *
+ *
+ * - During the visit of each instruction in MethodWriter, the state of the frame at the end of
+ * the current basic block is updated by simulating the action of the instruction on the
+ * previous state of this so called "output frame".
+ *
- After all instructions have been visited, a fix point algorithm is used in MethodWriter to
+ * compute the "input frame" of each basic block (i.e. the stack map frame at the beginning of
+ * the basic block). See {@link MethodWriter#computeAllFrames}.
+ *
+ *
+ * Output stack map frames are computed relatively to the input frame of the basic block, which
+ * is not yet known when output frames are computed. It is therefore necessary to be able to
+ * represent abstract types such as "the type at position x in the input frame locals" or "the type
+ * at position x from the top of the input frame stack" or even "the type at position x in the input
+ * frame, with y more (or less) array dimensions". This explains the rather complicated type format
+ * used in this class, explained below.
+ *
+ *
The local variables and the operand stack of input and output frames contain values called
+ * "abstract types" hereafter. An abstract type is represented with 4 fields named DIM, KIND, FLAGS
+ * and VALUE, packed in a single int value for better performance and memory efficiency:
+ *
+ *
+ * =====================================
+ * |...DIM|KIND|.F|...............VALUE|
+ * =====================================
+ *
+ *
+ *
+ * - the DIM field, stored in the 6 most significant bits, is a signed number of array
+ * dimensions (from -32 to 31, included). It can be retrieved with {@link #DIM_MASK} and a
+ * right shift of {@link #DIM_SHIFT}.
+ *
- the KIND field, stored in 4 bits, indicates the kind of VALUE used. These 4 bits can be
+ * retrieved with {@link #KIND_MASK} and, without any shift, must be equal to {@link
+ * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link #LOCAL_KIND}
+ * or {@link #STACK_KIND}.
+ *
- the FLAGS field, stored in 2 bits, contains up to 2 boolean flags. Currently only one flag
+ * is defined, namely {@link #TOP_IF_LONG_OR_DOUBLE_FLAG}.
+ *
- the VALUE field, stored in the remaining 20 bits, contains either
+ *
+ * - one of the constants {@link #ITEM_TOP}, {@link #ITEM_ASM_BOOLEAN}, {@link
+ * #ITEM_ASM_BYTE}, {@link #ITEM_ASM_CHAR} or {@link #ITEM_ASM_SHORT}, {@link
+ * #ITEM_INTEGER}, {@link #ITEM_FLOAT}, {@link #ITEM_LONG}, {@link #ITEM_DOUBLE}, {@link
+ * #ITEM_NULL} or {@link #ITEM_UNINITIALIZED_THIS}, if KIND is equal to {@link
+ * #CONSTANT_KIND}.
+ *
- the index of a {@link Symbol#TYPE_TAG} {@link Symbol} in the type table of a {@link
+ * SymbolTable}, if KIND is equal to {@link #REFERENCE_KIND}.
+ *
- the index of an {@link Symbol#UNINITIALIZED_TYPE_TAG} {@link Symbol} in the type
+ * table of a SymbolTable, if KIND is equal to {@link #UNINITIALIZED_KIND}.
+ *
- the index of a local variable in the input stack frame, if KIND is equal to {@link
+ * #LOCAL_KIND}.
+ *
- a position relatively to the top of the stack of the input stack frame, if KIND is
+ * equal to {@link #STACK_KIND},
+ *
+ *
+ *
+ * Output frames can contain abstract types of any kind and with a positive or negative array
+ * dimension (and even unassigned types, represented by 0 - which does not correspond to any valid
+ * abstract type value). Input frames can only contain CONSTANT_KIND, REFERENCE_KIND or
+ * UNINITIALIZED_KIND abstract types of positive or {@literal null} array dimension. In all cases
+ * the type table contains only internal type names (array type descriptors are forbidden - array
+ * dimensions must be represented through the DIM field).
+ *
+ *
The LONG and DOUBLE types are always represented by using two slots (LONG + TOP or DOUBLE +
+ * TOP), for local variables as well as in the operand stack. This is necessary to be able to
+ * simulate DUPx_y instructions, whose effect would be dependent on the concrete types represented
+ * by the abstract types in the stack (which are not always known).
+ *
* @author Eric Bruneton
*/
-final class Frame {
+class Frame {
- /*
- * Frames are computed in a two steps process: during the visit of each
- * instruction, the state of the frame at the end of current basic block is
- * updated by simulating the action of the instruction on the previous state
- * of this so called "output frame". In visitMaxs, a fix point algorithm is
- * used to compute the "input frame" of each basic block, i.e. the stack map
- * frame at the beginning of the basic block, starting from the input frame
- * of the first basic block (which is computed from the method descriptor),
- * and by using the previously computed output frames to compute the input
- * state of the other blocks.
- *
- * All output and input frames are stored as arrays of integers. Reference
- * and array types are represented by an index into a type table (which is
- * not the same as the constant pool of the class, in order to avoid adding
- * unnecessary constants in the pool - not all computed frames will end up
- * being stored in the stack map table). This allows very fast type
- * comparisons.
- *
- * Output stack map frames are computed relatively to the input frame of the
- * basic block, which is not yet known when output frames are computed. It
- * is therefore necessary to be able to represent abstract types such as
- * "the type at position x in the input frame locals" or "the type at
- * position x from the top of the input frame stack" or even "the type at
- * position x in the input frame, with y more (or less) array dimensions".
- * This explains the rather complicated type format used in output frames.
- *
- * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a
- * signed number of array dimensions (from -8 to 7). KIND is either BASE,
- * LOCAL or STACK. BASE is used for types that are not relative to the input
- * frame. LOCAL is used for types that are relative to the input local
- * variable types. STACK is used for types that are relative to the input
- * stack types. VALUE depends on KIND. For LOCAL types, it is an index in
- * the input local variable types. For STACK types, it is a position
- * relatively to the top of input frame stack. For BASE types, it is either
- * one of the constants defined in FrameVisitor, or for OBJECT and
- * UNINITIALIZED types, a tag and an index in the type table.
- *
- * Output frames can contain types of any kind and with a positive or
- * negative dimension (and even unassigned types, represented by 0 - which
- * does not correspond to any valid type value). Input frames can only
- * contain BASE types of positive or null dimension. In all cases the type
- * table contains only internal type names (array type descriptors are
- * forbidden - dimensions must be represented through the DIM field).
- *
- * The LONG and DOUBLE types are always represented by using two slots (LONG
- * + TOP or DOUBLE + TOP), for local variable types as well as in the
- * operand stack. This is necessary to be able to simulate DUPx_y
- * instructions, whose effect would be dependent on the actual type values
- * if types were always represented by a single slot in the stack (and this
- * is not possible, since actual type values are not always known - cf LOCAL
- * and STACK type kinds).
- */
+ // Constants used in the StackMapTable attribute.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.4.
- /**
- * Mask to get the dimension of a frame type. This dimension is a signed
- * integer between -8 and 7.
- */
- static final int DIM = 0xF0000000;
+ static final int SAME_FRAME = 0;
+ static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64;
+ static final int RESERVED = 128;
+ static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247;
+ static final int CHOP_FRAME = 248;
+ static final int SAME_FRAME_EXTENDED = 251;
+ static final int APPEND_FRAME = 252;
+ static final int FULL_FRAME = 255;
- /**
- * Constant to be added to a type to get a type with one more dimension.
- */
- static final int ARRAY_OF = 0x10000000;
+ static final int ITEM_TOP = 0;
+ static final int ITEM_INTEGER = 1;
+ static final int ITEM_FLOAT = 2;
+ static final int ITEM_DOUBLE = 3;
+ static final int ITEM_LONG = 4;
+ static final int ITEM_NULL = 5;
+ static final int ITEM_UNINITIALIZED_THIS = 6;
+ static final int ITEM_OBJECT = 7;
+ static final int ITEM_UNINITIALIZED = 8;
+ // Additional, ASM specific constants used in abstract types below.
+ private static final int ITEM_ASM_BOOLEAN = 9;
+ private static final int ITEM_ASM_BYTE = 10;
+ private static final int ITEM_ASM_CHAR = 11;
+ private static final int ITEM_ASM_SHORT = 12;
- /**
- * Constant to be added to a type to get a type with one less dimension.
- */
- static final int ELEMENT_OF = 0xF0000000;
+ // The size and offset in bits of each field of an abstract type.
- /**
- * Mask to get the kind of a frame type.
- *
- * @see #BASE
- * @see #LOCAL
- * @see #STACK
- */
- static final int KIND = 0xF000000;
+ private static final int DIM_SIZE = 6;
+ private static final int KIND_SIZE = 4;
+ private static final int FLAGS_SIZE = 2;
+ private static final int VALUE_SIZE = 32 - DIM_SIZE - KIND_SIZE - FLAGS_SIZE;
- /**
- * Flag used for LOCAL and STACK types. Indicates that if this type happens
- * to be a long or double type (during the computations of input frames),
- * then it must be set to TOP because the second word of this value has been
- * reused to store other data in the basic block. Hence the first word no
- * longer stores a valid long or double value.
- */
- static final int TOP_IF_LONG_OR_DOUBLE = 0x800000;
+ private static final int DIM_SHIFT = KIND_SIZE + FLAGS_SIZE + VALUE_SIZE;
+ private static final int KIND_SHIFT = FLAGS_SIZE + VALUE_SIZE;
+ private static final int FLAGS_SHIFT = VALUE_SIZE;
- /**
- * Mask to get the value of a frame type.
- */
- static final int VALUE = 0x7FFFFF;
+ // Bitmasks to get each field of an abstract type.
- /**
- * Mask to get the kind of base types.
- */
- static final int BASE_KIND = 0xFF00000;
+ private static final int DIM_MASK = ((1 << DIM_SIZE) - 1) << DIM_SHIFT;
+ private static final int KIND_MASK = ((1 << KIND_SIZE) - 1) << KIND_SHIFT;
+ private static final int VALUE_MASK = (1 << VALUE_SIZE) - 1;
- /**
- * Mask to get the value of base types.
- */
- static final int BASE_VALUE = 0xFFFFF;
+ // Constants to manipulate the DIM field of an abstract type.
- /**
- * Kind of the types that are not relative to an input stack map frame.
- */
- static final int BASE = 0x1000000;
+ /** The constant to be added to an abstract type to get one with one more array dimension. */
+ private static final int ARRAY_OF = +1 << DIM_SHIFT;
- /**
- * Base kind of the base reference types. The BASE_VALUE of such types is an
- * index into the type table.
- */
- static final int OBJECT = BASE | 0x700000;
+ /** The constant to be added to an abstract type to get one with one less array dimension. */
+ private static final int ELEMENT_OF = -1 << DIM_SHIFT;
- /**
- * Base kind of the uninitialized base types. The BASE_VALUE of such types
- * in an index into the type table (the Item at that index contains both an
- * instruction offset and an internal class name).
- */
- static final int UNINITIALIZED = BASE | 0x800000;
+ // Possible values for the KIND field of an abstract type.
- /**
- * Kind of the types that are relative to the local variable types of an
- * input stack map frame. The value of such types is a local variable index.
- */
- private static final int LOCAL = 0x2000000;
+ private static final int CONSTANT_KIND = 1 << KIND_SHIFT;
+ private static final int REFERENCE_KIND = 2 << KIND_SHIFT;
+ private static final int UNINITIALIZED_KIND = 3 << KIND_SHIFT;
+ private static final int LOCAL_KIND = 4 << KIND_SHIFT;
+ private static final int STACK_KIND = 5 << KIND_SHIFT;
- /**
- * Kind of the the types that are relative to the stack of an input stack
- * map frame. The value of such types is a position relatively to the top of
- * this stack.
- */
- private static final int STACK = 0x3000000;
+ // Possible flags for the FLAGS field of an abstract type.
- /**
- * The TOP type. This is a BASE type.
- */
- static final int TOP = BASE | 0;
+ /**
+ * A flag used for LOCAL_KIND and STACK_KIND abstract types, indicating that if the resolved,
+ * concrete type is LONG or DOUBLE, TOP should be used instead (because the value has been
+ * partially overridden with an xSTORE instruction).
+ */
+ private static final int TOP_IF_LONG_OR_DOUBLE_FLAG = 1 << FLAGS_SHIFT;
- /**
- * The BOOLEAN type. This is a BASE type mainly used for array types.
- */
- static final int BOOLEAN = BASE | 9;
+ // Useful predefined abstract types (all the possible CONSTANT_KIND types).
- /**
- * The BYTE type. This is a BASE type mainly used for array types.
- */
- static final int BYTE = BASE | 10;
+ private static final int TOP = CONSTANT_KIND | ITEM_TOP;
+ private static final int BOOLEAN = CONSTANT_KIND | ITEM_ASM_BOOLEAN;
+ private static final int BYTE = CONSTANT_KIND | ITEM_ASM_BYTE;
+ private static final int CHAR = CONSTANT_KIND | ITEM_ASM_CHAR;
+ private static final int SHORT = CONSTANT_KIND | ITEM_ASM_SHORT;
+ private static final int INTEGER = CONSTANT_KIND | ITEM_INTEGER;
+ private static final int FLOAT = CONSTANT_KIND | ITEM_FLOAT;
+ private static final int LONG = CONSTANT_KIND | ITEM_LONG;
+ private static final int DOUBLE = CONSTANT_KIND | ITEM_DOUBLE;
+ private static final int NULL = CONSTANT_KIND | ITEM_NULL;
+ private static final int UNINITIALIZED_THIS = CONSTANT_KIND | ITEM_UNINITIALIZED_THIS;
- /**
- * The CHAR type. This is a BASE type mainly used for array types.
- */
- static final int CHAR = BASE | 11;
+ // -----------------------------------------------------------------------------------------------
+ // Instance fields
+ // -----------------------------------------------------------------------------------------------
- /**
- * The SHORT type. This is a BASE type mainly used for array types.
- */
- static final int SHORT = BASE | 12;
+ /** The basic block to which these input and output stack map frames correspond. */
+ Label owner;
- /**
- * The INTEGER type. This is a BASE type.
- */
- static final int INTEGER = BASE | 1;
+ /** The input stack map frame locals. This is an array of abstract types. */
+ private int[] inputLocals;
- /**
- * The FLOAT type. This is a BASE type.
- */
- static final int FLOAT = BASE | 2;
+ /** The input stack map frame stack. This is an array of abstract types. */
+ private int[] inputStack;
- /**
- * The DOUBLE type. This is a BASE type.
- */
- static final int DOUBLE = BASE | 3;
+ /** The output stack map frame locals. This is an array of abstract types. */
+ private int[] outputLocals;
- /**
- * The LONG type. This is a BASE type.
- */
- static final int LONG = BASE | 4;
+ /** The output stack map frame stack. This is an array of abstract types. */
+ private int[] outputStack;
- /**
- * The NULL type. This is a BASE type.
- */
- static final int NULL = BASE | 5;
+ /**
+ * The start of the output stack, relatively to the input stack. This offset is always negative or
+ * null. A null offset means that the output stack must be appended to the input stack. A -n
+ * offset means that the first n output stack elements must replace the top n input stack
+ * elements, and that the other elements must be appended to the input stack.
+ */
+ private short outputStackStart;
- /**
- * The UNINITIALIZED_THIS type. This is a BASE type.
- */
- static final int UNINITIALIZED_THIS = BASE | 6;
+ /** The index of the top stack element in {@link #outputStack}. */
+ private short outputStackTop;
- /**
- * The stack size variation corresponding to each JVM instruction. This
- * stack variation is equal to the size of the values produced by an
- * instruction, minus the size of the values consumed by this instruction.
- */
- static final int[] SIZE;
+ /** The number of types that are initialized in the basic block. See {@link #initializations}. */
+ private int initializationCount;
- /**
- * Computes the stack size variation corresponding to each JVM instruction.
- */
- static {
- int i;
- int[] b = new int[202];
- String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD"
- + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD"
- + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED"
- + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE";
- for (i = 0; i < b.length; ++i) {
- b[i] = s.charAt(i) - 'E';
- }
- SIZE = b;
+ /**
+ * The abstract types that are initialized in the basic block. A constructor invocation on an
+ * UNINITIALIZED or UNINITIALIZED_THIS abstract type must replace every occurrence of this
+ * type in the local variables and in the operand stack. This cannot be done during the first step
+ * of the algorithm since, during this step, the local variables and the operand stack types are
+ * still abstract. It is therefore necessary to store the abstract types of the constructors which
+ * are invoked in the basic block, in order to do this replacement during the second step of the
+ * algorithm, where the frames are fully computed. Note that this array can contain abstract types
+ * that are relative to the input locals or to the input stack.
+ */
+ private int[] initializations;
- // code to generate the above string
- //
- // int NA = 0; // not applicable (unused opcode or variable size opcode)
- //
- // b = new int[] {
- // 0, //NOP, // visitInsn
- // 1, //ACONST_NULL, // -
- // 1, //ICONST_M1, // -
- // 1, //ICONST_0, // -
- // 1, //ICONST_1, // -
- // 1, //ICONST_2, // -
- // 1, //ICONST_3, // -
- // 1, //ICONST_4, // -
- // 1, //ICONST_5, // -
- // 2, //LCONST_0, // -
- // 2, //LCONST_1, // -
- // 1, //FCONST_0, // -
- // 1, //FCONST_1, // -
- // 1, //FCONST_2, // -
- // 2, //DCONST_0, // -
- // 2, //DCONST_1, // -
- // 1, //BIPUSH, // visitIntInsn
- // 1, //SIPUSH, // -
- // 1, //LDC, // visitLdcInsn
- // NA, //LDC_W, // -
- // NA, //LDC2_W, // -
- // 1, //ILOAD, // visitVarInsn
- // 2, //LLOAD, // -
- // 1, //FLOAD, // -
- // 2, //DLOAD, // -
- // 1, //ALOAD, // -
- // NA, //ILOAD_0, // -
- // NA, //ILOAD_1, // -
- // NA, //ILOAD_2, // -
- // NA, //ILOAD_3, // -
- // NA, //LLOAD_0, // -
- // NA, //LLOAD_1, // -
- // NA, //LLOAD_2, // -
- // NA, //LLOAD_3, // -
- // NA, //FLOAD_0, // -
- // NA, //FLOAD_1, // -
- // NA, //FLOAD_2, // -
- // NA, //FLOAD_3, // -
- // NA, //DLOAD_0, // -
- // NA, //DLOAD_1, // -
- // NA, //DLOAD_2, // -
- // NA, //DLOAD_3, // -
- // NA, //ALOAD_0, // -
- // NA, //ALOAD_1, // -
- // NA, //ALOAD_2, // -
- // NA, //ALOAD_3, // -
- // -1, //IALOAD, // visitInsn
- // 0, //LALOAD, // -
- // -1, //FALOAD, // -
- // 0, //DALOAD, // -
- // -1, //AALOAD, // -
- // -1, //BALOAD, // -
- // -1, //CALOAD, // -
- // -1, //SALOAD, // -
- // -1, //ISTORE, // visitVarInsn
- // -2, //LSTORE, // -
- // -1, //FSTORE, // -
- // -2, //DSTORE, // -
- // -1, //ASTORE, // -
- // NA, //ISTORE_0, // -
- // NA, //ISTORE_1, // -
- // NA, //ISTORE_2, // -
- // NA, //ISTORE_3, // -
- // NA, //LSTORE_0, // -
- // NA, //LSTORE_1, // -
- // NA, //LSTORE_2, // -
- // NA, //LSTORE_3, // -
- // NA, //FSTORE_0, // -
- // NA, //FSTORE_1, // -
- // NA, //FSTORE_2, // -
- // NA, //FSTORE_3, // -
- // NA, //DSTORE_0, // -
- // NA, //DSTORE_1, // -
- // NA, //DSTORE_2, // -
- // NA, //DSTORE_3, // -
- // NA, //ASTORE_0, // -
- // NA, //ASTORE_1, // -
- // NA, //ASTORE_2, // -
- // NA, //ASTORE_3, // -
- // -3, //IASTORE, // visitInsn
- // -4, //LASTORE, // -
- // -3, //FASTORE, // -
- // -4, //DASTORE, // -
- // -3, //AASTORE, // -
- // -3, //BASTORE, // -
- // -3, //CASTORE, // -
- // -3, //SASTORE, // -
- // -1, //POP, // -
- // -2, //POP2, // -
- // 1, //DUP, // -
- // 1, //DUP_X1, // -
- // 1, //DUP_X2, // -
- // 2, //DUP2, // -
- // 2, //DUP2_X1, // -
- // 2, //DUP2_X2, // -
- // 0, //SWAP, // -
- // -1, //IADD, // -
- // -2, //LADD, // -
- // -1, //FADD, // -
- // -2, //DADD, // -
- // -1, //ISUB, // -
- // -2, //LSUB, // -
- // -1, //FSUB, // -
- // -2, //DSUB, // -
- // -1, //IMUL, // -
- // -2, //LMUL, // -
- // -1, //FMUL, // -
- // -2, //DMUL, // -
- // -1, //IDIV, // -
- // -2, //LDIV, // -
- // -1, //FDIV, // -
- // -2, //DDIV, // -
- // -1, //IREM, // -
- // -2, //LREM, // -
- // -1, //FREM, // -
- // -2, //DREM, // -
- // 0, //INEG, // -
- // 0, //LNEG, // -
- // 0, //FNEG, // -
- // 0, //DNEG, // -
- // -1, //ISHL, // -
- // -1, //LSHL, // -
- // -1, //ISHR, // -
- // -1, //LSHR, // -
- // -1, //IUSHR, // -
- // -1, //LUSHR, // -
- // -1, //IAND, // -
- // -2, //LAND, // -
- // -1, //IOR, // -
- // -2, //LOR, // -
- // -1, //IXOR, // -
- // -2, //LXOR, // -
- // 0, //IINC, // visitIincInsn
- // 1, //I2L, // visitInsn
- // 0, //I2F, // -
- // 1, //I2D, // -
- // -1, //L2I, // -
- // -1, //L2F, // -
- // 0, //L2D, // -
- // 0, //F2I, // -
- // 1, //F2L, // -
- // 1, //F2D, // -
- // -1, //D2I, // -
- // 0, //D2L, // -
- // -1, //D2F, // -
- // 0, //I2B, // -
- // 0, //I2C, // -
- // 0, //I2S, // -
- // -3, //LCMP, // -
- // -1, //FCMPL, // -
- // -1, //FCMPG, // -
- // -3, //DCMPL, // -
- // -3, //DCMPG, // -
- // -1, //IFEQ, // visitJumpInsn
- // -1, //IFNE, // -
- // -1, //IFLT, // -
- // -1, //IFGE, // -
- // -1, //IFGT, // -
- // -1, //IFLE, // -
- // -2, //IF_ICMPEQ, // -
- // -2, //IF_ICMPNE, // -
- // -2, //IF_ICMPLT, // -
- // -2, //IF_ICMPGE, // -
- // -2, //IF_ICMPGT, // -
- // -2, //IF_ICMPLE, // -
- // -2, //IF_ACMPEQ, // -
- // -2, //IF_ACMPNE, // -
- // 0, //GOTO, // -
- // 1, //JSR, // -
- // 0, //RET, // visitVarInsn
- // -1, //TABLESWITCH, // visiTableSwitchInsn
- // -1, //LOOKUPSWITCH, // visitLookupSwitch
- // -1, //IRETURN, // visitInsn
- // -2, //LRETURN, // -
- // -1, //FRETURN, // -
- // -2, //DRETURN, // -
- // -1, //ARETURN, // -
- // 0, //RETURN, // -
- // NA, //GETSTATIC, // visitFieldInsn
- // NA, //PUTSTATIC, // -
- // NA, //GETFIELD, // -
- // NA, //PUTFIELD, // -
- // NA, //INVOKEVIRTUAL, // visitMethodInsn
- // NA, //INVOKESPECIAL, // -
- // NA, //INVOKESTATIC, // -
- // NA, //INVOKEINTERFACE, // -
- // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn
- // 1, //NEW, // visitTypeInsn
- // 0, //NEWARRAY, // visitIntInsn
- // 0, //ANEWARRAY, // visitTypeInsn
- // 0, //ARRAYLENGTH, // visitInsn
- // NA, //ATHROW, // -
- // 0, //CHECKCAST, // visitTypeInsn
- // 0, //INSTANCEOF, // -
- // -1, //MONITORENTER, // visitInsn
- // -1, //MONITOREXIT, // -
- // NA, //WIDE, // NOT VISITED
- // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn
- // -1, //IFNULL, // visitJumpInsn
- // -1, //IFNONNULL, // -
- // NA, //GOTO_W, // -
- // NA, //JSR_W, // -
- // };
- // for (i = 0; i < b.length; ++i) {
- // System.err.print((char)('E' + b[i]));
- // }
- // System.err.println();
- }
+ // -----------------------------------------------------------------------------------------------
+ // Constructor
+ // -----------------------------------------------------------------------------------------------
- /**
- * The label (i.e. basic block) to which these input and output stack map
- * frames correspond.
- */
- Label owner;
+ /**
+ * Constructs a new Frame.
+ *
+ * @param owner the basic block to which these input and output stack map frames correspond.
+ */
+ Frame(final Label owner) {
+ this.owner = owner;
+ }
- /**
- * The input stack map frame locals.
- */
- int[] inputLocals;
+ /**
+ * Sets this frame to the value of the given frame.
+ *
+ *
WARNING: after this method is called the two frames share the same data structures. It is
+ * recommended to discard the given frame to avoid unexpected side effects.
+ *
+ * @param frame The new frame value.
+ */
+ final void copyFrom(final Frame frame) {
+ inputLocals = frame.inputLocals;
+ inputStack = frame.inputStack;
+ outputStackStart = 0;
+ outputLocals = frame.outputLocals;
+ outputStack = frame.outputStack;
+ outputStackTop = frame.outputStackTop;
+ initializationCount = frame.initializationCount;
+ initializations = frame.initializations;
+ }
- /**
- * The input stack map frame stack.
- */
- int[] inputStack;
+ // -----------------------------------------------------------------------------------------------
+ // Static methods to get abstract types from other type formats
+ // -----------------------------------------------------------------------------------------------
- /**
- * The output stack map frame locals.
- */
- private int[] outputLocals;
-
- /**
- * The output stack map frame stack.
- */
- private int[] outputStack;
+ /**
+ * Returns the abstract type corresponding to the given public API frame element type.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param type a frame element type described using the same format as in {@link
+ * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
+ * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or
+ * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating
+ * a NEW instruction (for uninitialized types).
+ * @return the abstract type corresponding to the given frame element type.
+ */
+ static int getAbstractTypeFromApiFormat(final SymbolTable symbolTable, final Object type) {
+ if (type instanceof Integer) {
+ return CONSTANT_KIND | ((Integer) type).intValue();
+ } else if (type instanceof String) {
+ String descriptor = Type.getObjectType((String) type).getDescriptor();
+ return getAbstractTypeFromDescriptor(symbolTable, descriptor, 0);
+ } else {
+ return UNINITIALIZED_KIND
+ | symbolTable.addUninitializedType("", ((Label) type).bytecodeOffset);
+ }
+ }
- /**
- * Relative size of the output stack. The exact semantics of this field
- * depends on the algorithm that is used.
- *
- * When only the maximum stack size is computed, this field is the size of
- * the output stack relatively to the top of the input stack.
- *
- * When the stack map frames are completely computed, this field is the
- * actual number of types in {@link #outputStack}.
- */
- private int outputStackTop;
+ /**
+ * Returns the abstract type corresponding to the internal name of a class.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param internalName the internal name of a class. This must not be an array type
+ * descriptor.
+ * @return the abstract type value corresponding to the given internal name.
+ */
+ static int getAbstractTypeFromInternalName(
+ final SymbolTable symbolTable, final String internalName) {
+ return REFERENCE_KIND | symbolTable.addType(internalName);
+ }
- /**
- * Number of types that are initialized in the basic block.
- *
- * @see #initializations
- */
- private int initializationCount;
+ /**
+ * Returns the abstract type corresponding to the given type descriptor.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param buffer a string ending with a type descriptor.
+ * @param offset the start offset of the type descriptor in buffer.
+ * @return the abstract type corresponding to the given type descriptor.
+ */
+ private static int getAbstractTypeFromDescriptor(
+ final SymbolTable symbolTable, final String buffer, final int offset) {
+ String internalName;
+ switch (buffer.charAt(offset)) {
+ case 'V':
+ return 0;
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ return INTEGER;
+ case 'F':
+ return FLOAT;
+ case 'J':
+ return LONG;
+ case 'D':
+ return DOUBLE;
+ case 'L':
+ internalName = buffer.substring(offset + 1, buffer.length() - 1);
+ return REFERENCE_KIND | symbolTable.addType(internalName);
+ case '[':
+ int elementDescriptorOffset = offset + 1;
+ while (buffer.charAt(elementDescriptorOffset) == '[') {
+ ++elementDescriptorOffset;
+ }
+ int typeValue;
+ switch (buffer.charAt(elementDescriptorOffset)) {
+ case 'Z':
+ typeValue = BOOLEAN;
+ break;
+ case 'C':
+ typeValue = CHAR;
+ break;
+ case 'B':
+ typeValue = BYTE;
+ break;
+ case 'S':
+ typeValue = SHORT;
+ break;
+ case 'I':
+ typeValue = INTEGER;
+ break;
+ case 'F':
+ typeValue = FLOAT;
+ break;
+ case 'J':
+ typeValue = LONG;
+ break;
+ case 'D':
+ typeValue = DOUBLE;
+ break;
+ case 'L':
+ internalName = buffer.substring(elementDescriptorOffset + 1, buffer.length() - 1);
+ typeValue = REFERENCE_KIND | symbolTable.addType(internalName);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
- /**
- * The types that are initialized in the basic block. A constructor
- * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace
- * every occurence of this type in the local variables and in the
- * operand stack. This cannot be done during the first phase of the
- * algorithm since, during this phase, the local variables and the operand
- * stack are not completely computed. It is therefore necessary to store the
- * types on which constructors are invoked in the basic block, in order to
- * do this replacement during the second phase of the algorithm, where the
- * frames are fully computed. Note that this array can contain types that
- * are relative to input locals or to the input stack (see below for the
- * description of the algorithm).
- */
- private int[] initializations;
+ // -----------------------------------------------------------------------------------------------
+ // Methods related to the input frame
+ // -----------------------------------------------------------------------------------------------
- /**
- * Returns the output frame local variable type at the given index.
- *
- * @param local
- * the index of the local that must be returned.
- * @return the output frame local variable type at the given index.
- */
- private int get(final int local) {
- if (outputLocals == null || local >= outputLocals.length) {
- // this local has never been assigned in this basic block,
- // so it is still equal to its value in the input frame
- return LOCAL | local;
- } else {
- int type = outputLocals[local];
- if (type == 0) {
- // this local has never been assigned in this basic block,
- // so it is still equal to its value in the input frame
- type = outputLocals[local] = LOCAL | local;
- }
- return type;
- }
+ /**
+ * Sets the input frame from the given method description. This method is used to initialize the
+ * first frame of a method, which is implicit (i.e. not stored explicitly in the StackMapTable
+ * attribute).
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param access the method's access flags.
+ * @param descriptor the method descriptor.
+ * @param maxLocals the maximum number of local variables of the method.
+ */
+ final void setInputFrameFromDescriptor(
+ final SymbolTable symbolTable,
+ final int access,
+ final String descriptor,
+ final int maxLocals) {
+ inputLocals = new int[maxLocals];
+ inputStack = new int[0];
+ int inputLocalIndex = 0;
+ if ((access & Opcodes.ACC_STATIC) == 0) {
+ if ((access & Constants.ACC_CONSTRUCTOR) == 0) {
+ inputLocals[inputLocalIndex++] =
+ REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName());
+ } else {
+ inputLocals[inputLocalIndex++] = UNINITIALIZED_THIS;
+ }
+ }
+ for (Type argumentType : Type.getArgumentTypes(descriptor)) {
+ int abstractType =
+ getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0);
+ inputLocals[inputLocalIndex++] = abstractType;
+ if (abstractType == LONG || abstractType == DOUBLE) {
+ inputLocals[inputLocalIndex++] = TOP;
+ }
}
+ while (inputLocalIndex < maxLocals) {
+ inputLocals[inputLocalIndex++] = TOP;
+ }
+ }
- /**
- * Sets the output frame local variable type at the given index.
- *
- * @param local
- * the index of the local that must be set.
- * @param type
- * the value of the local that must be set.
- */
- private void set(final int local, final int type) {
- // creates and/or resizes the output local variables array if necessary
- if (outputLocals == null) {
- outputLocals = new int[10];
- }
- int n = outputLocals.length;
- if (local >= n) {
- int[] t = new int[Math.max(local + 1, 2 * n)];
- System.arraycopy(outputLocals, 0, t, 0, n);
- outputLocals = t;
- }
- // sets the local variable
- outputLocals[local] = type;
+ /**
+ * Sets the input frame from the given public API frame description.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param numLocal the number of local variables.
+ * @param local the local variable types, described using the same format as in {@link
+ * MethodVisitor#visitFrame}.
+ * @param numStack the number of operand stack elements.
+ * @param stack the operand stack types, described using the same format as in {@link
+ * MethodVisitor#visitFrame}.
+ */
+ final void setInputFrameFromApiFormat(
+ final SymbolTable symbolTable,
+ final int numLocal,
+ final Object[] local,
+ final int numStack,
+ final Object[] stack) {
+ int inputLocalIndex = 0;
+ for (int i = 0; i < numLocal; ++i) {
+ inputLocals[inputLocalIndex++] = getAbstractTypeFromApiFormat(symbolTable, local[i]);
+ if (local[i] == Opcodes.LONG || local[i] == Opcodes.DOUBLE) {
+ inputLocals[inputLocalIndex++] = TOP;
+ }
+ }
+ while (inputLocalIndex < inputLocals.length) {
+ inputLocals[inputLocalIndex++] = TOP;
+ }
+ int numStackTop = 0;
+ for (int i = 0; i < numStack; ++i) {
+ if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
+ ++numStackTop;
+ }
}
+ inputStack = new int[numStack + numStackTop];
+ int inputStackIndex = 0;
+ for (int i = 0; i < numStack; ++i) {
+ inputStack[inputStackIndex++] = getAbstractTypeFromApiFormat(symbolTable, stack[i]);
+ if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
+ inputStack[inputStackIndex++] = TOP;
+ }
+ }
+ outputStackTop = 0;
+ initializationCount = 0;
+ }
- /**
- * Pushes a new type onto the output frame stack.
- *
- * @param type
- * the type that must be pushed.
- */
- private void push(final int type) {
- // creates and/or resizes the output stack array if necessary
- if (outputStack == null) {
- outputStack = new int[10];
- }
- int n = outputStack.length;
- if (outputStackTop >= n) {
- int[] t = new int[Math.max(outputStackTop + 1, 2 * n)];
- System.arraycopy(outputStack, 0, t, 0, n);
- outputStack = t;
- }
- // pushes the type on the output stack
- outputStack[outputStackTop++] = type;
- // updates the maximun height reached by the output stack, if needed
- int top = owner.inputStackTop + outputStackTop;
- if (top > owner.outputStackMax) {
- owner.outputStackMax = top;
- }
+ final int getInputStackSize() {
+ return inputStack.length;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods related to the output frame
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the abstract type stored at the given local variable index in the output frame.
+ *
+ * @param localIndex the index of the local variable whose value must be returned.
+ * @return the abstract type stored at the given local variable index in the output frame.
+ */
+ private int getLocal(final int localIndex) {
+ if (outputLocals == null || localIndex >= outputLocals.length) {
+ // If this local has never been assigned in this basic block, it is still equal to its value
+ // in the input frame.
+ return LOCAL_KIND | localIndex;
+ } else {
+ int abstractType = outputLocals[localIndex];
+ if (abstractType == 0) {
+ // If this local has never been assigned in this basic block, so it is still equal to its
+ // value in the input frame.
+ abstractType = outputLocals[localIndex] = LOCAL_KIND | localIndex;
+ }
+ return abstractType;
}
+ }
- /**
- * Pushes a new type onto the output frame stack.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param desc
- * the descriptor of the type to be pushed. Can also be a method
- * descriptor (in this case this method pushes its return type
- * onto the output frame stack).
- */
- private void push(final ClassWriter cw, final String desc) {
- int type = type(cw, desc);
- if (type != 0) {
- push(type);
- if (type == LONG || type == DOUBLE) {
- push(TOP);
- }
- }
+ /**
+ * Replaces the abstract type stored at the given local variable index in the output frame.
+ *
+ * @param localIndex the index of the output frame local variable that must be set.
+ * @param abstractType the value that must be set.
+ */
+ private void setLocal(final int localIndex, final int abstractType) {
+ // Create and/or resize the output local variables array if necessary.
+ if (outputLocals == null) {
+ outputLocals = new int[10];
+ }
+ int outputLocalsLength = outputLocals.length;
+ if (localIndex >= outputLocalsLength) {
+ int[] newOutputLocals = new int[Math.max(localIndex + 1, 2 * outputLocalsLength)];
+ System.arraycopy(outputLocals, 0, newOutputLocals, 0, outputLocalsLength);
+ outputLocals = newOutputLocals;
}
+ // Set the local variable.
+ outputLocals[localIndex] = abstractType;
+ }
- /**
- * Returns the int encoding of the given type.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param desc
- * a type descriptor.
- * @return the int encoding of the given type.
- */
- private static int type(final ClassWriter cw, final String desc) {
- String t;
- int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0;
- switch (desc.charAt(index)) {
- case 'V':
- return 0;
- case 'Z':
- case 'C':
- case 'B':
- case 'S':
- case 'I':
- return INTEGER;
- case 'F':
- return FLOAT;
- case 'J':
- return LONG;
- case 'D':
- return DOUBLE;
- case 'L':
- // stores the internal name, not the descriptor!
- t = desc.substring(index + 1, desc.length() - 1);
- return OBJECT | cw.addType(t);
- // case '[':
- default:
- // extracts the dimensions and the element type
- int data;
- int dims = index + 1;
- while (desc.charAt(dims) == '[') {
- ++dims;
- }
- switch (desc.charAt(dims)) {
- case 'Z':
- data = BOOLEAN;
- break;
- case 'C':
- data = CHAR;
- break;
- case 'B':
- data = BYTE;
- break;
- case 'S':
- data = SHORT;
- break;
- case 'I':
- data = INTEGER;
- break;
- case 'F':
- data = FLOAT;
- break;
- case 'J':
- data = LONG;
- break;
- case 'D':
- data = DOUBLE;
- break;
- // case 'L':
- default:
- // stores the internal name, not the descriptor
- t = desc.substring(dims + 1, desc.length() - 1);
- data = OBJECT | cw.addType(t);
- }
- return (dims - index) << 28 | data;
- }
+ /**
+ * Pushes the given abstract type on the output frame stack.
+ *
+ * @param abstractType an abstract type.
+ */
+ private void push(final int abstractType) {
+ // Create and/or resize the output stack array if necessary.
+ if (outputStack == null) {
+ outputStack = new int[10];
+ }
+ int outputStackLength = outputStack.length;
+ if (outputStackTop >= outputStackLength) {
+ int[] newOutputStack = new int[Math.max(outputStackTop + 1, 2 * outputStackLength)];
+ System.arraycopy(outputStack, 0, newOutputStack, 0, outputStackLength);
+ outputStack = newOutputStack;
}
+ // Pushes the abstract type on the output stack.
+ outputStack[outputStackTop++] = abstractType;
+ // Updates the maximum size reached by the output stack, if needed (note that this size is
+ // relative to the input stack size, which is not known yet).
+ short outputStackSize = (short) (outputStackStart + outputStackTop);
+ if (outputStackSize > owner.outputStackMax) {
+ owner.outputStackMax = outputStackSize;
+ }
+ }
- /**
- * Pops a type from the output frame stack and returns its value.
- *
- * @return the type that has been popped from the output frame stack.
- */
- private int pop() {
- if (outputStackTop > 0) {
- return outputStack[--outputStackTop];
- } else {
- // if the output frame stack is empty, pops from the input stack
- return STACK | -(--owner.inputStackTop);
- }
+ /**
+ * Pushes the abstract type corresponding to the given descriptor on the output frame stack.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param descriptor a type or method descriptor (in which case its return type is pushed).
+ */
+ private void push(final SymbolTable symbolTable, final String descriptor) {
+ int typeDescriptorOffset =
+ descriptor.charAt(0) == '(' ? Type.getReturnTypeOffset(descriptor) : 0;
+ int abstractType = getAbstractTypeFromDescriptor(symbolTable, descriptor, typeDescriptorOffset);
+ if (abstractType != 0) {
+ push(abstractType);
+ if (abstractType == LONG || abstractType == DOUBLE) {
+ push(TOP);
+ }
}
+ }
- /**
- * Pops the given number of types from the output frame stack.
- *
- * @param elements
- * the number of types that must be popped.
- */
- private void pop(final int elements) {
- if (outputStackTop >= elements) {
- outputStackTop -= elements;
- } else {
- // if the number of elements to be popped is greater than the number
- // of elements in the output stack, clear it, and pops the remaining
- // elements from the input stack.
- owner.inputStackTop -= elements - outputStackTop;
- outputStackTop = 0;
- }
+ /**
+ * Pops an abstract type from the output frame stack and returns its value.
+ *
+ * @return the abstract type that has been popped from the output frame stack.
+ */
+ private int pop() {
+ if (outputStackTop > 0) {
+ return outputStack[--outputStackTop];
+ } else {
+ // If the output frame stack is empty, pop from the input stack.
+ return STACK_KIND | -(--outputStackStart);
}
+ }
- /**
- * Pops a type from the output frame stack.
- *
- * @param desc
- * the descriptor of the type to be popped. Can also be a method
- * descriptor (in this case this method pops the types
- * corresponding to the method arguments).
- */
- private void pop(final String desc) {
- char c = desc.charAt(0);
- if (c == '(') {
- pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1);
- } else if (c == 'J' || c == 'D') {
- pop(2);
- } else {
- pop(1);
- }
+ /**
+ * Pops the given number of abstract types from the output frame stack.
+ *
+ * @param elements the number of abstract types that must be popped.
+ */
+ private void pop(final int elements) {
+ if (outputStackTop >= elements) {
+ outputStackTop -= elements;
+ } else {
+ // If the number of elements to be popped is greater than the number of elements in the output
+ // stack, clear it, and pop the remaining elements from the input stack.
+ outputStackStart -= elements - outputStackTop;
+ outputStackTop = 0;
}
+ }
- /**
- * Adds a new type to the list of types on which a constructor is invoked in
- * the basic block.
- *
- * @param var
- * a type on a which a constructor is invoked.
- */
- private void init(final int var) {
- // creates and/or resizes the initializations array if necessary
- if (initializations == null) {
- initializations = new int[2];
- }
- int n = initializations.length;
- if (initializationCount >= n) {
- int[] t = new int[Math.max(initializationCount + 1, 2 * n)];
- System.arraycopy(initializations, 0, t, 0, n);
- initializations = t;
- }
- // stores the type to be initialized
- initializations[initializationCount++] = var;
+ /**
+ * Pops as many abstract types from the output frame stack as described by the given descriptor.
+ *
+ * @param descriptor a type or method descriptor (in which case its argument types are popped).
+ */
+ private void pop(final String descriptor) {
+ char firstDescriptorChar = descriptor.charAt(0);
+ if (firstDescriptorChar == '(') {
+ pop((Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1);
+ } else if (firstDescriptorChar == 'J' || firstDescriptorChar == 'D') {
+ pop(2);
+ } else {
+ pop(1);
}
+ }
- /**
- * Replaces the given type with the appropriate type if it is one of the
- * types on which a constructor is invoked in the basic block.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param t
- * a type
- * @return t or, if t is one of the types on which a constructor is invoked
- * in the basic block, the type corresponding to this constructor.
- */
- private int init(final ClassWriter cw, final int t) {
- int s;
- if (t == UNINITIALIZED_THIS) {
- s = OBJECT | cw.addType(cw.thisName);
- } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) {
- String type = cw.typeTable[t & BASE_VALUE].strVal1;
- s = OBJECT | cw.addType(type);
- } else {
- return t;
- }
- for (int j = 0; j < initializationCount; ++j) {
- int u = initializations[j];
- int dim = u & DIM;
- int kind = u & KIND;
- if (kind == LOCAL) {
- u = dim + inputLocals[u & VALUE];
- } else if (kind == STACK) {
- u = dim + inputStack[inputStack.length - (u & VALUE)];
- }
- if (t == u) {
- return s;
- }
- }
- return t;
+ // -----------------------------------------------------------------------------------------------
+ // Methods to handle uninitialized types
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds an abstract type to the list of types on which a constructor is invoked in the basic
+ * block.
+ *
+ * @param abstractType an abstract type on a which a constructor is invoked.
+ */
+ private void addInitializedType(final int abstractType) {
+ // Create and/or resize the initializations array if necessary.
+ if (initializations == null) {
+ initializations = new int[2];
}
+ int initializationsLength = initializations.length;
+ if (initializationCount >= initializationsLength) {
+ int[] newInitializations =
+ new int[Math.max(initializationCount + 1, 2 * initializationsLength)];
+ System.arraycopy(initializations, 0, newInitializations, 0, initializationsLength);
+ initializations = newInitializations;
+ }
+ // Store the abstract type.
+ initializations[initializationCount++] = abstractType;
+ }
- /**
- * Initializes the input frame of the first basic block from the method
- * descriptor.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param access
- * the access flags of the method to which this label belongs.
- * @param args
- * the formal parameter types of this method.
- * @param maxLocals
- * the maximum number of local variables of this method.
- */
- void initInputFrame(final ClassWriter cw, final int access,
- final Type[] args, final int maxLocals) {
- inputLocals = new int[maxLocals];
- inputStack = new int[0];
- int i = 0;
- if ((access & Opcodes.ACC_STATIC) == 0) {
- if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) {
- inputLocals[i++] = OBJECT | cw.addType(cw.thisName);
- } else {
- inputLocals[i++] = UNINITIALIZED_THIS;
- }
- }
- for (int j = 0; j < args.length; ++j) {
- int t = type(cw, args[j].getDescriptor());
- inputLocals[i++] = t;
- if (t == LONG || t == DOUBLE) {
- inputLocals[i++] = TOP;
- }
+ /**
+ * Returns the "initialized" abstract type corresponding to the given abstract type.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param abstractType an abstract type.
+ * @return the REFERENCE_KIND abstract type corresponding to abstractType if it is
+ * UNINITIALIZED_THIS or an UNINITIALIZED_KIND abstract type for one of the types on which a
+ * constructor is invoked in the basic block. Otherwise returns abstractType.
+ */
+ private int getInitializedType(final SymbolTable symbolTable, final int abstractType) {
+ if (abstractType == UNINITIALIZED_THIS
+ || (abstractType & (DIM_MASK | KIND_MASK)) == UNINITIALIZED_KIND) {
+ for (int i = 0; i < initializationCount; ++i) {
+ int initializedType = initializations[i];
+ int dim = initializedType & DIM_MASK;
+ int kind = initializedType & KIND_MASK;
+ int value = initializedType & VALUE_MASK;
+ if (kind == LOCAL_KIND) {
+ initializedType = dim + inputLocals[value];
+ } else if (kind == STACK_KIND) {
+ initializedType = dim + inputStack[inputStack.length - value];
}
- while (i < maxLocals) {
- inputLocals[i++] = TOP;
+ if (abstractType == initializedType) {
+ if (abstractType == UNINITIALIZED_THIS) {
+ return REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName());
+ } else {
+ return REFERENCE_KIND
+ | symbolTable.addType(symbolTable.getType(abstractType & VALUE_MASK).value);
+ }
}
+ }
}
+ return abstractType;
+ }
- /**
- * Simulates the action of the given instruction on the output stack frame.
- *
- * @param opcode
- * the opcode of the instruction.
- * @param arg
- * the operand of the instruction, if any.
- * @param cw
- * the class writer to which this label belongs.
- * @param item
- * the operand of the instructions, if any.
- */
- void execute(final int opcode, final int arg, final ClassWriter cw,
- final Item item) {
- int t1, t2, t3, t4;
- switch (opcode) {
- case Opcodes.NOP:
- case Opcodes.INEG:
- case Opcodes.LNEG:
- case Opcodes.FNEG:
- case Opcodes.DNEG:
- case Opcodes.I2B:
- case Opcodes.I2C:
- case Opcodes.I2S:
- case Opcodes.GOTO:
- case Opcodes.RETURN:
- break;
- case Opcodes.ACONST_NULL:
- push(NULL);
- break;
- case Opcodes.ICONST_M1:
- case Opcodes.ICONST_0:
- case Opcodes.ICONST_1:
- case Opcodes.ICONST_2:
- case Opcodes.ICONST_3:
- case Opcodes.ICONST_4:
- case Opcodes.ICONST_5:
- case Opcodes.BIPUSH:
- case Opcodes.SIPUSH:
- case Opcodes.ILOAD:
- push(INTEGER);
- break;
- case Opcodes.LCONST_0:
- case Opcodes.LCONST_1:
- case Opcodes.LLOAD:
- push(LONG);
- push(TOP);
- break;
- case Opcodes.FCONST_0:
- case Opcodes.FCONST_1:
- case Opcodes.FCONST_2:
- case Opcodes.FLOAD:
- push(FLOAT);
- break;
- case Opcodes.DCONST_0:
- case Opcodes.DCONST_1:
- case Opcodes.DLOAD:
- push(DOUBLE);
- push(TOP);
- break;
- case Opcodes.LDC:
- switch (item.type) {
- case ClassWriter.INT:
- push(INTEGER);
- break;
- case ClassWriter.LONG:
- push(LONG);
- push(TOP);
- break;
- case ClassWriter.FLOAT:
- push(FLOAT);
- break;
- case ClassWriter.DOUBLE:
- push(DOUBLE);
- push(TOP);
- break;
- case ClassWriter.CLASS:
- push(OBJECT | cw.addType("java/lang/Class"));
- break;
- case ClassWriter.STR:
- push(OBJECT | cw.addType("java/lang/String"));
- break;
- case ClassWriter.MTYPE:
- push(OBJECT | cw.addType("java/lang/invoke/MethodType"));
- break;
- // case ClassWriter.HANDLE_BASE + [1..9]:
- default:
- push(OBJECT | cw.addType("java/lang/invoke/MethodHandle"));
- }
- break;
- case Opcodes.ALOAD:
- push(get(arg));
- break;
- case Opcodes.IALOAD:
- case Opcodes.BALOAD:
- case Opcodes.CALOAD:
- case Opcodes.SALOAD:
- pop(2);
- push(INTEGER);
- break;
- case Opcodes.LALOAD:
- case Opcodes.D2L:
- pop(2);
- push(LONG);
- push(TOP);
- break;
- case Opcodes.FALOAD:
- pop(2);
- push(FLOAT);
- break;
- case Opcodes.DALOAD:
- case Opcodes.L2D:
- pop(2);
- push(DOUBLE);
- push(TOP);
- break;
- case Opcodes.AALOAD:
- pop(1);
- t1 = pop();
- push(ELEMENT_OF + t1);
- break;
- case Opcodes.ISTORE:
- case Opcodes.FSTORE:
- case Opcodes.ASTORE:
- t1 = pop();
- set(arg, t1);
- if (arg > 0) {
- t2 = get(arg - 1);
- // if t2 is of kind STACK or LOCAL we cannot know its size!
- if (t2 == LONG || t2 == DOUBLE) {
- set(arg - 1, TOP);
- } else if ((t2 & KIND) != BASE) {
- set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE);
- }
- }
- break;
- case Opcodes.LSTORE:
- case Opcodes.DSTORE:
- pop(1);
- t1 = pop();
- set(arg, t1);
- set(arg + 1, TOP);
- if (arg > 0) {
- t2 = get(arg - 1);
- // if t2 is of kind STACK or LOCAL we cannot know its size!
- if (t2 == LONG || t2 == DOUBLE) {
- set(arg - 1, TOP);
- } else if ((t2 & KIND) != BASE) {
- set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE);
- }
- }
- break;
- case Opcodes.IASTORE:
- case Opcodes.BASTORE:
- case Opcodes.CASTORE:
- case Opcodes.SASTORE:
- case Opcodes.FASTORE:
- case Opcodes.AASTORE:
- pop(3);
- break;
- case Opcodes.LASTORE:
- case Opcodes.DASTORE:
- pop(4);
- break;
- case Opcodes.POP:
- case Opcodes.IFEQ:
- case Opcodes.IFNE:
- case Opcodes.IFLT:
- case Opcodes.IFGE:
- case Opcodes.IFGT:
- case Opcodes.IFLE:
- case Opcodes.IRETURN:
- case Opcodes.FRETURN:
- case Opcodes.ARETURN:
- case Opcodes.TABLESWITCH:
- case Opcodes.LOOKUPSWITCH:
- case Opcodes.ATHROW:
- case Opcodes.MONITORENTER:
- case Opcodes.MONITOREXIT:
- case Opcodes.IFNULL:
- case Opcodes.IFNONNULL:
- pop(1);
- break;
- case Opcodes.POP2:
- case Opcodes.IF_ICMPEQ:
- case Opcodes.IF_ICMPNE:
- case Opcodes.IF_ICMPLT:
- case Opcodes.IF_ICMPGE:
- case Opcodes.IF_ICMPGT:
- case Opcodes.IF_ICMPLE:
- case Opcodes.IF_ACMPEQ:
- case Opcodes.IF_ACMPNE:
- case Opcodes.LRETURN:
- case Opcodes.DRETURN:
- pop(2);
- break;
- case Opcodes.DUP:
- t1 = pop();
- push(t1);
- push(t1);
- break;
- case Opcodes.DUP_X1:
- t1 = pop();
- t2 = pop();
- push(t1);
- push(t2);
- push(t1);
- break;
- case Opcodes.DUP_X2:
- t1 = pop();
- t2 = pop();
- t3 = pop();
- push(t1);
- push(t3);
- push(t2);
- push(t1);
- break;
- case Opcodes.DUP2:
- t1 = pop();
- t2 = pop();
- push(t2);
- push(t1);
- push(t2);
- push(t1);
- break;
- case Opcodes.DUP2_X1:
- t1 = pop();
- t2 = pop();
- t3 = pop();
- push(t2);
- push(t1);
- push(t3);
- push(t2);
- push(t1);
- break;
- case Opcodes.DUP2_X2:
- t1 = pop();
- t2 = pop();
- t3 = pop();
- t4 = pop();
- push(t2);
- push(t1);
- push(t4);
- push(t3);
- push(t2);
- push(t1);
- break;
- case Opcodes.SWAP:
- t1 = pop();
- t2 = pop();
- push(t1);
- push(t2);
- break;
- case Opcodes.IADD:
- case Opcodes.ISUB:
- case Opcodes.IMUL:
- case Opcodes.IDIV:
- case Opcodes.IREM:
- case Opcodes.IAND:
- case Opcodes.IOR:
- case Opcodes.IXOR:
- case Opcodes.ISHL:
- case Opcodes.ISHR:
- case Opcodes.IUSHR:
- case Opcodes.L2I:
- case Opcodes.D2I:
- case Opcodes.FCMPL:
- case Opcodes.FCMPG:
- pop(2);
+ // -----------------------------------------------------------------------------------------------
+ // Main method, to simulate the execution of each instruction on the output frame
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Simulates the action of the given instruction on the output stack frame.
+ *
+ * @param opcode the opcode of the instruction.
+ * @param arg the numeric operand of the instruction, if any.
+ * @param argSymbol the Symbol operand of the instruction, if any.
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ */
+ void execute(
+ final int opcode, final int arg, final Symbol argSymbol, final SymbolTable symbolTable) {
+ // Abstract types popped from the stack or read from local variables.
+ int abstractType1;
+ int abstractType2;
+ int abstractType3;
+ int abstractType4;
+ switch (opcode) {
+ case Opcodes.NOP:
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ case Opcodes.GOTO:
+ case Opcodes.RETURN:
+ break;
+ case Opcodes.ACONST_NULL:
+ push(NULL);
+ break;
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ case Opcodes.BIPUSH:
+ case Opcodes.SIPUSH:
+ case Opcodes.ILOAD:
+ push(INTEGER);
+ break;
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ case Opcodes.LLOAD:
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ case Opcodes.FLOAD:
+ push(FLOAT);
+ break;
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ case Opcodes.DLOAD:
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.LDC:
+ switch (argSymbol.tag) {
+ case Symbol.CONSTANT_INTEGER_TAG:
push(INTEGER);
break;
- case Opcodes.LADD:
- case Opcodes.LSUB:
- case Opcodes.LMUL:
- case Opcodes.LDIV:
- case Opcodes.LREM:
- case Opcodes.LAND:
- case Opcodes.LOR:
- case Opcodes.LXOR:
- pop(4);
+ case Symbol.CONSTANT_LONG_TAG:
push(LONG);
push(TOP);
break;
- case Opcodes.FADD:
- case Opcodes.FSUB:
- case Opcodes.FMUL:
- case Opcodes.FDIV:
- case Opcodes.FREM:
- case Opcodes.L2F:
- case Opcodes.D2F:
- pop(2);
+ case Symbol.CONSTANT_FLOAT_TAG:
push(FLOAT);
break;
- case Opcodes.DADD:
- case Opcodes.DSUB:
- case Opcodes.DMUL:
- case Opcodes.DDIV:
- case Opcodes.DREM:
- pop(4);
+ case Symbol.CONSTANT_DOUBLE_TAG:
push(DOUBLE);
push(TOP);
break;
- case Opcodes.LSHL:
- case Opcodes.LSHR:
- case Opcodes.LUSHR:
- pop(3);
- push(LONG);
- push(TOP);
+ case Symbol.CONSTANT_CLASS_TAG:
+ push(REFERENCE_KIND | symbolTable.addType("java/lang/Class"));
break;
- case Opcodes.IINC:
- set(arg, INTEGER);
+ case Symbol.CONSTANT_STRING_TAG:
+ push(REFERENCE_KIND | symbolTable.addType("java/lang/String"));
break;
- case Opcodes.I2L:
- case Opcodes.F2L:
- pop(1);
- push(LONG);
- push(TOP);
- break;
- case Opcodes.I2F:
- pop(1);
- push(FLOAT);
- break;
- case Opcodes.I2D:
- case Opcodes.F2D:
- pop(1);
- push(DOUBLE);
- push(TOP);
- break;
- case Opcodes.F2I:
- case Opcodes.ARRAYLENGTH:
- case Opcodes.INSTANCEOF:
- pop(1);
- push(INTEGER);
- break;
- case Opcodes.LCMP:
- case Opcodes.DCMPL:
- case Opcodes.DCMPG:
- pop(4);
- push(INTEGER);
- break;
- case Opcodes.JSR:
- case Opcodes.RET:
- throw new RuntimeException(
- "JSR/RET are not supported with computeFrames option");
- case Opcodes.GETSTATIC:
- push(cw, item.strVal3);
+ case Symbol.CONSTANT_METHOD_TYPE_TAG:
+ push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodType"));
break;
- case Opcodes.PUTSTATIC:
- pop(item.strVal3);
+ case Symbol.CONSTANT_METHOD_HANDLE_TAG:
+ push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodHandle"));
break;
- case Opcodes.GETFIELD:
- pop(1);
- push(cw, item.strVal3);
+ case Symbol.CONSTANT_DYNAMIC_TAG:
+ push(symbolTable, argSymbol.value);
break;
- case Opcodes.PUTFIELD:
- pop(item.strVal3);
- pop();
+ default:
+ throw new AssertionError();
+ }
+ break;
+ case Opcodes.ALOAD:
+ push(getLocal(arg));
+ break;
+ case Opcodes.LALOAD:
+ case Opcodes.D2L:
+ pop(2);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.DALOAD:
+ case Opcodes.L2D:
+ pop(2);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.AALOAD:
+ pop(1);
+ abstractType1 = pop();
+ push(abstractType1 == NULL ? abstractType1 : ELEMENT_OF + abstractType1);
+ break;
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.ASTORE:
+ abstractType1 = pop();
+ setLocal(arg, abstractType1);
+ if (arg > 0) {
+ int previousLocalType = getLocal(arg - 1);
+ if (previousLocalType == LONG || previousLocalType == DOUBLE) {
+ setLocal(arg - 1, TOP);
+ } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND
+ || (previousLocalType & KIND_MASK) == STACK_KIND) {
+ // The type of the previous local variable is not known yet, but if it later appears
+ // to be LONG or DOUBLE, we should then use TOP instead.
+ setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG);
+ }
+ }
+ break;
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ pop(1);
+ abstractType1 = pop();
+ setLocal(arg, abstractType1);
+ setLocal(arg + 1, TOP);
+ if (arg > 0) {
+ int previousLocalType = getLocal(arg - 1);
+ if (previousLocalType == LONG || previousLocalType == DOUBLE) {
+ setLocal(arg - 1, TOP);
+ } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND
+ || (previousLocalType & KIND_MASK) == STACK_KIND) {
+ // The type of the previous local variable is not known yet, but if it later appears
+ // to be LONG or DOUBLE, we should then use TOP instead.
+ setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG);
+ }
+ }
+ break;
+ case Opcodes.IASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.AASTORE:
+ pop(3);
+ break;
+ case Opcodes.LASTORE:
+ case Opcodes.DASTORE:
+ pop(4);
+ break;
+ case Opcodes.POP:
+ case Opcodes.IFEQ:
+ case Opcodes.IFNE:
+ case Opcodes.IFLT:
+ case Opcodes.IFGE:
+ case Opcodes.IFGT:
+ case Opcodes.IFLE:
+ case Opcodes.IRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.TABLESWITCH:
+ case Opcodes.LOOKUPSWITCH:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
+ pop(1);
+ break;
+ case Opcodes.POP2:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ICMPLT:
+ case Opcodes.IF_ICMPGE:
+ case Opcodes.IF_ICMPGT:
+ case Opcodes.IF_ICMPLE:
+ case Opcodes.IF_ACMPEQ:
+ case Opcodes.IF_ACMPNE:
+ case Opcodes.LRETURN:
+ case Opcodes.DRETURN:
+ pop(2);
+ break;
+ case Opcodes.DUP:
+ abstractType1 = pop();
+ push(abstractType1);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP_X1:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ push(abstractType1);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP_X2:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ abstractType3 = pop();
+ push(abstractType1);
+ push(abstractType3);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP2:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ push(abstractType2);
+ push(abstractType1);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP2_X1:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ abstractType3 = pop();
+ push(abstractType2);
+ push(abstractType1);
+ push(abstractType3);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.DUP2_X2:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ abstractType3 = pop();
+ abstractType4 = pop();
+ push(abstractType2);
+ push(abstractType1);
+ push(abstractType4);
+ push(abstractType3);
+ push(abstractType2);
+ push(abstractType1);
+ break;
+ case Opcodes.SWAP:
+ abstractType1 = pop();
+ abstractType2 = pop();
+ push(abstractType1);
+ push(abstractType2);
+ break;
+ case Opcodes.IALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ case Opcodes.IADD:
+ case Opcodes.ISUB:
+ case Opcodes.IMUL:
+ case Opcodes.IDIV:
+ case Opcodes.IREM:
+ case Opcodes.IAND:
+ case Opcodes.IOR:
+ case Opcodes.IXOR:
+ case Opcodes.ISHL:
+ case Opcodes.ISHR:
+ case Opcodes.IUSHR:
+ case Opcodes.L2I:
+ case Opcodes.D2I:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ pop(2);
+ push(INTEGER);
+ break;
+ case Opcodes.LADD:
+ case Opcodes.LSUB:
+ case Opcodes.LMUL:
+ case Opcodes.LDIV:
+ case Opcodes.LREM:
+ case Opcodes.LAND:
+ case Opcodes.LOR:
+ case Opcodes.LXOR:
+ pop(4);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FALOAD:
+ case Opcodes.FADD:
+ case Opcodes.FSUB:
+ case Opcodes.FMUL:
+ case Opcodes.FDIV:
+ case Opcodes.FREM:
+ case Opcodes.L2F:
+ case Opcodes.D2F:
+ pop(2);
+ push(FLOAT);
+ break;
+ case Opcodes.DADD:
+ case Opcodes.DSUB:
+ case Opcodes.DMUL:
+ case Opcodes.DDIV:
+ case Opcodes.DREM:
+ pop(4);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.LSHL:
+ case Opcodes.LSHR:
+ case Opcodes.LUSHR:
+ pop(3);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.IINC:
+ setLocal(arg, INTEGER);
+ break;
+ case Opcodes.I2L:
+ case Opcodes.F2L:
+ pop(1);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.I2F:
+ pop(1);
+ push(FLOAT);
+ break;
+ case Opcodes.I2D:
+ case Opcodes.F2D:
+ pop(1);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.F2I:
+ case Opcodes.ARRAYLENGTH:
+ case Opcodes.INSTANCEOF:
+ pop(1);
+ push(INTEGER);
+ break;
+ case Opcodes.LCMP:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ pop(4);
+ push(INTEGER);
+ break;
+ case Opcodes.JSR:
+ case Opcodes.RET:
+ throw new IllegalArgumentException("JSR/RET are not supported with computeFrames option");
+ case Opcodes.GETSTATIC:
+ push(symbolTable, argSymbol.value);
+ break;
+ case Opcodes.PUTSTATIC:
+ pop(argSymbol.value);
+ break;
+ case Opcodes.GETFIELD:
+ pop(1);
+ push(symbolTable, argSymbol.value);
+ break;
+ case Opcodes.PUTFIELD:
+ pop(argSymbol.value);
+ pop();
+ break;
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKESPECIAL:
+ case Opcodes.INVOKESTATIC:
+ case Opcodes.INVOKEINTERFACE:
+ pop(argSymbol.value);
+ if (opcode != Opcodes.INVOKESTATIC) {
+ abstractType1 = pop();
+ if (opcode == Opcodes.INVOKESPECIAL && argSymbol.name.charAt(0) == '<') {
+ addInitializedType(abstractType1);
+ }
+ }
+ push(symbolTable, argSymbol.value);
+ break;
+ case Opcodes.INVOKEDYNAMIC:
+ pop(argSymbol.value);
+ push(symbolTable, argSymbol.value);
+ break;
+ case Opcodes.NEW:
+ push(UNINITIALIZED_KIND | symbolTable.addUninitializedType(argSymbol.value, arg));
+ break;
+ case Opcodes.NEWARRAY:
+ pop();
+ switch (arg) {
+ case Opcodes.T_BOOLEAN:
+ push(ARRAY_OF | BOOLEAN);
break;
- case Opcodes.INVOKEVIRTUAL:
- case Opcodes.INVOKESPECIAL:
- case Opcodes.INVOKESTATIC:
- case Opcodes.INVOKEINTERFACE:
- pop(item.strVal3);
- if (opcode != Opcodes.INVOKESTATIC) {
- t1 = pop();
- if (opcode == Opcodes.INVOKESPECIAL
- && item.strVal2.charAt(0) == '<') {
- init(t1);
- }
- }
- push(cw, item.strVal3);
+ case Opcodes.T_CHAR:
+ push(ARRAY_OF | CHAR);
break;
- case Opcodes.INVOKEDYNAMIC:
- pop(item.strVal2);
- push(cw, item.strVal2);
+ case Opcodes.T_BYTE:
+ push(ARRAY_OF | BYTE);
break;
- case Opcodes.NEW:
- push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg));
+ case Opcodes.T_SHORT:
+ push(ARRAY_OF | SHORT);
break;
- case Opcodes.NEWARRAY:
- pop();
- switch (arg) {
- case Opcodes.T_BOOLEAN:
- push(ARRAY_OF | BOOLEAN);
- break;
- case Opcodes.T_CHAR:
- push(ARRAY_OF | CHAR);
- break;
- case Opcodes.T_BYTE:
- push(ARRAY_OF | BYTE);
- break;
- case Opcodes.T_SHORT:
- push(ARRAY_OF | SHORT);
- break;
- case Opcodes.T_INT:
- push(ARRAY_OF | INTEGER);
- break;
- case Opcodes.T_FLOAT:
- push(ARRAY_OF | FLOAT);
- break;
- case Opcodes.T_DOUBLE:
- push(ARRAY_OF | DOUBLE);
- break;
- // case Opcodes.T_LONG:
- default:
- push(ARRAY_OF | LONG);
- break;
- }
+ case Opcodes.T_INT:
+ push(ARRAY_OF | INTEGER);
break;
- case Opcodes.ANEWARRAY:
- String s = item.strVal1;
- pop();
- if (s.charAt(0) == '[') {
- push(cw, '[' + s);
- } else {
- push(ARRAY_OF | OBJECT | cw.addType(s));
- }
+ case Opcodes.T_FLOAT:
+ push(ARRAY_OF | FLOAT);
break;
- case Opcodes.CHECKCAST:
- s = item.strVal1;
- pop();
- if (s.charAt(0) == '[') {
- push(cw, s);
- } else {
- push(OBJECT | cw.addType(s));
- }
+ case Opcodes.T_DOUBLE:
+ push(ARRAY_OF | DOUBLE);
break;
- // case Opcodes.MULTIANEWARRAY:
- default:
- pop(arg);
- push(cw, item.strVal1);
+ case Opcodes.T_LONG:
+ push(ARRAY_OF | LONG);
break;
+ default:
+ throw new IllegalArgumentException();
}
+ break;
+ case Opcodes.ANEWARRAY:
+ String arrayElementType = argSymbol.value;
+ pop();
+ if (arrayElementType.charAt(0) == '[') {
+ push(symbolTable, '[' + arrayElementType);
+ } else {
+ push(ARRAY_OF | REFERENCE_KIND | symbolTable.addType(arrayElementType));
+ }
+ break;
+ case Opcodes.CHECKCAST:
+ String castType = argSymbol.value;
+ pop();
+ if (castType.charAt(0) == '[') {
+ push(symbolTable, castType);
+ } else {
+ push(REFERENCE_KIND | symbolTable.addType(castType));
+ }
+ break;
+ case Opcodes.MULTIANEWARRAY:
+ pop(arg);
+ push(symbolTable, argSymbol.value);
+ break;
+ default:
+ throw new IllegalArgumentException();
}
+ }
- /**
- * Merges the input frame of the given basic block with the input and output
- * frames of this basic block. Returns true if the input frame of
- * the given label has been changed by this operation.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param frame
- * the basic block whose input frame must be updated.
- * @param edge
- * the kind of the {@link Edge} between this label and 'label'.
- * See {@link Edge#info}.
- * @return true if the input frame of the given label has been
- * changed by this operation.
- */
- boolean merge(final ClassWriter cw, final Frame frame, final int edge) {
- boolean changed = false;
- int i, s, dim, kind, t;
+ // -----------------------------------------------------------------------------------------------
+ // Frame merging methods, used in the second step of the stack map frame computation algorithm
+ // -----------------------------------------------------------------------------------------------
- int nLocal = inputLocals.length;
- int nStack = inputStack.length;
- if (frame.inputLocals == null) {
- frame.inputLocals = new int[nLocal];
- changed = true;
- }
+ /**
+ * Computes the concrete output type corresponding to a given abstract output type.
+ *
+ * @param abstractOutputType an abstract output type.
+ * @param numStack the size of the input stack, used to resolve abstract output types of
+ * STACK_KIND kind.
+ * @return the concrete output type corresponding to 'abstractOutputType'.
+ */
+ private int getConcreteOutputType(final int abstractOutputType, final int numStack) {
+ int dim = abstractOutputType & DIM_MASK;
+ int kind = abstractOutputType & KIND_MASK;
+ if (kind == LOCAL_KIND) {
+ // By definition, a LOCAL_KIND type designates the concrete type of a local variable at
+ // the beginning of the basic block corresponding to this frame (which is known when
+ // this method is called, but was not when the abstract type was computed).
+ int concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK];
+ if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0
+ && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) {
+ concreteOutputType = TOP;
+ }
+ return concreteOutputType;
+ } else if (kind == STACK_KIND) {
+ // By definition, a STACK_KIND type designates the concrete type of a local variable at
+ // the beginning of the basic block corresponding to this frame (which is known when
+ // this method is called, but was not when the abstract type was computed).
+ int concreteOutputType = dim + inputStack[numStack - (abstractOutputType & VALUE_MASK)];
+ if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0
+ && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) {
+ concreteOutputType = TOP;
+ }
+ return concreteOutputType;
+ } else {
+ return abstractOutputType;
+ }
+ }
- for (i = 0; i < nLocal; ++i) {
- if (outputLocals != null && i < outputLocals.length) {
- s = outputLocals[i];
- if (s == 0) {
- t = inputLocals[i];
- } else {
- dim = s & DIM;
- kind = s & KIND;
- if (kind == BASE) {
- t = s;
- } else {
- if (kind == LOCAL) {
- t = dim + inputLocals[s & VALUE];
- } else {
- t = dim + inputStack[nStack - (s & VALUE)];
- }
- if ((s & TOP_IF_LONG_OR_DOUBLE) != 0
- && (t == LONG || t == DOUBLE)) {
- t = TOP;
- }
- }
- }
- } else {
- t = inputLocals[i];
- }
- if (initializations != null) {
- t = init(cw, t);
- }
- changed |= merge(cw, t, frame.inputLocals, i);
- }
+ /**
+ * Merges the input frame of the given {@link Frame} with the input and output frames of this
+ * {@link Frame}. Returns {@literal true} if the given frame has been changed by this operation
+ * (the input and output frames of this {@link Frame} are never changed).
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param dstFrame the {@link Frame} whose input frame must be updated. This should be the frame
+ * of a successor, in the control flow graph, of the basic block corresponding to this frame.
+ * @param catchTypeIndex if 'frame' corresponds to an exception handler basic block, the type
+ * table index of the caught exception type, otherwise 0.
+ * @return {@literal true} if the input frame of 'frame' has been changed by this operation.
+ */
+ final boolean merge(
+ final SymbolTable symbolTable, final Frame dstFrame, final int catchTypeIndex) {
+ boolean frameChanged = false;
- if (edge > 0) {
- for (i = 0; i < nLocal; ++i) {
- t = inputLocals[i];
- changed |= merge(cw, t, frame.inputLocals, i);
- }
- if (frame.inputStack == null) {
- frame.inputStack = new int[1];
- changed = true;
- }
- changed |= merge(cw, edge, frame.inputStack, 0);
- return changed;
+ // Compute the concrete types of the local variables at the end of the basic block corresponding
+ // to this frame, by resolving its abstract output types, and merge these concrete types with
+ // those of the local variables in the input frame of dstFrame.
+ int numLocal = inputLocals.length;
+ int numStack = inputStack.length;
+ if (dstFrame.inputLocals == null) {
+ dstFrame.inputLocals = new int[numLocal];
+ frameChanged = true;
+ }
+ for (int i = 0; i < numLocal; ++i) {
+ int concreteOutputType;
+ if (outputLocals != null && i < outputLocals.length) {
+ int abstractOutputType = outputLocals[i];
+ if (abstractOutputType == 0) {
+ // If the local variable has never been assigned in this basic block, it is equal to its
+ // value at the beginning of the block.
+ concreteOutputType = inputLocals[i];
+ } else {
+ concreteOutputType = getConcreteOutputType(abstractOutputType, numStack);
}
+ } else {
+ // If the local variable has never been assigned in this basic block, it is equal to its
+ // value at the beginning of the block.
+ concreteOutputType = inputLocals[i];
+ }
+ // concreteOutputType might be an uninitialized type from the input locals or from the input
+ // stack. However, if a constructor has been called for this class type in the basic block,
+ // then this type is no longer uninitialized at the end of basic block.
+ if (initializations != null) {
+ concreteOutputType = getInitializedType(symbolTable, concreteOutputType);
+ }
+ frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputLocals, i);
+ }
- int nInputStack = inputStack.length + owner.inputStackTop;
- if (frame.inputStack == null) {
- frame.inputStack = new int[nInputStack + outputStackTop];
- changed = true;
- }
+ // If dstFrame is an exception handler block, it can be reached from any instruction of the
+ // basic block corresponding to this frame, in particular from the first one. Therefore, the
+ // input locals of dstFrame should be compatible (i.e. merged) with the input locals of this
+ // frame (and the input stack of dstFrame should be compatible, i.e. merged, with a one
+ // element stack containing the caught exception type).
+ if (catchTypeIndex > 0) {
+ for (int i = 0; i < numLocal; ++i) {
+ frameChanged |= merge(symbolTable, inputLocals[i], dstFrame.inputLocals, i);
+ }
+ if (dstFrame.inputStack == null) {
+ dstFrame.inputStack = new int[1];
+ frameChanged = true;
+ }
+ frameChanged |= merge(symbolTable, catchTypeIndex, dstFrame.inputStack, 0);
+ return frameChanged;
+ }
- for (i = 0; i < nInputStack; ++i) {
- t = inputStack[i];
- if (initializations != null) {
- t = init(cw, t);
- }
- changed |= merge(cw, t, frame.inputStack, i);
- }
- for (i = 0; i < outputStackTop; ++i) {
- s = outputStack[i];
- dim = s & DIM;
- kind = s & KIND;
- if (kind == BASE) {
- t = s;
- } else {
- if (kind == LOCAL) {
- t = dim + inputLocals[s & VALUE];
- } else {
- t = dim + inputStack[nStack - (s & VALUE)];
- }
- if ((s & TOP_IF_LONG_OR_DOUBLE) != 0
- && (t == LONG || t == DOUBLE)) {
- t = TOP;
- }
- }
- if (initializations != null) {
- t = init(cw, t);
- }
- changed |= merge(cw, t, frame.inputStack, nInputStack + i);
- }
- return changed;
+ // Compute the concrete types of the stack operands at the end of the basic block corresponding
+ // to this frame, by resolving its abstract output types, and merge these concrete types with
+ // those of the stack operands in the input frame of dstFrame.
+ int numInputStack = inputStack.length + outputStackStart;
+ if (dstFrame.inputStack == null) {
+ dstFrame.inputStack = new int[numInputStack + outputStackTop];
+ frameChanged = true;
+ }
+ // First, do this for the stack operands that have not been popped in the basic block
+ // corresponding to this frame, and which are therefore equal to their value in the input
+ // frame (except for uninitialized types, which may have been initialized).
+ for (int i = 0; i < numInputStack; ++i) {
+ int concreteOutputType = inputStack[i];
+ if (initializations != null) {
+ concreteOutputType = getInitializedType(symbolTable, concreteOutputType);
+ }
+ frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, i);
+ }
+ // Then, do this for the stack operands that have pushed in the basic block (this code is the
+ // same as the one above for local variables).
+ for (int i = 0; i < outputStackTop; ++i) {
+ int abstractOutputType = outputStack[i];
+ int concreteOutputType = getConcreteOutputType(abstractOutputType, numStack);
+ if (initializations != null) {
+ concreteOutputType = getInitializedType(symbolTable, concreteOutputType);
+ }
+ frameChanged |=
+ merge(symbolTable, concreteOutputType, dstFrame.inputStack, numInputStack + i);
}
+ return frameChanged;
+ }
- /**
- * Merges the type at the given index in the given type array with the given
- * type. Returns true if the type array has been modified by this
- * operation.
- *
- * @param cw
- * the ClassWriter to which this label belongs.
- * @param t
- * the type with which the type array element must be merged.
- * @param types
- * an array of types.
- * @param index
- * the index of the type that must be merged in 'types'.
- * @return true if the type array has been modified by this
- * operation.
- */
- private static boolean merge(final ClassWriter cw, int t,
- final int[] types, final int index) {
- int u = types[index];
- if (u == t) {
- // if the types are equal, merge(u,t)=u, so there is no change
- return false;
- }
- if ((t & ~DIM) == NULL) {
- if (u == NULL) {
- return false;
- }
- t = NULL;
+ /**
+ * Merges the type at the given index in the given abstract type array with the given type.
+ * Returns {@literal true} if the type array has been modified by this operation.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param sourceType the abstract type with which the abstract type array element must be merged.
+ * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND} or {@link
+ * #UNINITIALIZED_KIND} kind, with positive or {@literal null} array dimensions.
+ * @param dstTypes an array of abstract types. These types should be of {@link #CONSTANT_KIND},
+ * {@link #REFERENCE_KIND} or {@link #UNINITIALIZED_KIND} kind, with positive or {@literal
+ * null} array dimensions.
+ * @param dstIndex the index of the type that must be merged in dstTypes.
+ * @return {@literal true} if the type array has been modified by this operation.
+ */
+ private static boolean merge(
+ final SymbolTable symbolTable,
+ final int sourceType,
+ final int[] dstTypes,
+ final int dstIndex) {
+ int dstType = dstTypes[dstIndex];
+ if (dstType == sourceType) {
+ // If the types are equal, merge(sourceType, dstType) = dstType, so there is no change.
+ return false;
+ }
+ int srcType = sourceType;
+ if ((sourceType & ~DIM_MASK) == NULL) {
+ if (dstType == NULL) {
+ return false;
+ }
+ srcType = NULL;
+ }
+ if (dstType == 0) {
+ // If dstTypes[dstIndex] has never been assigned, merge(srcType, dstType) = srcType.
+ dstTypes[dstIndex] = srcType;
+ return true;
+ }
+ int mergedType;
+ if ((dstType & DIM_MASK) != 0 || (dstType & KIND_MASK) == REFERENCE_KIND) {
+ // If dstType is a reference type of any array dimension.
+ if (srcType == NULL) {
+ // If srcType is the NULL type, merge(srcType, dstType) = dstType, so there is no change.
+ return false;
+ } else if ((srcType & (DIM_MASK | KIND_MASK)) == (dstType & (DIM_MASK | KIND_MASK))) {
+ // If srcType has the same array dimension and the same kind as dstType.
+ if ((dstType & KIND_MASK) == REFERENCE_KIND) {
+ // If srcType and dstType are reference types with the same array dimension,
+ // merge(srcType, dstType) = dim(srcType) | common super class of srcType and dstType.
+ mergedType =
+ (srcType & DIM_MASK)
+ | REFERENCE_KIND
+ | symbolTable.addMergedType(srcType & VALUE_MASK, dstType & VALUE_MASK);
+ } else {
+ // If srcType and dstType are array types of equal dimension but different element types,
+ // merge(srcType, dstType) = dim(srcType) - 1 | java/lang/Object.
+ int mergedDim = ELEMENT_OF + (srcType & DIM_MASK);
+ mergedType = mergedDim | REFERENCE_KIND | symbolTable.addType("java/lang/Object");
}
- if (u == 0) {
- // if types[index] has never been assigned, merge(u,t)=t
- types[index] = t;
- return true;
+ } else if ((srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND) {
+ // If srcType is any other reference or array type,
+ // merge(srcType, dstType) = min(srcDdim, dstDim) | java/lang/Object
+ // where srcDim is the array dimension of srcType, minus 1 if srcType is an array type
+ // with a non reference element type (and similarly for dstDim).
+ int srcDim = srcType & DIM_MASK;
+ if (srcDim != 0 && (srcType & KIND_MASK) != REFERENCE_KIND) {
+ srcDim = ELEMENT_OF + srcDim;
}
- int v;
- if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) {
- // if u is a reference type of any dimension
- if (t == NULL) {
- // if t is the NULL type, merge(u,t)=u, so there is no change
- return false;
- } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) {
- if ((u & BASE_KIND) == OBJECT) {
- // if t is also a reference type, and if u and t have the
- // same dimension merge(u,t) = dim(t) | common parent of the
- // element types of u and t
- v = (t & DIM) | OBJECT
- | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE);
- } else {
- // if u and t are array types, but not with the same element
- // type, merge(u,t)=java/lang/Object
- v = OBJECT | cw.addType("java/lang/Object");
- }
- } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) {
- // if t is any other reference or array type,
- // merge(u,t)=java/lang/Object
- v = OBJECT | cw.addType("java/lang/Object");
- } else {
- // if t is any other type, merge(u,t)=TOP
- v = TOP;
- }
- } else if (u == NULL) {
- // if u is the NULL type, merge(u,t)=t,
- // or TOP if t is not a reference type
- v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP;
- } else {
- // if u is any other type, merge(u,t)=TOP whatever t
- v = TOP;
+ int dstDim = dstType & DIM_MASK;
+ if (dstDim != 0 && (dstType & KIND_MASK) != REFERENCE_KIND) {
+ dstDim = ELEMENT_OF + dstDim;
}
- if (u != v) {
- types[index] = v;
- return true;
+ mergedType =
+ Math.min(srcDim, dstDim) | REFERENCE_KIND | symbolTable.addType("java/lang/Object");
+ } else {
+ // If srcType is any other type, merge(srcType, dstType) = TOP.
+ mergedType = TOP;
+ }
+ } else if (dstType == NULL) {
+ // If dstType is the NULL type, merge(srcType, dstType) = srcType, or TOP if srcType is not a
+ // an array type or a reference type.
+ mergedType =
+ (srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND ? srcType : TOP;
+ } else {
+ // If dstType is any other type, merge(srcType, dstType) = TOP whatever srcType.
+ mergedType = TOP;
+ }
+ if (mergedType != dstType) {
+ dstTypes[dstIndex] = mergedType;
+ return true;
+ }
+ return false;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Frame output methods, to generate StackMapFrame attributes
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Makes the given {@link MethodWriter} visit the input frame of this {@link Frame}. The visit is
+ * done with the {@link MethodWriter#visitFrameStart}, {@link MethodWriter#visitAbstractType} and
+ * {@link MethodWriter#visitFrameEnd} methods.
+ *
+ * @param methodWriter the {@link MethodWriter} that should visit the input frame of this {@link
+ * Frame}.
+ */
+ final void accept(final MethodWriter methodWriter) {
+ // Compute the number of locals, ignoring TOP types that are just after a LONG or a DOUBLE, and
+ // all trailing TOP types.
+ int[] localTypes = inputLocals;
+ int numLocal = 0;
+ int numTrailingTop = 0;
+ int i = 0;
+ while (i < localTypes.length) {
+ int localType = localTypes[i];
+ i += (localType == LONG || localType == DOUBLE) ? 2 : 1;
+ if (localType == TOP) {
+ numTrailingTop++;
+ } else {
+ numLocal += numTrailingTop + 1;
+ numTrailingTop = 0;
+ }
+ }
+ // Compute the stack size, ignoring TOP types that are just after a LONG or a DOUBLE.
+ int[] stackTypes = inputStack;
+ int numStack = 0;
+ i = 0;
+ while (i < stackTypes.length) {
+ int stackType = stackTypes[i];
+ i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1;
+ numStack++;
+ }
+ // Visit the frame and its content.
+ int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, numLocal, numStack);
+ i = 0;
+ while (numLocal-- > 0) {
+ int localType = localTypes[i];
+ i += (localType == LONG || localType == DOUBLE) ? 2 : 1;
+ methodWriter.visitAbstractType(frameIndex++, localType);
+ }
+ i = 0;
+ while (numStack-- > 0) {
+ int stackType = stackTypes[i];
+ i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1;
+ methodWriter.visitAbstractType(frameIndex++, stackType);
+ }
+ methodWriter.visitFrameEnd();
+ }
+
+ /**
+ * Put the given abstract type in the given ByteVector, using the JVMS verification_type_info
+ * format used in StackMapTable attributes.
+ *
+ * @param symbolTable the type table to use to lookup and store type {@link Symbol}.
+ * @param abstractType an abstract type, restricted to {@link Frame#CONSTANT_KIND}, {@link
+ * Frame#REFERENCE_KIND} or {@link Frame#UNINITIALIZED_KIND} types.
+ * @param output where the abstract type must be put.
+ * @see JVMS
+ * 4.7.4
+ */
+ static void putAbstractType(
+ final SymbolTable symbolTable, final int abstractType, final ByteVector output) {
+ int arrayDimensions = (abstractType & Frame.DIM_MASK) >> DIM_SHIFT;
+ if (arrayDimensions == 0) {
+ int typeValue = abstractType & VALUE_MASK;
+ switch (abstractType & KIND_MASK) {
+ case CONSTANT_KIND:
+ output.putByte(typeValue);
+ break;
+ case REFERENCE_KIND:
+ output
+ .putByte(ITEM_OBJECT)
+ .putShort(symbolTable.addConstantClass(symbolTable.getType(typeValue).value).index);
+ break;
+ case UNINITIALIZED_KIND:
+ output.putByte(ITEM_UNINITIALIZED).putShort((int) symbolTable.getType(typeValue).data);
+ break;
+ default:
+ throw new AssertionError();
+ }
+ } else {
+ // Case of an array type, we need to build its descriptor first.
+ StringBuilder typeDescriptor = new StringBuilder();
+ while (arrayDimensions-- > 0) {
+ typeDescriptor.append('[');
+ }
+ if ((abstractType & KIND_MASK) == REFERENCE_KIND) {
+ typeDescriptor
+ .append('L')
+ .append(symbolTable.getType(abstractType & VALUE_MASK).value)
+ .append(';');
+ } else {
+ switch (abstractType & VALUE_MASK) {
+ case Frame.ITEM_ASM_BOOLEAN:
+ typeDescriptor.append('Z');
+ break;
+ case Frame.ITEM_ASM_BYTE:
+ typeDescriptor.append('B');
+ break;
+ case Frame.ITEM_ASM_CHAR:
+ typeDescriptor.append('C');
+ break;
+ case Frame.ITEM_ASM_SHORT:
+ typeDescriptor.append('S');
+ break;
+ case Frame.ITEM_INTEGER:
+ typeDescriptor.append('I');
+ break;
+ case Frame.ITEM_FLOAT:
+ typeDescriptor.append('F');
+ break;
+ case Frame.ITEM_LONG:
+ typeDescriptor.append('J');
+ break;
+ case Frame.ITEM_DOUBLE:
+ typeDescriptor.append('D');
+ break;
+ default:
+ throw new AssertionError();
}
- return false;
+ }
+ output
+ .putByte(ITEM_OBJECT)
+ .putShort(symbolTable.addConstantClass(typeDescriptor.toString()).index);
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/Handle.java b/src/java/nginx/clojure/asm/Handle.java
index c3ad856b..5581b30c 100644
--- a/src/java/nginx/clojure/asm/Handle.java
+++ b/src/java/nginx/clojure/asm/Handle.java
@@ -1,170 +1,190 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
* A reference to a field or a method.
- *
+ *
* @author Remi Forax
* @author Eric Bruneton
*/
public final class Handle {
- /**
- * The kind of field or method designated by this Handle. Should be
- * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
- * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
- * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- */
- final int tag;
-
- /**
- * The internal name of the class that owns the field or method designated
- * by this handle.
- */
- final String owner;
-
- /**
- * The name of the field or method designated by this handle.
- */
- final String name;
-
- /**
- * The descriptor of the field or method designated by this handle.
- */
- final String desc;
-
- /**
- * Constructs a new field or method handle.
- *
- * @param tag
- * the kind of field or method designated by this Handle. Must be
- * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
- * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
- * {@link Opcodes#H_INVOKEVIRTUAL},
- * {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL},
- * {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- * @param owner
- * the internal name of the class that owns the field or method
- * designated by this handle.
- * @param name
- * the name of the field or method designated by this handle.
- * @param desc
- * the descriptor of the field or method designated by this
- * handle.
- */
- public Handle(int tag, String owner, String name, String desc) {
- this.tag = tag;
- this.owner = owner;
- this.name = name;
- this.desc = desc;
- }
+ /**
+ * The kind of field or method designated by this Handle. Should be {@link Opcodes#H_GETFIELD},
+ * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
+ * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ */
+ private final int tag;
- /**
- * Returns the kind of field or method designated by this handle.
- *
- * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
- * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
- * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
- * {@link Opcodes#H_INVOKESPECIAL},
- * {@link Opcodes#H_NEWINVOKESPECIAL} or
- * {@link Opcodes#H_INVOKEINTERFACE}.
- */
- public int getTag() {
- return tag;
- }
+ /** The internal name of the class that owns the field or method designated by this handle. */
+ private final String owner;
- /**
- * Returns the internal name of the class that owns the field or method
- * designated by this handle.
- *
- * @return the internal name of the class that owns the field or method
- * designated by this handle.
- */
- public String getOwner() {
- return owner;
- }
+ /** The name of the field or method designated by this handle. */
+ private final String name;
- /**
- * Returns the name of the field or method designated by this handle.
- *
- * @return the name of the field or method designated by this handle.
- */
- public String getName() {
- return name;
- }
+ /** The descriptor of the field or method designated by this handle. */
+ private final String descriptor;
- /**
- * Returns the descriptor of the field or method designated by this handle.
- *
- * @return the descriptor of the field or method designated by this handle.
- */
- public String getDesc() {
- return desc;
- }
+ /** Whether the owner is an interface or not. */
+ private final boolean isInterface;
- @Override
- public boolean equals(Object obj) {
- if (obj == this) {
- return true;
- }
- if (!(obj instanceof Handle)) {
- return false;
- }
- Handle h = (Handle) obj;
- return tag == h.tag && owner.equals(h.owner) && name.equals(h.name)
- && desc.equals(h.desc);
- }
+ /**
+ * Constructs a new field or method handle.
+ *
+ * @param tag the kind of field or method designated by this Handle. Must be {@link
+ * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link
+ * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link
+ * Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of the class that owns the field or method designated by this
+ * handle (see {@link Type#getInternalName()}).
+ * @param name the name of the field or method designated by this handle.
+ * @param descriptor the descriptor of the field or method designated by this handle.
+ * @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String,
+ * boolean)}.
+ */
+ @Deprecated
+ public Handle(final int tag, final String owner, final String name, final String descriptor) {
+ this(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE);
+ }
- @Override
- public int hashCode() {
- return tag + owner.hashCode() * name.hashCode() * desc.hashCode();
- }
+ /**
+ * Constructs a new field or method handle.
+ *
+ * @param tag the kind of field or method designated by this Handle. Must be {@link
+ * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link
+ * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link
+ * Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of the class that owns the field or method designated by this
+ * handle (see {@link Type#getInternalName()}).
+ * @param name the name of the field or method designated by this handle.
+ * @param descriptor the descriptor of the field or method designated by this handle.
+ * @param isInterface whether the owner is an interface or not.
+ */
+ public Handle(
+ final int tag,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ this.tag = tag;
+ this.owner = owner;
+ this.name = name;
+ this.descriptor = descriptor;
+ this.isInterface = isInterface;
+ }
+
+ /**
+ * Returns the kind of field or method designated by this handle.
+ *
+ * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
+ * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link
+ * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link
+ * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * Returns the internal name of the class that owns the field or method designated by this handle.
+ *
+ * @return the internal name of the class that owns the field or method designated by this handle
+ * (see {@link Type#getInternalName()}).
+ */
+ public String getOwner() {
+ return owner;
+ }
+
+ /**
+ * Returns the name of the field or method designated by this handle.
+ *
+ * @return the name of the field or method designated by this handle.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the descriptor of the field or method designated by this handle.
+ *
+ * @return the descriptor of the field or method designated by this handle.
+ */
+ public String getDesc() {
+ return descriptor;
+ }
- /**
- * Returns the textual representation of this handle. The textual
- * representation is:
- *
- *
- * owner '.' name desc ' ' '(' tag ')'
- *
- *
- * . As this format is unambiguous, it can be parsed if necessary.
- */
- @Override
- public String toString() {
- return owner + '.' + name + desc + " (" + tag + ')';
+ /**
+ * Returns true if the owner of the field or method designated by this handle is an interface.
+ *
+ * @return true if the owner of the field or method designated by this handle is an interface.
+ */
+ public boolean isInterface() {
+ return isInterface;
+ }
+
+ @Override
+ public boolean equals(final Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (!(object instanceof Handle)) {
+ return false;
}
+ Handle handle = (Handle) object;
+ return tag == handle.tag
+ && isInterface == handle.isInterface
+ && owner.equals(handle.owner)
+ && name.equals(handle.name)
+ && descriptor.equals(handle.descriptor);
+ }
+
+ @Override
+ public int hashCode() {
+ return tag
+ + (isInterface ? 64 : 0)
+ + owner.hashCode() * name.hashCode() * descriptor.hashCode();
+ }
+
+ /**
+ * Returns the textual representation of this handle. The textual representation is:
+ *
+ *
+ * - for a reference to a class: owner "." name descriptor " (" tag ")",
+ *
- for a reference to an interface: owner "." name descriptor " (" tag " itf)".
+ *
+ */
+ @Override
+ public String toString() {
+ return owner + '.' + name + descriptor + " (" + tag + (isInterface ? " itf" : "") + ')';
+ }
}
diff --git a/src/java/nginx/clojure/asm/Handler.java b/src/java/nginx/clojure/asm/Handler.java
index e191fbb3..07e29a3d 100644
--- a/src/java/nginx/clojure/asm/Handler.java
+++ b/src/java/nginx/clojure/asm/Handler.java
@@ -1,121 +1,198 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * Information about an exception handler block.
- *
+ * Information about an exception handler. Corresponds to an element of the exception_table array of
+ * a Code attribute, as defined in the Java Virtual Machine Specification (JVMS). Handler instances
+ * can be chained together, with their {@link #nextHandler} field, to describe a full JVMS
+ * exception_table array.
+ *
+ * @see JVMS
+ * 4.7.3
* @author Eric Bruneton
*/
-class Handler {
+final class Handler {
+
+ /**
+ * The start_pc field of this JVMS exception_table entry. Corresponds to the beginning of the
+ * exception handler's scope (inclusive).
+ */
+ final Label startPc;
+
+ /**
+ * The end_pc field of this JVMS exception_table entry. Corresponds to the end of the exception
+ * handler's scope (exclusive).
+ */
+ final Label endPc;
+
+ /**
+ * The handler_pc field of this JVMS exception_table entry. Corresponding to the beginning of the
+ * exception handler's code.
+ */
+ final Label handlerPc;
- /**
- * Beginning of the exception handler's scope (inclusive).
- */
- Label start;
+ /**
+ * The catch_type field of this JVMS exception_table entry. This is the constant pool index of the
+ * internal name of the type of exceptions handled by this handler, or 0 to catch any exceptions.
+ */
+ final int catchType;
- /**
- * End of the exception handler's scope (exclusive).
- */
- Label end;
+ /**
+ * The internal name of the type of exceptions handled by this handler, or {@literal null} to
+ * catch any exceptions.
+ */
+ final String catchTypeDescriptor;
- /**
- * Beginning of the exception handler's code.
- */
- Label handler;
+ /** The next exception handler. */
+ Handler nextHandler;
- /**
- * Internal name of the type of exceptions handled by this handler, or
- * null to catch any exceptions.
- */
- String desc;
+ /**
+ * Constructs a new Handler.
+ *
+ * @param startPc the start_pc field of this JVMS exception_table entry.
+ * @param endPc the end_pc field of this JVMS exception_table entry.
+ * @param handlerPc the handler_pc field of this JVMS exception_table entry.
+ * @param catchType The catch_type field of this JVMS exception_table entry.
+ * @param catchTypeDescriptor The internal name of the type of exceptions handled by this handler,
+ * or {@literal null} to catch any exceptions.
+ */
+ Handler(
+ final Label startPc,
+ final Label endPc,
+ final Label handlerPc,
+ final int catchType,
+ final String catchTypeDescriptor) {
+ this.startPc = startPc;
+ this.endPc = endPc;
+ this.handlerPc = handlerPc;
+ this.catchType = catchType;
+ this.catchTypeDescriptor = catchTypeDescriptor;
+ }
- /**
- * Constant pool index of the internal name of the type of exceptions
- * handled by this handler, or 0 to catch any exceptions.
- */
- int type;
+ /**
+ * Constructs a new Handler from the given one, with a different scope.
+ *
+ * @param handler an existing Handler.
+ * @param startPc the start_pc field of this JVMS exception_table entry.
+ * @param endPc the end_pc field of this JVMS exception_table entry.
+ */
+ Handler(final Handler handler, final Label startPc, final Label endPc) {
+ this(startPc, endPc, handler.handlerPc, handler.catchType, handler.catchTypeDescriptor);
+ this.nextHandler = handler.nextHandler;
+ }
+
+ /**
+ * Removes the range between start and end from the Handler list that begins with the given
+ * element.
+ *
+ * @param firstHandler the beginning of a Handler list. May be {@literal null}.
+ * @param start the start of the range to be removed.
+ * @param end the end of the range to be removed. Maybe {@literal null}.
+ * @return the exception handler list with the start-end range removed.
+ */
+ static Handler removeRange(final Handler firstHandler, final Label start, final Label end) {
+ if (firstHandler == null) {
+ return null;
+ } else {
+ firstHandler.nextHandler = removeRange(firstHandler.nextHandler, start, end);
+ }
+ int handlerStart = firstHandler.startPc.bytecodeOffset;
+ int handlerEnd = firstHandler.endPc.bytecodeOffset;
+ int rangeStart = start.bytecodeOffset;
+ int rangeEnd = end == null ? Integer.MAX_VALUE : end.bytecodeOffset;
+ // Return early if [handlerStart,handlerEnd[ and [rangeStart,rangeEnd[ don't intersect.
+ if (rangeStart >= handlerEnd || rangeEnd <= handlerStart) {
+ return firstHandler;
+ }
+ if (rangeStart <= handlerStart) {
+ if (rangeEnd >= handlerEnd) {
+ // If [handlerStart,handlerEnd[ is included in [rangeStart,rangeEnd[, remove firstHandler.
+ return firstHandler.nextHandler;
+ } else {
+ // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [rangeEnd,handlerEnd[
+ return new Handler(firstHandler, end, firstHandler.endPc);
+ }
+ } else if (rangeEnd >= handlerEnd) {
+ // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [handlerStart,rangeStart[
+ return new Handler(firstHandler, firstHandler.startPc, start);
+ } else {
+ // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ =
+ // [handlerStart,rangeStart[ + [rangeEnd,handerEnd[
+ firstHandler.nextHandler = new Handler(firstHandler, end, firstHandler.endPc);
+ return new Handler(firstHandler, firstHandler.startPc, start);
+ }
+ }
+
+ /**
+ * Returns the number of elements of the Handler list that begins with the given element.
+ *
+ * @param firstHandler the beginning of a Handler list. May be {@literal null}.
+ * @return the number of elements of the Handler list that begins with 'handler'.
+ */
+ static int getExceptionTableLength(final Handler firstHandler) {
+ int length = 0;
+ Handler handler = firstHandler;
+ while (handler != null) {
+ length++;
+ handler = handler.nextHandler;
+ }
+ return length;
+ }
- /**
- * Next exception handler block info.
- */
- Handler next;
+ /**
+ * Returns the size in bytes of the JVMS exception_table corresponding to the Handler list that
+ * begins with the given element. This includes the exception_table_length field.
+ *
+ * @param firstHandler the beginning of a Handler list. May be {@literal null}.
+ * @return the size in bytes of the exception_table_length and exception_table structures.
+ */
+ static int getExceptionTableSize(final Handler firstHandler) {
+ return 2 + 8 * getExceptionTableLength(firstHandler);
+ }
- /**
- * Removes the range between start and end from the given exception
- * handlers.
- *
- * @param h
- * an exception handler list.
- * @param start
- * the start of the range to be removed.
- * @param end
- * the end of the range to be removed. Maybe null.
- * @return the exception handler list with the start-end range removed.
- */
- static Handler remove(Handler h, Label start, Label end) {
- if (h == null) {
- return null;
- } else {
- h.next = remove(h.next, start, end);
- }
- int hstart = h.start.position;
- int hend = h.end.position;
- int s = start.position;
- int e = end == null ? Integer.MAX_VALUE : end.position;
- // if [hstart,hend[ and [s,e[ intervals intersect...
- if (s < hend && e > hstart) {
- if (s <= hstart) {
- if (e >= hend) {
- // [hstart,hend[ fully included in [s,e[, h removed
- h = h.next;
- } else {
- // [hstart,hend[ minus [s,e[ = [e,hend[
- h.start = end;
- }
- } else if (e >= hend) {
- // [hstart,hend[ minus [s,e[ = [hstart,s[
- h.end = start;
- } else {
- // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[
- Handler g = new Handler();
- g.start = end;
- g.end = h.end;
- g.handler = h.handler;
- g.desc = h.desc;
- g.type = h.type;
- g.next = h.next;
- h.end = start;
- h.next = g;
- }
- }
- return h;
+ /**
+ * Puts the JVMS exception_table corresponding to the Handler list that begins with the given
+ * element. This includes the exception_table_length field.
+ *
+ * @param firstHandler the beginning of a Handler list. May be {@literal null}.
+ * @param output where the exception_table_length and exception_table structures must be put.
+ */
+ static void putExceptionTable(final Handler firstHandler, final ByteVector output) {
+ output.putShort(getExceptionTableLength(firstHandler));
+ Handler handler = firstHandler;
+ while (handler != null) {
+ output
+ .putShort(handler.startPc.bytecodeOffset)
+ .putShort(handler.endPc.bytecodeOffset)
+ .putShort(handler.handlerPc.bytecodeOffset)
+ .putShort(handler.catchType);
+ handler = handler.nextHandler;
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/Item.java b/src/java/nginx/clojure/asm/Item.java
deleted file mode 100644
index 316efdc7..00000000
--- a/src/java/nginx/clojure/asm/Item.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-package nginx.clojure.asm;
-
-/**
- * A constant pool item. Constant pool items can be created with the 'newXXX'
- * methods in the {@link ClassWriter} class.
- *
- * @author Eric Bruneton
- */
-final class Item {
-
- /**
- * Index of this item in the constant pool.
- */
- int index;
-
- /**
- * Type of this constant pool item. A single class is used to represent all
- * constant pool item types, in order to minimize the bytecode size of this
- * package. The value of this field is one of {@link ClassWriter#INT},
- * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT},
- * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8},
- * {@link ClassWriter#STR}, {@link ClassWriter#CLASS},
- * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD},
- * {@link ClassWriter#METH}, {@link ClassWriter#IMETH},
- * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}.
- *
- * MethodHandle constant 9 variations are stored using a range of 9 values
- * from {@link ClassWriter#HANDLE_BASE} + 1 to
- * {@link ClassWriter#HANDLE_BASE} + 9.
- *
- * Special Item types are used for Items that are stored in the ClassWriter
- * {@link ClassWriter#typeTable}, instead of the constant pool, in order to
- * avoid clashes with normal constant pool items in the ClassWriter constant
- * pool's hash table. These special item types are
- * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and
- * {@link ClassWriter#TYPE_MERGED}.
- */
- int type;
-
- /**
- * Value of this item, for an integer item.
- */
- int intVal;
-
- /**
- * Value of this item, for a long item.
- */
- long longVal;
-
- /**
- * First part of the value of this item, for items that do not hold a
- * primitive value.
- */
- String strVal1;
-
- /**
- * Second part of the value of this item, for items that do not hold a
- * primitive value.
- */
- String strVal2;
-
- /**
- * Third part of the value of this item, for items that do not hold a
- * primitive value.
- */
- String strVal3;
-
- /**
- * The hash code value of this constant pool item.
- */
- int hashCode;
-
- /**
- * Link to another constant pool item, used for collision lists in the
- * constant pool's hash table.
- */
- Item next;
-
- /**
- * Constructs an uninitialized {@link Item}.
- */
- Item() {
- }
-
- /**
- * Constructs an uninitialized {@link Item} for constant pool element at
- * given position.
- *
- * @param index
- * index of the item to be constructed.
- */
- Item(final int index) {
- this.index = index;
- }
-
- /**
- * Constructs a copy of the given item.
- *
- * @param index
- * index of the item to be constructed.
- * @param i
- * the item that must be copied into the item to be constructed.
- */
- Item(final int index, final Item i) {
- this.index = index;
- type = i.type;
- intVal = i.intVal;
- longVal = i.longVal;
- strVal1 = i.strVal1;
- strVal2 = i.strVal2;
- strVal3 = i.strVal3;
- hashCode = i.hashCode;
- }
-
- /**
- * Sets this item to an integer item.
- *
- * @param intVal
- * the value of this item.
- */
- void set(final int intVal) {
- this.type = ClassWriter.INT;
- this.intVal = intVal;
- this.hashCode = 0x7FFFFFFF & (type + intVal);
- }
-
- /**
- * Sets this item to a long item.
- *
- * @param longVal
- * the value of this item.
- */
- void set(final long longVal) {
- this.type = ClassWriter.LONG;
- this.longVal = longVal;
- this.hashCode = 0x7FFFFFFF & (type + (int) longVal);
- }
-
- /**
- * Sets this item to a float item.
- *
- * @param floatVal
- * the value of this item.
- */
- void set(final float floatVal) {
- this.type = ClassWriter.FLOAT;
- this.intVal = Float.floatToRawIntBits(floatVal);
- this.hashCode = 0x7FFFFFFF & (type + (int) floatVal);
- }
-
- /**
- * Sets this item to a double item.
- *
- * @param doubleVal
- * the value of this item.
- */
- void set(final double doubleVal) {
- this.type = ClassWriter.DOUBLE;
- this.longVal = Double.doubleToRawLongBits(doubleVal);
- this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal);
- }
-
- /**
- * Sets this item to an item that do not hold a primitive value.
- *
- * @param type
- * the type of this item.
- * @param strVal1
- * first part of the value of this item.
- * @param strVal2
- * second part of the value of this item.
- * @param strVal3
- * third part of the value of this item.
- */
- void set(final int type, final String strVal1, final String strVal2,
- final String strVal3) {
- this.type = type;
- this.strVal1 = strVal1;
- this.strVal2 = strVal2;
- this.strVal3 = strVal3;
- switch (type) {
- case ClassWriter.UTF8:
- case ClassWriter.STR:
- case ClassWriter.CLASS:
- case ClassWriter.MTYPE:
- case ClassWriter.TYPE_NORMAL:
- hashCode = 0x7FFFFFFF & (type + strVal1.hashCode());
- return;
- case ClassWriter.NAME_TYPE: {
- hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
- * strVal2.hashCode());
- return;
- }
- // ClassWriter.FIELD:
- // ClassWriter.METH:
- // ClassWriter.IMETH:
- // ClassWriter.HANDLE_BASE + 1..9
- default:
- hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
- * strVal2.hashCode() * strVal3.hashCode());
- }
- }
-
- /**
- * Sets the item to an InvokeDynamic item.
- *
- * @param name
- * invokedynamic's name.
- * @param desc
- * invokedynamic's desc.
- * @param bsmIndex
- * zero based index into the class attribute BootrapMethods.
- */
- void set(String name, String desc, int bsmIndex) {
- this.type = ClassWriter.INDY;
- this.longVal = bsmIndex;
- this.strVal1 = name;
- this.strVal2 = desc;
- this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex
- * strVal1.hashCode() * strVal2.hashCode());
- }
-
- /**
- * Sets the item to a BootstrapMethod item.
- *
- * @param position
- * position in byte in the class attribute BootrapMethods.
- * @param hashCode
- * hashcode of the item. This hashcode is processed from the
- * hashcode of the bootstrap method and the hashcode of all
- * bootstrap arguments.
- */
- void set(int position, int hashCode) {
- this.type = ClassWriter.BSM;
- this.intVal = position;
- this.hashCode = hashCode;
- }
-
- /**
- * Indicates if the given item is equal to this one. This method assumes
- * that the two items have the same {@link #type}.
- *
- * @param i
- * the item to be compared to this one. Both items must have the
- * same {@link #type}.
- * @return true if the given item if equal to this one,
- * false otherwise.
- */
- boolean isEqualTo(final Item i) {
- switch (type) {
- case ClassWriter.UTF8:
- case ClassWriter.STR:
- case ClassWriter.CLASS:
- case ClassWriter.MTYPE:
- case ClassWriter.TYPE_NORMAL:
- return i.strVal1.equals(strVal1);
- case ClassWriter.TYPE_MERGED:
- case ClassWriter.LONG:
- case ClassWriter.DOUBLE:
- return i.longVal == longVal;
- case ClassWriter.INT:
- case ClassWriter.FLOAT:
- return i.intVal == intVal;
- case ClassWriter.TYPE_UNINIT:
- return i.intVal == intVal && i.strVal1.equals(strVal1);
- case ClassWriter.NAME_TYPE:
- return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2);
- case ClassWriter.INDY: {
- return i.longVal == longVal && i.strVal1.equals(strVal1)
- && i.strVal2.equals(strVal2);
- }
- // case ClassWriter.FIELD:
- // case ClassWriter.METH:
- // case ClassWriter.IMETH:
- // case ClassWriter.HANDLE_BASE + 1..9
- default:
- return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2)
- && i.strVal3.equals(strVal3);
- }
- }
-
-}
diff --git a/src/java/nginx/clojure/asm/Label.java b/src/java/nginx/clojure/asm/Label.java
index c0a366da..4be2007b 100644
--- a/src/java/nginx/clojure/asm/Label.java
+++ b/src/java/nginx/clojure/asm/Label.java
@@ -1,560 +1,622 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A label represents a position in the bytecode of a method. Labels are used
- * for jump, goto, and switch instructions, and for try catch blocks. A label
- * designates the instruction that is just after. Note however that there
- * can be other elements between a label and the instruction it designates (such
+ * A position in the bytecode of a method. Labels are used for jump, goto, and switch instructions,
+ * and for try catch blocks. A label designates the instruction that is just after. Note
+ * however that there can be other elements between a label and the instruction it designates (such
* as other labels, stack map frames, line numbers, etc.).
- *
+ *
* @author Eric Bruneton
*/
public class Label {
- /**
- * Indicates if this label is only used for debug attributes. Such a label
- * is not the start of a basic block, the target of a jump instruction, or
- * an exception handler. It can be safely ignored in control flow graph
- * analysis algorithms (for optimization purposes).
- */
- static final int DEBUG = 1;
-
- /**
- * Indicates if the position of this label is known.
- */
- static final int RESOLVED = 2;
-
- /**
- * Indicates if this label has been updated, after instruction resizing.
- */
- static final int RESIZED = 4;
-
- /**
- * Indicates if this basic block has been pushed in the basic block stack.
- * See {@link MethodWriter#visitMaxs visitMaxs}.
- */
- static final int PUSHED = 8;
-
- /**
- * Indicates if this label is the target of a jump instruction, or the start
- * of an exception handler.
- */
- static final int TARGET = 16;
-
- /**
- * Indicates if a stack map frame must be stored for this label.
- */
- static final int STORE = 32;
-
- /**
- * Indicates if this label corresponds to a reachable basic block.
- */
- static final int REACHABLE = 64;
-
- /**
- * Indicates if this basic block ends with a JSR instruction.
- */
- static final int JSR = 128;
-
- /**
- * Indicates if this basic block ends with a RET instruction.
- */
- static final int RET = 256;
-
- /**
- * Indicates if this basic block is the start of a subroutine.
- */
- static final int SUBROUTINE = 512;
-
- /**
- * Indicates if this subroutine basic block has been visited by a
- * visitSubroutine(null, ...) call.
- */
- static final int VISITED = 1024;
-
- /**
- * Indicates if this subroutine basic block has been visited by a
- * visitSubroutine(!null, ...) call.
- */
- static final int VISITED2 = 2048;
-
- /**
- * Field used to associate user information to a label. Warning: this field
- * is used by the ASM tree package. In order to use it with the ASM tree
- * package you must override the
- * {@link org.objectweb.asm.tree.MethodNode#getLabelNode} method.
- */
- public Object info;
-
- /**
- * Flags that indicate the status of this label.
- *
- * @see #DEBUG
- * @see #RESOLVED
- * @see #RESIZED
- * @see #PUSHED
- * @see #TARGET
- * @see #STORE
- * @see #REACHABLE
- * @see #JSR
- * @see #RET
- */
- int status;
-
- /**
- * The line number corresponding to this label, if known.
- */
- int line;
-
- /**
- * The position of this label in the code, if known.
- */
- int position;
-
- /**
- * Number of forward references to this label, times two.
- */
- private int referenceCount;
-
- /**
- * Informations about forward references. Each forward reference is
- * described by two consecutive integers in this array: the first one is the
- * position of the first byte of the bytecode instruction that contains the
- * forward reference, while the second is the position of the first byte of
- * the forward reference itself. In fact the sign of the first integer
- * indicates if this reference uses 2 or 4 bytes, and its absolute value
- * gives the position of the bytecode instruction. This array is also used
- * as a bitset to store the subroutines to which a basic block belongs. This
- * information is needed in {@linked MethodWriter#visitMaxs}, after all
- * forward references have been resolved. Hence the same array can be used
- * for both purposes without problems.
- */
- private int[] srcAndRefPositions;
-
- // ------------------------------------------------------------------------
-
- /*
- * Fields for the control flow and data flow graph analysis algorithms (used
- * to compute the maximum stack size or the stack map frames). A control
- * flow graph contains one node per "basic block", and one edge per "jump"
- * from one basic block to another. Each node (i.e., each basic block) is
- * represented by the Label object that corresponds to the first instruction
- * of this basic block. Each node also stores the list of its successors in
- * the graph, as a linked list of Edge objects.
- *
- * The control flow analysis algorithms used to compute the maximum stack
- * size or the stack map frames are similar and use two steps. The first
- * step, during the visit of each instruction, builds information about the
- * state of the local variables and the operand stack at the end of each
- * basic block, called the "output frame", relatively to the frame
- * state at the beginning of the basic block, which is called the "input
- * frame", and which is unknown during this step. The second step, in
- * {@link MethodWriter#visitMaxs}, is a fix point algorithm that computes
- * information about the input frame of each basic block, from the input
- * state of the first basic block (known from the method signature), and by
- * the using the previously computed relative output frames.
- *
- * The algorithm used to compute the maximum stack size only computes the
- * relative output and absolute input stack heights, while the algorithm
- * used to compute stack map frames computes relative output frames and
- * absolute input frames.
- */
-
- /**
- * Start of the output stack relatively to the input stack. The exact
- * semantics of this field depends on the algorithm that is used.
- *
- * When only the maximum stack size is computed, this field is the number of
- * elements in the input stack.
- *
- * When the stack map frames are completely computed, this field is the
- * offset of the first output stack element relatively to the top of the
- * input stack. This offset is always negative or null. A null offset means
- * that the output stack must be appended to the input stack. A -n offset
- * means that the first n output stack elements must replace the top n input
- * stack elements, and that the other elements must be appended to the input
- * stack.
- */
- int inputStackTop;
-
- /**
- * Maximum height reached by the output stack, relatively to the top of the
- * input stack. This maximum is always positive or null.
- */
- int outputStackMax;
-
- /**
- * Information about the input and output stack map frames of this basic
- * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES}
- * option is used.
- */
- Frame frame;
-
- /**
- * The successor of this label, in the order they are visited. This linked
- * list does not include labels used for debug info only. If
- * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it
- * does not contain successive labels that denote the same bytecode position
- * (in this case only the first label appears in this list).
- */
- Label successor;
-
- /**
- * The successors of this node in the control flow graph. These successors
- * are stored in a linked list of {@link Edge Edge} objects, linked to each
- * other by their {@link Edge#next} field.
- */
- Edge successors;
-
- /**
- * The next basic block in the basic block stack. This stack is used in the
- * main loop of the fix point algorithm used in the second step of the
- * control flow analysis algorithms. It is also used in
- * {@link #visitSubroutine} to avoid using a recursive method.
- *
- * @see MethodWriter#visitMaxs
- */
- Label next;
-
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a new label.
- */
- public Label() {
+ /**
+ * A flag indicating that a label is only used for debug attributes. Such a label is not the start
+ * of a basic block, the target of a jump instruction, or an exception handler. It can be safely
+ * ignored in control flow graph analysis algorithms (for optimization purposes).
+ */
+ static final int FLAG_DEBUG_ONLY = 1;
+
+ /**
+ * A flag indicating that a label is the target of a jump instruction, or the start of an
+ * exception handler.
+ */
+ static final int FLAG_JUMP_TARGET = 2;
+
+ /** A flag indicating that the bytecode offset of a label is known. */
+ static final int FLAG_RESOLVED = 4;
+
+ /** A flag indicating that a label corresponds to a reachable basic block. */
+ static final int FLAG_REACHABLE = 8;
+
+ /**
+ * A flag indicating that the basic block corresponding to a label ends with a subroutine call. By
+ * construction in {@link MethodWriter#visitJumpInsn}, labels with this flag set have at least two
+ * outgoing edges:
+ *
+ *
+ * - the first one corresponds to the instruction that follows the jsr instruction in the
+ * bytecode, i.e. where execution continues when it returns from the jsr call. This is a
+ * virtual control flow edge, since execution never goes directly from the jsr to the next
+ * instruction. Instead, it goes to the subroutine and eventually returns to the instruction
+ * following the jsr. This virtual edge is used to compute the real outgoing edges of the
+ * basic blocks ending with a ret instruction, in {@link #addSubroutineRetSuccessors}.
+ *
- the second one corresponds to the target of the jsr instruction,
+ *
+ */
+ static final int FLAG_SUBROUTINE_CALLER = 16;
+
+ /**
+ * A flag indicating that the basic block corresponding to a label is the start of a subroutine.
+ */
+ static final int FLAG_SUBROUTINE_START = 32;
+
+ /** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */
+ static final int FLAG_SUBROUTINE_END = 64;
+
+ /**
+ * The number of elements to add to the {@link #otherLineNumbers} array when it needs to be
+ * resized to store a new source line number.
+ */
+ static final int LINE_NUMBERS_CAPACITY_INCREMENT = 4;
+
+ /**
+ * The number of elements to add to the {@link #forwardReferences} array when it needs to be
+ * resized to store a new forward reference.
+ */
+ static final int FORWARD_REFERENCES_CAPACITY_INCREMENT = 6;
+
+ /**
+ * The bit mask to extract the type of a forward reference to this label. The extracted type is
+ * either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link #FORWARD_REFERENCE_TYPE_WIDE}.
+ *
+ * @see #forwardReferences
+ */
+ static final int FORWARD_REFERENCE_TYPE_MASK = 0xF0000000;
+
+ /**
+ * The type of forward references stored with two bytes in the bytecode. This is the case, for
+ * instance, of a forward reference from an ifnull instruction.
+ */
+ static final int FORWARD_REFERENCE_TYPE_SHORT = 0x10000000;
+
+ /**
+ * The type of forward references stored in four bytes in the bytecode. This is the case, for
+ * instance, of a forward reference from a lookupswitch instruction.
+ */
+ static final int FORWARD_REFERENCE_TYPE_WIDE = 0x20000000;
+
+ /**
+ * The bit mask to extract the 'handle' of a forward reference to this label. The extracted handle
+ * is the bytecode offset where the forward reference value is stored (using either 2 or 4 bytes,
+ * as indicated by the {@link #FORWARD_REFERENCE_TYPE_MASK}).
+ *
+ * @see #forwardReferences
+ */
+ static final int FORWARD_REFERENCE_HANDLE_MASK = 0x0FFFFFFF;
+
+ /**
+ * A sentinel element used to indicate the end of a list of labels.
+ *
+ * @see #nextListElement
+ */
+ static final Label EMPTY_LIST = new Label();
+
+ /**
+ * A user managed state associated with this label. Warning: this field is used by the ASM tree
+ * package. In order to use it with the ASM tree package you must override the getLabelNode method
+ * in MethodNode.
+ */
+ public Object info;
+
+ /**
+ * The type and status of this label or its corresponding basic block. Must be zero or more of
+ * {@link #FLAG_DEBUG_ONLY}, {@link #FLAG_JUMP_TARGET}, {@link #FLAG_RESOLVED}, {@link
+ * #FLAG_REACHABLE}, {@link #FLAG_SUBROUTINE_CALLER}, {@link #FLAG_SUBROUTINE_START}, {@link
+ * #FLAG_SUBROUTINE_END}.
+ */
+ short flags;
+
+ /**
+ * The source line number corresponding to this label, or 0. If there are several source line
+ * numbers corresponding to this label, the first one is stored in this field, and the remaining
+ * ones are stored in {@link #otherLineNumbers}.
+ */
+ private short lineNumber;
+
+ /**
+ * The source line numbers corresponding to this label, in addition to {@link #lineNumber}, or
+ * null. The first element of this array is the number n of source line numbers it contains, which
+ * are stored between indices 1 and n (inclusive).
+ */
+ private int[] otherLineNumbers;
+
+ /**
+ * The offset of this label in the bytecode of its method, in bytes. This value is set if and only
+ * if the {@link #FLAG_RESOLVED} flag is set.
+ */
+ int bytecodeOffset;
+
+ /**
+ * The forward references to this label. The first element is the number of forward references,
+ * times 2 (this corresponds to the index of the last element actually used in this array). Then,
+ * each forward reference is described with two consecutive integers noted
+ * 'sourceInsnBytecodeOffset' and 'reference':
+ *
+ *
+ * - 'sourceInsnBytecodeOffset' is the bytecode offset of the instruction that contains the
+ * forward reference,
+ *
- 'reference' contains the type and the offset in the bytecode where the forward reference
+ * value must be stored, which can be extracted with {@link #FORWARD_REFERENCE_TYPE_MASK}
+ * and {@link #FORWARD_REFERENCE_HANDLE_MASK}.
+ *
+ *
+ * For instance, for an ifnull instruction at bytecode offset x, 'sourceInsnBytecodeOffset' is
+ * equal to x, and 'reference' is of type {@link #FORWARD_REFERENCE_TYPE_SHORT} with value x + 1
+ * (because the ifnull instruction uses a 2 bytes bytecode offset operand stored one byte after
+ * the start of the instruction itself). For the default case of a lookupswitch instruction at
+ * bytecode offset x, 'sourceInsnBytecodeOffset' is equal to x, and 'reference' is of type {@link
+ * #FORWARD_REFERENCE_TYPE_WIDE} with value between x + 1 and x + 4 (because the lookupswitch
+ * instruction uses a 4 bytes bytecode offset operand stored one to four bytes after the start of
+ * the instruction itself).
+ */
+ private int[] forwardReferences;
+
+ // -----------------------------------------------------------------------------------------------
+
+ // Fields for the control flow and data flow graph analysis algorithms (used to compute the
+ // maximum stack size or the stack map frames). A control flow graph contains one node per "basic
+ // block", and one edge per "jump" from one basic block to another. Each node (i.e., each basic
+ // block) is represented with the Label object that corresponds to the first instruction of this
+ // basic block. Each node also stores the list of its successors in the graph, as a linked list of
+ // Edge objects.
+ //
+ // The control flow analysis algorithms used to compute the maximum stack size or the stack map
+ // frames are similar and use two steps. The first step, during the visit of each instruction,
+ // builds information about the state of the local variables and the operand stack at the end of
+ // each basic block, called the "output frame", relatively to the frame state at the
+ // beginning of the basic block, which is called the "input frame", and which is unknown
+ // during this step. The second step, in {@link MethodWriter#computeAllFrames} and {@link
+ // MethodWriter#computeMaxStackAndLocal}, is a fix point algorithm
+ // that computes information about the input frame of each basic block, from the input state of
+ // the first basic block (known from the method signature), and by the using the previously
+ // computed relative output frames.
+ //
+ // The algorithm used to compute the maximum stack size only computes the relative output and
+ // absolute input stack heights, while the algorithm used to compute stack map frames computes
+ // relative output frames and absolute input frames.
+
+ /**
+ * The number of elements in the input stack of the basic block corresponding to this label. This
+ * field is computed in {@link MethodWriter#computeMaxStackAndLocal}.
+ */
+ short inputStackSize;
+
+ /**
+ * The number of elements in the output stack, at the end of the basic block corresponding to this
+ * label. This field is only computed for basic blocks that end with a RET instruction.
+ */
+ short outputStackSize;
+
+ /**
+ * The maximum height reached by the output stack, relatively to the top of the input stack, in
+ * the basic block corresponding to this label. This maximum is always positive or {@literal
+ * null}.
+ */
+ short outputStackMax;
+
+ /**
+ * The id of the subroutine to which this basic block belongs, or 0. If the basic block belongs to
+ * several subroutines, this is the id of the "oldest" subroutine that contains it (with the
+ * convention that a subroutine calling another one is "older" than the callee). This field is
+ * computed in {@link MethodWriter#computeMaxStackAndLocal}, if the method contains JSR
+ * instructions.
+ */
+ short subroutineId;
+
+ /**
+ * The input and output stack map frames of the basic block corresponding to this label. This
+ * field is only used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} or {@link
+ * MethodWriter#COMPUTE_INSERTED_FRAMES} option is used.
+ */
+ Frame frame;
+
+ /**
+ * The successor of this label, in the order they are visited in {@link MethodVisitor#visitLabel}.
+ * This linked list does not include labels used for debug info only. If the {@link
+ * MethodWriter#COMPUTE_ALL_FRAMES} or {@link MethodWriter#COMPUTE_INSERTED_FRAMES} option is used
+ * then it does not contain either successive labels that denote the same bytecode offset (in this
+ * case only the first label appears in this list).
+ */
+ Label nextBasicBlock;
+
+ /**
+ * The outgoing edges of the basic block corresponding to this label, in the control flow graph of
+ * its method. These edges are stored in a linked list of {@link Edge} objects, linked to each
+ * other by their {@link Edge#nextEdge} field.
+ */
+ Edge outgoingEdges;
+
+ /**
+ * The next element in the list of labels to which this label belongs, or {@literal null} if it
+ * does not belong to any list. All lists of labels must end with the {@link #EMPTY_LIST}
+ * sentinel, in order to ensure that this field is null if and only if this label does not belong
+ * to a list of labels. Note that there can be several lists of labels at the same time, but that
+ * a label can belong to at most one list at a time (unless some lists share a common tail, but
+ * this is not used in practice).
+ *
+ *
List of labels are used in {@link MethodWriter#computeAllFrames} and {@link
+ * MethodWriter#computeMaxStackAndLocal} to compute stack map frames and the maximum stack size,
+ * respectively, as well as in {@link #markSubroutine} and {@link #addSubroutineRetSuccessors} to
+ * compute the basic blocks belonging to subroutines and their outgoing edges. Outside of these
+ * methods, this field should be null (this property is a precondition and a postcondition of
+ * these methods).
+ */
+ Label nextListElement;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructor and accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /** Constructs a new label. */
+ public Label() {
+ // Nothing to do.
+ }
+
+ /**
+ * Returns the bytecode offset corresponding to this label. This offset is computed from the start
+ * of the method's bytecode. This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.
+ *
+ * @return the bytecode offset corresponding to this label.
+ * @throws IllegalStateException if this label is not resolved yet.
+ */
+ public int getOffset() {
+ if ((flags & FLAG_RESOLVED) == 0) {
+ throw new IllegalStateException("Label offset position has not been resolved yet");
}
-
- // ------------------------------------------------------------------------
- // Methods to compute offsets and to manage forward references
- // ------------------------------------------------------------------------
-
- /**
- * Returns the offset corresponding to this label. This offset is computed
- * from the start of the method's bytecode. This method is intended for
- * {@link Attribute} sub classes, and is normally not needed by class
- * generators or adapters.
- *
- * @return the offset corresponding to this label.
- * @throws IllegalStateException
- * if this label is not resolved yet.
- */
- public int getOffset() {
- if ((status & RESOLVED) == 0) {
- throw new IllegalStateException(
- "Label offset position has not been resolved yet");
- }
- return position;
+ return bytecodeOffset;
+ }
+
+ /**
+ * Returns the "canonical" {@link Label} instance corresponding to this label's bytecode offset,
+ * if known, otherwise the label itself. The canonical instance is the first label (in the order
+ * of their visit by {@link MethodVisitor#visitLabel}) corresponding to this bytecode offset. It
+ * cannot be known for labels which have not been visited yet.
+ *
+ *
This method should only be used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} option
+ * is used.
+ *
+ * @return the label itself if {@link #frame} is null, otherwise the Label's frame owner. This
+ * corresponds to the "canonical" label instance described above thanks to the way the label
+ * frame is set in {@link MethodWriter#visitLabel}.
+ */
+ final Label getCanonicalInstance() {
+ return frame == null ? this : frame.owner;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods to manage line numbers
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a source line number corresponding to this label.
+ *
+ * @param lineNumber a source line number (which should be strictly positive).
+ */
+ final void addLineNumber(final int lineNumber) {
+ if (this.lineNumber == 0) {
+ this.lineNumber = (short) lineNumber;
+ } else {
+ if (otherLineNumbers == null) {
+ otherLineNumbers = new int[LINE_NUMBERS_CAPACITY_INCREMENT];
+ }
+ int otherLineNumberIndex = ++otherLineNumbers[0];
+ if (otherLineNumberIndex >= otherLineNumbers.length) {
+ int[] newLineNumbers = new int[otherLineNumbers.length + LINE_NUMBERS_CAPACITY_INCREMENT];
+ System.arraycopy(otherLineNumbers, 0, newLineNumbers, 0, otherLineNumbers.length);
+ otherLineNumbers = newLineNumbers;
+ }
+ otherLineNumbers[otherLineNumberIndex] = lineNumber;
}
-
- /**
- * Puts a reference to this label in the bytecode of a method. If the
- * position of the label is known, the offset is computed and written
- * directly. Otherwise, a null offset is written and a new forward reference
- * is declared for this label.
- *
- * @param owner
- * the code writer that calls this method.
- * @param out
- * the bytecode of the method.
- * @param source
- * the position of first byte of the bytecode instruction that
- * contains this label.
- * @param wideOffset
- * true if the reference must be stored in 4 bytes, or
- * false if it must be stored with 2 bytes.
- * @throws IllegalArgumentException
- * if this label has not been created by the given code writer.
- */
- void put(final MethodWriter owner, final ByteVector out, final int source,
- final boolean wideOffset) {
- if ((status & RESOLVED) == 0) {
- if (wideOffset) {
- addReference(-1 - source, out.length);
- out.putInt(-1);
- } else {
- addReference(source, out.length);
- out.putShort(-1);
- }
- } else {
- if (wideOffset) {
- out.putInt(position - source);
- } else {
- out.putShort(position - source);
- }
+ }
+
+ /**
+ * Makes the given visitor visit this label and its source line numbers, if applicable.
+ *
+ * @param methodVisitor a method visitor.
+ * @param visitLineNumbers whether to visit of the label's source line numbers, if any.
+ */
+ final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) {
+ methodVisitor.visitLabel(this);
+ if (visitLineNumbers && lineNumber != 0) {
+ methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this);
+ if (otherLineNumbers != null) {
+ for (int i = 1; i <= otherLineNumbers[0]; ++i) {
+ methodVisitor.visitLineNumber(otherLineNumbers[i], this);
}
+ }
}
-
- /**
- * Adds a forward reference to this label. This method must be called only
- * for a true forward reference, i.e. only if this label is not resolved
- * yet. For backward references, the offset of the reference can be, and
- * must be, computed and stored directly.
- *
- * @param sourcePosition
- * the position of the referencing instruction. This position
- * will be used to compute the offset of this forward reference.
- * @param referencePosition
- * the position where the offset for this forward reference must
- * be stored.
- */
- private void addReference(final int sourcePosition,
- final int referencePosition) {
- if (srcAndRefPositions == null) {
- srcAndRefPositions = new int[6];
- }
- if (referenceCount >= srcAndRefPositions.length) {
- int[] a = new int[srcAndRefPositions.length + 6];
- System.arraycopy(srcAndRefPositions, 0, a, 0,
- srcAndRefPositions.length);
- srcAndRefPositions = a;
- }
- srcAndRefPositions[referenceCount++] = sourcePosition;
- srcAndRefPositions[referenceCount++] = referencePosition;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods to compute offsets and to manage forward references
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Puts a reference to this label in the bytecode of a method. If the bytecode offset of the label
+ * is known, the relative bytecode offset between the label and the instruction referencing it is
+ * computed and written directly. Otherwise, a null relative offset is written and a new forward
+ * reference is declared for this label.
+ *
+ * @param code the bytecode of the method. This is where the reference is appended.
+ * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the
+ * reference to be appended.
+ * @param wideReference whether the reference must be stored in 4 bytes (instead of 2 bytes).
+ */
+ final void put(
+ final ByteVector code, final int sourceInsnBytecodeOffset, final boolean wideReference) {
+ if ((flags & FLAG_RESOLVED) == 0) {
+ if (wideReference) {
+ addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_WIDE, code.length);
+ code.putInt(-1);
+ } else {
+ addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_SHORT, code.length);
+ code.putShort(-1);
+ }
+ } else {
+ if (wideReference) {
+ code.putInt(bytecodeOffset - sourceInsnBytecodeOffset);
+ } else {
+ code.putShort(bytecodeOffset - sourceInsnBytecodeOffset);
+ }
}
-
- /**
- * Resolves all forward references to this label. This method must be called
- * when this label is added to the bytecode of the method, i.e. when its
- * position becomes known. This method fills in the blanks that where left
- * in the bytecode by each forward reference previously added to this label.
- *
- * @param owner
- * the code writer that calls this method.
- * @param position
- * the position of this label in the bytecode.
- * @param data
- * the bytecode of the method.
- * @return true if a blank that was left for this label was to
- * small to store the offset. In such a case the corresponding jump
- * instruction is replaced with a pseudo instruction (using unused
- * opcodes) using an unsigned two bytes offset. These pseudo
- * instructions will need to be replaced with true instructions with
- * wider offsets (4 bytes instead of 2). This is done in
- * {@link MethodWriter#resizeInstructions}.
- * @throws IllegalArgumentException
- * if this label has already been resolved, or if it has not
- * been created by the given code writer.
- */
- boolean resolve(final MethodWriter owner, final int position,
- final byte[] data) {
- boolean needUpdate = false;
- this.status |= RESOLVED;
- this.position = position;
- int i = 0;
- while (i < referenceCount) {
- int source = srcAndRefPositions[i++];
- int reference = srcAndRefPositions[i++];
- int offset;
- if (source >= 0) {
- offset = position - source;
- if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) {
- /*
- * changes the opcode of the jump instruction, in order to
- * be able to find it later (see resizeInstructions in
- * MethodWriter). These temporary opcodes are similar to
- * jump instruction opcodes, except that the 2 bytes offset
- * is unsigned (and can therefore represent values from 0 to
- * 65535, which is sufficient since the size of a method is
- * limited to 65535 bytes).
- */
- int opcode = data[reference - 1] & 0xFF;
- if (opcode <= Opcodes.JSR) {
- // changes IFEQ ... JSR to opcodes 202 to 217
- data[reference - 1] = (byte) (opcode + 49);
- } else {
- // changes IFNULL and IFNONNULL to opcodes 218 and 219
- data[reference - 1] = (byte) (opcode + 20);
- }
- needUpdate = true;
- }
- data[reference++] = (byte) (offset >>> 8);
- data[reference] = (byte) offset;
- } else {
- offset = position + source + 1;
- data[reference++] = (byte) (offset >>> 24);
- data[reference++] = (byte) (offset >>> 16);
- data[reference++] = (byte) (offset >>> 8);
- data[reference] = (byte) offset;
- }
- }
- return needUpdate;
+ }
+
+ /**
+ * Adds a forward reference to this label. This method must be called only for a true forward
+ * reference, i.e. only if this label is not resolved yet. For backward references, the relative
+ * bytecode offset of the reference can be, and must be, computed and stored directly.
+ *
+ * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the
+ * reference stored at referenceHandle.
+ * @param referenceType either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link
+ * #FORWARD_REFERENCE_TYPE_WIDE}.
+ * @param referenceHandle the offset in the bytecode where the forward reference value must be
+ * stored.
+ */
+ private void addForwardReference(
+ final int sourceInsnBytecodeOffset, final int referenceType, final int referenceHandle) {
+ if (forwardReferences == null) {
+ forwardReferences = new int[FORWARD_REFERENCES_CAPACITY_INCREMENT];
}
-
- /**
- * Returns the first label of the series to which this label belongs. For an
- * isolated label or for the first label in a series of successive labels,
- * this method returns the label itself. For other labels it returns the
- * first label of the series.
- *
- * @return the first label of the series to which this label belongs.
- */
- Label getFirst() {
- return !ClassReader.FRAMES || frame == null ? this : frame.owner;
+ int lastElementIndex = forwardReferences[0];
+ if (lastElementIndex + 2 >= forwardReferences.length) {
+ int[] newValues = new int[forwardReferences.length + FORWARD_REFERENCES_CAPACITY_INCREMENT];
+ System.arraycopy(forwardReferences, 0, newValues, 0, forwardReferences.length);
+ forwardReferences = newValues;
}
-
- // ------------------------------------------------------------------------
- // Methods related to subroutines
- // ------------------------------------------------------------------------
-
- /**
- * Returns true is this basic block belongs to the given subroutine.
- *
- * @param id
- * a subroutine id.
- * @return true is this basic block belongs to the given subroutine.
- */
- boolean inSubroutine(final long id) {
- if ((status & Label.VISITED) != 0) {
- return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0;
- }
- return false;
+ forwardReferences[++lastElementIndex] = sourceInsnBytecodeOffset;
+ forwardReferences[++lastElementIndex] = referenceType | referenceHandle;
+ forwardReferences[0] = lastElementIndex;
+ }
+
+ /**
+ * Sets the bytecode offset of this label to the given value and resolves the forward references
+ * to this label, if any. This method must be called when this label is added to the bytecode of
+ * the method, i.e. when its bytecode offset becomes known. This method fills in the blanks that
+ * where left in the bytecode by each forward reference previously added to this label.
+ *
+ * @param code the bytecode of the method.
+ * @param bytecodeOffset the bytecode offset of this label.
+ * @return {@literal true} if a blank that was left for this label was too small to store the
+ * offset. In such a case the corresponding jump instruction is replaced with an equivalent
+ * ASM specific instruction using an unsigned two bytes offset. These ASM specific
+ * instructions are later replaced with standard bytecode instructions with wider offsets (4
+ * bytes instead of 2), in ClassReader.
+ */
+ final boolean resolve(final byte[] code, final int bytecodeOffset) {
+ this.flags |= FLAG_RESOLVED;
+ this.bytecodeOffset = bytecodeOffset;
+ if (forwardReferences == null) {
+ return false;
}
-
- /**
- * Returns true if this basic block and the given one belong to a common
- * subroutine.
- *
- * @param block
- * another basic block.
- * @return true if this basic block and the given one belong to a common
- * subroutine.
- */
- boolean inSameSubroutine(final Label block) {
- if ((status & VISITED) == 0 || (block.status & VISITED) == 0) {
- return false;
- }
- for (int i = 0; i < srcAndRefPositions.length; ++i) {
- if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) {
- return true;
- }
+ boolean hasAsmInstructions = false;
+ for (int i = forwardReferences[0]; i > 0; i -= 2) {
+ final int sourceInsnBytecodeOffset = forwardReferences[i - 1];
+ final int reference = forwardReferences[i];
+ final int relativeOffset = bytecodeOffset - sourceInsnBytecodeOffset;
+ int handle = reference & FORWARD_REFERENCE_HANDLE_MASK;
+ if ((reference & FORWARD_REFERENCE_TYPE_MASK) == FORWARD_REFERENCE_TYPE_SHORT) {
+ if (relativeOffset < Short.MIN_VALUE || relativeOffset > Short.MAX_VALUE) {
+ // Change the opcode of the jump instruction, in order to be able to find it later in
+ // ClassReader. These ASM specific opcodes are similar to jump instruction opcodes, except
+ // that the 2 bytes offset is unsigned (and can therefore represent values from 0 to
+ // 65535, which is sufficient since the size of a method is limited to 65535 bytes).
+ int opcode = code[sourceInsnBytecodeOffset] & 0xFF;
+ if (opcode < Opcodes.IFNULL) {
+ // Change IFEQ ... JSR to ASM_IFEQ ... ASM_JSR.
+ code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_OPCODE_DELTA);
+ } else {
+ // Change IFNULL and IFNONNULL to ASM_IFNULL and ASM_IFNONNULL.
+ code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_IFNULL_OPCODE_DELTA);
+ }
+ hasAsmInstructions = true;
}
- return false;
+ code[handle++] = (byte) (relativeOffset >>> 8);
+ code[handle] = (byte) relativeOffset;
+ } else {
+ code[handle++] = (byte) (relativeOffset >>> 24);
+ code[handle++] = (byte) (relativeOffset >>> 16);
+ code[handle++] = (byte) (relativeOffset >>> 8);
+ code[handle] = (byte) relativeOffset;
+ }
}
-
- /**
- * Marks this basic block as belonging to the given subroutine.
- *
- * @param id
- * a subroutine id.
- * @param nbSubroutines
- * the total number of subroutines in the method.
- */
- void addToSubroutine(final long id, final int nbSubroutines) {
- if ((status & VISITED) == 0) {
- status |= VISITED;
- srcAndRefPositions = new int[(nbSubroutines - 1) / 32 + 1];
- }
- srcAndRefPositions[(int) (id >>> 32)] |= (int) id;
+ return hasAsmInstructions;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods related to subroutines
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Finds the basic blocks that belong to the subroutine starting with the basic block
+ * corresponding to this label, and marks these blocks as belonging to this subroutine. This
+ * method follows the control flow graph to find all the blocks that are reachable from the
+ * current basic block WITHOUT following any jsr target.
+ *
+ *
Note: a precondition and postcondition of this method is that all labels must have a null
+ * {@link #nextListElement}.
+ *
+ * @param subroutineId the id of the subroutine starting with the basic block corresponding to
+ * this label.
+ */
+ final void markSubroutine(final short subroutineId) {
+ // Data flow algorithm: put this basic block in a list of blocks to process (which are blocks
+ // belonging to subroutine subroutineId) and, while there are blocks to process, remove one from
+ // the list, mark it as belonging to the subroutine, and add its successor basic blocks in the
+ // control flow graph to the list of blocks to process (if not already done).
+ Label listOfBlocksToProcess = this;
+ listOfBlocksToProcess.nextListElement = EMPTY_LIST;
+ while (listOfBlocksToProcess != EMPTY_LIST) {
+ // Remove a basic block from the list of blocks to process.
+ Label basicBlock = listOfBlocksToProcess;
+ listOfBlocksToProcess = listOfBlocksToProcess.nextListElement;
+ basicBlock.nextListElement = null;
+
+ // If it is not already marked as belonging to a subroutine, mark it as belonging to
+ // subroutineId and add its successors to the list of blocks to process (unless already done).
+ if (basicBlock.subroutineId == 0) {
+ basicBlock.subroutineId = subroutineId;
+ listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess);
+ }
}
-
- /**
- * Finds the basic blocks that belong to a given subroutine, and marks these
- * blocks as belonging to this subroutine. This method follows the control
- * flow graph to find all the blocks that are reachable from the current
- * block WITHOUT following any JSR target.
- *
- * @param JSR
- * a JSR block that jumps to this subroutine. If this JSR is not
- * null it is added to the successor of the RET blocks found in
- * the subroutine.
- * @param id
- * the id of this subroutine.
- * @param nbSubroutines
- * the total number of subroutines in the method.
- */
- void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) {
- // user managed stack of labels, to avoid using a recursive method
- // (recursivity can lead to stack overflow with very large methods)
- Label stack = this;
- while (stack != null) {
- // removes a label l from the stack
- Label l = stack;
- stack = l.next;
- l.next = null;
-
- if (JSR != null) {
- if ((l.status & VISITED2) != 0) {
- continue;
- }
- l.status |= VISITED2;
- // adds JSR to the successors of l, if it is a RET block
- if ((l.status & RET) != 0) {
- if (!l.inSameSubroutine(JSR)) {
- Edge e = new Edge();
- e.info = l.inputStackTop;
- e.successor = JSR.successors.successor;
- e.next = l.successors;
- l.successors = e;
- }
- }
- } else {
- // if the l block already belongs to subroutine 'id', continue
- if (l.inSubroutine(id)) {
- continue;
- }
- // marks the l block as belonging to subroutine 'id'
- l.addToSubroutine(id, nbSubroutines);
- }
- // pushes each successor of l on the stack, except JSR targets
- Edge e = l.successors;
- while (e != null) {
- // if the l block is a JSR block, then 'l.successors.next' leads
- // to the JSR target (see {@link #visitJumpInsn}) and must
- // therefore not be followed
- if ((l.status & Label.JSR) == 0 || e != l.successors.next) {
- // pushes e.successor on the stack if it not already added
- if (e.successor.next == null) {
- e.successor.next = stack;
- stack = e.successor;
- }
- }
- e = e.next;
- }
- }
+ }
+
+ /**
+ * Finds the basic blocks that end a subroutine starting with the basic block corresponding to
+ * this label and, for each one of them, adds an outgoing edge to the basic block following the
+ * given subroutine call. In other words, completes the control flow graph by adding the edges
+ * corresponding to the return from this subroutine, when called from the given caller basic
+ * block.
+ *
+ *
Note: a precondition and postcondition of this method is that all labels must have a null
+ * {@link #nextListElement}.
+ *
+ * @param subroutineCaller a basic block that ends with a jsr to the basic block corresponding to
+ * this label. This label is supposed to correspond to the start of a subroutine.
+ */
+ final void addSubroutineRetSuccessors(final Label subroutineCaller) {
+ // Data flow algorithm: put this basic block in a list blocks to process (which are blocks
+ // belonging to a subroutine starting with this label) and, while there are blocks to process,
+ // remove one from the list, put it in a list of blocks that have been processed, add a return
+ // edge to the successor of subroutineCaller if applicable, and add its successor basic blocks
+ // in the control flow graph to the list of blocks to process (if not already done).
+ Label listOfProcessedBlocks = EMPTY_LIST;
+ Label listOfBlocksToProcess = this;
+ listOfBlocksToProcess.nextListElement = EMPTY_LIST;
+ while (listOfBlocksToProcess != EMPTY_LIST) {
+ // Move a basic block from the list of blocks to process to the list of processed blocks.
+ Label basicBlock = listOfBlocksToProcess;
+ listOfBlocksToProcess = basicBlock.nextListElement;
+ basicBlock.nextListElement = listOfProcessedBlocks;
+ listOfProcessedBlocks = basicBlock;
+
+ // Add an edge from this block to the successor of the caller basic block, if this block is
+ // the end of a subroutine and if this block and subroutineCaller do not belong to the same
+ // subroutine.
+ if ((basicBlock.flags & FLAG_SUBROUTINE_END) != 0
+ && basicBlock.subroutineId != subroutineCaller.subroutineId) {
+ basicBlock.outgoingEdges =
+ new Edge(
+ basicBlock.outputStackSize,
+ // By construction, the first outgoing edge of a basic block that ends with a jsr
+ // instruction leads to the jsr continuation block, i.e. where execution continues
+ // when ret is called (see {@link #FLAG_SUBROUTINE_CALLER}).
+ subroutineCaller.outgoingEdges.successor,
+ basicBlock.outgoingEdges);
+ }
+ // Add its successors to the list of blocks to process. Note that {@link #pushSuccessors} does
+ // not push basic blocks which are already in a list. Here this means either in the list of
+ // blocks to process, or in the list of already processed blocks. This second list is
+ // important to make sure we don't reprocess an already processed block.
+ listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess);
}
-
- // ------------------------------------------------------------------------
- // Overriden Object methods
- // ------------------------------------------------------------------------
-
- /**
- * Returns a string representation of this label.
- *
- * @return a string representation of this label.
- */
- @Override
- public String toString() {
- return "L" + System.identityHashCode(this);
+ // Reset the {@link #nextListElement} of all the basic blocks that have been processed to null,
+ // so that this method can be called again with a different subroutine or subroutine caller.
+ while (listOfProcessedBlocks != EMPTY_LIST) {
+ Label newListOfProcessedBlocks = listOfProcessedBlocks.nextListElement;
+ listOfProcessedBlocks.nextListElement = null;
+ listOfProcessedBlocks = newListOfProcessedBlocks;
+ }
+ }
+
+ /**
+ * Adds the successors of this label in the method's control flow graph (except those
+ * corresponding to a jsr target, and those already in a list of labels) to the given list of
+ * blocks to process, and returns the new list.
+ *
+ * @param listOfLabelsToProcess a list of basic blocks to process, linked together with their
+ * {@link #nextListElement} field.
+ * @return the new list of blocks to process.
+ */
+ private Label pushSuccessors(final Label listOfLabelsToProcess) {
+ Label newListOfLabelsToProcess = listOfLabelsToProcess;
+ Edge outgoingEdge = outgoingEdges;
+ while (outgoingEdge != null) {
+ // By construction, the second outgoing edge of a basic block that ends with a jsr instruction
+ // leads to the jsr target (see {@link #FLAG_SUBROUTINE_CALLER}).
+ boolean isJsrTarget =
+ (flags & Label.FLAG_SUBROUTINE_CALLER) != 0 && outgoingEdge == outgoingEdges.nextEdge;
+ if (!isJsrTarget && outgoingEdge.successor.nextListElement == null) {
+ // Add this successor to the list of blocks to process, if it does not already belong to a
+ // list of labels.
+ outgoingEdge.successor.nextListElement = newListOfLabelsToProcess;
+ newListOfLabelsToProcess = outgoingEdge.successor;
+ }
+ outgoingEdge = outgoingEdge.nextEdge;
}
+ return newListOfLabelsToProcess;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Overridden Object methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns a string representation of this label.
+ *
+ * @return a string representation of this label.
+ */
+ @Override
+ public String toString() {
+ return "L" + System.identityHashCode(this);
+ }
}
diff --git a/src/java/nginx/clojure/asm/MethodTooLargeException.java b/src/java/nginx/clojure/asm/MethodTooLargeException.java
new file mode 100644
index 00000000..e6231ebe
--- /dev/null
+++ b/src/java/nginx/clojure/asm/MethodTooLargeException.java
@@ -0,0 +1,99 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+/**
+ * Exception thrown when the Code attribute of a method produced by a {@link ClassWriter} is too
+ * large.
+ *
+ * @author Jason Zaugg
+ */
+public final class MethodTooLargeException extends IndexOutOfBoundsException {
+ private static final long serialVersionUID = 6807380416709738314L;
+
+ private final String className;
+ private final String methodName;
+ private final String descriptor;
+ private final int codeSize;
+
+ /**
+ * Constructs a new {@link MethodTooLargeException}.
+ *
+ * @param className the internal name of the owner class (see {@link Type#getInternalName()}).
+ * @param methodName the name of the method.
+ * @param descriptor the descriptor of the method.
+ * @param codeSize the size of the method's Code attribute, in bytes.
+ */
+ public MethodTooLargeException(
+ final String className,
+ final String methodName,
+ final String descriptor,
+ final int codeSize) {
+ super("Method too large: " + className + "." + methodName + " " + descriptor);
+ this.className = className;
+ this.methodName = methodName;
+ this.descriptor = descriptor;
+ this.codeSize = codeSize;
+ }
+
+ /**
+ * Returns the internal name of the owner class.
+ *
+ * @return the internal name of the owner class (see {@link Type#getInternalName()}).
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ /**
+ * Returns the name of the method.
+ *
+ * @return the name of the method.
+ */
+ public String getMethodName() {
+ return methodName;
+ }
+
+ /**
+ * Returns the descriptor of the method.
+ *
+ * @return the descriptor of the method.
+ */
+ public String getDescriptor() {
+ return descriptor;
+ }
+
+ /**
+ * Returns the size of the method's Code attribute, in bytes.
+ *
+ * @return the size of the method's Code attribute, in bytes.
+ */
+ public int getCodeSize() {
+ return codeSize;
+ }
+}
diff --git a/src/java/nginx/clojure/asm/MethodVisitor.java b/src/java/nginx/clojure/asm/MethodVisitor.java
index 6698ecdb..40196478 100644
--- a/src/java/nginx/clojure/asm/MethodVisitor.java
+++ b/src/java/nginx/clojure/asm/MethodVisitor.java
@@ -1,662 +1,799 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A visitor to visit a Java method. The methods of this class must be called in
- * the following order: [ visitAnnotationDefault ] (
- * visitAnnotation | visitParameterAnnotation |
- * visitAttribute )* [ visitCode ( visitFrame |
- * visitXInsn | visitLabel |
- * visitTryCatchBlock | visitLocalVariable |
- * visitLineNumber )* visitMaxs ] visitEnd. In
- * addition, the visitXInsn and visitLabel methods
- * must be called in the sequential order of the bytecode instructions of the
- * visited code, visitTryCatchBlock must be called before the
- * labels passed as arguments have been visited, and the
- * visitLocalVariable and visitLineNumber methods must be
- * called after the labels passed as arguments have been visited.
- *
+ * A visitor to visit a Java method. The methods of this class must be called in the following
+ * order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} |
+ * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} {@code
+ * visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} |
+ * {@code visitXInsn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code
+ * visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code
+ * visitLocalVariableAnnotation} | {@code visitLineNumber} )* {@code visitMaxs} ] {@code visitEnd}.
+ * In addition, the {@code visitXInsn} and {@code visitLabel} methods must be called in the
+ * sequential order of the bytecode instructions of the visited code, {@code visitInsnAnnotation}
+ * must be called after the annotated instruction, {@code visitTryCatchBlock} must be called
+ * before the labels passed as arguments have been visited, {@code
+ * visitTryCatchBlockAnnotation} must be called after the corresponding try catch block has
+ * been visited, and the {@code visitLocalVariable}, {@code visitLocalVariableAnnotation} and {@code
+ * visitLineNumber} methods must be called after the labels passed as arguments have been
+ * visited.
+ *
* @author Eric Bruneton
*/
public abstract class MethodVisitor {
- /**
- * The ASM API version implemented by this visitor. The value of this field
- * must be one of {@link Opcodes#ASM4}.
- */
- protected final int api;
-
- /**
- * The method visitor to which this visitor must delegate method calls. May
- * be null.
- */
- protected MethodVisitor mv;
-
- /**
- * Constructs a new {@link MethodVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- */
- public MethodVisitor(final int api) {
- this(api, null);
- }
-
- /**
- * Constructs a new {@link MethodVisitor}.
- *
- * @param api
- * the ASM API version implemented by this visitor. Must be one
- * of {@link Opcodes#ASM4}.
- * @param mv
- * the method visitor to which this visitor must delegate method
- * calls. May be null.
- */
- public MethodVisitor(final int api, final MethodVisitor mv) {
- if (api != Opcodes.ASM4) {
- throw new IllegalArgumentException();
- }
- this.api = api;
- this.mv = mv;
- }
-
- // -------------------------------------------------------------------------
- // Annotations and non standard attributes
- // -------------------------------------------------------------------------
-
- /**
- * Visits the default value of this annotation interface method.
- *
- * @return a visitor to the visit the actual default value of this
- * annotation interface method, or null if this visitor is
- * not interested in visiting this default value. The 'name'
- * parameters passed to the methods of this annotation visitor are
- * ignored. Moreover, exacly one visit method must be called on this
- * annotation visitor, followed by visitEnd.
- */
- public AnnotationVisitor visitAnnotationDefault() {
- if (mv != null) {
- return mv.visitAnnotationDefault();
- }
- return null;
- }
-
- /**
- * Visits an annotation of this method.
- *
- * @param desc
- * the class descriptor of the annotation class.
- * @param visible
- * true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if
- * this visitor is not interested in visiting this annotation.
- */
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (mv != null) {
- return mv.visitAnnotation(desc, visible);
- }
- return null;
- }
-
- /**
- * Visits an annotation of a parameter this method.
- *
- * @param parameter
- * the parameter index.
- * @param desc
- * the class descriptor of the annotation class.
- * @param visible
- * true if the annotation is visible at runtime.
- * @return a visitor to visit the annotation values, or null if
- * this visitor is not interested in visiting this annotation.
- */
- public AnnotationVisitor visitParameterAnnotation(int parameter,
- String desc, boolean visible) {
- if (mv != null) {
- return mv.visitParameterAnnotation(parameter, desc, visible);
- }
- return null;
- }
-
- /**
- * Visits a non standard attribute of this method.
- *
- * @param attr
- * an attribute.
- */
- public void visitAttribute(Attribute attr) {
- if (mv != null) {
- mv.visitAttribute(attr);
- }
- }
-
- /**
- * Starts the visit of the method's code, if any (i.e. non abstract method).
- */
- public void visitCode() {
- if (mv != null) {
- mv.visitCode();
- }
- }
-
- /**
- * Visits the current state of the local variables and operand stack
- * elements. This method must(*) be called just before any
- * instruction i that follows an unconditional branch instruction
- * such as GOTO or THROW, that is the target of a jump instruction, or that
- * starts an exception handler block. The visited types must describe the
- * values of the local variables and of the operand stack elements just
- * before i is executed.
- *
- * (*) this is mandatory only for classes whose version is greater than or
- * equal to {@link Opcodes#V1_6 V1_6}.
- *
- * The frames of a method must be given either in expanded form, or in
- * compressed form (all frames must use the same format, i.e. you must not
- * mix expanded and compressed frames within a single method):
- *
- * - In expanded form, all frames must have the F_NEW type.
- * - In compressed form, frames are basically "deltas" from the state of
- * the previous frame:
- *
- * - {@link Opcodes#F_SAME} representing frame with exactly the same
- * locals as the previous frame and with the empty stack.
- * - {@link Opcodes#F_SAME1} representing frame with exactly the same
- * locals as the previous frame and with single value on the stack (
- *
nStack
is 1 and stack[0]
contains value for the
- * type of the stack item).
- * - {@link Opcodes#F_APPEND} representing frame with current locals are
- * the same as the locals in the previous frame, except that additional
- * locals are defined (
nLocal
is 1, 2 or 3 and
- * local
elements contains values representing added types).
- * - {@link Opcodes#F_CHOP} representing frame with current locals are the
- * same as the locals in the previous frame, except that the last 1-3 locals
- * are absent and with the empty stack (
nLocals
is 1, 2 or 3).
- * - {@link Opcodes#F_FULL} representing complete frame data.
- *
- *
- * In both cases the first frame, corresponding to the method's parameters
- * and access flags, is implicit and must not be visited. Also, it is
- * illegal to visit two or more frames for the same code location (i.e., at
- * least one instruction must be visited between two calls to visitFrame).
- *
- * @param type
- * the type of this stack map frame. Must be
- * {@link Opcodes#F_NEW} for expanded frames, or
- * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND},
- * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or
- * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for
- * compressed frames.
- * @param nLocal
- * the number of local variables in the visited frame.
- * @param local
- * the local variable types in this frame. This array must not be
- * modified. Primitive types are represented by
- * {@link Opcodes#TOP}, {@link Opcodes#INTEGER},
- * {@link Opcodes#FLOAT}, {@link Opcodes#LONG},
- * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or
- * {@link Opcodes#UNINITIALIZED_THIS} (long and double are
- * represented by a single element). Reference types are
- * represented by String objects (representing internal names),
- * and uninitialized types by Label objects (this label
- * designates the NEW instruction that created this uninitialized
- * value).
- * @param nStack
- * the number of operand stack elements in the visited frame.
- * @param stack
- * the operand stack types in this frame. This array must not be
- * modified. Its content has the same format as the "local"
- * array.
- * @throws IllegalStateException
- * if a frame is visited just after another one, without any
- * instruction between the two (unless this frame is a
- * Opcodes#F_SAME frame, in which case it is silently ignored).
- */
- public void visitFrame(int type, int nLocal, Object[] local, int nStack,
- Object[] stack) {
- if (mv != null) {
- mv.visitFrame(type, nLocal, local, nStack, stack);
- }
- }
-
- // -------------------------------------------------------------------------
- // Normal instructions
- // -------------------------------------------------------------------------
-
- /**
- * Visits a zero operand instruction.
- *
- * @param opcode
- * the opcode of the instruction to be visited. This opcode is
- * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1,
- * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1,
- * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD,
- * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD,
- * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE,
- * SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1,
- * DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB,
- * IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM,
- * FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR,
- * IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D,
- * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S,
- * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN,
- * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER,
- * or MONITOREXIT.
- */
- public void visitInsn(int opcode) {
- if (mv != null) {
- mv.visitInsn(opcode);
- }
- }
-
- /**
- * Visits an instruction with a single int operand.
- *
- * @param opcode
- * the opcode of the instruction to be visited. This opcode is
- * either BIPUSH, SIPUSH or NEWARRAY.
- * @param operand
- * the operand of the instruction to be visited.
- * When opcode is BIPUSH, operand value should be between
- * Byte.MIN_VALUE and Byte.MAX_VALUE.
- * When opcode is SIPUSH, operand value should be between
- * Short.MIN_VALUE and Short.MAX_VALUE.
- * When opcode is NEWARRAY, operand value should be one of
- * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR},
- * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE},
- * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT},
- * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
- */
- public void visitIntInsn(int opcode, int operand) {
- if (mv != null) {
- mv.visitIntInsn(opcode, operand);
- }
- }
-
- /**
- * Visits a local variable instruction. A local variable instruction is an
- * instruction that loads or stores the value of a local variable.
- *
- * @param opcode
- * the opcode of the local variable instruction to be visited.
- * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD,
- * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
- * @param var
- * the operand of the instruction to be visited. This operand is
- * the index of a local variable.
- */
- public void visitVarInsn(int opcode, int var) {
- if (mv != null) {
- mv.visitVarInsn(opcode, var);
- }
- }
-
- /**
- * Visits a type instruction. A type instruction is an instruction that
- * takes the internal name of a class as parameter.
- *
- * @param opcode
- * the opcode of the type instruction to be visited. This opcode
- * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF.
- * @param type
- * the operand of the instruction to be visited. This operand
- * must be the internal name of an object or array class (see
- * {@link Type#getInternalName() getInternalName}).
- */
- public void visitTypeInsn(int opcode, String type) {
- if (mv != null) {
- mv.visitTypeInsn(opcode, type);
- }
- }
-
- /**
- * Visits a field instruction. A field instruction is an instruction that
- * loads or stores the value of a field of an object.
- *
- * @param opcode
- * the opcode of the type instruction to be visited. This opcode
- * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
- * @param owner
- * the internal name of the field's owner class (see
- * {@link Type#getInternalName() getInternalName}).
- * @param name
- * the field's name.
- * @param desc
- * the field's descriptor (see {@link Type Type}).
- */
- public void visitFieldInsn(int opcode, String owner, String name,
- String desc) {
- if (mv != null) {
- mv.visitFieldInsn(opcode, owner, name, desc);
- }
- }
-
- /**
- * Visits a method instruction. A method instruction is an instruction that
- * invokes a method.
- *
- * @param opcode
- * the opcode of the type instruction to be visited. This opcode
- * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or
- * INVOKEINTERFACE.
- * @param owner
- * the internal name of the method's owner class (see
- * {@link Type#getInternalName() getInternalName}).
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor (see {@link Type Type}).
- */
- public void visitMethodInsn(int opcode, String owner, String name,
- String desc) {
- if (mv != null) {
- mv.visitMethodInsn(opcode, owner, name, desc);
- }
- }
-
- /**
- * Visits an invokedynamic instruction.
- *
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor (see {@link Type Type}).
- * @param bsm
- * the bootstrap method.
- * @param bsmArgs
- * the bootstrap method constant arguments. Each argument must be
- * an {@link Integer}, {@link Float}, {@link Long},
- * {@link Double}, {@link String}, {@link Type} or {@link Handle}
- * value. This method is allowed to modify the content of the
- * array so a caller should expect that this array may change.
- */
- public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
- Object... bsmArgs) {
- if (mv != null) {
- mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
- }
- }
-
- /**
- * Visits a jump instruction. A jump instruction is an instruction that may
- * jump to another instruction.
- *
- * @param opcode
- * the opcode of the type instruction to be visited. This opcode
- * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ,
- * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE,
- * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
- * @param label
- * the operand of the instruction to be visited. This operand is
- * a label that designates the instruction to which the jump
- * instruction may jump.
- */
- public void visitJumpInsn(int opcode, Label label) {
- if (mv != null) {
- mv.visitJumpInsn(opcode, label);
- }
- }
-
- /**
- * Visits a label. A label designates the instruction that will be visited
- * just after it.
- *
- * @param label
- * a {@link Label Label} object.
- */
- public void visitLabel(Label label) {
- if (mv != null) {
- mv.visitLabel(label);
- }
- }
-
- // -------------------------------------------------------------------------
- // Special instructions
- // -------------------------------------------------------------------------
-
- /**
- * Visits a LDC instruction. Note that new constant types may be added in
- * future versions of the Java Virtual Machine. To easily detect new
- * constant types, implementations of this method should check for
- * unexpected constant types, like this:
- *
- *
- * if (cst instanceof Integer) {
- * // ...
- * } else if (cst instanceof Float) {
- * // ...
- * } else if (cst instanceof Long) {
- * // ...
- * } else if (cst instanceof Double) {
- * // ...
- * } else if (cst instanceof String) {
- * // ...
- * } else if (cst instanceof Type) {
- * int sort = ((Type) cst).getSort();
- * if (sort == Type.OBJECT) {
- * // ...
- * } else if (sort == Type.ARRAY) {
- * // ...
- * } else if (sort == Type.METHOD) {
- * // ...
- * } else {
- * // throw an exception
- * }
- * } else if (cst instanceof Handle) {
- * // ...
- * } else {
- * // throw an exception
- * }
- *
- *
- * @param cst
- * the constant to be loaded on the stack. This parameter must be
- * a non null {@link Integer}, a {@link Float}, a {@link Long}, a
- * {@link Double}, a {@link String}, a {@link Type} of OBJECT or
- * ARRAY sort for .class constants, for classes whose
- * version is 49.0, a {@link Type} of METHOD sort or a
- * {@link Handle} for MethodType and MethodHandle constants, for
- * classes whose version is 51.0.
- */
- public void visitLdcInsn(Object cst) {
- if (mv != null) {
- mv.visitLdcInsn(cst);
- }
- }
-
- /**
- * Visits an IINC instruction.
- *
- * @param var
- * index of the local variable to be incremented.
- * @param increment
- * amount to increment the local variable by.
- */
- public void visitIincInsn(int var, int increment) {
- if (mv != null) {
- mv.visitIincInsn(var, increment);
- }
- }
-
- /**
- * Visits a TABLESWITCH instruction.
- *
- * @param min
- * the minimum key value.
- * @param max
- * the maximum key value.
- * @param dflt
- * beginning of the default handler block.
- * @param labels
- * beginnings of the handler blocks. labels[i] is the
- * beginning of the handler block for the min + i key.
- */
- public void visitTableSwitchInsn(int min, int max, Label dflt,
- Label... labels) {
- if (mv != null) {
- mv.visitTableSwitchInsn(min, max, dflt, labels);
- }
- }
-
- /**
- * Visits a LOOKUPSWITCH instruction.
- *
- * @param dflt
- * beginning of the default handler block.
- * @param keys
- * the values of the keys.
- * @param labels
- * beginnings of the handler blocks. labels[i] is the
- * beginning of the handler block for the keys[i] key.
- */
- public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
- if (mv != null) {
- mv.visitLookupSwitchInsn(dflt, keys, labels);
- }
- }
-
- /**
- * Visits a MULTIANEWARRAY instruction.
- *
- * @param desc
- * an array type descriptor (see {@link Type Type}).
- * @param dims
- * number of dimensions of the array to allocate.
- */
- public void visitMultiANewArrayInsn(String desc, int dims) {
- if (mv != null) {
- mv.visitMultiANewArrayInsn(desc, dims);
- }
- }
-
- // -------------------------------------------------------------------------
- // Exceptions table entries, debug information, max stack and max locals
- // -------------------------------------------------------------------------
-
- /**
- * Visits a try catch block.
- *
- * @param start
- * beginning of the exception handler's scope (inclusive).
- * @param end
- * end of the exception handler's scope (exclusive).
- * @param handler
- * beginning of the exception handler's code.
- * @param type
- * internal name of the type of exceptions handled by the
- * handler, or null to catch any exceptions (for
- * "finally" blocks).
- * @throws IllegalArgumentException
- * if one of the labels has already been visited by this visitor
- * (by the {@link #visitLabel visitLabel} method).
- */
- public void visitTryCatchBlock(Label start, Label end, Label handler,
- String type) {
- if (mv != null) {
- mv.visitTryCatchBlock(start, end, handler, type);
- }
- }
-
- /**
- * Visits a local variable declaration.
- *
- * @param name
- * the name of a local variable.
- * @param desc
- * the type descriptor of this local variable.
- * @param signature
- * the type signature of this local variable. May be
- * null if the local variable type does not use generic
- * types.
- * @param start
- * the first instruction corresponding to the scope of this local
- * variable (inclusive).
- * @param end
- * the last instruction corresponding to the scope of this local
- * variable (exclusive).
- * @param index
- * the local variable's index.
- * @throws IllegalArgumentException
- * if one of the labels has not already been visited by this
- * visitor (by the {@link #visitLabel visitLabel} method).
- */
- public void visitLocalVariable(String name, String desc, String signature,
- Label start, Label end, int index) {
- if (mv != null) {
- mv.visitLocalVariable(name, desc, signature, start, end, index);
- }
- }
-
- /**
- * Visits a line number declaration.
- *
- * @param line
- * a line number. This number refers to the source file from
- * which the class was compiled.
- * @param start
- * the first instruction corresponding to this line number.
- * @throws IllegalArgumentException
- * if start has not already been visited by this
- * visitor (by the {@link #visitLabel visitLabel} method).
- */
- public void visitLineNumber(int line, Label start) {
- if (mv != null) {
- mv.visitLineNumber(line, start);
- }
- }
-
- /**
- * Visits the maximum stack size and the maximum number of local variables
- * of the method.
- *
- * @param maxStack
- * maximum stack size of the method.
- * @param maxLocals
- * maximum number of local variables for the method.
- */
- public void visitMaxs(int maxStack, int maxLocals) {
- if (mv != null) {
- mv.visitMaxs(maxStack, maxLocals);
- }
- }
-
- /**
- * Visits the end of the method. This method, which is the last one to be
- * called, is used to inform the visitor that all the annotations and
- * attributes of the method have been visited.
- */
- public void visitEnd() {
- if (mv != null) {
- mv.visitEnd();
- }
+ private static final String REQUIRES_ASM5 = "This feature requires ASM5";
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
+ */
+ protected final int api;
+
+ /**
+ * The method visitor to which this visitor must delegate method calls. May be {@literal null}.
+ */
+ protected MethodVisitor mv;
+
+ /**
+ * Constructs a new {@link MethodVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ */
+ protected MethodVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link MethodVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
+ * @param methodVisitor the method visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ protected MethodVisitor(final int api, final MethodVisitor methodVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
+ }
+ this.api = api;
+ this.mv = methodVisitor;
+ }
+
+ /**
+ * The method visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the method visitor to which this visitor must delegate method calls, or {@literal
+ * null}.
+ */
+ public MethodVisitor getDelegate() {
+ return mv;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Parameters, annotations and non standard attributes
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Visits a parameter of this method.
+ *
+ * @param name parameter name or {@literal null} if none is provided.
+ * @param access the parameter's access flags, only {@code ACC_FINAL}, {@code ACC_SYNTHETIC}
+ * or/and {@code ACC_MANDATED} are allowed (see {@link Opcodes}).
+ */
+ public void visitParameter(final String name, final int access) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ mv.visitParameter(name, access);
+ }
+ }
+
+ /**
+ * Visits the default value of this annotation interface method.
+ *
+ * @return a visitor to the visit the actual default value of this annotation interface method, or
+ * {@literal null} if this visitor is not interested in visiting this default value. The
+ * 'name' parameters passed to the methods of this annotation visitor are ignored. Moreover,
+ * exactly one visit method must be called on this annotation visitor, followed by visitEnd.
+ */
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (mv != null) {
+ return mv.visitAnnotationDefault();
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation of this method.
+ *
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (mv != null) {
+ return mv.visitAnnotation(descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on a type in the method signature.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#METHOD_TYPE_PARAMETER}, {@link
+ * TypeReference#METHOD_TYPE_PARAMETER_BOUND}, {@link TypeReference#METHOD_RETURN}, {@link
+ * TypeReference#METHOD_RECEIVER}, {@link TypeReference#METHOD_FORMAL_PARAMETER} or {@link
+ * TypeReference#THROWS}. See {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ return mv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits the number of method parameters that can have annotations. By default (i.e. when this
+ * method is not called), all the method parameters defined by the method descriptor can have
+ * annotations.
+ *
+ * @param parameterCount the number of method parameters than can have annotations. This number
+ * must be less or equal than the number of parameter types in the method descriptor. It can
+ * be strictly less when a method has synthetic parameters and when these parameters are
+ * ignored when computing parameter indices for the purpose of parameter annotations (see
+ * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18).
+ * @param visible {@literal true} to define the number of method parameters that can have
+ * annotations visible at runtime, {@literal false} to define the number of method parameters
+ * that can have annotations invisible at runtime.
+ */
+ public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
+ if (mv != null) {
+ mv.visitAnnotableParameterCount(parameterCount, visible);
+ }
+ }
+
+ /**
+ * Visits an annotation of a parameter this method.
+ *
+ * @param parameter the parameter index. This index must be strictly smaller than the number of
+ * parameters in the method descriptor, and strictly smaller than the parameter count
+ * specified in {@link #visitAnnotableParameterCount}. Important note: a parameter index i
+ * is not required to correspond to the i'th parameter descriptor in the method
+ * descriptor, in particular in case of synthetic parameters (see
+ * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18).
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitParameterAnnotation(
+ final int parameter, final String descriptor, final boolean visible) {
+ if (mv != null) {
+ return mv.visitParameterAnnotation(parameter, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of this method.
+ *
+ * @param attribute an attribute.
+ */
+ public void visitAttribute(final Attribute attribute) {
+ if (mv != null) {
+ mv.visitAttribute(attribute);
+ }
+ }
+
+ /** Starts the visit of the method's code, if any (i.e. non abstract method). */
+ public void visitCode() {
+ if (mv != null) {
+ mv.visitCode();
+ }
+ }
+
+ /**
+ * Visits the current state of the local variables and operand stack elements. This method must(*)
+ * be called just before any instruction i that follows an unconditional branch
+ * instruction such as GOTO or THROW, that is the target of a jump instruction, or that starts an
+ * exception handler block. The visited types must describe the values of the local variables and
+ * of the operand stack elements just before i is executed.
+ *
+ * (*) this is mandatory only for classes whose version is greater than or equal to {@link
+ * Opcodes#V1_6}.
+ *
+ * The frames of a method must be given either in expanded form, or in compressed form (all frames
+ * must use the same format, i.e. you must not mix expanded and compressed frames within a single
+ * method):
+ *
+ *
+ * - In expanded form, all frames must have the F_NEW type.
+ *
- In compressed form, frames are basically "deltas" from the state of the previous frame:
+ *
+ * - {@link Opcodes#F_SAME} representing frame with exactly the same locals as the
+ * previous frame and with the empty stack.
+ *
- {@link Opcodes#F_SAME1} representing frame with exactly the same locals as the
+ * previous frame and with single value on the stack (
numStack
is 1 and
+ * stack[0]
contains value for the type of the stack item).
+ * - {@link Opcodes#F_APPEND} representing frame with current locals are the same as the
+ * locals in the previous frame, except that additional locals are defined (
+ * numLocal
is 1, 2 or 3 and local
elements contains values
+ * representing added types).
+ * - {@link Opcodes#F_CHOP} representing frame with current locals are the same as the
+ * locals in the previous frame, except that the last 1-3 locals are absent and with
+ * the empty stack (
numLocal
is 1, 2 or 3).
+ * - {@link Opcodes#F_FULL} representing complete frame data.
+ *
+ *
+ *
+ *
+ * In both cases the first frame, corresponding to the method's parameters and access flags, is
+ * implicit and must not be visited. Also, it is illegal to visit two or more frames for the same
+ * code location (i.e., at least one instruction must be visited between two calls to visitFrame).
+ *
+ * @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded
+ * frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link
+ * Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames.
+ * @param numLocal the number of local variables in the visited frame. Long and double values
+ * count for one variable.
+ * @param local the local variable types in this frame. This array must not be modified. Primitive
+ * types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
+ * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or
+ * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element).
+ * Reference types are represented by String objects (representing internal names, see {@link
+ * Type#getInternalName()}), and uninitialized types by Label objects (this label designates
+ * the NEW instruction that created this uninitialized value).
+ * @param numStack the number of operand stack elements in the visited frame. Long and double
+ * values count for one stack element.
+ * @param stack the operand stack types in this frame. This array must not be modified. Its
+ * content has the same format as the "local" array.
+ * @throws IllegalStateException if a frame is visited just after another one, without any
+ * instruction between the two (unless this frame is a Opcodes#F_SAME frame, in which case it
+ * is silently ignored).
+ */
+ public void visitFrame(
+ final int type,
+ final int numLocal,
+ final Object[] local,
+ final int numStack,
+ final Object[] stack) {
+ if (mv != null) {
+ mv.visitFrame(type, numLocal, local, numStack, stack);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Normal instructions
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Visits a zero operand instruction.
+ *
+ * @param opcode the opcode of the instruction to be visited. This opcode is either NOP,
+ * ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5,
+ * LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD,
+ * FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE,
+ * AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2,
+ * SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV,
+ * FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR,
+ * LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I,
+ * D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN,
+ * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT.
+ */
+ public void visitInsn(final int opcode) {
+ if (mv != null) {
+ mv.visitInsn(opcode);
+ }
+ }
+
+ /**
+ * Visits an instruction with a single int operand.
+ *
+ * @param opcode the opcode of the instruction to be visited. This opcode is either BIPUSH, SIPUSH
+ * or NEWARRAY.
+ * @param operand the operand of the instruction to be visited.
+ * When opcode is BIPUSH, operand value should be between Byte.MIN_VALUE and Byte.MAX_VALUE.
+ *
+ * When opcode is SIPUSH, operand value should be between Short.MIN_VALUE and Short.MAX_VALUE.
+ *
+ * When opcode is NEWARRAY, operand value should be one of {@link Opcodes#T_BOOLEAN}, {@link
+ * Opcodes#T_CHAR}, {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, {@link Opcodes#T_BYTE},
+ * {@link Opcodes#T_SHORT}, {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
+ */
+ public void visitIntInsn(final int opcode, final int operand) {
+ if (mv != null) {
+ mv.visitIntInsn(opcode, operand);
+ }
+ }
+
+ /**
+ * Visits a local variable instruction. A local variable instruction is an instruction that loads
+ * or stores the value of a local variable.
+ *
+ * @param opcode the opcode of the local variable instruction to be visited. This opcode is either
+ * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
+ * @param varIndex the operand of the instruction to be visited. This operand is the index of a
+ * local variable.
+ */
+ public void visitVarInsn(final int opcode, final int varIndex) {
+ if (mv != null) {
+ mv.visitVarInsn(opcode, varIndex);
+ }
+ }
+
+ /**
+ * Visits a type instruction. A type instruction is an instruction that takes the internal name of
+ * a class as parameter (see {@link Type#getInternalName()}).
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either NEW,
+ * ANEWARRAY, CHECKCAST or INSTANCEOF.
+ * @param type the operand of the instruction to be visited. This operand must be the internal
+ * name of an object or array class (see {@link Type#getInternalName()}).
+ */
+ public void visitTypeInsn(final int opcode, final String type) {
+ if (mv != null) {
+ mv.visitTypeInsn(opcode, type);
+ }
+ }
+
+ /**
+ * Visits a field instruction. A field instruction is an instruction that loads or stores the
+ * value of a field of an object.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either
+ * GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
+ * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}).
+ * @param name the field's name.
+ * @param descriptor the field's descriptor (see {@link Type}).
+ */
+ public void visitFieldInsn(
+ final int opcode, final String owner, final String name, final String descriptor) {
+ if (mv != null) {
+ mv.visitFieldInsn(opcode, owner, name, descriptor);
+ }
+ }
+
+ /**
+ * Visits a method instruction. A method instruction is an instruction that invokes a method.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either
+ * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
+ * @param owner the internal name of the method's owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead.
+ */
+ @Deprecated
+ public void visitMethodInsn(
+ final int opcode, final String owner, final String name, final String descriptor) {
+ int opcodeAndSource = opcode | (api < Opcodes.ASM5 ? Opcodes.SOURCE_DEPRECATED : 0);
+ visitMethodInsn(opcodeAndSource, owner, name, descriptor, opcode == Opcodes.INVOKEINTERFACE);
+ }
+
+ /**
+ * Visits a method instruction. A method instruction is an instruction that invokes a method.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either
+ * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
+ * @param owner the internal name of the method's owner class (see {@link
+ * Type#getInternalName()}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @param isInterface if the method's owner class is an interface.
+ */
+ public void visitMethodInsn(
+ final int opcode,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ if (api < Opcodes.ASM5 && (opcode & Opcodes.SOURCE_DEPRECATED) == 0) {
+ if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) {
+ throw new UnsupportedOperationException("INVOKESPECIAL/STATIC on interfaces requires ASM5");
+ }
+ visitMethodInsn(opcode, owner, name, descriptor);
+ return;
+ }
+ if (mv != null) {
+ mv.visitMethodInsn(opcode & ~Opcodes.SOURCE_MASK, owner, name, descriptor, isInterface);
+ }
+ }
+
+ /**
+ * Visits an invokedynamic instruction.
+ *
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @param bootstrapMethodHandle the bootstrap method.
+ * @param bootstrapMethodArguments the bootstrap method constant arguments. Each argument must be
+ * an {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, {@link
+ * Type}, {@link Handle} or {@link ConstantDynamic} value. This method is allowed to modify
+ * the content of the array so a caller should expect that this array may change.
+ */
+ public void visitInvokeDynamicInsn(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+ }
+ }
+
+ /**
+ * Visits a jump instruction. A jump instruction is an instruction that may jump to another
+ * instruction.
+ *
+ * @param opcode the opcode of the type instruction to be visited. This opcode is either IFEQ,
+ * IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT,
+ * IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
+ * @param label the operand of the instruction to be visited. This operand is a label that
+ * designates the instruction to which the jump instruction may jump.
+ */
+ public void visitJumpInsn(final int opcode, final Label label) {
+ if (mv != null) {
+ mv.visitJumpInsn(opcode, label);
+ }
+ }
+
+ /**
+ * Visits a label. A label designates the instruction that will be visited just after it.
+ *
+ * @param label a {@link Label} object.
+ */
+ public void visitLabel(final Label label) {
+ if (mv != null) {
+ mv.visitLabel(label);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Special instructions
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Visits a LDC instruction. Note that new constant types may be added in future versions of the
+ * Java Virtual Machine. To easily detect new constant types, implementations of this method
+ * should check for unexpected constant types, like this:
+ *
+ *
+ * if (cst instanceof Integer) {
+ * // ...
+ * } else if (cst instanceof Float) {
+ * // ...
+ * } else if (cst instanceof Long) {
+ * // ...
+ * } else if (cst instanceof Double) {
+ * // ...
+ * } else if (cst instanceof String) {
+ * // ...
+ * } else if (cst instanceof Type) {
+ * int sort = ((Type) cst).getSort();
+ * if (sort == Type.OBJECT) {
+ * // ...
+ * } else if (sort == Type.ARRAY) {
+ * // ...
+ * } else if (sort == Type.METHOD) {
+ * // ...
+ * } else {
+ * // throw an exception
+ * }
+ * } else if (cst instanceof Handle) {
+ * // ...
+ * } else if (cst instanceof ConstantDynamic) {
+ * // ...
+ * } else {
+ * // throw an exception
+ * }
+ *
+ *
+ * @param value the constant to be loaded on the stack. This parameter must be a non null {@link
+ * Integer}, a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link
+ * Type} of OBJECT or ARRAY sort for {@code .class} constants, for classes whose version is
+ * 49, a {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle
+ * constants, for classes whose version is 51 or a {@link ConstantDynamic} for a constant
+ * dynamic for classes whose version is 55.
+ */
+ public void visitLdcInsn(final Object value) {
+ if (api < Opcodes.ASM5
+ && (value instanceof Handle
+ || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (api < Opcodes.ASM7 && value instanceof ConstantDynamic) {
+ throw new UnsupportedOperationException("This feature requires ASM7");
+ }
+ if (mv != null) {
+ mv.visitLdcInsn(value);
+ }
+ }
+
+ /**
+ * Visits an IINC instruction.
+ *
+ * @param varIndex index of the local variable to be incremented.
+ * @param increment amount to increment the local variable by.
+ */
+ public void visitIincInsn(final int varIndex, final int increment) {
+ if (mv != null) {
+ mv.visitIincInsn(varIndex, increment);
+ }
+ }
+
+ /**
+ * Visits a TABLESWITCH instruction.
+ *
+ * @param min the minimum key value.
+ * @param max the maximum key value.
+ * @param dflt beginning of the default handler block.
+ * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the
+ * handler block for the {@code min + i} key.
+ */
+ public void visitTableSwitchInsn(
+ final int min, final int max, final Label dflt, final Label... labels) {
+ if (mv != null) {
+ mv.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ /**
+ * Visits a LOOKUPSWITCH instruction.
+ *
+ * @param dflt beginning of the default handler block.
+ * @param keys the values of the keys.
+ * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the
+ * handler block for the {@code keys[i]} key.
+ */
+ public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
+ if (mv != null) {
+ mv.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ /**
+ * Visits a MULTIANEWARRAY instruction.
+ *
+ * @param descriptor an array type descriptor (see {@link Type}).
+ * @param numDimensions the number of dimensions of the array to allocate.
+ */
+ public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
+ if (mv != null) {
+ mv.visitMultiANewArrayInsn(descriptor, numDimensions);
+ }
+ }
+
+ /**
+ * Visits an annotation on an instruction. This method must be called just after the
+ * annotated instruction. It can be called several times for the same instruction.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#INSTANCEOF}, {@link TypeReference#NEW}, {@link
+ * TypeReference#CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE}, {@link
+ * TypeReference#CAST}, {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link
+ * TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT}, {@link
+ * TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link
+ * TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitInsnAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ return mv.visitInsnAnnotation(typeRef, typePath, descriptor, visible);
+ }
+ return null;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Exceptions table entries, debug information, max stack and max locals
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Visits a try catch block.
+ *
+ * @param start the beginning of the exception handler's scope (inclusive).
+ * @param end the end of the exception handler's scope (exclusive).
+ * @param handler the beginning of the exception handler's code.
+ * @param type the internal name of the type of exceptions handled by the handler (see {@link
+ * Type#getInternalName()}), or {@literal null} to catch any exceptions (for "finally"
+ * blocks).
+ * @throws IllegalArgumentException if one of the labels has already been visited by this visitor
+ * (by the {@link #visitLabel} method).
+ */
+ public void visitTryCatchBlock(
+ final Label start, final Label end, final Label handler, final String type) {
+ if (mv != null) {
+ mv.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ /**
+ * Visits an annotation on an exception handler type. This method must be called after the
+ * {@link #visitTryCatchBlock} for the annotated exception handler. It can be called several times
+ * for the same exception handler.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#EXCEPTION_PARAMETER}. See {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTryCatchAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ return mv.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a local variable declaration.
+ *
+ * @param name the name of a local variable.
+ * @param descriptor the type descriptor of this local variable.
+ * @param signature the type signature of this local variable. May be {@literal null} if the local
+ * variable type does not use generic types.
+ * @param start the first instruction corresponding to the scope of this local variable
+ * (inclusive).
+ * @param end the last instruction corresponding to the scope of this local variable (exclusive).
+ * @param index the local variable's index.
+ * @throws IllegalArgumentException if one of the labels has not already been visited by this
+ * visitor (by the {@link #visitLabel} method).
+ */
+ public void visitLocalVariable(
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Label start,
+ final Label end,
+ final int index) {
+ if (mv != null) {
+ mv.visitLocalVariable(name, descriptor, signature, start, end, index);
+ }
+ }
+
+ /**
+ * Visits an annotation on a local variable type.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE}. See {@link
+ * TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param start the fist instructions corresponding to the continuous ranges that make the scope
+ * of this local variable (inclusive).
+ * @param end the last instructions corresponding to the continuous ranges that make the scope of
+ * this local variable (exclusive). This array must have the same size as the 'start' array.
+ * @param index the local variable's index in each range. This array must have the same size as
+ * the 'start' array.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitLocalVariableAnnotation(
+ final int typeRef,
+ final TypePath typePath,
+ final Label[] start,
+ final Label[] end,
+ final int[] index,
+ final String descriptor,
+ final boolean visible) {
+ if (api < Opcodes.ASM5) {
+ throw new UnsupportedOperationException(REQUIRES_ASM5);
+ }
+ if (mv != null) {
+ return mv.visitLocalVariableAnnotation(
+ typeRef, typePath, start, end, index, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a line number declaration.
+ *
+ * @param line a line number. This number refers to the source file from which the class was
+ * compiled.
+ * @param start the first instruction corresponding to this line number.
+ * @throws IllegalArgumentException if {@code start} has not already been visited by this visitor
+ * (by the {@link #visitLabel} method).
+ */
+ public void visitLineNumber(final int line, final Label start) {
+ if (mv != null) {
+ mv.visitLineNumber(line, start);
+ }
+ }
+
+ /**
+ * Visits the maximum stack size and the maximum number of local variables of the method.
+ *
+ * @param maxStack maximum stack size of the method.
+ * @param maxLocals maximum number of local variables for the method.
+ */
+ public void visitMaxs(final int maxStack, final int maxLocals) {
+ if (mv != null) {
+ mv.visitMaxs(maxStack, maxLocals);
+ }
+ }
+
+ /**
+ * Visits the end of the method. This method, which is the last one to be called, is used to
+ * inform the visitor that all the annotations and attributes of the method have been visited.
+ */
+ public void visitEnd() {
+ if (mv != null) {
+ mv.visitEnd();
}
+ }
}
diff --git a/src/java/nginx/clojure/asm/MethodWriter.java b/src/java/nginx/clojure/asm/MethodWriter.java
index f02e2fdb..355af173 100644
--- a/src/java/nginx/clojure/asm/MethodWriter.java
+++ b/src/java/nginx/clojure/asm/MethodWriter.java
@@ -1,2685 +1,2394 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * A {@link MethodVisitor} that generates methods in bytecode form. Each visit
- * method of this class appends the bytecode corresponding to the visited
- * instruction to a byte vector, in the order these methods are called.
- *
+ * A {@link MethodVisitor} that generates a corresponding 'method_info' structure, as defined in the
+ * Java Virtual Machine Specification (JVMS).
+ *
+ * @see JVMS
+ * 4.6
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
-class MethodWriter extends MethodVisitor {
-
- /**
- * Pseudo access flag used to denote constructors.
- */
- static final int ACC_CONSTRUCTOR = 0x80000;
-
- /**
- * Frame has exactly the same locals as the previous stack map frame and
- * number of stack items is zero.
- */
- static final int SAME_FRAME = 0; // to 63 (0-3f)
-
- /**
- * Frame has exactly the same locals as the previous stack map frame and
- * number of stack items is 1
- */
- static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f)
-
- /**
- * Reserved for future use
- */
- static final int RESERVED = 128;
-
- /**
- * Frame has exactly the same locals as the previous stack map frame and
- * number of stack items is 1. Offset is bigger then 63;
- */
- static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7
-
- /**
- * Frame where current locals are the same as the locals in the previous
- * frame, except that the k last locals are absent. The value of k is given
- * by the formula 251-frame_type.
- */
- static final int CHOP_FRAME = 248; // to 250 (f8-fA)
-
- /**
- * Frame has exactly the same locals as the previous stack map frame and
- * number of stack items is zero. Offset is bigger then 63;
- */
- static final int SAME_FRAME_EXTENDED = 251; // fb
-
- /**
- * Frame where current locals are the same as the locals in the previous
- * frame, except that k additional locals are defined. The value of k is
- * given by the formula frame_type-251.
- */
- static final int APPEND_FRAME = 252; // to 254 // fc-fe
-
- /**
- * Full frame
- */
- static final int FULL_FRAME = 255; // ff
-
- /**
- * Indicates that the stack map frames must be recomputed from scratch. In
- * this case the maximum stack size and number of local variables is also
- * recomputed from scratch.
- *
- * @see #compute
- */
- private static final int FRAMES = 0;
-
- /**
- * Indicates that the maximum stack size and number of local variables must
- * be automatically computed.
- *
- * @see #compute
- */
- private static final int MAXS = 1;
-
- /**
- * Indicates that nothing must be automatically computed.
- *
- * @see #compute
- */
- private static final int NOTHING = 2;
-
- /**
- * The class writer to which this method must be added.
- */
- final ClassWriter cw;
-
- /**
- * Access flags of this method.
- */
- private int access;
-
- /**
- * The index of the constant pool item that contains the name of this
- * method.
- */
- private final int name;
-
- /**
- * The index of the constant pool item that contains the descriptor of this
- * method.
- */
- private final int desc;
-
- /**
- * The descriptor of this method.
- */
- private final String descriptor;
-
- /**
- * The signature of this method.
- */
- String signature;
-
- /**
- * If not zero, indicates that the code of this method must be copied from
- * the ClassReader associated to this writer in cw.cr
. More
- * precisely, this field gives the index of the first byte to copied from
- * cw.cr.b
.
- */
- int classReaderOffset;
-
- /**
- * If not zero, indicates that the code of this method must be copied from
- * the ClassReader associated to this writer in cw.cr
. More
- * precisely, this field gives the number of bytes to copied from
- * cw.cr.b
.
- */
- int classReaderLength;
-
- /**
- * Number of exceptions that can be thrown by this method.
- */
- int exceptionCount;
-
- /**
- * The exceptions that can be thrown by this method. More precisely, this
- * array contains the indexes of the constant pool items that contain the
- * internal names of these exception classes.
- */
- int[] exceptions;
-
- /**
- * The annotation default attribute of this method. May be null.
- */
- private ByteVector annd;
-
- /**
- * The runtime visible annotations of this method. May be null.
- */
- private AnnotationWriter anns;
-
- /**
- * The runtime invisible annotations of this method. May be null.
- */
- private AnnotationWriter ianns;
-
- /**
- * The runtime visible parameter annotations of this method. May be
- * null.
- */
- private AnnotationWriter[] panns;
-
- /**
- * The runtime invisible parameter annotations of this method. May be
- * null.
- */
- private AnnotationWriter[] ipanns;
-
- /**
- * The number of synthetic parameters of this method.
- */
- private int synthetics;
-
- /**
- * The non standard attributes of the method.
- */
- private Attribute attrs;
-
- /**
- * The bytecode of this method.
- */
- private ByteVector code = new ByteVector();
-
- /**
- * Maximum stack size of this method.
- */
- private int maxStack;
-
- /**
- * Maximum number of local variables for this method.
- */
- private int maxLocals;
-
- /**
- * Number of local variables in the current stack map frame.
- */
- private int currentLocals;
-
- /**
- * Number of stack map frames in the StackMapTable attribute.
- */
- private int frameCount;
-
- /**
- * The StackMapTable attribute.
- */
- private ByteVector stackMap;
-
- /**
- * The offset of the last frame that was written in the StackMapTable
- * attribute.
- */
- private int previousFrameOffset;
-
- /**
- * The last frame that was written in the StackMapTable attribute.
- *
- * @see #frame
- */
- private int[] previousFrame;
-
- /**
- * The current stack map frame. The first element contains the offset of the
- * instruction to which the frame corresponds, the second element is the
- * number of locals and the third one is the number of stack elements. The
- * local variables start at index 3 and are followed by the operand stack
- * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] =
- * nStack, frame[3] = nLocal. All types are encoded as integers, with the
- * same format as the one used in {@link Label}, but limited to BASE types.
- */
- private int[] frame;
-
- /**
- * Number of elements in the exception handler list.
- */
- private int handlerCount;
-
- /**
- * The first element in the exception handler list.
- */
- private Handler firstHandler;
-
- /**
- * The last element in the exception handler list.
- */
- private Handler lastHandler;
-
- /**
- * Number of entries in the LocalVariableTable attribute.
- */
- private int localVarCount;
-
- /**
- * The LocalVariableTable attribute.
- */
- private ByteVector localVar;
-
- /**
- * Number of entries in the LocalVariableTypeTable attribute.
- */
- private int localVarTypeCount;
-
- /**
- * The LocalVariableTypeTable attribute.
- */
- private ByteVector localVarType;
-
- /**
- * Number of entries in the LineNumberTable attribute.
- */
- private int lineNumberCount;
-
- /**
- * The LineNumberTable attribute.
- */
- private ByteVector lineNumber;
-
- /**
- * The non standard attributes of the method's code.
- */
- private Attribute cattrs;
-
- /**
- * Indicates if some jump instructions are too small and need to be resized.
- */
- private boolean resize;
-
- /**
- * The number of subroutines in this method.
- */
- private int subroutines;
-
- // ------------------------------------------------------------------------
-
- /*
- * Fields for the control flow graph analysis algorithm (used to compute the
- * maximum stack size). A control flow graph contains one node per "basic
- * block", and one edge per "jump" from one basic block to another. Each
- * node (i.e., each basic block) is represented by the Label object that
- * corresponds to the first instruction of this basic block. Each node also
- * stores the list of its successors in the graph, as a linked list of Edge
- * objects.
- */
-
- /**
- * Indicates what must be automatically computed.
- *
- * @see #FRAMES
- * @see #MAXS
- * @see #NOTHING
- */
- private final int compute;
-
- /**
- * A list of labels. This list is the list of basic blocks in the method,
- * i.e. a list of Label objects linked to each other by their
- * {@link Label#successor} field, in the order they are visited by
- * {@link MethodVisitor#visitLabel}, and starting with the first basic
- * block.
- */
- private Label labels;
-
- /**
- * The previous basic block.
- */
- private Label previousBlock;
-
- /**
- * The current basic block.
- */
- private Label currentBlock;
-
- /**
- * The (relative) stack size after the last visited instruction. This size
- * is relative to the beginning of the current basic block, i.e., the true
- * stack size after the last visited instruction is equal to the
- * {@link Label#inputStackTop beginStackSize} of the current basic block
- * plus stackSize.
- */
- private int stackSize;
-
- /**
- * The (relative) maximum stack size after the last visited instruction.
- * This size is relative to the beginning of the current basic block, i.e.,
- * the true maximum stack size after the last visited instruction is equal
- * to the {@link Label#inputStackTop beginStackSize} of the current basic
- * block plus stackSize.
- */
- private int maxStackSize;
-
- // ------------------------------------------------------------------------
- // Constructor
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a new {@link MethodWriter}.
- *
- * @param cw
- * the class writer in which the method must be added.
- * @param access
- * the method's access flags (see {@link Opcodes}).
- * @param name
- * the method's name.
- * @param desc
- * the method's descriptor (see {@link Type}).
- * @param signature
- * the method's signature. May be null.
- * @param exceptions
- * the internal names of the method's exceptions. May be
- * null.
- * @param computeMaxs
- * true if the maximum stack size and number of local
- * variables must be automatically computed.
- * @param computeFrames
- * true if the stack map tables must be recomputed from
- * scratch.
- */
- MethodWriter(final ClassWriter cw, final int access, final String name,
- final String desc, final String signature,
- final String[] exceptions, final boolean computeMaxs,
- final boolean computeFrames) {
- super(Opcodes.ASM4);
- if (cw.firstMethod == null) {
- cw.firstMethod = this;
- } else {
- cw.lastMethod.mv = this;
- }
- cw.lastMethod = this;
- this.cw = cw;
- this.access = access;
- if ("".equals(name)) {
- this.access |= ACC_CONSTRUCTOR;
- }
- this.name = cw.newUTF8(name);
- this.desc = cw.newUTF8(desc);
- this.descriptor = desc;
- if (ClassReader.SIGNATURES) {
- this.signature = signature;
- }
- if (exceptions != null && exceptions.length > 0) {
- exceptionCount = exceptions.length;
- this.exceptions = new int[exceptionCount];
- for (int i = 0; i < exceptionCount; ++i) {
- this.exceptions[i] = cw.newClass(exceptions[i]);
- }
- }
- this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING);
- if (computeMaxs || computeFrames) {
- // updates maxLocals
- int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
- if ((access & Opcodes.ACC_STATIC) != 0) {
- --size;
- }
- maxLocals = size;
- currentLocals = size;
- // creates and visits the label for the first basic block
- labels = new Label();
- labels.status |= Label.PUSHED;
- visitLabel(labels);
- }
+final class MethodWriter extends MethodVisitor {
+
+ /** Indicates that nothing must be computed. */
+ static final int COMPUTE_NOTHING = 0;
+
+ /**
+ * Indicates that the maximum stack size and the maximum number of local variables must be
+ * computed, from scratch.
+ */
+ static final int COMPUTE_MAX_STACK_AND_LOCAL = 1;
+
+ /**
+ * Indicates that the maximum stack size and the maximum number of local variables must be
+ * computed, from the existing stack map frames. This can be done more efficiently than with the
+ * control flow graph algorithm used for {@link #COMPUTE_MAX_STACK_AND_LOCAL}, by using a linear
+ * scan of the bytecode instructions.
+ */
+ static final int COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES = 2;
+
+ /**
+ * Indicates that the stack map frames of type F_INSERT must be computed. The other frames are not
+ * computed. They should all be of type F_NEW and should be sufficient to compute the content of
+ * the F_INSERT frames, together with the bytecode instructions between a F_NEW and a F_INSERT
+ * frame - and without any knowledge of the type hierarchy (by definition of F_INSERT).
+ */
+ static final int COMPUTE_INSERTED_FRAMES = 3;
+
+ /**
+ * Indicates that all the stack map frames must be computed. In this case the maximum stack size
+ * and the maximum number of local variables is also computed.
+ */
+ static final int COMPUTE_ALL_FRAMES = 4;
+
+ /** Indicates that {@link #STACK_SIZE_DELTA} is not applicable (not constant or never used). */
+ private static final int NA = 0;
+
+ /**
+ * The stack size variation corresponding to each JVM opcode. The stack size variation for opcode
+ * 'o' is given by the array element at index 'o'.
+ *
+ * @see JVMS 6
+ */
+ private static final int[] STACK_SIZE_DELTA = {
+ 0, // nop = 0 (0x0)
+ 1, // aconst_null = 1 (0x1)
+ 1, // iconst_m1 = 2 (0x2)
+ 1, // iconst_0 = 3 (0x3)
+ 1, // iconst_1 = 4 (0x4)
+ 1, // iconst_2 = 5 (0x5)
+ 1, // iconst_3 = 6 (0x6)
+ 1, // iconst_4 = 7 (0x7)
+ 1, // iconst_5 = 8 (0x8)
+ 2, // lconst_0 = 9 (0x9)
+ 2, // lconst_1 = 10 (0xa)
+ 1, // fconst_0 = 11 (0xb)
+ 1, // fconst_1 = 12 (0xc)
+ 1, // fconst_2 = 13 (0xd)
+ 2, // dconst_0 = 14 (0xe)
+ 2, // dconst_1 = 15 (0xf)
+ 1, // bipush = 16 (0x10)
+ 1, // sipush = 17 (0x11)
+ 1, // ldc = 18 (0x12)
+ NA, // ldc_w = 19 (0x13)
+ NA, // ldc2_w = 20 (0x14)
+ 1, // iload = 21 (0x15)
+ 2, // lload = 22 (0x16)
+ 1, // fload = 23 (0x17)
+ 2, // dload = 24 (0x18)
+ 1, // aload = 25 (0x19)
+ NA, // iload_0 = 26 (0x1a)
+ NA, // iload_1 = 27 (0x1b)
+ NA, // iload_2 = 28 (0x1c)
+ NA, // iload_3 = 29 (0x1d)
+ NA, // lload_0 = 30 (0x1e)
+ NA, // lload_1 = 31 (0x1f)
+ NA, // lload_2 = 32 (0x20)
+ NA, // lload_3 = 33 (0x21)
+ NA, // fload_0 = 34 (0x22)
+ NA, // fload_1 = 35 (0x23)
+ NA, // fload_2 = 36 (0x24)
+ NA, // fload_3 = 37 (0x25)
+ NA, // dload_0 = 38 (0x26)
+ NA, // dload_1 = 39 (0x27)
+ NA, // dload_2 = 40 (0x28)
+ NA, // dload_3 = 41 (0x29)
+ NA, // aload_0 = 42 (0x2a)
+ NA, // aload_1 = 43 (0x2b)
+ NA, // aload_2 = 44 (0x2c)
+ NA, // aload_3 = 45 (0x2d)
+ -1, // iaload = 46 (0x2e)
+ 0, // laload = 47 (0x2f)
+ -1, // faload = 48 (0x30)
+ 0, // daload = 49 (0x31)
+ -1, // aaload = 50 (0x32)
+ -1, // baload = 51 (0x33)
+ -1, // caload = 52 (0x34)
+ -1, // saload = 53 (0x35)
+ -1, // istore = 54 (0x36)
+ -2, // lstore = 55 (0x37)
+ -1, // fstore = 56 (0x38)
+ -2, // dstore = 57 (0x39)
+ -1, // astore = 58 (0x3a)
+ NA, // istore_0 = 59 (0x3b)
+ NA, // istore_1 = 60 (0x3c)
+ NA, // istore_2 = 61 (0x3d)
+ NA, // istore_3 = 62 (0x3e)
+ NA, // lstore_0 = 63 (0x3f)
+ NA, // lstore_1 = 64 (0x40)
+ NA, // lstore_2 = 65 (0x41)
+ NA, // lstore_3 = 66 (0x42)
+ NA, // fstore_0 = 67 (0x43)
+ NA, // fstore_1 = 68 (0x44)
+ NA, // fstore_2 = 69 (0x45)
+ NA, // fstore_3 = 70 (0x46)
+ NA, // dstore_0 = 71 (0x47)
+ NA, // dstore_1 = 72 (0x48)
+ NA, // dstore_2 = 73 (0x49)
+ NA, // dstore_3 = 74 (0x4a)
+ NA, // astore_0 = 75 (0x4b)
+ NA, // astore_1 = 76 (0x4c)
+ NA, // astore_2 = 77 (0x4d)
+ NA, // astore_3 = 78 (0x4e)
+ -3, // iastore = 79 (0x4f)
+ -4, // lastore = 80 (0x50)
+ -3, // fastore = 81 (0x51)
+ -4, // dastore = 82 (0x52)
+ -3, // aastore = 83 (0x53)
+ -3, // bastore = 84 (0x54)
+ -3, // castore = 85 (0x55)
+ -3, // sastore = 86 (0x56)
+ -1, // pop = 87 (0x57)
+ -2, // pop2 = 88 (0x58)
+ 1, // dup = 89 (0x59)
+ 1, // dup_x1 = 90 (0x5a)
+ 1, // dup_x2 = 91 (0x5b)
+ 2, // dup2 = 92 (0x5c)
+ 2, // dup2_x1 = 93 (0x5d)
+ 2, // dup2_x2 = 94 (0x5e)
+ 0, // swap = 95 (0x5f)
+ -1, // iadd = 96 (0x60)
+ -2, // ladd = 97 (0x61)
+ -1, // fadd = 98 (0x62)
+ -2, // dadd = 99 (0x63)
+ -1, // isub = 100 (0x64)
+ -2, // lsub = 101 (0x65)
+ -1, // fsub = 102 (0x66)
+ -2, // dsub = 103 (0x67)
+ -1, // imul = 104 (0x68)
+ -2, // lmul = 105 (0x69)
+ -1, // fmul = 106 (0x6a)
+ -2, // dmul = 107 (0x6b)
+ -1, // idiv = 108 (0x6c)
+ -2, // ldiv = 109 (0x6d)
+ -1, // fdiv = 110 (0x6e)
+ -2, // ddiv = 111 (0x6f)
+ -1, // irem = 112 (0x70)
+ -2, // lrem = 113 (0x71)
+ -1, // frem = 114 (0x72)
+ -2, // drem = 115 (0x73)
+ 0, // ineg = 116 (0x74)
+ 0, // lneg = 117 (0x75)
+ 0, // fneg = 118 (0x76)
+ 0, // dneg = 119 (0x77)
+ -1, // ishl = 120 (0x78)
+ -1, // lshl = 121 (0x79)
+ -1, // ishr = 122 (0x7a)
+ -1, // lshr = 123 (0x7b)
+ -1, // iushr = 124 (0x7c)
+ -1, // lushr = 125 (0x7d)
+ -1, // iand = 126 (0x7e)
+ -2, // land = 127 (0x7f)
+ -1, // ior = 128 (0x80)
+ -2, // lor = 129 (0x81)
+ -1, // ixor = 130 (0x82)
+ -2, // lxor = 131 (0x83)
+ 0, // iinc = 132 (0x84)
+ 1, // i2l = 133 (0x85)
+ 0, // i2f = 134 (0x86)
+ 1, // i2d = 135 (0x87)
+ -1, // l2i = 136 (0x88)
+ -1, // l2f = 137 (0x89)
+ 0, // l2d = 138 (0x8a)
+ 0, // f2i = 139 (0x8b)
+ 1, // f2l = 140 (0x8c)
+ 1, // f2d = 141 (0x8d)
+ -1, // d2i = 142 (0x8e)
+ 0, // d2l = 143 (0x8f)
+ -1, // d2f = 144 (0x90)
+ 0, // i2b = 145 (0x91)
+ 0, // i2c = 146 (0x92)
+ 0, // i2s = 147 (0x93)
+ -3, // lcmp = 148 (0x94)
+ -1, // fcmpl = 149 (0x95)
+ -1, // fcmpg = 150 (0x96)
+ -3, // dcmpl = 151 (0x97)
+ -3, // dcmpg = 152 (0x98)
+ -1, // ifeq = 153 (0x99)
+ -1, // ifne = 154 (0x9a)
+ -1, // iflt = 155 (0x9b)
+ -1, // ifge = 156 (0x9c)
+ -1, // ifgt = 157 (0x9d)
+ -1, // ifle = 158 (0x9e)
+ -2, // if_icmpeq = 159 (0x9f)
+ -2, // if_icmpne = 160 (0xa0)
+ -2, // if_icmplt = 161 (0xa1)
+ -2, // if_icmpge = 162 (0xa2)
+ -2, // if_icmpgt = 163 (0xa3)
+ -2, // if_icmple = 164 (0xa4)
+ -2, // if_acmpeq = 165 (0xa5)
+ -2, // if_acmpne = 166 (0xa6)
+ 0, // goto = 167 (0xa7)
+ 1, // jsr = 168 (0xa8)
+ 0, // ret = 169 (0xa9)
+ -1, // tableswitch = 170 (0xaa)
+ -1, // lookupswitch = 171 (0xab)
+ -1, // ireturn = 172 (0xac)
+ -2, // lreturn = 173 (0xad)
+ -1, // freturn = 174 (0xae)
+ -2, // dreturn = 175 (0xaf)
+ -1, // areturn = 176 (0xb0)
+ 0, // return = 177 (0xb1)
+ NA, // getstatic = 178 (0xb2)
+ NA, // putstatic = 179 (0xb3)
+ NA, // getfield = 180 (0xb4)
+ NA, // putfield = 181 (0xb5)
+ NA, // invokevirtual = 182 (0xb6)
+ NA, // invokespecial = 183 (0xb7)
+ NA, // invokestatic = 184 (0xb8)
+ NA, // invokeinterface = 185 (0xb9)
+ NA, // invokedynamic = 186 (0xba)
+ 1, // new = 187 (0xbb)
+ 0, // newarray = 188 (0xbc)
+ 0, // anewarray = 189 (0xbd)
+ 0, // arraylength = 190 (0xbe)
+ NA, // athrow = 191 (0xbf)
+ 0, // checkcast = 192 (0xc0)
+ 0, // instanceof = 193 (0xc1)
+ -1, // monitorenter = 194 (0xc2)
+ -1, // monitorexit = 195 (0xc3)
+ NA, // wide = 196 (0xc4)
+ NA, // multianewarray = 197 (0xc5)
+ -1, // ifnull = 198 (0xc6)
+ -1, // ifnonnull = 199 (0xc7)
+ NA, // goto_w = 200 (0xc8)
+ NA // jsr_w = 201 (0xc9)
+ };
+
+ /** Where the constants used in this MethodWriter must be stored. */
+ private final SymbolTable symbolTable;
+
+ // Note: fields are ordered as in the method_info structure, and those related to attributes are
+ // ordered as in Section 4.7 of the JVMS.
+
+ /**
+ * The access_flags field of the method_info JVMS structure. This field can contain ASM specific
+ * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
+ * ClassFile structure.
+ */
+ private final int accessFlags;
+
+ /** The name_index field of the method_info JVMS structure. */
+ private final int nameIndex;
+
+ /** The name of this method. */
+ private final String name;
+
+ /** The descriptor_index field of the method_info JVMS structure. */
+ private final int descriptorIndex;
+
+ /** The descriptor of this method. */
+ private final String descriptor;
+
+ // Code attribute fields and sub attributes:
+
+ /** The max_stack field of the Code attribute. */
+ private int maxStack;
+
+ /** The max_locals field of the Code attribute. */
+ private int maxLocals;
+
+ /** The 'code' field of the Code attribute. */
+ private final ByteVector code = new ByteVector();
+
+ /**
+ * The first element in the exception handler list (used to generate the exception_table of the
+ * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May
+ * be {@literal null}.
+ */
+ private Handler firstHandler;
+
+ /**
+ * The last element in the exception handler list (used to generate the exception_table of the
+ * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May
+ * be {@literal null}.
+ */
+ private Handler lastHandler;
+
+ /** The line_number_table_length field of the LineNumberTable code attribute. */
+ private int lineNumberTableLength;
+
+ /** The line_number_table array of the LineNumberTable code attribute, or {@literal null}. */
+ private ByteVector lineNumberTable;
+
+ /** The local_variable_table_length field of the LocalVariableTable code attribute. */
+ private int localVariableTableLength;
+
+ /**
+ * The local_variable_table array of the LocalVariableTable code attribute, or {@literal null}.
+ */
+ private ByteVector localVariableTable;
+
+ /** The local_variable_type_table_length field of the LocalVariableTypeTable code attribute. */
+ private int localVariableTypeTableLength;
+
+ /**
+ * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or {@literal
+ * null}.
+ */
+ private ByteVector localVariableTypeTable;
+
+ /** The number_of_entries field of the StackMapTable code attribute. */
+ private int stackMapTableNumberOfEntries;
+
+ /** The 'entries' array of the StackMapTable code attribute. */
+ private ByteVector stackMapTableEntries;
+
+ /**
+ * The last runtime visible type annotation of the Code attribute. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastCodeRuntimeVisibleTypeAnnotation;
+
+ /**
+ * The last runtime invisible type annotation of the Code attribute. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastCodeRuntimeInvisibleTypeAnnotation;
+
+ /**
+ * The first non standard attribute of the Code attribute. The next ones can be accessed with the
+ * {@link Attribute#nextAttribute} field. May be {@literal null}.
+ *
+ * WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
+ * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the
+ * reverse order specified by the user.
+ */
+ private Attribute firstCodeAttribute;
+
+ // Other method_info attributes:
+
+ /** The number_of_exceptions field of the Exceptions attribute. */
+ private final int numberOfExceptions;
+
+ /** The exception_index_table array of the Exceptions attribute, or {@literal null}. */
+ private final int[] exceptionIndexTable;
+
+ /** The signature_index field of the Signature attribute. */
+ private final int signatureIndex;
+
+ /**
+ * The last runtime visible annotation of this method. The previous ones can be accessed with the
+ * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleAnnotation;
+
+ /**
+ * The last runtime invisible annotation of this method. The previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleAnnotation;
+
+ /** The number of method parameters that can have runtime visible annotations, or 0. */
+ private int visibleAnnotableParameterCount;
+
+ /**
+ * The runtime visible parameter annotations of this method. Each array element contains the last
+ * annotation of a parameter (which can be {@literal null} - the previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}.
+ */
+ private AnnotationWriter[] lastRuntimeVisibleParameterAnnotations;
+
+ /** The number of method parameters that can have runtime visible annotations, or 0. */
+ private int invisibleAnnotableParameterCount;
+
+ /**
+ * The runtime invisible parameter annotations of this method. Each array element contains the
+ * last annotation of a parameter (which can be {@literal null} - the previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}.
+ */
+ private AnnotationWriter[] lastRuntimeInvisibleParameterAnnotations;
+
+ /**
+ * The last runtime visible type annotation of this method. The previous ones can be accessed with
+ * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
+
+ /**
+ * The last runtime invisible type annotation of this method. The previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
+
+ /** The default_value field of the AnnotationDefault attribute, or {@literal null}. */
+ private ByteVector defaultValue;
+
+ /** The parameters_count field of the MethodParameters attribute. */
+ private int parametersCount;
+
+ /** The 'parameters' array of the MethodParameters attribute, or {@literal null}. */
+ private ByteVector parameters;
+
+ /**
+ * The first non standard attribute of this method. The next ones can be accessed with the {@link
+ * Attribute#nextAttribute} field. May be {@literal null}.
+ *
+ *
WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link
+ * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the
+ * reverse order specified by the user.
+ */
+ private Attribute firstAttribute;
+
+ // -----------------------------------------------------------------------------------------------
+ // Fields used to compute the maximum stack size and number of locals, and the stack map frames
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link
+ * #COMPUTE_INSERTED_FRAMES}, {@link COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link
+ * #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}.
+ */
+ private final int compute;
+
+ /**
+ * The first basic block of the method. The next ones (in bytecode offset order) can be accessed
+ * with the {@link Label#nextBasicBlock} field.
+ */
+ private Label firstBasicBlock;
+
+ /**
+ * The last basic block of the method (in bytecode offset order). This field is updated each time
+ * a basic block is encountered, and is used to append it at the end of the basic block list.
+ */
+ private Label lastBasicBlock;
+
+ /**
+ * The current basic block, i.e. the basic block of the last visited instruction. When {@link
+ * #compute} is equal to {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_ALL_FRAMES}, this
+ * field is {@literal null} for unreachable code. When {@link #compute} is equal to {@link
+ * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays
+ * unchanged throughout the whole method (i.e. the whole code is seen as a single basic block;
+ * indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame -
+ * and the maximum stack size as well - without using any control flow graph).
+ */
+ private Label currentBasicBlock;
+
+ /**
+ * The relative stack size after the last visited instruction. This size is relative to the
+ * beginning of {@link #currentBasicBlock}, i.e. the true stack size after the last visited
+ * instruction is equal to the {@link Label#inputStackSize} of the current basic block plus {@link
+ * #relativeStackSize}. When {@link #compute} is equal to {@link
+ * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of
+ * the method, so this relative size is also equal to the absolute stack size after the last
+ * visited instruction.
+ */
+ private int relativeStackSize;
+
+ /**
+ * The maximum relative stack size after the last visited instruction. This size is relative to
+ * the beginning of {@link #currentBasicBlock}, i.e. the true maximum stack size after the last
+ * visited instruction is equal to the {@link Label#inputStackSize} of the current basic block
+ * plus {@link #maxRelativeStackSize}.When {@link #compute} is equal to {@link
+ * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of
+ * the method, so this relative size is also equal to the absolute maximum stack size after the
+ * last visited instruction.
+ */
+ private int maxRelativeStackSize;
+
+ /** The number of local variables in the last visited stack map frame. */
+ private int currentLocals;
+
+ /** The bytecode offset of the last frame that was written in {@link #stackMapTableEntries}. */
+ private int previousFrameOffset;
+
+ /**
+ * The last frame that was written in {@link #stackMapTableEntries}. This field has the same
+ * format as {@link #currentFrame}.
+ */
+ private int[] previousFrame;
+
+ /**
+ * The current stack map frame. The first element contains the bytecode offset of the instruction
+ * to which the frame corresponds, the second element is the number of locals and the third one is
+ * the number of stack elements. The local variables start at index 3 and are followed by the
+ * operand stack elements. In summary frame[0] = offset, frame[1] = numLocal, frame[2] = numStack.
+ * Local variables and operand stack entries contain abstract types, as defined in {@link Frame},
+ * but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND} or {@link
+ * Frame#UNINITIALIZED_KIND} abstract types. Long and double types use only one array entry.
+ */
+ private int[] currentFrame;
+
+ /** Whether this method contains subroutines. */
+ private boolean hasSubroutines;
+
+ // -----------------------------------------------------------------------------------------------
+ // Other miscellaneous status fields
+ // -----------------------------------------------------------------------------------------------
+
+ /** Whether the bytecode of this method contains ASM specific instructions. */
+ private boolean hasAsmInstructions;
+
+ /**
+ * The start offset of the last visited instruction. Used to set the offset field of type
+ * annotations of type 'offset_target' (see JVMS
+ * 4.7.20.1).
+ */
+ private int lastBytecodeOffset;
+
+ /**
+ * The offset in bytes in {@link SymbolTable#getSource} from which the method_info for this method
+ * (excluding its first 6 bytes) must be copied, or 0.
+ */
+ private int sourceOffset;
+
+ /**
+ * The length in bytes in {@link SymbolTable#getSource} which must be copied to get the
+ * method_info for this method (excluding its first 6 bytes for access_flags, name_index and
+ * descriptor_index).
+ */
+ private int sourceLength;
+
+ // -----------------------------------------------------------------------------------------------
+ // Constructor and accessors
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link MethodWriter}.
+ *
+ * @param symbolTable where the constants used in this AnnotationWriter must be stored.
+ * @param access the method's access flags (see {@link Opcodes}).
+ * @param name the method's name.
+ * @param descriptor the method's descriptor (see {@link Type}).
+ * @param signature the method's signature. May be {@literal null}.
+ * @param exceptions the internal names of the method's exceptions. May be {@literal null}.
+ * @param compute indicates what must be computed (see #compute).
+ */
+ MethodWriter(
+ final SymbolTable symbolTable,
+ final int access,
+ final String name,
+ final String descriptor,
+ final String signature,
+ final String[] exceptions,
+ final int compute) {
+ super(/* latest api = */ Opcodes.ASM9);
+ this.symbolTable = symbolTable;
+ this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
+ this.nameIndex = symbolTable.addConstantUtf8(name);
+ this.name = name;
+ this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
+ this.descriptor = descriptor;
+ this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature);
+ if (exceptions != null && exceptions.length > 0) {
+ numberOfExceptions = exceptions.length;
+ this.exceptionIndexTable = new int[numberOfExceptions];
+ for (int i = 0; i < numberOfExceptions; ++i) {
+ this.exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index;
+ }
+ } else {
+ numberOfExceptions = 0;
+ this.exceptionIndexTable = null;
+ }
+ this.compute = compute;
+ if (compute != COMPUTE_NOTHING) {
+ // Update maxLocals and currentLocals.
+ int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
+ if ((access & Opcodes.ACC_STATIC) != 0) {
+ --argumentsSize;
+ }
+ maxLocals = argumentsSize;
+ currentLocals = argumentsSize;
+ // Create and visit the label for the first basic block.
+ firstBasicBlock = new Label();
+ visitLabel(firstBasicBlock);
}
+ }
- // ------------------------------------------------------------------------
- // Implementation of the MethodVisitor abstract class
- // ------------------------------------------------------------------------
+ boolean hasFrames() {
+ return stackMapTableNumberOfEntries > 0;
+ }
- @Override
- public AnnotationVisitor visitAnnotationDefault() {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- annd = new ByteVector();
- return new AnnotationWriter(cw, false, annd, null, 0);
- }
+ boolean hasAsmInstructions() {
+ return hasAsmInstructions;
+ }
- @Override
- public AnnotationVisitor visitAnnotation(final String desc,
- final boolean visible) {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- ByteVector bv = new ByteVector();
- // write type, and reserve space for values count
- bv.putShort(cw.newUTF8(desc)).putShort(0);
- AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
- if (visible) {
- aw.next = anns;
- anns = aw;
- } else {
- aw.next = ianns;
- ianns = aw;
- }
- return aw;
- }
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the MethodVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
- @Override
- public AnnotationVisitor visitParameterAnnotation(final int parameter,
- final String desc, final boolean visible) {
- if (!ClassReader.ANNOTATIONS) {
- return null;
- }
- ByteVector bv = new ByteVector();
- if ("Ljava/lang/Synthetic;".equals(desc)) {
- // workaround for a bug in javac with synthetic parameters
- // see ClassReader.readParameterAnnotations
- synthetics = Math.max(synthetics, parameter + 1);
- return new AnnotationWriter(cw, false, bv, null, 0);
- }
- // write type, and reserve space for values count
- bv.putShort(cw.newUTF8(desc)).putShort(0);
- AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
- if (visible) {
- if (panns == null) {
- panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
- }
- aw.next = panns[parameter];
- panns[parameter] = aw;
- } else {
- if (ipanns == null) {
- ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
- }
- aw.next = ipanns[parameter];
- ipanns[parameter] = aw;
- }
- return aw;
+ @Override
+ public void visitParameter(final String name, final int access) {
+ if (parameters == null) {
+ parameters = new ByteVector();
}
-
- @Override
- public void visitAttribute(final Attribute attr) {
- if (attr.isCodeAttribute()) {
- attr.next = cattrs;
- cattrs = attr;
- } else {
- attr.next = attrs;
- attrs = attr;
- }
+ ++parametersCount;
+ parameters.putShort((name == null) ? 0 : symbolTable.addConstantUtf8(name)).putShort(access);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ defaultValue = new ByteVector();
+ return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, defaultValue, null);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
+ } else {
+ return lastRuntimeInvisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
}
-
- @Override
- public void visitCode() {
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastRuntimeInvisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
+ if (visible) {
+ visibleAnnotableParameterCount = parameterCount;
+ } else {
+ invisibleAnnotableParameterCount = parameterCount;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(
+ final int parameter, final String annotationDescriptor, final boolean visible) {
+ if (visible) {
+ if (lastRuntimeVisibleParameterAnnotations == null) {
+ lastRuntimeVisibleParameterAnnotations =
+ new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ }
+ return lastRuntimeVisibleParameterAnnotations[parameter] =
+ AnnotationWriter.create(
+ symbolTable, annotationDescriptor, lastRuntimeVisibleParameterAnnotations[parameter]);
+ } else {
+ if (lastRuntimeInvisibleParameterAnnotations == null) {
+ lastRuntimeInvisibleParameterAnnotations =
+ new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ }
+ return lastRuntimeInvisibleParameterAnnotations[parameter] =
+ AnnotationWriter.create(
+ symbolTable,
+ annotationDescriptor,
+ lastRuntimeInvisibleParameterAnnotations[parameter]);
+ }
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attribute) {
+ // Store the attributes in the reverse order of their visit by this method.
+ if (attribute.isCodeAttribute()) {
+ attribute.nextAttribute = firstCodeAttribute;
+ firstCodeAttribute = attribute;
+ } else {
+ attribute.nextAttribute = firstAttribute;
+ firstAttribute = attribute;
+ }
+ }
+
+ @Override
+ public void visitCode() {
+ // Nothing to do.
+ }
+
+ @Override
+ public void visitFrame(
+ final int type,
+ final int numLocal,
+ final Object[] local,
+ final int numStack,
+ final Object[] stack) {
+ if (compute == COMPUTE_ALL_FRAMES) {
+ return;
}
- @Override
- public void visitFrame(final int type, final int nLocal,
- final Object[] local, final int nStack, final Object[] stack) {
- if (!ClassReader.FRAMES || compute == FRAMES) {
- return;
- }
-
+ if (compute == COMPUTE_INSERTED_FRAMES) {
+ if (currentBasicBlock.frame == null) {
+ // This should happen only once, for the implicit first frame (which is explicitly visited
+ // in ClassReader if the EXPAND_ASM_INSNS option is used - and COMPUTE_INSERTED_FRAMES
+ // can't be set if EXPAND_ASM_INSNS is not used).
+ currentBasicBlock.frame = new CurrentFrame(currentBasicBlock);
+ currentBasicBlock.frame.setInputFrameFromDescriptor(
+ symbolTable, accessFlags, descriptor, numLocal);
+ currentBasicBlock.frame.accept(this);
+ } else {
if (type == Opcodes.F_NEW) {
- if (previousFrame == null) {
- visitImplicitFirstFrame();
- }
- currentLocals = nLocal;
- int frameIndex = startFrame(code.length, nLocal, nStack);
- for (int i = 0; i < nLocal; ++i) {
- if (local[i] instanceof String) {
- frame[frameIndex++] = Frame.OBJECT
- | cw.addType((String) local[i]);
- } else if (local[i] instanceof Integer) {
- frame[frameIndex++] = ((Integer) local[i]).intValue();
- } else {
- frame[frameIndex++] = Frame.UNINITIALIZED
- | cw.addUninitializedType("",
- ((Label) local[i]).position);
- }
- }
- for (int i = 0; i < nStack; ++i) {
- if (stack[i] instanceof String) {
- frame[frameIndex++] = Frame.OBJECT
- | cw.addType((String) stack[i]);
- } else if (stack[i] instanceof Integer) {
- frame[frameIndex++] = ((Integer) stack[i]).intValue();
- } else {
- frame[frameIndex++] = Frame.UNINITIALIZED
- | cw.addUninitializedType("",
- ((Label) stack[i]).position);
- }
- }
- endFrame();
- } else {
- int delta;
- if (stackMap == null) {
- stackMap = new ByteVector();
- delta = code.length;
- } else {
- delta = code.length - previousFrameOffset - 1;
- if (delta < 0) {
- if (type == Opcodes.F_SAME) {
- return;
- } else {
- throw new IllegalStateException();
- }
- }
- }
-
- switch (type) {
- case Opcodes.F_FULL:
- currentLocals = nLocal;
- stackMap.putByte(FULL_FRAME).putShort(delta).putShort(nLocal);
- for (int i = 0; i < nLocal; ++i) {
- writeFrameType(local[i]);
- }
- stackMap.putShort(nStack);
- for (int i = 0; i < nStack; ++i) {
- writeFrameType(stack[i]);
- }
- break;
- case Opcodes.F_APPEND:
- currentLocals += nLocal;
- stackMap.putByte(SAME_FRAME_EXTENDED + nLocal).putShort(delta);
- for (int i = 0; i < nLocal; ++i) {
- writeFrameType(local[i]);
- }
- break;
- case Opcodes.F_CHOP:
- currentLocals -= nLocal;
- stackMap.putByte(SAME_FRAME_EXTENDED - nLocal).putShort(delta);
- break;
- case Opcodes.F_SAME:
- if (delta < 64) {
- stackMap.putByte(delta);
- } else {
- stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
- }
- break;
- case Opcodes.F_SAME1:
- if (delta < 64) {
- stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
- } else {
- stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
- .putShort(delta);
- }
- writeFrameType(stack[0]);
- break;
- }
-
- previousFrameOffset = code.length;
- ++frameCount;
- }
+ currentBasicBlock.frame.setInputFrameFromApiFormat(
+ symbolTable, numLocal, local, numStack, stack);
+ }
+ // If type is not F_NEW then it is F_INSERT by hypothesis, and currentBlock.frame contains
+ // the stack map frame at the current instruction, computed from the last F_NEW frame and
+ // the bytecode instructions in between (via calls to CurrentFrame#execute).
+ currentBasicBlock.frame.accept(this);
+ }
+ } else if (type == Opcodes.F_NEW) {
+ if (previousFrame == null) {
+ int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
+ Frame implicitFirstFrame = new Frame(new Label());
+ implicitFirstFrame.setInputFrameFromDescriptor(
+ symbolTable, accessFlags, descriptor, argumentsSize);
+ implicitFirstFrame.accept(this);
+ }
+ currentLocals = numLocal;
+ int frameIndex = visitFrameStart(code.length, numLocal, numStack);
+ for (int i = 0; i < numLocal; ++i) {
+ currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, local[i]);
+ }
+ for (int i = 0; i < numStack; ++i) {
+ currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, stack[i]);
+ }
+ visitFrameEnd();
+ } else {
+ if (symbolTable.getMajorVersion() < Opcodes.V1_6) {
+ throw new IllegalArgumentException("Class versions V1_5 or less must use F_NEW frames.");
+ }
+ int offsetDelta;
+ if (stackMapTableEntries == null) {
+ stackMapTableEntries = new ByteVector();
+ offsetDelta = code.length;
+ } else {
+ offsetDelta = code.length - previousFrameOffset - 1;
+ if (offsetDelta < 0) {
+ if (type == Opcodes.F_SAME) {
+ return;
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ switch (type) {
+ case Opcodes.F_FULL:
+ currentLocals = numLocal;
+ stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal);
+ for (int i = 0; i < numLocal; ++i) {
+ putFrameType(local[i]);
+ }
+ stackMapTableEntries.putShort(numStack);
+ for (int i = 0; i < numStack; ++i) {
+ putFrameType(stack[i]);
+ }
+ break;
+ case Opcodes.F_APPEND:
+ currentLocals += numLocal;
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + numLocal).putShort(offsetDelta);
+ for (int i = 0; i < numLocal; ++i) {
+ putFrameType(local[i]);
+ }
+ break;
+ case Opcodes.F_CHOP:
+ currentLocals -= numLocal;
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - numLocal).putShort(offsetDelta);
+ break;
+ case Opcodes.F_SAME:
+ if (offsetDelta < 64) {
+ stackMapTableEntries.putByte(offsetDelta);
+ } else {
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta);
+ }
+ break;
+ case Opcodes.F_SAME1:
+ if (offsetDelta < 64) {
+ stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
+ } else {
+ stackMapTableEntries
+ .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
+ .putShort(offsetDelta);
+ }
+ putFrameType(stack[0]);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
- maxStack = Math.max(maxStack, nStack);
- maxLocals = Math.max(maxLocals, currentLocals);
- }
-
- @Override
- public void visitInsn(final int opcode) {
- // adds the instruction to the bytecode of the method
- code.putByte(opcode);
- // update currentBlock
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, null, null);
- } else {
- // updates current and max stack sizes
- int size = stackSize + Frame.SIZE[opcode];
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- // if opcode == ATHROW or xRETURN, ends current block (no successor)
- if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
- || opcode == Opcodes.ATHROW) {
- noSuccessor();
- }
- }
+ previousFrameOffset = code.length;
+ ++stackMapTableNumberOfEntries;
}
- @Override
- public void visitIntInsn(final int opcode, final int operand) {
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, operand, null, null);
- } else if (opcode != Opcodes.NEWARRAY) {
- // updates current and max stack sizes only for NEWARRAY
- // (stack size variation = 0 for BIPUSH or SIPUSH)
- int size = stackSize + 1;
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- if (opcode == Opcodes.SIPUSH) {
- code.put12(opcode, operand);
- } else { // BIPUSH or NEWARRAY
- code.put11(opcode, operand);
+ if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
+ relativeStackSize = numStack;
+ for (int i = 0; i < numStack; ++i) {
+ if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) {
+ relativeStackSize++;
}
+ }
+ if (relativeStackSize > maxRelativeStackSize) {
+ maxRelativeStackSize = relativeStackSize;
+ }
}
- @Override
- public void visitVarInsn(final int opcode, final int var) {
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, var, null, null);
- } else {
- // updates current and max stack sizes
- if (opcode == Opcodes.RET) {
- // no stack change, but end of current block (no successor)
- currentBlock.status |= Label.RET;
- // save 'stackSize' here for future use
- // (see {@link #findSubroutineSuccessors})
- currentBlock.inputStackTop = stackSize;
- noSuccessor();
- } else { // xLOAD or xSTORE
- int size = stackSize + Frame.SIZE[opcode];
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
+ maxStack = Math.max(maxStack, numStack);
+ maxLocals = Math.max(maxLocals, currentLocals);
+ }
+
+ @Override
+ public void visitInsn(final int opcode) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ code.putByte(opcode);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, 0, null, null);
+ } else {
+ int size = relativeStackSize + STACK_SIZE_DELTA[opcode];
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
+ endCurrentBasicBlockWithNoSuccessor();
+ }
+ }
+ }
+
+ @Override
+ public void visitIntInsn(final int opcode, final int operand) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ if (opcode == Opcodes.SIPUSH) {
+ code.put12(opcode, operand);
+ } else { // BIPUSH or NEWARRAY
+ code.put11(opcode, operand);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, operand, null, null);
+ } else if (opcode != Opcodes.NEWARRAY) {
+ // The stack size delta is 1 for BIPUSH or SIPUSH, and 0 for NEWARRAY.
+ int size = relativeStackSize + 1;
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitVarInsn(final int opcode, final int varIndex) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ if (varIndex < 4 && opcode != Opcodes.RET) {
+ int optimizedOpcode;
+ if (opcode < Opcodes.ISTORE) {
+ optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + varIndex;
+ } else {
+ optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + varIndex;
+ }
+ code.putByte(optimizedOpcode);
+ } else if (varIndex >= 256) {
+ code.putByte(Constants.WIDE).put12(opcode, varIndex);
+ } else {
+ code.put11(opcode, varIndex);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, varIndex, null, null);
+ } else {
+ if (opcode == Opcodes.RET) {
+ // No stack size delta.
+ currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_END;
+ currentBasicBlock.outputStackSize = (short) relativeStackSize;
+ endCurrentBasicBlockWithNoSuccessor();
+ } else { // xLOAD or xSTORE
+ int size = relativeStackSize + STACK_SIZE_DELTA[opcode];
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+ if (compute != COMPUTE_NOTHING) {
+ int currentMaxLocals;
+ if (opcode == Opcodes.LLOAD
+ || opcode == Opcodes.DLOAD
+ || opcode == Opcodes.LSTORE
+ || opcode == Opcodes.DSTORE) {
+ currentMaxLocals = varIndex + 2;
+ } else {
+ currentMaxLocals = varIndex + 1;
+ }
+ if (currentMaxLocals > maxLocals) {
+ maxLocals = currentMaxLocals;
+ }
+ }
+ if (opcode >= Opcodes.ISTORE && compute == COMPUTE_ALL_FRAMES && firstHandler != null) {
+ // If there are exception handler blocks, each instruction within a handler range is, in
+ // theory, a basic block (since execution can jump from this instruction to the exception
+ // handler). As a consequence, the local variable types at the beginning of the handler
+ // block should be the merge of the local variable types at all the instructions within the
+ // handler range. However, instead of creating a basic block for each instruction, we can
+ // get the same result in a more efficient way. Namely, by starting a new basic block after
+ // each xSTORE instruction, which is what we do here.
+ visitLabel(new Label());
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(final int opcode, final String type) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol typeSymbol = symbolTable.addConstantClass(type);
+ code.put12(opcode, typeSymbol.index);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, lastBytecodeOffset, typeSymbol, symbolTable);
+ } else if (opcode == Opcodes.NEW) {
+ // The stack size delta is 1 for NEW, and 0 for ANEWARRAY, CHECKCAST, or INSTANCEOF.
+ int size = relativeStackSize + 1;
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitFieldInsn(
+ final int opcode, final String owner, final String name, final String descriptor) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol fieldrefSymbol = symbolTable.addConstantFieldref(owner, name, descriptor);
+ code.put12(opcode, fieldrefSymbol.index);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, 0, fieldrefSymbol, symbolTable);
+ } else {
+ int size;
+ char firstDescChar = descriptor.charAt(0);
+ switch (opcode) {
+ case Opcodes.GETSTATIC:
+ size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 2 : 1);
+ break;
+ case Opcodes.PUTSTATIC:
+ size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -2 : -1);
+ break;
+ case Opcodes.GETFIELD:
+ size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 1 : 0);
+ break;
+ case Opcodes.PUTFIELD:
+ default:
+ size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -3 : -2);
+ break;
}
- if (compute != NOTHING) {
- // updates max locals
- int n;
- if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD
- || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) {
- n = var + 2;
- } else {
- n = var + 1;
- }
- if (n > maxLocals) {
- maxLocals = n;
- }
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
}
- // adds the instruction to the bytecode of the method
- if (var < 4 && opcode != Opcodes.RET) {
- int opt;
- if (opcode < Opcodes.ISTORE) {
- /* ILOAD_0 */
- opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var;
- } else {
- /* ISTORE_0 */
- opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var;
- }
- code.putByte(opt);
- } else if (var >= 256) {
- code.putByte(196 /* WIDE */).put12(opcode, var);
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(
+ final int opcode,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol methodrefSymbol = symbolTable.addConstantMethodref(owner, name, descriptor, isInterface);
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ code.put12(Opcodes.INVOKEINTERFACE, methodrefSymbol.index)
+ .put11(methodrefSymbol.getArgumentsAndReturnSizes() >> 2, 0);
+ } else {
+ code.put12(opcode, methodrefSymbol.index);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(opcode, 0, methodrefSymbol, symbolTable);
+ } else {
+ int argumentsAndReturnSize = methodrefSymbol.getArgumentsAndReturnSizes();
+ int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2);
+ int size;
+ if (opcode == Opcodes.INVOKESTATIC) {
+ size = relativeStackSize + stackSizeDelta + 1;
} else {
- code.put11(opcode, var);
+ size = relativeStackSize + stackSizeDelta;
}
- if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) {
- visitLabel(new Label());
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
}
+ relativeStackSize = size;
+ }
}
-
- @Override
- public void visitTypeInsn(final int opcode, final String type) {
- Item i = cw.newClassItem(type);
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, code.length, cw, i);
- } else if (opcode == Opcodes.NEW) {
- // updates current and max stack sizes only if opcode == NEW
- // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
- int size = stackSize + 1;
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- code.put12(opcode, i.index);
- }
-
- @Override
- public void visitFieldInsn(final int opcode, final String owner,
- final String name, final String desc) {
- Item i = cw.newFieldItem(owner, name, desc);
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, cw, i);
- } else {
- int size;
- // computes the stack size variation
- char c = desc.charAt(0);
- switch (opcode) {
- case Opcodes.GETSTATIC:
- size = stackSize + (c == 'D' || c == 'J' ? 2 : 1);
- break;
- case Opcodes.PUTSTATIC:
- size = stackSize + (c == 'D' || c == 'J' ? -2 : -1);
- break;
- case Opcodes.GETFIELD:
- size = stackSize + (c == 'D' || c == 'J' ? 1 : 0);
- break;
- // case Constants.PUTFIELD:
- default:
- size = stackSize + (c == 'D' || c == 'J' ? -3 : -2);
- break;
- }
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- code.put12(opcode, i.index);
- }
-
- @Override
- public void visitMethodInsn(final int opcode, final String owner,
- final String name, final String desc) {
- boolean itf = opcode == Opcodes.INVOKEINTERFACE;
- Item i = cw.newMethodItem(owner, name, desc, itf);
- int argSize = i.intVal;
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, cw, i);
- } else {
- /*
- * computes the stack size variation. In order not to recompute
- * several times this variation for the same Item, we use the
- * intVal field of this item to store this variation, once it
- * has been computed. More precisely this intVal field stores
- * the sizes of the arguments and of the return value
- * corresponding to desc.
- */
- if (argSize == 0) {
- // the above sizes have not been computed yet,
- // so we compute them...
- argSize = Type.getArgumentsAndReturnSizes(desc);
- // ... and we save them in order
- // not to recompute them in the future
- i.intVal = argSize;
- }
- int size;
- if (opcode == Opcodes.INVOKESTATIC) {
- size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
- } else {
- size = stackSize - (argSize >> 2) + (argSize & 0x03);
- }
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- if (itf) {
- if (argSize == 0) {
- argSize = Type.getArgumentsAndReturnSizes(desc);
- i.intVal = argSize;
- }
- code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0);
- } else {
- code.put12(opcode, i.index);
- }
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol invokeDynamicSymbol =
+ symbolTable.addConstantInvokeDynamic(
+ name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+ code.put12(Opcodes.INVOKEDYNAMIC, invokeDynamicSymbol.index);
+ code.putShort(0);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, invokeDynamicSymbol, symbolTable);
+ } else {
+ int argumentsAndReturnSize = invokeDynamicSymbol.getArgumentsAndReturnSizes();
+ int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2) + 1;
+ int size = relativeStackSize + stackSizeDelta;
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitJumpInsn(final int opcode, final Label label) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ // Compute the 'base' opcode, i.e. GOTO or JSR if opcode is GOTO_W or JSR_W, otherwise opcode.
+ int baseOpcode =
+ opcode >= Constants.GOTO_W ? opcode - Constants.WIDE_JUMP_OPCODE_DELTA : opcode;
+ boolean nextInsnIsJumpTarget = false;
+ if ((label.flags & Label.FLAG_RESOLVED) != 0
+ && label.bytecodeOffset - code.length < Short.MIN_VALUE) {
+ // Case of a backward jump with an offset < -32768. In this case we automatically replace GOTO
+ // with GOTO_W, JSR with JSR_W and IFxxx with IFNOTxxx GOTO_W L:..., where
+ // IFNOTxxx is the "opposite" opcode of IFxxx (e.g. IFNE for IFEQ) and where designates
+ // the instruction just after the GOTO_W.
+ if (baseOpcode == Opcodes.GOTO) {
+ code.putByte(Constants.GOTO_W);
+ } else if (baseOpcode == Opcodes.JSR) {
+ code.putByte(Constants.JSR_W);
+ } else {
+ // Put the "opposite" opcode of baseOpcode. This can be done by flipping the least
+ // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ (with a
+ // pre and post offset by 1). The jump offset is 8 bytes (3 for IFNOTxxx, 5 for GOTO_W).
+ code.putByte(baseOpcode >= Opcodes.IFNULL ? baseOpcode ^ 1 : ((baseOpcode + 1) ^ 1) - 1);
+ code.putShort(8);
+ // Here we could put a GOTO_W in theory, but if ASM specific instructions are used in this
+ // method or another one, and if the class has frames, we will need to insert a frame after
+ // this GOTO_W during the additional ClassReader -> ClassWriter round trip to remove the ASM
+ // specific instructions. To not miss this additional frame, we need to use an ASM_GOTO_W
+ // here, which has the unfortunate effect of forcing this additional round trip (which in
+ // some case would not have been really necessary, but we can't know this at this point).
+ code.putByte(Constants.ASM_GOTO_W);
+ hasAsmInstructions = true;
+ // The instruction after the GOTO_W becomes the target of the IFNOT instruction.
+ nextInsnIsJumpTarget = true;
+ }
+ label.put(code, code.length - 1, true);
+ } else if (baseOpcode != opcode) {
+ // Case of a GOTO_W or JSR_W specified by the user (normally ClassReader when used to remove
+ // ASM specific instructions). In this case we keep the original instruction.
+ code.putByte(opcode);
+ label.put(code, code.length - 1, true);
+ } else {
+ // Case of a jump with an offset >= -32768, or of a jump with an unknown offset. In these
+ // cases we store the offset in 2 bytes (which will be increased via a ClassReader ->
+ // ClassWriter round trip if it turns out that 2 bytes are not sufficient).
+ code.putByte(baseOpcode);
+ label.put(code, code.length - 1, false);
}
- @Override
- public void visitInvokeDynamicInsn(final String name, final String desc,
- final Handle bsm, final Object... bsmArgs) {
- Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs);
- int argSize = i.intVal;
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i);
- } else {
- /*
- * computes the stack size variation. In order not to recompute
- * several times this variation for the same Item, we use the
- * intVal field of this item to store this variation, once it
- * has been computed. More precisely this intVal field stores
- * the sizes of the arguments and of the return value
- * corresponding to desc.
- */
- if (argSize == 0) {
- // the above sizes have not been computed yet,
- // so we compute them...
- argSize = Type.getArgumentsAndReturnSizes(desc);
- // ... and we save them in order
- // not to recompute them in the future
- i.intVal = argSize;
- }
- int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
-
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- code.put12(Opcodes.INVOKEDYNAMIC, i.index);
- code.putShort(0);
- }
-
- @Override
- public void visitJumpInsn(final int opcode, final Label label) {
- Label nextInsn = null;
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(opcode, 0, null, null);
- // 'label' is the target of a jump instruction
- label.getFirst().status |= Label.TARGET;
- // adds 'label' as a successor of this basic block
- addSuccessor(Edge.NORMAL, label);
- if (opcode != Opcodes.GOTO) {
- // creates a Label for the next basic block
- nextInsn = new Label();
- }
- } else {
- if (opcode == Opcodes.JSR) {
- if ((label.status & Label.SUBROUTINE) == 0) {
- label.status |= Label.SUBROUTINE;
- ++subroutines;
- }
- currentBlock.status |= Label.JSR;
- addSuccessor(stackSize + 1, label);
- // creates a Label for the next basic block
- nextInsn = new Label();
- /*
- * note that, by construction in this method, a JSR block
- * has at least two successors in the control flow graph:
- * the first one leads the next instruction after the JSR,
- * while the second one leads to the JSR target.
- */
- } else {
- // updates current stack size (max stack size unchanged
- // because stack size variation always negative in this
- // case)
- stackSize += Frame.SIZE[opcode];
- addSuccessor(stackSize, label);
- }
- }
- }
- // adds the instruction to the bytecode of the method
- if ((label.status & Label.RESOLVED) != 0
- && label.position - code.length < Short.MIN_VALUE) {
- /*
- * case of a backward jump with an offset < -32768. In this case we
- * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx
- * with IFNOTxxx GOTO_W , where IFNOTxxx is the
- * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where
- * designates the instruction just after the GOTO_W.
- */
- if (opcode == Opcodes.GOTO) {
- code.putByte(200); // GOTO_W
- } else if (opcode == Opcodes.JSR) {
- code.putByte(201); // JSR_W
- } else {
- // if the IF instruction is transformed into IFNOT GOTO_W the
- // next instruction becomes the target of the IFNOT instruction
- if (nextInsn != null) {
- nextInsn.status |= Label.TARGET;
- }
- code.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1
- : opcode ^ 1);
- code.putShort(8); // jump offset
- code.putByte(200); // GOTO_W
- }
- label.put(this, code, code.length - 1, true);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ Label nextBasicBlock = null;
+ if (compute == COMPUTE_ALL_FRAMES) {
+ currentBasicBlock.frame.execute(baseOpcode, 0, null, null);
+ // Record the fact that 'label' is the target of a jump instruction.
+ label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
+ // Add 'label' as a successor of the current basic block.
+ addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
+ if (baseOpcode != Opcodes.GOTO) {
+ // The next instruction starts a new basic block (except for GOTO: by default the code
+ // following a goto is unreachable - unless there is an explicit label for it - and we
+ // should not compute stack frame types for its instructions).
+ nextBasicBlock = new Label();
+ }
+ } else if (compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(baseOpcode, 0, null, null);
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
+ // No need to update maxRelativeStackSize (the stack size delta is always negative).
+ relativeStackSize += STACK_SIZE_DELTA[baseOpcode];
+ } else {
+ if (baseOpcode == Opcodes.JSR) {
+ // Record the fact that 'label' designates a subroutine, if not already done.
+ if ((label.flags & Label.FLAG_SUBROUTINE_START) == 0) {
+ label.flags |= Label.FLAG_SUBROUTINE_START;
+ hasSubroutines = true;
+ }
+ currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_CALLER;
+ // Note that, by construction in this method, a block which calls a subroutine has at
+ // least two successors in the control flow graph: the first one (added below) leads to
+ // the instruction after the JSR, while the second one (added here) leads to the JSR
+ // target. Note that the first successor is virtual (it does not correspond to a possible
+ // execution path): it is only used to compute the successors of the basic blocks ending
+ // with a ret, in {@link Label#addSubroutineRetSuccessors}.
+ addSuccessorToCurrentBasicBlock(relativeStackSize + 1, label);
+ // The instruction after the JSR starts a new basic block.
+ nextBasicBlock = new Label();
} else {
- /*
- * case of a backward jump with an offset >= -32768, or of a forward
- * jump with, of course, an unknown offset. In these cases we store
- * the offset in 2 bytes (which will be increased in
- * resizeInstructions, if needed).
- */
- code.putByte(opcode);
- label.put(this, code, code.length - 1, false);
- }
- if (currentBlock != null) {
- if (nextInsn != null) {
- // if the jump instruction is not a GOTO, the next instruction
- // is also a successor of this instruction. Calling visitLabel
- // adds the label of this next instruction as a successor of the
- // current block, and starts a new basic block
- visitLabel(nextInsn);
- }
- if (opcode == Opcodes.GOTO) {
- noSuccessor();
- }
- }
+ // No need to update maxRelativeStackSize (the stack size delta is always negative).
+ relativeStackSize += STACK_SIZE_DELTA[baseOpcode];
+ addSuccessorToCurrentBasicBlock(relativeStackSize, label);
+ }
+ }
+ // If the next instruction starts a new basic block, call visitLabel to add the label of this
+ // instruction as a successor of the current block, and to start a new basic block.
+ if (nextBasicBlock != null) {
+ if (nextInsnIsJumpTarget) {
+ nextBasicBlock.flags |= Label.FLAG_JUMP_TARGET;
+ }
+ visitLabel(nextBasicBlock);
+ }
+ if (baseOpcode == Opcodes.GOTO) {
+ endCurrentBasicBlockWithNoSuccessor();
+ }
}
-
- @Override
- public void visitLabel(final Label label) {
- // resolves previous forward references to label, if any
- resize |= label.resolve(this, code.length, code.data);
- // updates currentBlock
- if ((label.status & Label.DEBUG) != 0) {
- return;
- }
- if (compute == FRAMES) {
- if (currentBlock != null) {
- if (label.position == currentBlock.position) {
- // successive labels, do not start a new basic block
- currentBlock.status |= (label.status & Label.TARGET);
- label.frame = currentBlock.frame;
- return;
- }
- // ends current block (with one new successor)
- addSuccessor(Edge.NORMAL, label);
- }
- // begins a new current block
- currentBlock = label;
- if (label.frame == null) {
- label.frame = new Frame();
- label.frame.owner = label;
- }
- // updates the basic block list
- if (previousBlock != null) {
- if (label.position == previousBlock.position) {
- previousBlock.status |= (label.status & Label.TARGET);
- label.frame = previousBlock.frame;
- currentBlock = previousBlock;
- return;
- }
- previousBlock.successor = label;
- }
- previousBlock = label;
- } else if (compute == MAXS) {
- if (currentBlock != null) {
- // ends current block (with one new successor)
- currentBlock.outputStackMax = maxStackSize;
- addSuccessor(stackSize, label);
- }
- // begins a new current block
- currentBlock = label;
- // resets the relative current and max stack sizes
- stackSize = 0;
- maxStackSize = 0;
- // updates the basic block list
- if (previousBlock != null) {
- previousBlock.successor = label;
- }
- previousBlock = label;
- }
+ }
+
+ @Override
+ public void visitLabel(final Label label) {
+ // Resolve the forward references to this label, if any.
+ hasAsmInstructions |= label.resolve(code.data, code.length);
+ // visitLabel starts a new basic block (except for debug only labels), so we need to update the
+ // previous and current block references and list of successors.
+ if ((label.flags & Label.FLAG_DEBUG_ONLY) != 0) {
+ return;
+ }
+ if (compute == COMPUTE_ALL_FRAMES) {
+ if (currentBasicBlock != null) {
+ if (label.bytecodeOffset == currentBasicBlock.bytecodeOffset) {
+ // We use {@link Label#getCanonicalInstance} to store the state of a basic block in only
+ // one place, but this does not work for labels which have not been visited yet.
+ // Therefore, when we detect here two labels having the same bytecode offset, we need to
+ // - consolidate the state scattered in these two instances into the canonical instance:
+ currentBasicBlock.flags |= (label.flags & Label.FLAG_JUMP_TARGET);
+ // - make sure the two instances share the same Frame instance (the implementation of
+ // {@link Label#getCanonicalInstance} relies on this property; here label.frame should be
+ // null):
+ label.frame = currentBasicBlock.frame;
+ // - and make sure to NOT assign 'label' into 'currentBasicBlock' or 'lastBasicBlock', so
+ // that they still refer to the canonical instance for this bytecode offset.
+ return;
+ }
+ // End the current basic block (with one new successor).
+ addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
+ }
+ // Append 'label' at the end of the basic block list.
+ if (lastBasicBlock != null) {
+ if (label.bytecodeOffset == lastBasicBlock.bytecodeOffset) {
+ // Same comment as above.
+ lastBasicBlock.flags |= (label.flags & Label.FLAG_JUMP_TARGET);
+ // Here label.frame should be null.
+ label.frame = lastBasicBlock.frame;
+ currentBasicBlock = lastBasicBlock;
+ return;
+ }
+ lastBasicBlock.nextBasicBlock = label;
+ }
+ lastBasicBlock = label;
+ // Make it the new current basic block.
+ currentBasicBlock = label;
+ // Here label.frame should be null.
+ label.frame = new Frame(label);
+ } else if (compute == COMPUTE_INSERTED_FRAMES) {
+ if (currentBasicBlock == null) {
+ // This case should happen only once, for the visitLabel call in the constructor. Indeed, if
+ // compute is equal to COMPUTE_INSERTED_FRAMES, currentBasicBlock stays unchanged.
+ currentBasicBlock = label;
+ } else {
+ // Update the frame owner so that a correct frame offset is computed in Frame.accept().
+ currentBasicBlock.frame.owner = label;
+ }
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
+ if (currentBasicBlock != null) {
+ // End the current basic block (with one new successor).
+ currentBasicBlock.outputStackMax = (short) maxRelativeStackSize;
+ addSuccessorToCurrentBasicBlock(relativeStackSize, label);
+ }
+ // Start a new current basic block, and reset the current and maximum relative stack sizes.
+ currentBasicBlock = label;
+ relativeStackSize = 0;
+ maxRelativeStackSize = 0;
+ // Append the new basic block at the end of the basic block list.
+ if (lastBasicBlock != null) {
+ lastBasicBlock.nextBasicBlock = label;
+ }
+ lastBasicBlock = label;
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES && currentBasicBlock == null) {
+ // This case should happen only once, for the visitLabel call in the constructor. Indeed, if
+ // compute is equal to COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES, currentBasicBlock stays
+ // unchanged.
+ currentBasicBlock = label;
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(final Object value) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol constantSymbol = symbolTable.addConstant(value);
+ int constantIndex = constantSymbol.index;
+ char firstDescriptorChar;
+ boolean isLongOrDouble =
+ constantSymbol.tag == Symbol.CONSTANT_LONG_TAG
+ || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG
+ || (constantSymbol.tag == Symbol.CONSTANT_DYNAMIC_TAG
+ && ((firstDescriptorChar = constantSymbol.value.charAt(0)) == 'J'
+ || firstDescriptorChar == 'D'));
+ if (isLongOrDouble) {
+ code.put12(Constants.LDC2_W, constantIndex);
+ } else if (constantIndex >= 256) {
+ code.put12(Constants.LDC_W, constantIndex);
+ } else {
+ code.put11(Opcodes.LDC, constantIndex);
}
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(Opcodes.LDC, 0, constantSymbol, symbolTable);
+ } else {
+ int size = relativeStackSize + (isLongOrDouble ? 2 : 1);
+ if (size > maxRelativeStackSize) {
+ maxRelativeStackSize = size;
+ }
+ relativeStackSize = size;
+ }
+ }
+ }
+
+ @Override
+ public void visitIincInsn(final int varIndex, final int increment) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ if ((varIndex > 255) || (increment > 127) || (increment < -128)) {
+ code.putByte(Constants.WIDE).put12(Opcodes.IINC, varIndex).putShort(increment);
+ } else {
+ code.putByte(Opcodes.IINC).put11(varIndex, increment);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null
+ && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) {
+ currentBasicBlock.frame.execute(Opcodes.IINC, varIndex, null, null);
+ }
+ if (compute != COMPUTE_NOTHING) {
+ int currentMaxLocals = varIndex + 1;
+ if (currentMaxLocals > maxLocals) {
+ maxLocals = currentMaxLocals;
+ }
+ }
+ }
+
+ @Override
+ public void visitTableSwitchInsn(
+ final int min, final int max, final Label dflt, final Label... labels) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ code.putByte(Opcodes.TABLESWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4);
+ dflt.put(code, lastBytecodeOffset, true);
+ code.putInt(min).putInt(max);
+ for (Label label : labels) {
+ label.put(code, lastBytecodeOffset, true);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ visitSwitchInsn(dflt, labels);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ code.putByte(Opcodes.LOOKUPSWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4);
+ dflt.put(code, lastBytecodeOffset, true);
+ code.putInt(labels.length);
+ for (int i = 0; i < labels.length; ++i) {
+ code.putInt(keys[i]);
+ labels[i].put(code, lastBytecodeOffset, true);
+ }
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ visitSwitchInsn(dflt, labels);
+ }
+
+ private void visitSwitchInsn(final Label dflt, final Label[] labels) {
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES) {
+ currentBasicBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null);
+ // Add all the labels as successors of the current basic block.
+ addSuccessorToCurrentBasicBlock(Edge.JUMP, dflt);
+ dflt.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
+ for (Label label : labels) {
+ addSuccessorToCurrentBasicBlock(Edge.JUMP, label);
+ label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET;
+ }
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
+ // No need to update maxRelativeStackSize (the stack size delta is always negative).
+ --relativeStackSize;
+ // Add all the labels as successors of the current basic block.
+ addSuccessorToCurrentBasicBlock(relativeStackSize, dflt);
+ for (Label label : labels) {
+ addSuccessorToCurrentBasicBlock(relativeStackSize, label);
+ }
+ }
+ // End the current basic block.
+ endCurrentBasicBlockWithNoSuccessor();
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
+ lastBytecodeOffset = code.length;
+ // Add the instruction to the bytecode of the method.
+ Symbol descSymbol = symbolTable.addConstantClass(descriptor);
+ code.put12(Opcodes.MULTIANEWARRAY, descSymbol.index).putByte(numDimensions);
+ // If needed, update the maximum stack size and number of locals, and stack map frames.
+ if (currentBasicBlock != null) {
+ if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
+ currentBasicBlock.frame.execute(
+ Opcodes.MULTIANEWARRAY, numDimensions, descSymbol, symbolTable);
+ } else {
+ // No need to update maxRelativeStackSize (the stack size delta is always negative).
+ relativeStackSize += 1 - numDimensions;
+ }
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitInsnAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastCodeRuntimeVisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable,
+ (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8),
+ typePath,
+ descriptor,
+ lastCodeRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastCodeRuntimeInvisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable,
+ (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8),
+ typePath,
+ descriptor,
+ lastCodeRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public void visitTryCatchBlock(
+ final Label start, final Label end, final Label handler, final String type) {
+ Handler newHandler =
+ new Handler(
+ start, end, handler, type != null ? symbolTable.addConstantClass(type).index : 0, type);
+ if (firstHandler == null) {
+ firstHandler = newHandler;
+ } else {
+ lastHandler.nextHandler = newHandler;
+ }
+ lastHandler = newHandler;
+ }
+
+ @Override
+ public AnnotationVisitor visitTryCatchAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastCodeRuntimeVisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastCodeRuntimeInvisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(
+ final String name,
+ final String descriptor,
+ final String signature,
+ final Label start,
+ final Label end,
+ final int index) {
+ if (signature != null) {
+ if (localVariableTypeTable == null) {
+ localVariableTypeTable = new ByteVector();
+ }
+ ++localVariableTypeTableLength;
+ localVariableTypeTable
+ .putShort(start.bytecodeOffset)
+ .putShort(end.bytecodeOffset - start.bytecodeOffset)
+ .putShort(symbolTable.addConstantUtf8(name))
+ .putShort(symbolTable.addConstantUtf8(signature))
+ .putShort(index);
+ }
+ if (localVariableTable == null) {
+ localVariableTable = new ByteVector();
+ }
+ ++localVariableTableLength;
+ localVariableTable
+ .putShort(start.bytecodeOffset)
+ .putShort(end.bytecodeOffset - start.bytecodeOffset)
+ .putShort(symbolTable.addConstantUtf8(name))
+ .putShort(symbolTable.addConstantUtf8(descriptor))
+ .putShort(index);
+ if (compute != COMPUTE_NOTHING) {
+ char firstDescChar = descriptor.charAt(0);
+ int currentMaxLocals = index + (firstDescChar == 'J' || firstDescChar == 'D' ? 2 : 1);
+ if (currentMaxLocals > maxLocals) {
+ maxLocals = currentMaxLocals;
+ }
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitLocalVariableAnnotation(
+ final int typeRef,
+ final TypePath typePath,
+ final Label[] start,
+ final Label[] end,
+ final int[] index,
+ final String descriptor,
+ final boolean visible) {
+ // Create a ByteVector to hold a 'type_annotation' JVMS structure.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20.
+ ByteVector typeAnnotation = new ByteVector();
+ // Write target_type, target_info, and target_path.
+ typeAnnotation.putByte(typeRef >>> 24).putShort(start.length);
+ for (int i = 0; i < start.length; ++i) {
+ typeAnnotation
+ .putShort(start[i].bytecodeOffset)
+ .putShort(end[i].bytecodeOffset - start[i].bytecodeOffset)
+ .putShort(index[i]);
+ }
+ TypePath.put(typePath, typeAnnotation);
+ // Write type_index and reserve space for num_element_value_pairs.
+ typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0);
+ if (visible) {
+ return lastCodeRuntimeVisibleTypeAnnotation =
+ new AnnotationWriter(
+ symbolTable,
+ /* useNamedValues = */ true,
+ typeAnnotation,
+ lastCodeRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastCodeRuntimeInvisibleTypeAnnotation =
+ new AnnotationWriter(
+ symbolTable,
+ /* useNamedValues = */ true,
+ typeAnnotation,
+ lastCodeRuntimeInvisibleTypeAnnotation);
+ }
+ }
- @Override
- public void visitLdcInsn(final Object cst) {
- Item i = cw.newConstItem(cst);
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.LDC, 0, cw, i);
- } else {
- int size;
- // computes the stack size variation
- if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
- size = stackSize + 2;
- } else {
- size = stackSize + 1;
- }
- // updates current and max stack sizes
- if (size > maxStackSize) {
- maxStackSize = size;
- }
- stackSize = size;
- }
- }
- // adds the instruction to the bytecode of the method
- int index = i.index;
- if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
- code.put12(20 /* LDC2_W */, index);
- } else if (index >= 256) {
- code.put12(19 /* LDC_W */, index);
- } else {
- code.put11(Opcodes.LDC, index);
- }
+ @Override
+ public void visitLineNumber(final int line, final Label start) {
+ if (lineNumberTable == null) {
+ lineNumberTable = new ByteVector();
+ }
+ ++lineNumberTableLength;
+ lineNumberTable.putShort(start.bytecodeOffset);
+ lineNumberTable.putShort(line);
+ }
+
+ @Override
+ public void visitMaxs(final int maxStack, final int maxLocals) {
+ if (compute == COMPUTE_ALL_FRAMES) {
+ computeAllFrames();
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
+ computeMaxStackAndLocal();
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) {
+ this.maxStack = maxRelativeStackSize;
+ } else {
+ this.maxStack = maxStack;
+ this.maxLocals = maxLocals;
+ }
+ }
+
+ /** Computes all the stack map frames of the method, from scratch. */
+ private void computeAllFrames() {
+ // Complete the control flow graph with exception handler blocks.
+ Handler handler = firstHandler;
+ while (handler != null) {
+ String catchTypeDescriptor =
+ handler.catchTypeDescriptor == null ? "java/lang/Throwable" : handler.catchTypeDescriptor;
+ int catchType = Frame.getAbstractTypeFromInternalName(symbolTable, catchTypeDescriptor);
+ // Mark handlerBlock as an exception handler.
+ Label handlerBlock = handler.handlerPc.getCanonicalInstance();
+ handlerBlock.flags |= Label.FLAG_JUMP_TARGET;
+ // Add handlerBlock as a successor of all the basic blocks in the exception handler range.
+ Label handlerRangeBlock = handler.startPc.getCanonicalInstance();
+ Label handlerRangeEnd = handler.endPc.getCanonicalInstance();
+ while (handlerRangeBlock != handlerRangeEnd) {
+ handlerRangeBlock.outgoingEdges =
+ new Edge(catchType, handlerBlock, handlerRangeBlock.outgoingEdges);
+ handlerRangeBlock = handlerRangeBlock.nextBasicBlock;
+ }
+ handler = handler.nextHandler;
}
- @Override
- public void visitIincInsn(final int var, final int increment) {
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.IINC, var, null, null);
- }
- }
- if (compute != NOTHING) {
- // updates max locals
- int n = var + 1;
- if (n > maxLocals) {
- maxLocals = n;
- }
- }
- // adds the instruction to the bytecode of the method
- if ((var > 255) || (increment > 127) || (increment < -128)) {
- code.putByte(196 /* WIDE */).put12(Opcodes.IINC, var)
- .putShort(increment);
- } else {
- code.putByte(Opcodes.IINC).put11(var, increment);
- }
+ // Create and visit the first (implicit) frame.
+ Frame firstFrame = firstBasicBlock.frame;
+ firstFrame.setInputFrameFromDescriptor(symbolTable, accessFlags, descriptor, this.maxLocals);
+ firstFrame.accept(this);
+
+ // Fix point algorithm: add the first basic block to a list of blocks to process (i.e. blocks
+ // whose stack map frame has changed) and, while there are blocks to process, remove one from
+ // the list and update the stack map frames of its successor blocks in the control flow graph
+ // (which might change them, in which case these blocks must be processed too, and are thus
+ // added to the list of blocks to process). Also compute the maximum stack size of the method,
+ // as a by-product.
+ Label listOfBlocksToProcess = firstBasicBlock;
+ listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST;
+ int maxStackSize = 0;
+ while (listOfBlocksToProcess != Label.EMPTY_LIST) {
+ // Remove a basic block from the list of blocks to process.
+ Label basicBlock = listOfBlocksToProcess;
+ listOfBlocksToProcess = listOfBlocksToProcess.nextListElement;
+ basicBlock.nextListElement = null;
+ // By definition, basicBlock is reachable.
+ basicBlock.flags |= Label.FLAG_REACHABLE;
+ // Update the (absolute) maximum stack size.
+ int maxBlockStackSize = basicBlock.frame.getInputStackSize() + basicBlock.outputStackMax;
+ if (maxBlockStackSize > maxStackSize) {
+ maxStackSize = maxBlockStackSize;
+ }
+ // Update the successor blocks of basicBlock in the control flow graph.
+ Edge outgoingEdge = basicBlock.outgoingEdges;
+ while (outgoingEdge != null) {
+ Label successorBlock = outgoingEdge.successor.getCanonicalInstance();
+ boolean successorBlockChanged =
+ basicBlock.frame.merge(symbolTable, successorBlock.frame, outgoingEdge.info);
+ if (successorBlockChanged && successorBlock.nextListElement == null) {
+ // If successorBlock has changed it must be processed. Thus, if it is not already in the
+ // list of blocks to process, add it to this list.
+ successorBlock.nextListElement = listOfBlocksToProcess;
+ listOfBlocksToProcess = successorBlock;
+ }
+ outgoingEdge = outgoingEdge.nextEdge;
+ }
}
- @Override
- public void visitTableSwitchInsn(final int min, final int max,
- final Label dflt, final Label... labels) {
- // adds the instruction to the bytecode of the method
- int source = code.length;
- code.putByte(Opcodes.TABLESWITCH);
- code.putByteArray(null, 0, (4 - code.length % 4) % 4);
- dflt.put(this, code, source, true);
- code.putInt(min).putInt(max);
- for (int i = 0; i < labels.length; ++i) {
- labels[i].put(this, code, source, true);
- }
- // updates currentBlock
- visitSwitchInsn(dflt, labels);
- }
-
- @Override
- public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
- final Label[] labels) {
- // adds the instruction to the bytecode of the method
- int source = code.length;
- code.putByte(Opcodes.LOOKUPSWITCH);
- code.putByteArray(null, 0, (4 - code.length % 4) % 4);
- dflt.put(this, code, source, true);
- code.putInt(labels.length);
- for (int i = 0; i < labels.length; ++i) {
- code.putInt(keys[i]);
- labels[i].put(this, code, source, true);
- }
- // updates currentBlock
- visitSwitchInsn(dflt, labels);
- }
-
- private void visitSwitchInsn(final Label dflt, final Label[] labels) {
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null);
- // adds current block successors
- addSuccessor(Edge.NORMAL, dflt);
- dflt.getFirst().status |= Label.TARGET;
- for (int i = 0; i < labels.length; ++i) {
- addSuccessor(Edge.NORMAL, labels[i]);
- labels[i].getFirst().status |= Label.TARGET;
- }
- } else {
- // updates current stack size (max stack size unchanged)
- --stackSize;
- // adds current block successors
- addSuccessor(stackSize, dflt);
- for (int i = 0; i < labels.length; ++i) {
- addSuccessor(stackSize, labels[i]);
- }
- }
- // ends current block
- noSuccessor();
- }
+ // Loop over all the basic blocks and visit the stack map frames that must be stored in the
+ // StackMapTable attribute. Also replace unreachable code with NOP* ATHROW, and remove it from
+ // exception handler ranges.
+ Label basicBlock = firstBasicBlock;
+ while (basicBlock != null) {
+ if ((basicBlock.flags & (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE))
+ == (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) {
+ basicBlock.frame.accept(this);
+ }
+ if ((basicBlock.flags & Label.FLAG_REACHABLE) == 0) {
+ // Find the start and end bytecode offsets of this unreachable block.
+ Label nextBasicBlock = basicBlock.nextBasicBlock;
+ int startOffset = basicBlock.bytecodeOffset;
+ int endOffset = (nextBasicBlock == null ? code.length : nextBasicBlock.bytecodeOffset) - 1;
+ if (endOffset >= startOffset) {
+ // Replace its instructions with NOP ... NOP ATHROW.
+ for (int i = startOffset; i < endOffset; ++i) {
+ code.data[i] = Opcodes.NOP;
+ }
+ code.data[endOffset] = (byte) Opcodes.ATHROW;
+ // Emit a frame for this unreachable block, with no local and a Throwable on the stack
+ // (so that the ATHROW could consume this Throwable if it were reachable).
+ int frameIndex = visitFrameStart(startOffset, /* numLocal = */ 0, /* numStack = */ 1);
+ currentFrame[frameIndex] =
+ Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable");
+ visitFrameEnd();
+ // Remove this unreachable basic block from the exception handler ranges.
+ firstHandler = Handler.removeRange(firstHandler, basicBlock, nextBasicBlock);
+ // The maximum stack size is now at least one, because of the Throwable declared above.
+ maxStackSize = Math.max(maxStackSize, 1);
+ }
+ }
+ basicBlock = basicBlock.nextBasicBlock;
}
- @Override
- public void visitMultiANewArrayInsn(final String desc, final int dims) {
- Item i = cw.newClassItem(desc);
- // Label currentBlock = this.currentBlock;
- if (currentBlock != null) {
- if (compute == FRAMES) {
- currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i);
- } else {
- // updates current stack size (max stack size unchanged because
- // stack size variation always negative or null)
- stackSize += 1 - dims;
- }
- }
- // adds the instruction to the bytecode of the method
- code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims);
- }
-
- @Override
- public void visitTryCatchBlock(final Label start, final Label end,
- final Label handler, final String type) {
- ++handlerCount;
- Handler h = new Handler();
- h.start = start;
- h.end = end;
- h.handler = handler;
- h.desc = type;
- h.type = type != null ? cw.newClass(type) : 0;
- if (lastHandler == null) {
- firstHandler = h;
+ this.maxStack = maxStackSize;
+ }
+
+ /** Computes the maximum stack size of the method. */
+ private void computeMaxStackAndLocal() {
+ // Complete the control flow graph with exception handler blocks.
+ Handler handler = firstHandler;
+ while (handler != null) {
+ Label handlerBlock = handler.handlerPc;
+ Label handlerRangeBlock = handler.startPc;
+ Label handlerRangeEnd = handler.endPc;
+ // Add handlerBlock as a successor of all the basic blocks in the exception handler range.
+ while (handlerRangeBlock != handlerRangeEnd) {
+ if ((handlerRangeBlock.flags & Label.FLAG_SUBROUTINE_CALLER) == 0) {
+ handlerRangeBlock.outgoingEdges =
+ new Edge(Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges);
} else {
- lastHandler.next = h;
- }
- lastHandler = h;
+ // If handlerRangeBlock is a JSR block, add handlerBlock after the first two outgoing
+ // edges to preserve the hypothesis about JSR block successors order (see
+ // {@link #visitJumpInsn}).
+ handlerRangeBlock.outgoingEdges.nextEdge.nextEdge =
+ new Edge(
+ Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges.nextEdge.nextEdge);
+ }
+ handlerRangeBlock = handlerRangeBlock.nextBasicBlock;
+ }
+ handler = handler.nextHandler;
}
- @Override
- public void visitLocalVariable(final String name, final String desc,
- final String signature, final Label start, final Label end,
- final int index) {
- if (signature != null) {
- if (localVarType == null) {
- localVarType = new ByteVector();
- }
- ++localVarTypeCount;
- localVarType.putShort(start.position)
- .putShort(end.position - start.position)
- .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(signature))
- .putShort(index);
- }
- if (localVar == null) {
- localVar = new ByteVector();
- }
- ++localVarCount;
- localVar.putShort(start.position)
- .putShort(end.position - start.position)
- .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(desc))
- .putShort(index);
- if (compute != NOTHING) {
- // updates max locals
- char c = desc.charAt(0);
- int n = index + (c == 'J' || c == 'D' ? 2 : 1);
- if (n > maxLocals) {
- maxLocals = n;
- }
- }
+ // Complete the control flow graph with the successor blocks of subroutines, if needed.
+ if (hasSubroutines) {
+ // First step: find the subroutines. This step determines, for each basic block, to which
+ // subroutine(s) it belongs. Start with the main "subroutine":
+ short numSubroutines = 1;
+ firstBasicBlock.markSubroutine(numSubroutines);
+ // Then, mark the subroutines called by the main subroutine, then the subroutines called by
+ // those called by the main subroutine, etc.
+ for (short currentSubroutine = 1; currentSubroutine <= numSubroutines; ++currentSubroutine) {
+ Label basicBlock = firstBasicBlock;
+ while (basicBlock != null) {
+ if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0
+ && basicBlock.subroutineId == currentSubroutine) {
+ Label jsrTarget = basicBlock.outgoingEdges.nextEdge.successor;
+ if (jsrTarget.subroutineId == 0) {
+ // If this subroutine has not been marked yet, find its basic blocks.
+ jsrTarget.markSubroutine(++numSubroutines);
+ }
+ }
+ basicBlock = basicBlock.nextBasicBlock;
+ }
+ }
+ // Second step: find the successors in the control flow graph of each subroutine basic block
+ // 'r' ending with a RET instruction. These successors are the virtual successors of the basic
+ // blocks ending with JSR instructions (see {@link #visitJumpInsn)} that can reach 'r'.
+ Label basicBlock = firstBasicBlock;
+ while (basicBlock != null) {
+ if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) {
+ // By construction, jsr targets are stored in the second outgoing edge of basic blocks
+ // that ends with a jsr instruction (see {@link #FLAG_SUBROUTINE_CALLER}).
+ Label subroutine = basicBlock.outgoingEdges.nextEdge.successor;
+ subroutine.addSubroutineRetSuccessors(basicBlock);
+ }
+ basicBlock = basicBlock.nextBasicBlock;
+ }
}
- @Override
- public void visitLineNumber(final int line, final Label start) {
- if (lineNumber == null) {
- lineNumber = new ByteVector();
- }
- ++lineNumberCount;
- lineNumber.putShort(start.position);
- lineNumber.putShort(line);
- }
-
- @Override
- public void visitMaxs(final int maxStack, final int maxLocals) {
- if (ClassReader.FRAMES && compute == FRAMES) {
- // completes the control flow graph with exception handler blocks
- Handler handler = firstHandler;
- while (handler != null) {
- Label l = handler.start.getFirst();
- Label h = handler.handler.getFirst();
- Label e = handler.end.getFirst();
- // computes the kind of the edges to 'h'
- String t = handler.desc == null ? "java/lang/Throwable"
- : handler.desc;
- int kind = Frame.OBJECT | cw.addType(t);
- // h is an exception handler
- h.status |= Label.TARGET;
- // adds 'h' as a successor of labels between 'start' and 'end'
- while (l != e) {
- // creates an edge to 'h'
- Edge b = new Edge();
- b.info = kind;
- b.successor = h;
- // adds it to the successors of 'l'
- b.next = l.successors;
- l.successors = b;
- // goes to the next label
- l = l.successor;
- }
- handler = handler.next;
- }
-
- // creates and visits the first (implicit) frame
- Frame f = labels.frame;
- Type[] args = Type.getArgumentTypes(descriptor);
- f.initInputFrame(cw, access, args, this.maxLocals);
- visitFrame(f);
-
- /*
- * fix point algorithm: mark the first basic block as 'changed'
- * (i.e. put it in the 'changed' list) and, while there are changed
- * basic blocks, choose one, mark it as unchanged, and update its
- * successors (which can be changed in the process).
- */
- int max = 0;
- Label changed = labels;
- while (changed != null) {
- // removes a basic block from the list of changed basic blocks
- Label l = changed;
- changed = changed.next;
- l.next = null;
- f = l.frame;
- // a reachable jump target must be stored in the stack map
- if ((l.status & Label.TARGET) != 0) {
- l.status |= Label.STORE;
- }
- // all visited labels are reachable, by definition
- l.status |= Label.REACHABLE;
- // updates the (absolute) maximum stack size
- int blockMax = f.inputStack.length + l.outputStackMax;
- if (blockMax > max) {
- max = blockMax;
- }
- // updates the successors of the current basic block
- Edge e = l.successors;
- while (e != null) {
- Label n = e.successor.getFirst();
- boolean change = f.merge(cw, n.frame, e.info);
- if (change && n.next == null) {
- // if n has changed and is not already in the 'changed'
- // list, adds it to this list
- n.next = changed;
- changed = n;
- }
- e = e.next;
- }
- }
-
- // visits all the frames that must be stored in the stack map
- Label l = labels;
- while (l != null) {
- f = l.frame;
- if ((l.status & Label.STORE) != 0) {
- visitFrame(f);
- }
- if ((l.status & Label.REACHABLE) == 0) {
- // finds start and end of dead basic block
- Label k = l.successor;
- int start = l.position;
- int end = (k == null ? code.length : k.position) - 1;
- // if non empty basic block
- if (end >= start) {
- max = Math.max(max, 1);
- // replaces instructions with NOP ... NOP ATHROW
- for (int i = start; i < end; ++i) {
- code.data[i] = Opcodes.NOP;
- }
- code.data[end] = (byte) Opcodes.ATHROW;
- // emits a frame for this unreachable block
- int frameIndex = startFrame(start, 0, 1);
- frame[frameIndex] = Frame.OBJECT
- | cw.addType("java/lang/Throwable");
- endFrame();
- // removes the start-end range from the exception
- // handlers
- firstHandler = Handler.remove(firstHandler, l, k);
- }
- }
- l = l.successor;
- }
-
- handler = firstHandler;
- handlerCount = 0;
- while (handler != null) {
- handlerCount += 1;
- handler = handler.next;
- }
-
- this.maxStack = max;
- } else if (compute == MAXS) {
- // completes the control flow graph with exception handler blocks
- Handler handler = firstHandler;
- while (handler != null) {
- Label l = handler.start;
- Label h = handler.handler;
- Label e = handler.end;
- // adds 'h' as a successor of labels between 'start' and 'end'
- while (l != e) {
- // creates an edge to 'h'
- Edge b = new Edge();
- b.info = Edge.EXCEPTION;
- b.successor = h;
- // adds it to the successors of 'l'
- if ((l.status & Label.JSR) == 0) {
- b.next = l.successors;
- l.successors = b;
- } else {
- // if l is a JSR block, adds b after the first two edges
- // to preserve the hypothesis about JSR block successors
- // order (see {@link #visitJumpInsn})
- b.next = l.successors.next.next;
- l.successors.next.next = b;
- }
- // goes to the next label
- l = l.successor;
- }
- handler = handler.next;
- }
-
- if (subroutines > 0) {
- // completes the control flow graph with the RET successors
- /*
- * first step: finds the subroutines. This step determines, for
- * each basic block, to which subroutine(s) it belongs.
- */
- // finds the basic blocks that belong to the "main" subroutine
- int id = 0;
- labels.visitSubroutine(null, 1, subroutines);
- // finds the basic blocks that belong to the real subroutines
- Label l = labels;
- while (l != null) {
- if ((l.status & Label.JSR) != 0) {
- // the subroutine is defined by l's TARGET, not by l
- Label subroutine = l.successors.next.successor;
- // if this subroutine has not been visited yet...
- if ((subroutine.status & Label.VISITED) == 0) {
- // ...assigns it a new id and finds its basic blocks
- id += 1;
- subroutine.visitSubroutine(null, (id / 32L) << 32
- | (1L << (id % 32)), subroutines);
- }
- }
- l = l.successor;
- }
- // second step: finds the successors of RET blocks
- l = labels;
- while (l != null) {
- if ((l.status & Label.JSR) != 0) {
- Label L = labels;
- while (L != null) {
- L.status &= ~Label.VISITED2;
- L = L.successor;
- }
- // the subroutine is defined by l's TARGET, not by l
- Label subroutine = l.successors.next.successor;
- subroutine.visitSubroutine(l, 0, subroutines);
- }
- l = l.successor;
- }
- }
-
- /*
- * control flow analysis algorithm: while the block stack is not
- * empty, pop a block from this stack, update the max stack size,
- * compute the true (non relative) begin stack size of the
- * successors of this block, and push these successors onto the
- * stack (unless they have already been pushed onto the stack).
- * Note: by hypothesis, the {@link Label#inputStackTop} of the
- * blocks in the block stack are the true (non relative) beginning
- * stack sizes of these blocks.
- */
- int max = 0;
- Label stack = labels;
- while (stack != null) {
- // pops a block from the stack
- Label l = stack;
- stack = stack.next;
- // computes the true (non relative) max stack size of this block
- int start = l.inputStackTop;
- int blockMax = start + l.outputStackMax;
- // updates the global max stack size
- if (blockMax > max) {
- max = blockMax;
- }
- // analyzes the successors of the block
- Edge b = l.successors;
- if ((l.status & Label.JSR) != 0) {
- // ignores the first edge of JSR blocks (virtual successor)
- b = b.next;
- }
- while (b != null) {
- l = b.successor;
- // if this successor has not already been pushed...
- if ((l.status & Label.PUSHED) == 0) {
- // computes its true beginning stack size...
- l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start
- + b.info;
- // ...and pushes it onto the stack
- l.status |= Label.PUSHED;
- l.next = stack;
- stack = l;
- }
- b = b.next;
- }
- }
- this.maxStack = Math.max(maxStack, max);
- } else {
- this.maxStack = maxStack;
- this.maxLocals = maxLocals;
- }
+ // Data flow algorithm: put the first basic block in a list of blocks to process (i.e. blocks
+ // whose input stack size has changed) and, while there are blocks to process, remove one
+ // from the list, update the input stack size of its successor blocks in the control flow
+ // graph, and add these blocks to the list of blocks to process (if not already done).
+ Label listOfBlocksToProcess = firstBasicBlock;
+ listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST;
+ int maxStackSize = maxStack;
+ while (listOfBlocksToProcess != Label.EMPTY_LIST) {
+ // Remove a basic block from the list of blocks to process. Note that we don't reset
+ // basicBlock.nextListElement to null on purpose, to make sure we don't reprocess already
+ // processed basic blocks.
+ Label basicBlock = listOfBlocksToProcess;
+ listOfBlocksToProcess = listOfBlocksToProcess.nextListElement;
+ // Compute the (absolute) input stack size and maximum stack size of this block.
+ int inputStackTop = basicBlock.inputStackSize;
+ int maxBlockStackSize = inputStackTop + basicBlock.outputStackMax;
+ // Update the absolute maximum stack size of the method.
+ if (maxBlockStackSize > maxStackSize) {
+ maxStackSize = maxBlockStackSize;
+ }
+ // Update the input stack size of the successor blocks of basicBlock in the control flow
+ // graph, and add these blocks to the list of blocks to process, if not already done.
+ Edge outgoingEdge = basicBlock.outgoingEdges;
+ if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) {
+ // Ignore the first outgoing edge of the basic blocks ending with a jsr: these are virtual
+ // edges which lead to the instruction just after the jsr, and do not correspond to a
+ // possible execution path (see {@link #visitJumpInsn} and
+ // {@link Label#FLAG_SUBROUTINE_CALLER}).
+ outgoingEdge = outgoingEdge.nextEdge;
+ }
+ while (outgoingEdge != null) {
+ Label successorBlock = outgoingEdge.successor;
+ if (successorBlock.nextListElement == null) {
+ successorBlock.inputStackSize =
+ (short) (outgoingEdge.info == Edge.EXCEPTION ? 1 : inputStackTop + outgoingEdge.info);
+ successorBlock.nextListElement = listOfBlocksToProcess;
+ listOfBlocksToProcess = successorBlock;
+ }
+ outgoingEdge = outgoingEdge.nextEdge;
+ }
}
-
- @Override
- public void visitEnd() {
- }
-
- // ------------------------------------------------------------------------
- // Utility methods: control flow analysis algorithm
- // ------------------------------------------------------------------------
-
- /**
- * Adds a successor to the {@link #currentBlock currentBlock} block.
- *
- * @param info
- * information about the control flow edge to be added.
- * @param successor
- * the successor block to be added to the current block.
- */
- private void addSuccessor(final int info, final Label successor) {
- // creates and initializes an Edge object...
- Edge b = new Edge();
- b.info = info;
- b.successor = successor;
- // ...and adds it to the successor list of the currentBlock block
- b.next = currentBlock.successors;
- currentBlock.successors = b;
- }
-
- /**
- * Ends the current basic block. This method must be used in the case where
- * the current basic block does not have any successor.
- */
- private void noSuccessor() {
- if (compute == FRAMES) {
- Label l = new Label();
- l.frame = new Frame();
- l.frame.owner = l;
- l.resolve(this, code.length, code.data);
- previousBlock.successor = l;
- previousBlock = l;
- } else {
- currentBlock.outputStackMax = maxStackSize;
- }
- currentBlock = null;
- }
-
- // ------------------------------------------------------------------------
- // Utility methods: stack map frames
- // ------------------------------------------------------------------------
-
- /**
- * Visits a frame that has been computed from scratch.
- *
- * @param f
- * the frame that must be visited.
- */
- private void visitFrame(final Frame f) {
- int i, t;
- int nTop = 0;
- int nLocal = 0;
- int nStack = 0;
- int[] locals = f.inputLocals;
- int[] stacks = f.inputStack;
- // computes the number of locals (ignores TOP types that are just after
- // a LONG or a DOUBLE, and all trailing TOP types)
- for (i = 0; i < locals.length; ++i) {
- t = locals[i];
- if (t == Frame.TOP) {
- ++nTop;
- } else {
- nLocal += nTop + 1;
- nTop = 0;
- }
- if (t == Frame.LONG || t == Frame.DOUBLE) {
- ++i;
- }
- }
- // computes the stack size (ignores TOP types that are just after
- // a LONG or a DOUBLE)
- for (i = 0; i < stacks.length; ++i) {
- t = stacks[i];
- ++nStack;
- if (t == Frame.LONG || t == Frame.DOUBLE) {
- ++i;
- }
- }
- // visits the frame and its content
- int frameIndex = startFrame(f.owner.position, nLocal, nStack);
- for (i = 0; nLocal > 0; ++i, --nLocal) {
- t = locals[i];
- frame[frameIndex++] = t;
- if (t == Frame.LONG || t == Frame.DOUBLE) {
- ++i;
- }
- }
- for (i = 0; i < stacks.length; ++i) {
- t = stacks[i];
- frame[frameIndex++] = t;
- if (t == Frame.LONG || t == Frame.DOUBLE) {
- ++i;
- }
- }
- endFrame();
- }
-
- /**
- * Visit the implicit first frame of this method.
- */
- private void visitImplicitFirstFrame() {
- // There can be at most descriptor.length() + 1 locals
- int frameIndex = startFrame(0, descriptor.length() + 1, 0);
- if ((access & Opcodes.ACC_STATIC) == 0) {
- if ((access & ACC_CONSTRUCTOR) == 0) {
- frame[frameIndex++] = Frame.OBJECT | cw.addType(cw.thisName);
- } else {
- frame[frameIndex++] = 6; // Opcodes.UNINITIALIZED_THIS;
- }
- }
- int i = 1;
- loop: while (true) {
- int j = i;
- switch (descriptor.charAt(i++)) {
- case 'Z':
- case 'C':
- case 'B':
- case 'S':
- case 'I':
- frame[frameIndex++] = 1; // Opcodes.INTEGER;
- break;
- case 'F':
- frame[frameIndex++] = 2; // Opcodes.FLOAT;
- break;
- case 'J':
- frame[frameIndex++] = 4; // Opcodes.LONG;
- break;
- case 'D':
- frame[frameIndex++] = 3; // Opcodes.DOUBLE;
- break;
- case '[':
- while (descriptor.charAt(i) == '[') {
- ++i;
- }
- if (descriptor.charAt(i) == 'L') {
- ++i;
- while (descriptor.charAt(i) != ';') {
- ++i;
- }
- }
- frame[frameIndex++] = Frame.OBJECT
- | cw.addType(descriptor.substring(j, ++i));
- break;
- case 'L':
- while (descriptor.charAt(i) != ';') {
- ++i;
- }
- frame[frameIndex++] = Frame.OBJECT
- | cw.addType(descriptor.substring(j + 1, i++));
- break;
- default:
- break loop;
- }
- }
- frame[1] = frameIndex - 3;
- endFrame();
- }
-
- /**
- * Starts the visit of a stack map frame.
- *
- * @param offset
- * the offset of the instruction to which the frame corresponds.
- * @param nLocal
- * the number of local variables in the frame.
- * @param nStack
- * the number of stack elements in the frame.
- * @return the index of the next element to be written in this frame.
- */
- private int startFrame(final int offset, final int nLocal, final int nStack) {
- int n = 3 + nLocal + nStack;
- if (frame == null || frame.length < n) {
- frame = new int[n];
- }
- frame[0] = offset;
- frame[1] = nLocal;
- frame[2] = nStack;
- return 3;
- }
-
- /**
- * Checks if the visit of the current frame {@link #frame} is finished, and
- * if yes, write it in the StackMapTable attribute.
- */
- private void endFrame() {
- if (previousFrame != null) { // do not write the first frame
- if (stackMap == null) {
- stackMap = new ByteVector();
- }
- writeFrame();
- ++frameCount;
- }
- previousFrame = frame;
- frame = null;
- }
-
- /**
- * Compress and writes the current frame {@link #frame} in the StackMapTable
- * attribute.
- */
- private void writeFrame() {
- int clocalsSize = frame[1];
- int cstackSize = frame[2];
- if ((cw.version & 0xFFFF) < Opcodes.V1_6) {
- stackMap.putShort(frame[0]).putShort(clocalsSize);
- writeFrameTypes(3, 3 + clocalsSize);
- stackMap.putShort(cstackSize);
- writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
- return;
- }
- int localsSize = previousFrame[1];
- int type = FULL_FRAME;
- int k = 0;
- int delta;
- if (frameCount == 0) {
- delta = frame[0];
- } else {
- delta = frame[0] - previousFrame[0] - 1;
- }
- if (cstackSize == 0) {
- k = clocalsSize - localsSize;
- switch (k) {
- case -3:
- case -2:
- case -1:
- type = CHOP_FRAME;
- localsSize = clocalsSize;
- break;
- case 0:
- type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED;
- break;
- case 1:
- case 2:
- case 3:
- type = APPEND_FRAME;
- break;
- }
- } else if (clocalsSize == localsSize && cstackSize == 1) {
- type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME
- : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
- }
- if (type != FULL_FRAME) {
- // verify if locals are the same
- int l = 3;
- for (int j = 0; j < localsSize; j++) {
- if (frame[l] != previousFrame[l]) {
- type = FULL_FRAME;
- break;
- }
- l++;
- }
- }
- switch (type) {
- case SAME_FRAME:
- stackMap.putByte(delta);
- break;
- case SAME_LOCALS_1_STACK_ITEM_FRAME:
- stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
- writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
- break;
- case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
- stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort(
- delta);
- writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
- break;
- case SAME_FRAME_EXTENDED:
- stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
- break;
- case CHOP_FRAME:
- stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
- break;
- case APPEND_FRAME:
- stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
- writeFrameTypes(3 + localsSize, 3 + clocalsSize);
- break;
- // case FULL_FRAME:
+ this.maxStack = maxStackSize;
+ }
+
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods: control flow analysis algorithm
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a successor to {@link #currentBasicBlock} in the control flow graph.
+ *
+ * @param info information about the control flow edge to be added.
+ * @param successor the successor block to be added to the current basic block.
+ */
+ private void addSuccessorToCurrentBasicBlock(final int info, final Label successor) {
+ currentBasicBlock.outgoingEdges = new Edge(info, successor, currentBasicBlock.outgoingEdges);
+ }
+
+ /**
+ * Ends the current basic block. This method must be used in the case where the current basic
+ * block does not have any successor.
+ *
+ * WARNING: this method must be called after the currently visited instruction has been put in
+ * {@link #code} (if frames are computed, this method inserts a new Label to start a new basic
+ * block after the current instruction).
+ */
+ private void endCurrentBasicBlockWithNoSuccessor() {
+ if (compute == COMPUTE_ALL_FRAMES) {
+ Label nextBasicBlock = new Label();
+ nextBasicBlock.frame = new Frame(nextBasicBlock);
+ nextBasicBlock.resolve(code.data, code.length);
+ lastBasicBlock.nextBasicBlock = nextBasicBlock;
+ lastBasicBlock = nextBasicBlock;
+ currentBasicBlock = null;
+ } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) {
+ currentBasicBlock.outputStackMax = (short) maxRelativeStackSize;
+ currentBasicBlock = null;
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods: stack map frames
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Starts the visit of a new stack map frame, stored in {@link #currentFrame}.
+ *
+ * @param offset the bytecode offset of the instruction to which the frame corresponds.
+ * @param numLocal the number of local variables in the frame.
+ * @param numStack the number of stack elements in the frame.
+ * @return the index of the next element to be written in this frame.
+ */
+ int visitFrameStart(final int offset, final int numLocal, final int numStack) {
+ int frameLength = 3 + numLocal + numStack;
+ if (currentFrame == null || currentFrame.length < frameLength) {
+ currentFrame = new int[frameLength];
+ }
+ currentFrame[0] = offset;
+ currentFrame[1] = numLocal;
+ currentFrame[2] = numStack;
+ return 3;
+ }
+
+ /**
+ * Sets an abstract type in {@link #currentFrame}.
+ *
+ * @param frameIndex the index of the element to be set in {@link #currentFrame}.
+ * @param abstractType an abstract type.
+ */
+ void visitAbstractType(final int frameIndex, final int abstractType) {
+ currentFrame[frameIndex] = abstractType;
+ }
+
+ /**
+ * Ends the visit of {@link #currentFrame} by writing it in the StackMapTable entries and by
+ * updating the StackMapTable number_of_entries (except if the current frame is the first one,
+ * which is implicit in StackMapTable). Then resets {@link #currentFrame} to {@literal null}.
+ */
+ void visitFrameEnd() {
+ if (previousFrame != null) {
+ if (stackMapTableEntries == null) {
+ stackMapTableEntries = new ByteVector();
+ }
+ putFrame();
+ ++stackMapTableNumberOfEntries;
+ }
+ previousFrame = currentFrame;
+ currentFrame = null;
+ }
+
+ /** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */
+ private void putFrame() {
+ final int numLocal = currentFrame[1];
+ final int numStack = currentFrame[2];
+ if (symbolTable.getMajorVersion() < Opcodes.V1_6) {
+ // Generate a StackMap attribute entry, which are always uncompressed.
+ stackMapTableEntries.putShort(currentFrame[0]).putShort(numLocal);
+ putAbstractTypes(3, 3 + numLocal);
+ stackMapTableEntries.putShort(numStack);
+ putAbstractTypes(3 + numLocal, 3 + numLocal + numStack);
+ return;
+ }
+ final int offsetDelta =
+ stackMapTableNumberOfEntries == 0
+ ? currentFrame[0]
+ : currentFrame[0] - previousFrame[0] - 1;
+ final int previousNumlocal = previousFrame[1];
+ final int numLocalDelta = numLocal - previousNumlocal;
+ int type = Frame.FULL_FRAME;
+ if (numStack == 0) {
+ switch (numLocalDelta) {
+ case -3:
+ case -2:
+ case -1:
+ type = Frame.CHOP_FRAME;
+ break;
+ case 0:
+ type = offsetDelta < 64 ? Frame.SAME_FRAME : Frame.SAME_FRAME_EXTENDED;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ type = Frame.APPEND_FRAME;
+ break;
default:
- stackMap.putByte(FULL_FRAME).putShort(delta).putShort(clocalsSize);
- writeFrameTypes(3, 3 + clocalsSize);
- stackMap.putShort(cstackSize);
- writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
- }
+ // Keep the FULL_FRAME type.
+ break;
+ }
+ } else if (numLocalDelta == 0 && numStack == 1) {
+ type =
+ offsetDelta < 63
+ ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME
+ : Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
}
-
- /**
- * Writes some types of the current frame {@link #frame} into the
- * StackMapTableAttribute. This method converts types from the format used
- * in {@link Label} to the format used in StackMapTable attributes. In
- * particular, it converts type table indexes to constant pool indexes.
- *
- * @param start
- * index of the first type in {@link #frame} to write.
- * @param end
- * index of last type in {@link #frame} to write (exclusive).
- */
- private void writeFrameTypes(final int start, final int end) {
- for (int i = start; i < end; ++i) {
- int t = frame[i];
- int d = t & Frame.DIM;
- if (d == 0) {
- int v = t & Frame.BASE_VALUE;
- switch (t & Frame.BASE_KIND) {
- case Frame.OBJECT:
- stackMap.putByte(7).putShort(
- cw.newClass(cw.typeTable[v].strVal1));
- break;
- case Frame.UNINITIALIZED:
- stackMap.putByte(8).putShort(cw.typeTable[v].intVal);
- break;
- default:
- stackMap.putByte(v);
- }
- } else {
- StringBuffer buf = new StringBuffer();
- d >>= 28;
- while (d-- > 0) {
- buf.append('[');
- }
- if ((t & Frame.BASE_KIND) == Frame.OBJECT) {
- buf.append('L');
- buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1);
- buf.append(';');
- } else {
- switch (t & 0xF) {
- case 1:
- buf.append('I');
- break;
- case 2:
- buf.append('F');
- break;
- case 3:
- buf.append('D');
- break;
- case 9:
- buf.append('Z');
- break;
- case 10:
- buf.append('B');
- break;
- case 11:
- buf.append('C');
- break;
- case 12:
- buf.append('S');
- break;
- default:
- buf.append('J');
- }
- }
- stackMap.putByte(7).putShort(cw.newClass(buf.toString()));
- }
- }
+ if (type != Frame.FULL_FRAME) {
+ // Verify if locals are the same as in the previous frame.
+ int frameIndex = 3;
+ for (int i = 0; i < previousNumlocal && i < numLocal; i++) {
+ if (currentFrame[frameIndex] != previousFrame[frameIndex]) {
+ type = Frame.FULL_FRAME;
+ break;
+ }
+ frameIndex++;
+ }
}
-
- private void writeFrameType(final Object type) {
- if (type instanceof String) {
- stackMap.putByte(7).putShort(cw.newClass((String) type));
- } else if (type instanceof Integer) {
- stackMap.putByte(((Integer) type).intValue());
- } else {
- stackMap.putByte(8).putShort(((Label) type).position);
- }
+ switch (type) {
+ case Frame.SAME_FRAME:
+ stackMapTableEntries.putByte(offsetDelta);
+ break;
+ case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME:
+ stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta);
+ putAbstractTypes(3 + numLocal, 4 + numLocal);
+ break;
+ case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
+ stackMapTableEntries
+ .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
+ .putShort(offsetDelta);
+ putAbstractTypes(3 + numLocal, 4 + numLocal);
+ break;
+ case Frame.SAME_FRAME_EXTENDED:
+ stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta);
+ break;
+ case Frame.CHOP_FRAME:
+ stackMapTableEntries
+ .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta)
+ .putShort(offsetDelta);
+ break;
+ case Frame.APPEND_FRAME:
+ stackMapTableEntries
+ .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta)
+ .putShort(offsetDelta);
+ putAbstractTypes(3 + previousNumlocal, 3 + numLocal);
+ break;
+ case Frame.FULL_FRAME:
+ default:
+ stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal);
+ putAbstractTypes(3, 3 + numLocal);
+ stackMapTableEntries.putShort(numStack);
+ putAbstractTypes(3 + numLocal, 3 + numLocal + numStack);
+ break;
}
-
- // ------------------------------------------------------------------------
- // Utility methods: dump bytecode array
- // ------------------------------------------------------------------------
-
- /**
- * Returns the size of the bytecode of this method.
- *
- * @return the size of the bytecode of this method.
- */
- final int getSize() {
- if (classReaderOffset != 0) {
- return 6 + classReaderLength;
- }
- if (resize) {
- // replaces the temporary jump opcodes introduced by Label.resolve.
- if (ClassReader.RESIZE) {
- resizeInstructions();
- } else {
- throw new RuntimeException("Method code too large!");
- }
- }
- int size = 8;
- if (code.length > 0) {
- if (code.length > 65536) {
- throw new RuntimeException("Method code too large!");
- }
- cw.newUTF8("Code");
- size += 18 + code.length + 8 * handlerCount;
- if (localVar != null) {
- cw.newUTF8("LocalVariableTable");
- size += 8 + localVar.length;
- }
- if (localVarType != null) {
- cw.newUTF8("LocalVariableTypeTable");
- size += 8 + localVarType.length;
- }
- if (lineNumber != null) {
- cw.newUTF8("LineNumberTable");
- size += 8 + lineNumber.length;
- }
- if (stackMap != null) {
- boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
- cw.newUTF8(zip ? "StackMapTable" : "StackMap");
- size += 8 + stackMap.length;
- }
- if (cattrs != null) {
- size += cattrs.getSize(cw, code.data, code.length, maxStack,
- maxLocals);
- }
- }
- if (exceptionCount > 0) {
- cw.newUTF8("Exceptions");
- size += 8 + 2 * exceptionCount;
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- cw.newUTF8("Synthetic");
- size += 6;
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- cw.newUTF8("Deprecated");
- size += 6;
- }
- if (ClassReader.SIGNATURES && signature != null) {
- cw.newUTF8("Signature");
- cw.newUTF8(signature);
- size += 8;
- }
- if (ClassReader.ANNOTATIONS && annd != null) {
- cw.newUTF8("AnnotationDefault");
- size += 6 + annd.length;
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- cw.newUTF8("RuntimeVisibleAnnotations");
- size += 8 + anns.getSize();
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- cw.newUTF8("RuntimeInvisibleAnnotations");
- size += 8 + ianns.getSize();
- }
- if (ClassReader.ANNOTATIONS && panns != null) {
- cw.newUTF8("RuntimeVisibleParameterAnnotations");
- size += 7 + 2 * (panns.length - synthetics);
- for (int i = panns.length - 1; i >= synthetics; --i) {
- size += panns[i] == null ? 0 : panns[i].getSize();
- }
- }
- if (ClassReader.ANNOTATIONS && ipanns != null) {
- cw.newUTF8("RuntimeInvisibleParameterAnnotations");
- size += 7 + 2 * (ipanns.length - synthetics);
- for (int i = ipanns.length - 1; i >= synthetics; --i) {
- size += ipanns[i] == null ? 0 : ipanns[i].getSize();
- }
- }
- if (attrs != null) {
- size += attrs.getSize(cw, null, 0, -1, -1);
- }
- return size;
- }
-
- /**
- * Puts the bytecode of this method in the given byte vector.
- *
- * @param out
- * the byte vector into which the bytecode of this method must be
- * copied.
- */
- final void put(final ByteVector out) {
- final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC;
- int mask = ACC_CONSTRUCTOR | Opcodes.ACC_DEPRECATED
- | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
- | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR);
- out.putShort(access & ~mask).putShort(name).putShort(desc);
- if (classReaderOffset != 0) {
- out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength);
- return;
- }
- int attributeCount = 0;
- if (code.length > 0) {
- ++attributeCount;
- }
- if (exceptionCount > 0) {
- ++attributeCount;
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- ++attributeCount;
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- ++attributeCount;
- }
- if (ClassReader.SIGNATURES && signature != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && annd != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && panns != null) {
- ++attributeCount;
- }
- if (ClassReader.ANNOTATIONS && ipanns != null) {
- ++attributeCount;
- }
- if (attrs != null) {
- attributeCount += attrs.getCount();
- }
- out.putShort(attributeCount);
- if (code.length > 0) {
- int size = 12 + code.length + 8 * handlerCount;
- if (localVar != null) {
- size += 8 + localVar.length;
- }
- if (localVarType != null) {
- size += 8 + localVarType.length;
- }
- if (lineNumber != null) {
- size += 8 + lineNumber.length;
- }
- if (stackMap != null) {
- size += 8 + stackMap.length;
- }
- if (cattrs != null) {
- size += cattrs.getSize(cw, code.data, code.length, maxStack,
- maxLocals);
- }
- out.putShort(cw.newUTF8("Code")).putInt(size);
- out.putShort(maxStack).putShort(maxLocals);
- out.putInt(code.length).putByteArray(code.data, 0, code.length);
- out.putShort(handlerCount);
- if (handlerCount > 0) {
- Handler h = firstHandler;
- while (h != null) {
- out.putShort(h.start.position).putShort(h.end.position)
- .putShort(h.handler.position).putShort(h.type);
- h = h.next;
- }
- }
- attributeCount = 0;
- if (localVar != null) {
- ++attributeCount;
- }
- if (localVarType != null) {
- ++attributeCount;
- }
- if (lineNumber != null) {
- ++attributeCount;
- }
- if (stackMap != null) {
- ++attributeCount;
- }
- if (cattrs != null) {
- attributeCount += cattrs.getCount();
- }
- out.putShort(attributeCount);
- if (localVar != null) {
- out.putShort(cw.newUTF8("LocalVariableTable"));
- out.putInt(localVar.length + 2).putShort(localVarCount);
- out.putByteArray(localVar.data, 0, localVar.length);
- }
- if (localVarType != null) {
- out.putShort(cw.newUTF8("LocalVariableTypeTable"));
- out.putInt(localVarType.length + 2).putShort(localVarTypeCount);
- out.putByteArray(localVarType.data, 0, localVarType.length);
- }
- if (lineNumber != null) {
- out.putShort(cw.newUTF8("LineNumberTable"));
- out.putInt(lineNumber.length + 2).putShort(lineNumberCount);
- out.putByteArray(lineNumber.data, 0, lineNumber.length);
- }
- if (stackMap != null) {
- boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
- out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap"));
- out.putInt(stackMap.length + 2).putShort(frameCount);
- out.putByteArray(stackMap.data, 0, stackMap.length);
- }
- if (cattrs != null) {
- cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out);
- }
- }
- if (exceptionCount > 0) {
- out.putShort(cw.newUTF8("Exceptions")).putInt(
- 2 * exceptionCount + 2);
- out.putShort(exceptionCount);
- for (int i = 0; i < exceptionCount; ++i) {
- out.putShort(exceptions[i]);
- }
- }
- if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
- if ((cw.version & 0xFFFF) < Opcodes.V1_5
- || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
- out.putShort(cw.newUTF8("Synthetic")).putInt(0);
- }
- }
- if ((access & Opcodes.ACC_DEPRECATED) != 0) {
- out.putShort(cw.newUTF8("Deprecated")).putInt(0);
- }
- if (ClassReader.SIGNATURES && signature != null) {
- out.putShort(cw.newUTF8("Signature")).putInt(2)
- .putShort(cw.newUTF8(signature));
- }
- if (ClassReader.ANNOTATIONS && annd != null) {
- out.putShort(cw.newUTF8("AnnotationDefault"));
- out.putInt(annd.length);
- out.putByteArray(annd.data, 0, annd.length);
- }
- if (ClassReader.ANNOTATIONS && anns != null) {
- out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
- anns.put(out);
- }
- if (ClassReader.ANNOTATIONS && ianns != null) {
- out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
- ianns.put(out);
- }
- if (ClassReader.ANNOTATIONS && panns != null) {
- out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations"));
- AnnotationWriter.put(panns, synthetics, out);
- }
- if (ClassReader.ANNOTATIONS && ipanns != null) {
- out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations"));
- AnnotationWriter.put(ipanns, synthetics, out);
- }
- if (attrs != null) {
- attrs.put(cw, null, 0, -1, -1, out);
- }
+ }
+
+ /**
+ * Puts some abstract types of {@link #currentFrame} in {@link #stackMapTableEntries} , using the
+ * JVMS verification_type_info format used in StackMapTable attributes.
+ *
+ * @param start index of the first type in {@link #currentFrame} to write.
+ * @param end index of last type in {@link #currentFrame} to write (exclusive).
+ */
+ private void putAbstractTypes(final int start, final int end) {
+ for (int i = start; i < end; ++i) {
+ Frame.putAbstractType(symbolTable, currentFrame[i], stackMapTableEntries);
}
-
- // ------------------------------------------------------------------------
- // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W)
- // ------------------------------------------------------------------------
-
- /**
- * Resizes and replaces the temporary instructions inserted by
- * {@link Label#resolve} for wide forward jumps, while keeping jump offsets
- * and instruction addresses consistent. This may require to resize other
- * existing instructions, or even to introduce new instructions: for
- * example, increasing the size of an instruction by 2 at the middle of a
- * method can increases the offset of an IFEQ instruction from 32766 to
- * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W
- * 32765. This, in turn, may require to increase the size of another jump
- * instruction, and so on... All these operations are handled automatically
- * by this method.
- *
- * This method must be called after all the method that is being built
- * has been visited. In particular, the {@link Label Label} objects used
- * to construct the method are no longer valid after this method has been
- * called.
- */
- private void resizeInstructions() {
- byte[] b = code.data; // bytecode of the method
- int u, v, label; // indexes in b
- int i, j; // loop indexes
- /*
- * 1st step: As explained above, resizing an instruction may require to
- * resize another one, which may require to resize yet another one, and
- * so on. The first step of the algorithm consists in finding all the
- * instructions that need to be resized, without modifying the code.
- * This is done by the following "fix point" algorithm:
- *
- * Parse the code to find the jump instructions whose offset will need
- * more than 2 bytes to be stored (the future offset is computed from
- * the current offset and from the number of bytes that will be inserted
- * or removed between the source and target instructions). For each such
- * instruction, adds an entry in (a copy of) the indexes and sizes
- * arrays (if this has not already been done in a previous iteration!).
- *
- * If at least one entry has been added during the previous step, go
- * back to the beginning, otherwise stop.
- *
- * In fact the real algorithm is complicated by the fact that the size
- * of TABLESWITCH and LOOKUPSWITCH instructions depends on their
- * position in the bytecode (because of padding). In order to ensure the
- * convergence of the algorithm, the number of bytes to be added or
- * removed from these instructions is over estimated during the previous
- * loop, and computed exactly only after the loop is finished (this
- * requires another pass to parse the bytecode of the method).
- */
- int[] allIndexes = new int[0]; // copy of indexes
- int[] allSizes = new int[0]; // copy of sizes
- boolean[] resize; // instructions to be resized
- int newOffset; // future offset of a jump instruction
-
- resize = new boolean[code.length];
-
- // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done
- int state = 3;
- do {
- if (state == 3) {
- state = 2;
- }
- u = 0;
- while (u < b.length) {
- int opcode = b[u] & 0xFF; // opcode of current instruction
- int insert = 0; // bytes to be added after this instruction
-
- switch (ClassWriter.TYPE[opcode]) {
- case ClassWriter.NOARG_INSN:
- case ClassWriter.IMPLVAR_INSN:
- u += 1;
- break;
- case ClassWriter.LABEL_INSN:
- if (opcode > 201) {
- // converts temporary opcodes 202 to 217, 218 and
- // 219 to IFEQ ... JSR (inclusive), IFNULL and
- // IFNONNULL
- opcode = opcode < 218 ? opcode - 49 : opcode - 20;
- label = u + readUnsignedShort(b, u + 1);
- } else {
- label = u + readShort(b, u + 1);
- }
- newOffset = getNewOffset(allIndexes, allSizes, u, label);
- if (newOffset < Short.MIN_VALUE
- || newOffset > Short.MAX_VALUE) {
- if (!resize[u]) {
- if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
- // two additional bytes will be required to
- // replace this GOTO or JSR instruction with
- // a GOTO_W or a JSR_W
- insert = 2;
- } else {
- // five additional bytes will be required to
- // replace this IFxxx instruction with
- // IFNOTxxx GOTO_W , where IFNOTxxx
- // is the "opposite" opcode of IFxxx (i.e.,
- // IFNE for IFEQ) and where designates
- // the instruction just after the GOTO_W.
- insert = 5;
- }
- resize[u] = true;
- }
- }
- u += 3;
- break;
- case ClassWriter.LABELW_INSN:
- u += 5;
- break;
- case ClassWriter.TABL_INSN:
- if (state == 1) {
- // true number of bytes to be added (or removed)
- // from this instruction = (future number of padding
- // bytes - current number of padding byte) -
- // previously over estimated variation =
- // = ((3 - newOffset%4) - (3 - u%4)) - u%4
- // = (-newOffset%4 + u%4) - u%4
- // = -(newOffset & 3)
- newOffset = getNewOffset(allIndexes, allSizes, 0, u);
- insert = -(newOffset & 3);
- } else if (!resize[u]) {
- // over estimation of the number of bytes to be
- // added to this instruction = 3 - current number
- // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3
- insert = u & 3;
- resize[u] = true;
- }
- // skips instruction
- u = u + 4 - (u & 3);
- u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12;
- break;
- case ClassWriter.LOOK_INSN:
- if (state == 1) {
- // like TABL_INSN
- newOffset = getNewOffset(allIndexes, allSizes, 0, u);
- insert = -(newOffset & 3);
- } else if (!resize[u]) {
- // like TABL_INSN
- insert = u & 3;
- resize[u] = true;
- }
- // skips instruction
- u = u + 4 - (u & 3);
- u += 8 * readInt(b, u + 4) + 8;
- break;
- case ClassWriter.WIDE_INSN:
- opcode = b[u + 1] & 0xFF;
- if (opcode == Opcodes.IINC) {
- u += 6;
- } else {
- u += 4;
- }
- break;
- case ClassWriter.VAR_INSN:
- case ClassWriter.SBYTE_INSN:
- case ClassWriter.LDC_INSN:
- u += 2;
- break;
- case ClassWriter.SHORT_INSN:
- case ClassWriter.LDCW_INSN:
- case ClassWriter.FIELDORMETH_INSN:
- case ClassWriter.TYPE_INSN:
- case ClassWriter.IINC_INSN:
- u += 3;
- break;
- case ClassWriter.ITFMETH_INSN:
- case ClassWriter.INDYMETH_INSN:
- u += 5;
- break;
- // case ClassWriter.MANA_INSN:
- default:
- u += 4;
- break;
- }
- if (insert != 0) {
- // adds a new (u, insert) entry in the allIndexes and
- // allSizes arrays
- int[] newIndexes = new int[allIndexes.length + 1];
- int[] newSizes = new int[allSizes.length + 1];
- System.arraycopy(allIndexes, 0, newIndexes, 0,
- allIndexes.length);
- System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length);
- newIndexes[allIndexes.length] = u;
- newSizes[allSizes.length] = insert;
- allIndexes = newIndexes;
- allSizes = newSizes;
- if (insert > 0) {
- state = 3;
- }
- }
- }
- if (state < 3) {
- --state;
- }
- } while (state != 0);
-
- // 2nd step:
- // copies the bytecode of the method into a new bytevector, updates the
- // offsets, and inserts (or removes) bytes as requested.
-
- ByteVector newCode = new ByteVector(code.length);
-
- u = 0;
- while (u < code.length) {
- int opcode = b[u] & 0xFF;
- switch (ClassWriter.TYPE[opcode]) {
- case ClassWriter.NOARG_INSN:
- case ClassWriter.IMPLVAR_INSN:
- newCode.putByte(opcode);
- u += 1;
- break;
- case ClassWriter.LABEL_INSN:
- if (opcode > 201) {
- // changes temporary opcodes 202 to 217 (inclusive), 218
- // and 219 to IFEQ ... JSR (inclusive), IFNULL and
- // IFNONNULL
- opcode = opcode < 218 ? opcode - 49 : opcode - 20;
- label = u + readUnsignedShort(b, u + 1);
- } else {
- label = u + readShort(b, u + 1);
- }
- newOffset = getNewOffset(allIndexes, allSizes, u, label);
- if (resize[u]) {
- // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx
- // with IFNOTxxx GOTO_W , where IFNOTxxx is
- // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ)
- // and where designates the instruction just after
- // the GOTO_W.
- if (opcode == Opcodes.GOTO) {
- newCode.putByte(200); // GOTO_W
- } else if (opcode == Opcodes.JSR) {
- newCode.putByte(201); // JSR_W
- } else {
- newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1
- : opcode ^ 1);
- newCode.putShort(8); // jump offset
- newCode.putByte(200); // GOTO_W
- // newOffset now computed from start of GOTO_W
- newOffset -= 3;
- }
- newCode.putInt(newOffset);
- } else {
- newCode.putByte(opcode);
- newCode.putShort(newOffset);
- }
- u += 3;
- break;
- case ClassWriter.LABELW_INSN:
- label = u + readInt(b, u + 1);
- newOffset = getNewOffset(allIndexes, allSizes, u, label);
- newCode.putByte(opcode);
- newCode.putInt(newOffset);
- u += 5;
- break;
- case ClassWriter.TABL_INSN:
- // skips 0 to 3 padding bytes
- v = u;
- u = u + 4 - (v & 3);
- // reads and copies instruction
- newCode.putByte(Opcodes.TABLESWITCH);
- newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4);
- label = v + readInt(b, u);
- u += 4;
- newOffset = getNewOffset(allIndexes, allSizes, v, label);
- newCode.putInt(newOffset);
- j = readInt(b, u);
- u += 4;
- newCode.putInt(j);
- j = readInt(b, u) - j + 1;
- u += 4;
- newCode.putInt(readInt(b, u - 4));
- for (; j > 0; --j) {
- label = v + readInt(b, u);
- u += 4;
- newOffset = getNewOffset(allIndexes, allSizes, v, label);
- newCode.putInt(newOffset);
- }
- break;
- case ClassWriter.LOOK_INSN:
- // skips 0 to 3 padding bytes
- v = u;
- u = u + 4 - (v & 3);
- // reads and copies instruction
- newCode.putByte(Opcodes.LOOKUPSWITCH);
- newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4);
- label = v + readInt(b, u);
- u += 4;
- newOffset = getNewOffset(allIndexes, allSizes, v, label);
- newCode.putInt(newOffset);
- j = readInt(b, u);
- u += 4;
- newCode.putInt(j);
- for (; j > 0; --j) {
- newCode.putInt(readInt(b, u));
- u += 4;
- label = v + readInt(b, u);
- u += 4;
- newOffset = getNewOffset(allIndexes, allSizes, v, label);
- newCode.putInt(newOffset);
- }
- break;
- case ClassWriter.WIDE_INSN:
- opcode = b[u + 1] & 0xFF;
- if (opcode == Opcodes.IINC) {
- newCode.putByteArray(b, u, 6);
- u += 6;
- } else {
- newCode.putByteArray(b, u, 4);
- u += 4;
- }
- break;
- case ClassWriter.VAR_INSN:
- case ClassWriter.SBYTE_INSN:
- case ClassWriter.LDC_INSN:
- newCode.putByteArray(b, u, 2);
- u += 2;
- break;
- case ClassWriter.SHORT_INSN:
- case ClassWriter.LDCW_INSN:
- case ClassWriter.FIELDORMETH_INSN:
- case ClassWriter.TYPE_INSN:
- case ClassWriter.IINC_INSN:
- newCode.putByteArray(b, u, 3);
- u += 3;
- break;
- case ClassWriter.ITFMETH_INSN:
- case ClassWriter.INDYMETH_INSN:
- newCode.putByteArray(b, u, 5);
- u += 5;
- break;
- // case MANA_INSN:
- default:
- newCode.putByteArray(b, u, 4);
- u += 4;
- break;
- }
- }
-
- // recomputes the stack map frames
- if (frameCount > 0) {
- if (compute == FRAMES) {
- frameCount = 0;
- stackMap = null;
- previousFrame = null;
- frame = null;
- Frame f = new Frame();
- f.owner = labels;
- Type[] args = Type.getArgumentTypes(descriptor);
- f.initInputFrame(cw, access, args, maxLocals);
- visitFrame(f);
- Label l = labels;
- while (l != null) {
- /*
- * here we need the original label position. getNewOffset
- * must therefore never have been called for this label.
- */
- u = l.position - 3;
- if ((l.status & Label.STORE) != 0 || (u >= 0 && resize[u])) {
- getNewOffset(allIndexes, allSizes, l);
- // TODO update offsets in UNINITIALIZED values
- visitFrame(l.frame);
- }
- l = l.successor;
- }
- } else {
- /*
- * Resizing an existing stack map frame table is really hard.
- * Not only the table must be parsed to update the offets, but
- * new frames may be needed for jump instructions that were
- * inserted by this method. And updating the offsets or
- * inserting frames can change the format of the following
- * frames, in case of packed frames. In practice the whole table
- * must be recomputed. For this the frames are marked as
- * potentially invalid. This will cause the whole class to be
- * reread and rewritten with the COMPUTE_FRAMES option (see the
- * ClassWriter.toByteArray method). This is not very efficient
- * but is much easier and requires much less code than any other
- * method I can think of.
- */
- cw.invalidFrames = true;
- }
- }
- // updates the exception handler block labels
- Handler h = firstHandler;
- while (h != null) {
- getNewOffset(allIndexes, allSizes, h.start);
- getNewOffset(allIndexes, allSizes, h.end);
- getNewOffset(allIndexes, allSizes, h.handler);
- h = h.next;
- }
- // updates the instructions addresses in the
- // local var and line number tables
- for (i = 0; i < 2; ++i) {
- ByteVector bv = i == 0 ? localVar : localVarType;
- if (bv != null) {
- b = bv.data;
- u = 0;
- while (u < bv.length) {
- label = readUnsignedShort(b, u);
- newOffset = getNewOffset(allIndexes, allSizes, 0, label);
- writeShort(b, u, newOffset);
- label += readUnsignedShort(b, u + 2);
- newOffset = getNewOffset(allIndexes, allSizes, 0, label)
- - newOffset;
- writeShort(b, u + 2, newOffset);
- u += 10;
- }
- }
- }
- if (lineNumber != null) {
- b = lineNumber.data;
- u = 0;
- while (u < lineNumber.length) {
- writeShort(
- b,
- u,
- getNewOffset(allIndexes, allSizes, 0,
- readUnsignedShort(b, u)));
- u += 4;
- }
- }
- // updates the labels of the other attributes
- Attribute attr = cattrs;
- while (attr != null) {
- Label[] labels = attr.getLabels();
- if (labels != null) {
- for (i = labels.length - 1; i >= 0; --i) {
- getNewOffset(allIndexes, allSizes, labels[i]);
- }
- }
- attr = attr.next;
- }
-
- // replaces old bytecodes with new ones
- code = newCode;
- }
-
- /**
- * Reads an unsigned short value in the given byte array.
- *
- * @param b
- * a byte array.
- * @param index
- * the start index of the value to be read.
- * @return the read value.
- */
- static int readUnsignedShort(final byte[] b, final int index) {
- return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
- }
-
- /**
- * Reads a signed short value in the given byte array.
- *
- * @param b
- * a byte array.
- * @param index
- * the start index of the value to be read.
- * @return the read value.
- */
- static short readShort(final byte[] b, final int index) {
- return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
- }
-
- /**
- * Reads a signed int value in the given byte array.
- *
- * @param b
- * a byte array.
- * @param index
- * the start index of the value to be read.
- * @return the read value.
- */
- static int readInt(final byte[] b, final int index) {
- return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
- | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
- }
-
- /**
- * Writes a short value in the given byte array.
- *
- * @param b
- * a byte array.
- * @param index
- * where the first byte of the short value must be written.
- * @param s
- * the value to be written in the given byte array.
- */
- static void writeShort(final byte[] b, final int index, final int s) {
- b[index] = (byte) (s >>> 8);
- b[index + 1] = (byte) s;
- }
-
- /**
- * Computes the future value of a bytecode offset.
- *
- * Note: it is possible to have several entries for the same instruction in
- * the indexes and sizes: two entries (index=a,size=b) and
- * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b').
- *
- * @param indexes
- * current positions of the instructions to be resized. Each
- * instruction must be designated by the index of its last
- * byte, plus one (or, in other words, by the index of the
- * first byte of the next instruction).
- * @param sizes
- * the number of bytes to be added to the above
- * instructions. More precisely, for each i < len,
- * sizes[i] bytes will be added at the end of the
- * instruction designated by indexes[i] or, if
- * sizes[i] is negative, the last |
- * sizes[i]| bytes of the instruction will be removed
- * (the instruction size must not become negative or
- * null).
- * @param begin
- * index of the first byte of the source instruction.
- * @param end
- * index of the first byte of the target instruction.
- * @return the future value of the given bytecode offset.
- */
- static int getNewOffset(final int[] indexes, final int[] sizes,
- final int begin, final int end) {
- int offset = end - begin;
- for (int i = 0; i < indexes.length; ++i) {
- if (begin < indexes[i] && indexes[i] <= end) {
- // forward jump
- offset += sizes[i];
- } else if (end < indexes[i] && indexes[i] <= begin) {
- // backward jump
- offset -= sizes[i];
- }
- }
- return offset;
- }
-
- /**
- * Updates the offset of the given label.
- *
- * @param indexes
- * current positions of the instructions to be resized. Each
- * instruction must be designated by the index of its last
- * byte, plus one (or, in other words, by the index of the
- * first byte of the next instruction).
- * @param sizes
- * the number of bytes to be added to the above
- * instructions. More precisely, for each i < len,
- * sizes[i] bytes will be added at the end of the
- * instruction designated by indexes[i] or, if
- * sizes[i] is negative, the last |
- * sizes[i]| bytes of the instruction will be removed
- * (the instruction size must not become negative or
- * null).
- * @param label
- * the label whose offset must be updated.
- */
- static void getNewOffset(final int[] indexes, final int[] sizes,
- final Label label) {
- if ((label.status & Label.RESIZED) == 0) {
- label.position = getNewOffset(indexes, sizes, 0, label.position);
- label.status |= Label.RESIZED;
- }
+ }
+
+ /**
+ * Puts the given public API frame element type in {@link #stackMapTableEntries} , using the JVMS
+ * verification_type_info format used in StackMapTable attributes.
+ *
+ * @param type a frame element type described using the same format as in {@link
+ * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
+ * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or
+ * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating
+ * a NEW instruction (for uninitialized types).
+ */
+ private void putFrameType(final Object type) {
+ if (type instanceof Integer) {
+ stackMapTableEntries.putByte(((Integer) type).intValue());
+ } else if (type instanceof String) {
+ stackMapTableEntries
+ .putByte(Frame.ITEM_OBJECT)
+ .putShort(symbolTable.addConstantClass((String) type).index);
+ } else {
+ stackMapTableEntries
+ .putByte(Frame.ITEM_UNINITIALIZED)
+ .putShort(((Label) type).bytecodeOffset);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns whether the attributes of this method can be copied from the attributes of the given
+ * method (assuming there is no method visitor between the given ClassReader and this
+ * MethodWriter). This method should only be called just after this MethodWriter has been created,
+ * and before any content is visited. It returns true if the attributes corresponding to the
+ * constructor arguments (at most a Signature, an Exception, a Deprecated and a Synthetic
+ * attribute) are the same as the corresponding attributes in the given method.
+ *
+ * @param source the source ClassReader from which the attributes of this method might be copied.
+ * @param hasSyntheticAttribute whether the method_info JVMS structure from which the attributes
+ * of this method might be copied contains a Synthetic attribute.
+ * @param hasDeprecatedAttribute whether the method_info JVMS structure from which the attributes
+ * of this method might be copied contains a Deprecated attribute.
+ * @param descriptorIndex the descriptor_index field of the method_info JVMS structure from which
+ * the attributes of this method might be copied.
+ * @param signatureIndex the constant pool index contained in the Signature attribute of the
+ * method_info JVMS structure from which the attributes of this method might be copied, or 0.
+ * @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info
+ * JVMS structure from which the attributes of this method might be copied, or 0.
+ * @return whether the attributes of this method can be copied from the attributes of the
+ * method_info JVMS structure in 'source.b', between 'methodInfoOffset' and 'methodInfoOffset'
+ * + 'methodInfoLength'.
+ */
+ boolean canCopyMethodAttributes(
+ final ClassReader source,
+ final boolean hasSyntheticAttribute,
+ final boolean hasDeprecatedAttribute,
+ final int descriptorIndex,
+ final int signatureIndex,
+ final int exceptionsOffset) {
+ // If the method descriptor has changed, with more locals than the max_locals field of the
+ // original Code attribute, if any, then the original method attributes can't be copied. A
+ // conservative check on the descriptor changes alone ensures this (being more precise is not
+ // worth the additional complexity, because these cases should be rare -- if a transform changes
+ // a method descriptor, most of the time it needs to change the method's code too).
+ if (source != symbolTable.getSource()
+ || descriptorIndex != this.descriptorIndex
+ || signatureIndex != this.signatureIndex
+ || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) {
+ return false;
+ }
+ boolean needSyntheticAttribute =
+ symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0;
+ if (hasSyntheticAttribute != needSyntheticAttribute) {
+ return false;
+ }
+ if (exceptionsOffset == 0) {
+ if (numberOfExceptions != 0) {
+ return false;
+ }
+ } else if (source.readUnsignedShort(exceptionsOffset) == numberOfExceptions) {
+ int currentExceptionOffset = exceptionsOffset + 2;
+ for (int i = 0; i < numberOfExceptions; ++i) {
+ if (source.readUnsignedShort(currentExceptionOffset) != exceptionIndexTable[i]) {
+ return false;
+ }
+ currentExceptionOffset += 2;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sets the source from which the attributes of this method will be copied.
+ *
+ * @param methodInfoOffset the offset in 'symbolTable.getSource()' of the method_info JVMS
+ * structure from which the attributes of this method will be copied.
+ * @param methodInfoLength the length in 'symbolTable.getSource()' of the method_info JVMS
+ * structure from which the attributes of this method will be copied.
+ */
+ void setMethodAttributesSource(final int methodInfoOffset, final int methodInfoLength) {
+ // Don't copy the attributes yet, instead store their location in the source class reader so
+ // they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes
+ // of the method_info JVMS structure.
+ this.sourceOffset = methodInfoOffset + 6;
+ this.sourceLength = methodInfoLength - 6;
+ }
+
+ /**
+ * Returns the size of the method_info JVMS structure generated by this MethodWriter. Also add the
+ * names of the attributes of this method in the constant pool.
+ *
+ * @return the size in bytes of the method_info JVMS structure.
+ */
+ int computeMethodInfoSize() {
+ // If this method_info must be copied from an existing one, the size computation is trivial.
+ if (sourceOffset != 0) {
+ // sourceLength excludes the first 6 bytes for access_flags, name_index and descriptor_index.
+ return 6 + sourceLength;
+ }
+ // 2 bytes each for access_flags, name_index, descriptor_index and attributes_count.
+ int size = 8;
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ if (code.length > 0) {
+ if (code.length > 65535) {
+ throw new MethodTooLargeException(
+ symbolTable.getClassName(), name, descriptor, code.length);
+ }
+ symbolTable.addConstantUtf8(Constants.CODE);
+ // The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack,
+ // max_locals, code_length and attributes_count, plus the bytecode and the exception table.
+ size += 16 + code.length + Handler.getExceptionTableSize(firstHandler);
+ if (stackMapTableEntries != null) {
+ boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
+ symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap");
+ // 6 header bytes and 2 bytes for number_of_entries.
+ size += 8 + stackMapTableEntries.length;
+ }
+ if (lineNumberTable != null) {
+ symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE);
+ // 6 header bytes and 2 bytes for line_number_table_length.
+ size += 8 + lineNumberTable.length;
+ }
+ if (localVariableTable != null) {
+ symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE);
+ // 6 header bytes and 2 bytes for local_variable_table_length.
+ size += 8 + localVariableTable.length;
+ }
+ if (localVariableTypeTable != null) {
+ symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE);
+ // 6 header bytes and 2 bytes for local_variable_type_table_length.
+ size += 8 + localVariableTypeTable.length;
+ }
+ if (lastCodeRuntimeVisibleTypeAnnotation != null) {
+ size +=
+ lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
+ size +=
+ lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (firstCodeAttribute != null) {
+ size +=
+ firstCodeAttribute.computeAttributesSize(
+ symbolTable, code.data, code.length, maxStack, maxLocals);
+ }
+ }
+ if (numberOfExceptions > 0) {
+ symbolTable.addConstantUtf8(Constants.EXCEPTIONS);
+ size += 8 + 2 * numberOfExceptions;
+ }
+ size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex);
+ size +=
+ AnnotationWriter.computeAnnotationsSize(
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation);
+ if (lastRuntimeVisibleParameterAnnotations != null) {
+ size +=
+ AnnotationWriter.computeParameterAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
+ lastRuntimeVisibleParameterAnnotations,
+ visibleAnnotableParameterCount == 0
+ ? lastRuntimeVisibleParameterAnnotations.length
+ : visibleAnnotableParameterCount);
+ }
+ if (lastRuntimeInvisibleParameterAnnotations != null) {
+ size +=
+ AnnotationWriter.computeParameterAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS,
+ lastRuntimeInvisibleParameterAnnotations,
+ invisibleAnnotableParameterCount == 0
+ ? lastRuntimeInvisibleParameterAnnotations.length
+ : invisibleAnnotableParameterCount);
+ }
+ if (defaultValue != null) {
+ symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT);
+ size += 6 + defaultValue.length;
+ }
+ if (parameters != null) {
+ symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS);
+ // 6 header bytes and 1 byte for parameters_count.
+ size += 7 + parameters.length;
+ }
+ if (firstAttribute != null) {
+ size += firstAttribute.computeAttributesSize(symbolTable);
+ }
+ return size;
+ }
+
+ /**
+ * Puts the content of the method_info JVMS structure generated by this MethodWriter into the
+ * given ByteVector.
+ *
+ * @param output where the method_info structure must be put.
+ */
+ void putMethodInfo(final ByteVector output) {
+ boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5;
+ int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0;
+ output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex);
+ // If this method_info must be copied from an existing one, copy it now and return early.
+ if (sourceOffset != 0) {
+ output.putByteArray(symbolTable.getSource().classFileBuffer, sourceOffset, sourceLength);
+ return;
+ }
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ int attributeCount = 0;
+ if (code.length > 0) {
+ ++attributeCount;
+ }
+ if (numberOfExceptions > 0) {
+ ++attributeCount;
+ }
+ if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) {
+ ++attributeCount;
+ }
+ if (signatureIndex != 0) {
+ ++attributeCount;
+ }
+ if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributeCount;
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeVisibleParameterAnnotations != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeInvisibleParameterAnnotations != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ ++attributeCount;
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ ++attributeCount;
+ }
+ if (defaultValue != null) {
+ ++attributeCount;
+ }
+ if (parameters != null) {
+ ++attributeCount;
+ }
+ if (firstAttribute != null) {
+ attributeCount += firstAttribute.getAttributeCount();
+ }
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ output.putShort(attributeCount);
+ if (code.length > 0) {
+ // 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and
+ // attributes_count, plus the bytecode and the exception table.
+ int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler);
+ int codeAttributeCount = 0;
+ if (stackMapTableEntries != null) {
+ // 6 header bytes and 2 bytes for number_of_entries.
+ size += 8 + stackMapTableEntries.length;
+ ++codeAttributeCount;
+ }
+ if (lineNumberTable != null) {
+ // 6 header bytes and 2 bytes for line_number_table_length.
+ size += 8 + lineNumberTable.length;
+ ++codeAttributeCount;
+ }
+ if (localVariableTable != null) {
+ // 6 header bytes and 2 bytes for local_variable_table_length.
+ size += 8 + localVariableTable.length;
+ ++codeAttributeCount;
+ }
+ if (localVariableTypeTable != null) {
+ // 6 header bytes and 2 bytes for local_variable_type_table_length.
+ size += 8 + localVariableTypeTable.length;
+ ++codeAttributeCount;
+ }
+ if (lastCodeRuntimeVisibleTypeAnnotation != null) {
+ size +=
+ lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ ++codeAttributeCount;
+ }
+ if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
+ size +=
+ lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize(
+ Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ ++codeAttributeCount;
+ }
+ if (firstCodeAttribute != null) {
+ size +=
+ firstCodeAttribute.computeAttributesSize(
+ symbolTable, code.data, code.length, maxStack, maxLocals);
+ codeAttributeCount += firstCodeAttribute.getAttributeCount();
+ }
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.CODE))
+ .putInt(size)
+ .putShort(maxStack)
+ .putShort(maxLocals)
+ .putInt(code.length)
+ .putByteArray(code.data, 0, code.length);
+ Handler.putExceptionTable(firstHandler, output);
+ output.putShort(codeAttributeCount);
+ if (stackMapTableEntries != null) {
+ boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6;
+ output
+ .putShort(
+ symbolTable.addConstantUtf8(
+ useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap"))
+ .putInt(2 + stackMapTableEntries.length)
+ .putShort(stackMapTableNumberOfEntries)
+ .putByteArray(stackMapTableEntries.data, 0, stackMapTableEntries.length);
+ }
+ if (lineNumberTable != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE))
+ .putInt(2 + lineNumberTable.length)
+ .putShort(lineNumberTableLength)
+ .putByteArray(lineNumberTable.data, 0, lineNumberTable.length);
+ }
+ if (localVariableTable != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE))
+ .putInt(2 + localVariableTable.length)
+ .putShort(localVariableTableLength)
+ .putByteArray(localVariableTable.data, 0, localVariableTable.length);
+ }
+ if (localVariableTypeTable != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE))
+ .putInt(2 + localVariableTypeTable.length)
+ .putShort(localVariableTypeTableLength)
+ .putByteArray(localVariableTypeTable.data, 0, localVariableTypeTable.length);
+ }
+ if (lastCodeRuntimeVisibleTypeAnnotation != null) {
+ lastCodeRuntimeVisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (lastCodeRuntimeInvisibleTypeAnnotation != null) {
+ lastCodeRuntimeInvisibleTypeAnnotation.putAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output);
+ }
+ if (firstCodeAttribute != null) {
+ firstCodeAttribute.putAttributes(
+ symbolTable, code.data, code.length, maxStack, maxLocals, output);
+ }
+ }
+ if (numberOfExceptions > 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS))
+ .putInt(2 + 2 * numberOfExceptions)
+ .putShort(numberOfExceptions);
+ for (int exceptionIndex : exceptionIndexTable) {
+ output.putShort(exceptionIndex);
+ }
+ }
+ Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output);
+ AnnotationWriter.putAnnotations(
+ symbolTable,
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation,
+ output);
+ if (lastRuntimeVisibleParameterAnnotations != null) {
+ AnnotationWriter.putParameterAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS),
+ lastRuntimeVisibleParameterAnnotations,
+ visibleAnnotableParameterCount == 0
+ ? lastRuntimeVisibleParameterAnnotations.length
+ : visibleAnnotableParameterCount,
+ output);
+ }
+ if (lastRuntimeInvisibleParameterAnnotations != null) {
+ AnnotationWriter.putParameterAnnotations(
+ symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS),
+ lastRuntimeInvisibleParameterAnnotations,
+ invisibleAnnotableParameterCount == 0
+ ? lastRuntimeInvisibleParameterAnnotations.length
+ : invisibleAnnotableParameterCount,
+ output);
+ }
+ if (defaultValue != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT))
+ .putInt(defaultValue.length)
+ .putByteArray(defaultValue.data, 0, defaultValue.length);
+ }
+ if (parameters != null) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS))
+ .putInt(1 + parameters.length)
+ .putByte(parametersCount)
+ .putByteArray(parameters.data, 0, parameters.length);
+ }
+ if (firstAttribute != null) {
+ firstAttribute.putAttributes(symbolTable, output);
}
+ }
+
+ /**
+ * Collects the attributes of this method into the given set of attribute prototypes.
+ *
+ * @param attributePrototypes a set of attribute prototypes.
+ */
+ final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
+ attributePrototypes.addAttributes(firstAttribute);
+ attributePrototypes.addAttributes(firstCodeAttribute);
+ }
}
diff --git a/src/java/nginx/clojure/asm/ModuleVisitor.java b/src/java/nginx/clojure/asm/ModuleVisitor.java
new file mode 100644
index 00000000..358b9c95
--- /dev/null
+++ b/src/java/nginx/clojure/asm/ModuleVisitor.java
@@ -0,0 +1,196 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+/**
+ * A visitor to visit a Java module. The methods of this class must be called in the following
+ * order: ( {@code visitMainClass} | ( {@code visitPackage} | {@code visitRequire} | {@code
+ * visitExport} | {@code visitOpen} | {@code visitUse} | {@code visitProvide} )* ) {@code visitEnd}.
+ *
+ * @author Remi Forax
+ * @author Eric Bruneton
+ */
+public abstract class ModuleVisitor {
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of {@link
+ * Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ */
+ protected final int api;
+
+ /**
+ * The module visitor to which this visitor must delegate method calls. May be {@literal null}.
+ */
+ protected ModuleVisitor mv;
+
+ /**
+ * Constructs a new {@link ModuleVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6}
+ * or {@link Opcodes#ASM7}.
+ */
+ protected ModuleVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link ModuleVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6}
+ * or {@link Opcodes#ASM7}.
+ * @param moduleVisitor the module visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ protected ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
+ }
+ this.api = api;
+ this.mv = moduleVisitor;
+ }
+
+ /**
+ * The module visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the module visitor to which this visitor must delegate method calls, or {@literal
+ * null}.
+ */
+ public ModuleVisitor getDelegate() {
+ return mv;
+ }
+
+ /**
+ * Visit the main class of the current module.
+ *
+ * @param mainClass the internal name of the main class of the current module (see {@link
+ * Type#getInternalName()}).
+ */
+ public void visitMainClass(final String mainClass) {
+ if (mv != null) {
+ mv.visitMainClass(mainClass);
+ }
+ }
+
+ /**
+ * Visit a package of the current module.
+ *
+ * @param packaze the internal name of a package (see {@link Type#getInternalName()}).
+ */
+ public void visitPackage(final String packaze) {
+ if (mv != null) {
+ mv.visitPackage(packaze);
+ }
+ }
+
+ /**
+ * Visits a dependence of the current module.
+ *
+ * @param module the fully qualified name (using dots) of the dependence.
+ * @param access the access flag of the dependence among {@code ACC_TRANSITIVE}, {@code
+ * ACC_STATIC_PHASE}, {@code ACC_SYNTHETIC} and {@code ACC_MANDATED}.
+ * @param version the module version at compile time, or {@literal null}.
+ */
+ public void visitRequire(final String module, final int access, final String version) {
+ if (mv != null) {
+ mv.visitRequire(module, access, version);
+ }
+ }
+
+ /**
+ * Visit an exported package of the current module.
+ *
+ * @param packaze the internal name of the exported package (see {@link Type#getInternalName()}).
+ * @param access the access flag of the exported package, valid values are among {@code
+ * ACC_SYNTHETIC} and {@code ACC_MANDATED}.
+ * @param modules the fully qualified names (using dots) of the modules that can access the public
+ * classes of the exported package, or {@literal null}.
+ */
+ public void visitExport(final String packaze, final int access, final String... modules) {
+ if (mv != null) {
+ mv.visitExport(packaze, access, modules);
+ }
+ }
+
+ /**
+ * Visit an open package of the current module.
+ *
+ * @param packaze the internal name of the opened package (see {@link Type#getInternalName()}).
+ * @param access the access flag of the opened package, valid values are among {@code
+ * ACC_SYNTHETIC} and {@code ACC_MANDATED}.
+ * @param modules the fully qualified names (using dots) of the modules that can use deep
+ * reflection to the classes of the open package, or {@literal null}.
+ */
+ public void visitOpen(final String packaze, final int access, final String... modules) {
+ if (mv != null) {
+ mv.visitOpen(packaze, access, modules);
+ }
+ }
+
+ /**
+ * Visit a service used by the current module. The name must be the internal name of an interface
+ * or a class.
+ *
+ * @param service the internal name of the service (see {@link Type#getInternalName()}).
+ */
+ public void visitUse(final String service) {
+ if (mv != null) {
+ mv.visitUse(service);
+ }
+ }
+
+ /**
+ * Visit an implementation of a service.
+ *
+ * @param service the internal name of the service (see {@link Type#getInternalName()}).
+ * @param providers the internal names (see {@link Type#getInternalName()}) of the implementations
+ * of the service (there is at least one provider).
+ */
+ public void visitProvide(final String service, final String... providers) {
+ if (mv != null) {
+ mv.visitProvide(service, providers);
+ }
+ }
+
+ /**
+ * Visits the end of the module. This method, which is the last one to be called, is used to
+ * inform the visitor that everything have been visited.
+ */
+ public void visitEnd() {
+ if (mv != null) {
+ mv.visitEnd();
+ }
+ }
+}
diff --git a/src/java/nginx/clojure/asm/ModuleWriter.java b/src/java/nginx/clojure/asm/ModuleWriter.java
new file mode 100644
index 00000000..954d83d4
--- /dev/null
+++ b/src/java/nginx/clojure/asm/ModuleWriter.java
@@ -0,0 +1,253 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+/**
+ * A {@link ModuleVisitor} that generates the corresponding Module, ModulePackages and
+ * ModuleMainClass attributes, as defined in the Java Virtual Machine Specification (JVMS).
+ *
+ * @see JVMS
+ * 4.7.25
+ * @see JVMS
+ * 4.7.26
+ * @see JVMS
+ * 4.7.27
+ * @author Remi Forax
+ * @author Eric Bruneton
+ */
+final class ModuleWriter extends ModuleVisitor {
+
+ /** Where the constants used in this AnnotationWriter must be stored. */
+ private final SymbolTable symbolTable;
+
+ /** The module_name_index field of the JVMS Module attribute. */
+ private final int moduleNameIndex;
+
+ /** The module_flags field of the JVMS Module attribute. */
+ private final int moduleFlags;
+
+ /** The module_version_index field of the JVMS Module attribute. */
+ private final int moduleVersionIndex;
+
+ /** The requires_count field of the JVMS Module attribute. */
+ private int requiresCount;
+
+ /** The binary content of the 'requires' array of the JVMS Module attribute. */
+ private final ByteVector requires;
+
+ /** The exports_count field of the JVMS Module attribute. */
+ private int exportsCount;
+
+ /** The binary content of the 'exports' array of the JVMS Module attribute. */
+ private final ByteVector exports;
+
+ /** The opens_count field of the JVMS Module attribute. */
+ private int opensCount;
+
+ /** The binary content of the 'opens' array of the JVMS Module attribute. */
+ private final ByteVector opens;
+
+ /** The uses_count field of the JVMS Module attribute. */
+ private int usesCount;
+
+ /** The binary content of the 'uses_index' array of the JVMS Module attribute. */
+ private final ByteVector usesIndex;
+
+ /** The provides_count field of the JVMS Module attribute. */
+ private int providesCount;
+
+ /** The binary content of the 'provides' array of the JVMS Module attribute. */
+ private final ByteVector provides;
+
+ /** The provides_count field of the JVMS ModulePackages attribute. */
+ private int packageCount;
+
+ /** The binary content of the 'package_index' array of the JVMS ModulePackages attribute. */
+ private final ByteVector packageIndex;
+
+ /** The main_class_index field of the JVMS ModuleMainClass attribute, or 0. */
+ private int mainClassIndex;
+
+ ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) {
+ super(/* latest api = */ Opcodes.ASM9);
+ this.symbolTable = symbolTable;
+ this.moduleNameIndex = name;
+ this.moduleFlags = access;
+ this.moduleVersionIndex = version;
+ this.requires = new ByteVector();
+ this.exports = new ByteVector();
+ this.opens = new ByteVector();
+ this.usesIndex = new ByteVector();
+ this.provides = new ByteVector();
+ this.packageIndex = new ByteVector();
+ }
+
+ @Override
+ public void visitMainClass(final String mainClass) {
+ this.mainClassIndex = symbolTable.addConstantClass(mainClass).index;
+ }
+
+ @Override
+ public void visitPackage(final String packaze) {
+ packageIndex.putShort(symbolTable.addConstantPackage(packaze).index);
+ packageCount++;
+ }
+
+ @Override
+ public void visitRequire(final String module, final int access, final String version) {
+ requires
+ .putShort(symbolTable.addConstantModule(module).index)
+ .putShort(access)
+ .putShort(version == null ? 0 : symbolTable.addConstantUtf8(version));
+ requiresCount++;
+ }
+
+ @Override
+ public void visitExport(final String packaze, final int access, final String... modules) {
+ exports.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
+ if (modules == null) {
+ exports.putShort(0);
+ } else {
+ exports.putShort(modules.length);
+ for (String module : modules) {
+ exports.putShort(symbolTable.addConstantModule(module).index);
+ }
+ }
+ exportsCount++;
+ }
+
+ @Override
+ public void visitOpen(final String packaze, final int access, final String... modules) {
+ opens.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
+ if (modules == null) {
+ opens.putShort(0);
+ } else {
+ opens.putShort(modules.length);
+ for (String module : modules) {
+ opens.putShort(symbolTable.addConstantModule(module).index);
+ }
+ }
+ opensCount++;
+ }
+
+ @Override
+ public void visitUse(final String service) {
+ usesIndex.putShort(symbolTable.addConstantClass(service).index);
+ usesCount++;
+ }
+
+ @Override
+ public void visitProvide(final String service, final String... providers) {
+ provides.putShort(symbolTable.addConstantClass(service).index);
+ provides.putShort(providers.length);
+ for (String provider : providers) {
+ provides.putShort(symbolTable.addConstantClass(provider).index);
+ }
+ providesCount++;
+ }
+
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
+
+ /**
+ * Returns the number of Module, ModulePackages and ModuleMainClass attributes generated by this
+ * ModuleWriter.
+ *
+ * @return the number of Module, ModulePackages and ModuleMainClass attributes (between 1 and 3).
+ */
+ int getAttributeCount() {
+ return 1 + (packageCount > 0 ? 1 : 0) + (mainClassIndex > 0 ? 1 : 0);
+ }
+
+ /**
+ * Returns the size of the Module, ModulePackages and ModuleMainClass attributes generated by this
+ * ModuleWriter. Also add the names of these attributes in the constant pool.
+ *
+ * @return the size in bytes of the Module, ModulePackages and ModuleMainClass attributes.
+ */
+ int computeAttributesSize() {
+ symbolTable.addConstantUtf8(Constants.MODULE);
+ // 6 attribute header bytes, 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
+ int size =
+ 22 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
+ if (packageCount > 0) {
+ symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES);
+ // 6 attribute header bytes, and 2 bytes for package_count.
+ size += 8 + packageIndex.length;
+ }
+ if (mainClassIndex > 0) {
+ symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS);
+ // 6 attribute header bytes, and 2 bytes for main_class_index.
+ size += 8;
+ }
+ return size;
+ }
+
+ /**
+ * Puts the Module, ModulePackages and ModuleMainClass attributes generated by this ModuleWriter
+ * in the given ByteVector.
+ *
+ * @param output where the attributes must be put.
+ */
+ void putAttributes(final ByteVector output) {
+ // 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
+ int moduleAttributeLength =
+ 16 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.MODULE))
+ .putInt(moduleAttributeLength)
+ .putShort(moduleNameIndex)
+ .putShort(moduleFlags)
+ .putShort(moduleVersionIndex)
+ .putShort(requiresCount)
+ .putByteArray(requires.data, 0, requires.length)
+ .putShort(exportsCount)
+ .putByteArray(exports.data, 0, exports.length)
+ .putShort(opensCount)
+ .putByteArray(opens.data, 0, opens.length)
+ .putShort(usesCount)
+ .putByteArray(usesIndex.data, 0, usesIndex.length)
+ .putShort(providesCount)
+ .putByteArray(provides.data, 0, provides.length);
+ if (packageCount > 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES))
+ .putInt(2 + packageIndex.length)
+ .putShort(packageCount)
+ .putByteArray(packageIndex.data, 0, packageIndex.length);
+ }
+ if (mainClassIndex > 0) {
+ output
+ .putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS))
+ .putInt(2)
+ .putShort(mainClassIndex);
+ }
+ }
+}
diff --git a/src/java/nginx/clojure/asm/Opcodes.java b/src/java/nginx/clojure/asm/Opcodes.java
index 656c1295..d2fa3ceb 100644
--- a/src/java/nginx/clojure/asm/Opcodes.java
+++ b/src/java/nginx/clojure/asm/Opcodes.java
@@ -1,359 +1,563 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
/**
- * Defines the JVM opcodes, access flags and array type codes. This interface
- * does not define all the JVM opcodes because some opcodes are automatically
- * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced
- * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n
- * opcodes are therefore not defined in this interface. Likewise for LDC,
- * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and
- * JSR_W.
- *
+ * The JVM opcodes, access flags and array type codes. This interface does not define all the JVM
+ * opcodes because some opcodes are automatically handled. For example, the xLOAD and xSTORE opcodes
+ * are automatically replaced by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and
+ * xSTORE_n opcodes are therefore not defined in this interface. Likewise for LDC, automatically
+ * replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and JSR_W.
+ *
+ * @see JVMS 6
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
+// DontCheck(InterfaceIsType): can't be fixed (for backward binary compatibility).
public interface Opcodes {
- // ASM API versions
+ // ASM API versions.
+
+ int ASM4 = 4 << 16 | 0 << 8;
+ int ASM5 = 5 << 16 | 0 << 8;
+ int ASM6 = 6 << 16 | 0 << 8;
+ int ASM7 = 7 << 16 | 0 << 8;
+ int ASM8 = 8 << 16 | 0 << 8;
+ int ASM9 = 9 << 16 | 0 << 8;
+
+ /**
+ * Experimental, use at your own risk. This field will be renamed when it becomes stable, this
+ * will break existing code using it. Only code compiled with --enable-preview can use this.
+ *
+ * @deprecated This API is experimental.
+ */
+ @Deprecated int ASM10_EXPERIMENTAL = 1 << 24 | 10 << 16 | 0 << 8;
+
+ /*
+ * Internal flags used to redirect calls to deprecated methods. For instance, if a visitOldStuff
+ * method in API_OLD is deprecated and replaced with visitNewStuff in API_NEW, then the
+ * redirection should be done as follows:
+ *
+ *
+ * public class StuffVisitor {
+ * ...
+ *
+ * @Deprecated public void visitOldStuff(int arg, ...) {
+ * // SOURCE_DEPRECATED means "a call from a deprecated method using the old 'api' value".
+ * visitNewStuf(arg | (api < API_NEW ? SOURCE_DEPRECATED : 0), ...);
+ * }
+ *
+ * public void visitNewStuff(int argAndSource, ...) {
+ * if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
+ * visitOldStuff(argAndSource, ...);
+ * } else {
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do stuff ]
+ * }
+ * }
+ * }
+ *
+ *
+ * If 'api' is equal to API_NEW, there are two cases:
+ *
+ *
+ * - call visitNewStuff: the redirection test is skipped and 'do stuff' is executed directly.
+ *
- call visitOldSuff: the source is not set to SOURCE_DEPRECATED before calling
+ * visitNewStuff, but the redirection test is skipped anyway in visitNewStuff, which
+ * directly executes 'do stuff'.
+ *
+ *
+ * If 'api' is equal to API_OLD, there are two cases:
+ *
+ *
+ * - call visitOldSuff: the source is set to SOURCE_DEPRECATED before calling visitNewStuff.
+ * Because of this visitNewStuff does not redirect back to visitOldStuff, and instead
+ * executes 'do stuff'.
+ *
- call visitNewStuff: the call is redirected to visitOldStuff because the source is 0.
+ * visitOldStuff now sets the source to SOURCE_DEPRECATED and calls visitNewStuff back. This
+ * time visitNewStuff does not redirect the call, and instead executes 'do stuff'.
+ *
+ *
+ * User subclasses
+ *
+ * If a user subclass overrides one of these methods, there are only two cases: either 'api' is
+ * API_OLD and visitOldStuff is overridden (and visitNewStuff is not), or 'api' is API_NEW or
+ * more, and visitNewStuff is overridden (and visitOldStuff is not). Any other case is a user
+ * programming error.
+ *
+ *
If 'api' is equal to API_NEW, the class hierarchy is equivalent to
+ *
+ *
+ * public class StuffVisitor {
+ * @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
+ * public void visitNewStuff(int arg, ...) { [ do stuff ] }
+ * }
+ * class UserStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int arg, ...) {
+ * super.visitNewStuff(int arg, ...); // optional
+ * [ do user stuff ]
+ * }
+ * }
+ *
+ *
+ * It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff' and 'do
+ * user stuff' will be executed, in this order.
+ *
+ *
If 'api' is equal to API_OLD, the class hierarchy is equivalent to
+ *
+ *
+ * public class StuffVisitor {
+ * @Deprecated public void visitOldStuff(int arg, ...) {
+ * visitNewStuff(arg | SOURCE_DEPRECATED, ...);
+ * }
+ * public void visitNewStuff(int argAndSource...) {
+ * if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+ * visitOldStuff(argAndSource, ...);
+ * } else {
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do stuff ]
+ * }
+ * }
+ * }
+ * class UserStuffVisitor extends StuffVisitor {
+ * @Override public void visitOldStuff(int arg, ...) {
+ * super.visitOldStuff(int arg, ...); // optional
+ * [ do user stuff ]
+ * }
+ * }
+ *
+ *
+ * and there are two cases:
+ *
+ *
+ * - call visitOldStuff: in the call to super.visitOldStuff, the source is set to
+ * SOURCE_DEPRECATED and visitNewStuff is called. Here 'do stuff' is run because the source
+ * was previously set to SOURCE_DEPRECATED, and execution eventually returns to
+ * UserStuffVisitor.visitOldStuff, where 'do user stuff' is run.
+ *
- call visitNewStuff: the call is redirected to UserStuffVisitor.visitOldStuff because the
+ * source is 0. Execution continues as in the previous case, resulting in 'do stuff' and 'do
+ * user stuff' being executed, in this order.
+ *
+ *
+ * ASM subclasses
+ *
+ * In ASM packages, subclasses of StuffVisitor can typically be sub classed again by the user,
+ * and can be used with API_OLD or API_NEW. Because of this, if such a subclass must override
+ * visitNewStuff, it must do so in the following way (and must not override visitOldStuff):
+ *
+ *
+ * public class AsmStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int argAndSource, ...) {
+ * if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
+ * super.visitNewStuff(argAndSource, ...);
+ * return;
+ * }
+ * super.visitNewStuff(argAndSource, ...); // optional
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do other stuff ]
+ * }
+ * }
+ *
+ *
+ * If a user class extends this with 'api' equal to API_NEW, the class hierarchy is equivalent
+ * to
+ *
+ *
+ * public class StuffVisitor {
+ * @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
+ * public void visitNewStuff(int arg, ...) { [ do stuff ] }
+ * }
+ * public class AsmStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int arg, ...) {
+ * super.visitNewStuff(arg, ...);
+ * [ do other stuff ]
+ * }
+ * }
+ * class UserStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int arg, ...) {
+ * super.visitNewStuff(int arg, ...);
+ * [ do user stuff ]
+ * }
+ * }
+ *
+ *
+ * It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do
+ * other stuff' and 'do user stuff' will be executed, in this order. If, on the other hand, a user
+ * class extends AsmStuffVisitor with 'api' equal to API_OLD, the class hierarchy is equivalent to
+ *
+ *
+ * public class StuffVisitor {
+ * @Deprecated public void visitOldStuff(int arg, ...) {
+ * visitNewStuf(arg | SOURCE_DEPRECATED, ...);
+ * }
+ * public void visitNewStuff(int argAndSource, ...) {
+ * if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+ * visitOldStuff(argAndSource, ...);
+ * } else {
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do stuff ]
+ * }
+ * }
+ * }
+ * public class AsmStuffVisitor extends StuffVisitor {
+ * @Override public void visitNewStuff(int argAndSource, ...) {
+ * if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+ * super.visitNewStuff(argAndSource, ...);
+ * return;
+ * }
+ * super.visitNewStuff(argAndSource, ...); // optional
+ * int arg = argAndSource & ~SOURCE_MASK;
+ * [ do other stuff ]
+ * }
+ * }
+ * class UserStuffVisitor extends StuffVisitor {
+ * @Override public void visitOldStuff(int arg, ...) {
+ * super.visitOldStuff(arg, ...);
+ * [ do user stuff ]
+ * }
+ * }
+ *
+ *
+ * and, here again, whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do other
+ * stuff' and 'do user stuff' will be executed, in this order (exercise left to the reader).
+ *
+ *
Notes
+ *
+ *
+ * - the SOURCE_DEPRECATED flag is set only if 'api' is API_OLD, just before calling
+ * visitNewStuff. By hypothesis, this method is not overridden by the user. Therefore, user
+ * classes can never see this flag. Only ASM subclasses must take care of extracting the
+ * actual argument value by clearing the source flags.
+ *
- because the SOURCE_DEPRECATED flag is immediately cleared in the caller, the caller can
+ * call visitOldStuff or visitNewStuff (in 'do stuff' and 'do user stuff') on a delegate
+ * visitor without any risks (breaking the redirection logic, "leaking" the flag, etc).
+ *
- all the scenarios discussed above are unit tested in MethodVisitorTest.
+ *
+ */
+
+ int SOURCE_DEPRECATED = 0x100;
+ int SOURCE_MASK = SOURCE_DEPRECATED;
+
+ // Java ClassFile versions (the minor version is stored in the 16 most significant bits, and the
+ // major version in the 16 least significant bits).
- int ASM4 = 4 << 16 | 0 << 8 | 0;
+ int V1_1 = 3 << 16 | 45;
+ int V1_2 = 0 << 16 | 46;
+ int V1_3 = 0 << 16 | 47;
+ int V1_4 = 0 << 16 | 48;
+ int V1_5 = 0 << 16 | 49;
+ int V1_6 = 0 << 16 | 50;
+ int V1_7 = 0 << 16 | 51;
+ int V1_8 = 0 << 16 | 52;
+ int V9 = 0 << 16 | 53;
+ int V10 = 0 << 16 | 54;
+ int V11 = 0 << 16 | 55;
+ int V12 = 0 << 16 | 56;
+ int V13 = 0 << 16 | 57;
+ int V14 = 0 << 16 | 58;
+ int V15 = 0 << 16 | 59;
+ int V16 = 0 << 16 | 60;
+ int V17 = 0 << 16 | 61;
+ int V18 = 0 << 16 | 62;
+ int V19 = 0 << 16 | 63;
+ int V20 = 0 << 16 | 64;
- // versions
+ /**
+ * Version flag indicating that the class is using 'preview' features.
+ *
+ * {@code version & V_PREVIEW == V_PREVIEW} tests if a version is flagged with {@code
+ * V_PREVIEW}.
+ */
+ int V_PREVIEW = 0xFFFF0000;
- int V1_1 = 3 << 16 | 45;
- int V1_2 = 0 << 16 | 46;
- int V1_3 = 0 << 16 | 47;
- int V1_4 = 0 << 16 | 48;
- int V1_5 = 0 << 16 | 49;
- int V1_6 = 0 << 16 | 50;
- int V1_7 = 0 << 16 | 51;
- int V1_8 = 0 << 16 | 52;
+ // Access flags values, defined in
+ // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1
+ // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.5-200-A.1
+ // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.6-200-A.1
+ // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.25
- // access flags
+ int ACC_PUBLIC = 0x0001; // class, field, method
+ int ACC_PRIVATE = 0x0002; // class, field, method
+ int ACC_PROTECTED = 0x0004; // class, field, method
+ int ACC_STATIC = 0x0008; // field, method
+ int ACC_FINAL = 0x0010; // class, field, method, parameter
+ int ACC_SUPER = 0x0020; // class
+ int ACC_SYNCHRONIZED = 0x0020; // method
+ int ACC_OPEN = 0x0020; // module
+ int ACC_TRANSITIVE = 0x0020; // module requires
+ int ACC_VOLATILE = 0x0040; // field
+ int ACC_BRIDGE = 0x0040; // method
+ int ACC_STATIC_PHASE = 0x0040; // module requires
+ int ACC_VARARGS = 0x0080; // method
+ int ACC_TRANSIENT = 0x0080; // field
+ int ACC_NATIVE = 0x0100; // method
+ int ACC_INTERFACE = 0x0200; // class
+ int ACC_ABSTRACT = 0x0400; // class, method
+ int ACC_STRICT = 0x0800; // method
+ int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module *
+ int ACC_ANNOTATION = 0x2000; // class
+ int ACC_ENUM = 0x4000; // class(?) field inner
+ int ACC_MANDATED = 0x8000; // field, method, parameter, module, module *
+ int ACC_MODULE = 0x8000; // class
- int ACC_PUBLIC = 0x0001; // class, field, method
- int ACC_PRIVATE = 0x0002; // class, field, method
- int ACC_PROTECTED = 0x0004; // class, field, method
- int ACC_STATIC = 0x0008; // field, method
- int ACC_FINAL = 0x0010; // class, field, method
- int ACC_SUPER = 0x0020; // class
- int ACC_SYNCHRONIZED = 0x0020; // method
- int ACC_VOLATILE = 0x0040; // field
- int ACC_BRIDGE = 0x0040; // method
- int ACC_VARARGS = 0x0080; // method
- int ACC_TRANSIENT = 0x0080; // field
- int ACC_NATIVE = 0x0100; // method
- int ACC_INTERFACE = 0x0200; // class
- int ACC_ABSTRACT = 0x0400; // class, method
- int ACC_STRICT = 0x0800; // method
- int ACC_SYNTHETIC = 0x1000; // class, field, method
- int ACC_ANNOTATION = 0x2000; // class
- int ACC_ENUM = 0x4000; // class(?) field inner
+ // ASM specific access flags.
+ // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard
+ // access flags, and also to make sure that these flags are automatically filtered out when
+ // written in class files (because access flags are stored using 16 bits only).
- // ASM specific pseudo access flags
+ int ACC_RECORD = 0x10000; // class
+ int ACC_DEPRECATED = 0x20000; // class, field, method
- int ACC_DEPRECATED = 0x20000; // class, field, method
+ // Possible values for the type operand of the NEWARRAY instruction.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.newarray.
- // types for NEWARRAY
+ int T_BOOLEAN = 4;
+ int T_CHAR = 5;
+ int T_FLOAT = 6;
+ int T_DOUBLE = 7;
+ int T_BYTE = 8;
+ int T_SHORT = 9;
+ int T_INT = 10;
+ int T_LONG = 11;
- int T_BOOLEAN = 4;
- int T_CHAR = 5;
- int T_FLOAT = 6;
- int T_DOUBLE = 7;
- int T_BYTE = 8;
- int T_SHORT = 9;
- int T_INT = 10;
- int T_LONG = 11;
+ // Possible values for the reference_kind field of CONSTANT_MethodHandle_info structures.
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.4.8.
- // tags for Handle
+ int H_GETFIELD = 1;
+ int H_GETSTATIC = 2;
+ int H_PUTFIELD = 3;
+ int H_PUTSTATIC = 4;
+ int H_INVOKEVIRTUAL = 5;
+ int H_INVOKESTATIC = 6;
+ int H_INVOKESPECIAL = 7;
+ int H_NEWINVOKESPECIAL = 8;
+ int H_INVOKEINTERFACE = 9;
- int H_GETFIELD = 1;
- int H_GETSTATIC = 2;
- int H_PUTFIELD = 3;
- int H_PUTSTATIC = 4;
- int H_INVOKEVIRTUAL = 5;
- int H_INVOKESTATIC = 6;
- int H_INVOKESPECIAL = 7;
- int H_NEWINVOKESPECIAL = 8;
- int H_INVOKEINTERFACE = 9;
+ // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}.
- // stack map frame types
+ /** An expanded frame. See {@link ClassReader#EXPAND_FRAMES}. */
+ int F_NEW = -1;
- /**
- * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}.
- */
- int F_NEW = -1;
+ /** A compressed frame with complete frame data. */
+ int F_FULL = 0;
- /**
- * Represents a compressed frame with complete frame data.
- */
- int F_FULL = 0;
+ /**
+ * A compressed frame where locals are the same as the locals in the previous frame, except that
+ * additional 1-3 locals are defined, and with an empty stack.
+ */
+ int F_APPEND = 1;
- /**
- * Represents a compressed frame where locals are the same as the locals in
- * the previous frame, except that additional 1-3 locals are defined, and
- * with an empty stack.
- */
- int F_APPEND = 1;
+ /**
+ * A compressed frame where locals are the same as the locals in the previous frame, except that
+ * the last 1-3 locals are absent and with an empty stack.
+ */
+ int F_CHOP = 2;
- /**
- * Represents a compressed frame where locals are the same as the locals in
- * the previous frame, except that the last 1-3 locals are absent and with
- * an empty stack.
- */
- int F_CHOP = 2;
+ /**
+ * A compressed frame with exactly the same locals as the previous frame and with an empty stack.
+ */
+ int F_SAME = 3;
- /**
- * Represents a compressed frame with exactly the same locals as the
- * previous frame and with an empty stack.
- */
- int F_SAME = 3;
+ /**
+ * A compressed frame with exactly the same locals as the previous frame and with a single value
+ * on the stack.
+ */
+ int F_SAME1 = 4;
- /**
- * Represents a compressed frame with exactly the same locals as the
- * previous frame and with a single value on the stack.
- */
- int F_SAME1 = 4;
+ // Standard stack map frame element types, used in {@link ClassVisitor#visitFrame}.
- Integer TOP = new Integer(0);
- Integer INTEGER = new Integer(1);
- Integer FLOAT = new Integer(2);
- Integer DOUBLE = new Integer(3);
- Integer LONG = new Integer(4);
- Integer NULL = new Integer(5);
- Integer UNINITIALIZED_THIS = new Integer(6);
+ Integer TOP = Frame.ITEM_TOP;
+ Integer INTEGER = Frame.ITEM_INTEGER;
+ Integer FLOAT = Frame.ITEM_FLOAT;
+ Integer DOUBLE = Frame.ITEM_DOUBLE;
+ Integer LONG = Frame.ITEM_LONG;
+ Integer NULL = Frame.ITEM_NULL;
+ Integer UNINITIALIZED_THIS = Frame.ITEM_UNINITIALIZED_THIS;
- // opcodes // visit method (- = idem)
+ // The JVM opcode values (with the MethodVisitor method name used to visit them in comment, and
+ // where '-' means 'same method name as on the previous line').
+ // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html.
- int NOP = 0; // visitInsn
- int ACONST_NULL = 1; // -
- int ICONST_M1 = 2; // -
- int ICONST_0 = 3; // -
- int ICONST_1 = 4; // -
- int ICONST_2 = 5; // -
- int ICONST_3 = 6; // -
- int ICONST_4 = 7; // -
- int ICONST_5 = 8; // -
- int LCONST_0 = 9; // -
- int LCONST_1 = 10; // -
- int FCONST_0 = 11; // -
- int FCONST_1 = 12; // -
- int FCONST_2 = 13; // -
- int DCONST_0 = 14; // -
- int DCONST_1 = 15; // -
- int BIPUSH = 16; // visitIntInsn
- int SIPUSH = 17; // -
- int LDC = 18; // visitLdcInsn
- // int LDC_W = 19; // -
- // int LDC2_W = 20; // -
- int ILOAD = 21; // visitVarInsn
- int LLOAD = 22; // -
- int FLOAD = 23; // -
- int DLOAD = 24; // -
- int ALOAD = 25; // -
- // int ILOAD_0 = 26; // -
- // int ILOAD_1 = 27; // -
- // int ILOAD_2 = 28; // -
- // int ILOAD_3 = 29; // -
- // int LLOAD_0 = 30; // -
- // int LLOAD_1 = 31; // -
- // int LLOAD_2 = 32; // -
- // int LLOAD_3 = 33; // -
- // int FLOAD_0 = 34; // -
- // int FLOAD_1 = 35; // -
- // int FLOAD_2 = 36; // -
- // int FLOAD_3 = 37; // -
- // int DLOAD_0 = 38; // -
- // int DLOAD_1 = 39; // -
- // int DLOAD_2 = 40; // -
- // int DLOAD_3 = 41; // -
- // int ALOAD_0 = 42; // -
- // int ALOAD_1 = 43; // -
- // int ALOAD_2 = 44; // -
- // int ALOAD_3 = 45; // -
- int IALOAD = 46; // visitInsn
- int LALOAD = 47; // -
- int FALOAD = 48; // -
- int DALOAD = 49; // -
- int AALOAD = 50; // -
- int BALOAD = 51; // -
- int CALOAD = 52; // -
- int SALOAD = 53; // -
- int ISTORE = 54; // visitVarInsn
- int LSTORE = 55; // -
- int FSTORE = 56; // -
- int DSTORE = 57; // -
- int ASTORE = 58; // -
- // int ISTORE_0 = 59; // -
- // int ISTORE_1 = 60; // -
- // int ISTORE_2 = 61; // -
- // int ISTORE_3 = 62; // -
- // int LSTORE_0 = 63; // -
- // int LSTORE_1 = 64; // -
- // int LSTORE_2 = 65; // -
- // int LSTORE_3 = 66; // -
- // int FSTORE_0 = 67; // -
- // int FSTORE_1 = 68; // -
- // int FSTORE_2 = 69; // -
- // int FSTORE_3 = 70; // -
- // int DSTORE_0 = 71; // -
- // int DSTORE_1 = 72; // -
- // int DSTORE_2 = 73; // -
- // int DSTORE_3 = 74; // -
- // int ASTORE_0 = 75; // -
- // int ASTORE_1 = 76; // -
- // int ASTORE_2 = 77; // -
- // int ASTORE_3 = 78; // -
- int IASTORE = 79; // visitInsn
- int LASTORE = 80; // -
- int FASTORE = 81; // -
- int DASTORE = 82; // -
- int AASTORE = 83; // -
- int BASTORE = 84; // -
- int CASTORE = 85; // -
- int SASTORE = 86; // -
- int POP = 87; // -
- int POP2 = 88; // -
- int DUP = 89; // -
- int DUP_X1 = 90; // -
- int DUP_X2 = 91; // -
- int DUP2 = 92; // -
- int DUP2_X1 = 93; // -
- int DUP2_X2 = 94; // -
- int SWAP = 95; // -
- int IADD = 96; // -
- int LADD = 97; // -
- int FADD = 98; // -
- int DADD = 99; // -
- int ISUB = 100; // -
- int LSUB = 101; // -
- int FSUB = 102; // -
- int DSUB = 103; // -
- int IMUL = 104; // -
- int LMUL = 105; // -
- int FMUL = 106; // -
- int DMUL = 107; // -
- int IDIV = 108; // -
- int LDIV = 109; // -
- int FDIV = 110; // -
- int DDIV = 111; // -
- int IREM = 112; // -
- int LREM = 113; // -
- int FREM = 114; // -
- int DREM = 115; // -
- int INEG = 116; // -
- int LNEG = 117; // -
- int FNEG = 118; // -
- int DNEG = 119; // -
- int ISHL = 120; // -
- int LSHL = 121; // -
- int ISHR = 122; // -
- int LSHR = 123; // -
- int IUSHR = 124; // -
- int LUSHR = 125; // -
- int IAND = 126; // -
- int LAND = 127; // -
- int IOR = 128; // -
- int LOR = 129; // -
- int IXOR = 130; // -
- int LXOR = 131; // -
- int IINC = 132; // visitIincInsn
- int I2L = 133; // visitInsn
- int I2F = 134; // -
- int I2D = 135; // -
- int L2I = 136; // -
- int L2F = 137; // -
- int L2D = 138; // -
- int F2I = 139; // -
- int F2L = 140; // -
- int F2D = 141; // -
- int D2I = 142; // -
- int D2L = 143; // -
- int D2F = 144; // -
- int I2B = 145; // -
- int I2C = 146; // -
- int I2S = 147; // -
- int LCMP = 148; // -
- int FCMPL = 149; // -
- int FCMPG = 150; // -
- int DCMPL = 151; // -
- int DCMPG = 152; // -
- int IFEQ = 153; // visitJumpInsn
- int IFNE = 154; // -
- int IFLT = 155; // -
- int IFGE = 156; // -
- int IFGT = 157; // -
- int IFLE = 158; // -
- int IF_ICMPEQ = 159; // -
- int IF_ICMPNE = 160; // -
- int IF_ICMPLT = 161; // -
- int IF_ICMPGE = 162; // -
- int IF_ICMPGT = 163; // -
- int IF_ICMPLE = 164; // -
- int IF_ACMPEQ = 165; // -
- int IF_ACMPNE = 166; // -
- int GOTO = 167; // -
- int JSR = 168; // -
- int RET = 169; // visitVarInsn
- int TABLESWITCH = 170; // visiTableSwitchInsn
- int LOOKUPSWITCH = 171; // visitLookupSwitch
- int IRETURN = 172; // visitInsn
- int LRETURN = 173; // -
- int FRETURN = 174; // -
- int DRETURN = 175; // -
- int ARETURN = 176; // -
- int RETURN = 177; // -
- int GETSTATIC = 178; // visitFieldInsn
- int PUTSTATIC = 179; // -
- int GETFIELD = 180; // -
- int PUTFIELD = 181; // -
- int INVOKEVIRTUAL = 182; // visitMethodInsn
- int INVOKESPECIAL = 183; // -
- int INVOKESTATIC = 184; // -
- int INVOKEINTERFACE = 185; // -
- int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn
- int NEW = 187; // visitTypeInsn
- int NEWARRAY = 188; // visitIntInsn
- int ANEWARRAY = 189; // visitTypeInsn
- int ARRAYLENGTH = 190; // visitInsn
- int ATHROW = 191; // -
- int CHECKCAST = 192; // visitTypeInsn
- int INSTANCEOF = 193; // -
- int MONITORENTER = 194; // visitInsn
- int MONITOREXIT = 195; // -
- // int WIDE = 196; // NOT VISITED
- int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn
- int IFNULL = 198; // visitJumpInsn
- int IFNONNULL = 199; // -
- // int GOTO_W = 200; // -
- // int JSR_W = 201; // -
+ int NOP = 0; // visitInsn
+ int ACONST_NULL = 1; // -
+ int ICONST_M1 = 2; // -
+ int ICONST_0 = 3; // -
+ int ICONST_1 = 4; // -
+ int ICONST_2 = 5; // -
+ int ICONST_3 = 6; // -
+ int ICONST_4 = 7; // -
+ int ICONST_5 = 8; // -
+ int LCONST_0 = 9; // -
+ int LCONST_1 = 10; // -
+ int FCONST_0 = 11; // -
+ int FCONST_1 = 12; // -
+ int FCONST_2 = 13; // -
+ int DCONST_0 = 14; // -
+ int DCONST_1 = 15; // -
+ int BIPUSH = 16; // visitIntInsn
+ int SIPUSH = 17; // -
+ int LDC = 18; // visitLdcInsn
+ int ILOAD = 21; // visitVarInsn
+ int LLOAD = 22; // -
+ int FLOAD = 23; // -
+ int DLOAD = 24; // -
+ int ALOAD = 25; // -
+ int IALOAD = 46; // visitInsn
+ int LALOAD = 47; // -
+ int FALOAD = 48; // -
+ int DALOAD = 49; // -
+ int AALOAD = 50; // -
+ int BALOAD = 51; // -
+ int CALOAD = 52; // -
+ int SALOAD = 53; // -
+ int ISTORE = 54; // visitVarInsn
+ int LSTORE = 55; // -
+ int FSTORE = 56; // -
+ int DSTORE = 57; // -
+ int ASTORE = 58; // -
+ int IASTORE = 79; // visitInsn
+ int LASTORE = 80; // -
+ int FASTORE = 81; // -
+ int DASTORE = 82; // -
+ int AASTORE = 83; // -
+ int BASTORE = 84; // -
+ int CASTORE = 85; // -
+ int SASTORE = 86; // -
+ int POP = 87; // -
+ int POP2 = 88; // -
+ int DUP = 89; // -
+ int DUP_X1 = 90; // -
+ int DUP_X2 = 91; // -
+ int DUP2 = 92; // -
+ int DUP2_X1 = 93; // -
+ int DUP2_X2 = 94; // -
+ int SWAP = 95; // -
+ int IADD = 96; // -
+ int LADD = 97; // -
+ int FADD = 98; // -
+ int DADD = 99; // -
+ int ISUB = 100; // -
+ int LSUB = 101; // -
+ int FSUB = 102; // -
+ int DSUB = 103; // -
+ int IMUL = 104; // -
+ int LMUL = 105; // -
+ int FMUL = 106; // -
+ int DMUL = 107; // -
+ int IDIV = 108; // -
+ int LDIV = 109; // -
+ int FDIV = 110; // -
+ int DDIV = 111; // -
+ int IREM = 112; // -
+ int LREM = 113; // -
+ int FREM = 114; // -
+ int DREM = 115; // -
+ int INEG = 116; // -
+ int LNEG = 117; // -
+ int FNEG = 118; // -
+ int DNEG = 119; // -
+ int ISHL = 120; // -
+ int LSHL = 121; // -
+ int ISHR = 122; // -
+ int LSHR = 123; // -
+ int IUSHR = 124; // -
+ int LUSHR = 125; // -
+ int IAND = 126; // -
+ int LAND = 127; // -
+ int IOR = 128; // -
+ int LOR = 129; // -
+ int IXOR = 130; // -
+ int LXOR = 131; // -
+ int IINC = 132; // visitIincInsn
+ int I2L = 133; // visitInsn
+ int I2F = 134; // -
+ int I2D = 135; // -
+ int L2I = 136; // -
+ int L2F = 137; // -
+ int L2D = 138; // -
+ int F2I = 139; // -
+ int F2L = 140; // -
+ int F2D = 141; // -
+ int D2I = 142; // -
+ int D2L = 143; // -
+ int D2F = 144; // -
+ int I2B = 145; // -
+ int I2C = 146; // -
+ int I2S = 147; // -
+ int LCMP = 148; // -
+ int FCMPL = 149; // -
+ int FCMPG = 150; // -
+ int DCMPL = 151; // -
+ int DCMPG = 152; // -
+ int IFEQ = 153; // visitJumpInsn
+ int IFNE = 154; // -
+ int IFLT = 155; // -
+ int IFGE = 156; // -
+ int IFGT = 157; // -
+ int IFLE = 158; // -
+ int IF_ICMPEQ = 159; // -
+ int IF_ICMPNE = 160; // -
+ int IF_ICMPLT = 161; // -
+ int IF_ICMPGE = 162; // -
+ int IF_ICMPGT = 163; // -
+ int IF_ICMPLE = 164; // -
+ int IF_ACMPEQ = 165; // -
+ int IF_ACMPNE = 166; // -
+ int GOTO = 167; // -
+ int JSR = 168; // -
+ int RET = 169; // visitVarInsn
+ int TABLESWITCH = 170; // visiTableSwitchInsn
+ int LOOKUPSWITCH = 171; // visitLookupSwitch
+ int IRETURN = 172; // visitInsn
+ int LRETURN = 173; // -
+ int FRETURN = 174; // -
+ int DRETURN = 175; // -
+ int ARETURN = 176; // -
+ int RETURN = 177; // -
+ int GETSTATIC = 178; // visitFieldInsn
+ int PUTSTATIC = 179; // -
+ int GETFIELD = 180; // -
+ int PUTFIELD = 181; // -
+ int INVOKEVIRTUAL = 182; // visitMethodInsn
+ int INVOKESPECIAL = 183; // -
+ int INVOKESTATIC = 184; // -
+ int INVOKEINTERFACE = 185; // -
+ int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn
+ int NEW = 187; // visitTypeInsn
+ int NEWARRAY = 188; // visitIntInsn
+ int ANEWARRAY = 189; // visitTypeInsn
+ int ARRAYLENGTH = 190; // visitInsn
+ int ATHROW = 191; // -
+ int CHECKCAST = 192; // visitTypeInsn
+ int INSTANCEOF = 193; // -
+ int MONITORENTER = 194; // visitInsn
+ int MONITOREXIT = 195; // -
+ int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn
+ int IFNULL = 198; // visitJumpInsn
+ int IFNONNULL = 199; // -
}
diff --git a/src/java/nginx/clojure/asm/RecordComponentVisitor.java b/src/java/nginx/clojure/asm/RecordComponentVisitor.java
new file mode 100644
index 00000000..b198d221
--- /dev/null
+++ b/src/java/nginx/clojure/asm/RecordComponentVisitor.java
@@ -0,0 +1,153 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+/**
+ * A visitor to visit a record component. The methods of this class must be called in the following
+ * order: ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code
+ * visitEnd}.
+ *
+ * @author Remi Forax
+ * @author Eric Bruneton
+ */
+public abstract class RecordComponentVisitor {
+ /**
+ * The ASM API version implemented by this visitor. The value of this field must be one of {@link
+ * Opcodes#ASM8} or {@link Opcodes#ASM9}.
+ */
+ protected final int api;
+
+ /**
+ * The record visitor to which this visitor must delegate method calls. May be {@literal null}.
+ */
+ protected RecordComponentVisitor delegate;
+
+ /**
+ * Constructs a new {@link RecordComponentVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM8}
+ * or {@link Opcodes#ASM9}.
+ */
+ protected RecordComponentVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link RecordComponentVisitor}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be {@link Opcodes#ASM8}.
+ * @param recordComponentVisitor the record component visitor to which this visitor must delegate
+ * method calls. May be null.
+ */
+ protected RecordComponentVisitor(
+ final int api, final RecordComponentVisitor recordComponentVisitor) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
+ && api != Opcodes.ASM6
+ && api != Opcodes.ASM5
+ && api != Opcodes.ASM4
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
+ throw new IllegalArgumentException("Unsupported api " + api);
+ }
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
+ }
+ this.api = api;
+ this.delegate = recordComponentVisitor;
+ }
+
+ /**
+ * The record visitor to which this visitor must delegate method calls. May be {@literal null}.
+ *
+ * @return the record visitor to which this visitor must delegate method calls, or {@literal
+ * null}.
+ */
+ public RecordComponentVisitor getDelegate() {
+ return delegate;
+ }
+
+ /**
+ * Visits an annotation of the record component.
+ *
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (delegate != null) {
+ return delegate.visitAnnotation(descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on a type in the record component signature.
+ *
+ * @param typeRef a reference to the annotated type. The sort of this type reference must be
+ * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link
+ * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See
+ * {@link TypeReference}.
+ * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+ * static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+ * 'typeRef' as a whole.
+ * @param descriptor the class descriptor of the annotation class.
+ * @param visible {@literal true} if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
+ * interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (delegate != null) {
+ return delegate.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of the record component.
+ *
+ * @param attribute an attribute.
+ */
+ public void visitAttribute(final Attribute attribute) {
+ if (delegate != null) {
+ delegate.visitAttribute(attribute);
+ }
+ }
+
+ /**
+ * Visits the end of the record component. This method, which is the last one to be called, is
+ * used to inform the visitor that everything have been visited.
+ */
+ public void visitEnd() {
+ if (delegate != null) {
+ delegate.visitEnd();
+ }
+ }
+}
diff --git a/src/java/nginx/clojure/asm/RecordComponentWriter.java b/src/java/nginx/clojure/asm/RecordComponentWriter.java
new file mode 100644
index 00000000..b8eae430
--- /dev/null
+++ b/src/java/nginx/clojure/asm/RecordComponentWriter.java
@@ -0,0 +1,225 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+final class RecordComponentWriter extends RecordComponentVisitor {
+ /** Where the constants used in this RecordComponentWriter must be stored. */
+ private final SymbolTable symbolTable;
+
+ // Note: fields are ordered as in the record_component_info structure, and those related to
+ // attributes are ordered as in Section 4.7 of the JVMS.
+
+ /** The name_index field of the Record attribute. */
+ private final int nameIndex;
+
+ /** The descriptor_index field of the the Record attribute. */
+ private final int descriptorIndex;
+
+ /**
+ * The signature_index field of the Signature attribute of this record component, or 0 if there is
+ * no Signature attribute.
+ */
+ private int signatureIndex;
+
+ /**
+ * The last runtime visible annotation of this record component. The previous ones can be accessed
+ * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleAnnotation;
+
+ /**
+ * The last runtime invisible annotation of this record component. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleAnnotation;
+
+ /**
+ * The last runtime visible type annotation of this record component. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeVisibleTypeAnnotation;
+
+ /**
+ * The last runtime invisible type annotation of this record component. The previous ones can be
+ * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}.
+ */
+ private AnnotationWriter lastRuntimeInvisibleTypeAnnotation;
+
+ /**
+ * The first non standard attribute of this record component. The next ones can be accessed with
+ * the {@link Attribute#nextAttribute} field. May be {@literal null}.
+ *
+ *
WARNING: this list stores the attributes in the reverse order of their visit.
+ * firstAttribute is actually the last attribute visited in {@link #visitAttribute(Attribute)}.
+ * The {@link #putRecordComponentInfo(ByteVector)} method writes the attributes in the order
+ * defined by this list, i.e. in the reverse order specified by the user.
+ */
+ private Attribute firstAttribute;
+
+ /**
+ * Constructs a new {@link RecordComponentWriter}.
+ *
+ * @param symbolTable where the constants used in this RecordComponentWriter must be stored.
+ * @param name the record component name.
+ * @param descriptor the record component descriptor (see {@link Type}).
+ * @param signature the record component signature. May be {@literal null}.
+ */
+ RecordComponentWriter(
+ final SymbolTable symbolTable,
+ final String name,
+ final String descriptor,
+ final String signature) {
+ super(/* latest api = */ Opcodes.ASM9);
+ this.symbolTable = symbolTable;
+ this.nameIndex = symbolTable.addConstantUtf8(name);
+ this.descriptorIndex = symbolTable.addConstantUtf8(descriptor);
+ if (signature != null) {
+ this.signatureIndex = symbolTable.addConstantUtf8(signature);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the FieldVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation);
+ } else {
+ return lastRuntimeInvisibleAnnotation =
+ AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation);
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ if (visible) {
+ return lastRuntimeVisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation);
+ } else {
+ return lastRuntimeInvisibleTypeAnnotation =
+ AnnotationWriter.create(
+ symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation);
+ }
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attribute) {
+ // Store the attributes in the reverse order of their visit by this method.
+ attribute.nextAttribute = firstAttribute;
+ firstAttribute = attribute;
+ }
+
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Utility methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the size of the record component JVMS structure generated by this
+ * RecordComponentWriter. Also adds the names of the attributes of this record component in the
+ * constant pool.
+ *
+ * @return the size in bytes of the record_component_info of the Record attribute.
+ */
+ int computeRecordComponentInfoSize() {
+ // name_index, descriptor_index and attributes_count fields use 6 bytes.
+ int size = 6;
+ size += Attribute.computeAttributesSize(symbolTable, 0, signatureIndex);
+ size +=
+ AnnotationWriter.computeAnnotationsSize(
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation);
+ if (firstAttribute != null) {
+ size += firstAttribute.computeAttributesSize(symbolTable);
+ }
+ return size;
+ }
+
+ /**
+ * Puts the content of the record component generated by this RecordComponentWriter into the given
+ * ByteVector.
+ *
+ * @param output where the record_component_info structure must be put.
+ */
+ void putRecordComponentInfo(final ByteVector output) {
+ output.putShort(nameIndex).putShort(descriptorIndex);
+ // Compute and put the attributes_count field.
+ // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS.
+ int attributesCount = 0;
+ if (signatureIndex != 0) {
+ ++attributesCount;
+ }
+ if (lastRuntimeVisibleAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeInvisibleAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeVisibleTypeAnnotation != null) {
+ ++attributesCount;
+ }
+ if (lastRuntimeInvisibleTypeAnnotation != null) {
+ ++attributesCount;
+ }
+ if (firstAttribute != null) {
+ attributesCount += firstAttribute.getAttributeCount();
+ }
+ output.putShort(attributesCount);
+ Attribute.putAttributes(symbolTable, 0, signatureIndex, output);
+ AnnotationWriter.putAnnotations(
+ symbolTable,
+ lastRuntimeVisibleAnnotation,
+ lastRuntimeInvisibleAnnotation,
+ lastRuntimeVisibleTypeAnnotation,
+ lastRuntimeInvisibleTypeAnnotation,
+ output);
+ if (firstAttribute != null) {
+ firstAttribute.putAttributes(symbolTable, output);
+ }
+ }
+
+ /**
+ * Collects the attributes of this record component into the given set of attribute prototypes.
+ *
+ * @param attributePrototypes a set of attribute prototypes.
+ */
+ final void collectAttributePrototypes(final Attribute.Set attributePrototypes) {
+ attributePrototypes.addAttributes(firstAttribute);
+ }
+}
diff --git a/src/java/nginx/clojure/asm/Symbol.java b/src/java/nginx/clojure/asm/Symbol.java
new file mode 100644
index 00000000..ea790fed
--- /dev/null
+++ b/src/java/nginx/clojure/asm/Symbol.java
@@ -0,0 +1,243 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+/**
+ * An entry of the constant pool, of the BootstrapMethods attribute, or of the (ASM specific) type
+ * table of a class.
+ *
+ * @see JVMS
+ * 4.4
+ * @see JVMS
+ * 4.7.23
+ * @author Eric Bruneton
+ */
+abstract class Symbol {
+
+ // Tag values for the constant pool entries (using the same order as in the JVMS).
+
+ /** The tag value of CONSTANT_Class_info JVMS structures. */
+ static final int CONSTANT_CLASS_TAG = 7;
+
+ /** The tag value of CONSTANT_Fieldref_info JVMS structures. */
+ static final int CONSTANT_FIELDREF_TAG = 9;
+
+ /** The tag value of CONSTANT_Methodref_info JVMS structures. */
+ static final int CONSTANT_METHODREF_TAG = 10;
+
+ /** The tag value of CONSTANT_InterfaceMethodref_info JVMS structures. */
+ static final int CONSTANT_INTERFACE_METHODREF_TAG = 11;
+
+ /** The tag value of CONSTANT_String_info JVMS structures. */
+ static final int CONSTANT_STRING_TAG = 8;
+
+ /** The tag value of CONSTANT_Integer_info JVMS structures. */
+ static final int CONSTANT_INTEGER_TAG = 3;
+
+ /** The tag value of CONSTANT_Float_info JVMS structures. */
+ static final int CONSTANT_FLOAT_TAG = 4;
+
+ /** The tag value of CONSTANT_Long_info JVMS structures. */
+ static final int CONSTANT_LONG_TAG = 5;
+
+ /** The tag value of CONSTANT_Double_info JVMS structures. */
+ static final int CONSTANT_DOUBLE_TAG = 6;
+
+ /** The tag value of CONSTANT_NameAndType_info JVMS structures. */
+ static final int CONSTANT_NAME_AND_TYPE_TAG = 12;
+
+ /** The tag value of CONSTANT_Utf8_info JVMS structures. */
+ static final int CONSTANT_UTF8_TAG = 1;
+
+ /** The tag value of CONSTANT_MethodHandle_info JVMS structures. */
+ static final int CONSTANT_METHOD_HANDLE_TAG = 15;
+
+ /** The tag value of CONSTANT_MethodType_info JVMS structures. */
+ static final int CONSTANT_METHOD_TYPE_TAG = 16;
+
+ /** The tag value of CONSTANT_Dynamic_info JVMS structures. */
+ static final int CONSTANT_DYNAMIC_TAG = 17;
+
+ /** The tag value of CONSTANT_InvokeDynamic_info JVMS structures. */
+ static final int CONSTANT_INVOKE_DYNAMIC_TAG = 18;
+
+ /** The tag value of CONSTANT_Module_info JVMS structures. */
+ static final int CONSTANT_MODULE_TAG = 19;
+
+ /** The tag value of CONSTANT_Package_info JVMS structures. */
+ static final int CONSTANT_PACKAGE_TAG = 20;
+
+ // Tag values for the BootstrapMethods attribute entries (ASM specific tag).
+
+ /** The tag value of the BootstrapMethods attribute entries. */
+ static final int BOOTSTRAP_METHOD_TAG = 64;
+
+ // Tag values for the type table entries (ASM specific tags).
+
+ /** The tag value of a normal type entry in the (ASM specific) type table of a class. */
+ static final int TYPE_TAG = 128;
+
+ /**
+ * The tag value of an {@link Frame#ITEM_UNINITIALIZED} type entry in the type table of a class.
+ */
+ static final int UNINITIALIZED_TYPE_TAG = 129;
+
+ /** The tag value of a merged type entry in the (ASM specific) type table of a class. */
+ static final int MERGED_TYPE_TAG = 130;
+
+ // Instance fields.
+
+ /**
+ * The index of this symbol in the constant pool, in the BootstrapMethods attribute, or in the
+ * (ASM specific) type table of a class (depending on the {@link #tag} value).
+ */
+ final int index;
+
+ /**
+ * A tag indicating the type of this symbol. Must be one of the static tag values defined in this
+ * class.
+ */
+ final int tag;
+
+ /**
+ * The internal name of the owner class of this symbol. Only used for {@link
+ * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link
+ * #CONSTANT_INTERFACE_METHODREF_TAG}, and {@link #CONSTANT_METHOD_HANDLE_TAG} symbols.
+ */
+ final String owner;
+
+ /**
+ * The name of the class field or method corresponding to this symbol. Only used for {@link
+ * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link
+ * #CONSTANT_INTERFACE_METHODREF_TAG}, {@link #CONSTANT_NAME_AND_TYPE_TAG}, {@link
+ * #CONSTANT_METHOD_HANDLE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols.
+ */
+ final String name;
+
+ /**
+ * The string value of this symbol. This is:
+ *
+ *
+ * - a field or method descriptor for {@link #CONSTANT_FIELDREF_TAG}, {@link
+ * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG}, {@link
+ * #CONSTANT_NAME_AND_TYPE_TAG}, {@link #CONSTANT_METHOD_HANDLE_TAG}, {@link
+ * #CONSTANT_METHOD_TYPE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols,
+ *
- an arbitrary string for {@link #CONSTANT_UTF8_TAG} and {@link #CONSTANT_STRING_TAG}
+ * symbols,
+ *
- an internal class name for {@link #CONSTANT_CLASS_TAG}, {@link #TYPE_TAG} and {@link
+ * #UNINITIALIZED_TYPE_TAG} symbols,
+ *
- {@literal null} for the other types of symbol.
+ *
+ */
+ final String value;
+
+ /**
+ * The numeric value of this symbol. This is:
+ *
+ *
+ * - the symbol's value for {@link #CONSTANT_INTEGER_TAG},{@link #CONSTANT_FLOAT_TAG}, {@link
+ * #CONSTANT_LONG_TAG}, {@link #CONSTANT_DOUBLE_TAG},
+ *
- the CONSTANT_MethodHandle_info reference_kind field value for {@link
+ * #CONSTANT_METHOD_HANDLE_TAG} symbols,
+ *
- the CONSTANT_InvokeDynamic_info bootstrap_method_attr_index field value for {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols,
+ *
- the offset of a bootstrap method in the BootstrapMethods boostrap_methods array, for
+ * {@link #CONSTANT_DYNAMIC_TAG} or {@link #BOOTSTRAP_METHOD_TAG} symbols,
+ *
- the bytecode offset of the NEW instruction that created an {@link
+ * Frame#ITEM_UNINITIALIZED} type for {@link #UNINITIALIZED_TYPE_TAG} symbols,
+ *
- the indices (in the class' type table) of two {@link #TYPE_TAG} source types for {@link
+ * #MERGED_TYPE_TAG} symbols,
+ *
- 0 for the other types of symbol.
+ *
+ */
+ final long data;
+
+ /**
+ * Additional information about this symbol, generally computed lazily. Warning: the value of
+ * this field is ignored when comparing Symbol instances (to avoid duplicate entries in a
+ * SymbolTable). Therefore, this field should only contain data that can be computed from the
+ * other fields of this class. It contains:
+ *
+ *
+ * - the {@link Type#getArgumentsAndReturnSizes} of the symbol's method descriptor for {@link
+ * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols,
+ *
- the index in the InnerClasses_attribute 'classes' array (plus one) corresponding to this
+ * class, for {@link #CONSTANT_CLASS_TAG} symbols,
+ *
- the index (in the class' type table) of the merged type of the two source types for
+ * {@link #MERGED_TYPE_TAG} symbols,
+ *
- 0 for the other types of symbol, or if this field has not been computed yet.
+ *
+ */
+ int info;
+
+ /**
+ * Constructs a new Symbol. This constructor can't be used directly because the Symbol class is
+ * abstract. Instead, use the factory methods of the {@link SymbolTable} class.
+ *
+ * @param index the symbol index in the constant pool, in the BootstrapMethods attribute, or in
+ * the (ASM specific) type table of a class (depending on 'tag').
+ * @param tag the symbol type. Must be one of the static tag values defined in this class.
+ * @param owner The internal name of the symbol's owner class. Maybe {@literal null}.
+ * @param name The name of the symbol's corresponding class field or method. Maybe {@literal
+ * null}.
+ * @param value The string value of this symbol. Maybe {@literal null}.
+ * @param data The numeric value of this symbol.
+ */
+ Symbol(
+ final int index,
+ final int tag,
+ final String owner,
+ final String name,
+ final String value,
+ final long data) {
+ this.index = index;
+ this.tag = tag;
+ this.owner = owner;
+ this.name = name;
+ this.value = value;
+ this.data = data;
+ }
+
+ /**
+ * Returns the result {@link Type#getArgumentsAndReturnSizes} on {@link #value}.
+ *
+ * @return the result {@link Type#getArgumentsAndReturnSizes} on {@link #value} (memoized in
+ * {@link #info} for efficiency). This should only be used for {@link
+ * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link
+ * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols.
+ */
+ int getArgumentsAndReturnSizes() {
+ if (info == 0) {
+ info = Type.getArgumentsAndReturnSizes(value);
+ }
+ return info;
+ }
+}
diff --git a/src/java/nginx/clojure/asm/SymbolTable.java b/src/java/nginx/clojure/asm/SymbolTable.java
new file mode 100644
index 00000000..c956510a
--- /dev/null
+++ b/src/java/nginx/clojure/asm/SymbolTable.java
@@ -0,0 +1,1322 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package nginx.clojure.asm;
+
+/**
+ * The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type
+ * table entries of a class.
+ *
+ * @author Eric Bruneton
+ * @see JVMS
+ * 4.4
+ * @see JVMS
+ * 4.7.23
+ */
+final class SymbolTable {
+
+ /**
+ * The ClassWriter to which this SymbolTable belongs. This is only used to get access to {@link
+ * ClassWriter#getCommonSuperClass} and to serialize custom attributes with {@link
+ * Attribute#write}.
+ */
+ final ClassWriter classWriter;
+
+ /**
+ * The ClassReader from which this SymbolTable was constructed, or {@literal null} if it was
+ * constructed from scratch.
+ */
+ private final ClassReader sourceClassReader;
+
+ /** The major version number of the class to which this symbol table belongs. */
+ private int majorVersion;
+
+ /** The internal name of the class to which this symbol table belongs. */
+ private String className;
+
+ /**
+ * The total number of {@link Entry} instances in {@link #entries}. This includes entries that are
+ * accessible (recursively) via {@link Entry#next}.
+ */
+ private int entryCount;
+
+ /**
+ * A hash set of all the entries in this SymbolTable (this includes the constant pool entries, the
+ * bootstrap method entries and the type table entries). Each {@link Entry} instance is stored at
+ * the array index given by its hash code modulo the array size. If several entries must be stored
+ * at the same array index, they are linked together via their {@link Entry#next} field. The
+ * factory methods of this class make sure that this table does not contain duplicated entries.
+ */
+ private Entry[] entries;
+
+ /**
+ * The number of constant pool items in {@link #constantPool}, plus 1. The first constant pool
+ * item has index 1, and long and double items count for two items.
+ */
+ private int constantPoolCount;
+
+ /**
+ * The content of the ClassFile's constant_pool JVMS structure corresponding to this SymbolTable.
+ * The ClassFile's constant_pool_count field is not included.
+ */
+ private ByteVector constantPool;
+
+ /**
+ * The number of bootstrap methods in {@link #bootstrapMethods}. Corresponds to the
+ * BootstrapMethods_attribute's num_bootstrap_methods field value.
+ */
+ private int bootstrapMethodCount;
+
+ /**
+ * The content of the BootstrapMethods attribute 'bootstrap_methods' array corresponding to this
+ * SymbolTable. Note that the first 6 bytes of the BootstrapMethods_attribute, and its
+ * num_bootstrap_methods field, are not included.
+ */
+ private ByteVector bootstrapMethods;
+
+ /**
+ * The actual number of elements in {@link #typeTable}. These elements are stored from index 0 to
+ * typeCount (excluded). The other array entries are empty.
+ */
+ private int typeCount;
+
+ /**
+ * An ASM specific type table used to temporarily store internal names that will not necessarily
+ * be stored in the constant pool. This type table is used by the control flow and data flow
+ * analysis algorithm used to compute stack map frames from scratch. This array stores {@link
+ * Symbol#TYPE_TAG} and {@link Symbol#UNINITIALIZED_TYPE_TAG}) Symbol. The type symbol at index
+ * {@code i} has its {@link Symbol#index} equal to {@code i} (and vice versa).
+ */
+ private Entry[] typeTable;
+
+ /**
+ * Constructs a new, empty SymbolTable for the given ClassWriter.
+ *
+ * @param classWriter a ClassWriter.
+ */
+ SymbolTable(final ClassWriter classWriter) {
+ this.classWriter = classWriter;
+ this.sourceClassReader = null;
+ this.entries = new Entry[256];
+ this.constantPoolCount = 1;
+ this.constantPool = new ByteVector();
+ }
+
+ /**
+ * Constructs a new SymbolTable for the given ClassWriter, initialized with the constant pool and
+ * bootstrap methods of the given ClassReader.
+ *
+ * @param classWriter a ClassWriter.
+ * @param classReader the ClassReader whose constant pool and bootstrap methods must be copied to
+ * initialize the SymbolTable.
+ */
+ SymbolTable(final ClassWriter classWriter, final ClassReader classReader) {
+ this.classWriter = classWriter;
+ this.sourceClassReader = classReader;
+
+ // Copy the constant pool binary content.
+ byte[] inputBytes = classReader.classFileBuffer;
+ int constantPoolOffset = classReader.getItem(1) - 1;
+ int constantPoolLength = classReader.header - constantPoolOffset;
+ constantPoolCount = classReader.getItemCount();
+ constantPool = new ByteVector(constantPoolLength);
+ constantPool.putByteArray(inputBytes, constantPoolOffset, constantPoolLength);
+
+ // Add the constant pool items in the symbol table entries. Reserve enough space in 'entries' to
+ // avoid too many hash set collisions (entries is not dynamically resized by the addConstant*
+ // method calls below), and to account for bootstrap method entries.
+ entries = new Entry[constantPoolCount * 2];
+ char[] charBuffer = new char[classReader.getMaxStringLength()];
+ boolean hasBootstrapMethods = false;
+ int itemIndex = 1;
+ while (itemIndex < constantPoolCount) {
+ int itemOffset = classReader.getItem(itemIndex);
+ int itemTag = inputBytes[itemOffset - 1];
+ int nameAndTypeItemOffset;
+ switch (itemTag) {
+ case Symbol.CONSTANT_FIELDREF_TAG:
+ case Symbol.CONSTANT_METHODREF_TAG:
+ case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
+ nameAndTypeItemOffset =
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
+ addConstantMemberReference(
+ itemIndex,
+ itemTag,
+ classReader.readClass(itemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer));
+ break;
+ case Symbol.CONSTANT_INTEGER_TAG:
+ case Symbol.CONSTANT_FLOAT_TAG:
+ addConstantIntegerOrFloat(itemIndex, itemTag, classReader.readInt(itemOffset));
+ break;
+ case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
+ addConstantNameAndType(
+ itemIndex,
+ classReader.readUTF8(itemOffset, charBuffer),
+ classReader.readUTF8(itemOffset + 2, charBuffer));
+ break;
+ case Symbol.CONSTANT_LONG_TAG:
+ case Symbol.CONSTANT_DOUBLE_TAG:
+ addConstantLongOrDouble(itemIndex, itemTag, classReader.readLong(itemOffset));
+ break;
+ case Symbol.CONSTANT_UTF8_TAG:
+ addConstantUtf8(itemIndex, classReader.readUtf(itemIndex, charBuffer));
+ break;
+ case Symbol.CONSTANT_METHOD_HANDLE_TAG:
+ int memberRefItemOffset =
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 1));
+ nameAndTypeItemOffset =
+ classReader.getItem(classReader.readUnsignedShort(memberRefItemOffset + 2));
+ addConstantMethodHandle(
+ itemIndex,
+ classReader.readByte(itemOffset),
+ classReader.readClass(memberRefItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer));
+ break;
+ case Symbol.CONSTANT_DYNAMIC_TAG:
+ case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
+ hasBootstrapMethods = true;
+ nameAndTypeItemOffset =
+ classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
+ addConstantDynamicOrInvokeDynamicReference(
+ itemTag,
+ itemIndex,
+ classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
+ classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer),
+ classReader.readUnsignedShort(itemOffset));
+ break;
+ case Symbol.CONSTANT_STRING_TAG:
+ case Symbol.CONSTANT_CLASS_TAG:
+ case Symbol.CONSTANT_METHOD_TYPE_TAG:
+ case Symbol.CONSTANT_MODULE_TAG:
+ case Symbol.CONSTANT_PACKAGE_TAG:
+ addConstantUtf8Reference(
+ itemIndex, itemTag, classReader.readUTF8(itemOffset, charBuffer));
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ itemIndex +=
+ (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1;
+ }
+
+ // Copy the BootstrapMethods, if any.
+ if (hasBootstrapMethods) {
+ copyBootstrapMethods(classReader, charBuffer);
+ }
+ }
+
+ /**
+ * Read the BootstrapMethods 'bootstrap_methods' array binary content and add them as entries of
+ * the SymbolTable.
+ *
+ * @param classReader the ClassReader whose bootstrap methods must be copied to initialize the
+ * SymbolTable.
+ * @param charBuffer a buffer used to read strings in the constant pool.
+ */
+ private void copyBootstrapMethods(final ClassReader classReader, final char[] charBuffer) {
+ // Find attributOffset of the 'bootstrap_methods' array.
+ byte[] inputBytes = classReader.classFileBuffer;
+ int currentAttributeOffset = classReader.getFirstAttributeOffset();
+ for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
+ String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer);
+ if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
+ bootstrapMethodCount = classReader.readUnsignedShort(currentAttributeOffset + 6);
+ break;
+ }
+ currentAttributeOffset += 6 + classReader.readInt(currentAttributeOffset + 2);
+ }
+ if (bootstrapMethodCount > 0) {
+ // Compute the offset and the length of the BootstrapMethods 'bootstrap_methods' array.
+ int bootstrapMethodsOffset = currentAttributeOffset + 8;
+ int bootstrapMethodsLength = classReader.readInt(currentAttributeOffset + 2) - 2;
+ bootstrapMethods = new ByteVector(bootstrapMethodsLength);
+ bootstrapMethods.putByteArray(inputBytes, bootstrapMethodsOffset, bootstrapMethodsLength);
+
+ // Add each bootstrap method in the symbol table entries.
+ int currentOffset = bootstrapMethodsOffset;
+ for (int i = 0; i < bootstrapMethodCount; i++) {
+ int offset = currentOffset - bootstrapMethodsOffset;
+ int bootstrapMethodRef = classReader.readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ int numBootstrapArguments = classReader.readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ int hashCode = classReader.readConst(bootstrapMethodRef, charBuffer).hashCode();
+ while (numBootstrapArguments-- > 0) {
+ int bootstrapArgument = classReader.readUnsignedShort(currentOffset);
+ currentOffset += 2;
+ hashCode ^= classReader.readConst(bootstrapArgument, charBuffer).hashCode();
+ }
+ add(new Entry(i, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode & 0x7FFFFFFF));
+ }
+ }
+ }
+
+ /**
+ * Returns the ClassReader from which this SymbolTable was constructed.
+ *
+ * @return the ClassReader from which this SymbolTable was constructed, or {@literal null} if it
+ * was constructed from scratch.
+ */
+ ClassReader getSource() {
+ return sourceClassReader;
+ }
+
+ /**
+ * Returns the major version of the class to which this symbol table belongs.
+ *
+ * @return the major version of the class to which this symbol table belongs.
+ */
+ int getMajorVersion() {
+ return majorVersion;
+ }
+
+ /**
+ * Returns the internal name of the class to which this symbol table belongs.
+ *
+ * @return the internal name of the class to which this symbol table belongs.
+ */
+ String getClassName() {
+ return className;
+ }
+
+ /**
+ * Sets the major version and the name of the class to which this symbol table belongs. Also adds
+ * the class name to the constant pool.
+ *
+ * @param majorVersion a major ClassFile version number.
+ * @param className an internal class name.
+ * @return the constant pool index of a new or already existing Symbol with the given class name.
+ */
+ int setMajorVersionAndClassName(final int majorVersion, final String className) {
+ this.majorVersion = majorVersion;
+ this.className = className;
+ return addConstantClass(className).index;
+ }
+
+ /**
+ * Returns the number of items in this symbol table's constant_pool array (plus 1).
+ *
+ * @return the number of items in this symbol table's constant_pool array (plus 1).
+ */
+ int getConstantPoolCount() {
+ return constantPoolCount;
+ }
+
+ /**
+ * Returns the length in bytes of this symbol table's constant_pool array.
+ *
+ * @return the length in bytes of this symbol table's constant_pool array.
+ */
+ int getConstantPoolLength() {
+ return constantPool.length;
+ }
+
+ /**
+ * Puts this symbol table's constant_pool array in the given ByteVector, preceded by the
+ * constant_pool_count value.
+ *
+ * @param output where the JVMS ClassFile's constant_pool array must be put.
+ */
+ void putConstantPool(final ByteVector output) {
+ output.putShort(constantPoolCount).putByteArray(constantPool.data, 0, constantPool.length);
+ }
+
+ /**
+ * Returns the size in bytes of this symbol table's BootstrapMethods attribute. Also adds the
+ * attribute name in the constant pool.
+ *
+ * @return the size in bytes of this symbol table's BootstrapMethods attribute.
+ */
+ int computeBootstrapMethodsSize() {
+ if (bootstrapMethods != null) {
+ addConstantUtf8(Constants.BOOTSTRAP_METHODS);
+ return 8 + bootstrapMethods.length;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Puts this symbol table's BootstrapMethods attribute in the given ByteVector. This includes the
+ * 6 attribute header bytes and the num_bootstrap_methods value.
+ *
+ * @param output where the JVMS BootstrapMethods attribute must be put.
+ */
+ void putBootstrapMethods(final ByteVector output) {
+ if (bootstrapMethods != null) {
+ output
+ .putShort(addConstantUtf8(Constants.BOOTSTRAP_METHODS))
+ .putInt(bootstrapMethods.length + 2)
+ .putShort(bootstrapMethodCount)
+ .putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Generic symbol table entries management.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the list of entries which can potentially have the given hash code.
+ *
+ * @param hashCode a {@link Entry#hashCode} value.
+ * @return the list of entries which can potentially have the given hash code. The list is stored
+ * via the {@link Entry#next} field.
+ */
+ private Entry get(final int hashCode) {
+ return entries[hashCode % entries.length];
+ }
+
+ /**
+ * Puts the given entry in the {@link #entries} hash set. This method does not check
+ * whether {@link #entries} already contains a similar entry or not. {@link #entries} is resized
+ * if necessary to avoid hash collisions (multiple entries needing to be stored at the same {@link
+ * #entries} array index) as much as possible, with reasonable memory usage.
+ *
+ * @param entry an Entry (which must not already be contained in {@link #entries}).
+ * @return the given entry
+ */
+ private Entry put(final Entry entry) {
+ if (entryCount > (entries.length * 3) / 4) {
+ int currentCapacity = entries.length;
+ int newCapacity = currentCapacity * 2 + 1;
+ Entry[] newEntries = new Entry[newCapacity];
+ for (int i = currentCapacity - 1; i >= 0; --i) {
+ Entry currentEntry = entries[i];
+ while (currentEntry != null) {
+ int newCurrentEntryIndex = currentEntry.hashCode % newCapacity;
+ Entry nextEntry = currentEntry.next;
+ currentEntry.next = newEntries[newCurrentEntryIndex];
+ newEntries[newCurrentEntryIndex] = currentEntry;
+ currentEntry = nextEntry;
+ }
+ }
+ entries = newEntries;
+ }
+ entryCount++;
+ int index = entry.hashCode % entries.length;
+ entry.next = entries[index];
+ return entries[index] = entry;
+ }
+
+ /**
+ * Adds the given entry in the {@link #entries} hash set. This method does not check
+ * whether {@link #entries} already contains a similar entry or not, and does not resize
+ * {@link #entries} if necessary.
+ *
+ * @param entry an Entry (which must not already be contained in {@link #entries}).
+ */
+ private void add(final Entry entry) {
+ entryCount++;
+ int index = entry.hashCode % entries.length;
+ entry.next = entries[index];
+ entries[index] = entry;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Constant pool entries management.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a number or string constant to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value the value of the constant to be added to the constant pool. This parameter must be
+ * an {@link Integer}, {@link Byte}, {@link Character}, {@link Short}, {@link Boolean}, {@link
+ * Float}, {@link Long}, {@link Double}, {@link String}, {@link Type} or {@link Handle}.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstant(final Object value) {
+ if (value instanceof Integer) {
+ return addConstantInteger(((Integer) value).intValue());
+ } else if (value instanceof Byte) {
+ return addConstantInteger(((Byte) value).intValue());
+ } else if (value instanceof Character) {
+ return addConstantInteger(((Character) value).charValue());
+ } else if (value instanceof Short) {
+ return addConstantInteger(((Short) value).intValue());
+ } else if (value instanceof Boolean) {
+ return addConstantInteger(((Boolean) value).booleanValue() ? 1 : 0);
+ } else if (value instanceof Float) {
+ return addConstantFloat(((Float) value).floatValue());
+ } else if (value instanceof Long) {
+ return addConstantLong(((Long) value).longValue());
+ } else if (value instanceof Double) {
+ return addConstantDouble(((Double) value).doubleValue());
+ } else if (value instanceof String) {
+ return addConstantString((String) value);
+ } else if (value instanceof Type) {
+ Type type = (Type) value;
+ int typeSort = type.getSort();
+ if (typeSort == Type.OBJECT) {
+ return addConstantClass(type.getInternalName());
+ } else if (typeSort == Type.METHOD) {
+ return addConstantMethodType(type.getDescriptor());
+ } else { // type is a primitive or array type.
+ return addConstantClass(type.getDescriptor());
+ }
+ } else if (value instanceof Handle) {
+ Handle handle = (Handle) value;
+ return addConstantMethodHandle(
+ handle.getTag(),
+ handle.getOwner(),
+ handle.getName(),
+ handle.getDesc(),
+ handle.isInterface());
+ } else if (value instanceof ConstantDynamic) {
+ ConstantDynamic constantDynamic = (ConstantDynamic) value;
+ return addConstantDynamic(
+ constantDynamic.getName(),
+ constantDynamic.getDescriptor(),
+ constantDynamic.getBootstrapMethod(),
+ constantDynamic.getBootstrapMethodArgumentsUnsafe());
+ } else {
+ throw new IllegalArgumentException("value " + value);
+ }
+ }
+
+ /**
+ * Adds a CONSTANT_Class_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value the internal name of a class.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantClass(final String value) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_CLASS_TAG, value);
+ }
+
+ /**
+ * Adds a CONSTANT_Fieldref_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param owner the internal name of a class.
+ * @param name a field name.
+ * @param descriptor a field descriptor.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantFieldref(final String owner, final String name, final String descriptor) {
+ return addConstantMemberReference(Symbol.CONSTANT_FIELDREF_TAG, owner, name, descriptor);
+ }
+
+ /**
+ * Adds a CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to the constant pool of this
+ * symbol table. Does nothing if the constant pool already contains a similar item.
+ *
+ * @param owner the internal name of a class.
+ * @param name a method name.
+ * @param descriptor a method descriptor.
+ * @param isInterface whether owner is an interface or not.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantMethodref(
+ final String owner, final String name, final String descriptor, final boolean isInterface) {
+ int tag = isInterface ? Symbol.CONSTANT_INTERFACE_METHODREF_TAG : Symbol.CONSTANT_METHODREF_TAG;
+ return addConstantMemberReference(tag, owner, name, descriptor);
+ }
+
+ /**
+ * Adds a CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to
+ * the constant pool of this symbol table. Does nothing if the constant pool already contains a
+ * similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG}
+ * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}.
+ * @param owner the internal name of a class.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ * @return a new or already existing Symbol with the given value.
+ */
+ private Entry addConstantMemberReference(
+ final int tag, final String owner, final String name, final String descriptor) {
+ int hashCode = hash(tag, owner, name, descriptor);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag
+ && entry.hashCode == hashCode
+ && entry.owner.equals(owner)
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ constantPool.put122(
+ tag, addConstantClass(owner).index, addConstantNameAndType(name, descriptor));
+ return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, 0, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info
+ * to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG}
+ * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}.
+ * @param owner the internal name of a class.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ */
+ private void addConstantMemberReference(
+ final int index,
+ final int tag,
+ final String owner,
+ final String name,
+ final String descriptor) {
+ add(new Entry(index, tag, owner, name, descriptor, 0, hash(tag, owner, name, descriptor)));
+ }
+
+ /**
+ * Adds a CONSTANT_String_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a string.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantString(final String value) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_STRING_TAG, value);
+ }
+
+ /**
+ * Adds a CONSTANT_Integer_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value an int.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantInteger(final int value) {
+ return addConstantIntegerOrFloat(Symbol.CONSTANT_INTEGER_TAG, value);
+ }
+
+ /**
+ * Adds a CONSTANT_Float_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a float.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantFloat(final float value) {
+ return addConstantIntegerOrFloat(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value));
+ }
+
+ /**
+ * Adds a CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol table.
+ * Does nothing if the constant pool already contains a similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}.
+ * @param value an int or float.
+ * @return a constant pool constant with the given tag and primitive values.
+ */
+ private Symbol addConstantIntegerOrFloat(final int tag, final int value) {
+ int hashCode = hash(tag, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ constantPool.putByte(tag).putInt(value);
+ return put(new Entry(constantPoolCount++, tag, value, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol
+ * table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}.
+ * @param value an int or float.
+ */
+ private void addConstantIntegerOrFloat(final int index, final int tag, final int value) {
+ add(new Entry(index, tag, value, hash(tag, value)));
+ }
+
+ /**
+ * Adds a CONSTANT_Long_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a long.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantLong(final long value) {
+ return addConstantLongOrDouble(Symbol.CONSTANT_LONG_TAG, value);
+ }
+
+ /**
+ * Adds a CONSTANT_Double_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a double.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantDouble(final double value) {
+ return addConstantLongOrDouble(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value));
+ }
+
+ /**
+ * Adds a CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol table.
+ * Does nothing if the constant pool already contains a similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}.
+ * @param value a long or double.
+ * @return a constant pool constant with the given tag and primitive values.
+ */
+ private Symbol addConstantLongOrDouble(final int tag, final long value) {
+ int hashCode = hash(tag, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ int index = constantPoolCount;
+ constantPool.putByte(tag).putLong(value);
+ constantPoolCount += 2;
+ return put(new Entry(index, tag, value, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol
+ * table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}.
+ * @param value a long or double.
+ */
+ private void addConstantLongOrDouble(final int index, final int tag, final long value) {
+ add(new Entry(index, tag, value, hash(tag, value)));
+ }
+
+ /**
+ * Adds a CONSTANT_NameAndType_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ * @return a new or already existing Symbol with the given value.
+ */
+ int addConstantNameAndType(final String name, final String descriptor) {
+ final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG;
+ int hashCode = hash(tag, name, descriptor);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag
+ && entry.hashCode == hashCode
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ constantPool.put122(tag, addConstantUtf8(name), addConstantUtf8(descriptor));
+ return put(new Entry(constantPoolCount++, tag, name, descriptor, hashCode)).index;
+ }
+
+ /**
+ * Adds a new CONSTANT_NameAndType_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ */
+ private void addConstantNameAndType(final int index, final String name, final String descriptor) {
+ final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG;
+ add(new Entry(index, tag, name, descriptor, hash(tag, name, descriptor)));
+ }
+
+ /**
+ * Adds a CONSTANT_Utf8_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param value a string.
+ * @return a new or already existing Symbol with the given value.
+ */
+ int addConstantUtf8(final String value) {
+ int hashCode = hash(Symbol.CONSTANT_UTF8_TAG, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.CONSTANT_UTF8_TAG
+ && entry.hashCode == hashCode
+ && entry.value.equals(value)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ constantPool.putByte(Symbol.CONSTANT_UTF8_TAG).putUTF8(value);
+ return put(new Entry(constantPoolCount++, Symbol.CONSTANT_UTF8_TAG, value, hashCode)).index;
+ }
+
+ /**
+ * Adds a new CONSTANT_String_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param value a string.
+ */
+ private void addConstantUtf8(final int index, final String value) {
+ add(new Entry(index, Symbol.CONSTANT_UTF8_TAG, value, hash(Symbol.CONSTANT_UTF8_TAG, value)));
+ }
+
+ /**
+ * Adds a CONSTANT_MethodHandle_info to the constant pool of this symbol table. Does nothing if
+ * the constant pool already contains a similar item.
+ *
+ * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link
+ * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link
+ * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link
+ * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of a class of interface.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ * @param isInterface whether owner is an interface or not.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantMethodHandle(
+ final int referenceKind,
+ final String owner,
+ final String name,
+ final String descriptor,
+ final boolean isInterface) {
+ final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG;
+ // Note that we don't need to include isInterface in the hash computation, because it is
+ // redundant with owner (we can't have the same owner with different isInterface values).
+ int hashCode = hash(tag, owner, name, descriptor, referenceKind);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag
+ && entry.hashCode == hashCode
+ && entry.data == referenceKind
+ && entry.owner.equals(owner)
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ if (referenceKind <= Opcodes.H_PUTSTATIC) {
+ constantPool.put112(tag, referenceKind, addConstantFieldref(owner, name, descriptor).index);
+ } else {
+ constantPool.put112(
+ tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index);
+ }
+ return put(
+ new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_MethodHandle_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link
+ * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link
+ * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link
+ * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner the internal name of a class of interface.
+ * @param name a field or method name.
+ * @param descriptor a field or method descriptor.
+ */
+ private void addConstantMethodHandle(
+ final int index,
+ final int referenceKind,
+ final String owner,
+ final String name,
+ final String descriptor) {
+ final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG;
+ int hashCode = hash(tag, owner, name, descriptor, referenceKind);
+ add(new Entry(index, tag, owner, name, descriptor, referenceKind, hashCode));
+ }
+
+ /**
+ * Adds a CONSTANT_MethodType_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantMethodType(final String methodDescriptor) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_METHOD_TYPE_TAG, methodDescriptor);
+ }
+
+ /**
+ * Adds a CONSTANT_Dynamic_info to the constant pool of this symbol table. Also adds the related
+ * bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the constant
+ * pool already contains a similar item.
+ *
+ * @param name a method name.
+ * @param descriptor a field descriptor.
+ * @param bootstrapMethodHandle a bootstrap method handle.
+ * @param bootstrapMethodArguments the bootstrap method arguments.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments);
+ return addConstantDynamicOrInvokeDynamicReference(
+ Symbol.CONSTANT_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index);
+ }
+
+ /**
+ * Adds a CONSTANT_InvokeDynamic_info to the constant pool of this symbol table. Also adds the
+ * related bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param name a method name.
+ * @param descriptor a method descriptor.
+ * @param bootstrapMethodHandle a bootstrap method handle.
+ * @param bootstrapMethodArguments the bootstrap method arguments.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantInvokeDynamic(
+ final String name,
+ final String descriptor,
+ final Handle bootstrapMethodHandle,
+ final Object... bootstrapMethodArguments) {
+ Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments);
+ return addConstantDynamicOrInvokeDynamicReference(
+ Symbol.CONSTANT_INVOKE_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index);
+ }
+
+ /**
+ * Adds a CONSTANT_Dynamic or a CONSTANT_InvokeDynamic_info to the constant pool of this symbol
+ * table. Does nothing if the constant pool already contains a similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link
+ * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}.
+ * @param name a method name.
+ * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG) or a method descriptor for
+ * CONSTANT_INVOKE_DYNAMIC_TAG.
+ * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute.
+ * @return a new or already existing Symbol with the given value.
+ */
+ private Symbol addConstantDynamicOrInvokeDynamicReference(
+ final int tag, final String name, final String descriptor, final int bootstrapMethodIndex) {
+ int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag
+ && entry.hashCode == hashCode
+ && entry.data == bootstrapMethodIndex
+ && entry.name.equals(name)
+ && entry.value.equals(descriptor)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ constantPool.put122(tag, bootstrapMethodIndex, addConstantNameAndType(name, descriptor));
+ return put(
+ new Entry(
+ constantPoolCount++, tag, null, name, descriptor, bootstrapMethodIndex, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Dynamic_info or CONSTANT_InvokeDynamic_info to the constant pool of this
+ * symbol table.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link
+ * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}.
+ * @param index the constant pool index of the new Symbol.
+ * @param name a method name.
+ * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG or a method descriptor for
+ * CONSTANT_INVOKE_DYNAMIC_TAG.
+ * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute.
+ */
+ private void addConstantDynamicOrInvokeDynamicReference(
+ final int tag,
+ final int index,
+ final String name,
+ final String descriptor,
+ final int bootstrapMethodIndex) {
+ int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex);
+ add(new Entry(index, tag, null, name, descriptor, bootstrapMethodIndex, hashCode));
+ }
+
+ /**
+ * Adds a CONSTANT_Module_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param moduleName a fully qualified name (using dots) of a module.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantModule(final String moduleName) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_MODULE_TAG, moduleName);
+ }
+
+ /**
+ * Adds a CONSTANT_Package_info to the constant pool of this symbol table. Does nothing if the
+ * constant pool already contains a similar item.
+ *
+ * @param packageName the internal name of a package.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addConstantPackage(final String packageName) {
+ return addConstantUtf8Reference(Symbol.CONSTANT_PACKAGE_TAG, packageName);
+ }
+
+ /**
+ * Adds a CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info,
+ * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. Does
+ * nothing if the constant pool already contains a similar item.
+ *
+ * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link
+ * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link
+ * Symbol#CONSTANT_PACKAGE_TAG}.
+ * @param value an internal class name, an arbitrary string, a method descriptor, a module or a
+ * package name, depending on tag.
+ * @return a new or already existing Symbol with the given value.
+ */
+ private Symbol addConstantUtf8Reference(final int tag, final String value) {
+ int hashCode = hash(tag, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == tag && entry.hashCode == hashCode && entry.value.equals(value)) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ constantPool.put12(tag, addConstantUtf8(value));
+ return put(new Entry(constantPoolCount++, tag, value, hashCode));
+ }
+
+ /**
+ * Adds a new CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info,
+ * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table.
+ *
+ * @param index the constant pool index of the new Symbol.
+ * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link
+ * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link
+ * Symbol#CONSTANT_PACKAGE_TAG}.
+ * @param value an internal class name, an arbitrary string, a method descriptor, a module or a
+ * package name, depending on tag.
+ */
+ private void addConstantUtf8Reference(final int index, final int tag, final String value) {
+ add(new Entry(index, tag, value, hash(tag, value)));
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Bootstrap method entries management.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if
+ * the BootstrapMethods already contains a similar bootstrap method.
+ *
+ * @param bootstrapMethodHandle a bootstrap method handle.
+ * @param bootstrapMethodArguments the bootstrap method arguments.
+ * @return a new or already existing Symbol with the given value.
+ */
+ Symbol addBootstrapMethod(
+ final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
+ ByteVector bootstrapMethodsAttribute = bootstrapMethods;
+ if (bootstrapMethodsAttribute == null) {
+ bootstrapMethodsAttribute = bootstrapMethods = new ByteVector();
+ }
+
+ // The bootstrap method arguments can be Constant_Dynamic values, which reference other
+ // bootstrap methods. We must therefore add the bootstrap method arguments to the constant pool
+ // and BootstrapMethods attribute first, so that the BootstrapMethods attribute is not modified
+ // while adding the given bootstrap method to it, in the rest of this method.
+ int numBootstrapArguments = bootstrapMethodArguments.length;
+ int[] bootstrapMethodArgumentIndexes = new int[numBootstrapArguments];
+ for (int i = 0; i < numBootstrapArguments; i++) {
+ bootstrapMethodArgumentIndexes[i] = addConstant(bootstrapMethodArguments[i]).index;
+ }
+
+ // Write the bootstrap method in the BootstrapMethods table. This is necessary to be able to
+ // compare it with existing ones, and will be reverted below if there is already a similar
+ // bootstrap method.
+ int bootstrapMethodOffset = bootstrapMethodsAttribute.length;
+ bootstrapMethodsAttribute.putShort(
+ addConstantMethodHandle(
+ bootstrapMethodHandle.getTag(),
+ bootstrapMethodHandle.getOwner(),
+ bootstrapMethodHandle.getName(),
+ bootstrapMethodHandle.getDesc(),
+ bootstrapMethodHandle.isInterface())
+ .index);
+
+ bootstrapMethodsAttribute.putShort(numBootstrapArguments);
+ for (int i = 0; i < numBootstrapArguments; i++) {
+ bootstrapMethodsAttribute.putShort(bootstrapMethodArgumentIndexes[i]);
+ }
+
+ // Compute the length and the hash code of the bootstrap method.
+ int bootstrapMethodlength = bootstrapMethodsAttribute.length - bootstrapMethodOffset;
+ int hashCode = bootstrapMethodHandle.hashCode();
+ for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
+ hashCode ^= bootstrapMethodArgument.hashCode();
+ }
+ hashCode &= 0x7FFFFFFF;
+
+ // Add the bootstrap method to the symbol table or revert the above changes.
+ return addBootstrapMethod(bootstrapMethodOffset, bootstrapMethodlength, hashCode);
+ }
+
+ /**
+ * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if
+ * the BootstrapMethods already contains a similar bootstrap method (more precisely, reverts the
+ * content of {@link #bootstrapMethods} to remove the last, duplicate bootstrap method).
+ *
+ * @param offset the offset of the last bootstrap method in {@link #bootstrapMethods}, in bytes.
+ * @param length the length of this bootstrap method in {@link #bootstrapMethods}, in bytes.
+ * @param hashCode the hash code of this bootstrap method.
+ * @return a new or already existing Symbol with the given value.
+ */
+ private Symbol addBootstrapMethod(final int offset, final int length, final int hashCode) {
+ final byte[] bootstrapMethodsData = bootstrapMethods.data;
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.BOOTSTRAP_METHOD_TAG && entry.hashCode == hashCode) {
+ int otherOffset = (int) entry.data;
+ boolean isSameBootstrapMethod = true;
+ for (int i = 0; i < length; ++i) {
+ if (bootstrapMethodsData[offset + i] != bootstrapMethodsData[otherOffset + i]) {
+ isSameBootstrapMethod = false;
+ break;
+ }
+ }
+ if (isSameBootstrapMethod) {
+ bootstrapMethods.length = offset; // Revert to old position.
+ return entry;
+ }
+ }
+ entry = entry.next;
+ }
+ return put(new Entry(bootstrapMethodCount++, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode));
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Type table entries management.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the type table element whose index is given.
+ *
+ * @param typeIndex a type table index.
+ * @return the type table element whose index is given.
+ */
+ Symbol getType(final int typeIndex) {
+ return typeTable[typeIndex];
+ }
+
+ /**
+ * Adds a type in the type table of this symbol table. Does nothing if the type table already
+ * contains a similar type.
+ *
+ * @param value an internal class name.
+ * @return the index of a new or already existing type Symbol with the given value.
+ */
+ int addType(final String value) {
+ int hashCode = hash(Symbol.TYPE_TAG, value);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.TYPE_TAG && entry.hashCode == hashCode && entry.value.equals(value)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ return addTypeInternal(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode));
+ }
+
+ /**
+ * Adds an {@link Frame#ITEM_UNINITIALIZED} type in the type table of this symbol table. Does
+ * nothing if the type table already contains a similar type.
+ *
+ * @param value an internal class name.
+ * @param bytecodeOffset the bytecode offset of the NEW instruction that created this {@link
+ * Frame#ITEM_UNINITIALIZED} type value.
+ * @return the index of a new or already existing type Symbol with the given value.
+ */
+ int addUninitializedType(final String value, final int bytecodeOffset) {
+ int hashCode = hash(Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.UNINITIALIZED_TYPE_TAG
+ && entry.hashCode == hashCode
+ && entry.data == bytecodeOffset
+ && entry.value.equals(value)) {
+ return entry.index;
+ }
+ entry = entry.next;
+ }
+ return addTypeInternal(
+ new Entry(typeCount, Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset, hashCode));
+ }
+
+ /**
+ * Adds a merged type in the type table of this symbol table. Does nothing if the type table
+ * already contains a similar type.
+ *
+ * @param typeTableIndex1 a {@link Symbol#TYPE_TAG} type, specified by its index in the type
+ * table.
+ * @param typeTableIndex2 another {@link Symbol#TYPE_TAG} type, specified by its index in the type
+ * table.
+ * @return the index of a new or already existing {@link Symbol#TYPE_TAG} type Symbol,
+ * corresponding to the common super class of the given types.
+ */
+ int addMergedType(final int typeTableIndex1, final int typeTableIndex2) {
+ long data =
+ typeTableIndex1 < typeTableIndex2
+ ? typeTableIndex1 | (((long) typeTableIndex2) << 32)
+ : typeTableIndex2 | (((long) typeTableIndex1) << 32);
+ int hashCode = hash(Symbol.MERGED_TYPE_TAG, typeTableIndex1 + typeTableIndex2);
+ Entry entry = get(hashCode);
+ while (entry != null) {
+ if (entry.tag == Symbol.MERGED_TYPE_TAG && entry.hashCode == hashCode && entry.data == data) {
+ return entry.info;
+ }
+ entry = entry.next;
+ }
+ String type1 = typeTable[typeTableIndex1].value;
+ String type2 = typeTable[typeTableIndex2].value;
+ int commonSuperTypeIndex = addType(classWriter.getCommonSuperClass(type1, type2));
+ put(new Entry(typeCount, Symbol.MERGED_TYPE_TAG, data, hashCode)).info = commonSuperTypeIndex;
+ return commonSuperTypeIndex;
+ }
+
+ /**
+ * Adds the given type Symbol to {@link #typeTable}.
+ *
+ * @param entry a {@link Symbol#TYPE_TAG} or {@link Symbol#UNINITIALIZED_TYPE_TAG} type symbol.
+ * The index of this Symbol must be equal to the current value of {@link #typeCount}.
+ * @return the index in {@link #typeTable} where the given type was added, which is also equal to
+ * entry's index by hypothesis.
+ */
+ private int addTypeInternal(final Entry entry) {
+ if (typeTable == null) {
+ typeTable = new Entry[16];
+ }
+ if (typeCount == typeTable.length) {
+ Entry[] newTypeTable = new Entry[2 * typeTable.length];
+ System.arraycopy(typeTable, 0, newTypeTable, 0, typeTable.length);
+ typeTable = newTypeTable;
+ }
+ typeTable[typeCount++] = entry;
+ return put(entry).index;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Static helper methods to compute hash codes.
+ // -----------------------------------------------------------------------------------------------
+
+ private static int hash(final int tag, final int value) {
+ return 0x7FFFFFFF & (tag + value);
+ }
+
+ private static int hash(final int tag, final long value) {
+ return 0x7FFFFFFF & (tag + (int) value + (int) (value >>> 32));
+ }
+
+ private static int hash(final int tag, final String value) {
+ return 0x7FFFFFFF & (tag + value.hashCode());
+ }
+
+ private static int hash(final int tag, final String value1, final int value2) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() + value2);
+ }
+
+ private static int hash(final int tag, final String value1, final String value2) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode());
+ }
+
+ private static int hash(
+ final int tag, final String value1, final String value2, final int value3) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * (value3 + 1));
+ }
+
+ private static int hash(
+ final int tag, final String value1, final String value2, final String value3) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode());
+ }
+
+ private static int hash(
+ final int tag,
+ final String value1,
+ final String value2,
+ final String value3,
+ final int value4) {
+ return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode() * value4);
+ }
+
+ /**
+ * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields
+ * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid
+ * duplicate symbols). See {@link #entries}.
+ *
+ * @author Eric Bruneton
+ */
+ private static class Entry extends Symbol {
+
+ /** The hash code of this entry. */
+ final int hashCode;
+
+ /**
+ * Another entry (and so on recursively) having the same hash code (modulo the size of {@link
+ * #entries}) as this one.
+ */
+ Entry next;
+
+ Entry(
+ final int index,
+ final int tag,
+ final String owner,
+ final String name,
+ final String value,
+ final long data,
+ final int hashCode) {
+ super(index, tag, owner, name, value, data);
+ this.hashCode = hashCode;
+ }
+
+ Entry(final int index, final int tag, final String value, final int hashCode) {
+ super(index, tag, /* owner = */ null, /* name = */ null, value, /* data = */ 0);
+ this.hashCode = hashCode;
+ }
+
+ Entry(final int index, final int tag, final String value, final long data, final int hashCode) {
+ super(index, tag, /* owner = */ null, /* name = */ null, value, data);
+ this.hashCode = hashCode;
+ }
+
+ Entry(
+ final int index, final int tag, final String name, final String value, final int hashCode) {
+ super(index, tag, /* owner = */ null, name, value, /* data = */ 0);
+ this.hashCode = hashCode;
+ }
+
+ Entry(final int index, final int tag, final long data, final int hashCode) {
+ super(index, tag, /* owner = */ null, /* name = */ null, /* value = */ null, data);
+ this.hashCode = hashCode;
+ }
+ }
+}
diff --git a/src/java/nginx/clojure/asm/Type.java b/src/java/nginx/clojure/asm/Type.java
index 2ad1387e..32191523 100644
--- a/src/java/nginx/clojure/asm/Type.java
+++ b/src/java/nginx/clojure/asm/Type.java
@@ -1,895 +1,895 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
- * A Java field or method type. This class can be used to make it easier to
- * manipulate type and method descriptors.
- *
+ * A Java field or method type. This class can be used to make it easier to manipulate type and
+ * method descriptors.
+ *
* @author Eric Bruneton
* @author Chris Nokleberg
*/
-public class Type {
-
- /**
- * The sort of the void type. See {@link #getSort getSort}.
- */
- public static final int VOID = 0;
-
- /**
- * The sort of the boolean type. See {@link #getSort getSort}.
- */
- public static final int BOOLEAN = 1;
-
- /**
- * The sort of the char type. See {@link #getSort getSort}.
- */
- public static final int CHAR = 2;
-
- /**
- * The sort of the byte type. See {@link #getSort getSort}.
- */
- public static final int BYTE = 3;
-
- /**
- * The sort of the short type. See {@link #getSort getSort}.
- */
- public static final int SHORT = 4;
-
- /**
- * The sort of the int type. See {@link #getSort getSort}.
- */
- public static final int INT = 5;
-
- /**
- * The sort of the float type. See {@link #getSort getSort}.
- */
- public static final int FLOAT = 6;
-
- /**
- * The sort of the long type. See {@link #getSort getSort}.
- */
- public static final int LONG = 7;
-
- /**
- * The sort of the double type. See {@link #getSort getSort}.
- */
- public static final int DOUBLE = 8;
-
- /**
- * The sort of array reference types. See {@link #getSort getSort}.
- */
- public static final int ARRAY = 9;
-
- /**
- * The sort of object reference types. See {@link #getSort getSort}.
- */
- public static final int OBJECT = 10;
-
- /**
- * The sort of method types. See {@link #getSort getSort}.
- */
- public static final int METHOD = 11;
-
- /**
- * The void type.
- */
- public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24)
- | (5 << 16) | (0 << 8) | 0, 1);
-
- /**
- * The boolean type.
- */
- public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24)
- | (0 << 16) | (5 << 8) | 1, 1);
-
- /**
- * The char type.
- */
- public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24)
- | (0 << 16) | (6 << 8) | 1, 1);
-
- /**
- * The byte type.
- */
- public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24)
- | (0 << 16) | (5 << 8) | 1, 1);
-
- /**
- * The short type.
- */
- public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24)
- | (0 << 16) | (7 << 8) | 1, 1);
-
- /**
- * The int type.
- */
- public static final Type INT_TYPE = new Type(INT, null, ('I' << 24)
- | (0 << 16) | (0 << 8) | 1, 1);
-
- /**
- * The float type.
- */
- public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24)
- | (2 << 16) | (2 << 8) | 1, 1);
-
- /**
- * The long type.
- */
- public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24)
- | (1 << 16) | (1 << 8) | 2, 1);
-
- /**
- * The double type.
- */
- public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24)
- | (3 << 16) | (3 << 8) | 2, 1);
-
- // ------------------------------------------------------------------------
- // Fields
- // ------------------------------------------------------------------------
-
- /**
- * The sort of this Java type.
- */
- private final int sort;
-
- /**
- * A buffer containing the internal name of this Java type. This field is
- * only used for reference types.
- */
- private final char[] buf;
-
- /**
- * The offset of the internal name of this Java type in {@link #buf buf} or,
- * for primitive types, the size, descriptor and getOpcode offsets for this
- * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset
- * for IALOAD or IASTORE, byte 3 the offset for all other instructions).
- */
- private final int off;
-
- /**
- * The length of the internal name of this Java type.
- */
- private final int len;
-
- // ------------------------------------------------------------------------
- // Constructors
- // ------------------------------------------------------------------------
-
- /**
- * Constructs a reference type.
- *
- * @param sort
- * the sort of the reference type to be constructed.
- * @param buf
- * a buffer containing the descriptor of the previous type.
- * @param off
- * the offset of this descriptor in the previous buffer.
- * @param len
- * the length of this descriptor.
- */
- private Type(final int sort, final char[] buf, final int off, final int len) {
- this.sort = sort;
- this.buf = buf;
- this.off = off;
- this.len = len;
- }
-
- /**
- * Returns the Java type corresponding to the given type descriptor.
- *
- * @param typeDescriptor
- * a field or method type descriptor.
- * @return the Java type corresponding to the given type descriptor.
- */
- public static Type getType(final String typeDescriptor) {
- return getType(typeDescriptor.toCharArray(), 0);
- }
-
- /**
- * Returns the Java type corresponding to the given internal name.
- *
- * @param internalName
- * an internal name.
- * @return the Java type corresponding to the given internal name.
- */
- public static Type getObjectType(final String internalName) {
- char[] buf = internalName.toCharArray();
- return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length);
- }
+public final class Type {
- /**
- * Returns the Java type corresponding to the given method descriptor.
- * Equivalent to Type.getType(methodDescriptor)
.
- *
- * @param methodDescriptor
- * a method descriptor.
- * @return the Java type corresponding to the given method descriptor.
- */
- public static Type getMethodType(final String methodDescriptor) {
- return getType(methodDescriptor.toCharArray(), 0);
- }
+ /** The sort of the {@code void} type. See {@link #getSort}. */
+ public static final int VOID = 0;
- /**
- * Returns the Java method type corresponding to the given argument and
- * return types.
- *
- * @param returnType
- * the return type of the method.
- * @param argumentTypes
- * the argument types of the method.
- * @return the Java type corresponding to the given argument and return
- * types.
- */
- public static Type getMethodType(final Type returnType,
- final Type... argumentTypes) {
- return getType(getMethodDescriptor(returnType, argumentTypes));
- }
+ /** The sort of the {@code boolean} type. See {@link #getSort}. */
+ public static final int BOOLEAN = 1;
- /**
- * Returns the Java type corresponding to the given class.
- *
- * @param c
- * a class.
- * @return the Java type corresponding to the given class.
- */
- public static Type getType(final Class> c) {
- if (c.isPrimitive()) {
- if (c == Integer.TYPE) {
- return INT_TYPE;
- } else if (c == Void.TYPE) {
- return VOID_TYPE;
- } else if (c == Boolean.TYPE) {
- return BOOLEAN_TYPE;
- } else if (c == Byte.TYPE) {
- return BYTE_TYPE;
- } else if (c == Character.TYPE) {
- return CHAR_TYPE;
- } else if (c == Short.TYPE) {
- return SHORT_TYPE;
- } else if (c == Double.TYPE) {
- return DOUBLE_TYPE;
- } else if (c == Float.TYPE) {
- return FLOAT_TYPE;
- } else /* if (c == Long.TYPE) */{
- return LONG_TYPE;
- }
- } else {
- return getType(getDescriptor(c));
- }
- }
+ /** The sort of the {@code char} type. See {@link #getSort}. */
+ public static final int CHAR = 2;
- /**
- * Returns the Java method type corresponding to the given constructor.
- *
- * @param c
- * a {@link Constructor Constructor} object.
- * @return the Java method type corresponding to the given constructor.
- */
- public static Type getType(final Constructor> c) {
- return getType(getConstructorDescriptor(c));
- }
+ /** The sort of the {@code byte} type. See {@link #getSort}. */
+ public static final int BYTE = 3;
- /**
- * Returns the Java method type corresponding to the given method.
- *
- * @param m
- * a {@link Method Method} object.
- * @return the Java method type corresponding to the given method.
- */
- public static Type getType(final Method m) {
- return getType(getMethodDescriptor(m));
- }
+ /** The sort of the {@code short} type. See {@link #getSort}. */
+ public static final int SHORT = 4;
- /**
- * Returns the Java types corresponding to the argument types of the given
- * method descriptor.
- *
- * @param methodDescriptor
- * a method descriptor.
- * @return the Java types corresponding to the argument types of the given
- * method descriptor.
- */
- public static Type[] getArgumentTypes(final String methodDescriptor) {
- char[] buf = methodDescriptor.toCharArray();
- int off = 1;
- int size = 0;
- while (true) {
- char car = buf[off++];
- if (car == ')') {
- break;
- } else if (car == 'L') {
- while (buf[off++] != ';') {
- }
- ++size;
- } else if (car != '[') {
- ++size;
- }
+ /** The sort of the {@code int} type. See {@link #getSort}. */
+ public static final int INT = 5;
+
+ /** The sort of the {@code float} type. See {@link #getSort}. */
+ public static final int FLOAT = 6;
+
+ /** The sort of the {@code long} type. See {@link #getSort}. */
+ public static final int LONG = 7;
+
+ /** The sort of the {@code double} type. See {@link #getSort}. */
+ public static final int DOUBLE = 8;
+
+ /** The sort of array reference types. See {@link #getSort}. */
+ public static final int ARRAY = 9;
+
+ /** The sort of object reference types. See {@link #getSort}. */
+ public static final int OBJECT = 10;
+
+ /** The sort of method types. See {@link #getSort}. */
+ public static final int METHOD = 11;
+
+ /** The (private) sort of object reference types represented with an internal name. */
+ private static final int INTERNAL = 12;
+
+ /** The descriptors of the primitive types. */
+ private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD";
+
+ /** The {@code void} type. */
+ public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1);
+
+ /** The {@code boolean} type. */
+ public static final Type BOOLEAN_TYPE =
+ new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1);
+
+ /** The {@code char} type. */
+ public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1);
+
+ /** The {@code byte} type. */
+ public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1);
+
+ /** The {@code short} type. */
+ public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1);
+
+ /** The {@code int} type. */
+ public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1);
+
+ /** The {@code float} type. */
+ public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1);
+
+ /** The {@code long} type. */
+ public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1);
+
+ /** The {@code double} type. */
+ public static final Type DOUBLE_TYPE =
+ new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1);
+
+ // -----------------------------------------------------------------------------------------------
+ // Fields
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * The sort of this type. Either {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE},
+ * {@link #SHORT}, {@link #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY},
+ * {@link #OBJECT}, {@link #METHOD} or {@link #INTERNAL}.
+ */
+ private final int sort;
+
+ /**
+ * A buffer containing the value of this field or method type. This value is an internal name for
+ * {@link #OBJECT} and {@link #INTERNAL} types, and a field or method descriptor in the other
+ * cases.
+ *
+ * For {@link #OBJECT} types, this field also contains the descriptor: the characters in
+ * [{@link #valueBegin},{@link #valueEnd}) contain the internal name, and those in [{@link
+ * #valueBegin} - 1, {@link #valueEnd} + 1) contain the descriptor.
+ */
+ private final String valueBuffer;
+
+ /**
+ * The beginning index, inclusive, of the value of this Java field or method type in {@link
+ * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types,
+ * and a field or method descriptor in the other cases.
+ */
+ private final int valueBegin;
+
+ /**
+ * The end index, exclusive, of the value of this Java field or method type in {@link
+ * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types,
+ * and a field or method descriptor in the other cases.
+ */
+ private final int valueEnd;
+
+ /**
+ * Constructs a reference type.
+ *
+ * @param sort the sort of this type, see {@link #sort}.
+ * @param valueBuffer a buffer containing the value of this field or method type.
+ * @param valueBegin the beginning index, inclusive, of the value of this field or method type in
+ * valueBuffer.
+ * @param valueEnd the end index, exclusive, of the value of this field or method type in
+ * valueBuffer.
+ */
+ private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) {
+ this.sort = sort;
+ this.valueBuffer = valueBuffer;
+ this.valueBegin = valueBegin;
+ this.valueEnd = valueEnd;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods to get Type(s) from a descriptor, a reflected Method or Constructor, other types, etc.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the {@link Type} corresponding to the given type descriptor.
+ *
+ * @param typeDescriptor a field or method type descriptor.
+ * @return the {@link Type} corresponding to the given type descriptor.
+ */
+ public static Type getType(final String typeDescriptor) {
+ return getTypeInternal(typeDescriptor, 0, typeDescriptor.length());
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given class.
+ *
+ * @param clazz a class.
+ * @return the {@link Type} corresponding to the given class.
+ */
+ public static Type getType(final Class> clazz) {
+ if (clazz.isPrimitive()) {
+ if (clazz == Integer.TYPE) {
+ return INT_TYPE;
+ } else if (clazz == Void.TYPE) {
+ return VOID_TYPE;
+ } else if (clazz == Boolean.TYPE) {
+ return BOOLEAN_TYPE;
+ } else if (clazz == Byte.TYPE) {
+ return BYTE_TYPE;
+ } else if (clazz == Character.TYPE) {
+ return CHAR_TYPE;
+ } else if (clazz == Short.TYPE) {
+ return SHORT_TYPE;
+ } else if (clazz == Double.TYPE) {
+ return DOUBLE_TYPE;
+ } else if (clazz == Float.TYPE) {
+ return FLOAT_TYPE;
+ } else if (clazz == Long.TYPE) {
+ return LONG_TYPE;
+ } else {
+ throw new AssertionError();
+ }
+ } else {
+ return getType(getDescriptor(clazz));
+ }
+ }
+
+ /**
+ * Returns the method {@link Type} corresponding to the given constructor.
+ *
+ * @param constructor a {@link Constructor} object.
+ * @return the method {@link Type} corresponding to the given constructor.
+ */
+ public static Type getType(final Constructor> constructor) {
+ return getType(getConstructorDescriptor(constructor));
+ }
+
+ /**
+ * Returns the method {@link Type} corresponding to the given method.
+ *
+ * @param method a {@link Method} object.
+ * @return the method {@link Type} corresponding to the given method.
+ */
+ public static Type getType(final Method method) {
+ return getType(getMethodDescriptor(method));
+ }
+
+ /**
+ * Returns the type of the elements of this array type. This method should only be used for an
+ * array type.
+ *
+ * @return Returns the type of the elements of this array type.
+ */
+ public Type getElementType() {
+ final int numDimensions = getDimensions();
+ return getTypeInternal(valueBuffer, valueBegin + numDimensions, valueEnd);
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given internal name.
+ *
+ * @param internalName an internal name (see {@link Type#getInternalName()}).
+ * @return the {@link Type} corresponding to the given internal name.
+ */
+ public static Type getObjectType(final String internalName) {
+ return new Type(
+ internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length());
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given method descriptor. Equivalent to
+ * Type.getType(methodDescriptor)
.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the {@link Type} corresponding to the given method descriptor.
+ */
+ public static Type getMethodType(final String methodDescriptor) {
+ return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length());
+ }
+
+ /**
+ * Returns the method {@link Type} corresponding to the given argument and return types.
+ *
+ * @param returnType the return type of the method.
+ * @param argumentTypes the argument types of the method.
+ * @return the method {@link Type} corresponding to the given argument and return types.
+ */
+ public static Type getMethodType(final Type returnType, final Type... argumentTypes) {
+ return getType(getMethodDescriptor(returnType, argumentTypes));
+ }
+
+ /**
+ * Returns the argument types of methods of this type. This method should only be used for method
+ * types.
+ *
+ * @return the argument types of methods of this type.
+ */
+ public Type[] getArgumentTypes() {
+ return getArgumentTypes(getDescriptor());
+ }
+
+ /**
+ * Returns the {@link Type} values corresponding to the argument types of the given method
+ * descriptor.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the {@link Type} values corresponding to the argument types of the given method
+ * descriptor.
+ */
+ public static Type[] getArgumentTypes(final String methodDescriptor) {
+ // First step: compute the number of argument types in methodDescriptor.
+ int numArgumentTypes = 0;
+ // Skip the first character, which is always a '('.
+ int currentOffset = 1;
+ // Parse the argument types, one at a each loop iteration.
+ while (methodDescriptor.charAt(currentOffset) != ')') {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
+ }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ // Skip the argument descriptor content.
+ int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
+ currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
+ }
+ ++numArgumentTypes;
+ }
+
+ // Second step: create a Type instance for each argument type.
+ Type[] argumentTypes = new Type[numArgumentTypes];
+ // Skip the first character, which is always a '('.
+ currentOffset = 1;
+ // Parse and create the argument types, one at each loop iteration.
+ int currentArgumentTypeIndex = 0;
+ while (methodDescriptor.charAt(currentOffset) != ')') {
+ final int currentArgumentTypeOffset = currentOffset;
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
+ }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ // Skip the argument descriptor content.
+ int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
+ currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
+ }
+ argumentTypes[currentArgumentTypeIndex++] =
+ getTypeInternal(methodDescriptor, currentArgumentTypeOffset, currentOffset);
+ }
+ return argumentTypes;
+ }
+
+ /**
+ * Returns the {@link Type} values corresponding to the argument types of the given method.
+ *
+ * @param method a method.
+ * @return the {@link Type} values corresponding to the argument types of the given method.
+ */
+ public static Type[] getArgumentTypes(final Method method) {
+ Class>[] classes = method.getParameterTypes();
+ Type[] types = new Type[classes.length];
+ for (int i = classes.length - 1; i >= 0; --i) {
+ types[i] = getType(classes[i]);
+ }
+ return types;
+ }
+
+ /**
+ * Returns the return type of methods of this type. This method should only be used for method
+ * types.
+ *
+ * @return the return type of methods of this type.
+ */
+ public Type getReturnType() {
+ return getReturnType(getDescriptor());
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the return type of the given method descriptor.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the {@link Type} corresponding to the return type of the given method descriptor.
+ */
+ public static Type getReturnType(final String methodDescriptor) {
+ return getTypeInternal(
+ methodDescriptor, getReturnTypeOffset(methodDescriptor), methodDescriptor.length());
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the return type of the given method.
+ *
+ * @param method a method.
+ * @return the {@link Type} corresponding to the return type of the given method.
+ */
+ public static Type getReturnType(final Method method) {
+ return getType(method.getReturnType());
+ }
+
+ /**
+ * Returns the start index of the return type of the given method descriptor.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the start index of the return type of the given method descriptor.
+ */
+ static int getReturnTypeOffset(final String methodDescriptor) {
+ // Skip the first character, which is always a '('.
+ int currentOffset = 1;
+ // Skip the argument types, one at a each loop iteration.
+ while (methodDescriptor.charAt(currentOffset) != ')') {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
+ }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ // Skip the argument descriptor content.
+ int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
+ currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
+ }
+ }
+ return currentOffset + 1;
+ }
+
+ /**
+ * Returns the {@link Type} corresponding to the given field or method descriptor.
+ *
+ * @param descriptorBuffer a buffer containing the field or method descriptor.
+ * @param descriptorBegin the beginning index, inclusive, of the field or method descriptor in
+ * descriptorBuffer.
+ * @param descriptorEnd the end index, exclusive, of the field or method descriptor in
+ * descriptorBuffer.
+ * @return the {@link Type} corresponding to the given type descriptor.
+ */
+ private static Type getTypeInternal(
+ final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) {
+ switch (descriptorBuffer.charAt(descriptorBegin)) {
+ case 'V':
+ return VOID_TYPE;
+ case 'Z':
+ return BOOLEAN_TYPE;
+ case 'C':
+ return CHAR_TYPE;
+ case 'B':
+ return BYTE_TYPE;
+ case 'S':
+ return SHORT_TYPE;
+ case 'I':
+ return INT_TYPE;
+ case 'F':
+ return FLOAT_TYPE;
+ case 'J':
+ return LONG_TYPE;
+ case 'D':
+ return DOUBLE_TYPE;
+ case '[':
+ return new Type(ARRAY, descriptorBuffer, descriptorBegin, descriptorEnd);
+ case 'L':
+ return new Type(OBJECT, descriptorBuffer, descriptorBegin + 1, descriptorEnd - 1);
+ case '(':
+ return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd);
+ default:
+ throw new IllegalArgumentException("Invalid descriptor: " + descriptorBuffer);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods to get class names, internal names or descriptors.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the binary name of the class corresponding to this type. This method must not be used
+ * on method types.
+ *
+ * @return the binary name of the class corresponding to this type.
+ */
+ public String getClassName() {
+ switch (sort) {
+ case VOID:
+ return "void";
+ case BOOLEAN:
+ return "boolean";
+ case CHAR:
+ return "char";
+ case BYTE:
+ return "byte";
+ case SHORT:
+ return "short";
+ case INT:
+ return "int";
+ case FLOAT:
+ return "float";
+ case LONG:
+ return "long";
+ case DOUBLE:
+ return "double";
+ case ARRAY:
+ StringBuilder stringBuilder = new StringBuilder(getElementType().getClassName());
+ for (int i = getDimensions(); i > 0; --i) {
+ stringBuilder.append("[]");
}
- Type[] args = new Type[size];
- off = 1;
- size = 0;
- while (buf[off] != ')') {
- args[size] = getType(buf, off);
- off += args[size].len + (args[size].sort == OBJECT ? 2 : 0);
- size += 1;
- }
- return args;
- }
-
- /**
- * Returns the Java types corresponding to the argument types of the given
- * method.
- *
- * @param method
- * a method.
- * @return the Java types corresponding to the argument types of the given
- * method.
- */
- public static Type[] getArgumentTypes(final Method method) {
- Class>[] classes = method.getParameterTypes();
- Type[] types = new Type[classes.length];
- for (int i = classes.length - 1; i >= 0; --i) {
- types[i] = getType(classes[i]);
+ return stringBuilder.toString();
+ case OBJECT:
+ case INTERNAL:
+ return valueBuffer.substring(valueBegin, valueEnd).replace('/', '.');
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Returns the internal name of the class corresponding to this object or array type. The internal
+ * name of a class is its fully qualified name (as returned by Class.getName(), where '.' are
+ * replaced by '/'). This method should only be used for an object or array type.
+ *
+ * @return the internal name of the class corresponding to this object type.
+ */
+ public String getInternalName() {
+ return valueBuffer.substring(valueBegin, valueEnd);
+ }
+
+ /**
+ * Returns the internal name of the given class. The internal name of a class is its fully
+ * qualified name, as returned by Class.getName(), where '.' are replaced by '/'.
+ *
+ * @param clazz an object or array class.
+ * @return the internal name of the given class.
+ */
+ public static String getInternalName(final Class> clazz) {
+ return clazz.getName().replace('.', '/');
+ }
+
+ /**
+ * Returns the descriptor corresponding to this type.
+ *
+ * @return the descriptor corresponding to this type.
+ */
+ public String getDescriptor() {
+ if (sort == OBJECT) {
+ return valueBuffer.substring(valueBegin - 1, valueEnd + 1);
+ } else if (sort == INTERNAL) {
+ return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';';
+ } else {
+ return valueBuffer.substring(valueBegin, valueEnd);
+ }
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given class.
+ *
+ * @param clazz an object class, a primitive class or an array class.
+ * @return the descriptor corresponding to the given class.
+ */
+ public static String getDescriptor(final Class> clazz) {
+ StringBuilder stringBuilder = new StringBuilder();
+ appendDescriptor(clazz, stringBuilder);
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given constructor.
+ *
+ * @param constructor a {@link Constructor} object.
+ * @return the descriptor of the given constructor.
+ */
+ public static String getConstructorDescriptor(final Constructor> constructor) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append('(');
+ Class>[] parameters = constructor.getParameterTypes();
+ for (Class> parameter : parameters) {
+ appendDescriptor(parameter, stringBuilder);
+ }
+ return stringBuilder.append(")V").toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given argument and return types.
+ *
+ * @param returnType the return type of the method.
+ * @param argumentTypes the argument types of the method.
+ * @return the descriptor corresponding to the given argument and return types.
+ */
+ public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append('(');
+ for (Type argumentType : argumentTypes) {
+ argumentType.appendDescriptor(stringBuilder);
+ }
+ stringBuilder.append(')');
+ returnType.appendDescriptor(stringBuilder);
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given method.
+ *
+ * @param method a {@link Method} object.
+ * @return the descriptor of the given method.
+ */
+ public static String getMethodDescriptor(final Method method) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append('(');
+ Class>[] parameters = method.getParameterTypes();
+ for (Class> parameter : parameters) {
+ appendDescriptor(parameter, stringBuilder);
+ }
+ stringBuilder.append(')');
+ appendDescriptor(method.getReturnType(), stringBuilder);
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Appends the descriptor corresponding to this type to the given string buffer.
+ *
+ * @param stringBuilder the string builder to which the descriptor must be appended.
+ */
+ private void appendDescriptor(final StringBuilder stringBuilder) {
+ if (sort == OBJECT) {
+ stringBuilder.append(valueBuffer, valueBegin - 1, valueEnd + 1);
+ } else if (sort == INTERNAL) {
+ stringBuilder.append('L').append(valueBuffer, valueBegin, valueEnd).append(';');
+ } else {
+ stringBuilder.append(valueBuffer, valueBegin, valueEnd);
+ }
+ }
+
+ /**
+ * Appends the descriptor of the given class to the given string builder.
+ *
+ * @param clazz the class whose descriptor must be computed.
+ * @param stringBuilder the string builder to which the descriptor must be appended.
+ */
+ private static void appendDescriptor(final Class> clazz, final StringBuilder stringBuilder) {
+ Class> currentClass = clazz;
+ while (currentClass.isArray()) {
+ stringBuilder.append('[');
+ currentClass = currentClass.getComponentType();
+ }
+ if (currentClass.isPrimitive()) {
+ char descriptor;
+ if (currentClass == Integer.TYPE) {
+ descriptor = 'I';
+ } else if (currentClass == Void.TYPE) {
+ descriptor = 'V';
+ } else if (currentClass == Boolean.TYPE) {
+ descriptor = 'Z';
+ } else if (currentClass == Byte.TYPE) {
+ descriptor = 'B';
+ } else if (currentClass == Character.TYPE) {
+ descriptor = 'C';
+ } else if (currentClass == Short.TYPE) {
+ descriptor = 'S';
+ } else if (currentClass == Double.TYPE) {
+ descriptor = 'D';
+ } else if (currentClass == Float.TYPE) {
+ descriptor = 'F';
+ } else if (currentClass == Long.TYPE) {
+ descriptor = 'J';
+ } else {
+ throw new AssertionError();
+ }
+ stringBuilder.append(descriptor);
+ } else {
+ stringBuilder.append('L').append(getInternalName(currentClass)).append(';');
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Methods to get the sort, dimension, size, and opcodes corresponding to a Type or descriptor.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Returns the sort of this type.
+ *
+ * @return {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, {@link #SHORT}, {@link
+ * #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, {@link #OBJECT} or
+ * {@link #METHOD}.
+ */
+ public int getSort() {
+ return sort == INTERNAL ? OBJECT : sort;
+ }
+
+ /**
+ * Returns the number of dimensions of this array type. This method should only be used for an
+ * array type.
+ *
+ * @return the number of dimensions of this array type.
+ */
+ public int getDimensions() {
+ int numDimensions = 1;
+ while (valueBuffer.charAt(valueBegin + numDimensions) == '[') {
+ numDimensions++;
+ }
+ return numDimensions;
+ }
+
+ /**
+ * Returns the size of values of this type. This method must not be used for method types.
+ *
+ * @return the size of values of this type, i.e., 2 for {@code long} and {@code double}, 0 for
+ * {@code void} and 1 otherwise.
+ */
+ public int getSize() {
+ switch (sort) {
+ case VOID:
+ return 0;
+ case BOOLEAN:
+ case CHAR:
+ case BYTE:
+ case SHORT:
+ case INT:
+ case FLOAT:
+ case ARRAY:
+ case OBJECT:
+ case INTERNAL:
+ return 1;
+ case LONG:
+ case DOUBLE:
+ return 2;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Returns the size of the arguments and of the return value of methods of this type. This method
+ * should only be used for method types.
+ *
+ * @return the size of the arguments of the method (plus one for the implicit this argument),
+ * argumentsSize, and the size of its return value, returnSize, packed into a single int i =
+ * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
+ * i >> 2}, and returnSize to {@code i & 0x03}).
+ */
+ public int getArgumentsAndReturnSizes() {
+ return getArgumentsAndReturnSizes(getDescriptor());
+ }
+
+ /**
+ * Computes the size of the arguments and of the return value of a method.
+ *
+ * @param methodDescriptor a method descriptor.
+ * @return the size of the arguments of the method (plus one for the implicit this argument),
+ * argumentsSize, and the size of its return value, returnSize, packed into a single int i =
+ * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
+ * i >> 2}, and returnSize to {@code i & 0x03}).
+ */
+ public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
+ int argumentsSize = 1;
+ // Skip the first character, which is always a '('.
+ int currentOffset = 1;
+ int currentChar = methodDescriptor.charAt(currentOffset);
+ // Parse the argument types and compute their size, one at a each loop iteration.
+ while (currentChar != ')') {
+ if (currentChar == 'J' || currentChar == 'D') {
+ currentOffset++;
+ argumentsSize += 2;
+ } else {
+ while (methodDescriptor.charAt(currentOffset) == '[') {
+ currentOffset++;
}
- return types;
- }
-
- /**
- * Returns the Java type corresponding to the return type of the given
- * method descriptor.
- *
- * @param methodDescriptor
- * a method descriptor.
- * @return the Java type corresponding to the return type of the given
- * method descriptor.
- */
- public static Type getReturnType(final String methodDescriptor) {
- char[] buf = methodDescriptor.toCharArray();
- return getType(buf, methodDescriptor.indexOf(')') + 1);
- }
-
- /**
- * Returns the Java type corresponding to the return type of the given
- * method.
- *
- * @param method
- * a method.
- * @return the Java type corresponding to the return type of the given
- * method.
- */
- public static Type getReturnType(final Method method) {
- return getType(method.getReturnType());
- }
-
- /**
- * Computes the size of the arguments and of the return value of a method.
- *
- * @param desc
- * the descriptor of a method.
- * @return the size of the arguments of the method (plus one for the
- * implicit this argument), argSize, and the size of its return
- * value, retSize, packed into a single int i =
- * (argSize << 2) | retSize (argSize is therefore equal to
- * i >> 2, and retSize to i & 0x03).
- */
- public static int getArgumentsAndReturnSizes(final String desc) {
- int n = 1;
- int c = 1;
- while (true) {
- char car = desc.charAt(c++);
- if (car == ')') {
- car = desc.charAt(c);
- return n << 2
- | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1));
- } else if (car == 'L') {
- while (desc.charAt(c++) != ';') {
- }
- n += 1;
- } else if (car == '[') {
- while ((car = desc.charAt(c)) == '[') {
- ++c;
- }
- if (car == 'D' || car == 'J') {
- n -= 1;
- }
- } else if (car == 'D' || car == 'J') {
- n += 2;
- } else {
- n += 1;
- }
+ if (methodDescriptor.charAt(currentOffset++) == 'L') {
+ // Skip the argument descriptor content.
+ int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset);
+ currentOffset = Math.max(currentOffset, semiColumnOffset + 1);
}
- }
-
- /**
- * Returns the Java type corresponding to the given type descriptor. For
- * method descriptors, buf is supposed to contain nothing more than the
- * descriptor itself.
- *
- * @param buf
- * a buffer containing a type descriptor.
- * @param off
- * the offset of this descriptor in the previous buffer.
- * @return the Java type corresponding to the given type descriptor.
- */
- private static Type getType(final char[] buf, final int off) {
- int len;
- switch (buf[off]) {
- case 'V':
- return VOID_TYPE;
- case 'Z':
- return BOOLEAN_TYPE;
- case 'C':
- return CHAR_TYPE;
- case 'B':
- return BYTE_TYPE;
- case 'S':
- return SHORT_TYPE;
- case 'I':
- return INT_TYPE;
- case 'F':
- return FLOAT_TYPE;
- case 'J':
- return LONG_TYPE;
- case 'D':
- return DOUBLE_TYPE;
- case '[':
- len = 1;
- while (buf[off + len] == '[') {
- ++len;
- }
- if (buf[off + len] == 'L') {
- ++len;
- while (buf[off + len] != ';') {
- ++len;
- }
- }
- return new Type(ARRAY, buf, off, len + 1);
- case 'L':
- len = 1;
- while (buf[off + len] != ';') {
- ++len;
- }
- return new Type(OBJECT, buf, off + 1, len - 1);
- // case '(':
+ argumentsSize += 1;
+ }
+ currentChar = methodDescriptor.charAt(currentOffset);
+ }
+ currentChar = methodDescriptor.charAt(currentOffset + 1);
+ if (currentChar == 'V') {
+ return argumentsSize << 2;
+ } else {
+ int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1;
+ return argumentsSize << 2 | returnSize;
+ }
+ }
+
+ /**
+ * Returns a JVM instruction opcode adapted to this {@link Type}. This method must not be used for
+ * method types.
+ *
+ * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD,
+ * IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and
+ * IRETURN.
+ * @return an opcode that is similar to the given opcode, but adapted to this {@link Type}. For
+ * example, if this type is {@code float} and {@code opcode} is IRETURN, this method returns
+ * FRETURN.
+ */
+ public int getOpcode(final int opcode) {
+ if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
+ switch (sort) {
+ case BOOLEAN:
+ case BYTE:
+ return opcode + (Opcodes.BALOAD - Opcodes.IALOAD);
+ case CHAR:
+ return opcode + (Opcodes.CALOAD - Opcodes.IALOAD);
+ case SHORT:
+ return opcode + (Opcodes.SALOAD - Opcodes.IALOAD);
+ case INT:
+ return opcode;
+ case FLOAT:
+ return opcode + (Opcodes.FALOAD - Opcodes.IALOAD);
+ case LONG:
+ return opcode + (Opcodes.LALOAD - Opcodes.IALOAD);
+ case DOUBLE:
+ return opcode + (Opcodes.DALOAD - Opcodes.IALOAD);
+ case ARRAY:
+ case OBJECT:
+ case INTERNAL:
+ return opcode + (Opcodes.AALOAD - Opcodes.IALOAD);
+ case METHOD:
+ case VOID:
+ throw new UnsupportedOperationException();
default:
- return new Type(METHOD, buf, off, buf.length - off);
- }
- }
-
- // ------------------------------------------------------------------------
- // Accessors
- // ------------------------------------------------------------------------
-
- /**
- * Returns the sort of this Java type.
- *
- * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, {@link #CHAR CHAR},
- * {@link #BYTE BYTE}, {@link #SHORT SHORT}, {@link #INT INT},
- * {@link #FLOAT FLOAT}, {@link #LONG LONG}, {@link #DOUBLE DOUBLE},
- * {@link #ARRAY ARRAY}, {@link #OBJECT OBJECT} or {@link #METHOD
- * METHOD}.
- */
- public int getSort() {
- return sort;
- }
-
- /**
- * Returns the number of dimensions of this array type. This method should
- * only be used for an array type.
- *
- * @return the number of dimensions of this array type.
- */
- public int getDimensions() {
- int i = 1;
- while (buf[off + i] == '[') {
- ++i;
- }
- return i;
- }
-
- /**
- * Returns the type of the elements of this array type. This method should
- * only be used for an array type.
- *
- * @return Returns the type of the elements of this array type.
- */
- public Type getElementType() {
- return getType(buf, off + getDimensions());
- }
-
- /**
- * Returns the binary name of the class corresponding to this type. This
- * method must not be used on method types.
- *
- * @return the binary name of the class corresponding to this type.
- */
- public String getClassName() {
- switch (sort) {
+ throw new AssertionError();
+ }
+ } else {
+ switch (sort) {
case VOID:
- return "void";
+ if (opcode != Opcodes.IRETURN) {
+ throw new UnsupportedOperationException();
+ }
+ return Opcodes.RETURN;
case BOOLEAN:
- return "boolean";
- case CHAR:
- return "char";
case BYTE:
- return "byte";
+ case CHAR:
case SHORT:
- return "short";
case INT:
- return "int";
+ return opcode;
case FLOAT:
- return "float";
+ return opcode + (Opcodes.FRETURN - Opcodes.IRETURN);
case LONG:
- return "long";
+ return opcode + (Opcodes.LRETURN - Opcodes.IRETURN);
case DOUBLE:
- return "double";
+ return opcode + (Opcodes.DRETURN - Opcodes.IRETURN);
case ARRAY:
- StringBuffer b = new StringBuffer(getElementType().getClassName());
- for (int i = getDimensions(); i > 0; --i) {
- b.append("[]");
- }
- return b.toString();
case OBJECT:
- return new String(buf, off, len).replace('/', '.');
+ case INTERNAL:
+ if (opcode != Opcodes.ILOAD && opcode != Opcodes.ISTORE && opcode != Opcodes.IRETURN) {
+ throw new UnsupportedOperationException();
+ }
+ return opcode + (Opcodes.ARETURN - Opcodes.IRETURN);
+ case METHOD:
+ throw new UnsupportedOperationException();
default:
- return null;
- }
- }
-
- /**
- * Returns the internal name of the class corresponding to this object or
- * array type. The internal name of a class is its fully qualified name (as
- * returned by Class.getName(), where '.' are replaced by '/'. This method
- * should only be used for an object or array type.
- *
- * @return the internal name of the class corresponding to this object type.
- */
- public String getInternalName() {
- return new String(buf, off, len);
- }
-
- /**
- * Returns the argument types of methods of this type. This method should
- * only be used for method types.
- *
- * @return the argument types of methods of this type.
- */
- public Type[] getArgumentTypes() {
- return getArgumentTypes(getDescriptor());
- }
-
- /**
- * Returns the return type of methods of this type. This method should only
- * be used for method types.
- *
- * @return the return type of methods of this type.
- */
- public Type getReturnType() {
- return getReturnType(getDescriptor());
- }
-
- /**
- * Returns the size of the arguments and of the return value of methods of
- * this type. This method should only be used for method types.
- *
- * @return the size of the arguments (plus one for the implicit this
- * argument), argSize, and the size of the return value, retSize,
- * packed into a single int i = (argSize << 2) | retSize
- * (argSize is therefore equal to i >> 2, and retSize to
- * i & 0x03).
- */
- public int getArgumentsAndReturnSizes() {
- return getArgumentsAndReturnSizes(getDescriptor());
- }
-
- // ------------------------------------------------------------------------
- // Conversion to type descriptors
- // ------------------------------------------------------------------------
-
- /**
- * Returns the descriptor corresponding to this Java type.
- *
- * @return the descriptor corresponding to this Java type.
- */
- public String getDescriptor() {
- StringBuffer buf = new StringBuffer();
- getDescriptor(buf);
- return buf.toString();
- }
-
- /**
- * Returns the descriptor corresponding to the given argument and return
- * types.
- *
- * @param returnType
- * the return type of the method.
- * @param argumentTypes
- * the argument types of the method.
- * @return the descriptor corresponding to the given argument and return
- * types.
- */
- public static String getMethodDescriptor(final Type returnType,
- final Type... argumentTypes) {
- StringBuffer buf = new StringBuffer();
- buf.append('(');
- for (int i = 0; i < argumentTypes.length; ++i) {
- argumentTypes[i].getDescriptor(buf);
- }
- buf.append(')');
- returnType.getDescriptor(buf);
- return buf.toString();
- }
-
- /**
- * Appends the descriptor corresponding to this Java type to the given
- * string buffer.
- *
- * @param buf
- * the string buffer to which the descriptor must be appended.
- */
- private void getDescriptor(final StringBuffer buf) {
- if (this.buf == null) {
- // descriptor is in byte 3 of 'off' for primitive types (buf ==
- // null)
- buf.append((char) ((off & 0xFF000000) >>> 24));
- } else if (sort == OBJECT) {
- buf.append('L');
- buf.append(this.buf, off, len);
- buf.append(';');
- } else { // sort == ARRAY || sort == METHOD
- buf.append(this.buf, off, len);
- }
- }
-
- // ------------------------------------------------------------------------
- // Direct conversion from classes to type descriptors,
- // without intermediate Type objects
- // ------------------------------------------------------------------------
-
- /**
- * Returns the internal name of the given class. The internal name of a
- * class is its fully qualified name, as returned by Class.getName(), where
- * '.' are replaced by '/'.
- *
- * @param c
- * an object or array class.
- * @return the internal name of the given class.
- */
- public static String getInternalName(final Class> c) {
- return c.getName().replace('.', '/');
- }
-
- /**
- * Returns the descriptor corresponding to the given Java type.
- *
- * @param c
- * an object class, a primitive class or an array class.
- * @return the descriptor corresponding to the given class.
- */
- public static String getDescriptor(final Class> c) {
- StringBuffer buf = new StringBuffer();
- getDescriptor(buf, c);
- return buf.toString();
- }
-
- /**
- * Returns the descriptor corresponding to the given constructor.
- *
- * @param c
- * a {@link Constructor Constructor} object.
- * @return the descriptor of the given constructor.
- */
- public static String getConstructorDescriptor(final Constructor> c) {
- Class>[] parameters = c.getParameterTypes();
- StringBuffer buf = new StringBuffer();
- buf.append('(');
- for (int i = 0; i < parameters.length; ++i) {
- getDescriptor(buf, parameters[i]);
- }
- return buf.append(")V").toString();
- }
-
- /**
- * Returns the descriptor corresponding to the given method.
- *
- * @param m
- * a {@link Method Method} object.
- * @return the descriptor of the given method.
- */
- public static String getMethodDescriptor(final Method m) {
- Class>[] parameters = m.getParameterTypes();
- StringBuffer buf = new StringBuffer();
- buf.append('(');
- for (int i = 0; i < parameters.length; ++i) {
- getDescriptor(buf, parameters[i]);
- }
- buf.append(')');
- getDescriptor(buf, m.getReturnType());
- return buf.toString();
- }
-
- /**
- * Appends the descriptor of the given class to the given string buffer.
- *
- * @param buf
- * the string buffer to which the descriptor must be appended.
- * @param c
- * the class whose descriptor must be computed.
- */
- private static void getDescriptor(final StringBuffer buf, final Class> c) {
- Class> d = c;
- while (true) {
- if (d.isPrimitive()) {
- char car;
- if (d == Integer.TYPE) {
- car = 'I';
- } else if (d == Void.TYPE) {
- car = 'V';
- } else if (d == Boolean.TYPE) {
- car = 'Z';
- } else if (d == Byte.TYPE) {
- car = 'B';
- } else if (d == Character.TYPE) {
- car = 'C';
- } else if (d == Short.TYPE) {
- car = 'S';
- } else if (d == Double.TYPE) {
- car = 'D';
- } else if (d == Float.TYPE) {
- car = 'F';
- } else /* if (d == Long.TYPE) */{
- car = 'J';
- }
- buf.append(car);
- return;
- } else if (d.isArray()) {
- buf.append('[');
- d = d.getComponentType();
- } else {
- buf.append('L');
- String name = d.getName();
- int len = name.length();
- for (int i = 0; i < len; ++i) {
- char car = name.charAt(i);
- buf.append(car == '.' ? '/' : car);
- }
- buf.append(';');
- return;
- }
- }
- }
-
- // ------------------------------------------------------------------------
- // Corresponding size and opcodes
- // ------------------------------------------------------------------------
-
- /**
- * Returns the size of values of this type. This method must not be used for
- * method types.
- *
- * @return the size of values of this type, i.e., 2 for long and
- * double, 0 for void and 1 otherwise.
- */
- public int getSize() {
- // the size is in byte 0 of 'off' for primitive types (buf == null)
- return buf == null ? (off & 0xFF) : 1;
- }
-
- /**
- * Returns a JVM instruction opcode adapted to this Java type. This method
- * must not be used for method types.
- *
- * @param opcode
- * a JVM instruction opcode. This opcode must be one of ILOAD,
- * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG,
- * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN.
- * @return an opcode that is similar to the given opcode, but adapted to
- * this Java type. For example, if this type is float and
- * opcode is IRETURN, this method returns FRETURN.
- */
- public int getOpcode(final int opcode) {
- if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
- // the offset for IALOAD or IASTORE is in byte 1 of 'off' for
- // primitive types (buf == null)
- return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4);
- } else {
- // the offset for other instructions is in byte 2 of 'off' for
- // primitive types (buf == null)
- return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4);
- }
- }
-
- // ------------------------------------------------------------------------
- // Equals, hashCode and toString
- // ------------------------------------------------------------------------
-
- /**
- * Tests if the given object is equal to this type.
- *
- * @param o
- * the object to be compared to this type.
- * @return true if the given object is equal to this type.
- */
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof Type)) {
- return false;
- }
- Type t = (Type) o;
- if (sort != t.sort) {
- return false;
- }
- if (sort >= ARRAY) {
- if (len != t.len) {
- return false;
- }
- for (int i = off, j = t.off, end = i + len; i < end; i++, j++) {
- if (buf[i] != t.buf[j]) {
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Returns a hash code value for this type.
- *
- * @return a hash code value for this type.
- */
- @Override
- public int hashCode() {
- int hc = 13 * sort;
- if (sort >= ARRAY) {
- for (int i = off, end = i + len; i < end; i++) {
- hc = 17 * (hc + buf[i]);
- }
- }
- return hc;
- }
-
- /**
- * Returns a string representation of this type.
- *
- * @return the descriptor of this type.
- */
- @Override
- public String toString() {
- return getDescriptor();
- }
+ throw new AssertionError();
+ }
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Equals, hashCode and toString.
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Tests if the given object is equal to this type.
+ *
+ * @param object the object to be compared to this type.
+ * @return {@literal true} if the given object is equal to this type.
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (!(object instanceof Type)) {
+ return false;
+ }
+ Type other = (Type) object;
+ if ((sort == INTERNAL ? OBJECT : sort) != (other.sort == INTERNAL ? OBJECT : other.sort)) {
+ return false;
+ }
+ int begin = valueBegin;
+ int end = valueEnd;
+ int otherBegin = other.valueBegin;
+ int otherEnd = other.valueEnd;
+ // Compare the values.
+ if (end - begin != otherEnd - otherBegin) {
+ return false;
+ }
+ for (int i = begin, j = otherBegin; i < end; i++, j++) {
+ if (valueBuffer.charAt(i) != other.valueBuffer.charAt(j)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a hash code value for this type.
+ *
+ * @return a hash code value for this type.
+ */
+ @Override
+ public int hashCode() {
+ int hashCode = 13 * (sort == INTERNAL ? OBJECT : sort);
+ if (sort >= ARRAY) {
+ for (int i = valueBegin, end = valueEnd; i < end; i++) {
+ hashCode = 17 * (hashCode + valueBuffer.charAt(i));
+ }
+ }
+ return hashCode;
+ }
+
+ /**
+ * Returns a string representation of this type.
+ *
+ * @return the descriptor of this type.
+ */
+ @Override
+ public String toString() {
+ return getDescriptor();
+ }
}
diff --git a/src/java/nginx/clojure/asm/TypePath.java b/src/java/nginx/clojure/asm/TypePath.java
new file mode 100644
index 00000000..3e6d8d74
--- /dev/null
+++ b/src/java/nginx/clojure/asm/TypePath.java
@@ -0,0 +1,201 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+
+package nginx.clojure.asm;
+
+/**
+ * The path to a type argument, wildcard bound, array element type, or static inner type within an
+ * enclosing type.
+ *
+ * @author Eric Bruneton
+ */
+public final class TypePath {
+
+ /** A type path step that steps into the element type of an array type. See {@link #getStep}. */
+ public static final int ARRAY_ELEMENT = 0;
+
+ /** A type path step that steps into the nested type of a class type. See {@link #getStep}. */
+ public static final int INNER_TYPE = 1;
+
+ /** A type path step that steps into the bound of a wildcard type. See {@link #getStep}. */
+ public static final int WILDCARD_BOUND = 2;
+
+ /** A type path step that steps into a type argument of a generic type. See {@link #getStep}. */
+ public static final int TYPE_ARGUMENT = 3;
+
+ /**
+ * The byte array where the 'type_path' structure - as defined in the Java Virtual Machine
+ * Specification (JVMS) - corresponding to this TypePath is stored. The first byte of the
+ * structure in this array is given by {@link #typePathOffset}.
+ *
+ * @see JVMS
+ * 4.7.20.2
+ */
+ private final byte[] typePathContainer;
+
+ /** The offset of the first byte of the type_path JVMS structure in {@link #typePathContainer}. */
+ private final int typePathOffset;
+
+ /**
+ * Constructs a new TypePath.
+ *
+ * @param typePathContainer a byte array containing a type_path JVMS structure.
+ * @param typePathOffset the offset of the first byte of the type_path structure in
+ * typePathContainer.
+ */
+ TypePath(final byte[] typePathContainer, final int typePathOffset) {
+ this.typePathContainer = typePathContainer;
+ this.typePathOffset = typePathOffset;
+ }
+
+ /**
+ * Returns the length of this path, i.e. its number of steps.
+ *
+ * @return the length of this path.
+ */
+ public int getLength() {
+ // path_length is stored in the first byte of a type_path.
+ return typePathContainer[typePathOffset];
+ }
+
+ /**
+ * Returns the value of the given step of this path.
+ *
+ * @param index an index between 0 and {@link #getLength()}, exclusive.
+ * @return one of {@link #ARRAY_ELEMENT}, {@link #INNER_TYPE}, {@link #WILDCARD_BOUND}, or {@link
+ * #TYPE_ARGUMENT}.
+ */
+ public int getStep(final int index) {
+ // Returns the type_path_kind of the path element of the given index.
+ return typePathContainer[typePathOffset + 2 * index + 1];
+ }
+
+ /**
+ * Returns the index of the type argument that the given step is stepping into. This method should
+ * only be used for steps whose value is {@link #TYPE_ARGUMENT}.
+ *
+ * @param index an index between 0 and {@link #getLength()}, exclusive.
+ * @return the index of the type argument that the given step is stepping into.
+ */
+ public int getStepArgument(final int index) {
+ // Returns the type_argument_index of the path element of the given index.
+ return typePathContainer[typePathOffset + 2 * index + 2];
+ }
+
+ /**
+ * Converts a type path in string form, in the format used by {@link #toString()}, into a TypePath
+ * object.
+ *
+ * @param typePath a type path in string form, in the format used by {@link #toString()}. May be
+ * {@literal null} or empty.
+ * @return the corresponding TypePath object, or {@literal null} if the path is empty.
+ */
+ public static TypePath fromString(final String typePath) {
+ if (typePath == null || typePath.length() == 0) {
+ return null;
+ }
+ int typePathLength = typePath.length();
+ ByteVector output = new ByteVector(typePathLength);
+ output.putByte(0);
+ int typePathIndex = 0;
+ while (typePathIndex < typePathLength) {
+ char c = typePath.charAt(typePathIndex++);
+ if (c == '[') {
+ output.put11(ARRAY_ELEMENT, 0);
+ } else if (c == '.') {
+ output.put11(INNER_TYPE, 0);
+ } else if (c == '*') {
+ output.put11(WILDCARD_BOUND, 0);
+ } else if (c >= '0' && c <= '9') {
+ int typeArg = c - '0';
+ while (typePathIndex < typePathLength) {
+ c = typePath.charAt(typePathIndex++);
+ if (c >= '0' && c <= '9') {
+ typeArg = typeArg * 10 + c - '0';
+ } else if (c == ';') {
+ break;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+ output.put11(TYPE_ARGUMENT, typeArg);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+ output.data[0] = (byte) (output.length / 2);
+ return new TypePath(output.data, 0);
+ }
+
+ /**
+ * Returns a string representation of this type path. {@link #ARRAY_ELEMENT} steps are represented
+ * with '[', {@link #INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND} steps with '*' and {@link
+ * #TYPE_ARGUMENT} steps with their type argument index in decimal form followed by ';'.
+ */
+ @Override
+ public String toString() {
+ int length = getLength();
+ StringBuilder result = new StringBuilder(length * 2);
+ for (int i = 0; i < length; ++i) {
+ switch (getStep(i)) {
+ case ARRAY_ELEMENT:
+ result.append('[');
+ break;
+ case INNER_TYPE:
+ result.append('.');
+ break;
+ case WILDCARD_BOUND:
+ result.append('*');
+ break;
+ case TYPE_ARGUMENT:
+ result.append(getStepArgument(i)).append(';');
+ break;
+ default:
+ throw new AssertionError();
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Puts the type_path JVMS structure corresponding to the given TypePath into the given
+ * ByteVector.
+ *
+ * @param typePath a TypePath instance, or {@literal null} for empty paths.
+ * @param output where the type path must be put.
+ */
+ static void put(final TypePath typePath, final ByteVector output) {
+ if (typePath == null) {
+ output.putByte(0);
+ } else {
+ int length = typePath.typePathContainer[typePath.typePathOffset] * 2 + 1;
+ output.putByteArray(typePath.typePathContainer, typePath.typePathOffset, length);
+ }
+ }
+}
diff --git a/src/java/nginx/clojure/asm/TypeReference.java b/src/java/nginx/clojure/asm/TypeReference.java
new file mode 100644
index 00000000..7c748f30
--- /dev/null
+++ b/src/java/nginx/clojure/asm/TypeReference.java
@@ -0,0 +1,436 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+
+package nginx.clojure.asm;
+
+/**
+ * A reference to a type appearing in a class, field or method declaration, or on an instruction.
+ * Such a reference designates the part of the class where the referenced type is appearing (e.g. an
+ * 'extends', 'implements' or 'throws' clause, a 'new' instruction, a 'catch' clause, a type cast, a
+ * local variable declaration, etc).
+ *
+ * @author Eric Bruneton
+ */
+public class TypeReference {
+
+ /**
+ * The sort of type references that target a type parameter of a generic class. See {@link
+ * #getSort}.
+ */
+ public static final int CLASS_TYPE_PARAMETER = 0x00;
+
+ /**
+ * The sort of type references that target a type parameter of a generic method. See {@link
+ * #getSort}.
+ */
+ public static final int METHOD_TYPE_PARAMETER = 0x01;
+
+ /**
+ * The sort of type references that target the super class of a class or one of the interfaces it
+ * implements. See {@link #getSort}.
+ */
+ public static final int CLASS_EXTENDS = 0x10;
+
+ /**
+ * The sort of type references that target a bound of a type parameter of a generic class. See
+ * {@link #getSort}.
+ */
+ public static final int CLASS_TYPE_PARAMETER_BOUND = 0x11;
+
+ /**
+ * The sort of type references that target a bound of a type parameter of a generic method. See
+ * {@link #getSort}.
+ */
+ public static final int METHOD_TYPE_PARAMETER_BOUND = 0x12;
+
+ /** The sort of type references that target the type of a field. See {@link #getSort}. */
+ public static final int FIELD = 0x13;
+
+ /** The sort of type references that target the return type of a method. See {@link #getSort}. */
+ public static final int METHOD_RETURN = 0x14;
+
+ /**
+ * The sort of type references that target the receiver type of a method. See {@link #getSort}.
+ */
+ public static final int METHOD_RECEIVER = 0x15;
+
+ /**
+ * The sort of type references that target the type of a formal parameter of a method. See {@link
+ * #getSort}.
+ */
+ public static final int METHOD_FORMAL_PARAMETER = 0x16;
+
+ /**
+ * The sort of type references that target the type of an exception declared in the throws clause
+ * of a method. See {@link #getSort}.
+ */
+ public static final int THROWS = 0x17;
+
+ /**
+ * The sort of type references that target the type of a local variable in a method. See {@link
+ * #getSort}.
+ */
+ public static final int LOCAL_VARIABLE = 0x40;
+
+ /**
+ * The sort of type references that target the type of a resource variable in a method. See {@link
+ * #getSort}.
+ */
+ public static final int RESOURCE_VARIABLE = 0x41;
+
+ /**
+ * The sort of type references that target the type of the exception of a 'catch' clause in a
+ * method. See {@link #getSort}.
+ */
+ public static final int EXCEPTION_PARAMETER = 0x42;
+
+ /**
+ * The sort of type references that target the type declared in an 'instanceof' instruction. See
+ * {@link #getSort}.
+ */
+ public static final int INSTANCEOF = 0x43;
+
+ /**
+ * The sort of type references that target the type of the object created by a 'new' instruction.
+ * See {@link #getSort}.
+ */
+ public static final int NEW = 0x44;
+
+ /**
+ * The sort of type references that target the receiver type of a constructor reference. See
+ * {@link #getSort}.
+ */
+ public static final int CONSTRUCTOR_REFERENCE = 0x45;
+
+ /**
+ * The sort of type references that target the receiver type of a method reference. See {@link
+ * #getSort}.
+ */
+ public static final int METHOD_REFERENCE = 0x46;
+
+ /**
+ * The sort of type references that target the type declared in an explicit or implicit cast
+ * instruction. See {@link #getSort}.
+ */
+ public static final int CAST = 0x47;
+
+ /**
+ * The sort of type references that target a type parameter of a generic constructor in a
+ * constructor call. See {@link #getSort}.
+ */
+ public static final int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48;
+
+ /**
+ * The sort of type references that target a type parameter of a generic method in a method call.
+ * See {@link #getSort}.
+ */
+ public static final int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49;
+
+ /**
+ * The sort of type references that target a type parameter of a generic constructor in a
+ * constructor reference. See {@link #getSort}.
+ */
+ public static final int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A;
+
+ /**
+ * The sort of type references that target a type parameter of a generic method in a method
+ * reference. See {@link #getSort}.
+ */
+ public static final int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B;
+
+ /**
+ * The target_type and target_info structures - as defined in the Java Virtual Machine
+ * Specification (JVMS) - corresponding to this type reference. target_type uses one byte, and all
+ * the target_info union fields use up to 3 bytes (except localvar_target, handled with the
+ * specific method {@link MethodVisitor#visitLocalVariableAnnotation}). Thus, both structures can
+ * be stored in an int.
+ *
+ *
This int field stores target_type (called the TypeReference 'sort' in the public API of this
+ * class) in its most significant byte, followed by the target_info fields. Depending on
+ * target_type, 1, 2 or even 3 least significant bytes of this field are unused. target_info
+ * fields which reference bytecode offsets are set to 0 (these offsets are ignored in ClassReader,
+ * and recomputed in MethodWriter).
+ *
+ * @see JVMS
+ * 4.7.20
+ * @see JVMS
+ * 4.7.20.1
+ */
+ private final int targetTypeAndInfo;
+
+ /**
+ * Constructs a new TypeReference.
+ *
+ * @param typeRef the int encoded value of the type reference, as received in a visit method
+ * related to type annotations, such as {@link ClassVisitor#visitTypeAnnotation}.
+ */
+ public TypeReference(final int typeRef) {
+ this.targetTypeAndInfo = typeRef;
+ }
+
+ /**
+ * Returns a type reference of the given sort.
+ *
+ * @param sort one of {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link
+ * #LOCAL_VARIABLE}, {@link #RESOURCE_VARIABLE}, {@link #INSTANCEOF}, {@link #NEW}, {@link
+ * #CONSTRUCTOR_REFERENCE}, or {@link #METHOD_REFERENCE}.
+ * @return a type reference of the given sort.
+ */
+ public static TypeReference newTypeReference(final int sort) {
+ return new TypeReference(sort << 24);
+ }
+
+ /**
+ * Returns a reference to a type parameter of a generic class or method.
+ *
+ * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}.
+ * @param paramIndex the type parameter index.
+ * @return a reference to the given generic class or method type parameter.
+ */
+ public static TypeReference newTypeParameterReference(final int sort, final int paramIndex) {
+ return new TypeReference((sort << 24) | (paramIndex << 16));
+ }
+
+ /**
+ * Returns a reference to a type parameter bound of a generic class or method.
+ *
+ * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}.
+ * @param paramIndex the type parameter index.
+ * @param boundIndex the type bound index within the above type parameters.
+ * @return a reference to the given generic class or method type parameter bound.
+ */
+ public static TypeReference newTypeParameterBoundReference(
+ final int sort, final int paramIndex, final int boundIndex) {
+ return new TypeReference((sort << 24) | (paramIndex << 16) | (boundIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the super class or to an interface of the 'implements' clause of a
+ * class.
+ *
+ * @param itfIndex the index of an interface in the 'implements' clause of a class, or -1 to
+ * reference the super class of the class.
+ * @return a reference to the given super type of a class.
+ */
+ public static TypeReference newSuperTypeReference(final int itfIndex) {
+ return new TypeReference((CLASS_EXTENDS << 24) | ((itfIndex & 0xFFFF) << 8));
+ }
+
+ /**
+ * Returns a reference to the type of a formal parameter of a method.
+ *
+ * @param paramIndex the formal parameter index.
+ * @return a reference to the type of the given method formal parameter.
+ */
+ public static TypeReference newFormalParameterReference(final int paramIndex) {
+ return new TypeReference((METHOD_FORMAL_PARAMETER << 24) | (paramIndex << 16));
+ }
+
+ /**
+ * Returns a reference to the type of an exception, in a 'throws' clause of a method.
+ *
+ * @param exceptionIndex the index of an exception in a 'throws' clause of a method.
+ * @return a reference to the type of the given exception.
+ */
+ public static TypeReference newExceptionReference(final int exceptionIndex) {
+ return new TypeReference((THROWS << 24) | (exceptionIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of the exception declared in a 'catch' clause of a method.
+ *
+ * @param tryCatchBlockIndex the index of a try catch block (using the order in which they are
+ * visited with visitTryCatchBlock).
+ * @return a reference to the type of the given exception.
+ */
+ public static TypeReference newTryCatchReference(final int tryCatchBlockIndex) {
+ return new TypeReference((EXCEPTION_PARAMETER << 24) | (tryCatchBlockIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of a type argument in a constructor or method call or
+ * reference.
+ *
+ * @param sort one of {@link #CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link
+ * #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link
+ * #METHOD_REFERENCE_TYPE_ARGUMENT}.
+ * @param argIndex the type argument index.
+ * @return a reference to the type of the given type argument.
+ */
+ public static TypeReference newTypeArgumentReference(final int sort, final int argIndex) {
+ return new TypeReference((sort << 24) | argIndex);
+ }
+
+ /**
+ * Returns the sort of this type reference.
+ *
+ * @return one of {@link #CLASS_TYPE_PARAMETER}, {@link #METHOD_TYPE_PARAMETER}, {@link
+ * #CLASS_EXTENDS}, {@link #CLASS_TYPE_PARAMETER_BOUND}, {@link #METHOD_TYPE_PARAMETER_BOUND},
+ * {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link
+ * #METHOD_FORMAL_PARAMETER}, {@link #THROWS}, {@link #LOCAL_VARIABLE}, {@link
+ * #RESOURCE_VARIABLE}, {@link #EXCEPTION_PARAMETER}, {@link #INSTANCEOF}, {@link #NEW},
+ * {@link #CONSTRUCTOR_REFERENCE}, {@link #METHOD_REFERENCE}, {@link #CAST}, {@link
+ * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link
+ * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}.
+ */
+ public int getSort() {
+ return targetTypeAndInfo >>> 24;
+ }
+
+ /**
+ * Returns the index of the type parameter referenced by this type reference. This method must
+ * only be used for type references whose sort is {@link #CLASS_TYPE_PARAMETER}, {@link
+ * #METHOD_TYPE_PARAMETER}, {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link
+ * #METHOD_TYPE_PARAMETER_BOUND}.
+ *
+ * @return a type parameter index.
+ */
+ public int getTypeParameterIndex() {
+ return (targetTypeAndInfo & 0x00FF0000) >> 16;
+ }
+
+ /**
+ * Returns the index of the type parameter bound, within the type parameter {@link
+ * #getTypeParameterIndex}, referenced by this type reference. This method must only be used for
+ * type references whose sort is {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link
+ * #METHOD_TYPE_PARAMETER_BOUND}.
+ *
+ * @return a type parameter bound index.
+ */
+ public int getTypeParameterBoundIndex() {
+ return (targetTypeAndInfo & 0x0000FF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the "super type" of a class that is referenced by this type reference.
+ * This method must only be used for type references whose sort is {@link #CLASS_EXTENDS}.
+ *
+ * @return the index of an interface in the 'implements' clause of a class, or -1 if this type
+ * reference references the type of the super class.
+ */
+ public int getSuperTypeIndex() {
+ return (short) ((targetTypeAndInfo & 0x00FFFF00) >> 8);
+ }
+
+ /**
+ * Returns the index of the formal parameter whose type is referenced by this type reference. This
+ * method must only be used for type references whose sort is {@link #METHOD_FORMAL_PARAMETER}.
+ *
+ * @return a formal parameter index.
+ */
+ public int getFormalParameterIndex() {
+ return (targetTypeAndInfo & 0x00FF0000) >> 16;
+ }
+
+ /**
+ * Returns the index of the exception, in a 'throws' clause of a method, whose type is referenced
+ * by this type reference. This method must only be used for type references whose sort is {@link
+ * #THROWS}.
+ *
+ * @return the index of an exception in the 'throws' clause of a method.
+ */
+ public int getExceptionIndex() {
+ return (targetTypeAndInfo & 0x00FFFF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the try catch block (using the order in which they are visited with
+ * visitTryCatchBlock), whose 'catch' type is referenced by this type reference. This method must
+ * only be used for type references whose sort is {@link #EXCEPTION_PARAMETER} .
+ *
+ * @return the index of an exception in the 'throws' clause of a method.
+ */
+ public int getTryCatchBlockIndex() {
+ return (targetTypeAndInfo & 0x00FFFF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the type argument referenced by this type reference. This method must only
+ * be used for type references whose sort is {@link #CAST}, {@link
+ * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link
+ * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}.
+ *
+ * @return a type parameter index.
+ */
+ public int getTypeArgumentIndex() {
+ return targetTypeAndInfo & 0xFF;
+ }
+
+ /**
+ * Returns the int encoded value of this type reference, suitable for use in visit methods related
+ * to type annotations, like visitTypeAnnotation.
+ *
+ * @return the int encoded value of this type reference.
+ */
+ public int getValue() {
+ return targetTypeAndInfo;
+ }
+
+ /**
+ * Puts the given target_type and target_info JVMS structures into the given ByteVector.
+ *
+ * @param targetTypeAndInfo a target_type and a target_info structures encoded as in {@link
+ * #targetTypeAndInfo}. LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported.
+ * @param output where the type reference must be put.
+ */
+ static void putTarget(final int targetTypeAndInfo, final ByteVector output) {
+ switch (targetTypeAndInfo >>> 24) {
+ case CLASS_TYPE_PARAMETER:
+ case METHOD_TYPE_PARAMETER:
+ case METHOD_FORMAL_PARAMETER:
+ output.putShort(targetTypeAndInfo >>> 16);
+ break;
+ case FIELD:
+ case METHOD_RETURN:
+ case METHOD_RECEIVER:
+ output.putByte(targetTypeAndInfo >>> 24);
+ break;
+ case CAST:
+ case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
+ case METHOD_INVOCATION_TYPE_ARGUMENT:
+ case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
+ case METHOD_REFERENCE_TYPE_ARGUMENT:
+ output.putInt(targetTypeAndInfo);
+ break;
+ case CLASS_EXTENDS:
+ case CLASS_TYPE_PARAMETER_BOUND:
+ case METHOD_TYPE_PARAMETER_BOUND:
+ case THROWS:
+ case EXCEPTION_PARAMETER:
+ case INSTANCEOF:
+ case NEW:
+ case CONSTRUCTOR_REFERENCE:
+ case METHOD_REFERENCE:
+ output.put12(targetTypeAndInfo >>> 24, (targetTypeAndInfo & 0xFFFF00) >> 8);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/src/java/nginx/clojure/asm/commons/AdviceAdapter.java b/src/java/nginx/clojure/asm/commons/AdviceAdapter.java
index 9e983dc8..3faf9080 100644
--- a/src/java/nginx/clojure/asm/commons/AdviceAdapter.java
+++ b/src/java/nginx/clojure/asm/commons/AdviceAdapter.java
@@ -1,32 +1,30 @@
-/***
- * ASM: a very small and fast Java bytecode manipulation framework
- * Copyright (c) 2000-2011 INRIA, France Telecom
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
package nginx.clojure.asm.commons;
import java.util.ArrayList;
@@ -34,6 +32,7 @@
import java.util.List;
import java.util.Map;
+import nginx.clojure.asm.ConstantDynamic;
import nginx.clojure.asm.Handle;
import nginx.clojure.asm.Label;
import nginx.clojure.asm.MethodVisitor;
@@ -41,585 +40,632 @@
import nginx.clojure.asm.Type;
/**
- * A {@link nginx.clojure.asm.MethodVisitor} to insert before, after and around
- * advices in methods and constructors.
- *
- * The behavior for constructors is like this:
- *
- *
- * - as long as the INVOKESPECIAL for the object initialization has not been
- * reached, every bytecode instruction is dispatched in the ctor code visitor
- *
- * - when this one is reached, it is only added in the ctor code visitor and a
- * JP invoke is added
- *
- * - after that, only the other code visitor receives the instructions
- *
- *
- *
+ * A {@link MethodVisitor} to insert before, after and around advices in methods and constructors.
+ * For constructors, the code keeps track of the elements on the stack in order to detect when the
+ * super class constructor is called (note that there can be multiple such calls in different
+ * branches). {@code onMethodEnter} is called after each super class constructor call, because the
+ * object cannot be used before it is properly initialized.
+ *
* @author Eugene Kuleshov
* @author Eric Bruneton
*/
public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes {
- private static final Object THIS = new Object();
-
- private static final Object OTHER = new Object();
-
- protected int methodAccess;
-
- protected String methodDesc;
-
- private boolean constructor;
-
- private boolean superInitialized;
-
- private List