@@ -53,6 +55,12 @@ public class Coroutine implements Runnable, Serializable {
private static final long serialVersionUID = 2783452871536981L;
+ private static boolean useNative = false;
+
+ private static NativeCoroutineBuilder nativeCoroutineBuilder;
+
+ private NativeCoroutine nativeCoroutine;
+
public enum State {
/** The Coroutine has not yet been executed */
NEW,
@@ -79,6 +87,10 @@ public interface FinishAwaredRunnable extends Runnable {
private Object locals;
private Object inheritableLocals;
+ public static boolean isUseNative() {
+ return useNative;
+ }
+
/**
* Suspend the currently running Coroutine on the calling thread.
*
@@ -86,7 +98,11 @@ public interface FinishAwaredRunnable extends Runnable {
* @throws java.lang.IllegalStateException If not called from a Coroutine
*/
public static void yield() throws SuspendExecution, IllegalStateException {
- throw new Error("Calling function not instrumented");
+ if (useNative) {
+ nativeCoroutineBuilder.yield();
+ } else {
+ throw new Error("Calling function not instrumented");
+ }
}
/**
@@ -121,29 +137,40 @@ public Coroutine(Runnable proto) {
*/
public Coroutine(Runnable proto, int stackSize) {
this.proto = proto;
- this.stack = new Stack(this, stackSize);
- this.cstack = new SuspendableConstructorUtilStack(stackSize/8);
this.state = State.NEW;
Thread thread = Thread.currentThread();
Object currentLocals = HackUtils.getThreadLocals(Thread.currentThread());
this.locals = HackUtils.cloneThreadLocalMap(currentLocals);
- try {
- HackUtils.setThreadLocals(thread, this.locals);
- Stack.setStack(this.stack);
- SuspendableConstructorUtilStack.setStack(this.cstack);
- }finally {
- HackUtils.setThreadLocals(thread, currentLocals);
- }
- Object inheritableLocals = HackUtils.getInheritableThreadLocals(Thread.currentThread());
- if (inheritableLocals != null) {
- this.inheritableLocals = HackUtils.createInheritedMap(inheritableLocals);
- }
-
- if(proto == null) {
- throw new NullPointerException("proto");
+ if (useNative) {
+ this.nativeCoroutine = nativeCoroutineBuilder.build(proto);
+ this.stack = new Stack(this, 0);;
+ try {
+ HackUtils.setThreadLocals(thread, this.locals);
+ Stack.setStack(this.stack);
+ } finally {
+ HackUtils.setThreadLocals(thread, currentLocals);
+ }
+
+ this.cstack = null;
+ } else {
+ this.stack = new Stack(this, stackSize);
+ this.cstack = new SuspendableConstructorUtilStack(stackSize/8);
+ try {
+ HackUtils.setThreadLocals(thread, this.locals);
+ Stack.setStack(this.stack);
+ SuspendableConstructorUtilStack.setStack(this.cstack);
+ } finally {
+ HackUtils.setThreadLocals(thread, currentLocals);
+ }
+
+ Object inheritableLocals = HackUtils.getInheritableThreadLocals(Thread.currentThread());
+ if (inheritableLocals != null) {
+ this.inheritableLocals = HackUtils.createInheritedMap(inheritableLocals);
+ }
+
+ assert isInstrumented(proto) : "Not instrumented";
}
- assert isInstrumented(proto) : "Not instrumented";
}
public void reset() {
@@ -153,8 +180,13 @@ public void reset() {
this.locals = HackUtils.cloneThreadLocalMap(currentLocals);
try {
HackUtils.setThreadLocals(thread, this.locals);
- Stack.setStack(this.stack);
- SuspendableConstructorUtilStack.setStack(this.cstack);
+ if (useNative) {
+ this.nativeCoroutine = nativeCoroutineBuilder.build(proto);
+ Stack.setStack(this.stack);
+ } else {
+ Stack.setStack(this.stack);
+ SuspendableConstructorUtilStack.setStack(this.cstack);
+ }
}finally {
HackUtils.setThreadLocals(thread, currentLocals);
}
@@ -204,7 +236,12 @@ public State getState() {
return state;
}
-
+ /**
+ * @param state the state to set
+ */
+ protected void setState(State state) {
+ this.state = state;
+ }
/**
* Runs the Coroutine until it is finished or suspended. This method must only
@@ -241,14 +278,20 @@ public void resume() {
state = State.RUNNING;
// Stack.setStack(stack);
// SuspendableConstructorUtilStack.setStack(cstack);
-
- try {
+ if (useNative) {
+ nativeCoroutine.resume();
+ if (state == State.SUSPENDED) {
+ result = State.SUSPENDED;
+ }
+ } else {
+ try {
proto.run();
- } catch (SuspendExecution ex) {
- assert ex == SuspendExecution.instance;
- result = State.SUSPENDED;
- //stack.dump();
- stack.resumeStack();
+ } catch (SuspendExecution ex) {
+ assert ex == SuspendExecution.instance;
+ result = State.SUSPENDED;
+ //stack.dump();
+ stack.resumeStack();
+ }
}
} finally {
if (result == State.FINISHED) {
@@ -333,4 +376,13 @@ private boolean isInstrumented(Runnable proto) {
return true; // it's just a check - make sure we don't fail if something goes wrong
}
}
+
+ public static void prepareNative() {
+ useNative = true;
+ try {
+ nativeCoroutineBuilder = (NativeCoroutineBuilder) Coroutine.class.forName("nginx.clojure.NativeCoroutineBuilderImp").newInstance();
+ } catch (Throwable e) {
+ throw new IllegalStateException("can not load nginx.clojure.NativeCoroutineBuilderImp", e);
+ }
+ }
}
diff --git a/src/java/nginx/clojure/EtlListHeaderHolder.java b/src/java/nginx/clojure/EtlListHeaderHolder.java
new file mode 100644
index 00000000..9c0aafd8
--- /dev/null
+++ b/src/java/nginx/clojure/EtlListHeaderHolder.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright (C) Zhang,Yuexiang (xfeep)
+ *
+ */
+package nginx.clojure;
+
+import static nginx.clojure.MiniConstants.DEFAULT_ENCODING;
+import static nginx.clojure.MiniConstants.HEADERS_NAMES;
+import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_ARRAY_ELTS_OFFSET;
+import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET;
+import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET;
+import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_PTR_SIZE;
+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_OK;
+import static nginx.clojure.NginxClojureRT.UNSAFE;
+import static nginx.clojure.NginxClojureRT.fetchNGXInt;
+import static nginx.clojure.NginxClojureRT.fetchNGXString;
+import static nginx.clojure.NginxClojureRT.ngx_array_destory;
+import static nginx.clojure.NginxClojureRT.ngx_array_init;
+import static nginx.clojure.NginxClojureRT.ngx_array_push_n;
+import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_shadow_copy_ngx_str;
+import static nginx.clojure.NginxClojureRT.pushNGXInt;
+import static nginx.clojure.NginxClojureRT.pushNGXString;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+
+public class EtlListHeaderHolder extends AbstractHeaderHolder {
+
+ public EtlListHeaderHolder(String name, long offset, long headersOffset) {
+ this.offset = offset;
+ this.name = name;
+ this.headersOffset = headersOffset;
+ if (offset < 0) {
+ throw new IllegalArgumentException("offset of " + name + " is invalid, must >=0 but meets " + offset );
+ }
+ if (headersOffset < 0) {
+ throw new IllegalArgumentException("headersOffset of " + name + " is invalid, must >=0 but meets " + headersOffset );
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void push(long h, long pool, Object v) {
+
+ List seq = null;
+ if (v == null || v instanceof String) {
+ String val = (String) v;
+ seq = Arrays.asList(val);
+ }else if (v instanceof List) {
+ seq = (List) v;
+ }else if (v.getClass().isArray()){
+ seq = (List)Arrays.asList((Object[])v);
+ }
+
+ int c = seq.size();
+ if (c == 0) {
+ clear(h);
+ return;
+ }
+
+ long lp = h + offset;
+ long p = UNSAFE.getAddress(lp);
+ long pname = HEADERS_NAMES.get(name);
+
+ if (p == 0) {
+ for (String val : seq) {
+ if (val != null) {
+ p = NginxClojureRT.ngx_list_push(h + NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET);
+ if (p == 0) {
+ throw new RuntimeException("can not push ngx etl list for headers");
+ }
+ UNSAFE.putAddress(lp, p);
+ pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 1);
+ ngx_http_clojure_mem_shadow_copy_ngx_str(pname, p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET);
+ pushNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, val, DEFAULT_ENCODING, pool);
+ lp = p + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET;
+ }
+ }
+ } else {
+ for (String val : seq) {
+ if (val != null) {
+ if (p == 0) {
+ p = NginxClojureRT.ngx_list_push(h + NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET);
+ if (p == 0) {
+ throw new RuntimeException("can not push ngx etl list for headers");
+ }
+ UNSAFE.putAddress(lp, p);
+
+ }
+ pushNGXInt(p + NGX_HTTP_CLOJURE_TEL_HASH_OFFSET, 1);
+ ngx_http_clojure_mem_shadow_copy_ngx_str(pname, p + NGX_HTTP_CLOJURE_TEL_KEY_OFFSET);
+ pushNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET, val, DEFAULT_ENCODING, pool);
+ lp = p + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET;
+ p = UNSAFE.getAddress(lp);
+ }
+ }
+ }
+
+ UNSAFE.putAddress(lp, 0);
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ public Object fetch(long h) {
+ long p = UNSAFE.getAddress(h + offset);
+ if (p == 0) {
+ return null;
+ }
+
+ ArrayList list = new ArrayList(2);
+ while (p != 0) {
+ list.add(fetchNGXString(p + NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET , DEFAULT_ENCODING));
+ p = UNSAFE.getAddress(p + NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET);
+ }
+ return list.size() == 1 ? list.get(0) : list.toArray(new String[list.size()]);
+ }
+
+ @Override
+ public boolean exists(long h) {
+ return h != 0 && UNSAFE.getAddress(h + offset) != 0;
+ }
+
+}
diff --git a/src/java/nginx/clojure/MiniConstants.java b/src/java/nginx/clojure/MiniConstants.java
index 32e8116f..fdd45d7f 100644
--- a/src/java/nginx/clojure/MiniConstants.java
+++ b/src/java/nginx/clojure/MiniConstants.java
@@ -163,6 +163,12 @@ public class MiniConstants {
public static long NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET;
public static int NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_IDX = 15;
public static long NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_OFFSET;
+
+ //#if (nginx_version >= 1023000)
+ public static int NGX_HTTP_CLOJURE_TEL_NEXT_IDX = 96;
+ public static long NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET;
+ //#endif
+
public static int NGX_HTTP_CLOJURE_CHAINT_SIZE_IDX = 16;
public static long NGX_HTTP_CLOJURE_CHAINT_SIZE;
@@ -384,7 +390,7 @@ public class MiniConstants {
//nginx clojure java runtime required the lowest version of nginx-clojure c module
public static long NGINX_CLOJURE_RT_REQUIRED_LVER = 5002;
- public static long NGINX_CLOJURE_RT_VER = 5003;
+ public static long NGINX_CLOJURE_RT_VER = 6001;
//ngx_core.h
public final static int NGX_OK = 0;
@@ -407,11 +413,14 @@ public class MiniConstants {
public final static int NGX_HTTP_CONTENT_PHASE = 9;
public final static int NGX_HTTP_LOG_PHASE = 10;
+ //fake phase for load balance handler
+ public final static int NGX_HTTP_LOAD_BALANCE_PHASE = 16;
+
//fake phase for filter
- public final static int NGX_HTTP_INIT_PROCESS_PHASE= 17;
- public final static int NGX_HTTP_HEADER_FILTER_PHASE= 18;
- public final static int NGX_HTTP_BODY_FILTER_PHASE= 19;
- public final static int NGX_HTTP_EXIT_PROCESS_PHASE= 20;
+ public final static int NGX_HTTP_INIT_PROCESS_PHASE = 17;
+ public final static int NGX_HTTP_HEADER_FILTER_PHASE = 18;
+ public final static int NGX_HTTP_BODY_FILTER_PHASE = 19;
+ public final static int NGX_HTTP_EXIT_PROCESS_PHASE = 20;
/*fake chain for header filter*/
public final static int NGX_HTTP_HEADER_FILTER = -1;
diff --git a/src/java/nginx/clojure/NativeCoroutineBuilder.java b/src/java/nginx/clojure/NativeCoroutineBuilder.java
new file mode 100644
index 00000000..68bd5815
--- /dev/null
+++ b/src/java/nginx/clojure/NativeCoroutineBuilder.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (C) Zhang,Yuexiang (xfeep)
+ *
+ */
+package nginx.clojure;
+
+/**
+ * @author Zhang,Yuexiang (xfeep)
+ *
+ */
+public interface NativeCoroutineBuilder {
+
+ static interface NativeCoroutine {
+ void resume();
+ }
+
+ public NativeCoroutine build(Runnable r);
+
+ public boolean yield();
+}
diff --git a/src/java/nginx/clojure/NginxClojureRT.java b/src/java/nginx/clojure/NginxClojureRT.java
index 7b06dcc0..93e36e61 100644
--- a/src/java/nginx/clojure/NginxClojureRT.java
+++ b/src/java/nginx/clojure/NginxClojureRT.java
@@ -6,6 +6,10 @@
+import static nginx.clojure.MiniConstants.NGINX_VER;
+import static nginx.clojure.MiniConstants.NGX_HTTP_CLOJURE_REQ_POOL_OFFSET;
+import static nginx.clojure.NginxClojureRT.UNSAFE;
+
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.Socket;
@@ -274,10 +278,15 @@ public static long ngx_http_clojure_websocket_upgrade(long req) {
static {
//be friendly to lein ring testing
- getLog();
- initUnsafe();
- appEventListenerManager = new AppEventListenerManager();
- processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
+ try {
+ getLog();
+ initUnsafe();
+ appEventListenerManager = new AppEventListenerManager();
+ processId = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
+ } catch (Throwable e) {
+ //to be friendly to nginx jni error log
+ e.printStackTrace();
+ }
}
public static AppEventListenerManager getAppEventListenerManager() {
@@ -444,8 +453,11 @@ private static void initWorkers(int n) {
"worker won't be blocked when access services provide by the same nginx instance");
n = Runtime.getRuntime().availableProcessors() * 2;
}
- }else {
+ } else {
log.info("java agent configured so we turn on coroutine support!");
+ if (JavaAgent.db.isEnableNativeCoroutine()) {
+ Coroutine.prepareNative();
+ }
if (n > 0) {
log.warn("found jvm_workers = %d, and not = 0 we just ignored!", n);
}
@@ -542,6 +554,9 @@ private static NginxHeaderHolder safeBuildKnownTableEltHeaderHolder(String name,
private static NginxHeaderHolder safeBuildKnownArrayHeaderHolder(String name, long offset, long headersOffset) {
if (offset >= 0) {
+ if (NGINX_VER >= 1023000) {
+ return new EtlListHeaderHolder(name, offset, headersOffset);
+ }
return new ArrayHeaderHolder(name, offset, headersOffset);
}
return new UnknownHeaderHolder(name, headersOffset);
@@ -601,6 +616,7 @@ private static synchronized void initMemIndex(long idxpt) {
NGX_HTTP_CLOJURE_TEL_KEY_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_TEL_KEY_IDX];
NGX_HTTP_CLOJURE_TEL_VALUE_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_TEL_VALUE_IDX];
NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_TEL_LOWCASE_KEY_IDX];
+ NGX_HTTP_CLOJURE_TEL_NEXT_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_TEL_NEXT_IDX];
NGX_HTTP_CLOJURE_REQT_SIZE = MEM_INDEX[NGX_HTTP_CLOJURE_REQT_SIZE_IDX];
NGX_HTTP_CLOJURE_REQ_METHOD_OFFSET = MEM_INDEX[NGX_HTTP_CLOJURE_REQ_METHOD_IDX];
@@ -785,7 +801,7 @@ private static synchronized void initMemIndex(long idxpt) {
KNOWN_RESP_HEADERS.put("WWW-Authenticate", safeBuildKnownTableEltHeaderHolder("WWW-Authenticate", NGX_HTTP_CLOJURE_HEADERSO_WWW_AUTHENTICATE_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET));
KNOWN_RESP_HEADERS.put("Expires", safeBuildKnownTableEltHeaderHolder("Expires", NGX_HTTP_CLOJURE_HEADERSO_EXPIRES_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET));
KNOWN_RESP_HEADERS.put("Etag", safeBuildKnownTableEltHeaderHolder("Etag", NGX_HTTP_CLOJURE_HEADERSO_ETAG_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET));
- KNOWN_RESP_HEADERS.put("Cache-Control", new ArrayHeaderHolder("Cache-Control", NGX_HTTP_CLOJURE_HEADERSO_CACHE_CONTROL_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET));
+ KNOWN_RESP_HEADERS.put("Cache-Control", safeBuildKnownArrayHeaderHolder("Cache-Control", NGX_HTTP_CLOJURE_HEADERSO_CACHE_CONTROL_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET));
KNOWN_RESP_HEADERS.put("Content-Type", RESP_CONTENT_TYPE_HOLDER = new ResponseContentTypeHolder());
KNOWN_RESP_HEADERS.put("Content-Length", new OffsetHeaderHolder("Content-Length", NGX_HTTP_CLOJURE_HEADERSO_CONTENT_LENGTH_N_OFFSET, NGX_HTTP_CLOJURE_HEADERSO_HEADERS_OFFSET) );
@@ -861,29 +877,38 @@ public static synchronized int registerCode(int phase, long typeNStr, long nameN
String type = fetchNGXString(typeNStr, DEFAULT_ENCODING);
String name = fetchNGXString(nameNStr, DEFAULT_ENCODING);
String code = fetchNGXString(codeNStr, DEFAULT_ENCODING);
-
NginxHandler handler = NginxHandlerFactory.fetchHandler(phase, type, name, code);
HANDLERS.add(handler);
- if (pros != 0) {
- Map properties = new ArrayMap();
- int size = fetchNGXInt(pros + NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET);
- long ele = UNSAFE.getAddress(pros + NGX_HTTP_CLOJURE_ARRAY_ELTS_OFFSET);
- for (int i = 0; i < size; i++) {
- long kv = ele + i * NGX_HTTP_CLOJURE_KEYVALT_SIZE;
- properties.put(fetchNGXString(kv + NGX_HTTP_CLOJURE_KEYVALT_KEY_OFFSET, DEFAULT_ENCODING),
- fetchNGXString(kv + NGX_HTTP_CLOJURE_KEYVALT_VALUE_OFFSET, DEFAULT_ENCODING));
- }
- for (Entry en : properties.entrySet()) {
- en.setValue(evalSimpleExp(en.getValue(), properties));
- }
- if (handler instanceof Configurable) {
- Configurable cr = (Configurable) handler;
- cr.config(properties);
- }else {
- log.warn("%s is not an instance of nginx.clojure.Configurable, so properties will be ignored!",
- handler.getClass());
+ Runnable runnable = new Runnable() {
+ public void run() {
+ if (pros != 0) {
+ Map properties = new ArrayMap();
+ int size = fetchNGXInt(pros + NGX_HTTP_CLOJURE_ARRAY_NELTS_OFFSET);
+ long ele = UNSAFE.getAddress(pros + NGX_HTTP_CLOJURE_ARRAY_ELTS_OFFSET);
+ for (int i = 0; i < size; i++) {
+ long kv = ele + i * NGX_HTTP_CLOJURE_KEYVALT_SIZE;
+ properties.put(fetchNGXString(kv + NGX_HTTP_CLOJURE_KEYVALT_KEY_OFFSET, DEFAULT_ENCODING),
+ fetchNGXString(kv + NGX_HTTP_CLOJURE_KEYVALT_VALUE_OFFSET, DEFAULT_ENCODING));
+ }
+ for (Entry en : properties.entrySet()) {
+ en.setValue(evalSimpleExp(en.getValue(), properties));
+ }
+ if (handler instanceof Configurable) {
+ Configurable cr = (Configurable) handler;
+ cr.config(properties);
+ }else {
+ log.warn("%s is not an instance of nginx.clojure.Configurable, so properties will be ignored!",
+ handler.getClass());
+ }
+ }
}
+ };
+ if (coroutineEnabled) {
+ new Coroutine(runnable).resume();
+ } else {
+ runnable.run();
}
+
return HANDLERS.size() - 1;
}
@@ -1527,6 +1552,38 @@ protected static long handleReturnCodeFromHandler(long r, int phase, long rc, in
return rc;
}
+ public static int handleLoadBalancerResponse(NginxRequest req, long c, NginxResponse resp) {
+ if (resp == null) {
+ return NGX_HTTP_NOT_FOUND;
+ }
+
+ int status = resp.fetchStatus(NGX_ERROR);
+ if (status != NGX_HTTP_OK) {
+ return status;
+ }
+
+ Object body = resp.fetchBody();
+ if (body == null) {
+ return NGX_HTTP_NOT_FOUND;
+ }
+
+ long idxOrLenAddr = UNSAFE.getAddress(c);
+ long urlAddr = UNSAFE.getAddress(c + NGX_HTTP_CLOJURE_UINT_SIZE);
+ long pool = UNSAFE.getAddress(req.nativeRequest() + NGX_HTTP_CLOJURE_REQ_POOL_OFFSET);
+ if (body instanceof String) {
+ String url = (String)body;
+ pushNGXInt(idxOrLenAddr, url.length());
+ pushString(urlAddr, url, DEFAULT_ENCODING, pool);
+ } else if (body instanceof Integer) {
+ pushNGXInt(idxOrLenAddr, (Integer)body);
+ } else {
+ log.error("bad load balancer result type :" + body.getClass() + ", should be integer or string");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
public static int handleResponse(NginxRequest r, final NginxResponse resp) {
if (Thread.currentThread() != NGINX_MAIN_THREAD) {
throw new RuntimeException("handleResponse can not be called out of nginx clojure main thread!");
@@ -1536,6 +1593,7 @@ public static int handleResponse(NginxRequest r, final NginxResponse resp) {
return NGX_HTTP_NOT_FOUND;
}
int phase = r.phase();
+
if (resp.type() == NginxResponse.TYPE_FAKE_PHASE_DONE) {
if (phase == NGX_HTTP_REWRITE_PHASE || phase == NGX_HTTP_ACCESS_PHASE) {
return NGX_DECLINED;
diff --git a/src/java/nginx/clojure/NginxSimpleHandler.java b/src/java/nginx/clojure/NginxSimpleHandler.java
index 08616a54..0f8af0d1 100644
--- a/src/java/nginx/clojure/NginxSimpleHandler.java
+++ b/src/java/nginx/clojure/NginxSimpleHandler.java
@@ -16,6 +16,7 @@
import static nginx.clojure.MiniConstants.NGX_HTTP_HEADER_FILTER_PHASE;
import static nginx.clojure.MiniConstants.NGX_HTTP_INTERNAL_SERVER_ERROR;
import static nginx.clojure.MiniConstants.NGX_HTTP_LOG_PHASE;
+import static nginx.clojure.MiniConstants.NGX_HTTP_LOAD_BALANCE_PHASE;
import static nginx.clojure.MiniConstants.NGX_HTTP_NO_CONTENT;
import static nginx.clojure.MiniConstants.NGX_HTTP_OK;
import static nginx.clojure.MiniConstants.NGX_HTTP_SWITCHING_PROTOCOLS;
@@ -24,6 +25,7 @@
import static nginx.clojure.NginxClojureRT.UNSAFE;
import static nginx.clojure.NginxClojureRT.coroutineEnabled;
import static nginx.clojure.NginxClojureRT.handleResponse;
+import static nginx.clojure.NginxClojureRT.handleLoadBalancerResponse;
import static nginx.clojure.NginxClojureRT.log;
import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_build_file_chain;
import static nginx.clojure.NginxClojureRT.ngx_http_clojure_mem_build_temp_chain;
@@ -103,6 +105,11 @@ public int execute(final long r, final long c) {
final int phase = req.phase();
boolean isWebSocket = req.isWebSocket();
+ if (phase == NGX_HTTP_LOAD_BALANCE_PHASE) {
+ NginxResponse resp = handleRequest(req);
+ return handleLoadBalancerResponse(req, c, resp);
+ }
+
if (forcePrefetchAllProperties) {
//for safe access with another thread
req.prefetchAll(DefinedPrefetch.ALL_HEADERS,
diff --git a/src/java/nginx/clojure/Stack.java b/src/java/nginx/clojure/Stack.java
index da8557e0..5f8daffb 100644
--- a/src/java/nginx/clojure/Stack.java
+++ b/src/java/nginx/clojure/Stack.java
@@ -74,6 +74,12 @@ public final class Stack implements Serializable {
Stack(Coroutine co, int stackSize) {
+
+ if (Coroutine.isUseNative()) {
+ this.co = co;
+ return;
+ }
+
if(stackSize <= 0) {
throw new IllegalArgumentException("stackSize");
}
@@ -542,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/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 888e1036..a1995b17 100644
--- a/src/java/nginx/clojure/UnknownHeaderHolder.java
+++ b/src/java/nginx/clojure/UnknownHeaderHolder.java
@@ -11,6 +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.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;
@@ -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/asm/AnnotationVisitor.java b/src/java/nginx/clojure/asm/AnnotationVisitor.java
index b6e4c6a4..ae76fb12 100644
--- a/src/java/nginx/clojure/asm/AnnotationVisitor.java
+++ b/src/java/nginx/clojure/asm/AnnotationVisitor.java
@@ -38,8 +38,8 @@
public abstract class AnnotationVisitor {
/**
- * The ASM API version implemented by this visitor. The value of this field must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * 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;
@@ -52,37 +52,49 @@ public abstract class AnnotationVisitor {
/**
* Constructs a new {@link AnnotationVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- public AnnotationVisitor(final int api) {
+ 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}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @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}.
*/
-@SuppressWarnings("deprecation")
-public AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) {
- if (api != Opcodes.ASM7
+ 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.ASM8_EXPERIMENTAL) {
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
- if (api == Opcodes.ASM8_EXPERIMENTAL) {
- Constants.checkAsm8Experimental(this);
+ 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.
*
@@ -130,9 +142,9 @@ public AnnotationVisitor visitAnnotation(final String name, final String descrip
}
/**
- * Visits an array value of the annotation. Note that arrays of primitive types (such as byte,
+ * 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.
+ * 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
diff --git a/src/java/nginx/clojure/asm/AnnotationWriter.java b/src/java/nginx/clojure/asm/AnnotationWriter.java
index 03d23dab..d85252bf 100644
--- a/src/java/nginx/clojure/asm/AnnotationWriter.java
+++ b/src/java/nginx/clojure/asm/AnnotationWriter.java
@@ -112,7 +112,7 @@ final class AnnotationWriter extends AnnotationVisitor {
final boolean useNamedValues,
final ByteVector annotation,
final AnnotationWriter previousAnnotation) {
- super(/* latest api = */ Opcodes.ASM7);
+ super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.useNamedValues = useNamedValues;
this.annotation = annotation;
diff --git a/src/java/nginx/clojure/asm/ByteVector.java b/src/java/nginx/clojure/asm/ByteVector.java
index 83618e4d..f06678c8 100644
--- a/src/java/nginx/clojure/asm/ByteVector.java
+++ b/src/java/nginx/clojure/asm/ByteVector.java
@@ -65,6 +65,15 @@ public ByteVector(final int initialCapacity) {
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.
*
@@ -352,6 +361,9 @@ public ByteVector putByteArray(
* @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];
diff --git a/src/java/nginx/clojure/asm/ClassReader.java b/src/java/nginx/clojure/asm/ClassReader.java
index 045c6a6d..8a3aeb20 100644
--- a/src/java/nginx/clojure/asm/ClassReader.java
+++ b/src/java/nginx/clojure/asm/ClassReader.java
@@ -50,10 +50,11 @@ public class ClassReader {
public static final int SKIP_CODE = 1;
/**
- * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable
- * and LineNumberTable attributes. If this flag is set these attributes are neither parsed nor
- * visited (i.e. {@link ClassVisitor#visitSource}, {@link MethodVisitor#visitLocalVariable} and
- * {@link MethodVisitor#visitLineNumber} are not called).
+ * 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;
@@ -87,6 +88,9 @@ public class ClassReader {
*/
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;
@@ -100,6 +104,9 @@ public class ClassReader {
// 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
@@ -146,9 +153,6 @@ public class ClassReader {
*/
private final int maxStringLength;
- /** The offset in bytes of the ClassFile's access_flags field. */
- public final int header;
-
// -----------------------------------------------------------------------------------------------
// Constructors
// -----------------------------------------------------------------------------------------------
@@ -190,7 +194,7 @@ public ClassReader(
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.V14) {
+ if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V20) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
@@ -304,18 +308,25 @@ public ClassReader(final String className) throws IOException {
* @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[INPUT_STREAM_DATA_CHUNK_SIZE];
+ byte[] data = new byte[bufferSize];
int bytesRead;
- while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
+ 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) {
@@ -324,6 +335,19 @@ private static byte[] readStream(final InputStream inputStream, final boolean cl
}
}
+ 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.
+ */
+ if (expectedLength < 256) {
+ return INPUT_STREAM_DATA_CHUNK_SIZE;
+ }
+ return Math.min(expectedLength, MAX_BUFFER_SIZE);
+ }
+
// -----------------------------------------------------------------------------------------------
// Accessors
// -----------------------------------------------------------------------------------------------
@@ -351,7 +375,7 @@ public String getClassName() {
}
/**
- * Returns the internal of name of the super class (see {@link Type#getInternalName()}). For
+ * 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.
@@ -414,8 +438,7 @@ public void accept(final ClassVisitor classVisitor, final int parsingOptions) {
* @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}.
*/
-@SuppressWarnings("deprecation")
-public void accept(
+ public void accept(
final ClassVisitor classVisitor,
final Attribute[] attributePrototypes,
final int parsingOptions) {
@@ -467,8 +490,10 @@ public void accept(
String nestHostClass = null;
// - The offset of the NestMembers attribute, or 0.
int nestMembersOffset = 0;
- // - The offset of the PermittedSubtypes attribute, or 0
- int permittedSubtypesOffset = 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;
@@ -491,8 +516,8 @@ public void accept(
nestHostClass = readClass(currentAttributeOffset, charBuffer);
} else if (Constants.NEST_MEMBERS.equals(attributeName)) {
nestMembersOffset = currentAttributeOffset;
- } else if (Constants.PERMITTED_SUBTYPES.equals(attributeName)) {
- permittedSubtypesOffset = 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)) {
@@ -504,12 +529,18 @@ public void accept(
} 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)) {
@@ -667,14 +698,14 @@ public void accept(
}
}
- // Visit the PermittedSubtypes attribute.
- if (permittedSubtypesOffset != 0) {
- int numberOfPermittedSubtypes = readUnsignedShort(permittedSubtypesOffset);
- int currentPermittedSubtypeOffset = permittedSubtypesOffset + 2;
- while (numberOfPermittedSubtypes-- > 0) {
- classVisitor.visitPermittedSubtypeExperimental(
- readClass(currentPermittedSubtypeOffset, charBuffer));
- currentPermittedSubtypeOffset += 2;
+ // 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;
}
}
@@ -692,6 +723,15 @@ public void accept(
}
}
+ // Visit Record components.
+ if (recordOffset != 0) {
+ int recordComponentsCount = readUnsignedShort(recordOffset);
+ recordOffset += 2;
+ while (recordComponentsCount-- > 0) {
+ recordOffset = readRecordComponent(classVisitor, context, recordOffset);
+ }
+ }
+
// Visit the fields and methods.
int fieldsCount = readUnsignedShort(currentOffset);
currentOffset += 2;
@@ -819,7 +859,7 @@ private void readModuleAttributes(
currentOffset += 2;
}
- // Read the 'provides_count' and 'provides' fields.
+ // Read the 'provides_count' and 'provides' fields.
int providesCount = readUnsignedShort(currentOffset);
currentOffset += 2;
while (providesCount-- > 0) {
@@ -839,6 +879,180 @@ private void readModuleAttributes(
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;
+ }
+
+ RecordComponentVisitor recordComponentVisitor =
+ classVisitor.visitRecordComponent(name, descriptor, signature);
+ if (recordComponentVisitor == null) {
+ return currentOffset;
+ }
+
+ // 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);
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ // 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;
+ }
+
+ // Visit the end of the field.
+ recordComponentVisitor.visitEnd();
+ return currentOffset;
+ }
+
/**
* Reads a JVMS field_info structure and makes the given visitor visit it.
*
@@ -1165,7 +1379,7 @@ private int readMethod(
}
// Visit the MethodParameters attribute.
- if (methodParametersOffset != 0) {
+ if (methodParametersOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) {
int parametersCount = readByte(methodParametersOffset);
int currentParameterOffset = methodParametersOffset + 1;
while (parametersCount-- > 0) {
@@ -1328,6 +1542,9 @@ private void readCode(
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;
@@ -1337,113 +1554,113 @@ private void readCode(
final int bytecodeOffset = currentOffset - bytecodeStartOffset;
final int opcode = classBuffer[currentOffset] & 0xFF;
switch (opcode) {
- case Constants.NOP:
- case Constants.ACONST_NULL:
- case Constants.ICONST_M1:
- case Constants.ICONST_0:
- case Constants.ICONST_1:
- case Constants.ICONST_2:
- case Constants.ICONST_3:
- case Constants.ICONST_4:
- case Constants.ICONST_5:
- case Constants.LCONST_0:
- case Constants.LCONST_1:
- case Constants.FCONST_0:
- case Constants.FCONST_1:
- case Constants.FCONST_2:
- case Constants.DCONST_0:
- case Constants.DCONST_1:
- case Constants.IALOAD:
- case Constants.LALOAD:
- case Constants.FALOAD:
- case Constants.DALOAD:
- case Constants.AALOAD:
- case Constants.BALOAD:
- case Constants.CALOAD:
- case Constants.SALOAD:
- case Constants.IASTORE:
- case Constants.LASTORE:
- case Constants.FASTORE:
- case Constants.DASTORE:
- case Constants.AASTORE:
- case Constants.BASTORE:
- case Constants.CASTORE:
- case Constants.SASTORE:
- case Constants.POP:
- case Constants.POP2:
- case Constants.DUP:
- case Constants.DUP_X1:
- case Constants.DUP_X2:
- case Constants.DUP2:
- case Constants.DUP2_X1:
- case Constants.DUP2_X2:
- case Constants.SWAP:
- case Constants.IADD:
- case Constants.LADD:
- case Constants.FADD:
- case Constants.DADD:
- case Constants.ISUB:
- case Constants.LSUB:
- case Constants.FSUB:
- case Constants.DSUB:
- case Constants.IMUL:
- case Constants.LMUL:
- case Constants.FMUL:
- case Constants.DMUL:
- case Constants.IDIV:
- case Constants.LDIV:
- case Constants.FDIV:
- case Constants.DDIV:
- case Constants.IREM:
- case Constants.LREM:
- case Constants.FREM:
- case Constants.DREM:
- case Constants.INEG:
- case Constants.LNEG:
- case Constants.FNEG:
- case Constants.DNEG:
- case Constants.ISHL:
- case Constants.LSHL:
- case Constants.ISHR:
- case Constants.LSHR:
- case Constants.IUSHR:
- case Constants.LUSHR:
- case Constants.IAND:
- case Constants.LAND:
- case Constants.IOR:
- case Constants.LOR:
- case Constants.IXOR:
- case Constants.LXOR:
- case Constants.I2L:
- case Constants.I2F:
- case Constants.I2D:
- case Constants.L2I:
- case Constants.L2F:
- case Constants.L2D:
- case Constants.F2I:
- case Constants.F2L:
- case Constants.F2D:
- case Constants.D2I:
- case Constants.D2L:
- case Constants.D2F:
- case Constants.I2B:
- case Constants.I2C:
- case Constants.I2S:
- case Constants.LCMP:
- case Constants.FCMPL:
- case Constants.FCMPG:
- case Constants.DCMPL:
- case Constants.DCMPG:
- case Constants.IRETURN:
- case Constants.LRETURN:
- case Constants.FRETURN:
- case Constants.DRETURN:
- case Constants.ARETURN:
- case Constants.RETURN:
- case Constants.ARRAYLENGTH:
- case Constants.ATHROW:
- case Constants.MONITORENTER:
- case Constants.MONITOREXIT:
+ 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:
@@ -1486,24 +1703,24 @@ private void readCode(
case Constants.ASTORE_3:
currentOffset += 1;
break;
- case Constants.IFEQ:
- case Constants.IFNE:
- case Constants.IFLT:
- case Constants.IFGE:
- case Constants.IFGT:
- case Constants.IFLE:
- case Constants.IF_ICMPEQ:
- case Constants.IF_ICMPNE:
- case Constants.IF_ICMPLT:
- case Constants.IF_ICMPGE:
- case Constants.IF_ICMPGT:
- case Constants.IF_ICMPLE:
- case Constants.IF_ACMPEQ:
- case Constants.IF_ACMPNE:
- case Constants.GOTO:
- case Constants.JSR:
- case Constants.IFNULL:
- case Constants.IFNONNULL:
+ 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;
@@ -1536,27 +1753,27 @@ private void readCode(
break;
case Constants.WIDE:
switch (classBuffer[currentOffset + 1] & 0xFF) {
- case Constants.ILOAD:
- case Constants.FLOAD:
- case Constants.ALOAD:
- case Constants.LLOAD:
- case Constants.DLOAD:
- case Constants.ISTORE:
- case Constants.FSTORE:
- case Constants.ASTORE:
- case Constants.LSTORE:
- case Constants.DSTORE:
- case Constants.RET:
+ 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 Constants.IINC:
+ case Opcodes.IINC:
currentOffset += 6;
break;
default:
throw new IllegalArgumentException();
}
break;
- case Constants.TABLESWITCH:
+ case Opcodes.TABLESWITCH:
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (bytecodeOffset & 3);
// Read the default label and the number of table entries.
@@ -1569,7 +1786,7 @@ private void readCode(
currentOffset += 4;
}
break;
- case Constants.LOOKUPSWITCH:
+ case Opcodes.LOOKUPSWITCH:
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (bytecodeOffset & 3);
// Read the default label and the number of switch cases.
@@ -1582,44 +1799,44 @@ private void readCode(
currentOffset += 8;
}
break;
- case Constants.ILOAD:
- case Constants.LLOAD:
- case Constants.FLOAD:
- case Constants.DLOAD:
- case Constants.ALOAD:
- case Constants.ISTORE:
- case Constants.LSTORE:
- case Constants.FSTORE:
- case Constants.DSTORE:
- case Constants.ASTORE:
- case Constants.RET:
- case Constants.BIPUSH:
- case Constants.NEWARRAY:
- case Constants.LDC:
+ 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 Constants.SIPUSH:
+ case Opcodes.SIPUSH:
case Constants.LDC_W:
case Constants.LDC2_W:
- case Constants.GETSTATIC:
- case Constants.PUTSTATIC:
- case Constants.GETFIELD:
- case Constants.PUTFIELD:
- case Constants.INVOKEVIRTUAL:
- case Constants.INVOKESPECIAL:
- case Constants.INVOKESTATIC:
- case Constants.NEW:
- case Constants.ANEWARRAY:
- case Constants.CHECKCAST:
- case Constants.INSTANCEOF:
- case Constants.IINC:
+ 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 Constants.INVOKEINTERFACE:
- case Constants.INVOKEDYNAMIC:
+ case Opcodes.INVOKEINTERFACE:
+ case Opcodes.INVOKEDYNAMIC:
currentOffset += 5;
break;
- case Constants.MULTIANEWARRAY:
+ case Opcodes.MULTIANEWARRAY:
currentOffset += 4;
break;
default:
@@ -1886,113 +2103,113 @@ private void readCode(
// Visit the instruction at this bytecode offset.
int opcode = classBuffer[currentOffset] & 0xFF;
switch (opcode) {
- case Constants.NOP:
- case Constants.ACONST_NULL:
- case Constants.ICONST_M1:
- case Constants.ICONST_0:
- case Constants.ICONST_1:
- case Constants.ICONST_2:
- case Constants.ICONST_3:
- case Constants.ICONST_4:
- case Constants.ICONST_5:
- case Constants.LCONST_0:
- case Constants.LCONST_1:
- case Constants.FCONST_0:
- case Constants.FCONST_1:
- case Constants.FCONST_2:
- case Constants.DCONST_0:
- case Constants.DCONST_1:
- case Constants.IALOAD:
- case Constants.LALOAD:
- case Constants.FALOAD:
- case Constants.DALOAD:
- case Constants.AALOAD:
- case Constants.BALOAD:
- case Constants.CALOAD:
- case Constants.SALOAD:
- case Constants.IASTORE:
- case Constants.LASTORE:
- case Constants.FASTORE:
- case Constants.DASTORE:
- case Constants.AASTORE:
- case Constants.BASTORE:
- case Constants.CASTORE:
- case Constants.SASTORE:
- case Constants.POP:
- case Constants.POP2:
- case Constants.DUP:
- case Constants.DUP_X1:
- case Constants.DUP_X2:
- case Constants.DUP2:
- case Constants.DUP2_X1:
- case Constants.DUP2_X2:
- case Constants.SWAP:
- case Constants.IADD:
- case Constants.LADD:
- case Constants.FADD:
- case Constants.DADD:
- case Constants.ISUB:
- case Constants.LSUB:
- case Constants.FSUB:
- case Constants.DSUB:
- case Constants.IMUL:
- case Constants.LMUL:
- case Constants.FMUL:
- case Constants.DMUL:
- case Constants.IDIV:
- case Constants.LDIV:
- case Constants.FDIV:
- case Constants.DDIV:
- case Constants.IREM:
- case Constants.LREM:
- case Constants.FREM:
- case Constants.DREM:
- case Constants.INEG:
- case Constants.LNEG:
- case Constants.FNEG:
- case Constants.DNEG:
- case Constants.ISHL:
- case Constants.LSHL:
- case Constants.ISHR:
- case Constants.LSHR:
- case Constants.IUSHR:
- case Constants.LUSHR:
- case Constants.IAND:
- case Constants.LAND:
- case Constants.IOR:
- case Constants.LOR:
- case Constants.IXOR:
- case Constants.LXOR:
- case Constants.I2L:
- case Constants.I2F:
- case Constants.I2D:
- case Constants.L2I:
- case Constants.L2F:
- case Constants.L2D:
- case Constants.F2I:
- case Constants.F2L:
- case Constants.F2D:
- case Constants.D2I:
- case Constants.D2L:
- case Constants.D2F:
- case Constants.I2B:
- case Constants.I2C:
- case Constants.I2S:
- case Constants.LCMP:
- case Constants.FCMPL:
- case Constants.FCMPG:
- case Constants.DCMPL:
- case Constants.DCMPG:
- case Constants.IRETURN:
- case Constants.LRETURN:
- case Constants.FRETURN:
- case Constants.DRETURN:
- case Constants.ARETURN:
- case Constants.RETURN:
- case Constants.ARRAYLENGTH:
- case Constants.ATHROW:
- case Constants.MONITORENTER:
- case Constants.MONITOREXIT:
+ 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;
@@ -2044,24 +2261,24 @@ private void readCode(
methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3);
currentOffset += 1;
break;
- case Constants.IFEQ:
- case Constants.IFNE:
- case Constants.IFLT:
- case Constants.IFGE:
- case Constants.IFGT:
- case Constants.IFLE:
- case Constants.IF_ICMPEQ:
- case Constants.IF_ICMPNE:
- case Constants.IF_ICMPLT:
- case Constants.IF_ICMPGE:
- case Constants.IF_ICMPGT:
- case Constants.IF_ICMPLE:
- case Constants.IF_ACMPEQ:
- case Constants.IF_ACMPNE:
- case Constants.GOTO:
- case Constants.JSR:
- case Constants.IFNULL:
- case Constants.IFNONNULL:
+ 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;
@@ -2142,7 +2359,7 @@ private void readCode(
currentOffset += 4;
}
break;
- case Constants.TABLESWITCH:
+ case Opcodes.TABLESWITCH:
{
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (currentBytecodeOffset & 3);
@@ -2159,7 +2376,7 @@ private void readCode(
methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table);
break;
}
- case Constants.LOOKUPSWITCH:
+ case Opcodes.LOOKUPSWITCH:
{
// Skip 0 to 3 padding bytes.
currentOffset += 4 - (currentBytecodeOffset & 3);
@@ -2177,30 +2394,30 @@ private void readCode(
methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values);
break;
}
- case Constants.ILOAD:
- case Constants.LLOAD:
- case Constants.FLOAD:
- case Constants.DLOAD:
- case Constants.ALOAD:
- case Constants.ISTORE:
- case Constants.LSTORE:
- case Constants.FSTORE:
- case Constants.DSTORE:
- case Constants.ASTORE:
- case Constants.RET:
+ 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 Constants.BIPUSH:
- case Constants.NEWARRAY:
+ case Opcodes.BIPUSH:
+ case Opcodes.NEWARRAY:
methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]);
currentOffset += 2;
break;
- case Constants.SIPUSH:
+ case Opcodes.SIPUSH:
methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1));
currentOffset += 3;
break;
- case Constants.LDC:
+ case Opcodes.LDC:
methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer));
currentOffset += 2;
break;
@@ -2209,14 +2426,14 @@ private void readCode(
methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer));
currentOffset += 3;
break;
- case Constants.GETSTATIC:
- case Constants.PUTSTATIC:
- case Constants.GETFIELD:
- case Constants.PUTFIELD:
- case Constants.INVOKEVIRTUAL:
- case Constants.INVOKESPECIAL:
- case Constants.INVOKESTATIC:
- case Constants.INVOKEINTERFACE:
+ 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)];
@@ -2237,7 +2454,7 @@ private void readCode(
}
break;
}
- case Constants.INVOKEDYNAMIC:
+ case Opcodes.INVOKEDYNAMIC:
{
int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)];
int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)];
@@ -2259,19 +2476,19 @@ private void readCode(
currentOffset += 5;
break;
}
- case Constants.NEW:
- case Constants.ANEWARRAY:
- case Constants.CHECKCAST:
- case Constants.INSTANCEOF:
+ case Opcodes.NEW:
+ case Opcodes.ANEWARRAY:
+ case Opcodes.CHECKCAST:
+ case Opcodes.INSTANCEOF:
methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer));
currentOffset += 3;
break;
- case Constants.IINC:
+ case Opcodes.IINC:
methodVisitor.visitIincInsn(
classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]);
currentOffset += 3;
break;
- case Constants.MULTIANEWARRAY:
+ case Opcodes.MULTIANEWARRAY:
methodVisitor.visitMultiANewArrayInsn(
readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF);
currentOffset += 4;
@@ -2779,7 +2996,7 @@ private int readElementValues(
// Parse the array_value array.
while (numElementValuePairs-- > 0) {
currentOffset =
- readElementValue(annotationVisitor, currentOffset, /* named = */ null, charBuffer);
+ readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer);
}
}
if (annotationVisitor != null) {
@@ -3257,7 +3474,6 @@ final int getFirstAttributeOffset() {
private int[] readBootstrapMethodsAttribute(final int maxStringLength) {
char[] charBuffer = new char[maxStringLength];
int currentAttributeOffset = getFirstAttributeOffset();
- int[] currentBootstrapMethodOffsets = null;
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);
@@ -3265,17 +3481,17 @@ private int[] readBootstrapMethodsAttribute(final int maxStringLength) {
currentAttributeOffset += 6;
if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
// Read the num_bootstrap_methods field and create an array of this size.
- currentBootstrapMethodOffsets = new int[readUnsignedShort(currentAttributeOffset)];
+ 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 < currentBootstrapMethodOffsets.length; ++j) {
- currentBootstrapMethodOffsets[j] = currentBootstrapMethodOffset;
+ 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 currentBootstrapMethodOffsets;
+ return result;
}
currentAttributeOffset += attributeLength;
}
diff --git a/src/java/nginx/clojure/asm/ClassTooLargeException.java b/src/java/nginx/clojure/asm/ClassTooLargeException.java
index 9a8e152f..517a6771 100644
--- a/src/java/nginx/clojure/asm/ClassTooLargeException.java
+++ b/src/java/nginx/clojure/asm/ClassTooLargeException.java
@@ -42,7 +42,8 @@ public final class ClassTooLargeException extends IndexOutOfBoundsException {
/**
* Constructs a new {@link ClassTooLargeException}.
*
- * @param className the internal name of the class.
+ * @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) {
@@ -52,7 +53,7 @@ public ClassTooLargeException(final String className, final int constantPoolCoun
}
/**
- * Returns the internal name of the class.
+ * Returns the internal name of the class (see {@link nginx.clojure.asm.Type#getInternalName()}).
*
* @return the internal name of the class.
*/
diff --git a/src/java/nginx/clojure/asm/ClassVisitor.java b/src/java/nginx/clojure/asm/ClassVisitor.java
index 80c630b1..8cf6ad1d 100644
--- a/src/java/nginx/clojure/asm/ClassVisitor.java
+++ b/src/java/nginx/clojure/asm/ClassVisitor.java
@@ -30,17 +30,18 @@
/**
* 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
- * visitPermittedSubtype} ][ {@code visitOuterClass} ] ( {@code visitAnnotation} | {@code
- * visitTypeAnnotation} | {@code visitAttribute} )* ( {@code visitNestMember} | {@code
- * visitInnerClass} | {@code visitField} | {@code visitMethod} )* {@code visitEnd}.
+ * 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}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * 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;
@@ -50,44 +51,55 @@ public abstract class ClassVisitor {
/**
* Constructs a new {@link ClassVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- public ClassVisitor(final int api) {
+ 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 {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @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.
*/
- @SuppressWarnings("deprecation")
- public ClassVisitor(final int api, final ClassVisitor classVisitor) {
- if (api != Opcodes.ASM7
+ 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.ASM8_EXPERIMENTAL) {
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
- if (api == Opcodes.ASM8_EXPERIMENTAL) {
- Constants.checkAsm8Experimental(this);
+ 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.
+ * 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.
@@ -104,6 +116,9 @@ public void visit(
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);
}
@@ -135,7 +150,7 @@ public void visitSource(final String source, final String debug) {
*/
public ModuleVisitor visitModule(final String name, final int access, final String version) {
if (api < Opcodes.ASM6) {
- throw new UnsupportedOperationException("This feature requires ASM6");
+ throw new UnsupportedOperationException("Module requires ASM6");
}
if (cv != null) {
return cv.visitModule(name, access, version);
@@ -151,11 +166,12 @@ public ModuleVisitor visitModule(final String name, final int access, final Stri
* 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.
+ * @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("This feature requires ASM7");
+ throw new UnsupportedOperationException("NestHost requires ASM7");
}
if (cv != null) {
cv.visitNestHost(nestHost);
@@ -163,14 +179,19 @@ public void visitNestHost(final String nestHost) {
}
/**
- * Visits the enclosing class of the class. This method must be called only if the class has an
- * enclosing class.
+ * 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.
+ * @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 of its enclosing class.
+ * 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 of its enclosing class.
+ * 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) {
@@ -211,7 +232,7 @@ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean
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");
+ throw new UnsupportedOperationException("TypeAnnotation requires ASM5");
}
if (cv != null) {
return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
@@ -237,11 +258,11 @@ public void visitAttribute(final Attribute attribute) {
* 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.
+ * @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("This feature requires ASM7");
+ throw new UnsupportedOperationException("NestMember requires ASM7");
}
if (cv != null) {
cv.visitNestMember(nestMember);
@@ -249,34 +270,35 @@ public void visitNestMember(final String nestMember) {
}
/**
- * Experimental, use at your own risk. This method will be renamed when it becomes stable, this
- * will break existing code using it. Visits a permitted subtypes. A permitted subtypes is one
- * of the allowed subtypes of the current class.
+ * Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the
+ * current class.
*
- * @param permittedSubtype the internal name of a permitted subtype.
- * @deprecated this API is experimental.
+ * @param permittedSubclass the internal name of a permitted subclass (see {@link
+ * Type#getInternalName()}).
*/
- @Deprecated
- public void visitPermittedSubtypeExperimental(final String permittedSubtype) {
- if (api != Opcodes.ASM8_EXPERIMENTAL) {
- throw new UnsupportedOperationException("This feature requires ASM8_EXPERIMENTAL");
+ public void visitPermittedSubclass(final String permittedSubclass) {
+ if (api < Opcodes.ASM9) {
+ throw new UnsupportedOperationException("PermittedSubclasses requires ASM9");
}
if (cv != null) {
- cv.visitPermittedSubtypeExperimental(permittedSubtype);
+ cv.visitPermittedSubclass(permittedSubclass);
}
}
/**
* Visits information about an inner class. This inner class is not necessarily a member of the
- * class being visited.
+ * 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 an inner class (see {@link Type#getInternalName()}).
- * @param outerName the internal name of the class to which the inner class belongs (see {@link
- * Type#getInternalName()}). May be {@literal null} for not member classes.
- * @param innerName the (simple) name of the inner class inside its enclosing class. May be
- * {@literal null} for anonymous inner classes.
- * @param access the access flags of the inner class as originally declared in the enclosing
- * class.
+ * @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) {
@@ -285,6 +307,27 @@ public void visitInnerClass(
}
}
+ /**
+ * 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.
*
diff --git a/src/java/nginx/clojure/asm/ClassWriter.java b/src/java/nginx/clojure/asm/ClassWriter.java
index f4351cbb..17273461 100644
--- a/src/java/nginx/clojure/asm/ClassWriter.java
+++ b/src/java/nginx/clojure/asm/ClassWriter.java
@@ -65,6 +65,12 @@ public class ClassWriter extends ClassVisitor {
*/
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.
@@ -79,8 +85,8 @@ public class ClassWriter extends ClassVisitor {
/**
* The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific
- * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the
- * ClassFile structure.
+ * access flags, such as {@link Opcodes#ACC_DEPRECATED} or {@link Opcodes#ACC_RECORD}, which are
+ * removed when generating the ClassFile structure.
*/
private int accessFlags;
@@ -177,11 +183,25 @@ public class ClassWriter extends ClassVisitor {
/** The 'classes' array of the NestMembers attribute, or {@literal null}. */
private ByteVector nestMemberClasses;
- /** The number_of_classes field of the PermittedSubtypes attribute, or 0. */
- private int numberOfPermittedSubtypeClasses;
+ /** 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 'classes' array of the PermittedSubtypes attribute, or {@literal null}. */
- private ByteVector permittedSubtypeClasses;
+ /**
+ * 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
@@ -234,23 +254,39 @@ public ClassWriter(final int flags) {
* @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
+ * @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.ASM7);
+ super(/* latest api = */ Opcodes.ASM9);
+ this.flags = flags;
symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader);
if ((flags & COMPUTE_FRAMES) != 0) {
- this.compute = MethodWriter.COMPUTE_ALL_FRAMES;
+ compute = MethodWriter.COMPUTE_ALL_FRAMES;
} else if ((flags & COMPUTE_MAXS) != 0) {
- this.compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL;
+ compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL;
} else {
- this.compute = MethodWriter.COMPUTE_NOTHING;
+ compute = MethodWriter.COMPUTE_NOTHING;
}
}
+ // -----------------------------------------------------------------------------------------------
+ // 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
// -----------------------------------------------------------------------------------------------
@@ -359,12 +395,12 @@ public final void visitNestMember(final String nestMember) {
}
@Override
- public final void visitPermittedSubtypeExperimental(final String permittedSubtype) {
- if (permittedSubtypeClasses == null) {
- permittedSubtypeClasses = new ByteVector();
+ public final void visitPermittedSubclass(final String permittedSubclass) {
+ if (permittedSubclasses == null) {
+ permittedSubclasses = new ByteVector();
}
- ++numberOfPermittedSubtypeClasses;
- permittedSubtypeClasses.putShort(symbolTable.addConstantClass(permittedSubtype).index);
+ ++numberOfPermittedSubclasses;
+ permittedSubclasses.putShort(symbolTable.addConstantClass(permittedSubclass).index);
}
@Override
@@ -392,6 +428,19 @@ public final void visitInnerClass(
// 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;
+ }
+ return lastRecordComponent = recordComponentWriter;
+ }
+
@Override
public final FieldVisitor visitField(
final int access,
@@ -462,6 +511,7 @@ public byte[] toByteArray() {
size += methodWriter.computeMethodInfoSize();
methodWriter = (MethodWriter) methodWriter.mv;
}
+
// 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) {
@@ -541,10 +591,23 @@ public byte[] toByteArray() {
size += 8 + nestMemberClasses.length;
symbolTable.addConstantUtf8(Constants.NEST_MEMBERS);
}
- if (permittedSubtypeClasses != null) {
+ if (permittedSubclasses != null) {
++attributesCount;
- size += 8 + permittedSubtypeClasses.length;
- symbolTable.addConstantUtf8(Constants.PERMITTED_SUBTYPES);
+ size += 8 + permittedSubclasses.length;
+ symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES);
+ }
+ 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);
}
if (firstAttribute != null) {
attributesCount += firstAttribute.getAttributeCount();
@@ -650,12 +713,23 @@ public byte[] toByteArray() {
.putShort(numberOfNestMemberClasses)
.putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length);
}
- if (permittedSubtypeClasses != null) {
+ if (permittedSubclasses != null) {
result
- .putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBTYPES))
- .putInt(permittedSubtypeClasses.length + 2)
- .putShort(numberOfPermittedSubtypeClasses)
- .putByteArray(permittedSubtypeClasses.data, 0, permittedSubtypeClasses.length);
+ .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);
@@ -693,8 +767,10 @@ private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasF
nestHostClassIndex = 0;
numberOfNestMemberClasses = 0;
nestMemberClasses = null;
- numberOfPermittedSubtypeClasses = 0;
- permittedSubtypeClasses = 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)
@@ -723,6 +799,11 @@ private Attribute[] getAttributePrototypes() {
methodWriter.collectAttributePrototypes(attributePrototypes);
methodWriter = (MethodWriter) methodWriter.mv;
}
+ RecordComponentWriter recordComponentWriter = firstRecordComponent;
+ while (recordComponentWriter != null) {
+ recordComponentWriter.collectAttributePrototypes(attributePrototypes);
+ recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate;
+ }
return attributePrototypes.toArray();
}
@@ -761,7 +842,7 @@ public int newUTF8(final String value) {
* 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.
+ * @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) {
@@ -813,7 +894,8 @@ public int newPackage(final String packageName) {
* 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 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.
@@ -835,7 +917,8 @@ public int newHandle(
* 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 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.
@@ -897,7 +980,7 @@ public int newInvokeDynamic(
* 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 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.
@@ -911,7 +994,8 @@ public int newField(final String owner, final String name, final String descript
* 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 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.
@@ -947,9 +1031,10 @@ public int newNameType(final String name, final String descriptor) {
* 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.
+ * @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();
diff --git a/src/java/nginx/clojure/asm/Constants.java b/src/java/nginx/clojure/asm/Constants.java
index beec1c13..fe402185 100644
--- a/src/java/nginx/clojure/asm/Constants.java
+++ b/src/java/nginx/clojure/asm/Constants.java
@@ -30,6 +30,7 @@
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
@@ -38,7 +39,7 @@
* @see JVMS 6
* @author Eric Bruneton
*/
-final class Constants implements Opcodes {
+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.
@@ -72,7 +73,8 @@ final class Constants implements Opcodes {
static final String MODULE_MAIN_CLASS = "ModuleMainClass";
static final String NEST_HOST = "NestHost";
static final String NEST_MEMBERS = "NestMembers";
- static final String PERMITTED_SUBTYPES = "PermittedSubtypes";
+ 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
@@ -145,7 +147,7 @@ final class Constants implements Opcodes {
// 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 - GOTO;
+ 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.
@@ -158,48 +160,62 @@ final class Constants implements Opcodes {
// ASM specific opcodes, used for long forward jump instructions.
- static final int ASM_IFEQ = IFEQ + ASM_OPCODE_DELTA;
- static final int ASM_IFNE = IFNE + ASM_OPCODE_DELTA;
- static final int ASM_IFLT = IFLT + ASM_OPCODE_DELTA;
- static final int ASM_IFGE = IFGE + ASM_OPCODE_DELTA;
- static final int ASM_IFGT = IFGT + ASM_OPCODE_DELTA;
- static final int ASM_IFLE = IFLE + ASM_OPCODE_DELTA;
- static final int ASM_IF_ICMPEQ = IF_ICMPEQ + ASM_OPCODE_DELTA;
- static final int ASM_IF_ICMPNE = IF_ICMPNE + ASM_OPCODE_DELTA;
- static final int ASM_IF_ICMPLT = IF_ICMPLT + ASM_OPCODE_DELTA;
- static final int ASM_IF_ICMPGE = IF_ICMPGE + ASM_OPCODE_DELTA;
- static final int ASM_IF_ICMPGT = IF_ICMPGT + ASM_OPCODE_DELTA;
- static final int ASM_IF_ICMPLE = IF_ICMPLE + ASM_OPCODE_DELTA;
- static final int ASM_IF_ACMPEQ = IF_ACMPEQ + ASM_OPCODE_DELTA;
- static final int ASM_IF_ACMPNE = IF_ACMPNE + ASM_OPCODE_DELTA;
- static final int ASM_GOTO = GOTO + ASM_OPCODE_DELTA;
- static final int ASM_JSR = JSR + ASM_OPCODE_DELTA;
- static final int ASM_IFNULL = IFNULL + ASM_IFNULL_OPCODE_DELTA;
- static final int ASM_IFNONNULL = IFNONNULL + ASM_IFNULL_OPCODE_DELTA;
+ 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 checkAsm8Experimental(final Object caller) {
+ static void checkAsmExperimental(final Object caller) {
Class> callerClass = caller.getClass();
- if (callerClass.getName().startsWith("nginx.clojure.asm.")) {
- return;
+ String internalName = callerClass.getName().replace('.', '/');
+ if (!isWhitelisted(internalName)) {
+ checkIsPreview(callerClass.getClassLoader().getResourceAsStream(internalName + ".class"));
}
- String callerClassResource = callerClass.getName().replace('.', '/') + ".class";
- InputStream inputStream = callerClass.getClassLoader().getResourceAsStream(callerClassResource);
- if (inputStream == null) {
+ }
+
+ 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(inputStream); ) {
+ 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);
+ throw new IllegalStateException("I/O error, can't check class version", ioe);
}
if (minorVersion != 0xFFFF) {
throw new IllegalStateException(
- "ASM8_EXPERIMENTAL can only be used by classes compiled with --enable-preview");
+ "ASM9_EXPERIMENTAL can only be used by classes compiled with --enable-preview");
}
}
}
diff --git a/src/java/nginx/clojure/asm/FieldVisitor.java b/src/java/nginx/clojure/asm/FieldVisitor.java
index 350e8672..468aada8 100644
--- a/src/java/nginx/clojure/asm/FieldVisitor.java
+++ b/src/java/nginx/clojure/asm/FieldVisitor.java
@@ -37,8 +37,8 @@
public abstract class FieldVisitor {
/**
- * The ASM API version implemented by this visitor. The value of this field must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * 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;
@@ -48,37 +48,47 @@ public abstract class FieldVisitor {
/**
* Constructs a new {@link FieldVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- public FieldVisitor(final int api) {
+ 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 {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @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.
*/
- @SuppressWarnings("deprecation")
- public FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
- if (api != Opcodes.ASM7
+ 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.ASM8_EXPERIMENTAL) {
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
- if (api == Opcodes.ASM8_EXPERIMENTAL) {
- Constants.checkAsm8Experimental(this);
+ if (api == Opcodes.ASM10_EXPERIMENTAL) {
+ Constants.checkAsmExperimental(this);
}
this.api = api;
this.fv = fieldVisitor;
}
+ /**
+ * 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.
*
diff --git a/src/java/nginx/clojure/asm/FieldWriter.java b/src/java/nginx/clojure/asm/FieldWriter.java
index aef87843..4447a2b2 100644
--- a/src/java/nginx/clojure/asm/FieldWriter.java
+++ b/src/java/nginx/clojure/asm/FieldWriter.java
@@ -124,7 +124,7 @@ final class FieldWriter extends FieldVisitor {
final String descriptor,
final String signature,
final Object constantValue) {
- super(/* latest api = */ Opcodes.ASM7);
+ super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.accessFlags = access;
this.nameIndex = symbolTable.addConstantUtf8(name);
diff --git a/src/java/nginx/clojure/asm/Handle.java b/src/java/nginx/clojure/asm/Handle.java
index ced6f3e9..5581b30c 100644
--- a/src/java/nginx/clojure/asm/Handle.java
+++ b/src/java/nginx/clojure/asm/Handle.java
@@ -65,7 +65,7 @@ public final class Handle {
* {@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.
+ * 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,
@@ -85,7 +85,7 @@ public Handle(final int tag, final String owner, final String name, final String
* {@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.
+ * 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.
@@ -118,7 +118,8 @@ public int getTag() {
/**
* 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.
+ * @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;
diff --git a/src/java/nginx/clojure/asm/MethodTooLargeException.java b/src/java/nginx/clojure/asm/MethodTooLargeException.java
index 417fbc07..e6231ebe 100644
--- a/src/java/nginx/clojure/asm/MethodTooLargeException.java
+++ b/src/java/nginx/clojure/asm/MethodTooLargeException.java
@@ -44,7 +44,7 @@ public final class MethodTooLargeException extends IndexOutOfBoundsException {
/**
* Constructs a new {@link MethodTooLargeException}.
*
- * @param className the internal name of the owner class.
+ * @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.
@@ -64,7 +64,7 @@ public MethodTooLargeException(
/**
* Returns the internal name of the owner class.
*
- * @return the internal name of the owner class.
+ * @return the internal name of the owner class (see {@link Type#getInternalName()}).
*/
public String getClassName() {
return className;
diff --git a/src/java/nginx/clojure/asm/MethodVisitor.java b/src/java/nginx/clojure/asm/MethodVisitor.java
index 4dbdd518..40196478 100644
--- a/src/java/nginx/clojure/asm/MethodVisitor.java
+++ b/src/java/nginx/clojure/asm/MethodVisitor.java
@@ -51,8 +51,8 @@ public abstract class MethodVisitor {
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 {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * 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;
@@ -64,37 +64,48 @@ public abstract class MethodVisitor {
/**
* Constructs a new {@link MethodVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- public MethodVisitor(final int api) {
+ 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 {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @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.
*/
- @SuppressWarnings("deprecation")
- public MethodVisitor(final int api, final MethodVisitor methodVisitor) {
- if (api != Opcodes.ASM7
+ 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.ASM8_EXPERIMENTAL) {
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
- if (api == Opcodes.ASM8_EXPERIMENTAL) {
- Constants.checkAsm8Experimental(this);
+ 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
// -----------------------------------------------------------------------------------------------
@@ -121,7 +132,7 @@ public void visitParameter(final String name, final int access) {
* @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,
- * exacly one visit method must be called on this annotation visitor, followed by visitEnd.
+ * exactly one visit method must be called on this annotation visitor, followed by visitEnd.
*/
public AnnotationVisitor visitAnnotationDefault() {
if (mv != null) {
@@ -274,15 +285,17 @@ public void visitCode() {
* @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.
+ * @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), 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.
+ * 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
@@ -350,18 +363,18 @@ public void visitIntInsn(final int opcode, final int operand) {
*
* @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.
+ * @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 var) {
+ public void visitVarInsn(final int opcode, final int varIndex) {
if (mv != null) {
- mv.visitVarInsn(opcode, var);
+ mv.visitVarInsn(opcode, varIndex);
}
}
/**
* Visits a type instruction. A type instruction is an instruction that takes the internal name of
- * a class as parameter.
+ * 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.
@@ -553,12 +566,12 @@ public void visitLdcInsn(final Object value) {
/**
* Visits an IINC instruction.
*
- * @param var index of the local variable to be incremented.
+ * @param varIndex index of the local variable to be incremented.
* @param increment amount to increment the local variable by.
*/
- public void visitIincInsn(final int var, final int increment) {
+ public void visitIincInsn(final int varIndex, final int increment) {
if (mv != null) {
- mv.visitIincInsn(var, increment);
+ mv.visitIincInsn(varIndex, increment);
}
}
@@ -644,8 +657,9 @@ public AnnotationVisitor visitInsnAnnotation(
* @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, or {@literal
- * null} to catch any exceptions (for "finally" blocks).
+ * @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).
*/
diff --git a/src/java/nginx/clojure/asm/MethodWriter.java b/src/java/nginx/clojure/asm/MethodWriter.java
index c53ac476..355af173 100644
--- a/src/java/nginx/clojure/asm/MethodWriter.java
+++ b/src/java/nginx/clojure/asm/MethodWriter.java
@@ -466,7 +466,8 @@ final class MethodWriter extends MethodVisitor {
/**
* Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link
- * #COMPUTE_INSERTED_FRAMES}, {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}.
+ * #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;
@@ -592,7 +593,7 @@ final class MethodWriter extends MethodVisitor {
final String signature,
final String[] exceptions,
final int compute) {
- super(/* latest api = */ Opcodes.ASM7);
+ super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access;
this.nameIndex = symbolTable.addConstantUtf8(name);
@@ -904,26 +905,26 @@ public void visitIntInsn(final int opcode, final int operand) {
}
@Override
- public void visitVarInsn(final int opcode, final int var) {
+ public void visitVarInsn(final int opcode, final int varIndex) {
lastBytecodeOffset = code.length;
// Add the instruction to the bytecode of the method.
- if (var < 4 && opcode != Opcodes.RET) {
+ if (varIndex < 4 && opcode != Opcodes.RET) {
int optimizedOpcode;
if (opcode < Opcodes.ISTORE) {
- optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var;
+ optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + varIndex;
} else {
- optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var;
+ optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + varIndex;
}
code.putByte(optimizedOpcode);
- } else if (var >= 256) {
- code.putByte(Constants.WIDE).put12(opcode, var);
+ } else if (varIndex >= 256) {
+ code.putByte(Constants.WIDE).put12(opcode, varIndex);
} else {
- code.put11(opcode, var);
+ 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, var, null, null);
+ currentBasicBlock.frame.execute(opcode, varIndex, null, null);
} else {
if (opcode == Opcodes.RET) {
// No stack size delta.
@@ -945,9 +946,9 @@ public void visitVarInsn(final int opcode, final int var) {
|| opcode == Opcodes.DLOAD
|| opcode == Opcodes.LSTORE
|| opcode == Opcodes.DSTORE) {
- currentMaxLocals = var + 2;
+ currentMaxLocals = varIndex + 2;
} else {
- currentMaxLocals = var + 1;
+ currentMaxLocals = varIndex + 1;
}
if (currentMaxLocals > maxLocals) {
maxLocals = currentMaxLocals;
@@ -1307,21 +1308,21 @@ public void visitLdcInsn(final Object value) {
}
@Override
- public void visitIincInsn(final int var, final int increment) {
+ public void visitIincInsn(final int varIndex, final int increment) {
lastBytecodeOffset = code.length;
// Add the instruction to the bytecode of the method.
- if ((var > 255) || (increment > 127) || (increment < -128)) {
- code.putByte(Constants.WIDE).put12(Opcodes.IINC, var).putShort(increment);
+ if ((varIndex > 255) || (increment > 127) || (increment < -128)) {
+ code.putByte(Constants.WIDE).put12(Opcodes.IINC, varIndex).putShort(increment);
} else {
- code.putByte(Opcodes.IINC).put11(var, increment);
+ 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, var, null, null);
+ currentBasicBlock.frame.execute(Opcodes.IINC, varIndex, null, null);
}
if (compute != COMPUTE_NOTHING) {
- int currentMaxLocals = var + 1;
+ int currentMaxLocals = varIndex + 1;
if (currentMaxLocals > maxLocals) {
maxLocals = currentMaxLocals;
}
diff --git a/src/java/nginx/clojure/asm/ModuleVisitor.java b/src/java/nginx/clojure/asm/ModuleVisitor.java
index d4cde9c7..358b9c95 100644
--- a/src/java/nginx/clojure/asm/ModuleVisitor.java
+++ b/src/java/nginx/clojure/asm/ModuleVisitor.java
@@ -53,7 +53,7 @@ public abstract class ModuleVisitor {
* @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6}
* or {@link Opcodes#ASM7}.
*/
- public ModuleVisitor(final int api) {
+ protected ModuleVisitor(final int api) {
this(api, null);
}
@@ -65,26 +65,38 @@ public ModuleVisitor(final int api) {
* @param moduleVisitor the module visitor to which this visitor must delegate method calls. May
* be null.
*/
- @SuppressWarnings("deprecation")
- public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) {
- if (api != Opcodes.ASM7
+ 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.ASM8_EXPERIMENTAL) {
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
- if (api == Opcodes.ASM8_EXPERIMENTAL) {
- Constants.checkAsm8Experimental(this);
+ 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.
+ * @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) {
@@ -95,7 +107,7 @@ public void visitMainClass(final String mainClass) {
/**
* Visit a package of the current module.
*
- * @param packaze the internal name of a package.
+ * @param packaze the internal name of a package (see {@link Type#getInternalName()}).
*/
public void visitPackage(final String packaze) {
if (mv != null) {
@@ -120,7 +132,7 @@ public void visitRequire(final String module, final int access, final String ver
/**
* Visit an exported package of the current module.
*
- * @param packaze the internal name of the exported package.
+ * @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
@@ -135,7 +147,7 @@ public void visitExport(final String packaze, final int access, final String...
/**
* Visit an open package of the current module.
*
- * @param packaze the internal name of the opened package.
+ * @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
@@ -151,7 +163,7 @@ public void visitOpen(final String packaze, final int access, final String... mo
* 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.
+ * @param service the internal name of the service (see {@link Type#getInternalName()}).
*/
public void visitUse(final String service) {
if (mv != null) {
@@ -162,9 +174,9 @@ public void visitUse(final String service) {
/**
* Visit an implementation of a service.
*
- * @param service the internal name of the service.
- * @param providers the internal names of the implementations of the service (there is at least
- * one provider).
+ * @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) {
diff --git a/src/java/nginx/clojure/asm/ModuleWriter.java b/src/java/nginx/clojure/asm/ModuleWriter.java
index c1144dcb..954d83d4 100644
--- a/src/java/nginx/clojure/asm/ModuleWriter.java
+++ b/src/java/nginx/clojure/asm/ModuleWriter.java
@@ -94,7 +94,7 @@ final class ModuleWriter extends ModuleVisitor {
private int mainClassIndex;
ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) {
- super(/* latest api = */ Opcodes.ASM7);
+ super(/* latest api = */ Opcodes.ASM9);
this.symbolTable = symbolTable;
this.moduleNameIndex = name;
this.moduleFlags = access;
diff --git a/src/java/nginx/clojure/asm/Opcodes.java b/src/java/nginx/clojure/asm/Opcodes.java
index f0c5fe58..d2fa3ceb 100644
--- a/src/java/nginx/clojure/asm/Opcodes.java
+++ b/src/java/nginx/clojure/asm/Opcodes.java
@@ -47,6 +47,8 @@ public interface Opcodes {
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
@@ -54,7 +56,7 @@ public interface Opcodes {
*
* @deprecated This API is experimental.
*/
- @Deprecated int ASM8_EXPERIMENTAL = 1 << 24 | 8 << 16 | 0 << 8;
+ @Deprecated int ASM10_EXPERIMENTAL = 1 << 24 | 10 << 16 | 0 << 8;
/*
* Internal flags used to redirect calls to deprecated methods. For instance, if a visitOldStuff
@@ -131,7 +133,7 @@ public interface Opcodes {
*
* public class StuffVisitor {
* @Deprecated public void visitOldStuff(int arg, ...) {
- * visitNewStuf(arg | SOURCE_DEPRECATED, ...);
+ * visitNewStuff(arg | SOURCE_DEPRECATED, ...);
* }
* public void visitNewStuff(int argAndSource...) {
* if ((argAndSource & SOURCE_DEPRECATED) == 0) {
@@ -153,7 +155,7 @@ public interface Opcodes {
*
and there are two cases:
*
*
- *
call visitOldSuff: in the call to super.visitOldStuff, the source is set to
+ *
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.
@@ -278,6 +280,12 @@ public interface Opcodes {
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;
/**
* Version flag indicating that the class is using 'preview' features.
@@ -314,7 +322,7 @@ public interface Opcodes {
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; // parameter, module, module *
+ int ACC_MANDATED = 0x8000; // field, method, parameter, module, module *
int ACC_MODULE = 0x8000; // class
// ASM specific access flags.
@@ -322,6 +330,7 @@ public interface Opcodes {
// 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).
+ int ACC_RECORD = 0x10000; // class
int ACC_DEPRECATED = 0x20000; // class, field, method
// Possible values for the type operand of the NEWARRAY instruction.
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/SymbolTable.java b/src/java/nginx/clojure/asm/SymbolTable.java
index 6560fadf..c956510a 100644
--- a/src/java/nginx/clojure/asm/SymbolTable.java
+++ b/src/java/nginx/clojure/asm/SymbolTable.java
@@ -31,11 +31,11 @@
* 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
- * @author Eric Bruneton
*/
final class SymbolTable {
@@ -1046,8 +1046,10 @@ Symbol addBootstrapMethod(
// 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.
- for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
- addConstant(bootstrapMethodArgument);
+ 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
@@ -1062,10 +1064,10 @@ Symbol addBootstrapMethod(
bootstrapMethodHandle.getDesc(),
bootstrapMethodHandle.isInterface())
.index);
- int numBootstrapArguments = bootstrapMethodArguments.length;
+
bootstrapMethodsAttribute.putShort(numBootstrapArguments);
- for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
- bootstrapMethodsAttribute.putShort(addConstant(bootstrapMethodArgument).index);
+ for (int i = 0; i < numBootstrapArguments; i++) {
+ bootstrapMethodsAttribute.putShort(bootstrapMethodArgumentIndexes[i]);
}
// Compute the length and the hash code of the bootstrap method.
diff --git a/src/java/nginx/clojure/asm/Type.java b/src/java/nginx/clojure/asm/Type.java
index cfe16734..32191523 100644
--- a/src/java/nginx/clojure/asm/Type.java
+++ b/src/java/nginx/clojure/asm/Type.java
@@ -245,7 +245,7 @@ public Type getElementType() {
/**
* Returns the {@link Type} corresponding to the given internal name.
*
- * @param internalName an 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) {
@@ -440,7 +440,7 @@ private static Type getTypeInternal(
case '(':
return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd);
default:
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Invalid descriptor: " + descriptorBuffer);
}
}
diff --git a/src/java/nginx/clojure/asm/commons/AdviceAdapter.java b/src/java/nginx/clojure/asm/commons/AdviceAdapter.java
index c840e7eb..3faf9080 100644
--- a/src/java/nginx/clojure/asm/commons/AdviceAdapter.java
+++ b/src/java/nginx/clojure/asm/commons/AdviceAdapter.java
@@ -99,8 +99,8 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
/**
* Constructs a new {@link AdviceAdapter}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @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 adapter delegates calls.
* @param access the method's access flags (see {@link Opcodes}).
* @param name the method's name.
@@ -155,10 +155,12 @@ public void visitInsn(final int opcode) {
throw new IllegalArgumentException("Invalid return in constructor");
case RETURN: // empty stack
onMethodExit(opcode);
+ endConstructorBasicBlockWithoutSuccessor();
break;
case ATHROW: // 1 before n/a after
popValue();
onMethodExit(opcode);
+ endConstructorBasicBlockWithoutSuccessor();
break;
case NOP:
case LALOAD: // remove 2 add 2
@@ -326,8 +328,8 @@ public void visitInsn(final int opcode) {
}
@Override
- public void visitVarInsn(final int opcode, final int var) {
- super.visitVarInsn(opcode, var);
+ public void visitVarInsn(final int opcode, final int varIndex) {
+ super.visitVarInsn(opcode, varIndex);
if (isConstructor && !superClassConstructorCalled) {
switch (opcode) {
case ILOAD:
@@ -340,7 +342,7 @@ public void visitVarInsn(final int opcode, final int var) {
pushValue(OTHER);
break;
case ALOAD:
- pushValue(var == 0 ? UNINITIALIZED_THIS : OTHER);
+ pushValue(varIndex == 0 ? UNINITIALIZED_THIS : OTHER);
break;
case ASTORE:
case ISTORE:
@@ -353,6 +355,7 @@ public void visitVarInsn(final int opcode, final int var) {
popValue();
break;
case RET:
+ endConstructorBasicBlockWithoutSuccessor();
break;
default:
throw new IllegalArgumentException(INVALID_OPCODE + opcode);
@@ -454,10 +457,10 @@ public void visitMethodInsn(
super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK;
- doVisitMethodInsn(opcode, descriptor);
+ doVisitMethodInsn(opcode, name, descriptor);
}
- private void doVisitMethodInsn(final int opcode, final String descriptor) {
+ private void doVisitMethodInsn(final int opcode, final String name, final String descriptor) {
if (isConstructor && !superClassConstructorCalled) {
for (Type argumentType : Type.getArgumentTypes(descriptor)) {
popValue();
@@ -472,7 +475,9 @@ private void doVisitMethodInsn(final int opcode, final String descriptor) {
break;
case INVOKESPECIAL:
Object value = popValue();
- if (value == UNINITIALIZED_THIS && !superClassConstructorCalled) {
+ if (value == UNINITIALIZED_THIS
+ && !superClassConstructorCalled
+ && name.equals("")) {
superClassConstructorCalled = true;
onMethodEnter();
}
@@ -498,7 +503,7 @@ public void visitInvokeDynamicInsn(
final Handle bootstrapMethodHandle,
final Object... bootstrapMethodArguments) {
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
- doVisitMethodInsn(Opcodes.INVOKEDYNAMIC, descriptor);
+ doVisitMethodInsn(Opcodes.INVOKEDYNAMIC, name, descriptor);
}
@Override
@@ -530,6 +535,9 @@ public void visitJumpInsn(final int opcode, final Label label) {
case JSR:
pushValue(OTHER);
break;
+ case GOTO:
+ endConstructorBasicBlockWithoutSuccessor();
+ break;
default:
break;
}
@@ -543,6 +551,7 @@ public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Labe
if (isConstructor && !superClassConstructorCalled) {
popValue();
addForwardJumps(dflt, labels);
+ endConstructorBasicBlockWithoutSuccessor();
}
}
@@ -553,6 +562,7 @@ public void visitTableSwitchInsn(
if (isConstructor && !superClassConstructorCalled) {
popValue();
addForwardJumps(dflt, labels);
+ endConstructorBasicBlockWithoutSuccessor();
}
}
@@ -589,6 +599,19 @@ private void addForwardJump(final Label label) {
forwardJumpStackFrames.put(label, new ArrayList<>(stackFrame));
}
+ private void endConstructorBasicBlockWithoutSuccessor() {
+ // The next instruction is not reachable from this instruction. If it is dead code, we
+ // should not try to simulate stack operations, and there is no need to insert advices
+ // here. If it is reachable with a backward jump, the only possible case is that the super
+ // class constructor has already been called (backward jumps are forbidden before it is
+ // called). If it is reachable with a forward jump, there are two sub-cases. Either the
+ // super class constructor has already been called when reaching the next instruction, or
+ // it has not been called. But in this case there must be a forwardJumpStackFrames entry
+ // for a Label designating the next instruction, and superClassConstructorCalled will be
+ // reset to false there. We can therefore always reset this field to true here.
+ superClassConstructorCalled = true;
+ }
+
private Object popValue() {
return stackFrame.remove(stackFrame.size() - 1);
}
diff --git a/src/java/nginx/clojure/asm/commons/AnalyzerAdapter.java b/src/java/nginx/clojure/asm/commons/AnalyzerAdapter.java
index d927a17d..5fe19959 100644
--- a/src/java/nginx/clojure/asm/commons/AnalyzerAdapter.java
+++ b/src/java/nginx/clojure/asm/commons/AnalyzerAdapter.java
@@ -61,9 +61,10 @@ public class AnalyzerAdapter extends MethodVisitor {
* {@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 two elements, the second one being TOP). 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). This
- * field is {@literal null} for unreachable instructions.
+ * 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). This field is {@literal null} for
+ * unreachable instructions.
*/
public List
*/
public SimpleRemapper(final Map mapping) {
@@ -65,7 +66,8 @@ public SimpleRemapper(final Map mapping) {
*
* @param oldName the key corresponding to a method, field or internal name (see {@link
* #SimpleRemapper(Map)} for the format of these keys).
- * @param newName the new method, field or internal name.
+ * @param newName the new method, field or internal name (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}).
*/
public SimpleRemapper(final String oldName, final String newName) {
this.mapping = Collections.singletonMap(oldName, newName);
@@ -83,6 +85,12 @@ public String mapInvokeDynamicMethodName(final String name, final String descrip
return remappedName == null ? name : remappedName;
}
+ @Override
+ public String mapAnnotationAttributeName(final String descriptor, final String name) {
+ String remappedName = map(descriptor + '.' + name);
+ return remappedName == null ? name : remappedName;
+ }
+
@Override
public String mapFieldName(final String owner, final String name, final String descriptor) {
String remappedName = map(owner + '.' + name);
diff --git a/src/java/nginx/clojure/asm/commons/StaticInitMerger.java b/src/java/nginx/clojure/asm/commons/StaticInitMerger.java
index 7c174873..3e744ff6 100644
--- a/src/java/nginx/clojure/asm/commons/StaticInitMerger.java
+++ b/src/java/nginx/clojure/asm/commons/StaticInitMerger.java
@@ -61,14 +61,14 @@ public class StaticInitMerger extends ClassVisitor {
* null.
*/
public StaticInitMerger(final String prefix, final ClassVisitor classVisitor) {
- this(/* latest api = */ Opcodes.ASM7, prefix, classVisitor);
+ this(/* latest api = */ Opcodes.ASM9, prefix, classVisitor);
}
/**
* Constructs a new {@link StaticInitMerger}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5} or {@link Opcodes#ASM6}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param prefix the prefix to use to rename the existing <clinit> methods.
* @param classVisitor the class visitor to which this visitor must delegate method calls. May be
* null.
diff --git a/src/java/nginx/clojure/asm/commons/TryCatchBlockSorter.java b/src/java/nginx/clojure/asm/commons/TryCatchBlockSorter.java
index 7d7d1911..32b8fe3d 100644
--- a/src/java/nginx/clojure/asm/commons/TryCatchBlockSorter.java
+++ b/src/java/nginx/clojure/asm/commons/TryCatchBlockSorter.java
@@ -72,7 +72,7 @@ public TryCatchBlockSorter(
final String signature,
final String[] exceptions) {
this(
- /* latest api = */ Opcodes.ASM7,
+ /* latest api = */ Opcodes.ASM9,
methodVisitor,
access,
name,
diff --git a/src/java/nginx/clojure/asm/signature/SignatureVisitor.java b/src/java/nginx/clojure/asm/signature/SignatureVisitor.java
index fed3dc21..b438169a 100644
--- a/src/java/nginx/clojure/asm/signature/SignatureVisitor.java
+++ b/src/java/nginx/clojure/asm/signature/SignatureVisitor.java
@@ -6,13 +6,13 @@
// 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.
+// 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.
+// 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.
+// 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
@@ -60,24 +60,25 @@ public abstract class SignatureVisitor {
public static final char INSTANCEOF = '=';
/**
- * The ASM API version implemented by this visitor. The value of this field must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * 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;
/**
* Constructs a new {@link SignatureVisitor}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
- @SuppressWarnings("deprecation")
- public SignatureVisitor(final int api) {
- if (api != Opcodes.ASM7
+ protected SignatureVisitor(final int api) {
+ if (api != Opcodes.ASM9
+ && api != Opcodes.ASM8
+ && api != Opcodes.ASM7
&& api != Opcodes.ASM6
&& api != Opcodes.ASM5
&& api != Opcodes.ASM4
- && api != Opcodes.ASM8_EXPERIMENTAL) {
+ && api != Opcodes.ASM10_EXPERIMENTAL) {
throw new IllegalArgumentException("Unsupported api " + api);
}
this.api = api;
@@ -179,7 +180,8 @@ public SignatureVisitor visitArrayType() {
/**
* Starts the visit of a signature corresponding to a class or interface type.
*
- * @param name the internal name of the class or interface.
+ * @param name the internal name of the class or interface (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}).
*/
public void visitClassType(final String name) {}
diff --git a/src/java/nginx/clojure/asm/signature/SignatureWriter.java b/src/java/nginx/clojure/asm/signature/SignatureWriter.java
index a8914601..123986b2 100644
--- a/src/java/nginx/clojure/asm/signature/SignatureWriter.java
+++ b/src/java/nginx/clojure/asm/signature/SignatureWriter.java
@@ -41,7 +41,7 @@
public class SignatureWriter extends SignatureVisitor {
/** The builder used to construct the visited signature. */
- private final StringBuilder stringBuilder = new StringBuilder();
+ private final StringBuilder stringBuilder;
/** Whether the visited signature contains formal type parameters. */
private boolean hasFormals;
@@ -51,8 +51,9 @@ public class SignatureWriter extends SignatureVisitor {
/**
* The stack used to keep track of class types that have arguments. Each element of this stack is
- * a boolean encoded in one bit. The top of the stack is the least significant bit. Pushing false
- * = *2, pushing true = *2+1, popping = /2.
+ * a boolean encoded in one bit. The top of the stack is the least significant bit. The bottom of
+ * the stack is a sentinel element always equal to 1 (used to detect when the stack is full).
+ * Pushing false = {@code <<= 1}, pushing true = {@code ( <<= 1) | 1}, popping = {@code >>>= 1}.
*
*
Class type arguments must be surrounded with '<' and '>' and, because
*
@@ -62,15 +63,20 @@ public class SignatureWriter extends SignatureVisitor {
* SignatureWriter instances),
*
*
- *
we need a stack to properly balance these 'parentheses'. A new element is pushed on this
- * stack for each new visited type, and popped when the visit of this type ends (either is
+ *
we need a stack to properly balance these angle brackets. A new element is pushed on this
+ * stack for each new visited type, and popped when the visit of this type ends (either in
* visitEnd, or because visitInnerClassType is called).
*/
- private int argumentStack;
+ private int argumentStack = 1;
/** Constructs a new {@link SignatureWriter}. */
public SignatureWriter() {
- super(Opcodes.ASM7);
+ this(new StringBuilder());
+ }
+
+ private SignatureWriter(final StringBuilder stringBuilder) {
+ super(/* latest api =*/ Opcodes.ASM9);
+ this.stringBuilder = stringBuilder;
}
// -----------------------------------------------------------------------------------------------
@@ -159,7 +165,7 @@ public void visitClassType(final String name) {
stringBuilder.append(name);
// Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as
// we can tell at this point).
- argumentStack *= 2;
+ argumentStack <<= 1;
}
@Override
@@ -169,7 +175,7 @@ public void visitInnerClassType(final String name) {
stringBuilder.append(name);
// Pushes 'false' on the stack, meaning that this type does not have type arguments (as far as
// we can tell at this point).
- argumentStack *= 2;
+ argumentStack <<= 1;
}
@Override
@@ -177,7 +183,7 @@ public void visitTypeArgument() {
// If the top of the stack is 'false', this means we are visiting the first type argument of the
// currently visited type. We therefore need to append a '<', and to replace the top stack
// element with 'true' (meaning that the current type does have type arguments).
- if (argumentStack % 2 == 0) {
+ if ((argumentStack & 1) == 0) {
argumentStack |= 1;
stringBuilder.append('<');
}
@@ -189,14 +195,15 @@ public SignatureVisitor visitTypeArgument(final char wildcard) {
// If the top of the stack is 'false', this means we are visiting the first type argument of the
// currently visited type. We therefore need to append a '<', and to replace the top stack
// element with 'true' (meaning that the current type does have type arguments).
- if (argumentStack % 2 == 0) {
+ if ((argumentStack & 1) == 0) {
argumentStack |= 1;
stringBuilder.append('<');
}
if (wildcard != '=') {
stringBuilder.append(wildcard);
}
- return this;
+ // If the stack is full, start a nested one by returning a new SignatureWriter.
+ return (argumentStack & (1 << 31)) == 0 ? this : new SignatureWriter(stringBuilder);
}
@Override
@@ -232,9 +239,9 @@ private void endArguments() {
// If the top of the stack is 'true', this means that some type arguments have been visited for
// the type whose visit is now ending. We therefore need to append a '>', and to pop one element
// from the stack.
- if (argumentStack % 2 == 1) {
+ if ((argumentStack & 1) == 1) {
stringBuilder.append('>');
}
- argumentStack /= 2;
+ argumentStack >>>= 1;
}
}
diff --git a/src/java/nginx/clojure/asm/tree/AbstractInsnNode.java b/src/java/nginx/clojure/asm/tree/AbstractInsnNode.java
index d8127d1d..af5338c2 100644
--- a/src/java/nginx/clojure/asm/tree/AbstractInsnNode.java
+++ b/src/java/nginx/clojure/asm/tree/AbstractInsnNode.java
@@ -89,7 +89,10 @@ public abstract class AbstractInsnNode {
/** The type of {@link LineNumberNode} "instructions". */
public static final int LINE = 15;
- /** The opcode of this instruction. */
+ /**
+ * The opcode of this instruction, or -1 if this is not a JVM instruction (e.g. a label or a line
+ * number).
+ */
protected int opcode;
/**
@@ -132,7 +135,8 @@ protected AbstractInsnNode(final int opcode) {
/**
* Returns the opcode of this instruction.
*
- * @return the opcode of this instruction.
+ * @return the opcode of this instruction, or -1 if this is not a JVM instruction (e.g. a label or
+ * a line number).
*/
public int getOpcode() {
return opcode;
diff --git a/src/java/nginx/clojure/asm/tree/AnnotationNode.java b/src/java/nginx/clojure/asm/tree/AnnotationNode.java
index 33d43ee2..d9e582a4 100644
--- a/src/java/nginx/clojure/asm/tree/AnnotationNode.java
+++ b/src/java/nginx/clojure/asm/tree/AnnotationNode.java
@@ -61,7 +61,7 @@ public class AnnotationNode extends AnnotationVisitor {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public AnnotationNode(final String descriptor) {
- this(/* latest api = */ Opcodes.ASM7, descriptor);
+ this(/* latest api = */ Opcodes.ASM9, descriptor);
if (getClass() != AnnotationNode.class) {
throw new IllegalStateException();
}
@@ -70,8 +70,8 @@ public AnnotationNode(final String descriptor) {
/**
* Constructs a new {@link AnnotationNode}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param descriptor the class descriptor of the annotation class.
*/
public AnnotationNode(final int api, final String descriptor) {
@@ -85,7 +85,7 @@ public AnnotationNode(final int api, final String descriptor) {
* @param values where the visited values must be stored.
*/
AnnotationNode(final List values) {
- super(/* latest api = */ Opcodes.ASM7);
+ super(/* latest api = */ Opcodes.ASM9);
this.values = values;
}
@@ -173,8 +173,8 @@ public void visitEnd() {
* checks that this node, and all its children recursively, do not contain elements that were
* introduced in more recent versions of the ASM API than the given version.
*
- * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}, {@link Opcodes#ASM5},
- * {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api an ASM API version. Must be one of the {@code ASM}x values in {@link
+ * Opcodes}.
*/
public void check(final int api) {
// nothing to do
diff --git a/src/java/nginx/clojure/asm/tree/ClassNode.java b/src/java/nginx/clojure/asm/tree/ClassNode.java
index 3edfa054..c02f7ac5 100644
--- a/src/java/nginx/clojure/asm/tree/ClassNode.java
+++ b/src/java/nginx/clojure/asm/tree/ClassNode.java
@@ -37,6 +37,7 @@
import nginx.clojure.asm.MethodVisitor;
import nginx.clojure.asm.ModuleVisitor;
import nginx.clojure.asm.Opcodes;
+import nginx.clojure.asm.RecordComponentVisitor;
import nginx.clojure.asm.TypePath;
/**
@@ -54,18 +55,18 @@ public class ClassNode extends ClassVisitor {
/**
* The class's access flags (see {@link nginx.clojure.asm.Opcodes}). This field also indicates if
- * the class is deprecated.
+ * the class is deprecated {@link Opcodes#ACC_DEPRECATED} or a record {@link Opcodes#ACC_RECORD}.
*/
public int access;
- /** The internal name of this class (see {@link nginx.clojure.asm.Type#getInternalName}). */
+ /** The internal name of this class (see {@link nginx.clojure.asm.Type#getInternalName()}). */
public String name;
/** The signature of this class. May be {@literal null}. */
public String signature;
/**
- * The internal of name of the super class (see {@link nginx.clojure.asm.Type#getInternalName}).
+ * The internal of name of the super class (see {@link nginx.clojure.asm.Type#getInternalName()}).
* For interfaces, the super class is {@link Object}. May be {@literal null}, but only for the
* {@link Object} class.
*/
@@ -73,7 +74,7 @@ public class ClassNode extends ClassVisitor {
/**
* The internal names of the interfaces directly implemented by this class (see {@link
- * nginx.clojure.asm.Type#getInternalName}).
+ * nginx.clojure.asm.Type#getInternalName()}).
*/
public List interfaces;
@@ -88,18 +89,26 @@ public class ClassNode extends ClassVisitor {
/** The module stored in this class. May be {@literal null}. */
public ModuleNode module;
- /** The internal name of the enclosing class of this class. May be {@literal null}. */
+ /**
+ * The internal name of the enclosing class of this class (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}). Must be {@literal null} if this class has no
+ * enclosing class, or if it is a local or anonymous class.
+ */
public String outerClass;
/**
- * The name of the method that contains this class, or {@literal null} if this class is not
- * enclosed in a method.
+ * The name of the method that contains the class, or {@literal null} if the class has no
+ * enclosing class, or 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 String outerMethod;
/**
- * The descriptor of the method that contains this class, or {@literal null} if this class is not
- * enclosed in a method.
+ * The descriptor of the method that contains the class, or {@literal null} if the class has no
+ * enclosing class, or 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 String outerMethodDesc;
@@ -121,20 +130,26 @@ public class ClassNode extends ClassVisitor {
/** The inner classes of this class. */
public List innerClasses;
- /** The internal name of the nest host class of this class. May be {@literal null}. */
+ /**
+ * The internal name of the nest host class of this class (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}). May be {@literal null}.
+ */
public String nestHostClass;
- /** The internal names of the nest members of this class. May be {@literal null}. */
+ /**
+ * The internal names of the nest members of this class (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}). May be {@literal null}.
+ */
public List nestMembers;
/**
- * Experimental, use at your own risk. This method will be renamed when it becomes stable, this
- * will break existing code using it. The internal names of the permitted subtypes of this
- * class. May be {@literal null}.
- *
- * @deprecated this API is experimental.
+ * The internal names of the permitted subclasses of this class (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}). May be {@literal null}.
*/
- @Deprecated public List permittedSubtypesExperimental;
+ public List permittedSubclasses;
+
+ /** The record components of this class. May be {@literal null}. */
+ public List recordComponents;
/** The fields of this class. */
public List fields;
@@ -149,7 +164,7 @@ public class ClassNode extends ClassVisitor {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public ClassNode() {
- this(Opcodes.ASM7);
+ this(Opcodes.ASM9);
if (getClass() != ClassNode.class) {
throw new IllegalStateException();
}
@@ -158,8 +173,8 @@ public ClassNode() {
/**
* Constructs a new {@link ClassNode}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
public ClassNode(final int api) {
super(api);
@@ -247,8 +262,8 @@ public void visitNestMember(final String nestMember) {
}
@Override
- public void visitPermittedSubtypeExperimental(final String permittedSubtype) {
- permittedSubtypesExperimental = Util.add(permittedSubtypesExperimental, permittedSubtype);
+ public void visitPermittedSubclass(final String permittedSubclass) {
+ permittedSubclasses = Util.add(permittedSubclasses, permittedSubclass);
}
@Override
@@ -258,6 +273,14 @@ public void visitInnerClass(
innerClasses.add(innerClass);
}
+ @Override
+ public RecordComponentVisitor visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ RecordComponentNode recordComponent = new RecordComponentNode(name, descriptor, signature);
+ recordComponents = Util.add(recordComponents, recordComponent);
+ return recordComponent;
+ }
+
@Override
public FieldVisitor visitField(
final int access,
@@ -296,12 +319,14 @@ public void visitEnd() {
* that this node, and all its children recursively, do not contain elements that were introduced
* in more recent versions of the ASM API than the given version.
*
- * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}, {@link Opcodes#ASM5},
- * {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api an ASM API version. Must be one of the {@code ASM}x values in {@link
+ * Opcodes}.
*/
- @SuppressWarnings("deprecation")
public void check(final int api) {
- if (api != Opcodes.ASM8_EXPERIMENTAL && permittedSubtypesExperimental != null) {
+ if (api < Opcodes.ASM9 && permittedSubclasses != null) {
+ throw new UnsupportedClassVersionException();
+ }
+ if (api < Opcodes.ASM8 && ((access & Opcodes.ACC_RECORD) != 0 || recordComponents != null)) {
throw new UnsupportedClassVersionException();
}
if (api < Opcodes.ASM7 && (nestHostClass != null || nestMembers != null)) {
@@ -339,6 +364,11 @@ public void check(final int api) {
invisibleTypeAnnotations.get(i).check(api);
}
}
+ if (recordComponents != null) {
+ for (int i = recordComponents.size() - 1; i >= 0; --i) {
+ recordComponents.get(i).check(api);
+ }
+ }
for (int i = fields.size() - 1; i >= 0; --i) {
fields.get(i).check(api);
}
@@ -352,7 +382,6 @@ public void check(final int api) {
*
* @param classVisitor a class visitor.
*/
- @SuppressWarnings("deprecation")
public void accept(final ClassVisitor classVisitor) {
// Visit the header.
String[] interfacesArray = new String[this.interfaces.size()];
@@ -415,16 +444,22 @@ public void accept(final ClassVisitor classVisitor) {
classVisitor.visitNestMember(nestMembers.get(i));
}
}
- // Visit the permitted subtypes.
- if (permittedSubtypesExperimental != null) {
- for (int i = 0, n = permittedSubtypesExperimental.size(); i < n; ++i) {
- classVisitor.visitPermittedSubtypeExperimental(permittedSubtypesExperimental.get(i));
+ // Visit the permitted subclasses.
+ if (permittedSubclasses != null) {
+ for (int i = 0, n = permittedSubclasses.size(); i < n; ++i) {
+ classVisitor.visitPermittedSubclass(permittedSubclasses.get(i));
}
}
// Visit the inner classes.
for (int i = 0, n = innerClasses.size(); i < n; ++i) {
innerClasses.get(i).accept(classVisitor);
}
+ // Visit the record components.
+ if (recordComponents != null) {
+ for (int i = 0, n = recordComponents.size(); i < n; ++i) {
+ recordComponents.get(i).accept(classVisitor);
+ }
+ }
// Visit the fields.
for (int i = 0, n = fields.size(); i < n; ++i) {
fields.get(i).accept(classVisitor);
diff --git a/src/java/nginx/clojure/asm/tree/FieldInsnNode.java b/src/java/nginx/clojure/asm/tree/FieldInsnNode.java
index 9b18b443..f82eafb4 100644
--- a/src/java/nginx/clojure/asm/tree/FieldInsnNode.java
+++ b/src/java/nginx/clojure/asm/tree/FieldInsnNode.java
@@ -41,7 +41,7 @@ public class FieldInsnNode extends AbstractInsnNode {
/**
* The internal name of the field's owner class (see {@link
- * nginx.clojure.asm.Type#getInternalName}).
+ * nginx.clojure.asm.Type#getInternalName()}).
*/
public String owner;
@@ -57,7 +57,7 @@ public class FieldInsnNode extends AbstractInsnNode {
* @param opcode the opcode of the type instruction to be constructed. This opcode must be
* GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
* @param owner the internal name of the field's owner class (see {@link
- * nginx.clojure.asm.Type#getInternalName}).
+ * nginx.clojure.asm.Type#getInternalName()}).
* @param name the field's name.
* @param descriptor the field's descriptor (see {@link nginx.clojure.asm.Type}).
*/
diff --git a/src/java/nginx/clojure/asm/tree/FieldNode.java b/src/java/nginx/clojure/asm/tree/FieldNode.java
index 269bb142..cb40a8e1 100644
--- a/src/java/nginx/clojure/asm/tree/FieldNode.java
+++ b/src/java/nginx/clojure/asm/tree/FieldNode.java
@@ -100,17 +100,17 @@ public FieldNode(
final String descriptor,
final String signature,
final Object value) {
- this(/* latest api = */ Opcodes.ASM7, access, name, descriptor, signature, value);
+ this(/* latest api = */ Opcodes.ASM9, access, name, descriptor, signature, value);
if (getClass() != FieldNode.class) {
throw new IllegalStateException();
}
}
/**
- * Constructs a new {@link FieldNode}. Subclasses must not use this constructor.
+ * Constructs a new {@link FieldNode}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM4}
- * or {@link Opcodes#ASM5}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param access the field's access flags (see {@link nginx.clojure.asm.Opcodes}). This parameter
* also indicates if the field is synthetic and/or deprecated.
* @param name the field's name.
@@ -181,8 +181,8 @@ public void visitEnd() {
* that this node, and all its children recursively, do not contain elements that were introduced
* in more recent versions of the ASM API than the given version.
*
- * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}, {@link Opcodes#ASM5},
- * {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api an ASM API version. Must be one of the {@code ASM}x values in {@link
+ * Opcodes}.
*/
public void check(final int api) {
if (api == Opcodes.ASM4) {
diff --git a/src/java/nginx/clojure/asm/tree/FrameNode.java b/src/java/nginx/clojure/asm/tree/FrameNode.java
index 16a0f842..eee91a85 100644
--- a/src/java/nginx/clojure/asm/tree/FrameNode.java
+++ b/src/java/nginx/clojure/asm/tree/FrameNode.java
@@ -80,14 +80,18 @@ private FrameNode() {
* @param type the type of this 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 number of local variables of this stack map frame.
+ * @param numLocal number of local variables of this stack map frame. Long and double values count
+ * for one variable.
* @param local the types of the local variables of this stack map frame. Elements of this list
* can be Integer, String or LabelNode objects (for primitive, reference and uninitialized
- * types respectively - see {@link MethodVisitor}).
- * @param numStack number of operand stack elements of this stack map frame.
+ * types respectively - see {@link MethodVisitor}). Long and double values are represented by
+ * a single element.
+ * @param numStack number of operand stack elements of this stack map frame. Long and double
+ * values count for one stack element.
* @param stack the types of the operand stack elements of this stack map frame. Elements of this
* list can be Integer, String or LabelNode objects (for primitive, reference and
- * uninitialized types respectively - see {@link MethodVisitor}).
+ * uninitialized types respectively - see {@link MethodVisitor}). Long and double values are
+ * represented by a single element.
*/
public FrameNode(
final int type,
diff --git a/src/java/nginx/clojure/asm/tree/IincInsnNode.java b/src/java/nginx/clojure/asm/tree/IincInsnNode.java
index 07d1c464..744c796f 100644
--- a/src/java/nginx/clojure/asm/tree/IincInsnNode.java
+++ b/src/java/nginx/clojure/asm/tree/IincInsnNode.java
@@ -48,12 +48,12 @@ public class IincInsnNode extends AbstractInsnNode {
/**
* Constructs a new {@link IincInsnNode}.
*
- * @param var index of the local variable to be incremented.
+ * @param varIndex index of the local variable to be incremented.
* @param incr increment amount to increment the local variable by.
*/
- public IincInsnNode(final int var, final int incr) {
+ public IincInsnNode(final int varIndex, final int incr) {
super(Opcodes.IINC);
- this.var = var;
+ this.var = varIndex;
this.incr = incr;
}
diff --git a/src/java/nginx/clojure/asm/tree/InnerClassNode.java b/src/java/nginx/clojure/asm/tree/InnerClassNode.java
index 49da149f..a60d5885 100644
--- a/src/java/nginx/clojure/asm/tree/InnerClassNode.java
+++ b/src/java/nginx/clojure/asm/tree/InnerClassNode.java
@@ -30,7 +30,11 @@
import nginx.clojure.asm.ClassVisitor;
/**
- * A node that represents an inner class.
+ * A node that represents an inner class. This inner class is not necessarily a member of the {@link
+ * ClassNode} containing this object. More precisely, every class or interface C which is referenced
+ * by a {@link ClassNode} and which is not a package member must be represented with an {@link
+ * InnerClassNode}. The {@link ClassNode} must reference its nested class or interface members, and
+ * its enclosing class, if any. See the JVMS 4.7.6 section for more details.
*
* @author Eric Bruneton
*/
@@ -46,25 +50,27 @@ public class InnerClassNode {
public String outerName;
/**
- * The (simple) name of the inner class inside its enclosing class. May be {@literal null} for
- * anonymous inner classes.
+ * The (simple) name of the inner class inside its enclosing class. Must be {@literal null} if the
+ * inner class is not the member of a class or interface (e.g. for local or anonymous classes).
*/
public String innerName;
- /** The access flags of the inner class as originally declared in the enclosing class. */
+ /**
+ * The access flags of the inner class as originally declared in the source code from which the
+ * class was compiled.
+ */
public int access;
/**
- * Constructs a new {@link InnerClassNode}.
+ * Constructs a new {@link InnerClassNode} for an inner class C.
*
- * @param name the internal name of an inner class (see {@link
- * nginx.clojure.asm.Type#getInternalName()}).
- * @param outerName the internal name of the class to which the inner class belongs (see {@link
- * nginx.clojure.asm.Type#getInternalName()}). May be {@literal null}.
- * @param innerName the (simple) name of the inner class inside its enclosing class. May be
- * {@literal null} for anonymous inner classes.
- * @param access the access flags of the inner class as originally declared in the enclosing
- * class.
+ * @param name the internal name of C (see {@link nginx.clojure.asm.Type#getInternalName()}).
+ * @param outerName the internal name of the class or interface C is a member of (see {@link
+ * nginx.clojure.asm.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 InnerClassNode(
final String name, final String outerName, final String innerName, final int access) {
diff --git a/src/java/nginx/clojure/asm/tree/InsnList.java b/src/java/nginx/clojure/asm/tree/InsnList.java
index f2815270..c9bfd009 100644
--- a/src/java/nginx/clojure/asm/tree/InsnList.java
+++ b/src/java/nginx/clojure/asm/tree/InsnList.java
@@ -488,12 +488,19 @@ private final class InsnListIterator implements ListIterator {
AbstractInsnNode remove;
InsnListIterator(final int index) {
- if (index == size()) {
+ if (index < 0 || index > size()) {
+ throw new IndexOutOfBoundsException();
+ } else if (index == size()) {
nextInsn = null;
previousInsn = getLast();
} else {
- nextInsn = get(index);
- previousInsn = nextInsn.previousInsn;
+ AbstractInsnNode currentInsn = getFirst();
+ for (int i = 0; i < index; i++) {
+ currentInsn = currentInsn.nextInsn;
+ }
+
+ nextInsn = currentInsn;
+ previousInsn = currentInsn.previousInsn;
}
}
diff --git a/src/java/nginx/clojure/asm/tree/LdcInsnNode.java b/src/java/nginx/clojure/asm/tree/LdcInsnNode.java
index efe8bba8..9dc95dd3 100644
--- a/src/java/nginx/clojure/asm/tree/LdcInsnNode.java
+++ b/src/java/nginx/clojure/asm/tree/LdcInsnNode.java
@@ -29,8 +29,11 @@
import java.util.Map;
+import nginx.clojure.asm.ConstantDynamic;
+import nginx.clojure.asm.Handle;
import nginx.clojure.asm.MethodVisitor;
import nginx.clojure.asm.Opcodes;
+import nginx.clojure.asm.Type;
/**
* A node that represents an LDC instruction.
@@ -40,17 +43,23 @@
public class LdcInsnNode extends AbstractInsnNode {
/**
- * 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} or a {@link
- * nginx.clojure.asm.Type}.
+ * The constant to be loaded on the stack. This field 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 Object cst;
/**
* Constructs a new {@link LdcInsnNode}.
*
- * @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} or a {@link String}.
+ * @param value the constant to be loaded on the stack. This parameter mist 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 LdcInsnNode(final Object value) {
super(Opcodes.LDC);
diff --git a/src/java/nginx/clojure/asm/tree/LocalVariableAnnotationNode.java b/src/java/nginx/clojure/asm/tree/LocalVariableAnnotationNode.java
index 64020eac..77b3ed14 100644
--- a/src/java/nginx/clojure/asm/tree/LocalVariableAnnotationNode.java
+++ b/src/java/nginx/clojure/asm/tree/LocalVariableAnnotationNode.java
@@ -85,14 +85,14 @@ public LocalVariableAnnotationNode(
final LabelNode[] end,
final int[] index,
final String descriptor) {
- this(/* latest api = */ Opcodes.ASM7, typeRef, typePath, start, end, index, descriptor);
+ this(/* latest api = */ Opcodes.ASM9, typeRef, typePath, start, end, index, descriptor);
}
/**
* Constructs a new {@link LocalVariableAnnotationNode}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param typeRef a reference to the annotated type. See {@link nginx.clojure.asm.TypeReference}.
* @param start the fist instructions corresponding to the continuous ranges that make the scope
* of this local variable (inclusive).
diff --git a/src/java/nginx/clojure/asm/tree/MethodNode.java b/src/java/nginx/clojure/asm/tree/MethodNode.java
index 3168ddba..d312ed0a 100644
--- a/src/java/nginx/clojure/asm/tree/MethodNode.java
+++ b/src/java/nginx/clojure/asm/tree/MethodNode.java
@@ -156,7 +156,7 @@ public class MethodNode extends MethodVisitor {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public MethodNode() {
- this(/* latest api = */ Opcodes.ASM7);
+ this(/* latest api = */ Opcodes.ASM9);
if (getClass() != MethodNode.class) {
throw new IllegalStateException();
}
@@ -165,8 +165,8 @@ public MethodNode() {
/**
* Constructs an uninitialized {@link MethodNode}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
public MethodNode(final int api) {
super(api);
@@ -192,7 +192,7 @@ public MethodNode(
final String descriptor,
final String signature,
final String[] exceptions) {
- this(/* latest api = */ Opcodes.ASM7, access, name, descriptor, signature, exceptions);
+ this(/* latest api = */ Opcodes.ASM9, access, name, descriptor, signature, exceptions);
if (getClass() != MethodNode.class) {
throw new IllegalStateException();
}
@@ -201,8 +201,8 @@ public MethodNode(
/**
* Constructs a new {@link MethodNode}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @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.
@@ -348,8 +348,8 @@ public void visitIntInsn(final int opcode, final int operand) {
}
@Override
- public void visitVarInsn(final int opcode, final int var) {
- instructions.add(new VarInsnNode(opcode, var));
+ public void visitVarInsn(final int opcode, final int varIndex) {
+ instructions.add(new VarInsnNode(opcode, varIndex));
}
@Override
@@ -407,8 +407,8 @@ public void visitLdcInsn(final Object value) {
}
@Override
- public void visitIincInsn(final int var, final int increment) {
- instructions.add(new IincInsnNode(var, increment));
+ public void visitIincInsn(final int varIndex, final int increment) {
+ instructions.add(new IincInsnNode(varIndex, increment));
}
@Override
@@ -566,8 +566,8 @@ private Object[] getLabelNodes(final Object[] objects) {
* that this node, and all its children recursively, do not contain elements that were introduced
* in more recent versions of the ASM API than the given version.
*
- * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}, {@link Opcodes#ASM5},
- * {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api an ASM API version. Must be one of the {@code ASM}x values in {@link
+ * Opcodes}.
*/
public void check(final int api) {
if (api == Opcodes.ASM4) {
diff --git a/src/java/nginx/clojure/asm/tree/ModuleExportNode.java b/src/java/nginx/clojure/asm/tree/ModuleExportNode.java
index ac06fd4b..f4ecc75a 100644
--- a/src/java/nginx/clojure/asm/tree/ModuleExportNode.java
+++ b/src/java/nginx/clojure/asm/tree/ModuleExportNode.java
@@ -38,7 +38,10 @@
*/
public class ModuleExportNode {
- /** The internal name of the exported package. */
+ /**
+ * The internal name of the exported package (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}).
+ */
public String packaze;
/**
@@ -56,7 +59,8 @@ public class ModuleExportNode {
/**
* Constructs a new {@link ModuleExportNode}.
*
- * @param packaze the internal name of the exported package.
+ * @param packaze the internal name of the exported package (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}).
* @param access the package access flags, one or more of {@code ACC_SYNTHETIC} and {@code
* ACC_MANDATED}.
* @param modules a list of modules that can access this exported package, specified with fully
diff --git a/src/java/nginx/clojure/asm/tree/ModuleNode.java b/src/java/nginx/clojure/asm/tree/ModuleNode.java
index 2897cf1c..5907e649 100644
--- a/src/java/nginx/clojure/asm/tree/ModuleNode.java
+++ b/src/java/nginx/clojure/asm/tree/ModuleNode.java
@@ -53,10 +53,16 @@ public class ModuleNode extends ModuleVisitor {
/** The version of this module. May be {@literal null}. */
public String version;
- /** The internal name of the main class of this module. May be {@literal null}. */
+ /**
+ * The internal name of the main class of this module (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}). May be {@literal null}.
+ */
public String mainClass;
- /** The internal name of the packages declared by this module. May be {@literal null}. */
+ /**
+ * The internal name of the packages declared by this module (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}). May be {@literal null}.
+ */
public List packages;
/** The dependencies of this module. May be {@literal null}. */
@@ -68,7 +74,10 @@ public class ModuleNode extends ModuleVisitor {
/** The packages opened by this module. May be {@literal null}. */
public List opens;
- /** The internal names of the services used by this module. May be {@literal null}. */
+ /**
+ * The internal names of the services used by this module (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}). May be {@literal null}.
+ */
public List uses;
/** The services provided by this module. May be {@literal null}. */
@@ -85,7 +94,7 @@ public class ModuleNode extends ModuleVisitor {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public ModuleNode(final String name, final int access, final String version) {
- super(/* latest api = */ Opcodes.ASM7);
+ super(/* latest api = */ Opcodes.ASM9);
if (getClass() != ModuleNode.class) {
throw new IllegalStateException();
}
@@ -98,8 +107,8 @@ public ModuleNode(final String name, final int access, final String version) {
/**
* Constructs a {@link ModuleNode}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6}
- * or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link
+ * Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link Opcodes#ASM8} or {@link Opcodes#ASM9}.
* @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}.
@@ -107,7 +116,8 @@ public ModuleNode(final String name, final int access, final String version) {
* @param requires The dependencies of this module. May be {@literal null}.
* @param exports The packages exported by this module. May be {@literal null}.
* @param opens The packages opened by this module. May be {@literal null}.
- * @param uses The internal names of the services used by this module. May be {@literal null}.
+ * @param uses The internal names of the services used by this module (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}). May be {@literal null}.
* @param provides The services provided by this module. May be {@literal null}.
*/
public ModuleNode(
diff --git a/src/java/nginx/clojure/asm/tree/ModuleOpenNode.java b/src/java/nginx/clojure/asm/tree/ModuleOpenNode.java
index 2768ccf8..517e3bea 100644
--- a/src/java/nginx/clojure/asm/tree/ModuleOpenNode.java
+++ b/src/java/nginx/clojure/asm/tree/ModuleOpenNode.java
@@ -38,7 +38,9 @@
*/
public class ModuleOpenNode {
- /** The internal name of the opened package. */
+ /**
+ * The internal name of the opened package (see {@link nginx.clojure.asm.Type#getInternalName()}).
+ */
public String packaze;
/**
@@ -56,7 +58,8 @@ public class ModuleOpenNode {
/**
* Constructs a new {@link ModuleOpenNode}.
*
- * @param packaze the internal name of the opened package.
+ * @param packaze the internal name of the opened package (see {@link
+ * nginx.clojure.asm.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
diff --git a/src/java/nginx/clojure/asm/tree/ModuleProvideNode.java b/src/java/nginx/clojure/asm/tree/ModuleProvideNode.java
index 23743c22..b46626f6 100644
--- a/src/java/nginx/clojure/asm/tree/ModuleProvideNode.java
+++ b/src/java/nginx/clojure/asm/tree/ModuleProvideNode.java
@@ -38,10 +38,13 @@
*/
public class ModuleProvideNode {
- /** The internal name of the service. */
+ /** The internal name of the service (see {@link nginx.clojure.asm.Type#getInternalName()}). */
public String service;
- /** The internal names of the implementations of the service (there is at least one provider). */
+ /**
+ * The internal names of the implementations of the service (there is at least one provider). See
+ * {@link nginx.clojure.asm.Type#getInternalName()}.
+ */
public List providers;
/**
@@ -49,7 +52,7 @@ public class ModuleProvideNode {
*
* @param service the internal name of the service.
* @param providers the internal names of the implementations of the service (there is at least
- * one provider).
+ * one provider). See {@link nginx.clojure.asm.Type#getInternalName()}.
*/
public ModuleProvideNode(final String service, final List providers) {
this.service = service;
diff --git a/src/java/nginx/clojure/asm/tree/RecordComponentNode.java b/src/java/nginx/clojure/asm/tree/RecordComponentNode.java
new file mode 100644
index 00000000..56ff651f
--- /dev/null
+++ b/src/java/nginx/clojure/asm/tree/RecordComponentNode.java
@@ -0,0 +1,205 @@
+// 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.tree;
+
+import java.util.List;
+
+import nginx.clojure.asm.AnnotationVisitor;
+import nginx.clojure.asm.Attribute;
+import nginx.clojure.asm.ClassVisitor;
+import nginx.clojure.asm.Opcodes;
+import nginx.clojure.asm.RecordComponentVisitor;
+import nginx.clojure.asm.TypePath;
+
+/**
+ * A node that represents a record component.
+ *
+ * @author Remi Forax
+ */
+public class RecordComponentNode extends RecordComponentVisitor {
+
+ /** The record component name. */
+ public String name;
+
+ /** The record component descriptor (see {@link nginx.clojure.asm.Type}). */
+ public String descriptor;
+
+ /** The record component signature. May be {@literal null}. */
+ public String signature;
+
+ /** The runtime visible annotations of this record component. May be {@literal null}. */
+ public List visibleAnnotations;
+
+ /** The runtime invisible annotations of this record component. May be {@literal null}. */
+ public List invisibleAnnotations;
+
+ /** The runtime visible type annotations of this record component. May be {@literal null}. */
+ public List visibleTypeAnnotations;
+
+ /** The runtime invisible type annotations of this record component. May be {@literal null}. */
+ public List invisibleTypeAnnotations;
+
+ /** The non standard attributes of this record component. * May be {@literal null}. */
+ public List attrs;
+
+ /**
+ * Constructs a new {@link RecordComponentNode}. Subclasses must not use this constructor.
+ * Instead, they must use the {@link #RecordComponentNode(int, String, String, String)} version.
+ *
+ * @param name the record component name.
+ * @param descriptor the record component descriptor (see {@link nginx.clojure.asm.Type}).
+ * @param signature the record component signature.
+ * @throws IllegalStateException If a subclass calls this constructor.
+ */
+ public RecordComponentNode(final String name, final String descriptor, final String signature) {
+ this(/* latest api = */ Opcodes.ASM9, name, descriptor, signature);
+ if (getClass() != RecordComponentNode.class) {
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Constructs a new {@link RecordComponentNode}.
+ *
+ * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM8}
+ * or {@link Opcodes#ASM9}.
+ * @param name the record component name.
+ * @param descriptor the record component descriptor (see {@link nginx.clojure.asm.Type}).
+ * @param signature the record component signature.
+ */
+ public RecordComponentNode(
+ final int api, final String name, final String descriptor, final String signature) {
+ super(api);
+ this.name = name;
+ this.descriptor = descriptor;
+ this.signature = signature;
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Implementation of the FieldVisitor abstract class
+ // -----------------------------------------------------------------------------------------------
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
+ AnnotationNode annotation = new AnnotationNode(descriptor);
+ if (visible) {
+ visibleAnnotations = Util.add(visibleAnnotations, annotation);
+ } else {
+ invisibleAnnotations = Util.add(invisibleAnnotations, annotation);
+ }
+ return annotation;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ TypeAnnotationNode typeAnnotation = new TypeAnnotationNode(typeRef, typePath, descriptor);
+ if (visible) {
+ visibleTypeAnnotations = Util.add(visibleTypeAnnotations, typeAnnotation);
+ } else {
+ invisibleTypeAnnotations = Util.add(invisibleTypeAnnotations, typeAnnotation);
+ }
+ return typeAnnotation;
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attribute) {
+ attrs = Util.add(attrs, attribute);
+ }
+
+ @Override
+ public void visitEnd() {
+ // Nothing to do.
+ }
+
+ // -----------------------------------------------------------------------------------------------
+ // Accept methods
+ // -----------------------------------------------------------------------------------------------
+
+ /**
+ * Checks that this record component node is compatible with the given ASM API version. This
+ * method checks that this node, and all its children recursively, do not contain elements that
+ * were introduced in more recent versions of the ASM API than the given version.
+ *
+ * @param api an ASM API version. Must be one of {@link Opcodes#ASM8} or {@link Opcodes#ASM9}.
+ */
+ public void check(final int api) {
+ if (api < Opcodes.ASM8) {
+ throw new UnsupportedClassVersionException();
+ }
+ }
+
+ /**
+ * Makes the given class visitor visit this record component.
+ *
+ * @param classVisitor a class visitor.
+ */
+ public void accept(final ClassVisitor classVisitor) {
+ RecordComponentVisitor recordComponentVisitor =
+ classVisitor.visitRecordComponent(name, descriptor, signature);
+ if (recordComponentVisitor == null) {
+ return;
+ }
+ // Visit the annotations.
+ if (visibleAnnotations != null) {
+ for (int i = 0, n = visibleAnnotations.size(); i < n; ++i) {
+ AnnotationNode annotation = visibleAnnotations.get(i);
+ annotation.accept(recordComponentVisitor.visitAnnotation(annotation.desc, true));
+ }
+ }
+ if (invisibleAnnotations != null) {
+ for (int i = 0, n = invisibleAnnotations.size(); i < n; ++i) {
+ AnnotationNode annotation = invisibleAnnotations.get(i);
+ annotation.accept(recordComponentVisitor.visitAnnotation(annotation.desc, false));
+ }
+ }
+ if (visibleTypeAnnotations != null) {
+ for (int i = 0, n = visibleTypeAnnotations.size(); i < n; ++i) {
+ TypeAnnotationNode typeAnnotation = visibleTypeAnnotations.get(i);
+ typeAnnotation.accept(
+ recordComponentVisitor.visitTypeAnnotation(
+ typeAnnotation.typeRef, typeAnnotation.typePath, typeAnnotation.desc, true));
+ }
+ }
+ if (invisibleTypeAnnotations != null) {
+ for (int i = 0, n = invisibleTypeAnnotations.size(); i < n; ++i) {
+ TypeAnnotationNode typeAnnotation = invisibleTypeAnnotations.get(i);
+ typeAnnotation.accept(
+ recordComponentVisitor.visitTypeAnnotation(
+ typeAnnotation.typeRef, typeAnnotation.typePath, typeAnnotation.desc, false));
+ }
+ }
+ // Visit the non standard attributes.
+ if (attrs != null) {
+ for (int i = 0, n = attrs.size(); i < n; ++i) {
+ recordComponentVisitor.visitAttribute(attrs.get(i));
+ }
+ }
+ recordComponentVisitor.visitEnd();
+ }
+}
diff --git a/src/java/nginx/clojure/asm/tree/TryCatchBlockNode.java b/src/java/nginx/clojure/asm/tree/TryCatchBlockNode.java
index 2f30aa31..ea6bdc96 100644
--- a/src/java/nginx/clojure/asm/tree/TryCatchBlockNode.java
+++ b/src/java/nginx/clojure/asm/tree/TryCatchBlockNode.java
@@ -67,8 +67,9 @@ public class TryCatchBlockNode {
* @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, or {@literal
- * null} to catch any exceptions (for "finally" blocks).
+ * @param type the internal name of the type of exceptions handled by the handler (see {@link
+ * nginx.clojure.asm.Type#getInternalName()}), or {@literal null} to catch any exceptions (for
+ * "finally" blocks).
*/
public TryCatchBlockNode(
final LabelNode start, final LabelNode end, final LabelNode handler, final String type) {
diff --git a/src/java/nginx/clojure/asm/tree/TypeAnnotationNode.java b/src/java/nginx/clojure/asm/tree/TypeAnnotationNode.java
index 56f3fae1..969a7c52 100644
--- a/src/java/nginx/clojure/asm/tree/TypeAnnotationNode.java
+++ b/src/java/nginx/clojure/asm/tree/TypeAnnotationNode.java
@@ -59,7 +59,7 @@ public class TypeAnnotationNode extends AnnotationNode {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public TypeAnnotationNode(final int typeRef, final TypePath typePath, final String descriptor) {
- this(/* latest api = */ Opcodes.ASM7, typeRef, typePath, descriptor);
+ this(/* latest api = */ Opcodes.ASM9, typeRef, typePath, descriptor);
if (getClass() != TypeAnnotationNode.class) {
throw new IllegalStateException();
}
@@ -68,8 +68,8 @@ public TypeAnnotationNode(final int typeRef, final TypePath typePath, final Stri
/**
* Constructs a new {@link AnnotationNode}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this visitor. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param typeRef a reference to the annotated type. See {@link nginx.clojure.asm.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
diff --git a/src/java/nginx/clojure/asm/tree/TypeInsnNode.java b/src/java/nginx/clojure/asm/tree/TypeInsnNode.java
index a8c3052a..10ba5c4e 100644
--- a/src/java/nginx/clojure/asm/tree/TypeInsnNode.java
+++ b/src/java/nginx/clojure/asm/tree/TypeInsnNode.java
@@ -32,16 +32,16 @@
import nginx.clojure.asm.MethodVisitor;
/**
- * A node that represents a type instruction. A type instruction is an instruction that takes a type
- * descriptor as parameter.
+ * A node that represents a type instruction. A type instruction is an instruction which takes an
+ * internal name as parameter (see {@link nginx.clojure.asm.Type#getInternalName()}).
*
* @author Eric Bruneton
*/
public class TypeInsnNode extends AbstractInsnNode {
/**
- * The operand of this instruction. This operand is an internal name (see {@link
- * nginx.clojure.asm.Type}).
+ * The operand of this instruction. Despite its name (due to historical reasons), this operand is
+ * an internal name (see {@link nginx.clojure.asm.Type#getInternalName()}).
*/
public String desc;
@@ -50,12 +50,12 @@ public class TypeInsnNode extends AbstractInsnNode {
*
* @param opcode the opcode of the type instruction to be constructed. This opcode must be NEW,
* ANEWARRAY, CHECKCAST or INSTANCEOF.
- * @param descriptor the operand of the instruction to be constructed. This operand is an internal
- * name (see {@link nginx.clojure.asm.Type}).
+ * @param type the operand of the instruction to be constructed. This operand is an internal name
+ * (see {@link nginx.clojure.asm.Type#getInternalName()}).
*/
- public TypeInsnNode(final int opcode, final String descriptor) {
+ public TypeInsnNode(final int opcode, final String type) {
super(opcode);
- this.desc = descriptor;
+ this.desc = type;
}
/**
diff --git a/src/java/nginx/clojure/asm/tree/Util.java b/src/java/nginx/clojure/asm/tree/Util.java
index 4c12c699..4f7511c3 100644
--- a/src/java/nginx/clojure/asm/tree/Util.java
+++ b/src/java/nginx/clojure/asm/tree/Util.java
@@ -41,7 +41,7 @@ final class Util {
private Util() {}
static List add(final List list, final T element) {
- List newList = list == null ? new ArrayList(1) : list;
+ List newList = list == null ? new ArrayList<>(1) : list;
newList.add(element);
return newList;
}
@@ -60,7 +60,7 @@ static List asArrayList(final T[] array) {
}
ArrayList list = new ArrayList<>(array.length);
for (T t : array) {
- list.add(t);
+ list.add(t); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return list;
}
@@ -71,7 +71,7 @@ static List asArrayList(final byte[] byteArray) {
}
ArrayList byteList = new ArrayList<>(byteArray.length);
for (byte b : byteArray) {
- byteList.add(b);
+ byteList.add(b); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return byteList;
}
@@ -82,7 +82,7 @@ static List asArrayList(final boolean[] booleanArray) {
}
ArrayList booleanList = new ArrayList<>(booleanArray.length);
for (boolean b : booleanArray) {
- booleanList.add(b);
+ booleanList.add(b); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return booleanList;
}
@@ -93,7 +93,7 @@ static List asArrayList(final short[] shortArray) {
}
ArrayList shortList = new ArrayList<>(shortArray.length);
for (short s : shortArray) {
- shortList.add(s);
+ shortList.add(s); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return shortList;
}
@@ -104,7 +104,7 @@ static List asArrayList(final char[] charArray) {
}
ArrayList charList = new ArrayList<>(charArray.length);
for (char c : charArray) {
- charList.add(c);
+ charList.add(c); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return charList;
}
@@ -115,7 +115,7 @@ static List asArrayList(final int[] intArray) {
}
ArrayList intList = new ArrayList<>(intArray.length);
for (int i : intArray) {
- intList.add(i);
+ intList.add(i); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return intList;
}
@@ -126,7 +126,7 @@ static List asArrayList(final float[] floatArray) {
}
ArrayList floatList = new ArrayList<>(floatArray.length);
for (float f : floatArray) {
- floatList.add(f);
+ floatList.add(f); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return floatList;
}
@@ -137,7 +137,7 @@ static List asArrayList(final long[] longArray) {
}
ArrayList longList = new ArrayList<>(longArray.length);
for (long l : longArray) {
- longList.add(l);
+ longList.add(l); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return longList;
}
@@ -148,7 +148,7 @@ static List asArrayList(final double[] doubleArray) {
}
ArrayList doubleList = new ArrayList<>(doubleArray.length);
for (double d : doubleArray) {
- doubleList.add(d);
+ doubleList.add(d); // NOPMD(UseArraysAsList): we want a modifiable list.
}
return doubleList;
}
diff --git a/src/java/nginx/clojure/asm/tree/VarInsnNode.java b/src/java/nginx/clojure/asm/tree/VarInsnNode.java
index b30956b4..9aa40688 100644
--- a/src/java/nginx/clojure/asm/tree/VarInsnNode.java
+++ b/src/java/nginx/clojure/asm/tree/VarInsnNode.java
@@ -47,12 +47,12 @@ public class VarInsnNode extends AbstractInsnNode {
*
* @param opcode the opcode of the local variable instruction to be constructed. This opcode must
* be ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
- * @param var the operand of the instruction to be constructed. This operand is the index of a
- * local variable.
+ * @param varIndex the operand of the instruction to be constructed. This operand is the index of
+ * a local variable.
*/
- public VarInsnNode(final int opcode, final int var) {
+ public VarInsnNode(final int opcode, final int varIndex) {
super(opcode);
- this.var = var;
+ this.var = varIndex;
}
/**
diff --git a/src/java/nginx/clojure/asm/tree/analysis/Analyzer.java b/src/java/nginx/clojure/asm/tree/analysis/Analyzer.java
index 27c59c74..fcf567ac 100644
--- a/src/java/nginx/clojure/asm/tree/analysis/Analyzer.java
+++ b/src/java/nginx/clojure/asm/tree/analysis/Analyzer.java
@@ -49,7 +49,7 @@
* A semantic bytecode analyzer. This class does not fully check that JSR and RET instructions
* are valid.
*
- * @param type of the Value used for the analysis.
+ * @param type of the {@link Value} used for the analysis.
* @author Eric Bruneton
*/
public class Analyzer implements Opcodes {
@@ -93,8 +93,10 @@ public Analyzer(final Interpreter interpreter) {
/**
* Analyzes the given method.
*
- * @param owner the internal name of the class to which 'method' belongs.
- * @param method the method to be analyzed.
+ * @param owner the internal name of the class to which 'method' belongs (see {@link
+ * Type#getInternalName()}).
+ * @param method the method to be analyzed. The maxStack and maxLocals fields must have correct
+ * values.
* @return the symbolic state of the execution stack frame at each bytecode instruction of the
* method. The size of the returned array is equal to the number of instructions (and labels)
* of the method. A given frame is {@literal null} if and only if the corresponding
@@ -132,32 +134,8 @@ public Frame[] analyze(final String owner, final MethodNode method) throws An
}
}
- // For each instruction, compute the subroutine to which it belongs.
- // Follow the main 'subroutine', and collect the jsr instructions to nested subroutines.
- Subroutine main = new Subroutine(null, method.maxLocals, null);
- List jsrInsns = new ArrayList<>();
- findSubroutine(0, main, jsrInsns);
- // Follow the nested subroutines, and collect their own nested subroutines, until all
- // subroutines are found.
- Map jsrSubroutines = new HashMap<>();
- while (!jsrInsns.isEmpty()) {
- JumpInsnNode jsrInsn = (JumpInsnNode) jsrInsns.remove(0);
- Subroutine subroutine = jsrSubroutines.get(jsrInsn.label);
- if (subroutine == null) {
- subroutine = new Subroutine(jsrInsn.label, method.maxLocals, jsrInsn);
- jsrSubroutines.put(jsrInsn.label, subroutine);
- findSubroutine(insnList.indexOf(jsrInsn.label), subroutine, jsrInsns);
- } else {
- subroutine.callers.add(jsrInsn);
- }
- }
- // Clear the main 'subroutine', which is not a real subroutine (and was used only as an
- // intermediate step above to find the real ones).
- for (int i = 0; i < insnListSize; ++i) {
- if (subroutines[i] != null && subroutines[i].start == null) {
- subroutines[i] = null;
- }
- }
+ // Finds the method's subroutines.
+ findSubroutines(method.maxLocals);
// Initializes the data structures for the control flow analysis.
Frame currentFrame = computeInitialFrame(owner, method);
@@ -252,17 +230,17 @@ public Frame[] analyze(final String owner, final MethodNode method) throws An
} else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) {
if (subroutine != null) {
if (insnNode instanceof VarInsnNode) {
- int var = ((VarInsnNode) insnNode).var;
- subroutine.localsUsed[var] = true;
+ int varIndex = ((VarInsnNode) insnNode).var;
+ subroutine.localsUsed[varIndex] = true;
if (insnOpcode == LLOAD
|| insnOpcode == DLOAD
|| insnOpcode == LSTORE
|| insnOpcode == DSTORE) {
- subroutine.localsUsed[var + 1] = true;
+ subroutine.localsUsed[varIndex + 1] = true;
}
} else if (insnNode instanceof IincInsnNode) {
- int var = ((IincInsnNode) insnNode).var;
- subroutine.localsUsed[var] = true;
+ int varIndex = ((IincInsnNode) insnNode).var;
+ subroutine.localsUsed[varIndex] = true;
}
}
merge(insnIndex + 1, currentFrame, subroutine);
@@ -300,6 +278,114 @@ public Frame[] analyze(final String owner, final MethodNode method) throws An
return frames;
}
+ /**
+ * Analyzes the given method and computes and sets its maximum stack size and maximum number of
+ * local variables.
+ *
+ * @param owner the internal name of the class to which 'method' belongs (see {@link
+ * Type#getInternalName()}).
+ * @param method the method to be analyzed.
+ * @return the symbolic state of the execution stack frame at each bytecode instruction of the
+ * method. The size of the returned array is equal to the number of instructions (and labels)
+ * of the method. A given frame is {@literal null} if and only if the corresponding
+ * instruction cannot be reached (dead code).
+ * @throws AnalyzerException if a problem occurs during the analysis.
+ */
+ public Frame[] analyzeAndComputeMaxs(final String owner, final MethodNode method)
+ throws AnalyzerException {
+ method.maxLocals = computeMaxLocals(method);
+ method.maxStack = -1;
+ analyze(owner, method);
+ method.maxStack = computeMaxStack(frames);
+ return frames;
+ }
+
+ /**
+ * Computes and returns the maximum number of local variables used in the given method.
+ *
+ * @param method a method.
+ * @return the maximum number of local variables used in the given method.
+ */
+ private static int computeMaxLocals(final MethodNode method) {
+ int maxLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2;
+ if ((method.access & Opcodes.ACC_STATIC) != 0) {
+ maxLocals -= 1;
+ }
+ for (AbstractInsnNode insnNode : method.instructions) {
+ if (insnNode instanceof VarInsnNode) {
+ int local = ((VarInsnNode) insnNode).var;
+ int size =
+ (insnNode.getOpcode() == Opcodes.LLOAD
+ || insnNode.getOpcode() == Opcodes.DLOAD
+ || insnNode.getOpcode() == Opcodes.LSTORE
+ || insnNode.getOpcode() == Opcodes.DSTORE)
+ ? 2
+ : 1;
+ maxLocals = Math.max(maxLocals, local + size);
+ } else if (insnNode instanceof IincInsnNode) {
+ int local = ((IincInsnNode) insnNode).var;
+ maxLocals = Math.max(maxLocals, local + 1);
+ }
+ }
+ return maxLocals;
+ }
+
+ /**
+ * Computes and returns the maximum stack size of a method, given its stack map frames.
+ *
+ * @param frames the stack map frames of a method.
+ * @return the maximum stack size of the given method.
+ */
+ private static int computeMaxStack(final Frame>[] frames) {
+ int maxStack = 0;
+ for (Frame> frame : frames) {
+ if (frame != null) {
+ int stackSize = 0;
+ for (int i = 0; i < frame.getStackSize(); ++i) {
+ stackSize += frame.getStack(i).getSize();
+ }
+ maxStack = Math.max(maxStack, stackSize);
+ }
+ }
+ return maxStack;
+ }
+
+ /**
+ * Finds the subroutines of the currently analyzed method and stores them in {@link #subroutines}.
+ *
+ * @param maxLocals the maximum number of local variables of the currently analyzed method (long
+ * and double values count for two variables).
+ * @throws AnalyzerException if the control flow graph can fall off the end of the code.
+ */
+ private void findSubroutines(final int maxLocals) throws AnalyzerException {
+ // For each instruction, compute the subroutine to which it belongs.
+ // Follow the main 'subroutine', and collect the jsr instructions to nested subroutines.
+ Subroutine main = new Subroutine(null, maxLocals, null);
+ List jsrInsns = new ArrayList<>();
+ findSubroutine(0, main, jsrInsns);
+ // Follow the nested subroutines, and collect their own nested subroutines, until all
+ // subroutines are found.
+ Map jsrSubroutines = new HashMap<>();
+ while (!jsrInsns.isEmpty()) {
+ JumpInsnNode jsrInsn = (JumpInsnNode) jsrInsns.remove(0);
+ Subroutine subroutine = jsrSubroutines.get(jsrInsn.label);
+ if (subroutine == null) {
+ subroutine = new Subroutine(jsrInsn.label, maxLocals, jsrInsn);
+ jsrSubroutines.put(jsrInsn.label, subroutine);
+ findSubroutine(insnList.indexOf(jsrInsn.label), subroutine, jsrInsns);
+ } else {
+ subroutine.callers.add(jsrInsn);
+ }
+ }
+ // Clear the main 'subroutine', which is not a real subroutine (and was used only as an
+ // intermediate step above to find the real ones).
+ for (int i = 0; i < insnListSize; ++i) {
+ if (subroutines[i] != null && subroutines[i].start == null) {
+ subroutines[i] = null;
+ }
+ }
+ }
+
/**
* Follows the control flow graph of the currently analyzed method, starting at the given
* instruction index, and stores a copy of the given subroutine in {@link #subroutines} for each
@@ -385,7 +471,8 @@ private void findSubroutine(
/**
* Computes the initial execution stack frame of the given method.
*
- * @param owner the internal name of the class to which 'method' belongs.
+ * @param owner the internal name of the class to which 'method' belongs (see {@link
+ * Type#getInternalName()}).
* @param method the method to be analyzed.
* @return the initial execution stack frame of the 'method'.
*/
@@ -442,9 +529,10 @@ public List getHandlers(final int insnIndex) {
/**
* Initializes this analyzer. This method is called just before the execution of control flow
- * analysis loop in #analyze. The default implementation of this method does nothing.
+ * analysis loop in {@link #analyze}. The default implementation of this method does nothing.
*
- * @param owner the internal name of the class to which the method belongs.
+ * @param owner the internal name of the class to which the method belongs (see {@link
+ * Type#getInternalName()}).
* @param method the method to be analyzed.
* @throws AnalyzerException if a problem occurs.
*/
diff --git a/src/java/nginx/clojure/asm/tree/analysis/BasicInterpreter.java b/src/java/nginx/clojure/asm/tree/analysis/BasicInterpreter.java
index 2252a555..eb102fb4 100644
--- a/src/java/nginx/clojure/asm/tree/analysis/BasicInterpreter.java
+++ b/src/java/nginx/clojure/asm/tree/analysis/BasicInterpreter.java
@@ -62,7 +62,7 @@ public class BasicInterpreter extends Interpreter implements Opcodes
* version.
*/
public BasicInterpreter() {
- super(/* latest api = */ ASM7);
+ super(/* latest api = */ ASM9);
if (getClass() != BasicInterpreter.class) {
throw new IllegalStateException();
}
@@ -71,9 +71,8 @@ public BasicInterpreter() {
/**
* Constructs a new {@link BasicInterpreter}.
*
- * @param api the ASM API version supported by this interpreter. Must be one of {@link
- * nginx.clojure.asm.Opcodes#ASM4}, {@link nginx.clojure.asm.Opcodes#ASM5}, {@link
- * nginx.clojure.asm.Opcodes#ASM6} or {@link nginx.clojure.asm.Opcodes#ASM7}.
+ * @param api the ASM API version supported by this interpreter. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
protected BasicInterpreter(final int api) {
super(api);
diff --git a/src/java/nginx/clojure/asm/tree/analysis/BasicVerifier.java b/src/java/nginx/clojure/asm/tree/analysis/BasicVerifier.java
index b928beea..6c790ff5 100644
--- a/src/java/nginx/clojure/asm/tree/analysis/BasicVerifier.java
+++ b/src/java/nginx/clojure/asm/tree/analysis/BasicVerifier.java
@@ -29,6 +29,7 @@
import java.util.List;
+import nginx.clojure.asm.Opcodes;
import nginx.clojure.asm.Type;
import nginx.clojure.asm.tree.AbstractInsnNode;
import nginx.clojure.asm.tree.FieldInsnNode;
@@ -48,7 +49,7 @@ public class BasicVerifier extends BasicInterpreter {
* use this constructor
. Instead, they must use the {@link #BasicVerifier(int)} version.
*/
public BasicVerifier() {
- super(/* latest api = */ ASM7);
+ super(/* latest api = */ ASM9);
if (getClass() != BasicVerifier.class) {
throw new IllegalStateException();
}
@@ -57,9 +58,8 @@ public BasicVerifier() {
/**
* Constructs a new {@link BasicVerifier}.
*
- * @param api the ASM API version supported by this interpreter. Must be one of {@link
- * nginx.clojure.asm.Opcodes#ASM4}, {@link nginx.clojure.asm.Opcodes#ASM5}, {@link
- * nginx.clojure.asm.Opcodes#ASM6} or {@link nginx.clojure.asm.Opcodes#ASM7}.
+ * @param api the ASM API version supported by this interpreter. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
protected BasicVerifier(final int api) {
super(api);
diff --git a/src/java/nginx/clojure/asm/tree/analysis/Frame.java b/src/java/nginx/clojure/asm/tree/analysis/Frame.java
index a94a0967..bb050dd0 100644
--- a/src/java/nginx/clojure/asm/tree/analysis/Frame.java
+++ b/src/java/nginx/clojure/asm/tree/analysis/Frame.java
@@ -50,6 +50,9 @@
*/
public class Frame {
+ /** The maximum size of the operand stack of any method. */
+ private static final int MAX_STACK_SIZE = 65536;
+
/**
* The expected return type of the analyzed method, or {@literal null} if the method returns void.
*/
@@ -58,26 +61,43 @@ public class Frame {
/**
* The local variables and the operand stack of this frame. The first {@link #numLocals} elements
* correspond to the local variables. The following {@link #numStack} elements correspond to the
- * operand stack.
+ * operand stack. Long and double values are represented with two elements in the local variables
+ * section, and with one element in the operand stack section.
*/
private V[] values;
- /** The number of local variables of this frame. */
+ /**
+ * The number of local variables of this frame. Long and double values are represented with two
+ * elements.
+ */
private int numLocals;
- /** The number of elements in the operand stack. */
+ /**
+ * The number of elements in the operand stack. Long and double values are represented with a
+ * single element.
+ */
private int numStack;
+ /**
+ * The maximum number of elements in the operand stack. Long and double values are represented
+ * with a single element.
+ */
+ private int maxStack;
+
/**
* Constructs a new frame with the given size.
*
- * @param numLocals the maximum number of local variables of the frame.
- * @param numStack the maximum stack size of the frame.
+ * @param numLocals the number of local variables of the frame. Long and double values are
+ * represented with two elements.
+ * @param maxStack the maximum number of elements in the operand stack, or -1 if there is no
+ * maximum value. Long and double values are represented with a single element.
*/
@SuppressWarnings("unchecked")
- public Frame(final int numLocals, final int numStack) {
- this.values = (V[]) new Value[numLocals + numStack];
+ public Frame(final int numLocals, final int maxStack) {
+ this.values = (V[]) new Value[numLocals + (maxStack >= 0 ? maxStack : 4)];
this.numLocals = numLocals;
+ this.numStack = 0;
+ this.maxStack = maxStack >= 0 ? maxStack : MAX_STACK_SIZE;
}
/**
@@ -98,8 +118,14 @@ public Frame(final Frame extends V> frame) {
*/
public Frame init(final Frame extends V> frame) {
returnValue = frame.returnValue;
- System.arraycopy(frame.values, 0, values, 0, values.length);
+ if (values.length < frame.values.length) {
+ values = frame.values.clone();
+ } else {
+ System.arraycopy(frame.values, 0, values, 0, frame.values.length);
+ }
+ numLocals = frame.numLocals;
numStack = frame.numStack;
+ maxStack = frame.maxStack;
return this;
}
@@ -136,7 +162,8 @@ public void setReturn(final V v) {
}
/**
- * Returns the maximum number of local variables of this frame.
+ * Returns the maximum number of local variables of this frame. Long and double values are
+ * represented with two variables.
*
* @return the maximum number of local variables of this frame.
*/
@@ -145,16 +172,18 @@ public int getLocals() {
}
/**
- * Returns the maximum stack size of this frame.
+ * Returns the maximum number of elements in the operand stack of this frame. Long and double
+ * values are represented with a single element.
*
- * @return the maximum stack size of this frame.
+ * @return the maximum number of elements in the operand stack of this frame.
*/
public int getMaxStackSize() {
- return values.length - numLocals;
+ return maxStack;
}
/**
- * Returns the value of the given local variable.
+ * Returns the value of the given local variable. Long and double values are represented with two
+ * variables.
*
* @param index a local variable index.
* @return the value of the given local variable.
@@ -168,7 +197,8 @@ public V getLocal(final int index) {
}
/**
- * Sets the value of the given local variable.
+ * Sets the value of the given local variable. Long and double values are represented with two
+ * variables.
*
* @param index a local variable index.
* @param value the new value of this local variable.
@@ -182,10 +212,10 @@ public void setLocal(final int index, final V value) {
}
/**
- * Returns the number of values in the operand stack of this frame. Long and double values are
- * treated as single values.
+ * Returns the number of elements in the operand stack of this frame. Long and double values are
+ * represented with a single element.
*
- * @return the number of values in the operand stack of this frame.
+ * @return the number of elements in the operand stack of this frame.
*/
public int getStackSize() {
return numStack;
@@ -237,9 +267,15 @@ public V pop() {
* @param value the value that must be pushed into the stack.
* @throws IndexOutOfBoundsException if the operand stack is full.
*/
+ @SuppressWarnings("unchecked")
public void push(final V value) {
if (numLocals + numStack >= values.length) {
- throw new IndexOutOfBoundsException("Insufficient maximum stack size.");
+ if (numLocals + numStack >= maxStack) {
+ throw new IndexOutOfBoundsException("Insufficient maximum stack size.");
+ }
+ V[] oldValues = values;
+ values = (V[]) new Value[2 * values.length];
+ System.arraycopy(oldValues, 0, values, 0, oldValues.length);
}
values[numLocals + (numStack++)] = value;
}
@@ -258,7 +294,7 @@ public void execute(final AbstractInsnNode insn, final Interpreter interprete
V value2;
V value3;
V value4;
- int var;
+ int varIndex;
switch (insn.getOpcode()) {
case Opcodes.NOP:
@@ -296,15 +332,15 @@ public void execute(final AbstractInsnNode insn, final Interpreter interprete
case Opcodes.DSTORE:
case Opcodes.ASTORE:
value1 = interpreter.copyOperation(insn, pop());
- var = ((VarInsnNode) insn).var;
- setLocal(var, value1);
+ varIndex = ((VarInsnNode) insn).var;
+ setLocal(varIndex, value1);
if (value1.getSize() == 2) {
- setLocal(var + 1, interpreter.newEmptyValue(var + 1));
+ setLocal(varIndex + 1, interpreter.newEmptyValue(varIndex + 1));
}
- if (var > 0) {
- Value local = getLocal(var - 1);
+ if (varIndex > 0) {
+ Value local = getLocal(varIndex - 1);
if (local != null && local.getSize() == 2) {
- setLocal(var - 1, interpreter.newEmptyValue(var - 1));
+ setLocal(varIndex - 1, interpreter.newEmptyValue(varIndex - 1));
}
}
break;
@@ -492,8 +528,8 @@ public void execute(final AbstractInsnNode insn, final Interpreter interprete
push(interpreter.unaryOperation(insn, pop()));
break;
case Opcodes.IINC:
- var = ((IincInsnNode) insn).var;
- setLocal(var, interpreter.unaryOperation(insn, getLocal(var)));
+ varIndex = ((IincInsnNode) insn).var;
+ setLocal(varIndex, interpreter.unaryOperation(insn, getLocal(varIndex)));
break;
case Opcodes.I2L:
case Opcodes.I2F:
diff --git a/src/java/nginx/clojure/asm/tree/analysis/Interpreter.java b/src/java/nginx/clojure/asm/tree/analysis/Interpreter.java
index 3a3f34b3..d6e05bf7 100644
--- a/src/java/nginx/clojure/asm/tree/analysis/Interpreter.java
+++ b/src/java/nginx/clojure/asm/tree/analysis/Interpreter.java
@@ -29,6 +29,7 @@
import java.util.List;
+import nginx.clojure.asm.Opcodes;
import nginx.clojure.asm.Type;
import nginx.clojure.asm.tree.AbstractInsnNode;
import nginx.clojure.asm.tree.TryCatchBlockNode;
@@ -46,18 +47,16 @@
public abstract class Interpreter {
/**
- * The ASM API version supported by this interpreter. The value of this field must be one of
- * {@link nginx.clojure.asm.Opcodes#ASM4}, {@link nginx.clojure.asm.Opcodes#ASM5}, {@link
- * nginx.clojure.asm.Opcodes#ASM6} or {@link nginx.clojure.asm.Opcodes#ASM7}.
+ * The ASM API version supported by this interpreter. The value of this field must be one of the
+ * {@code ASM}x values in {@link Opcodes}.
*/
protected final int api;
/**
* Constructs a new {@link Interpreter}.
*
- * @param api the ASM API version supported by this interpreter. Must be one of {@link
- * nginx.clojure.asm.Opcodes#ASM4}, {@link nginx.clojure.asm.Opcodes#ASM5}, {@link
- * nginx.clojure.asm.Opcodes#ASM6} or {@link nginx.clojure.asm.Opcodes#ASM7}.
+ * @param api the ASM API version supported by this interpreter. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
protected Interpreter(final int api) {
this.api = api;
diff --git a/src/java/nginx/clojure/asm/tree/analysis/SimpleVerifier.java b/src/java/nginx/clojure/asm/tree/analysis/SimpleVerifier.java
index d6dec978..61510f4b 100644
--- a/src/java/nginx/clojure/asm/tree/analysis/SimpleVerifier.java
+++ b/src/java/nginx/clojure/asm/tree/analysis/SimpleVerifier.java
@@ -29,6 +29,7 @@
import java.util.List;
+import nginx.clojure.asm.Opcodes;
import nginx.clojure.asm.Type;
/**
@@ -95,7 +96,7 @@ public SimpleVerifier(
final List currentClassInterfaces,
final boolean isInterface) {
this(
- /* latest api = */ ASM7,
+ /* latest api = */ ASM9,
currentClass,
currentSuperClass,
currentClassInterfaces,
@@ -109,9 +110,8 @@ public SimpleVerifier(
* Constructs a new {@link SimpleVerifier} to verify a specific class. This class will not be
* loaded into the JVM since it may be incorrect.
*
- * @param api the ASM API version supported by this verifier. Must be one of {@link
- * nginx.clojure.asm.Opcodes#ASM4}, {@link nginx.clojure.asm.Opcodes#ASM5}, {@link
- * nginx.clojure.asm.Opcodes#ASM6} or {@link nginx.clojure.asm.Opcodes#ASM7}.
+ * @param api the ASM API version supported by this verifier. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param currentClass the type of the class to be verified.
* @param currentSuperClass the type of the super class of the class to be verified.
* @param currentClassInterfaces the types of the interfaces directly implemented by the class to
diff --git a/src/java/nginx/clojure/asm/tree/analysis/SourceInterpreter.java b/src/java/nginx/clojure/asm/tree/analysis/SourceInterpreter.java
index 7cf42bf6..d2bb412d 100644
--- a/src/java/nginx/clojure/asm/tree/analysis/SourceInterpreter.java
+++ b/src/java/nginx/clojure/asm/tree/analysis/SourceInterpreter.java
@@ -52,7 +52,7 @@ public class SourceInterpreter extends Interpreter implements Opcod
* version.
*/
public SourceInterpreter() {
- super(/* latest api = */ ASM7);
+ super(/* latest api = */ ASM9);
if (getClass() != SourceInterpreter.class) {
throw new IllegalStateException();
}
@@ -61,9 +61,8 @@ public SourceInterpreter() {
/**
* Constructs a new {@link SourceInterpreter}.
*
- * @param api the ASM API version supported by this interpreter. Must be one of {@link
- * nginx.clojure.asm.Opcodes#ASM4}, {@link nginx.clojure.asm.Opcodes#ASM5}, {@link
- * nginx.clojure.asm.Opcodes#ASM6} or {@link nginx.clojure.asm.Opcodes#ASM7}.
+ * @param api the ASM API version supported by this interpreter. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
*/
protected SourceInterpreter(final int api) {
super(api);
diff --git a/src/java/nginx/clojure/asm/tree/analysis/SourceValue.java b/src/java/nginx/clojure/asm/tree/analysis/SourceValue.java
index fec89dfd..a2e32be6 100644
--- a/src/java/nginx/clojure/asm/tree/analysis/SourceValue.java
+++ b/src/java/nginx/clojure/asm/tree/analysis/SourceValue.java
@@ -66,7 +66,7 @@ public class SourceValue implements Value {
* short, int, float, object and array types, and 2 for long and double.
*/
public SourceValue(final int size) {
- this(size, new SmallSet());
+ this(size, new SmallSet<>());
}
/**
diff --git a/src/java/nginx/clojure/asm/util/ASMifier.java b/src/java/nginx/clojure/asm/util/ASMifier.java
index 59b62436..65684ac8 100644
--- a/src/java/nginx/clojure/asm/util/ASMifier.java
+++ b/src/java/nginx/clojure/asm/util/ASMifier.java
@@ -54,7 +54,7 @@ public class ASMifier extends Printer {
/** The help message shown when command line arguments are incorrect. */
private static final String USAGE =
"Prints the ASM code to generate the given class.\n"
- + "Usage: ASMifier [-debug] ";
+ + "Usage: ASMifier [-nodebug] ";
/** A pseudo access flag used to distinguish class access flags. */
private static final int ACCESS_CLASS = 0x40000;
@@ -105,6 +105,12 @@ public class ASMifier extends Printer {
classVersions.put(Opcodes.V12, "V12");
classVersions.put(Opcodes.V13, "V13");
classVersions.put(Opcodes.V14, "V14");
+ classVersions.put(Opcodes.V15, "V15");
+ classVersions.put(Opcodes.V16, "V16");
+ classVersions.put(Opcodes.V17, "V17");
+ classVersions.put(Opcodes.V18, "V18");
+ classVersions.put(Opcodes.V19, "V19");
+ classVersions.put(Opcodes.V20, "V20");
CLASS_VERSIONS = Collections.unmodifiableMap(classVersions);
}
@@ -124,7 +130,7 @@ public class ASMifier extends Printer {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public ASMifier() {
- this(/* latest api = */ Opcodes.ASM7, "classWriter", 0);
+ this(/* latest api = */ Opcodes.ASM9, "classWriter", 0);
if (getClass() != ASMifier.class) {
throw new IllegalStateException();
}
@@ -133,8 +139,8 @@ public ASMifier() {
/**
* Constructs a new {@link ASMifier}.
*
- * @param api the ASM API version implemented by this class. Must be one of {@link Opcodes#ASM4},
- * {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @param api the ASM API version implemented by this class. Must be one of the {@code
+ * ASM}x values in {@link Opcodes}.
* @param visitorVariableName the name of the visitor variable in the produced code.
* @param annotationVisitorId identifier of the annotation visitor variable in the produced code.
*/
@@ -148,7 +154,7 @@ protected ASMifier(
/**
* Prints the ASM source code to generate the given class to the standard output.
*
- *
Usage: ASMifier [-debug] <binary class name or class file name>
+ *
Usage: ASMifier [-nodebug] <binary class name or class file name>
*
* @param args the command line arguments.
* @throws IOException if the class cannot be found, or if an IOException occurs.
@@ -160,7 +166,7 @@ public static void main(final String[] args) throws IOException {
/**
* Prints the ASM source code to generate the given class to the given output.
*
- *
Usage: ASMifier [-debug] <binary class name or class file name>
+ *
Usage: ASMifier [-nodebug] <binary class name or class file name>
*
* @param args the command line arguments.
* @param output where to print the result.
@@ -206,12 +212,14 @@ public void visit(
text.add("import nginx.clojure.asm.Label;\n");
text.add("import nginx.clojure.asm.MethodVisitor;\n");
text.add("import nginx.clojure.asm.Opcodes;\n");
+ text.add("import nginx.clojure.asm.RecordComponentVisitor;\n");
text.add("import nginx.clojure.asm.Type;\n");
text.add("import nginx.clojure.asm.TypePath;\n");
text.add("public class " + simpleName + "Dump implements Opcodes {\n\n");
text.add("public static byte[] dump () throws Exception {\n\n");
text.add("ClassWriter classWriter = new ClassWriter(0);\n");
text.add("FieldVisitor fieldVisitor;\n");
+ text.add("RecordComponentVisitor recordComponentVisitor;\n");
text.add("MethodVisitor methodVisitor;\n");
text.add("AnnotationVisitor annotationVisitor0;\n\n");
@@ -260,6 +268,7 @@ public void visitSource(final String file, final String debug) {
@Override
public Printer visitModule(final String name, final int flags, final String version) {
stringBuilder.setLength(0);
+ stringBuilder.append("{\n");
stringBuilder.append("ModuleVisitor moduleVisitor = classWriter.visitModule(");
appendConstant(name);
stringBuilder.append(", ");
@@ -322,10 +331,10 @@ public void visitNestMember(final String nestMember) {
}
@Override
- public void visitPermittedSubtypeExperimental(final String visitPermittedSubtype) {
+ public void visitPermittedSubclass(final String permittedSubclass) {
stringBuilder.setLength(0);
- stringBuilder.append("classWriter.visitPermittedSubtypeExperimental(");
- appendConstant(visitPermittedSubtype);
+ stringBuilder.append("classWriter.visitPermittedSubclass(");
+ appendConstant(permittedSubclass);
stringBuilder.append(END_PARAMETERS);
text.add(stringBuilder.toString());
}
@@ -346,6 +355,25 @@ public void visitInnerClass(
text.add(stringBuilder.toString());
}
+ @Override
+ public ASMifier visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ stringBuilder.setLength(0);
+ stringBuilder.append("{\n");
+ stringBuilder.append("recordComponentVisitor = classWriter.visitRecordComponent(");
+ appendConstant(name);
+ stringBuilder.append(", ");
+ appendConstant(descriptor);
+ stringBuilder.append(", ");
+ appendConstant(signature);
+ stringBuilder.append(");\n");
+ text.add(stringBuilder.toString());
+ ASMifier asmifier = createASMifier("recordComponentVisitor", 0);
+ text.add(asmifier.getText());
+ text.add("}\n");
+ return asmifier;
+ }
+
@Override
public ASMifier visitField(
final int access,
@@ -583,6 +611,31 @@ public void visitAnnotationEnd() {
text.add(stringBuilder.toString());
}
+ // -----------------------------------------------------------------------------------------------
+ // Record components
+ // -----------------------------------------------------------------------------------------------
+
+ @Override
+ public ASMifier visitRecordComponentAnnotation(final String descriptor, final boolean visible) {
+ return visitAnnotation(descriptor, visible);
+ }
+
+ @Override
+ public ASMifier visitRecordComponentTypeAnnotation(
+ final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+ return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+ }
+
+ @Override
+ public void visitRecordComponentAttribute(final Attribute attribute) {
+ visitAttribute(attribute);
+ }
+
+ @Override
+ public void visitRecordComponentEnd() {
+ visitMemberEnd();
+ }
+
// -----------------------------------------------------------------------------------------------
// Fields
// -----------------------------------------------------------------------------------------------
@@ -605,9 +658,7 @@ public void visitFieldAttribute(final Attribute attribute) {
@Override
public void visitFieldEnd() {
- stringBuilder.setLength(0);
- stringBuilder.append(name).append(VISIT_END);
- text.add(stringBuilder.toString());
+ visitMemberEnd();
}
// -----------------------------------------------------------------------------------------------
@@ -774,14 +825,14 @@ public void visitIntInsn(final int opcode, final int operand) {
}
@Override
- public void visitVarInsn(final int opcode, final int var) {
+ public void visitVarInsn(final int opcode, final int varIndex) {
stringBuilder.setLength(0);
stringBuilder
.append(name)
.append(".visitVarInsn(")
.append(OPCODES[opcode])
.append(", ")
- .append(var)
+ .append(varIndex)
.append(");\n");
text.add(stringBuilder.toString());
}
@@ -887,12 +938,12 @@ public void visitLdcInsn(final Object value) {
}
@Override
- public void visitIincInsn(final int var, final int increment) {
+ public void visitIincInsn(final int varIndex, final int increment) {
stringBuilder.setLength(0);
stringBuilder
.append(name)
.append(".visitIincInsn(")
- .append(var)
+ .append(varIndex)
.append(", ")
.append(increment)
.append(");\n");
@@ -1080,9 +1131,7 @@ public void visitMaxs(final int maxStack, final int maxLocals) {
@Override
public void visitMethodEnd() {
- stringBuilder.setLength(0);
- stringBuilder.append(name).append(VISIT_END);
- text.add(stringBuilder.toString());
+ visitMemberEnd();
}
// -----------------------------------------------------------------------------------------------
@@ -1154,9 +1203,9 @@ public ASMifier visitTypeAnnotation(
.append("{\n")
.append(ANNOTATION_VISITOR0)
.append(name)
- .append(".")
+ .append('.')
.append(method)
- .append("(")
+ .append('(')
.append(typeRef);
if (typePath == null) {
stringBuilder.append(", null, ");
@@ -1192,6 +1241,13 @@ public void visitAttribute(final Attribute attribute) {
text.add(stringBuilder.toString());
}
+ /** Visits the end of a field, record component or method. */
+ private void visitMemberEnd() {
+ stringBuilder.setLength(0);
+ stringBuilder.append(name).append(VISIT_END);
+ text.add(stringBuilder.toString());
+ }
+
// -----------------------------------------------------------------------------------------------
// Utility methods
// -----------------------------------------------------------------------------------------------
@@ -1232,11 +1288,7 @@ private void appendAccessFlags(final int accessFlags) {
if (!isEmpty) {
stringBuilder.append(" | ");
}
- if ((accessFlags & ACCESS_MODULE) == 0) {
- stringBuilder.append("ACC_FINAL");
- } else {
- stringBuilder.append("ACC_TRANSITIVE");
- }
+ stringBuilder.append("ACC_FINAL");
isEmpty = false;
}
if ((accessFlags & Opcodes.ACC_STATIC) != 0) {
@@ -1352,6 +1404,13 @@ private void appendAccessFlags(final int accessFlags) {
stringBuilder.append("ACC_DEPRECATED");
isEmpty = false;
}
+ if ((accessFlags & Opcodes.ACC_RECORD) != 0) {
+ if (!isEmpty) {
+ stringBuilder.append(" | ");
+ }
+ stringBuilder.append("ACC_RECORD");
+ isEmpty = false;
+ }
if ((accessFlags & (Opcodes.ACC_MANDATED | Opcodes.ACC_MODULE)) != 0) {
if (!isEmpty) {
stringBuilder.append(" | ");
@@ -1391,7 +1450,7 @@ protected void appendConstant(final Object value) {
stringBuilder.append(handle.getOwner()).append(COMMA);
stringBuilder.append(handle.getName()).append(COMMA);
stringBuilder.append(handle.getDesc()).append("\", ");
- stringBuilder.append(handle.isInterface()).append(")");
+ stringBuilder.append(handle.isInterface()).append(')');
} else if (value instanceof ConstantDynamic) {
stringBuilder.append("new ConstantDynamic(\"");
ConstantDynamic constantDynamic = (ConstantDynamic) value;
diff --git a/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java b/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java
index cae8dd8a..2931e37f 100644
--- a/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java
+++ b/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java
@@ -52,7 +52,7 @@ public CheckAnnotationAdapter(final AnnotationVisitor annotationVisitor) {
}
CheckAnnotationAdapter(final AnnotationVisitor annotationVisitor, final boolean useNamedValues) {
- super(/* latest api = */ Opcodes.ASM7, annotationVisitor);
+ super(/* latest api = */ Opcodes.ASM9, annotationVisitor);
this.useNamedValue = useNamedValues;
}
diff --git a/src/java/nginx/clojure/asm/util/CheckClassAdapter.java b/src/java/nginx/clojure/asm/util/CheckClassAdapter.java
index 9e0af286..21de6a34 100644
--- a/src/java/nginx/clojure/asm/util/CheckClassAdapter.java
+++ b/src/java/nginx/clojure/asm/util/CheckClassAdapter.java
@@ -40,11 +40,13 @@
import nginx.clojure.asm.Attribute;
import nginx.clojure.asm.ClassReader;
import nginx.clojure.asm.ClassVisitor;
+import nginx.clojure.asm.ClassWriter;
import nginx.clojure.asm.FieldVisitor;
import nginx.clojure.asm.Label;
import nginx.clojure.asm.MethodVisitor;
import nginx.clojure.asm.ModuleVisitor;
import nginx.clojure.asm.Opcodes;
+import nginx.clojure.asm.RecordComponentVisitor;
import nginx.clojure.asm.Type;
import nginx.clojure.asm.TypePath;
import nginx.clojure.asm.TypeReference;
@@ -161,7 +163,7 @@ public class CheckClassAdapter extends ClassVisitor {
* @param classVisitor the class visitor to which this adapter must delegate calls.
*/
public CheckClassAdapter(final ClassVisitor classVisitor) {
- this(classVisitor, true);
+ this(classVisitor, /* checkDataFlow = */ true);
}
/**
@@ -169,12 +171,11 @@ public CheckClassAdapter(final ClassVisitor classVisitor) {
* Instead, they must use the {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version.
*
* @param classVisitor the class visitor to which this adapter must delegate calls.
- * @param checkDataFlow whether to perform basic data flow checks. This option requires valid
- * maxLocals and maxStack values.
+ * @param checkDataFlow whether to perform basic data flow checks.
* @throws IllegalStateException If a subclass calls this constructor.
*/
public CheckClassAdapter(final ClassVisitor classVisitor, final boolean checkDataFlow) {
- this(/* latest api = */ Opcodes.ASM7, classVisitor, checkDataFlow);
+ this(/* latest api = */ Opcodes.ASM9, classVisitor, checkDataFlow);
if (getClass() != CheckClassAdapter.class) {
throw new IllegalStateException();
}
@@ -183,12 +184,11 @@ public CheckClassAdapter(final ClassVisitor classVisitor, final boolean checkDat
/**
* Constructs a new {@link CheckClassAdapter}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @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 adapter must delegate calls.
* @param checkDataFlow {@literal true} to perform basic data flow checks, or {@literal false} to
- * not perform any data flow check (see {@link CheckMethodAdapter}). This option requires
- * valid maxLocals and maxStack values.
+ * not perform any data flow check (see {@link CheckMethodAdapter}).
*/
protected CheckClassAdapter(
final int api, final ClassVisitor classVisitor, final boolean checkDataFlow) {
@@ -225,6 +225,7 @@ public void visit(
| Opcodes.ACC_ANNOTATION
| Opcodes.ACC_ENUM
| Opcodes.ACC_DEPRECATED
+ | Opcodes.ACC_RECORD
| Opcodes.ACC_MODULE);
if (name == null) {
throw new IllegalArgumentException("Illegal class name (null)");
@@ -320,12 +321,11 @@ public void visitNestMember(final String nestMember) {
super.visitNestMember(nestMember);
}
- @SuppressWarnings("deprecation")
@Override
- public void visitPermittedSubtypeExperimental(final String permittedSubtype) {
+ public void visitPermittedSubclass(final String permittedSubclass) {
checkState();
- CheckMethodAdapter.checkInternalName(version, permittedSubtype, "permittedSubtype");
- super.visitPermittedSubtypeExperimental(permittedSubtype);
+ CheckMethodAdapter.checkInternalName(version, permittedSubclass, "permittedSubclass");
+ super.visitPermittedSubclass(permittedSubclass);
}
@Override
@@ -376,6 +376,19 @@ public void visitInnerClass(
super.visitInnerClass(name, outerName, innerName, access);
}
+ @Override
+ public RecordComponentVisitor visitRecordComponent(
+ final String name, final String descriptor, final String signature) {
+ checkState();
+ CheckMethodAdapter.checkUnqualifiedName(version, name, "record component name");
+ CheckMethodAdapter.checkDescriptor(version, descriptor, /* canBeVoid = */ false);
+ if (signature != null) {
+ checkFieldSignature(signature);
+ }
+ return new CheckRecordComponentAdapter(
+ api, super.visitRecordComponent(name, descriptor, signature));
+ }
+
@Override
public FieldVisitor visitField(
final int access,
@@ -395,6 +408,7 @@ public FieldVisitor visitField(
| Opcodes.ACC_TRANSIENT
| Opcodes.ACC_SYNTHETIC
| Opcodes.ACC_ENUM
+ | Opcodes.ACC_MANDATED
| Opcodes.ACC_DEPRECATED);
CheckMethodAdapter.checkUnqualifiedName(version, name, "field name");
CheckMethodAdapter.checkDescriptor(version, descriptor, /* canBeVoid = */ false);
@@ -415,7 +429,8 @@ public MethodVisitor visitMethod(
final String signature,
final String[] exceptions) {
checkState();
- checkAccess(
+ checkMethodAccess(
+ version,
access,
Opcodes.ACC_PUBLIC
| Opcodes.ACC_PRIVATE
@@ -429,6 +444,7 @@ public MethodVisitor visitMethod(
| Opcodes.ACC_ABSTRACT
| Opcodes.ACC_STRICT
| Opcodes.ACC_SYNTHETIC
+ | Opcodes.ACC_MANDATED
| Opcodes.ACC_DEPRECATED);
if (!"".equals(name) && !"".equals(name)) {
CheckMethodAdapter.checkMethodIdentifier(version, name, "method name");
@@ -444,21 +460,18 @@ public MethodVisitor visitMethod(
}
}
CheckMethodAdapter checkMethodAdapter;
+ MethodVisitor methodVisitor =
+ super.visitMethod(access, name, descriptor, signature, exceptions);
if (checkDataFlow) {
+ if (cv instanceof ClassWriter) {
+ methodVisitor =
+ new CheckMethodAdapter.MethodWriterWrapper(
+ api, version, (ClassWriter) cv, methodVisitor);
+ }
checkMethodAdapter =
- new CheckMethodAdapter(
- api,
- access,
- name,
- descriptor,
- super.visitMethod(access, name, descriptor, signature, exceptions),
- labelInsnIndices);
+ new CheckMethodAdapter(api, access, name, descriptor, methodVisitor, labelInsnIndices);
} else {
- checkMethodAdapter =
- new CheckMethodAdapter(
- api,
- super.visitMethod(access, name, descriptor, signature, exceptions),
- labelInsnIndices);
+ checkMethodAdapter = new CheckMethodAdapter(api, methodVisitor, labelInsnIndices);
}
checkMethodAdapter.version = version;
return checkMethodAdapter;
@@ -539,6 +552,23 @@ static void checkAccess(final int access, final int possibleAccess) {
}
}
+ /**
+ * Checks that the given access flags do not contain invalid flags for a method. This method also
+ * checks that mutually incompatible flags are not set simultaneously.
+ *
+ * @param version the class version.
+ * @param access the method access flags to be checked.
+ * @param possibleAccess the valid access flags.
+ */
+ private static void checkMethodAccess(
+ final int version, final int access, final int possibleAccess) {
+ checkAccess(access, possibleAccess);
+ if ((version & 0xFFFF) < Opcodes.V17
+ && Integer.bitCount(access & (Opcodes.ACC_STRICT | Opcodes.ACC_ABSTRACT)) > 1) {
+ throw new IllegalArgumentException("strictfp and abstract are mutually exclusive: " + access);
+ }
+ }
+
/**
* Checks that the given name is a fully qualified name, using dots.
*
@@ -927,9 +957,9 @@ static void checkTypeRef(final int typeRef) {
mask = 0xFF0000FF;
break;
default:
- throw new AssertionError();
+ break;
}
- if ((typeRef & ~mask) != 0) {
+ if (mask == 0 || (typeRef & ~mask) != 0) {
throw new IllegalArgumentException(
"Invalid type reference 0x" + Integer.toHexString(typeRef));
}
@@ -980,9 +1010,10 @@ static void main(final String[] args, final PrintWriter logger) throws IOExcepti
ClassReader classReader;
if (args[0].endsWith(".class")) {
- InputStream inputStream =
- new FileInputStream(args[0]); // NOPMD(AvoidFileStream): can't fix for 1.5 compatibility
- classReader = new ClassReader(inputStream);
+ // Can't fix PMD warning for 1.5 compatibility.
+ try (InputStream inputStream = new FileInputStream(args[0])) { // NOPMD(AvoidFileStream)
+ classReader = new ClassReader(inputStream);
+ }
} else {
classReader = new ClassReader(args[0]);
}
@@ -1011,7 +1042,6 @@ public static void verify(
* @param printResults whether to print the results of the bytecode verification.
* @param printWriter where the results (or the stack trace in case of error) must be printed.
*/
- @SuppressWarnings("deprecation")
public static void verify(
final ClassReader classReader,
final ClassLoader loader,
@@ -1019,7 +1049,7 @@ public static void verify(
final PrintWriter printWriter) {
ClassNode classNode = new ClassNode();
classReader.accept(
- new CheckClassAdapter(Opcodes.ASM8_EXPERIMENTAL, classNode, false) {},
+ new CheckClassAdapter(/*latest*/ Opcodes.ASM10_EXPERIMENTAL, classNode, false) {},
ClassReader.SKIP_DEBUG);
Type syperType = classNode.superName == null ? null : Type.getObjectType(classNode.superName);
@@ -1098,7 +1128,11 @@ private static String getUnqualifiedName(final String name) {
if (name.charAt(endIndex - 1) == ';') {
endIndex--;
}
- return name.substring(lastSlashIndex + 1, endIndex);
+ int lastBracketIndex = name.lastIndexOf('[');
+ if (lastBracketIndex == -1) {
+ return name.substring(lastSlashIndex + 1, endIndex);
+ }
+ return name.substring(0, lastBracketIndex + 1) + name.substring(lastSlashIndex + 1, endIndex);
}
}
}
diff --git a/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java b/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java
index 2922a807..d92ce3d2 100644
--- a/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java
+++ b/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java
@@ -52,7 +52,7 @@ public class CheckFieldAdapter extends FieldVisitor {
* @throws IllegalStateException If a subclass calls this constructor.
*/
public CheckFieldAdapter(final FieldVisitor fieldVisitor) {
- this(/* latest api = */ Opcodes.ASM7, fieldVisitor);
+ this(/* latest api = */ Opcodes.ASM9, fieldVisitor);
if (getClass() != CheckFieldAdapter.class) {
throw new IllegalStateException();
}
@@ -61,8 +61,8 @@ public CheckFieldAdapter(final FieldVisitor fieldVisitor) {
/**
* Constructs a new {@link CheckFieldAdapter}.
*
- * @param api the ASM API version implemented by this visitor. Must be one of {@link
- * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+ * @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 adapter must delegate calls.
*/
protected CheckFieldAdapter(final int api, final FieldVisitor fieldVisitor) {
diff --git a/src/java/nginx/clojure/asm/util/CheckFrameAnalyzer.java b/src/java/nginx/clojure/asm/util/CheckFrameAnalyzer.java
new file mode 100644
index 00000000..fa11f25c
--- /dev/null
+++ b/src/java/nginx/clojure/asm/util/CheckFrameAnalyzer.java
@@ -0,0 +1,478 @@
+// 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.util;
+
+import java.util.Collections;
+import java.util.List;
+
+import nginx.clojure.asm.Opcodes;
+import nginx.clojure.asm.Type;
+import nginx.clojure.asm.tree.AbstractInsnNode;
+import nginx.clojure.asm.tree.FrameNode;
+import nginx.clojure.asm.tree.InsnList;
+import nginx.clojure.asm.tree.InsnNode;
+import nginx.clojure.asm.tree.JumpInsnNode;
+import nginx.clojure.asm.tree.LabelNode;
+import nginx.clojure.asm.tree.LookupSwitchInsnNode;
+import nginx.clojure.asm.tree.MethodNode;
+import nginx.clojure.asm.tree.TableSwitchInsnNode;
+import nginx.clojure.asm.tree.TryCatchBlockNode;
+import nginx.clojure.asm.tree.TypeInsnNode;
+import nginx.clojure.asm.tree.analysis.Analyzer;
+import nginx.clojure.asm.tree.analysis.AnalyzerException;
+import nginx.clojure.asm.tree.analysis.Frame;
+import nginx.clojure.asm.tree.analysis.Interpreter;
+import nginx.clojure.asm.tree.analysis.Value;
+
+/**
+ * An {@link Analyzer} subclass which checks that methods provide stack map frames where expected
+ * (i.e. at jump target and after instructions without immediate successor), and that these stack
+ * map frames are valid (for the provided interpreter; they may still be invalid for the JVM, if the
+ * {@link Interpreter} uses a simplified type system compared to the JVM verifier). This is done in
+ * two steps:
+ *
+ *
+ *
First, the stack map frames in {@link FrameNode}s are expanded, and stored at their
+ * respective instruction offsets. The expansion process uncompresses the APPEND, CHOP and
+ * SAME frames to FULL frames. It also converts the stack map frame verification types to
+ * {@link Value}s, via the provided {@link Interpreter}. The expansion is done in {@link
+ * #expandFrames}, by looking at each {@link FrameNode} in sequence (compressed frames are
+ * defined relatively to the previous {@link FrameNode}, or the implicit first frame). The
+ * actual decompression is done in {@link #expandFrame}, and the type conversion in {@link
+ * #newFrameValue}.
+ *
Next, the method instructions are checked in sequence. Starting from the implicit initial
+ * frame, the execution of each instruction i is simulated on the current stack map
+ * frame, with the {@link Frame#execute} method. This gives a new stack map frame f,
+ * representing the stack map frame state after the execution of i. Then:
+ *
+ *
If there is a next instruction and if the control flow cannot continue to it (e.g. if
+ * i is a RETURN or an ATHROW, for instance): an existing stack map frame
+ * f0 (coming from the first step) is expected after i.
+ *
If there is a next instruction and if the control flow can continue to it (e.g. if
+ * i is a ALOAD, for instance): either there an existing stack map frame
+ * f0 (coming from the first step) after i, or there is none. In the
+ * first case f and f0 must be compatible: the types in
+ * f must be sub types of the corresponding types in the existing frame
+ * f0 (otherwise an exception is thrown). In the second case, f0 is
+ * simply set to the value of f.
+ *
If the control flow can continue to some instruction j (e.g. if i
+ * is an IF_EQ, for instance): an existing stack map frame f0 (coming from the
+ * first step) is expected at j, which must be compatible with f (as
+ * defined previously).
+ *
+ * The sequential loop over the instructions is done in {@link #init}, which is called from
+ * the {@link Analyzer#analyze} method. Cases where the control flow cannot continue to the
+ * next instruction are handled in {@link #endControlFlow}. Cases where the control flow can
+ * continue to the next instruction, or jump to another instruction, are handled in {@link
+ * #checkFrame}. This method checks that an existing stack map frame is present when required,
+ * and checks the stack map frames compatibility with {@link #checkMerge}.
+ *
+ *
+ * @author Eric Bruneton
+ * @param type of the {@link Value} used for the analysis.
+ */
+class CheckFrameAnalyzer extends Analyzer {
+
+ /** The interpreter to use to symbolically interpret the bytecode instructions. */
+ private final Interpreter interpreter;
+
+ /** The instructions of the currently analyzed method. */
+ private InsnList insnList;
+
+ /**
+ * The number of locals in the last stack map frame processed by {@link expandFrame}. Long and
+ * double values are represented with two elements.
+ */
+ private int currentLocals;
+
+ CheckFrameAnalyzer(final Interpreter interpreter) {
+ super(interpreter);
+ this.interpreter = interpreter;
+ }
+
+ @Override
+ protected void init(final String owner, final MethodNode method) throws AnalyzerException {
+ insnList = method.instructions;
+ currentLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2;
+
+ Frame[] frames = getFrames();
+ Frame currentFrame = frames[0];
+ expandFrames(owner, method, currentFrame);
+ for (int insnIndex = 0; insnIndex < insnList.size(); ++insnIndex) {
+ Frame oldFrame = frames[insnIndex];
+
+ // Simulate the execution of this instruction.
+ AbstractInsnNode insnNode = null;
+ try {
+ insnNode = method.instructions.get(insnIndex);
+ int insnOpcode = insnNode.getOpcode();
+ int insnType = insnNode.getType();
+
+ if (insnType == AbstractInsnNode.LABEL
+ || insnType == AbstractInsnNode.LINE
+ || insnType == AbstractInsnNode.FRAME) {
+ checkFrame(insnIndex + 1, oldFrame, /* requireFrame = */ false);
+ } else {
+ currentFrame.init(oldFrame).execute(insnNode, interpreter);
+
+ if (insnNode instanceof JumpInsnNode) {
+ if (insnOpcode == JSR) {
+ throw new AnalyzerException(insnNode, "JSR instructions are unsupported");
+ }
+ JumpInsnNode jumpInsn = (JumpInsnNode) insnNode;
+ int targetInsnIndex = insnList.indexOf(jumpInsn.label);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ if (insnOpcode == GOTO) {
+ endControlFlow(insnIndex);
+ } else {
+ checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false);
+ }
+ } else if (insnNode instanceof LookupSwitchInsnNode) {
+ LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) insnNode;
+ int targetInsnIndex = insnList.indexOf(lookupSwitchInsn.dflt);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ for (int i = 0; i < lookupSwitchInsn.labels.size(); ++i) {
+ LabelNode label = lookupSwitchInsn.labels.get(i);
+ targetInsnIndex = insnList.indexOf(label);
+ currentFrame.initJumpTarget(insnOpcode, label);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ }
+ endControlFlow(insnIndex);
+ } else if (insnNode instanceof TableSwitchInsnNode) {
+ TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) insnNode;
+ int targetInsnIndex = insnList.indexOf(tableSwitchInsn.dflt);
+ currentFrame.initJumpTarget(insnOpcode, tableSwitchInsn.dflt);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ newControlFlowEdge(insnIndex, targetInsnIndex);
+ for (int i = 0; i < tableSwitchInsn.labels.size(); ++i) {
+ LabelNode label = tableSwitchInsn.labels.get(i);
+ currentFrame.initJumpTarget(insnOpcode, label);
+ targetInsnIndex = insnList.indexOf(label);
+ checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true);
+ }
+ endControlFlow(insnIndex);
+ } else if (insnOpcode == RET) {
+ throw new AnalyzerException(insnNode, "RET instructions are unsupported");
+ } else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) {
+ checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false);
+ } else {
+ endControlFlow(insnIndex);
+ }
+ }
+
+ List insnHandlers = getHandlers(insnIndex);
+ if (insnHandlers != null) {
+ for (TryCatchBlockNode tryCatchBlock : insnHandlers) {
+ Type catchType;
+ if (tryCatchBlock.type == null) {
+ catchType = Type.getObjectType("java/lang/Throwable");
+ } else {
+ catchType = Type.getObjectType(tryCatchBlock.type);
+ }
+ Frame handler = newFrame(oldFrame);
+ handler.clearStack();
+ handler.push(interpreter.newExceptionValue(tryCatchBlock, handler, catchType));
+ checkFrame(insnList.indexOf(tryCatchBlock.handler), handler, /* requireFrame = */ true);
+ }
+ }
+
+ if (!hasNextJvmInsnOrFrame(insnIndex)) {
+ break;
+ }
+ } catch (AnalyzerException e) {
+ throw new AnalyzerException(
+ e.node, "Error at instruction " + insnIndex + ": " + e.getMessage(), e);
+ } catch (RuntimeException e) {
+ // DontCheck(IllegalCatch): can't be fixed, for backward compatibility.
+ throw new AnalyzerException(
+ insnNode, "Error at instruction " + insnIndex + ": " + e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Expands the {@link FrameNode} "instructions" of the given method into {@link Frame} objects and
+ * stores them at the corresponding indices of the {@link #frames} array. The expanded frames are
+ * also associated with the label and line number nodes immediately preceding each frame node.
+ *
+ * @param owner the internal name of the class to which 'method' belongs.
+ * @param method the method whose frames must be expanded.
+ * @param initialFrame the implicit initial frame of 'method'.
+ * @throws AnalyzerException if the stack map frames of 'method', i.e. its FrameNode
+ * "instructions", are invalid.
+ */
+ private void expandFrames(
+ final String owner, final MethodNode method, final Frame initialFrame)
+ throws AnalyzerException {
+ int lastJvmOrFrameInsnIndex = -1;
+ Frame currentFrame = initialFrame;
+ int currentInsnIndex = 0;
+ for (AbstractInsnNode insnNode : method.instructions) {
+ if (insnNode instanceof FrameNode) {
+ try {
+ currentFrame = expandFrame(owner, currentFrame, (FrameNode) insnNode);
+ } catch (AnalyzerException e) {
+ throw new AnalyzerException(
+ e.node, "Error at instruction " + currentInsnIndex + ": " + e.getMessage(), e);
+ }
+ for (int index = lastJvmOrFrameInsnIndex + 1; index <= currentInsnIndex; ++index) {
+ getFrames()[index] = currentFrame;
+ }
+ }
+ if (isJvmInsnNode(insnNode) || insnNode instanceof FrameNode) {
+ lastJvmOrFrameInsnIndex = currentInsnIndex;
+ }
+ currentInsnIndex += 1;
+ }
+ }
+
+ /**
+ * Returns the expanded representation of the given {@link FrameNode}.
+ *
+ * @param owner the internal name of the class to which 'frameNode' belongs.
+ * @param previousFrame the frame before 'frameNode', in expanded form.
+ * @param frameNode a possibly compressed stack map frame.
+ * @return the expanded version of 'frameNode'.
+ * @throws AnalyzerException if 'frameNode' is invalid.
+ */
+ private Frame expandFrame(
+ final String owner, final Frame previousFrame, final FrameNode frameNode)
+ throws AnalyzerException {
+ Frame frame = newFrame(previousFrame);
+ List locals = frameNode.local == null ? Collections.emptyList() : frameNode.local;
+ int currentLocal = currentLocals;
+ switch (frameNode.type) {
+ case Opcodes.F_NEW:
+ case Opcodes.F_FULL:
+ currentLocal = 0;
+ // fall through
+ case Opcodes.F_APPEND:
+ for (Object type : locals) {
+ V value = newFrameValue(owner, frameNode, type);
+ if (currentLocal + value.getSize() > frame.getLocals()) {
+ throw new AnalyzerException(frameNode, "Cannot append more locals than maxLocals");
+ }
+ frame.setLocal(currentLocal++, value);
+ if (value.getSize() == 2) {
+ frame.setLocal(currentLocal++, interpreter.newValue(null));
+ }
+ }
+ break;
+ case Opcodes.F_CHOP:
+ for (Object unusedType : locals) {
+ if (currentLocal <= 0) {
+ throw new AnalyzerException(frameNode, "Cannot chop more locals than defined");
+ }
+ if (currentLocal > 1 && frame.getLocal(currentLocal - 2).getSize() == 2) {
+ currentLocal -= 2;
+ } else {
+ currentLocal -= 1;
+ }
+ }
+ break;
+ case Opcodes.F_SAME:
+ case Opcodes.F_SAME1:
+ break;
+ default:
+ throw new AnalyzerException(frameNode, "Illegal frame type " + frameNode.type);
+ }
+ currentLocals = currentLocal;
+ while (currentLocal < frame.getLocals()) {
+ frame.setLocal(currentLocal++, interpreter.newValue(null));
+ }
+
+ List stack = frameNode.stack == null ? Collections.emptyList() : frameNode.stack;
+ frame.clearStack();
+ for (Object type : stack) {
+ frame.push(newFrameValue(owner, frameNode, type));
+ }
+ return frame;
+ }
+
+ /**
+ * Creates a new {@link Value} that represents the given stack map frame type.
+ *
+ * @param owner the internal name of the class to which 'frameNode' belongs.
+ * @param frameNode the stack map frame to which 'type' belongs.
+ * @param type an Integer, String or LabelNode object representing a primitive, reference or
+ * uninitialized a stack map frame type, respectively. See {@link FrameNode}.
+ * @return a value that represents the given type.
+ * @throws AnalyzerException if 'type' is an invalid stack map frame type.
+ */
+ private V newFrameValue(final String owner, final FrameNode frameNode, final Object type)
+ throws AnalyzerException {
+ if (type == Opcodes.TOP) {
+ return interpreter.newValue(null);
+ } else if (type == Opcodes.INTEGER) {
+ return interpreter.newValue(Type.INT_TYPE);
+ } else if (type == Opcodes.FLOAT) {
+ return interpreter.newValue(Type.FLOAT_TYPE);
+ } else if (type == Opcodes.LONG) {
+ return interpreter.newValue(Type.LONG_TYPE);
+ } else if (type == Opcodes.DOUBLE) {
+ return interpreter.newValue(Type.DOUBLE_TYPE);
+ } else if (type == Opcodes.NULL) {
+ return interpreter.newOperation(new InsnNode(Opcodes.ACONST_NULL));
+ } else if (type == Opcodes.UNINITIALIZED_THIS) {
+ return interpreter.newValue(Type.getObjectType(owner));
+ } else if (type instanceof String) {
+ return interpreter.newValue(Type.getObjectType((String) type));
+ } else if (type instanceof LabelNode) {
+ AbstractInsnNode referencedNode = (LabelNode) type;
+ while (referencedNode != null && !isJvmInsnNode(referencedNode)) {
+ referencedNode = referencedNode.getNext();
+ }
+ if (referencedNode == null || referencedNode.getOpcode() != Opcodes.NEW) {
+ throw new AnalyzerException(frameNode, "LabelNode does not designate a NEW instruction");
+ }
+ return interpreter.newValue(Type.getObjectType(((TypeInsnNode) referencedNode).desc));
+ }
+ throw new AnalyzerException(frameNode, "Illegal stack map frame value " + type);
+ }
+
+ /**
+ * Checks that the given frame is compatible with the frame at the given instruction index, if
+ * any. If there is no frame at this instruction index and none is required, the frame at
+ * 'insnIndex' is set to the given frame. Otherwise, if the merge of the two frames is not equal
+ * to the current frame at 'insnIndex', an exception is thrown.
+ *
+ * @param insnIndex an instruction index.
+ * @param frame a frame. This frame is left unchanged by this method.
+ * @param requireFrame whether a frame must already exist or not in {@link #frames} at
+ * 'insnIndex'.
+ * @throws AnalyzerException if the frames have incompatible sizes or if the frame at 'insnIndex'
+ * is missing (if required) or not compatible with 'frame'.
+ */
+ private void checkFrame(final int insnIndex, final Frame frame, final boolean requireFrame)
+ throws AnalyzerException {
+ Frame oldFrame = getFrames()[insnIndex];
+ if (oldFrame == null) {
+ if (requireFrame) {
+ throw new AnalyzerException(null, "Expected stack map frame at instruction " + insnIndex);
+ }
+ getFrames()[insnIndex] = newFrame(frame);
+ } else {
+ String error = checkMerge(frame, oldFrame);
+ if (error != null) {
+ throw new AnalyzerException(
+ null,
+ "Stack map frame incompatible with frame at instruction "
+ + insnIndex
+ + " ("
+ + error
+ + ")");
+ }
+ }
+ }
+
+ /**
+ * Checks that merging the two given frames would not produce any change, i.e. that the types in
+ * the source frame are sub types of the corresponding types in the destination frame.
+ *
+ * @param srcFrame a source frame. This frame is left unchanged by this method.
+ * @param dstFrame a destination frame. This frame is left unchanged by this method.
+ * @return an error message if the frames have incompatible sizes, or if a type in the source
+ * frame is not a sub type of the corresponding type in the destination frame. Returns
+ * {@literal null} otherwise.
+ */
+ private String checkMerge(final Frame srcFrame, final Frame dstFrame) {
+ int numLocals = srcFrame.getLocals();
+ if (numLocals != dstFrame.getLocals()) {
+ throw new AssertionError();
+ }
+ for (int i = 0; i < numLocals; ++i) {
+ V v = interpreter.merge(srcFrame.getLocal(i), dstFrame.getLocal(i));
+ if (!v.equals(dstFrame.getLocal(i))) {
+ return "incompatible types at local "
+ + i
+ + ": "
+ + srcFrame.getLocal(i)
+ + " and "
+ + dstFrame.getLocal(i);
+ }
+ }
+ int numStack = srcFrame.getStackSize();
+ if (numStack != dstFrame.getStackSize()) {
+ return "incompatible stack heights";
+ }
+ for (int i = 0; i < numStack; ++i) {
+ V v = interpreter.merge(srcFrame.getStack(i), dstFrame.getStack(i));
+ if (!v.equals(dstFrame.getStack(i))) {
+ return "incompatible types at stack item "
+ + i
+ + ": "
+ + srcFrame.getStack(i)
+ + " and "
+ + dstFrame.getStack(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Ends the control flow graph at the given instruction. This method checks that there is an
+ * existing frame for the next instruction, if any.
+ *
+ * @param insnIndex an instruction index.
+ * @throws AnalyzerException if 'insnIndex' is not the last instruction and there is no frame at
+ * 'insnIndex' + 1 in {@link #getFrames}.
+ */
+ private void endControlFlow(final int insnIndex) throws AnalyzerException {
+ if (hasNextJvmInsnOrFrame(insnIndex) && getFrames()[insnIndex + 1] == null) {
+ throw new AnalyzerException(
+ null, "Expected stack map frame at instruction " + (insnIndex + 1));
+ }
+ }
+
+ /**
+ * Returns true if the given instruction is followed by a JVM instruction or a by stack map frame.
+ *
+ * @param insnIndex an instruction index.
+ * @return true if 'insnIndex' is followed by a JVM instruction or a by stack map frame.
+ */
+ private boolean hasNextJvmInsnOrFrame(final int insnIndex) {
+ AbstractInsnNode insn = insnList.get(insnIndex).getNext();
+ while (insn != null) {
+ if (isJvmInsnNode(insn) || insn instanceof FrameNode) {
+ return true;
+ }
+ insn = insn.getNext();
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given instruction node corresponds to a real JVM instruction.
+ *
+ * @param insnNode an instruction node.
+ * @return true except for label, line number and stack map frame nodes.
+ */
+ private static boolean isJvmInsnNode(final AbstractInsnNode insnNode) {
+ return insnNode.getOpcode() >= 0;
+ }
+}
diff --git a/src/java/nginx/clojure/asm/util/CheckMethodAdapter.java b/src/java/nginx/clojure/asm/util/CheckMethodAdapter.java
index d438fd56..ecfcb7f7 100644
--- a/src/java/nginx/clojure/asm/util/CheckMethodAdapter.java
+++ b/src/java/nginx/clojure/asm/util/CheckMethodAdapter.java
@@ -38,6 +38,7 @@
import nginx.clojure.asm.AnnotationVisitor;
import nginx.clojure.asm.Attribute;
+import nginx.clojure.asm.ClassWriter;
import nginx.clojure.asm.ConstantDynamic;
import nginx.clojure.asm.Handle;
import nginx.clojure.asm.Label;
@@ -351,7 +352,7 @@ private enum Method {
* @param methodvisitor the method visitor to which this adapter must delegate calls.
*/
public CheckMethodAdapter(final MethodVisitor methodvisitor) {
- this(methodvisitor, new HashMap