diff --git a/example-projects/jersey-example/.classpath b/example-projects/jersey-example/.classpath new file mode 100644 index 00000000..f619a536 --- /dev/null +++ b/example-projects/jersey-example/.classpath @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example-projects/jersey-example/.gitignore b/example-projects/jersey-example/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/example-projects/jersey-example/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/example-projects/jersey-example/.project b/example-projects/jersey-example/.project new file mode 100644 index 00000000..fdd46163 --- /dev/null +++ b/example-projects/jersey-example/.project @@ -0,0 +1,23 @@ + + + jersey-example + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/example-projects/jersey-example/.settings/org.eclipse.core.resources.prefs b/example-projects/jersey-example/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..f9fe3459 --- /dev/null +++ b/example-projects/jersey-example/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/example-projects/jersey-example/.settings/org.eclipse.jdt.core.prefs b/example-projects/jersey-example/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..ec4300d5 --- /dev/null +++ b/example-projects/jersey-example/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/example-projects/jersey-example/.settings/org.eclipse.m2e.core.prefs b/example-projects/jersey-example/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/example-projects/jersey-example/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/example-projects/jersey-example/README.md b/example-projects/jersey-example/README.md new file mode 100644 index 00000000..aafd4f92 --- /dev/null +++ b/example-projects/jersey-example/README.md @@ -0,0 +1,7 @@ +# nginx jsersey example + +## build + +```bash +mvn compile assembly:single -DskipTests +``` \ No newline at end of file diff --git a/example-projects/jersey-example/pom.xml b/example-projects/jersey-example/pom.xml new file mode 100644 index 00000000..a0827443 --- /dev/null +++ b/example-projects/jersey-example/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + nginx-clojure + jersey-example + jar + 1.0 + jersey-example + + + + + org.glassfish.jersey + jersey-bom + ${jersey.version} + pom + import + + + + + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + junit + junit + 4.9 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + true + + 1.7 + 1.7 + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + + java + + + + + nginx.clojure.jersey.example.Main + + + + maven-assembly-plugin + + + + nginx.clojure.jersey.example.Main + + + + jar-with-dependencies + + + + + + + + 2.25.1 + UTF-8 + + diff --git a/example-projects/jersey-example/src/main/java/nginx/clojure/jersey/example/Main.java b/example-projects/jersey-example/src/main/java/nginx/clojure/jersey/example/Main.java new file mode 100644 index 00000000..1d54f245 --- /dev/null +++ b/example-projects/jersey-example/src/main/java/nginx/clojure/jersey/example/Main.java @@ -0,0 +1,45 @@ +package nginx.clojure.jersey.example; + +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; +import org.glassfish.jersey.server.ResourceConfig; + +import java.io.IOException; +import java.net.URI; + +/** + * Main class. + * + */ +public class Main { + // Base URI the Grizzly HTTP server will listen on + public static final String BASE_URI = "http://localhost:8087/myapp/"; + + /** + * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application. + * @return Grizzly HTTP server. + */ + public static HttpServer startServer() { + // create a resource config that scans for JAX-RS resources and providers + // in nginx.clojure.jersey.example package + final ResourceConfig rc = new ResourceConfig().packages("nginx.clojure.jersey.example"); + + // create and start a new instance of grizzly http server + // exposing the Jersey application at BASE_URI + return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc); + } + + /** + * Main method. + * @param args + * @throws IOException + */ + public static void main(String[] args) throws IOException { + final HttpServer server = startServer(); + System.out.println(String.format("Jersey app started with WADL available at " + + "%sapplication.wadl\nHit enter to stop it...", BASE_URI)); + System.in.read(); + server.stop(); + } +} + diff --git a/example-projects/jersey-example/src/main/java/nginx/clojure/jersey/example/MyResource.java b/example-projects/jersey-example/src/main/java/nginx/clojure/jersey/example/MyResource.java new file mode 100644 index 00000000..e1413b91 --- /dev/null +++ b/example-projects/jersey-example/src/main/java/nginx/clojure/jersey/example/MyResource.java @@ -0,0 +1,32 @@ +package nginx.clojure.jersey.example; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * Root resource (exposed at "myresource" path) + */ +@Path("myresource") +public class MyResource { + + /** + * Method handling HTTP GET requests. The returned object will be sent + * to the client as "text/plain" media type. + * + * @return String that will be returned as a text/plain response. + */ + @GET + @Produces(MediaType.TEXT_PLAIN) + public String getIt() { + return "Got it!"; + } + + @GET + @Path("/empty") + public Response empty() { + return Response.status(201).entity("").build(); + } +} diff --git a/example-projects/jersey-example/src/test/java/nginx/clojure/jersey/example/MyResourceTest.java b/example-projects/jersey-example/src/test/java/nginx/clojure/jersey/example/MyResourceTest.java new file mode 100644 index 00000000..e238af26 --- /dev/null +++ b/example-projects/jersey-example/src/test/java/nginx/clojure/jersey/example/MyResourceTest.java @@ -0,0 +1,48 @@ +package nginx.clojure.jersey.example; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; + +import org.glassfish.grizzly.http.server.HttpServer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.assertEquals; + +public class MyResourceTest { + + private HttpServer server; + private WebTarget target; + + @Before + public void setUp() throws Exception { + // start the server + server = Main.startServer(); + // create the client + Client c = ClientBuilder.newClient(); + + // uncomment the following line if you want to enable + // support for JSON in the client (you also have to uncomment + // dependency on jersey-media-json module in pom.xml and Main.startServer()) + // -- + // c.configuration().enable(new org.glassfish.jersey.media.json.JsonJaxbFeature()); + + target = c.target(Main.BASE_URI); + } + + @After + public void tearDown() throws Exception { + server.stop(); + } + + /** + * Test to see that the message "Got it!" is sent in the response. + */ + @Test + public void testGetIt() { + String responseMsg = target.path("myresource").request().get(String.class); + assertEquals("Got it!", responseMsg); + } +} diff --git a/nginx-clojure-embed/configure-centos5-x64 b/nginx-clojure-embed/configure-centos5-x64 new file mode 100755 index 00000000..07842003 --- /dev/null +++ b/nginx-clojure-embed/configure-centos5-x64 @@ -0,0 +1,139 @@ +#!/bin/sh + +# Copyright (C) Zhang,Yuexiang (xfeep) + + +NGINX_SRC=$NGINX_SRC +NGINX_SRC=/home/who/build4embed/nginx-current +NGINX_CLOJURE_SRC=$(pwd)/.. +NGINX_CLOJURE_EMBED_SRC=$(pwd) + +help(){ + echo "[Usage]:" \ + "env NGINX_SRC=nginx-src-path ./configure" + echo "[example]: env NGINX_SRC=/home/who/share/tmp/nginx-release-1.8.0 ./configure" +} + +if ! [ -f $NGINX_SRC/src/core/nginx.c ]; then + echo "[ERROR]:nginx source not found:\$NGINX_SRC=\"$NGINX_SRC\"" + help + exit 1 +fi + +if ! [ -f $NGINX_CLOJURE_SRC/src/c/ngx_http_clojure_mem.c ]; then + echo "[ERROR]:nginx-clojure source not found:\$NGINX_CLOJURE_SRC=\"$NGINX_CLOJURE_SRC\"" + help + exit 1 +fi + + + +##check jdk +if ! type javac; then + echo "javac not found, please put it in your PATH" + exit 1 +fi + +if ! type java; then + echo "java not found, please put it in your PATH" + exit 1 +fi + +mkdir /tmp/nc-DiscoverJvm +javac $NGINX_CLOJURE_SRC/src/java/nginx/clojure/DiscoverJvm.java -d /tmp/nc-DiscoverJvm + +if [ -z $JNI_INCS ]; then + JNI_INCS=`java -classpath /tmp/nc-DiscoverJvm nginx.clojure.DiscoverJvm getJniIncludes`; +fi + +nginx_clojure_embed_ext=`java -classpath /tmp/nc-DiscoverJvm nginx.clojure.DiscoverJvm detectOSArchExt` +nginx_clojure_embed_ext="-$nginx_clojure_embed_ext" +rm -rf /tmp/nc-DiscoverJvm + +cd $NGINX_SRC +if ! [ -f src/core/nginx.c.org ]; then + cp src/core/nginx.c src/core/nginx.c.org +# sed -e ':a' -e 'N' -e '$!ba' -e 's/static[ ]ngx_int_t ngx_save_argv/ngx_int_t ngx_save_argv/g' src/core/nginx.c > src/core/nginx.c-new +# sed -e ':a' -e 'N' -e '$!ba' -e 's/static[ ]ngx_int_t\s*\n*ngx_save_argv/ngx_int_t ngx_save_argv/g' src/core/nginx.c-new > src/core/nginx.c + sed -e ':a' -e 'N' -e '$!ba' -e 's/static[ ]ngx_int_t/ngx_int_t/g' src/core/nginx.c > src/core/nginx.c-new + mv src/core/nginx.c-new src/core/nginx.c +fi + +set -- --prefix= \ + --conf-path=conf/nginx.conf --pid-path=logs/nginx.pid \ + --http-log-path=logs/access.log --error-log-path=logs/error.log \ + --sbin-path=nginx --http-client-body-temp-path=temp/client_body_temp \ + --http-proxy-temp-path=temp/proxy_temp \ + --http-proxy-temp-path=temp/proxy_temp \ + --without-http_ssi_module \ + --without-http_userid_module \ + --without-http_geo_module \ + --without-http_split_clients_module \ + --without-http_referer_module \ + --without-http_fastcgi_module \ + --without-http_uwsgi_module \ + --without-http_scgi_module \ + --without-http_memcached_module \ + --without-http_empty_gif_module \ + --without-http_upstream_hash_module \ + --without-http_upstream_ip_hash_module \ + --without-http_upstream_least_conn_module \ + --without-http_upstream_keepalive_module \ + --without-http-cache \ + --without-mail_pop3_module \ + --without-mail_imap_module \ + --without-mail_smtp_module \ + --with-debug \ + --add-module=${NGINX_CLOJURE_SRC}/src/c \ + --add-module=${NGINX_CLOJURE_EMBED_SRC}/src/c \ + --with-pcre=../pcre-8.40 \ + --with-pcre-opt=-fPIC + +if [ -f "auto/configure" ]; then + . auto/configure +else + . ./configure +fi + + + +nginx_clojure_embed_shared_lib=nginx-clojure-embed${nginx_clojure_embed_ext} + +cat << END >> objs/Makefile + + +$NGX_OBJS${ngx_dirsep}${nginx_clojure_embed_shared_lib}: $ngx_deps$ngx_spacer + \$(LINK) ${ngx_long_start}${ngx_binout}$NGX_OBJS${ngx_dirsep}${nginx_clojure_embed_shared_lib}$ngx_long_cont$ngx_objs$ngx_libs$ngx_link + $ngx_rcc +${ngx_long_end} + +embed: $NGX_OBJS${ngx_dirsep}${nginx_clojure_embed_shared_lib} + mkdir -p "$NGINX_CLOJURE_EMBED_SRC/res/slib" + cp $NGX_OBJS${ngx_dirsep}${nginx_clojure_embed_shared_lib} $NGINX_CLOJURE_EMBED_SRC/res/slib + if type strip; then \ + strip -x -S $NGINX_CLOJURE_EMBED_SRC/res/slib/${nginx_clojure_embed_shared_lib}; \ + fi + cd $NGINX_CLOJURE_EMBED_SRC; \ + echo "finish build nginx-clojure embed" +END + +echo "creating $NGINX_CLOJURE_EMBED_SRC\Makefile" +cd $NGINX_CLOJURE_EMBED_SRC +cat << END > Makefile +default: + cd $NGINX_SRC; \ + \$(MAKE) -f objs/Makefile embed + cd $NGINX_CLOJURE_EMBED_SRC + +clean: + cd $NGINX_SRC; \ + \$(MAKE) clean + cd $NGINX_CLOJURE_EMBED_SRC; \ + rm $NGINX_CLOJURE_EMBED_SRC/res/slib/${nginx_clojure_embed_shared_lib} && \ + rm Makefile +END + +echo "done" +echo "please try make to compile shared library of nginx-clojure-embed" +echo "[if nginx version < 1.11.2] make sure use './config -fPIC && make' to make openssl" +echo "[if nginx version < 1.11.2] modify nginx/objs/Makefile replace -lcrypt with ../openssl-1.0.1e/libcrypto.a" \ No newline at end of file diff --git a/nginx-clojure-embed/configure-win32 b/nginx-clojure-embed/configure-win32 new file mode 100644 index 00000000..6ba34bb9 --- /dev/null +++ b/nginx-clojure-embed/configure-win32 @@ -0,0 +1,147 @@ +#!/bin/sh + +# Copyright (C) Zhang,Yuexiang (xfeep) + +NGINX_SRC=c:/mingw/msys/1.0/home/myadmin/build-for-embed/nginx-1.12.0 +NGINX_CLOJURE_SRC=c:/mingw/msys/1.0/home/myadmin/build-for-embed/nginx-clojure +NGINX_CLOJURE_EMBED_SRC=c:/mingw/msys/1.0/home/myadmin/build-for-embed/nginx-clojure/nginx-clojure-embed + +help(){ + echo "[Usage]:" \ + "env NGINX_SRC=nginx-src-path ./configure" + echo "[example]: env NGINX_SRC=/home/who/share/tmp/nginx-release-1.8.0 ./configure" +} + +if ! [ -f $NGINX_SRC/src/core/nginx.c ]; then + echo "[ERROR]:nginx source not found:\$NGINX_SRC=\"$NGINX_SRC\"" + help + exit 1 +fi + +if ! [ -f $NGINX_CLOJURE_SRC/src/c/ngx_http_clojure_mem.c ]; then + echo "[ERROR]:nginx-clojure source not found:\$NGINX_CLOJURE_SRC=\"$NGINX_CLOJURE_SRC\"" + help + exit 1 +fi + + + +##check jdk +if ! type javac; then + echo "javac not found, please put it in your PATH" + exit 1 +fi + +if ! type java; then + echo "java not found, please put it in your PATH" + exit 1 +fi + +javac $NGINX_CLOJURE_SRC/src/java/nginx/clojure/DiscoverJvm.java + +if [ -z $JNI_INCS ]; then + JNI_INCS=`java -classpath $NGINX_CLOJURE_SRC/src/java nginx.clojure.DiscoverJvm getJniIncludes`; +fi + +nginx_clojure_embed_ext=`java -classpath $NGINX_CLOJURE_SRC/src/java nginx.clojure.DiscoverJvm detectOSArchExt` +nginx_clojure_embed_ext="-$nginx_clojure_embed_ext" +rm $NGINX_CLOJURE_SRC/src/java/nginx/clojure/DiscoverJvm.class + +cd $NGINX_SRC +if ! [ -f src/core/nginx.c.org ]; then + cp src/core/nginx.c src/core/nginx.c.org +# sed -e ':a' -e 'N' -e '$!ba' -e 's/static[ ]ngx_int_t ngx_save_argv/ngx_int_t ngx_save_argv/g' src/core/nginx.c > src/core/nginx.c-new +# sed -e ':a' -e 'N' -e '$!ba' -e 's/static[ ]ngx_int_t\s*\n*ngx_save_argv/ngx_int_t ngx_save_argv/g' src/core/nginx.c-new > src/core/nginx.c + sed -e ':a' -e 'N' -e '$!ba' -e 's/static[ ]ngx_int_t/ngx_int_t/g' src/core/nginx.c > src/core/nginx.c-new + mv src/core/nginx.c-new src/core/nginx.c +fi + +set -- --prefix= \ + --with-cc=cl \ + --builddir=objs \ + --sbin-path=nginx.exe \ + --with-cc-opt=-DFD_SETSIZE=4096 \ + --with-select_module \ + --conf-path=conf/nginx.conf --pid-path=logs/nginx.pid \ + --http-log-path=logs/access.log --error-log-path=logs/error.log \ + --sbin-path=nginx --http-client-body-temp-path=temp/client_body_temp \ + --http-proxy-temp-path=temp/proxy_temp \ + --http-proxy-temp-path=temp/proxy_temp \ + --without-http_ssi_module \ + --without-http_userid_module \ + --without-http_geo_module \ + --without-http_split_clients_module \ + --without-http_referer_module \ + --without-http_fastcgi_module \ + --without-http_uwsgi_module \ + --without-http_scgi_module \ + --without-http_memcached_module \ + --without-http_empty_gif_module \ + --without-http_upstream_hash_module \ + --without-http_upstream_ip_hash_module \ + --without-http_upstream_least_conn_module \ + --without-http_upstream_keepalive_module \ + --without-http-cache \ + --without-mail_pop3_module \ + --without-mail_imap_module \ + --without-mail_smtp_module \ + --with-debug \ + --add-module=${NGINX_CLOJURE_SRC}/src/c \ + --add-module=${NGINX_CLOJURE_EMBED_SRC}/src/c \ + --with-pcre=../pcre-8.40 \ + --with-zlib=../zlib-1.2.11 \ + +## --with-sha1=C:/MinGW/msys/1.0/home/myadmin/nginx-current/objs/lib/openssl-1.0.1e/crypto/sha \ +## --with-cc-opt="-I C:/MinGW/msys/1.0/home/myadmin/nginx-current/objs/lib/openssl-1.0.1e/openssl/include" +## --with-http_ssl_module \ +## --with-openssl=C:/MinGW/msys/1.0/home/myadmin/nginx-current/objs/lib/openssl-1.0.1e + + +if [ -f "auto/configure" ]; then + . auto/configure +else + . ./configure +fi + + + +nginx_clojure_embed_shared_lib=nginx-clojure-embed${nginx_clojure_embed_ext} + +cat << END >> objs/Makefile + + +$NGX_OBJS${ngx_dirsep}${nginx_clojure_embed_shared_lib}: $ngx_deps$ngx_spacer + \$(LINK) ${ngx_long_start}${ngx_binout}$NGX_OBJS${ngx_dirsep}${nginx_clojure_embed_shared_lib}$ngx_long_cont$ngx_objs$ngx_libs$ngx_link + $ngx_rcc +${ngx_long_end} + +embed: $NGX_OBJS${ngx_dirsep}${nginx_clojure_embed_shared_lib} + mkdir -p "$NGINX_CLOJURE_EMBED_SRC/res/slib" + cp $NGX_OBJS${ngx_dirsep}${nginx_clojure_embed_shared_lib} $NGINX_CLOJURE_EMBED_SRC/res/slib + if type strip; then \ + strip -x -S $NGINX_CLOJURE_EMBED_SRC/res/slib/${nginx_clojure_embed_shared_lib}; \ + fi + cd $NGINX_CLOJURE_EMBED_SRC; \ + echo "finish build nginx-clojure embed" +END + +echo "creating $NGINX_CLOJURE_EMBED_SRC\Makefile" +cd $NGINX_CLOJURE_EMBED_SRC +cat << END > Makefile +default: + cd $NGINX_SRC; \ + \$(MAKE) -f objs/Makefile embed + cd $NGINX_CLOJURE_EMBED_SRC + +clean: + cd $NGINX_SRC; \ + \$(MAKE) clean + cd $NGINX_CLOJURE_EMBED_SRC; \ + rm $NGINX_CLOJURE_EMBED_SRC/res/slib/${nginx_clojure_embed_shared_lib} && \ + rm Makefile +END + +echo "done" +echo "please try make to compile shared library of nginx-clojure-embed" +echo "please modify $NGINX_SRC/auto/lib/sha1/makefile.msvc to add openssl sha headers" +echo "CFLAGS = -nologo -O2 -Ob1 -Oi -Gs $(LIBC) $(CPU_OPT) -D L_ENDIAN -I C:/MinGW/msys/1.0/home/myadmin/nginx-current/objs/lib/openssl-1.0.1e/openssl/include -I C:/MinGW/msys/1.0/home/myadmin/nginx-current/objs/lib/openssl-1.0.1e/crypto" diff --git a/nginx-clojure-embed/macos-issue b/nginx-clojure-embed/macos-issue new file mode 100644 index 00000000..60466dc6 --- /dev/null +++ b/nginx-clojure-embed/macos-issue @@ -0,0 +1,11 @@ + +for ncs in ngx_http_clojure_mem ngx_http_clojure_socket ngx_http_clojure_shared_map_tinymap ngx_http_clojure_shared_map_hashmap; +do +gcc -c -fpic -fvisibility=hidden -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include" -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin" -I "/Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c" -DNGX_CLOJURE_BE_SILENT_WITHOUT_JVM -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include" -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin" -pipe -O -Wall -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I ../pcre-8.40 -I objs -I src/http -I src/http/modules -I /Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c \ + -o objs/addon/c/${ncs}.o \ + /Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c/${ncs}.c +done + +gcc -c -fpic -fvisibility=hidden -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include" -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin" -I "/Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c" -DNGX_CLOJURE_BE_SILENT_WITHOUT_JVM -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include" -I "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin" -pipe -O -Wall -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I ../pcre-8.40 -I objs -I src/http -I src/http/modules -I /Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/../src/c \ + -o objs/addon/c/ngx_http_clojure_embed.o \ + /Users/whomac/Dev/build-for-embed/nginx-clojure/nginx-clojure-embed/src/c/ngx_http_clojure_embed.c diff --git a/project.clj b/project.clj index 7cb6c4cb..71a5a2e6 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject nginx-clojure/nginx-clojure "0.4.5" +(defproject nginx-clojure/nginx-clojure "0.5.0" :description "Nginx module for clojure or groovy or java programming" :url "https://github.com/nginx-clojure/nginx-clojure" :license {:name "BSD 3-Clause license" @@ -62,9 +62,9 @@ [stylefruits/gniazdo "0.4.0"] ]} :unittest { - :jvm-opts ["-javaagent:target/nginx-clojure-0.4.4.jar=mb" + :jvm-opts ["-javaagent:target/nginx-clojure-0.5.0.jar=mb" "-Dnginx.clojure.wave.udfs=pure-clj.txt,compojure.txt,compojure-http-clj.txt" - "-Xbootclasspath/a:target/nginx-clojure-0.4.4.jar"] + "-Xbootclasspath/a:target/nginx-clojure-0.5.0.jar"] :junit-options {:fork "on"} :java-source-paths ["test/java" "test/clojure"] :test-paths ["src/test/clojure"] diff --git a/src/clojure/nginx/clojure/core.clj b/src/clojure/nginx/clojure/core.clj index 23eac42a..d2eb1789 100644 --- a/src/clojure/nginx/clojure/core.clj +++ b/src/clojure/nginx/clojure/core.clj @@ -195,7 +195,7 @@ (.addListener ch ch (proxy [WholeMessageAdapter] [max-message-size] (onOpen [c] (if on-open (on-open c))) (onWholeTextMessage [c msg] (if on-message (on-message c msg))) - (onWholeBiniaryMessage [c msg rem?] (if on-message (on-message c msg))) + (onWholeBiniaryMessage [c msg] (if on-message (on-message c msg))) (onClose ;([c] (if on-close (on-close c "0"))) ([c status reason] (if on-close (on-close c (str status ":" reason))))) (onError [c status] (if on-error (on-error c (NginxClojureAsynSocket/errorCodeToString status))))))) diff --git a/src/java/nginx/clojure/NginxClojureRT.java b/src/java/nginx/clojure/NginxClojureRT.java index 36ba5fc7..7a2d70d6 100644 --- a/src/java/nginx/clojure/NginxClojureRT.java +++ b/src/java/nginx/clojure/NginxClojureRT.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.lang.management.ManagementFactory; -import java.lang.reflect.Field; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -556,16 +555,7 @@ public static void initStringAddrMapsByNativeAddr(Map map, long ad private static synchronized void initMemIndex(long idxpt) { getLog(); - initUnsafe(); - - //hack mysql jdbc driver to keep from creating connections by reflective invoking the constructor - try { - Class mysqljdbcUtilClz = Thread.currentThread().getContextClassLoader().loadClass("com.mysql.jdbc.Util"); - Field isJdbc4Field = mysqljdbcUtilClz.getDeclaredField("isJdbc4"); - isJdbc4Field.setAccessible(true); - isJdbc4Field.set(null, false); - } catch (Throwable e) { - } + initUnsafe(); NGINX_MAIN_THREAD = Thread.currentThread(); diff --git a/src/java/nginx/clojure/asm/AnnotationVisitor.java b/src/java/nginx/clojure/asm/AnnotationVisitor.java index 94b298d1..f128cc97 100644 --- a/src/java/nginx/clojure/asm/AnnotationVisitor.java +++ b/src/java/nginx/clojure/asm/AnnotationVisitor.java @@ -41,7 +41,7 @@ public abstract class AnnotationVisitor { /** * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ protected final int api; @@ -56,7 +56,7 @@ public abstract class AnnotationVisitor { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ public AnnotationVisitor(final int api) { this(api, null); @@ -67,13 +67,13 @@ public AnnotationVisitor(final int api) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param av * the annotation visitor to which this visitor must delegate * method calls. May be null. */ public AnnotationVisitor(final int api, final AnnotationVisitor av) { - if (api != Opcodes.ASM4) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { throw new IllegalArgumentException(); } this.api = api; @@ -89,7 +89,7 @@ public AnnotationVisitor(final int api, final AnnotationVisitor av) { * the actual value, whose type must be {@link Byte}, * {@link Boolean}, {@link Character}, {@link Short}, * {@link Integer} , {@link Long}, {@link Float}, {@link Double}, - * {@link String} or {@link Type} or OBJECT or ARRAY sort. This + * {@link String} or {@link Type} of OBJECT or ARRAY sort. This * value can also be an array of byte, boolean, short, char, int, * long, float or double values (this is equivalent to using * {@link #visitArray visitArray} and visiting each array element diff --git a/src/java/nginx/clojure/asm/AnnotationWriter.java b/src/java/nginx/clojure/asm/AnnotationWriter.java index 67827fa1..de1b472c 100644 --- a/src/java/nginx/clojure/asm/AnnotationWriter.java +++ b/src/java/nginx/clojure/asm/AnnotationWriter.java @@ -104,7 +104,7 @@ final class AnnotationWriter extends AnnotationVisitor { */ AnnotationWriter(final ClassWriter cw, final boolean named, final ByteVector bv, final ByteVector parent, final int offset) { - super(Opcodes.ASM4); + super(Opcodes.ASM5); this.cw = cw; this.named = named; this.bv = bv; @@ -315,4 +315,57 @@ static void put(final AnnotationWriter[] panns, final int off, } } } + + /** + * Puts the given type reference and type path into the given bytevector. + * LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported. + * + * @param typeRef + * a reference to the annotated type. 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 + * null if the annotation targets 'typeRef' as a whole. + * @param out + * where the type reference and type path must be put. + */ + static void putTarget(int typeRef, TypePath typePath, ByteVector out) { + switch (typeRef >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + out.putShort(typeRef >>> 16); + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + out.putByte(typeRef >>> 24); + break; + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + out.putInt(typeRef); + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + out.put12(typeRef >>> 24, (typeRef & 0xFFFF00) >> 8); + break; + } + if (typePath == null) { + out.putByte(0); + } else { + int length = typePath.b[typePath.offset] * 2 + 1; + out.putByteArray(typePath.b, typePath.offset, length); + } + } } diff --git a/src/java/nginx/clojure/asm/ByteVector.java b/src/java/nginx/clojure/asm/ByteVector.java index 2eaf5c9d..21d7da8a 100644 --- a/src/java/nginx/clojure/asm/ByteVector.java +++ b/src/java/nginx/clojure/asm/ByteVector.java @@ -230,41 +230,68 @@ public ByteVector putUTF8(final String s) { if (c >= '\001' && c <= '\177') { data[len++] = (byte) c; } else { - int byteLength = i; - for (int j = i; j < charLength; ++j) { - c = s.charAt(j); - if (c >= '\001' && c <= '\177') { - byteLength++; - } else if (c > '\u07FF') { - byteLength += 3; - } else { - byteLength += 2; - } - } - if (byteLength > 65535) { - throw new IllegalArgumentException(); - } - data[length] = (byte) (byteLength >>> 8); - data[length + 1] = (byte) byteLength; - if (length + 2 + byteLength > data.length) { - length = len; - enlarge(2 + byteLength); - data = this.data; - } - for (int j = i; j < charLength; ++j) { - c = s.charAt(j); - if (c >= '\001' && c <= '\177') { - data[len++] = (byte) c; - } else if (c > '\u07FF') { - data[len++] = (byte) (0xE0 | c >> 12 & 0xF); - data[len++] = (byte) (0x80 | c >> 6 & 0x3F); - data[len++] = (byte) (0x80 | c & 0x3F); - } else { - data[len++] = (byte) (0xC0 | c >> 6 & 0x1F); - data[len++] = (byte) (0x80 | c & 0x3F); - } - } - break; + length = len; + return encodeUTF8(s, i, 65535); + } + } + length = len; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is + * automatically enlarged if necessary. The string length is encoded in two + * bytes before the encoded characters, if there is space for that (i.e. if + * this.length - i - 2 >= 0). + * + * @param s + * the String to encode. + * @param i + * the index of the first character to encode. The previous + * characters are supposed to have already been encoded, using + * only one byte per character. + * @param maxByteLength + * the maximum byte length of the encoded string, including the + * already encoded characters. + * @return this byte vector. + */ + ByteVector encodeUTF8(final String s, int i, int maxByteLength) { + int charLength = s.length(); + int byteLength = i; + char c; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + byteLength++; + } else if (c > '\u07FF') { + byteLength += 3; + } else { + byteLength += 2; + } + } + if (byteLength > maxByteLength) { + throw new IllegalArgumentException(); + } + int start = length - i - 2; + if (start >= 0) { + data[start] = (byte) (byteLength >>> 8); + data[start + 1] = (byte) byteLength; + } + if (length + byteLength - i > data.length) { + enlarge(byteLength - i); + } + int len = length; + for (int j = i; j < charLength; ++j) { + c = s.charAt(j); + if (c >= '\001' && c <= '\177') { + data[len++] = (byte) c; + } else if (c > '\u07FF') { + data[len++] = (byte) (0xE0 | c >> 12 & 0xF); + data[len++] = (byte) (0x80 | c >> 6 & 0x3F); + data[len++] = (byte) (0x80 | c & 0x3F); + } else { + data[len++] = (byte) (0xC0 | c >> 6 & 0x1F); + data[len++] = (byte) (0x80 | c & 0x3F); } } length = len; diff --git a/src/java/nginx/clojure/asm/ClassReader.java b/src/java/nginx/clojure/asm/ClassReader.java index 96a49860..e2bc182d 100644 --- a/src/java/nginx/clojure/asm/ClassReader.java +++ b/src/java/nginx/clojure/asm/ClassReader.java @@ -104,6 +104,21 @@ public class ClassReader { */ public static final int EXPAND_FRAMES = 8; + /** + * Flag to expand the ASM pseudo instructions into an equivalent sequence of + * standard bytecode instructions. When resolving a forward jump it may + * happen that the signed 2 bytes offset reserved for it is not sufficient + * to store the bytecode offset. In this case the jump instruction is + * replaced with a temporary ASM pseudo instruction using an unsigned 2 + * bytes offset (see Label#resolve). This internal flag is used to re-read + * classes containing such instructions, in order to replace them with + * standard instructions. In addition, when this flag is used, GOTO_W and + * JSR_W are not converted into GOTO and JSR, to make sure that + * infinite loops where a GOTO_W is replaced with a GOTO in ClassReader and + * converted back to a GOTO_W in ClassWriter cannot occur. + */ + static final int EXPAND_ASM_INSNS = 256; + /** * The class to be parsed. The content of this array must not be * modified. This field is intended for {@link Attribute} sub classes, and @@ -557,6 +572,8 @@ public void accept(final ClassVisitor classVisitor, String enclosingDesc = null; int anns = 0; int ianns = 0; + int tanns = 0; + int itanns = 0; int innerClasses = 0; Attribute attributes = null; @@ -581,6 +598,9 @@ public void accept(final ClassVisitor classVisitor, } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; } else if ("Deprecated".equals(attrName)) { access |= Opcodes.ACC_DEPRECATED; } else if ("Synthetic".equals(attrName)) { @@ -592,6 +612,9 @@ public void accept(final ClassVisitor classVisitor, } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; } else if ("BootstrapMethods".equals(attrName)) { int[] bootstrapMethods = new int[readUnsignedShort(u + 8)]; for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) { @@ -626,7 +649,7 @@ public void accept(final ClassVisitor classVisitor, enclosingDesc); } - // visits the class annotations + // visits the class annotations and type annotations if (ANNOTATIONS && anns != 0) { for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { v = readAnnotationValues(v + 2, c, true, @@ -639,6 +662,22 @@ public void accept(final ClassVisitor classVisitor, classVisitor.visitAnnotation(readUTF8(v, c), false)); } } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + classVisitor.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } // visits the attributes while (attributes != null) { @@ -697,6 +736,8 @@ private int readField(final ClassVisitor classVisitor, String signature = null; int anns = 0; int ianns = 0; + int tanns = 0; + int itanns = 0; Object value = null; Attribute attributes = null; @@ -717,9 +758,15 @@ private int readField(final ClassVisitor classVisitor, } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; } else { Attribute attr = readAttribute(context.attrs, attrName, u + 8, readInt(u + 4), c, -1, null); @@ -739,7 +786,7 @@ private int readField(final ClassVisitor classVisitor, return u; } - // visits the field annotations + // visits the field annotations and type annotations if (ANNOTATIONS && anns != 0) { for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) { v = readAnnotationValues(v + 2, c, true, @@ -752,6 +799,22 @@ private int readField(final ClassVisitor classVisitor, fv.visitAnnotation(readUTF8(v, c), false)); } } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + fv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + fv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } // visits the field attributes while (attributes != null) { @@ -782,9 +845,9 @@ private int readMethod(final ClassVisitor classVisitor, final Context context, int u) { // reads the method declaration char[] c = context.buffer; - int access = readUnsignedShort(u); - String name = readUTF8(u + 2, c); - String desc = readUTF8(u + 4, c); + context.access = readUnsignedShort(u); + context.name = readUTF8(u + 2, c); + context.desc = readUTF8(u + 4, c); u += 6; // reads the method attributes @@ -792,8 +855,11 @@ private int readMethod(final ClassVisitor classVisitor, int exception = 0; String[] exceptions = null; String signature = null; + int methodParameters = 0; int anns = 0; int ianns = 0; + int tanns = 0; + int itanns = 0; int dann = 0; int mpanns = 0; int impanns = 0; @@ -818,24 +884,32 @@ private int readMethod(final ClassVisitor classVisitor, } else if (SIGNATURES && "Signature".equals(attrName)) { signature = readUTF8(u + 8, c); } else if ("Deprecated".equals(attrName)) { - access |= Opcodes.ACC_DEPRECATED; + context.access |= Opcodes.ACC_DEPRECATED; } else if (ANNOTATIONS && "RuntimeVisibleAnnotations".equals(attrName)) { anns = u + 8; + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = u + 8; } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) { dann = u + 8; } else if ("Synthetic".equals(attrName)) { - access |= Opcodes.ACC_SYNTHETIC + context.access |= Opcodes.ACC_SYNTHETIC | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE; } else if (ANNOTATIONS && "RuntimeInvisibleAnnotations".equals(attrName)) { ianns = u + 8; + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = u + 8; } else if (ANNOTATIONS && "RuntimeVisibleParameterAnnotations".equals(attrName)) { mpanns = u + 8; } else if (ANNOTATIONS && "RuntimeInvisibleParameterAnnotations".equals(attrName)) { impanns = u + 8; + } else if ("MethodParameters".equals(attrName)) { + methodParameters = u + 8; } else { Attribute attr = readAttribute(context.attrs, attrName, u + 8, readInt(u + 4), c, -1, null); @@ -849,8 +923,8 @@ private int readMethod(final ClassVisitor classVisitor, u += 2; // visits the method declaration - MethodVisitor mv = classVisitor.visitMethod(access, name, desc, - signature, exceptions); + MethodVisitor mv = classVisitor.visitMethod(context.access, + context.name, context.desc, signature, exceptions); if (mv == null) { return u; } @@ -894,6 +968,13 @@ private int readMethod(final ClassVisitor classVisitor, } } + // visit the method parameters + if (methodParameters != 0) { + for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) { + mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2)); + } + } + // visits the method annotations if (ANNOTATIONS && dann != 0) { AnnotationVisitor dv = mv.visitAnnotationDefault(); @@ -914,11 +995,27 @@ private int readMethod(final ClassVisitor classVisitor, mv.visitAnnotation(readUTF8(v, c), false)); } } + if (ANNOTATIONS && tanns != 0) { + for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + mv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + } + if (ANNOTATIONS && itanns != 0) { + for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) { + v = readAnnotationTarget(context, v); + v = readAnnotationValues(v + 2, c, true, + mv.visitTypeAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + } if (ANNOTATIONS && mpanns != 0) { - readParameterAnnotations(mpanns, desc, c, true, mv); + readParameterAnnotations(mv, context, mpanns, true); } if (ANNOTATIONS && impanns != 0) { - readParameterAnnotations(impanns, desc, c, false, mv); + readParameterAnnotations(mv, context, impanns, false); } // visits the method attributes @@ -931,9 +1028,6 @@ private int readMethod(final ClassVisitor classVisitor, // visits the method code if (code != 0) { - context.access = access; - context.name = name; - context.desc = desc; mv.visitCode(); readCode(mv, context, code); } @@ -966,7 +1060,7 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { // reads the bytecode to find the labels int codeStart = u; int codeEnd = u + codeLength; - Label[] labels = new Label[codeLength + 2]; + Label[] labels = context.labels = new Label[codeLength + 2]; readLabel(codeLength + 1, labels); while (u < codeEnd) { int offset = u - codeStart; @@ -980,6 +1074,10 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { readLabel(offset + readShort(u + 1), labels); u += 3; break; + case ClassWriter.ASM_LABEL_INSN: + readLabel(offset + readUnsignedShort(u + 1), labels); + u += 3; + break; case ClassWriter.LABELW_INSN: readLabel(offset + readInt(u + 1), labels); u += 5; @@ -1049,6 +1147,12 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { u += 2; // reads the code attributes + int[] tanns = null; // start index of each visible type annotation + int[] itanns = null; // start index of each invisible type annotation + int tann = 0; // current index in tanns array + int itann = 0; // current index in itanns array + int ntoff = -1; // next visible type annotation code offset + int nitoff = -1; // next invisible type annotation code offset int varTable = 0; int varTypeTable = 0; boolean zip = true; @@ -1085,10 +1189,27 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { if (labels[label] == null) { readLabel(label, labels).status |= Label.DEBUG; } - labels[label].line = readUnsignedShort(v + 12); + Label l = labels[label]; + while (l.line > 0) { + if (l.next == null) { + l.next = new Label(); + } + l = l.next; + } + l.line = readUnsignedShort(v + 12); v += 4; } } + } else if (ANNOTATIONS + && "RuntimeVisibleTypeAnnotations".equals(attrName)) { + tanns = readTypeAnnotations(mv, context, u + 8, true); + ntoff = tanns.length == 0 || readByte(tanns[0]) < 0x43 ? -1 + : readUnsignedShort(tanns[0] + 1); + } else if (ANNOTATIONS + && "RuntimeInvisibleTypeAnnotations".equals(attrName)) { + itanns = readTypeAnnotations(mv, context, u + 8, false); + nitoff = itanns.length == 0 || readByte(itanns[0]) < 0x43 ? -1 + : readUnsignedShort(itanns[0] + 1); } else if (FRAMES && "StackMapTable".equals(attrName)) { if ((context.flags & SKIP_FRAMES) == 0) { stackMap = u + 10; @@ -1181,8 +1302,23 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { } } } + if ((context.flags & EXPAND_ASM_INSNS) != 0) { + // Expanding the ASM pseudo instructions can introduce F_INSERT + // frames, even if the method does not currently have any frame. + // Also these inserted frames must be computed by simulating the + // effect of the bytecode instructions one by one, starting from the + // first one and the last existing frame (or the implicit first + // one). Finally, due to the way MethodWriter computes this (with + // the compute = INSERTED_FRAMES option), MethodWriter needs to know + // maxLocals before the first instruction is visited. For all these + // reasons we always visit the implicit first frame in this case + // (passing only maxLocals - the rest can be and is computed in + // MethodWriter). + mv.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null); + } // visits the instructions + int opcodeDelta = (context.flags & EXPAND_ASM_INSNS) == 0 ? -33 : 0; u = codeStart; while (u < codeEnd) { int offset = u - codeStart; @@ -1190,9 +1326,15 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { // visits the label and line number for this offset, if any Label l = labels[offset]; if (l != null) { + Label next = l.next; + l.next = null; mv.visitLabel(l); if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) { mv.visitLineNumber(l.line, l); + while (next != null) { + mv.visitLineNumber(next.line, l); + next = next.next; + } } } @@ -1211,7 +1353,7 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { } } if (frameCount > 0) { - stackMap = readFrame(stackMap, zip, unzip, labels, frame); + stackMap = readFrame(stackMap, zip, unzip, frame); --frameCount; } else { frame = null; @@ -1241,9 +1383,42 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { u += 3; break; case ClassWriter.LABELW_INSN: - mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]); + mv.visitJumpInsn(opcode + opcodeDelta, labels[offset + + readInt(u + 1)]); u += 5; break; + case ClassWriter.ASM_LABEL_INSN: { + // changes temporary opcodes 202 to 217 (inclusive), 218 + // and 219 to IFEQ ... JSR (inclusive), IFNULL and + // IFNONNULL + opcode = opcode < 218 ? opcode - 49 : opcode - 20; + Label target = labels[offset + readUnsignedShort(u + 1)]; + // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx + // with IFNOTxxx GOTO_W , where IFNOTxxx is + // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) + // and where designates the instruction just after + // the GOTO_W. + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + mv.visitJumpInsn(opcode + 33, target); + } else { + opcode = opcode <= 166 ? ((opcode + 1) ^ 1) - 1 + : opcode ^ 1; + Label endif = new Label(); + mv.visitJumpInsn(opcode, endif); + mv.visitJumpInsn(200, target); // GOTO_W + mv.visitLabel(endif); + // since we introduced an unconditional jump instruction we + // also need to insert a stack map frame here, unless there + // is already one. The actual frame content will be computed + // in MethodWriter. + if (FRAMES && stackMap != 0 + && (frame == null || frame.offset != offset + 3)) { + mv.visitFrame(ClassWriter.F_INSERT, 0, null, 0, null); + } + } + u += 3; + break; + } case ClassWriter.WIDE_INSN: opcode = b[u + 1] & 0xFF; if (opcode == Opcodes.IINC) { @@ -1310,6 +1485,7 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { case ClassWriter.FIELDORMETH_INSN: case ClassWriter.ITFMETH_INSN: { int cpIndex = items[readUnsignedShort(u + 1)]; + boolean itf = b[cpIndex - 1] == ClassWriter.IMETH; String iowner = readClass(cpIndex, c); cpIndex = items[readUnsignedShort(cpIndex + 2)]; String iname = readUTF8(cpIndex, c); @@ -1317,7 +1493,7 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { if (opcode < Opcodes.INVOKEVIRTUAL) { mv.visitFieldInsn(opcode, iowner, iname, idesc); } else { - mv.visitMethodInsn(opcode, iowner, iname, idesc); + mv.visitMethodInsn(opcode, iowner, iname, idesc, itf); } if (opcode == Opcodes.INVOKEINTERFACE) { u += 5; @@ -1358,6 +1534,29 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { u += 4; break; } + + // visit the instruction annotations, if any + while (tanns != null && tann < tanns.length && ntoff <= offset) { + if (ntoff == offset) { + int v = readAnnotationTarget(context, tanns[tann]); + readAnnotationValues(v + 2, c, true, + mv.visitInsnAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), true)); + } + ntoff = ++tann >= tanns.length || readByte(tanns[tann]) < 0x43 ? -1 + : readUnsignedShort(tanns[tann] + 1); + } + while (itanns != null && itann < itanns.length && nitoff <= offset) { + if (nitoff == offset) { + int v = readAnnotationTarget(context, itanns[itann]); + readAnnotationValues(v + 2, c, true, + mv.visitInsnAnnotation(context.typeRef, + context.typePath, readUTF8(v, c), false)); + } + nitoff = ++itann >= itanns.length + || readByte(itanns[itann]) < 0x43 ? -1 + : readUnsignedShort(itanns[itann] + 1); + } } if (labels[codeLength] != null) { mv.visitLabel(labels[codeLength]); @@ -1397,6 +1596,32 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { } } + // visits the local variables type annotations + if (tanns != null) { + for (int i = 0; i < tanns.length; ++i) { + if ((readByte(tanns[i]) >> 1) == (0x40 >> 1)) { + int v = readAnnotationTarget(context, tanns[i]); + v = readAnnotationValues(v + 2, c, true, + mv.visitLocalVariableAnnotation(context.typeRef, + context.typePath, context.start, + context.end, context.index, readUTF8(v, c), + true)); + } + } + } + if (itanns != null) { + for (int i = 0; i < itanns.length; ++i) { + if ((readByte(itanns[i]) >> 1) == (0x40 >> 1)) { + int v = readAnnotationTarget(context, itanns[i]); + v = readAnnotationValues(v + 2, c, true, + mv.visitLocalVariableAnnotation(context.typeRef, + context.typePath, context.start, + context.end, context.index, readUTF8(v, c), + false)); + } + } + } + // visits the code attributes while (attributes != null) { Attribute attr = attributes.next; @@ -1409,25 +1634,176 @@ private void readCode(final MethodVisitor mv, final Context context, int u) { mv.visitMaxs(maxStack, maxLocals); } + /** + * Parses a type annotation table to find the labels, and to visit the try + * catch block annotations. + * + * @param u + * the start offset of a type annotation table. + * @param mv + * the method visitor to be used to visit the try catch block + * annotations. + * @param context + * information about the class being parsed. + * @param visible + * if the type annotation table to parse contains runtime visible + * annotations. + * @return the start offset of each type annotation in the parsed table. + */ + private int[] readTypeAnnotations(final MethodVisitor mv, + final Context context, int u, boolean visible) { + char[] c = context.buffer; + int[] offsets = new int[readUnsignedShort(u)]; + u += 2; + for (int i = 0; i < offsets.length; ++i) { + offsets[i] = u; + int target = readInt(u); + switch (target >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + u += 2; + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + u += 1; + break; + case 0x40: // LOCAL_VARIABLE + case 0x41: // RESOURCE_VARIABLE + for (int j = readUnsignedShort(u + 1); j > 0; --j) { + int start = readUnsignedShort(u + 3); + int length = readUnsignedShort(u + 5); + readLabel(start, context.labels); + readLabel(start + length, context.labels); + u += 6; + } + u += 3; + break; + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + u += 4; + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + u += 3; + break; + } + int pathLength = readByte(u); + if ((target >>> 24) == 0x42) { + TypePath path = pathLength == 0 ? null : new TypePath(b, u); + u += 1 + 2 * pathLength; + u = readAnnotationValues(u + 2, c, true, + mv.visitTryCatchAnnotation(target, path, + readUTF8(u, c), visible)); + } else { + u = readAnnotationValues(u + 3 + 2 * pathLength, c, true, null); + } + } + return offsets; + } + + /** + * Parses the header of a type annotation to extract its target_type and + * target_path (the result is stored in the given context), and returns the + * start offset of the rest of the type_annotation structure (i.e. the + * offset to the type_index field, which is followed by + * num_element_value_pairs and then the name,value pairs). + * + * @param context + * information about the class being parsed. This is where the + * extracted target_type and target_path must be stored. + * @param u + * the start offset of a type_annotation structure. + * @return the start offset of the rest of the type_annotation structure. + */ + private int readAnnotationTarget(final Context context, int u) { + int target = readInt(u); + switch (target >>> 24) { + case 0x00: // CLASS_TYPE_PARAMETER + case 0x01: // METHOD_TYPE_PARAMETER + case 0x16: // METHOD_FORMAL_PARAMETER + target &= 0xFFFF0000; + u += 2; + break; + case 0x13: // FIELD + case 0x14: // METHOD_RETURN + case 0x15: // METHOD_RECEIVER + target &= 0xFF000000; + u += 1; + break; + case 0x40: // LOCAL_VARIABLE + case 0x41: { // RESOURCE_VARIABLE + target &= 0xFF000000; + int n = readUnsignedShort(u + 1); + context.start = new Label[n]; + context.end = new Label[n]; + context.index = new int[n]; + u += 3; + for (int i = 0; i < n; ++i) { + int start = readUnsignedShort(u); + int length = readUnsignedShort(u + 2); + context.start[i] = readLabel(start, context.labels); + context.end[i] = readLabel(start + length, context.labels); + context.index[i] = readUnsignedShort(u + 4); + u += 6; + } + break; + } + case 0x47: // CAST + case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT + case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT + target &= 0xFF0000FF; + u += 4; + break; + // case 0x10: // CLASS_EXTENDS + // case 0x11: // CLASS_TYPE_PARAMETER_BOUND + // case 0x12: // METHOD_TYPE_PARAMETER_BOUND + // case 0x17: // THROWS + // case 0x42: // EXCEPTION_PARAMETER + // case 0x43: // INSTANCEOF + // case 0x44: // NEW + // case 0x45: // CONSTRUCTOR_REFERENCE + // case 0x46: // METHOD_REFERENCE + default: + target &= (target >>> 24) < 0x43 ? 0xFFFFFF00 : 0xFF000000; + u += 3; + break; + } + int pathLength = readByte(u); + context.typeRef = target; + context.typePath = pathLength == 0 ? null : new TypePath(b, u); + return u + 1 + 2 * pathLength; + } + /** * Reads parameter annotations and makes the given visitor visit them. * + * @param mv + * the visitor that must visit the annotations. + * @param context + * information about the class being parsed. * @param v * start offset in {@link #b b} of the annotations to be read. - * @param desc - * the method descriptor. - * @param buf - * buffer to be used to call {@link #readUTF8 readUTF8}, - * {@link #readClass(int,char[]) readClass} or {@link #readConst - * readConst}. * @param visible * true if the annotations to be read are visible at * runtime. - * @param mv - * the visitor that must visit the annotations. */ - private void readParameterAnnotations(int v, final String desc, - final char[] buf, final boolean visible, final MethodVisitor mv) { + private void readParameterAnnotations(final MethodVisitor mv, + final Context context, int v, final boolean visible) { int i; int n = b[v++] & 0xFF; // workaround for a bug in javac (javac compiler generates a parameter @@ -1436,7 +1812,7 @@ private void readParameterAnnotations(int v, final String desc, // equal to the number of parameters in the method descriptor - which // includes the synthetic parameters added by the compiler). This work- // around supposes that the synthetic parameters are the first ones. - int synthetics = Type.getArgumentTypes(desc).length - n; + int synthetics = Type.getArgumentTypes(context.desc).length - n; AnnotationVisitor av; for (i = 0; i < synthetics; ++i) { // virtual annotation to detect synthetic parameters in MethodWriter @@ -1445,12 +1821,13 @@ private void readParameterAnnotations(int v, final String desc, av.visitEnd(); } } + char[] c = context.buffer; for (; i < n + synthetics; ++i) { int j = readUnsignedShort(v); v += 2; for (; j > 0; --j) { - av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible); - v = readAnnotationValues(v + 2, buf, true, av); + av = mv.visitParameterAnnotation(i, readUTF8(v, c), visible); + v = readAnnotationValues(v + 2, c, true, av); } } } @@ -1531,8 +1908,7 @@ private int readAnnotationValue(int v, final char[] buf, final String name, v += 2; break; case 'B': // pointer to CONSTANT_Byte - av.visit(name, - new Byte((byte) readInt(items[readUnsignedShort(v)]))); + av.visit(name, (byte) readInt(items[readUnsignedShort(v)])); v += 2; break; case 'Z': // pointer to CONSTANT_Boolean @@ -1542,13 +1918,11 @@ private int readAnnotationValue(int v, final char[] buf, final String name, v += 2; break; case 'S': // pointer to CONSTANT_Short - av.visit(name, new Short( - (short) readInt(items[readUnsignedShort(v)]))); + av.visit(name, (short) readInt(items[readUnsignedShort(v)])); v += 2; break; case 'C': // pointer to CONSTANT_Char - av.visit(name, new Character( - (char) readInt(items[readUnsignedShort(v)]))); + av.visit(name, (char) readInt(items[readUnsignedShort(v)])); v += 2; break; case 's': // pointer to CONSTANT_Utf8 @@ -1729,17 +2103,14 @@ private void getImplicitFrame(final Context frame) { * if the stack map frame at stackMap is compressed or not. * @param unzip * if the stack map frame must be uncompressed. - * @param labels - * the labels of the method currently being parsed, indexed by - * their offset. A new label for the parsed stack map frame is - * stored in this array if it does not already exist. * @param frame * where the parsed stack map frame must be stored. * @return the offset of the first byte following the parsed frame. */ private int readFrame(int stackMap, boolean zip, boolean unzip, - Label[] labels, Context frame) { + Context frame) { char[] c = frame.buffer; + Label[] labels = frame.labels; int tag; int delta; if (zip) { @@ -2175,13 +2546,13 @@ public Object readConst(final int item, final char[] buf) { int index = items[item]; switch (b[index - 1]) { case ClassWriter.INT: - return new Integer(readInt(index)); + return readInt(index); case ClassWriter.FLOAT: - return new Float(Float.intBitsToFloat(readInt(index))); + return Float.intBitsToFloat(readInt(index)); case ClassWriter.LONG: - return new Long(readLong(index)); + return readLong(index); case ClassWriter.DOUBLE: - return new Double(Double.longBitsToDouble(readLong(index))); + return Double.longBitsToDouble(readLong(index)); case ClassWriter.CLASS: return Type.getObjectType(readUTF8(index, buf)); case ClassWriter.STR: @@ -2192,11 +2563,12 @@ public Object readConst(final int item, final char[] buf) { int tag = readByte(index); int[] items = this.items; int cpIndex = items[readUnsignedShort(index + 1)]; + boolean itf = b[cpIndex - 1] == ClassWriter.IMETH; String owner = readClass(cpIndex, buf); cpIndex = items[readUnsignedShort(cpIndex + 2)]; String name = readUTF8(cpIndex, buf); String desc = readUTF8(cpIndex + 2, buf); - return new Handle(tag, owner, name, desc); + return new Handle(tag, owner, name, desc, itf); } } } diff --git a/src/java/nginx/clojure/asm/ClassVisitor.java b/src/java/nginx/clojure/asm/ClassVisitor.java index 0c294864..85a60550 100644 --- a/src/java/nginx/clojure/asm/ClassVisitor.java +++ b/src/java/nginx/clojure/asm/ClassVisitor.java @@ -33,8 +33,9 @@ * A visitor to visit a Java class. The methods of this class must be called in * the following order: visit [ visitSource ] [ * visitOuterClass ] ( visitAnnotation | - * visitAttribute )* ( visitInnerClass | visitField | - * visitMethod )* visitEnd. + * visitTypeAnnotation | visitAttribute )* ( + * visitInnerClass | visitField | visitMethod )* + * visitEnd. * * @author Eric Bruneton */ @@ -42,7 +43,7 @@ public abstract class ClassVisitor { /** * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ protected final int api; @@ -57,7 +58,7 @@ public abstract class ClassVisitor { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ public ClassVisitor(final int api) { this(api, null); @@ -68,13 +69,13 @@ public ClassVisitor(final int api) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param cv * the class visitor to which this visitor must delegate method * calls. May be null. */ public ClassVisitor(final int api, final ClassVisitor cv) { - if (api != Opcodes.ASM4) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { throw new IllegalArgumentException(); } this.api = api; @@ -168,6 +169,39 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return null; } + /** + * Visits an annotation on a type in the class signature. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#CLASS_TYPE_PARAMETER + * CLASS_TYPE_PARAMETER}, + * {@link TypeReference#CLASS_TYPE_PARAMETER_BOUND + * CLASS_TYPE_PARAMETER_BOUND} or + * {@link TypeReference#CLASS_EXTENDS 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (cv != null) { + return cv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + /** * Visits a non standard attribute of the class. * diff --git a/src/java/nginx/clojure/asm/ClassWriter.java b/src/java/nginx/clojure/asm/ClassWriter.java index b921e0ee..3e57d330 100644 --- a/src/java/nginx/clojure/asm/ClassWriter.java +++ b/src/java/nginx/clojure/asm/ClassWriter.java @@ -58,8 +58,8 @@ public class ClassWriter extends ClassVisitor { * {@link MethodVisitor#visitFrame} method are ignored, and the stack map * frames are recomputed from the methods bytecode. The arguments of the * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and - * recomputed from the bytecode. In other words, computeFrames implies - * computeMaxs. + * recomputed from the bytecode. In other words, COMPUTE_FRAMES implies + * COMPUTE_MAXS. * * @see #ClassWriter(int) */ @@ -167,6 +167,22 @@ public class ClassWriter extends ClassVisitor { */ static final int WIDE_INSN = 17; + /** + * The type of the ASM pseudo instructions with an unsigned 2 bytes offset + * label (see Label#resolve). + */ + static final int ASM_LABEL_INSN = 18; + + /** + * Represents a frame inserted between already existing frames. This kind of + * frame can only be used if the frame content can be computed from the + * previous existing frame and from the instructions between this existing + * frame and the inserted one, without any knowledge of the type hierarchy. + * This kind of frame is only used when an unconditional jump is inserted in + * a method while expanding an ASM pseudo instruction (see ClassReader). + */ + static final int F_INSERT = 256; + /** * The instruction types of all JVM opcodes. */ @@ -416,6 +432,16 @@ public class ClassWriter extends ClassVisitor { */ private AnnotationWriter ianns; + /** + * The runtime visible type annotations of this class. + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this class. + */ + private AnnotationWriter itanns; + /** * The non standard attributes of this class. */ @@ -474,25 +500,19 @@ public class ClassWriter extends ClassVisitor { MethodWriter lastMethod; /** - * true if the maximum stack size and number of local variables - * must be automatically computed. - */ - private boolean computeMaxs; - - /** - * true if the stack map frames must be recomputed from scratch. + * Indicates what must be automatically computed. + * + * @see MethodWriter#compute */ - private boolean computeFrames; + private int compute; /** - * true if the stack map tables of this class are invalid. The - * {@link MethodWriter#resizeInstructions} method cannot transform existing - * stack map tables, and so produces potentially invalid classes when it is - * executed. In this case the class is reread and rewritten with the - * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize - * stack map tables when this option is used). + * true if some methods have wide forward jumps using ASM pseudo + * instructions, which need to be expanded into sequences of standard + * bytecode instructions. In this case the class is re-read and re-written + * with a ClassReader -> ClassWriter chain to perform this transformation. */ - boolean invalidFrames; + boolean hasAsmInsns; // ------------------------------------------------------------------------ // Static initializer @@ -507,7 +527,7 @@ public class ClassWriter extends ClassVisitor { String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" - + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ"; + + "AAAAGGGGGGGHIFBFAAFFAARQJJKKSSSSSSSSSSSSSSSSSS"; for (i = 0; i < b.length; ++i) { b[i] = (byte) (s.charAt(i) - 'A'); } @@ -561,7 +581,7 @@ public class ClassWriter extends ClassVisitor { // // temporary opcodes used internally by ASM - see Label and // MethodWriter // for (i = 202; i < 220; ++i) { - // b[i] = LABEL_INSN; + // b[i] = ASM_LABEL_INSN; // } // // // LDC(_W) instructions @@ -595,7 +615,7 @@ public class ClassWriter extends ClassVisitor { * {@link #COMPUTE_FRAMES}. */ public ClassWriter(final int flags) { - super(Opcodes.ASM4); + super(Opcodes.ASM5); index = 1; pool = new ByteVector(); items = new Item[256]; @@ -604,8 +624,9 @@ public ClassWriter(final int flags) { key2 = new Item(); key3 = new Item(); key4 = new Item(); - this.computeMaxs = (flags & COMPUTE_MAXS) != 0; - this.computeFrames = (flags & COMPUTE_FRAMES) != 0; + this.compute = (flags & COMPUTE_FRAMES) != 0 ? MethodWriter.FRAMES + : ((flags & COMPUTE_MAXS) != 0 ? MethodWriter.MAXS + : MethodWriter.NOTHING); } /** @@ -635,9 +656,9 @@ public ClassWriter(final int flags) { * @param flags * option flags that can be used to modify the default behavior * of this class. These option flags do not affect methods - * that are copied as is in the new class. This means that the - * maximum stack size nor the stack frames will be computed for - * these methods. See {@link #COMPUTE_MAXS}, + * 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. See {@link #COMPUTE_MAXS}, * {@link #COMPUTE_FRAMES}. */ public ClassWriter(final ClassReader classReader, final int flags) { @@ -677,7 +698,8 @@ public final void visitSource(final String file, final String debug) { sourceFile = newUTF8(file); } if (debug != null) { - sourceDebug = new ByteVector().putUTF8(debug); + sourceDebug = new ByteVector().encodeUTF8(debug, 0, + Integer.MAX_VALUE); } } @@ -710,6 +732,29 @@ public final AnnotationVisitor visitAnnotation(final String desc, return aw; } + @Override + public final AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + @Override public final void visitAttribute(final Attribute attr) { attr.next = attrs; @@ -722,11 +767,29 @@ public final void visitInnerClass(final String name, if (innerClasses == null) { innerClasses = new ByteVector(); } - ++innerClassesCount; - innerClasses.putShort(name == null ? 0 : newClass(name)); - innerClasses.putShort(outerName == null ? 0 : newClass(outerName)); - innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName)); - innerClasses.putShort(access); + // Sec. 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the + // constant_pool table which represents a class or interface C that is + // not a package member must have exactly one corresponding entry in the + // classes array". To avoid duplicates we keep track in the intVal field + // of the Item of each CONSTANT_Class_info entry C whether an inner + // class entry has already been added for C (this field is unused for + // class entries, and changing its value does not change the hashcode + // and equality tests). If so we store the index of this inner class + // entry (plus one) in intVal. This hack allows duplicate detection in + // O(1) time. + Item nameItem = newClassItem(name); + if (nameItem.intVal == 0) { + ++innerClassesCount; + innerClasses.putShort(nameItem.index); + innerClasses.putShort(outerName == null ? 0 : newClass(outerName)); + innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName)); + innerClasses.putShort(access); + nameItem.intVal = innerClassesCount; + } else { + // Compare the inner classes entry nameItem.intVal - 1 with the + // arguments of this method and throw an exception if there is a + // difference? + } } @Override @@ -739,7 +802,7 @@ public final FieldVisitor visitField(final int access, final String name, public final MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { return new MethodWriter(this, access, name, desc, signature, - exceptions, computeMaxs, computeFrames); + exceptions, compute); } @Override @@ -795,7 +858,7 @@ public byte[] toByteArray() { } if (sourceDebug != null) { ++attributeCount; - size += sourceDebug.length + 4; + size += sourceDebug.length + 6; newUTF8("SourceDebugExtension"); } if (enclosingMethodOwner != 0) { @@ -831,6 +894,16 @@ public byte[] toByteArray() { size += 8 + ianns.getSize(); newUTF8("RuntimeInvisibleAnnotations"); } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + size += 8 + tanns.getSize(); + newUTF8("RuntimeVisibleTypeAnnotations"); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + size += 8 + itanns.getSize(); + newUTF8("RuntimeInvisibleTypeAnnotations"); + } if (attrs != null) { attributeCount += attrs.getCount(); size += attrs.getSize(this, null, 0, -1, -1); @@ -874,9 +947,9 @@ public byte[] toByteArray() { out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile); } if (sourceDebug != null) { - int len = sourceDebug.length - 2; + int len = sourceDebug.length; out.putShort(newUTF8("SourceDebugExtension")).putInt(len); - out.putByteArray(sourceDebug.data, 2, len); + out.putByteArray(sourceDebug.data, 0, len); } if (enclosingMethodOwner != 0) { out.putShort(newUTF8("EnclosingMethod")).putInt(4); @@ -904,25 +977,31 @@ public byte[] toByteArray() { out.putShort(newUTF8("RuntimeInvisibleAnnotations")); ianns.put(out); } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } if (attrs != null) { attrs.put(this, null, 0, -1, -1, out); } - if (invalidFrames) { + if (hasAsmInsns) { anns = null; ianns = null; attrs = null; innerClassesCount = 0; innerClasses = null; - bootstrapMethodsCount = 0; - bootstrapMethods = null; firstField = null; lastField = null; firstMethod = null; lastMethod = null; - computeMaxs = false; - computeFrames = true; - invalidFrames = false; - new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES); + compute = MethodWriter.INSERTED_FRAMES; + hasAsmInsns = false; + new ClassReader(out.data).accept(this, ClassReader.EXPAND_FRAMES + | ClassReader.EXPAND_ASM_INSNS); return toByteArray(); } return out.data; @@ -982,7 +1061,7 @@ Item newConstItem(final Object cst) { } } else if (cst instanceof Handle) { Handle h = (Handle) cst; - return newHandleItem(h.tag, h.owner, h.name, h.desc); + return newHandleItem(h.tag, h.owner, h.name, h.desc, h.itf); } else { throw new IllegalArgumentException("value " + cst); } @@ -1117,10 +1196,12 @@ public int newMethodType(final String methodDesc) { * the name of the field or method. * @param desc * the descriptor of the field or method. + * @param itf + * true if the owner is an interface. * @return a new or an already existing method type reference item. */ Item newHandleItem(final int tag, final String owner, final String name, - final String desc) { + final String desc, final boolean itf) { key4.set(HANDLE_BASE + tag, owner, name, desc); Item result = get(key4); if (result == null) { @@ -1129,8 +1210,7 @@ Item newHandleItem(final int tag, final String owner, final String name, } else { put112(HANDLE, tag, - newMethod(owner, name, desc, - tag == Opcodes.H_INVOKEINTERFACE)); + newMethod(owner, name, desc, itf)); } result = new Item(index++, key4); put(result); @@ -1160,12 +1240,46 @@ Item newHandleItem(final int tag, final String owner, final String name, * the descriptor of the field or method. * @return the index of a new or already existing method type reference * item. + * + * @deprecated this method is superseded by + * {@link #newHandle(int, String, String, String, boolean)}. */ + @Deprecated public int newHandle(final int tag, final String owner, final String name, final String desc) { - return newHandleItem(tag, owner, name, desc).index; + return newHandle(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE); } + /** + * Adds a handle to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by + * class generators or adapters. + * + * @param tag + * the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the field or method owner class. + * @param name + * the name of the field or method. + * @param desc + * the descriptor of the field or method. + * @param itf + * true if the owner is an interface. + * @return the index of a new or already existing method type reference + * item. + */ + public int newHandle(final int tag, final String owner, final String name, + final String desc, final boolean itf) { + return newHandleItem(tag, owner, name, desc, itf).index; + } + /** * Adds an invokedynamic reference to the constant pool of the class being * build. Does nothing if the constant pool already contains a similar item. @@ -1195,7 +1309,7 @@ Item newInvokeDynamicItem(final String name, final String desc, int hashCode = bsm.hashCode(); bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name, - bsm.desc)); + bsm.desc, bsm.isInterface())); int argsLength = bsmArgs.length; bootstrapMethods.putShort(argsLength); @@ -1590,7 +1704,7 @@ int getMergedType(final int type1, final int type2) { /** * Returns the common super type of the two given types. The default - * implementation of this method loads the two given classes and uses + * implementation of this method loads the two given classes and uses * the java.lang.Class methods to find the common super class. It can be * overridden to compute this common super type in other ways, in particular * without actually loading any class, or to take into account the class diff --git a/src/java/nginx/clojure/asm/Context.java b/src/java/nginx/clojure/asm/Context.java index f0e446ba..e2d3c450 100644 --- a/src/java/nginx/clojure/asm/Context.java +++ b/src/java/nginx/clojure/asm/Context.java @@ -72,11 +72,46 @@ class Context { */ String desc; + /** + * The label objects, indexed by bytecode offset, of the method currently + * being parsed (only bytecode offsets for which a label is needed have a + * non null associated Label object). + */ + Label[] labels; + + /** + * The target of the type annotation currently being parsed. + */ + int typeRef; + + /** + * The path of the type annotation currently being parsed. + */ + TypePath typePath; + /** * The offset of the latest stack map frame that has been parsed. */ int offset; + /** + * The labels corresponding to the start of the local variable ranges in the + * local variable type annotation currently being parsed. + */ + Label[] start; + + /** + * The labels corresponding to the end of the local variable ranges in the + * local variable type annotation currently being parsed. + */ + Label[] end; + + /** + * The local variable indices for each local variable range in the local + * variable type annotation currently being parsed. + */ + int[] index; + /** * The encoding of the latest stack map frame that has been parsed. */ diff --git a/src/java/nginx/clojure/asm/CurrentFrame.java b/src/java/nginx/clojure/asm/CurrentFrame.java new file mode 100644 index 00000000..4fa5c6d6 --- /dev/null +++ b/src/java/nginx/clojure/asm/CurrentFrame.java @@ -0,0 +1,56 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package nginx.clojure.asm; + +/** + * Information about the input stack map frame at the "current" instruction of a + * method. This is implemented as a Frame subclass for a "basic block" + * containing only one instruction. + * + * @author Eric Bruneton + */ +class CurrentFrame extends Frame { + + /** + * Sets this CurrentFrame to the input stack map frame of the next "current" + * instruction, i.e. the instruction just after the given one. It is assumed + * that the value of this object when this method is called is the stack map + * frame status just before the given instruction is executed. + */ + @Override + void execute(int opcode, int arg, ClassWriter cw, Item item) { + super.execute(opcode, arg, cw, item); + Frame successor = new Frame(); + merge(cw, successor, 0); + set(successor); + owner.inputStackTop = 0; + } +} diff --git a/src/java/nginx/clojure/asm/FieldVisitor.java b/src/java/nginx/clojure/asm/FieldVisitor.java index a2d39981..f1ea3cf7 100644 --- a/src/java/nginx/clojure/asm/FieldVisitor.java +++ b/src/java/nginx/clojure/asm/FieldVisitor.java @@ -31,8 +31,8 @@ /** * A visitor to visit a Java field. The methods of this class must be called in - * the following order: ( visitAnnotation | visitAttribute )* - * visitEnd. + * the following order: ( visitAnnotation | + * visitTypeAnnotation | visitAttribute )* visitEnd. * * @author Eric Bruneton */ @@ -40,7 +40,7 @@ public abstract class FieldVisitor { /** * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ protected final int api; @@ -55,7 +55,7 @@ public abstract class FieldVisitor { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ public FieldVisitor(final int api) { this(api, null); @@ -66,13 +66,13 @@ public FieldVisitor(final int api) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param fv * the field visitor to which this visitor must delegate method * calls. May be null. */ public FieldVisitor(final int api, final FieldVisitor fv) { - if (api != Opcodes.ASM4) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { throw new IllegalArgumentException(); } this.api = api; @@ -96,6 +96,35 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return null; } + /** + * Visits an annotation on the type of the field. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#FIELD FIELD}. See + * {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (fv != null) { + return fv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + /** * Visits a non standard attribute of the field. * diff --git a/src/java/nginx/clojure/asm/FieldWriter.java b/src/java/nginx/clojure/asm/FieldWriter.java index 6a0b706b..925b23a6 100644 --- a/src/java/nginx/clojure/asm/FieldWriter.java +++ b/src/java/nginx/clojure/asm/FieldWriter.java @@ -80,6 +80,17 @@ final class FieldWriter extends FieldVisitor { */ private AnnotationWriter ianns; + /** + * The runtime visible type annotations of this field. May be null. + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this field. May be + * null. + */ + private AnnotationWriter itanns; + /** * The non standard attributes of this field. May be null. */ @@ -107,7 +118,7 @@ final class FieldWriter extends FieldVisitor { */ FieldWriter(final ClassWriter cw, final int access, final String name, final String desc, final String signature, final Object value) { - super(Opcodes.ASM4); + super(Opcodes.ASM5); if (cw.firstField == null) { cw.firstField = this; } else { @@ -150,6 +161,29 @@ public AnnotationVisitor visitAnnotation(final String desc, return aw; } + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + @Override public void visitAttribute(final Attribute attr) { attr.next = attrs; @@ -198,6 +232,14 @@ int getSize() { cw.newUTF8("RuntimeInvisibleAnnotations"); size += 8 + ianns.getSize(); } + if (ClassReader.ANNOTATIONS && tanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + tanns.getSize(); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + itanns.getSize(); + } if (attrs != null) { size += attrs.getSize(cw, null, 0, -1, -1); } @@ -237,6 +279,12 @@ void put(final ByteVector out) { if (ClassReader.ANNOTATIONS && ianns != null) { ++attributeCount; } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + } if (attrs != null) { attributeCount += attrs.getCount(); } @@ -266,6 +314,14 @@ void put(final ByteVector out) { out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); ianns.put(out); } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } if (attrs != null) { attrs.put(cw, null, 0, -1, -1, out); } diff --git a/src/java/nginx/clojure/asm/Frame.java b/src/java/nginx/clojure/asm/Frame.java index 1d5378ee..6df82b84 100644 --- a/src/java/nginx/clojure/asm/Frame.java +++ b/src/java/nginx/clojure/asm/Frame.java @@ -34,7 +34,7 @@ * * @author Eric Bruneton */ -final class Frame { +class Frame { /* * Frames are computed in a two steps process: during the visit of each @@ -70,8 +70,8 @@ final class Frame { * stack types. VALUE depends on KIND. For LOCAL types, it is an index in * the input local variable types. For STACK types, it is a position * relatively to the top of input frame stack. For BASE types, it is either - * one of the constants defined in FrameVisitor, or for OBJECT and - * UNINITIALIZED types, a tag and an index in the type table. + * one of the constants defined below, or for OBJECT and UNINITIALIZED + * types, a tag and an index in the type table. * * Output frames can contain types of any kind and with a positive or * negative dimension (and even unassigned types, represented by 0 - which @@ -496,7 +496,7 @@ final class Frame { * When the stack map frames are completely computed, this field is the * actual number of types in {@link #outputStack}. */ - private int outputStackTop; + int outputStackTop; /** * Number of types that are initialized in the basic block. @@ -520,6 +520,110 @@ final class Frame { */ private int[] initializations; + /** + * Sets this frame to the given value. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param nLocal + * the number of local variables. + * @param local + * the local variable types. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are + * represented by String objects (representing internal names), + * and uninitialized types by Label objects (this label + * designates the NEW instruction that created this uninitialized + * value). + * @param nStack + * the number of operand stack elements. + * @param stack + * the operand stack types (same format as the "local" array). + */ + final void set(ClassWriter cw, final int nLocal, final Object[] local, + final int nStack, final Object[] stack) { + int i = convert(cw, nLocal, local, inputLocals); + while (i < local.length) { + inputLocals[i++] = TOP; + } + int nStackTop = 0; + for (int j = 0; j < nStack; ++j) { + if (stack[j] == Opcodes.LONG || stack[j] == Opcodes.DOUBLE) { + ++nStackTop; + } + } + inputStack = new int[nStack + nStackTop]; + convert(cw, nStack, stack, inputStack); + outputStackTop = 0; + initializationCount = 0; + } + + /** + * Converts types from the MethodWriter.visitFrame() format to the Frame + * format. + * + * @param cw + * the ClassWriter to which this label belongs. + * @param nInput + * the number of types to convert. + * @param input + * the types to convert. 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 output + * where to store the converted types. + * @return the number of output elements. + */ + private static int convert(ClassWriter cw, int nInput, Object[] input, + int[] output) { + int i = 0; + for (int j = 0; j < nInput; ++j) { + if (input[j] instanceof Integer) { + output[i++] = BASE | ((Integer) input[j]).intValue(); + if (input[j] == Opcodes.LONG || input[j] == Opcodes.DOUBLE) { + output[i++] = TOP; + } + } else if (input[j] instanceof String) { + output[i++] = type(cw, Type.getObjectType((String) input[j]) + .getDescriptor()); + } else { + output[i++] = UNINITIALIZED + | cw.addUninitializedType("", + ((Label) input[j]).position); + } + } + return i; + } + + /** + * Sets this frame to the value of the given frame. WARNING: after this + * method is called the two frames share the same data structures. It is + * recommended to discard the given frame f to avoid unexpected side + * effects. + * + * @param f + * The new frame value. + */ + final void set(final Frame f) { + inputLocals = f.inputLocals; + inputStack = f.inputStack; + outputLocals = f.outputLocals; + outputStack = f.outputStack; + outputStackTop = f.outputStackTop; + initializationCount = f.initializationCount; + initializations = f.initializations; + } + /** * Returns the output frame local variable type at the given index. * @@ -585,7 +689,7 @@ private void push(final int type) { } // pushes the type on the output stack outputStack[outputStackTop++] = type; - // updates the maximun height reached by the output stack, if needed + // updates the maximum height reached by the output stack, if needed int top = owner.inputStackTop + outputStackTop; if (top > owner.outputStackMax) { owner.outputStackMax = top; @@ -809,7 +913,7 @@ private int init(final ClassWriter cw, final int t) { * @param maxLocals * the maximum number of local variables of this method. */ - void initInputFrame(final ClassWriter cw, final int access, + final void initInputFrame(final ClassWriter cw, final int access, final Type[] args, final int maxLocals) { inputLocals = new int[maxLocals]; inputStack = new int[0]; @@ -1283,7 +1387,7 @@ void execute(final int opcode, final int arg, final ClassWriter cw, * @return true if the input frame of the given label has been * changed by this operation. */ - boolean merge(final ClassWriter cw, final Frame frame, final int edge) { + final boolean merge(final ClassWriter cw, final Frame frame, final int edge) { boolean changed = false; int i, s, dim, kind, t; @@ -1417,6 +1521,7 @@ private static boolean merge(final ClassWriter cw, int t, // if t is the NULL type, merge(u,t)=u, so there is no change return false; } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) { + // if t and u have the same dimension and same base kind if ((u & BASE_KIND) == OBJECT) { // if t is also a reference type, and if u and t have the // same dimension merge(u,t) = dim(t) | common parent of the @@ -1425,13 +1530,21 @@ private static boolean merge(final ClassWriter cw, int t, | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE); } else { // if u and t are array types, but not with the same element - // type, merge(u,t)=java/lang/Object - v = OBJECT | cw.addType("java/lang/Object"); + // type, merge(u,t) = dim(u) - 1 | java/lang/Object + int vdim = ELEMENT_OF + (u & DIM); + v = vdim | OBJECT | cw.addType("java/lang/Object"); } } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) { - // if t is any other reference or array type, - // merge(u,t)=java/lang/Object - v = OBJECT | cw.addType("java/lang/Object"); + // if t is any other reference or array type, the merged type + // is min(udim, tdim) | java/lang/Object, where udim is the + // array dimension of u, minus 1 if u is an array type with a + // primitive element type (and similarly for tdim). + int tdim = (((t & DIM) == 0 || (t & BASE_KIND) == OBJECT) ? 0 + : ELEMENT_OF) + (t & DIM); + int udim = (((u & DIM) == 0 || (u & BASE_KIND) == OBJECT) ? 0 + : ELEMENT_OF) + (u & DIM); + v = Math.min(tdim, udim) | OBJECT + | cw.addType("java/lang/Object"); } else { // if t is any other type, merge(u,t)=TOP v = TOP; diff --git a/src/java/nginx/clojure/asm/Handle.java b/src/java/nginx/clojure/asm/Handle.java index c3ad856b..d92fe3d0 100644 --- a/src/java/nginx/clojure/asm/Handle.java +++ b/src/java/nginx/clojure/asm/Handle.java @@ -63,6 +63,12 @@ public final class Handle { * The descriptor of the field or method designated by this handle. */ final String desc; + + + /** + * Indicate if the owner is an interface or not. + */ + final boolean itf; /** * Constructs a new field or method handle. @@ -84,14 +90,46 @@ public final class Handle { * @param desc * the descriptor of the field or method designated by this * handle. + * + * @deprecated this constructor has been superseded + * by {@link #Handle(int, String, String, String, boolean)}. */ + @Deprecated public Handle(int tag, String owner, String name, String desc) { + this(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE); + } + + /** + * Constructs a new field or method handle. + * + * @param tag + * the kind of field or method designated by this Handle. Must be + * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, + * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, + * {@link Opcodes#H_INVOKEVIRTUAL}, + * {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or + * {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner + * the internal name of the class that owns the field or method + * designated by this handle. + * @param name + * the name of the field or method designated by this handle. + * @param desc + * the descriptor of the field or method designated by this + * handle. + * @param itf + * true if the owner is an interface. + */ + public Handle(int tag, String owner, String name, String desc, boolean itf) { this.tag = tag; this.owner = owner; this.name = name; this.desc = desc; + this.itf = itf; } - + /** * Returns the kind of field or method designated by this handle. * @@ -134,6 +172,17 @@ public String getName() { public String getDesc() { return desc; } + + /** + * Returns true if the owner of the field or method designated + * by this handle is an interface. + * + * @return true if the owner of the field or method designated + * by this handle is an interface. + */ + public boolean isInterface() { + return itf; + } @Override public boolean equals(Object obj) { @@ -144,13 +193,13 @@ public boolean equals(Object obj) { return false; } Handle h = (Handle) obj; - return tag == h.tag && owner.equals(h.owner) && name.equals(h.name) - && desc.equals(h.desc); + return tag == h.tag && itf == h.itf && owner.equals(h.owner) + && name.equals(h.name) && desc.equals(h.desc); } @Override public int hashCode() { - return tag + owner.hashCode() * name.hashCode() * desc.hashCode(); + return tag + (itf? 64: 0) + owner.hashCode() * name.hashCode() * desc.hashCode(); } /** @@ -158,13 +207,16 @@ public int hashCode() { * representation is: * *
+     * for a reference to a class:
      * owner '.' name desc ' ' '(' tag ')'
+     * for a reference to an interface:
+     * owner '.' name desc ' ' '(' tag ' ' itf ')'
      * 
* * . As this format is unambiguous, it can be parsed if necessary. */ @Override public String toString() { - return owner + '.' + name + desc + " (" + tag + ')'; + return owner + '.' + name + desc + " (" + tag + (itf? " itf": "") + ')'; } } diff --git a/src/java/nginx/clojure/asm/Item.java b/src/java/nginx/clojure/asm/Item.java index 316efdc7..bfdb0c4f 100644 --- a/src/java/nginx/clojure/asm/Item.java +++ b/src/java/nginx/clojure/asm/Item.java @@ -201,6 +201,7 @@ void set(final double doubleVal) { * @param strVal3 * third part of the value of this item. */ + @SuppressWarnings("fallthrough") void set(final int type, final String strVal1, final String strVal2, final String strVal3) { this.type = type; @@ -208,9 +209,10 @@ void set(final int type, final String strVal1, final String strVal2, this.strVal2 = strVal2; this.strVal3 = strVal3; switch (type) { + case ClassWriter.CLASS: + this.intVal = 0; // intVal of a class must be zero, see visitInnerClass case ClassWriter.UTF8: case ClassWriter.STR: - case ClassWriter.CLASS: case ClassWriter.MTYPE: case ClassWriter.TYPE_NORMAL: hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); diff --git a/src/java/nginx/clojure/asm/Label.java b/src/java/nginx/clojure/asm/Label.java index c0a366da..fd0b1f96 100644 --- a/src/java/nginx/clojure/asm/Label.java +++ b/src/java/nginx/clojure/asm/Label.java @@ -111,7 +111,7 @@ public class Label { * Field used to associate user information to a label. Warning: this field * is used by the ASM tree package. In order to use it with the ASM tree * package you must override the - * {@link org.objectweb.asm.tree.MethodNode#getLabelNode} method. + * {@link nginx.clojure.asm.tree.MethodNode#getLabelNode} method. */ public Object info; @@ -131,7 +131,11 @@ public class Label { int status; /** - * The line number corresponding to this label, if known. + * The line number corresponding to this label, if known. If there are + * several lines, each line is stored in a separate label, all linked via + * their next field (these links are created in ClassReader and removed just + * before visitLabel is called, so that this does not impact the rest of the + * code). */ int line; @@ -239,7 +243,8 @@ public class Label { * The next basic block in the basic block stack. This stack is used in the * main loop of the fix point algorithm used in the second step of the * control flow analysis algorithms. It is also used in - * {@link #visitSubroutine} to avoid using a recursive method. + * {@link #visitSubroutine} to avoid using a recursive method, and in + * ClassReader to temporarily store multiple source lines for a label. * * @see MethodWriter#visitMaxs */ @@ -359,9 +364,8 @@ private void addReference(final int sourcePosition, * small to store the offset. In such a case the corresponding jump * instruction is replaced with a pseudo instruction (using unused * opcodes) using an unsigned two bytes offset. These pseudo - * instructions will need to be replaced with true instructions with - * wider offsets (4 bytes instead of 2). This is done in - * {@link MethodWriter#resizeInstructions}. + * instructions will be replaced with standard bytecode instructions + * with wider offsets (4 bytes instead of 2), in ClassReader. * @throws IllegalArgumentException * if this label has already been resolved, or if it has not * been created by the given code writer. @@ -473,7 +477,7 @@ boolean inSameSubroutine(final Label block) { void addToSubroutine(final long id, final int nbSubroutines) { if ((status & VISITED) == 0) { status |= VISITED; - srcAndRefPositions = new int[(nbSubroutines - 1) / 32 + 1]; + srcAndRefPositions = new int[nbSubroutines / 32 + 1]; } srcAndRefPositions[(int) (id >>> 32)] |= (int) id; } diff --git a/src/java/nginx/clojure/asm/MethodVisitor.java b/src/java/nginx/clojure/asm/MethodVisitor.java index 6698ecdb..1927b682 100644 --- a/src/java/nginx/clojure/asm/MethodVisitor.java +++ b/src/java/nginx/clojure/asm/MethodVisitor.java @@ -31,18 +31,25 @@ /** * A visitor to visit a Java method. The methods of this class must be called in - * the following order: [ visitAnnotationDefault ] ( - * visitAnnotation | visitParameterAnnotation | + * the following order: ( visitParameter )* [ + * visitAnnotationDefault ] ( visitAnnotation | + * visitParameterAnnotation visitTypeAnnotation | * visitAttribute )* [ visitCode ( visitFrame | - * visitXInsn | visitLabel | - * visitTryCatchBlock | visitLocalVariable | - * visitLineNumber )* visitMaxs ] visitEnd. In - * addition, the visitXInsn and visitLabel methods - * must be called in the sequential order of the bytecode instructions of the - * visited code, visitTryCatchBlock must be called before the - * labels passed as arguments have been visited, and the - * visitLocalVariable and visitLineNumber methods must be - * called after the labels passed as arguments have been visited. + * visitXInsn | visitLabel | + * visitInsnAnnotation | visitTryCatchBlock | + * visitTryCatchAnnotation | visitLocalVariable | + * visitLocalVariableAnnotation | visitLineNumber )* + * visitMaxs ] visitEnd. In addition, the + * visitXInsn and visitLabel methods must be called in + * the sequential order of the bytecode instructions of the visited code, + * visitInsnAnnotation must be called after the annotated + * instruction, visitTryCatchBlock must be called before the + * labels passed as arguments have been visited, + * visitTryCatchBlockAnnotation must be called after the + * corresponding try catch block has been visited, and the + * visitLocalVariable, visitLocalVariableAnnotation and + * visitLineNumber methods must be called after the labels + * passed as arguments have been visited. * * @author Eric Bruneton */ @@ -50,7 +57,7 @@ public abstract class MethodVisitor { /** * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ protected final int api; @@ -65,7 +72,7 @@ public abstract class MethodVisitor { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ public MethodVisitor(final int api) { this(api, null); @@ -76,13 +83,13 @@ public MethodVisitor(final int api) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param mv * the method visitor to which this visitor must delegate method * calls. May be null. */ public MethodVisitor(final int api, final MethodVisitor mv) { - if (api != Opcodes.ASM4) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { throw new IllegalArgumentException(); } this.api = api; @@ -90,9 +97,28 @@ public MethodVisitor(final int api, final MethodVisitor mv) { } // ------------------------------------------------------------------------- - // Annotations and non standard attributes + // Parameters, annotations and non standard attributes // ------------------------------------------------------------------------- + /** + * Visits a parameter of this method. + * + * @param name + * parameter name or null if none is provided. + * @param access + * the parameter's access flags, only ACC_FINAL, + * ACC_SYNTHETIC or/and ACC_MANDATED are + * allowed (see {@link Opcodes}). + */ + public void visitParameter(String name, int access) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + mv.visitParameter(name, access); + } + } + /** * Visits the default value of this annotation interface method. * @@ -127,6 +153,42 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return null; } + /** + * Visits an annotation on a type in the method signature. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#METHOD_TYPE_PARAMETER + * METHOD_TYPE_PARAMETER}, + * {@link TypeReference#METHOD_TYPE_PARAMETER_BOUND + * METHOD_TYPE_PARAMETER_BOUND}, + * {@link TypeReference#METHOD_RETURN METHOD_RETURN}, + * {@link TypeReference#METHOD_RECEIVER METHOD_RECEIVER}, + * {@link TypeReference#METHOD_FORMAL_PARAMETER + * METHOD_FORMAL_PARAMETER} or {@link TypeReference#THROWS + * THROWS}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + return mv.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + /** * Visits an annotation of a parameter this method. * @@ -201,9 +263,11 @@ public void visitCode() { *
  • {@link Opcodes#F_CHOP} representing frame with current locals are the * same as the locals in the previous frame, except that the last 1-3 locals * are absent and with the empty stack (nLocals is 1, 2 or 3).
  • - *
  • {@link Opcodes#F_FULL} representing complete frame data.
  • + *
  • {@link Opcodes#F_FULL} representing complete frame data.
  • + * + * * - *
    + *
    * In both cases the first frame, corresponding to the method's parameters * and access flags, is implicit and must not be visited. Also, it is * illegal to visit two or more frames for the same code location (i.e., at @@ -376,13 +440,52 @@ public void visitFieldInsn(int opcode, String owner, String name, * @param desc * the method's descriptor (see {@link Type Type}). */ + @Deprecated public void visitMethodInsn(int opcode, String owner, String name, String desc) { + if (api >= Opcodes.ASM5) { + boolean itf = opcode == Opcodes.INVOKEINTERFACE; + visitMethodInsn(opcode, owner, name, desc, itf); + return; + } if (mv != null) { mv.visitMethodInsn(opcode, owner, name, desc); } } + /** + * Visits a method instruction. A method instruction is an instruction that + * invokes a method. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link Type Type}). + * @param itf + * if the method's owner class is an interface. + */ + public void visitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + if (api < Opcodes.ASM5) { + if (itf != (opcode == Opcodes.INVOKEINTERFACE)) { + throw new IllegalArgumentException( + "INVOKESPECIAL/STATIC on interfaces require ASM 5"); + } + visitMethodInsn(opcode, owner, name, desc); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + /** * Visits an invokedynamic instruction. * @@ -558,6 +661,48 @@ public void visitMultiANewArrayInsn(String desc, int dims) { } } + /** + * Visits an annotation on an instruction. This method must be called just + * after the annotated instruction. It can be called several times + * for the same instruction. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#INSTANCEOF INSTANCEOF}, + * {@link TypeReference#NEW NEW}, + * {@link TypeReference#CONSTRUCTOR_REFERENCE + * CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE + * METHOD_REFERENCE}, {@link TypeReference#CAST CAST}, + * {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + return mv.visitInsnAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + // ------------------------------------------------------------------------- // Exceptions table entries, debug information, max stack and max locals // ------------------------------------------------------------------------- @@ -586,6 +731,38 @@ public void visitTryCatchBlock(Label start, Label end, Label handler, } } + /** + * Visits an annotation on an exception handler type. This method must be + * called after the {@link #visitTryCatchBlock} for the annotated + * exception handler. It can be called several times for the same exception + * handler. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#EXCEPTION_PARAMETER + * EXCEPTION_PARAMETER}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible); + } + return null; + } + /** * Visits a local variable declaration. * @@ -616,6 +793,48 @@ public void visitLocalVariable(String name, String desc, String signature, } } + /** + * Visits an annotation on a local variable type. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link TypeReference#LOCAL_VARIABLE + * LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE + * RESOURCE_VARIABLE}. See {@link TypeReference}. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param start + * the fist instructions corresponding to the continuous ranges + * that make the scope of this local variable (inclusive). + * @param end + * the last instructions corresponding to the continuous ranges + * that make the scope of this local variable (exclusive). This + * array must have the same size as the 'start' array. + * @param index + * the local variable's index in each range. This array must have + * the same size as the 'start' array. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or null if + * this visitor is not interested in visiting this annotation. + */ + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + if (api < Opcodes.ASM5) { + throw new RuntimeException(); + } + if (mv != null) { + return mv.visitLocalVariableAnnotation(typeRef, typePath, start, + end, index, desc, visible); + } + return null; + } + /** * Visits a line number declaration. * diff --git a/src/java/nginx/clojure/asm/MethodWriter.java b/src/java/nginx/clojure/asm/MethodWriter.java index f02e2fdb..3fe7ce46 100644 --- a/src/java/nginx/clojure/asm/MethodWriter.java +++ b/src/java/nginx/clojure/asm/MethodWriter.java @@ -43,7 +43,7 @@ class MethodWriter extends MethodVisitor { * Pseudo access flag used to denote constructors. */ static final int ACC_CONSTRUCTOR = 0x80000; - + /** * Frame has exactly the same locals as the previous stack map frame and * number of stack items is zero. @@ -99,7 +99,19 @@ class MethodWriter extends MethodVisitor { * * @see #compute */ - private static final int FRAMES = 0; + static final int FRAMES = 0; + + /** + * Indicates that the stack map frames of type F_INSERT must be computed. + * The other frames are not (re)computed. They should all be of type F_NEW + * and should be sufficient to compute the content of the F_INSERT frames, + * together with the bytecode instructions between a F_NEW and a F_INSERT + * frame - and without any knowledge of the type hierarchy (by definition of + * F_INSERT). + * + * @see #compute + */ + static final int INSERTED_FRAMES = 1; /** * Indicates that the maximum stack size and number of local variables must @@ -107,14 +119,14 @@ class MethodWriter extends MethodVisitor { * * @see #compute */ - private static final int MAXS = 1; + static final int MAXS = 2; /** * Indicates that nothing must be automatically computed. * * @see #compute */ - private static final int NOTHING = 2; + static final int NOTHING = 3; /** * The class writer to which this method must be added. @@ -191,6 +203,18 @@ class MethodWriter extends MethodVisitor { */ private AnnotationWriter ianns; + /** + * The runtime visible type annotations of this method. May be null + * . + */ + private AnnotationWriter tanns; + + /** + * The runtime invisible type annotations of this method. May be + * null. + */ + private AnnotationWriter itanns; + /** * The runtime visible parameter annotations of this method. May be * null. @@ -282,6 +306,16 @@ class MethodWriter extends MethodVisitor { */ private Handler lastHandler; + /** + * Number of entries in the MethodParameters attribute. + */ + private int methodParametersCount; + + /** + * The MethodParameters attribute. + */ + private ByteVector methodParameters; + /** * Number of entries in the LocalVariableTable attribute. */ @@ -313,14 +347,24 @@ class MethodWriter extends MethodVisitor { private ByteVector lineNumber; /** - * The non standard attributes of the method's code. + * The start offset of the last visited instruction. */ - private Attribute cattrs; + private int lastCodeOffset; /** - * Indicates if some jump instructions are too small and need to be resized. + * The runtime visible type annotations of the code. May be null. */ - private boolean resize; + private AnnotationWriter ctanns; + + /** + * The runtime invisible type annotations of the code. May be null. + */ + private AnnotationWriter ictanns; + + /** + * The non standard attributes of the method's code. + */ + private Attribute cattrs; /** * The number of subroutines in this method. @@ -343,6 +387,7 @@ class MethodWriter extends MethodVisitor { * Indicates what must be automatically computed. * * @see #FRAMES + * @see #INSERTED_FRAMES * @see #MAXS * @see #NOTHING */ @@ -405,18 +450,13 @@ class MethodWriter extends MethodVisitor { * @param exceptions * the internal names of the method's exceptions. May be * null. - * @param computeMaxs - * true if the maximum stack size and number of local - * variables must be automatically computed. - * @param computeFrames - * true if the stack map tables must be recomputed from - * scratch. + * @param compute + * Indicates what must be automatically computed (see #compute). */ MethodWriter(final ClassWriter cw, final int access, final String name, final String desc, final String signature, - final String[] exceptions, final boolean computeMaxs, - final boolean computeFrames) { - super(Opcodes.ASM4); + final String[] exceptions, final int compute) { + super(Opcodes.ASM5); if (cw.firstMethod == null) { cw.firstMethod = this; } else { @@ -441,8 +481,8 @@ class MethodWriter extends MethodVisitor { this.exceptions[i] = cw.newClass(exceptions[i]); } } - this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING); - if (computeMaxs || computeFrames) { + this.compute = compute; + if (compute != NOTHING) { // updates maxLocals int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2; if ((access & Opcodes.ACC_STATIC) != 0) { @@ -461,6 +501,16 @@ class MethodWriter extends MethodVisitor { // Implementation of the MethodVisitor abstract class // ------------------------------------------------------------------------ + @Override + public void visitParameter(String name, int access) { + if (methodParameters == null) { + methodParameters = new ByteVector(); + } + ++methodParametersCount; + methodParameters.putShort((name == null) ? 0 : cw.newUTF8(name)) + .putShort(access); + } + @Override public AnnotationVisitor visitAnnotationDefault() { if (!ClassReader.ANNOTATIONS) { @@ -490,6 +540,29 @@ public AnnotationVisitor visitAnnotation(final String desc, return aw; } + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = tanns; + tanns = aw; + } else { + aw.next = itanns; + itanns = aw; + } + return aw; + } + @Override public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { @@ -544,7 +617,29 @@ public void visitFrame(final int type, final int nLocal, return; } - if (type == Opcodes.F_NEW) { + if (compute == INSERTED_FRAMES) { + if (currentBlock.frame == null) { + // This should happen only once, for the implicit first frame + // (which is explicitly visited in ClassReader if the + // EXPAND_ASM_INSNS option is used). + currentBlock.frame = new CurrentFrame(); + currentBlock.frame.owner = currentBlock; + currentBlock.frame.initInputFrame(cw, access, + Type.getArgumentTypes(descriptor), nLocal); + visitImplicitFirstFrame(); + } else { + if (type == Opcodes.F_NEW) { + currentBlock.frame.set(cw, nLocal, local, nStack, stack); + } else { + // In this case type is equal to F_INSERT by hypothesis, and + // currentBlock.frame contains the stack map frame at the + // current instruction, computed from the last F_NEW frame + // and the bytecode instructions in between (via calls to + // CurrentFrame#execute). + } + visitFrame(currentBlock.frame); + } + } else if (type == Opcodes.F_NEW) { if (previousFrame == null) { visitImplicitFirstFrame(); } @@ -642,12 +737,13 @@ public void visitFrame(final int type, final int nLocal, @Override public void visitInsn(final int opcode) { + lastCodeOffset = code.length; // adds the instruction to the bytecode of the method code.putByte(opcode); // update currentBlock // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, 0, null, null); } else { // updates current and max stack sizes @@ -667,9 +763,10 @@ public void visitInsn(final int opcode) { @Override public void visitIntInsn(final int opcode, final int operand) { + lastCodeOffset = code.length; // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, operand, null, null); } else if (opcode != Opcodes.NEWARRAY) { // updates current and max stack sizes only for NEWARRAY @@ -691,9 +788,10 @@ public void visitIntInsn(final int opcode, final int operand) { @Override public void visitVarInsn(final int opcode, final int var) { + lastCodeOffset = code.length; // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, var, null, null); } else { // updates current and max stack sizes @@ -749,10 +847,11 @@ public void visitVarInsn(final int opcode, final int var) { @Override public void visitTypeInsn(final int opcode, final String type) { + lastCodeOffset = code.length; Item i = cw.newClassItem(type); // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, code.length, cw, i); } else if (opcode == Opcodes.NEW) { // updates current and max stack sizes only if opcode == NEW @@ -771,10 +870,11 @@ public void visitTypeInsn(final int opcode, final String type) { @Override public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { + lastCodeOffset = code.length; Item i = cw.newFieldItem(owner, name, desc); // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, 0, cw, i); } else { int size; @@ -808,13 +908,13 @@ public void visitFieldInsn(final int opcode, final String owner, @Override public void visitMethodInsn(final int opcode, final String owner, - final String name, final String desc) { - boolean itf = opcode == Opcodes.INVOKEINTERFACE; + final String name, final String desc, final boolean itf) { + lastCodeOffset = code.length; Item i = cw.newMethodItem(owner, name, desc, itf); int argSize = i.intVal; // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(opcode, 0, cw, i); } else { /* @@ -847,7 +947,7 @@ public void visitMethodInsn(final int opcode, final String owner, } } // adds the instruction to the bytecode of the method - if (itf) { + if (opcode == Opcodes.INVOKEINTERFACE) { if (argSize == 0) { argSize = Type.getArgumentsAndReturnSizes(desc); i.intVal = argSize; @@ -861,11 +961,12 @@ public void visitMethodInsn(final int opcode, final String owner, @Override public void visitInvokeDynamicInsn(final String name, final String desc, final Handle bsm, final Object... bsmArgs) { + lastCodeOffset = code.length; Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs); int argSize = i.intVal; // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i); } else { /* @@ -899,7 +1000,10 @@ public void visitInvokeDynamicInsn(final String name, final String desc, } @Override - public void visitJumpInsn(final int opcode, final Label label) { + public void visitJumpInsn(int opcode, final Label label) { + boolean isWide = opcode >= 200; // GOTO_W + opcode = isWide ? opcode - 33 : opcode; + lastCodeOffset = code.length; Label nextInsn = null; // Label currentBlock = this.currentBlock; if (currentBlock != null) { @@ -913,6 +1017,8 @@ public void visitJumpInsn(final int opcode, final Label label) { // creates a Label for the next basic block nextInsn = new Label(); } + } else if (compute == INSERTED_FRAMES) { + currentBlock.frame.execute(opcode, 0, null, null); } else { if (opcode == Opcodes.JSR) { if ((label.status & Label.SUBROUTINE) == 0) { @@ -964,6 +1070,14 @@ public void visitJumpInsn(final int opcode, final Label label) { code.putByte(200); // GOTO_W } label.put(this, code, code.length - 1, true); + } else if (isWide) { + /* + * case of a GOTO_W or JSR_W specified by the user (normally + * ClassReader when used to resize instructions). In this case we + * keep the original instruction. + */ + code.putByte(opcode + 33); + label.put(this, code, code.length - 1, true); } else { /* * case of a backward jump with an offset >= -32768, or of a forward @@ -991,7 +1105,7 @@ public void visitJumpInsn(final int opcode, final Label label) { @Override public void visitLabel(final Label label) { // resolves previous forward references to label, if any - resize |= label.resolve(this, code.length, code.data); + cw.hasAsmInsns |= label.resolve(this, code.length, code.data); // updates currentBlock if ((label.status & Label.DEBUG) != 0) { return; @@ -1024,6 +1138,18 @@ public void visitLabel(final Label label) { previousBlock.successor = label; } previousBlock = label; + } else if (compute == INSERTED_FRAMES) { + if (currentBlock == null) { + // This case should happen only once, for the visitLabel call in + // the constructor. Indeed, if compute is equal to + // INSERTED_FRAMES currentBlock can not be set back to null (see + // #noSuccessor). + currentBlock = label; + } else { + // Updates the frame owner so that a correct frame offset is + // computed in visitFrame(Frame). + currentBlock.frame.owner = label; + } } else if (compute == MAXS) { if (currentBlock != null) { // ends current block (with one new successor) @@ -1045,10 +1171,11 @@ public void visitLabel(final Label label) { @Override public void visitLdcInsn(final Object cst) { + lastCodeOffset = code.length; Item i = cw.newConstItem(cst); // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(Opcodes.LDC, 0, cw, i); } else { int size; @@ -1078,8 +1205,9 @@ public void visitLdcInsn(final Object cst) { @Override public void visitIincInsn(final int var, final int increment) { + lastCodeOffset = code.length; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(Opcodes.IINC, var, null, null); } } @@ -1102,6 +1230,7 @@ public void visitIincInsn(final int var, final int increment) { @Override public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) { + lastCodeOffset = code.length; // adds the instruction to the bytecode of the method int source = code.length; code.putByte(Opcodes.TABLESWITCH); @@ -1118,6 +1247,7 @@ public void visitTableSwitchInsn(final int min, final int max, @Override public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + lastCodeOffset = code.length; // adds the instruction to the bytecode of the method int source = code.length; code.putByte(Opcodes.LOOKUPSWITCH); @@ -1160,10 +1290,11 @@ private void visitSwitchInsn(final Label dflt, final Label[] labels) { @Override public void visitMultiANewArrayInsn(final String desc, final int dims) { + lastCodeOffset = code.length; Item i = cw.newClassItem(desc); // Label currentBlock = this.currentBlock; if (currentBlock != null) { - if (compute == FRAMES) { + if (compute == FRAMES || compute == INSERTED_FRAMES) { currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i); } else { // updates current stack size (max stack size unchanged because @@ -1175,6 +1306,30 @@ public void visitMultiANewArrayInsn(final String desc, final int dims) { code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims); } + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + typeRef = (typeRef & 0xFF0000FF) | (lastCodeOffset << 8); + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + @Override public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { @@ -1193,6 +1348,29 @@ public void visitTryCatchBlock(final Label start, final Label end, lastHandler = h; } + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + AnnotationWriter.putTarget(typeRef, typePath, bv); + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + @Override public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, @@ -1225,6 +1403,41 @@ public void visitLocalVariable(final String name, final String desc, } } + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + if (!ClassReader.ANNOTATIONS) { + return null; + } + ByteVector bv = new ByteVector(); + // write target_type and target_info + bv.putByte(typeRef >>> 24).putShort(start.length); + for (int i = 0; i < start.length; ++i) { + bv.putShort(start[i].position) + .putShort(end[i].position - start[i].position) + .putShort(index[i]); + } + if (typePath == null) { + bv.putByte(0); + } else { + int length = typePath.b[typePath.offset] * 2 + 1; + bv.putByteArray(typePath.b, typePath.offset, length); + } + // write type, and reserve space for values count + bv.putShort(cw.newUTF8(desc)).putShort(0); + AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, + bv.length - 2); + if (visible) { + aw.next = ctanns; + ctanns = aw; + } else { + aw.next = ictanns; + ictanns = aw; + } + return aw; + } + @Override public void visitLineNumber(final int line, final Label start) { if (lineNumber == null) { @@ -1267,8 +1480,8 @@ public void visitMaxs(final int maxStack, final int maxLocals) { // creates and visits the first (implicit) frame Frame f = labels.frame; - Type[] args = Type.getArgumentTypes(descriptor); - f.initInputFrame(cw, access, args, this.maxLocals); + f.initInputFrame(cw, access, Type.getArgumentTypes(descriptor), + this.maxLocals); visitFrame(f); /* @@ -1516,7 +1729,9 @@ private void noSuccessor() { } else { currentBlock.outputStackMax = maxStackSize; } - currentBlock = null; + if (compute != INSERTED_FRAMES) { + currentBlock = null; + } } // ------------------------------------------------------------------------ @@ -1794,43 +2009,43 @@ private void writeFrameTypes(final int start, final int end) { stackMap.putByte(v); } } else { - StringBuffer buf = new StringBuffer(); + StringBuilder sb = new StringBuilder(); d >>= 28; while (d-- > 0) { - buf.append('['); + sb.append('['); } if ((t & Frame.BASE_KIND) == Frame.OBJECT) { - buf.append('L'); - buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); - buf.append(';'); + sb.append('L'); + sb.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1); + sb.append(';'); } else { switch (t & 0xF) { case 1: - buf.append('I'); + sb.append('I'); break; case 2: - buf.append('F'); + sb.append('F'); break; case 3: - buf.append('D'); + sb.append('D'); break; case 9: - buf.append('Z'); + sb.append('Z'); break; case 10: - buf.append('B'); + sb.append('B'); break; case 11: - buf.append('C'); + sb.append('C'); break; case 12: - buf.append('S'); + sb.append('S'); break; default: - buf.append('J'); + sb.append('J'); } } - stackMap.putByte(7).putShort(cw.newClass(buf.toString())); + stackMap.putByte(7).putShort(cw.newClass(sb.toString())); } } } @@ -1858,17 +2073,9 @@ final int getSize() { if (classReaderOffset != 0) { return 6 + classReaderLength; } - if (resize) { - // replaces the temporary jump opcodes introduced by Label.resolve. - if (ClassReader.RESIZE) { - resizeInstructions(); - } else { - throw new RuntimeException("Method code too large!"); - } - } int size = 8; if (code.length > 0) { - if (code.length > 65536) { + if (code.length > 65535) { throw new RuntimeException("Method code too large!"); } cw.newUTF8("Code"); @@ -1890,6 +2097,14 @@ final int getSize() { cw.newUTF8(zip ? "StackMapTable" : "StackMap"); size += 8 + stackMap.length; } + if (ClassReader.ANNOTATIONS && ctanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + ctanns.getSize(); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + ictanns.getSize(); + } if (cattrs != null) { size += cattrs.getSize(cw, code.data, code.length, maxStack, maxLocals); @@ -1915,6 +2130,10 @@ final int getSize() { cw.newUTF8(signature); size += 8; } + if (methodParameters != null) { + cw.newUTF8("MethodParameters"); + size += 7 + methodParameters.length; + } if (ClassReader.ANNOTATIONS && annd != null) { cw.newUTF8("AnnotationDefault"); size += 6 + annd.length; @@ -1927,6 +2146,14 @@ final int getSize() { cw.newUTF8("RuntimeInvisibleAnnotations"); size += 8 + ianns.getSize(); } + if (ClassReader.ANNOTATIONS && tanns != null) { + cw.newUTF8("RuntimeVisibleTypeAnnotations"); + size += 8 + tanns.getSize(); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + cw.newUTF8("RuntimeInvisibleTypeAnnotations"); + size += 8 + itanns.getSize(); + } if (ClassReader.ANNOTATIONS && panns != null) { cw.newUTF8("RuntimeVisibleParameterAnnotations"); size += 7 + 2 * (panns.length - synthetics); @@ -1983,6 +2210,9 @@ final void put(final ByteVector out) { if (ClassReader.SIGNATURES && signature != null) { ++attributeCount; } + if (methodParameters != null) { + ++attributeCount; + } if (ClassReader.ANNOTATIONS && annd != null) { ++attributeCount; } @@ -1992,6 +2222,12 @@ final void put(final ByteVector out) { if (ClassReader.ANNOTATIONS && ianns != null) { ++attributeCount; } + if (ClassReader.ANNOTATIONS && tanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && itanns != null) { + ++attributeCount; + } if (ClassReader.ANNOTATIONS && panns != null) { ++attributeCount; } @@ -2016,6 +2252,12 @@ final void put(final ByteVector out) { if (stackMap != null) { size += 8 + stackMap.length; } + if (ClassReader.ANNOTATIONS && ctanns != null) { + size += 8 + ctanns.getSize(); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + size += 8 + ictanns.getSize(); + } if (cattrs != null) { size += cattrs.getSize(cw, code.data, code.length, maxStack, maxLocals); @@ -2045,6 +2287,12 @@ final void put(final ByteVector out) { if (stackMap != null) { ++attributeCount; } + if (ClassReader.ANNOTATIONS && ctanns != null) { + ++attributeCount; + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + ++attributeCount; + } if (cattrs != null) { attributeCount += cattrs.getCount(); } @@ -2070,6 +2318,14 @@ final void put(final ByteVector out) { out.putInt(stackMap.length + 2).putShort(frameCount); out.putByteArray(stackMap.data, 0, stackMap.length); } + if (ClassReader.ANNOTATIONS && ctanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + ctanns.put(out); + } + if (ClassReader.ANNOTATIONS && ictanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + ictanns.put(out); + } if (cattrs != null) { cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out); } @@ -2095,6 +2351,12 @@ final void put(final ByteVector out) { out.putShort(cw.newUTF8("Signature")).putInt(2) .putShort(cw.newUTF8(signature)); } + if (methodParameters != null) { + out.putShort(cw.newUTF8("MethodParameters")); + out.putInt(methodParameters.length + 1).putByte( + methodParametersCount); + out.putByteArray(methodParameters.data, 0, methodParameters.length); + } if (ClassReader.ANNOTATIONS && annd != null) { out.putShort(cw.newUTF8("AnnotationDefault")); out.putInt(annd.length); @@ -2108,6 +2370,14 @@ final void put(final ByteVector out) { out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations")); ianns.put(out); } + if (ClassReader.ANNOTATIONS && tanns != null) { + out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations")); + tanns.put(out); + } + if (ClassReader.ANNOTATIONS && itanns != null) { + out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations")); + itanns.put(out); + } if (ClassReader.ANNOTATIONS && panns != null) { out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations")); AnnotationWriter.put(panns, synthetics, out); @@ -2120,566 +2390,4 @@ final void put(final ByteVector out) { attrs.put(cw, null, 0, -1, -1, out); } } - - // ------------------------------------------------------------------------ - // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W) - // ------------------------------------------------------------------------ - - /** - * Resizes and replaces the temporary instructions inserted by - * {@link Label#resolve} for wide forward jumps, while keeping jump offsets - * and instruction addresses consistent. This may require to resize other - * existing instructions, or even to introduce new instructions: for - * example, increasing the size of an instruction by 2 at the middle of a - * method can increases the offset of an IFEQ instruction from 32766 to - * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W - * 32765. This, in turn, may require to increase the size of another jump - * instruction, and so on... All these operations are handled automatically - * by this method. - *

    - * This method must be called after all the method that is being built - * has been visited. In particular, the {@link Label Label} objects used - * to construct the method are no longer valid after this method has been - * called. - */ - private void resizeInstructions() { - byte[] b = code.data; // bytecode of the method - int u, v, label; // indexes in b - int i, j; // loop indexes - /* - * 1st step: As explained above, resizing an instruction may require to - * resize another one, which may require to resize yet another one, and - * so on. The first step of the algorithm consists in finding all the - * instructions that need to be resized, without modifying the code. - * This is done by the following "fix point" algorithm: - * - * Parse the code to find the jump instructions whose offset will need - * more than 2 bytes to be stored (the future offset is computed from - * the current offset and from the number of bytes that will be inserted - * or removed between the source and target instructions). For each such - * instruction, adds an entry in (a copy of) the indexes and sizes - * arrays (if this has not already been done in a previous iteration!). - * - * If at least one entry has been added during the previous step, go - * back to the beginning, otherwise stop. - * - * In fact the real algorithm is complicated by the fact that the size - * of TABLESWITCH and LOOKUPSWITCH instructions depends on their - * position in the bytecode (because of padding). In order to ensure the - * convergence of the algorithm, the number of bytes to be added or - * removed from these instructions is over estimated during the previous - * loop, and computed exactly only after the loop is finished (this - * requires another pass to parse the bytecode of the method). - */ - int[] allIndexes = new int[0]; // copy of indexes - int[] allSizes = new int[0]; // copy of sizes - boolean[] resize; // instructions to be resized - int newOffset; // future offset of a jump instruction - - resize = new boolean[code.length]; - - // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done - int state = 3; - do { - if (state == 3) { - state = 2; - } - u = 0; - while (u < b.length) { - int opcode = b[u] & 0xFF; // opcode of current instruction - int insert = 0; // bytes to be added after this instruction - - switch (ClassWriter.TYPE[opcode]) { - case ClassWriter.NOARG_INSN: - case ClassWriter.IMPLVAR_INSN: - u += 1; - break; - case ClassWriter.LABEL_INSN: - if (opcode > 201) { - // converts temporary opcodes 202 to 217, 218 and - // 219 to IFEQ ... JSR (inclusive), IFNULL and - // IFNONNULL - opcode = opcode < 218 ? opcode - 49 : opcode - 20; - label = u + readUnsignedShort(b, u + 1); - } else { - label = u + readShort(b, u + 1); - } - newOffset = getNewOffset(allIndexes, allSizes, u, label); - if (newOffset < Short.MIN_VALUE - || newOffset > Short.MAX_VALUE) { - if (!resize[u]) { - if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { - // two additional bytes will be required to - // replace this GOTO or JSR instruction with - // a GOTO_W or a JSR_W - insert = 2; - } else { - // five additional bytes will be required to - // replace this IFxxx instruction with - // IFNOTxxx GOTO_W , where IFNOTxxx - // is the "opposite" opcode of IFxxx (i.e., - // IFNE for IFEQ) and where designates - // the instruction just after the GOTO_W. - insert = 5; - } - resize[u] = true; - } - } - u += 3; - break; - case ClassWriter.LABELW_INSN: - u += 5; - break; - case ClassWriter.TABL_INSN: - if (state == 1) { - // true number of bytes to be added (or removed) - // from this instruction = (future number of padding - // bytes - current number of padding byte) - - // previously over estimated variation = - // = ((3 - newOffset%4) - (3 - u%4)) - u%4 - // = (-newOffset%4 + u%4) - u%4 - // = -(newOffset & 3) - newOffset = getNewOffset(allIndexes, allSizes, 0, u); - insert = -(newOffset & 3); - } else if (!resize[u]) { - // over estimation of the number of bytes to be - // added to this instruction = 3 - current number - // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3 - insert = u & 3; - resize[u] = true; - } - // skips instruction - u = u + 4 - (u & 3); - u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12; - break; - case ClassWriter.LOOK_INSN: - if (state == 1) { - // like TABL_INSN - newOffset = getNewOffset(allIndexes, allSizes, 0, u); - insert = -(newOffset & 3); - } else if (!resize[u]) { - // like TABL_INSN - insert = u & 3; - resize[u] = true; - } - // skips instruction - u = u + 4 - (u & 3); - u += 8 * readInt(b, u + 4) + 8; - break; - case ClassWriter.WIDE_INSN: - opcode = b[u + 1] & 0xFF; - if (opcode == Opcodes.IINC) { - u += 6; - } else { - u += 4; - } - break; - case ClassWriter.VAR_INSN: - case ClassWriter.SBYTE_INSN: - case ClassWriter.LDC_INSN: - u += 2; - break; - case ClassWriter.SHORT_INSN: - case ClassWriter.LDCW_INSN: - case ClassWriter.FIELDORMETH_INSN: - case ClassWriter.TYPE_INSN: - case ClassWriter.IINC_INSN: - u += 3; - break; - case ClassWriter.ITFMETH_INSN: - case ClassWriter.INDYMETH_INSN: - u += 5; - break; - // case ClassWriter.MANA_INSN: - default: - u += 4; - break; - } - if (insert != 0) { - // adds a new (u, insert) entry in the allIndexes and - // allSizes arrays - int[] newIndexes = new int[allIndexes.length + 1]; - int[] newSizes = new int[allSizes.length + 1]; - System.arraycopy(allIndexes, 0, newIndexes, 0, - allIndexes.length); - System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length); - newIndexes[allIndexes.length] = u; - newSizes[allSizes.length] = insert; - allIndexes = newIndexes; - allSizes = newSizes; - if (insert > 0) { - state = 3; - } - } - } - if (state < 3) { - --state; - } - } while (state != 0); - - // 2nd step: - // copies the bytecode of the method into a new bytevector, updates the - // offsets, and inserts (or removes) bytes as requested. - - ByteVector newCode = new ByteVector(code.length); - - u = 0; - while (u < code.length) { - int opcode = b[u] & 0xFF; - switch (ClassWriter.TYPE[opcode]) { - case ClassWriter.NOARG_INSN: - case ClassWriter.IMPLVAR_INSN: - newCode.putByte(opcode); - u += 1; - break; - case ClassWriter.LABEL_INSN: - if (opcode > 201) { - // changes temporary opcodes 202 to 217 (inclusive), 218 - // and 219 to IFEQ ... JSR (inclusive), IFNULL and - // IFNONNULL - opcode = opcode < 218 ? opcode - 49 : opcode - 20; - label = u + readUnsignedShort(b, u + 1); - } else { - label = u + readShort(b, u + 1); - } - newOffset = getNewOffset(allIndexes, allSizes, u, label); - if (resize[u]) { - // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx - // with IFNOTxxx GOTO_W , where IFNOTxxx is - // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) - // and where designates the instruction just after - // the GOTO_W. - if (opcode == Opcodes.GOTO) { - newCode.putByte(200); // GOTO_W - } else if (opcode == Opcodes.JSR) { - newCode.putByte(201); // JSR_W - } else { - newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1 - : opcode ^ 1); - newCode.putShort(8); // jump offset - newCode.putByte(200); // GOTO_W - // newOffset now computed from start of GOTO_W - newOffset -= 3; - } - newCode.putInt(newOffset); - } else { - newCode.putByte(opcode); - newCode.putShort(newOffset); - } - u += 3; - break; - case ClassWriter.LABELW_INSN: - label = u + readInt(b, u + 1); - newOffset = getNewOffset(allIndexes, allSizes, u, label); - newCode.putByte(opcode); - newCode.putInt(newOffset); - u += 5; - break; - case ClassWriter.TABL_INSN: - // skips 0 to 3 padding bytes - v = u; - u = u + 4 - (v & 3); - // reads and copies instruction - newCode.putByte(Opcodes.TABLESWITCH); - newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - j = readInt(b, u); - u += 4; - newCode.putInt(j); - j = readInt(b, u) - j + 1; - u += 4; - newCode.putInt(readInt(b, u - 4)); - for (; j > 0; --j) { - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - } - break; - case ClassWriter.LOOK_INSN: - // skips 0 to 3 padding bytes - v = u; - u = u + 4 - (v & 3); - // reads and copies instruction - newCode.putByte(Opcodes.LOOKUPSWITCH); - newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4); - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - j = readInt(b, u); - u += 4; - newCode.putInt(j); - for (; j > 0; --j) { - newCode.putInt(readInt(b, u)); - u += 4; - label = v + readInt(b, u); - u += 4; - newOffset = getNewOffset(allIndexes, allSizes, v, label); - newCode.putInt(newOffset); - } - break; - case ClassWriter.WIDE_INSN: - opcode = b[u + 1] & 0xFF; - if (opcode == Opcodes.IINC) { - newCode.putByteArray(b, u, 6); - u += 6; - } else { - newCode.putByteArray(b, u, 4); - u += 4; - } - break; - case ClassWriter.VAR_INSN: - case ClassWriter.SBYTE_INSN: - case ClassWriter.LDC_INSN: - newCode.putByteArray(b, u, 2); - u += 2; - break; - case ClassWriter.SHORT_INSN: - case ClassWriter.LDCW_INSN: - case ClassWriter.FIELDORMETH_INSN: - case ClassWriter.TYPE_INSN: - case ClassWriter.IINC_INSN: - newCode.putByteArray(b, u, 3); - u += 3; - break; - case ClassWriter.ITFMETH_INSN: - case ClassWriter.INDYMETH_INSN: - newCode.putByteArray(b, u, 5); - u += 5; - break; - // case MANA_INSN: - default: - newCode.putByteArray(b, u, 4); - u += 4; - break; - } - } - - // recomputes the stack map frames - if (frameCount > 0) { - if (compute == FRAMES) { - frameCount = 0; - stackMap = null; - previousFrame = null; - frame = null; - Frame f = new Frame(); - f.owner = labels; - Type[] args = Type.getArgumentTypes(descriptor); - f.initInputFrame(cw, access, args, maxLocals); - visitFrame(f); - Label l = labels; - while (l != null) { - /* - * here we need the original label position. getNewOffset - * must therefore never have been called for this label. - */ - u = l.position - 3; - if ((l.status & Label.STORE) != 0 || (u >= 0 && resize[u])) { - getNewOffset(allIndexes, allSizes, l); - // TODO update offsets in UNINITIALIZED values - visitFrame(l.frame); - } - l = l.successor; - } - } else { - /* - * Resizing an existing stack map frame table is really hard. - * Not only the table must be parsed to update the offets, but - * new frames may be needed for jump instructions that were - * inserted by this method. And updating the offsets or - * inserting frames can change the format of the following - * frames, in case of packed frames. In practice the whole table - * must be recomputed. For this the frames are marked as - * potentially invalid. This will cause the whole class to be - * reread and rewritten with the COMPUTE_FRAMES option (see the - * ClassWriter.toByteArray method). This is not very efficient - * but is much easier and requires much less code than any other - * method I can think of. - */ - cw.invalidFrames = true; - } - } - // updates the exception handler block labels - Handler h = firstHandler; - while (h != null) { - getNewOffset(allIndexes, allSizes, h.start); - getNewOffset(allIndexes, allSizes, h.end); - getNewOffset(allIndexes, allSizes, h.handler); - h = h.next; - } - // updates the instructions addresses in the - // local var and line number tables - for (i = 0; i < 2; ++i) { - ByteVector bv = i == 0 ? localVar : localVarType; - if (bv != null) { - b = bv.data; - u = 0; - while (u < bv.length) { - label = readUnsignedShort(b, u); - newOffset = getNewOffset(allIndexes, allSizes, 0, label); - writeShort(b, u, newOffset); - label += readUnsignedShort(b, u + 2); - newOffset = getNewOffset(allIndexes, allSizes, 0, label) - - newOffset; - writeShort(b, u + 2, newOffset); - u += 10; - } - } - } - if (lineNumber != null) { - b = lineNumber.data; - u = 0; - while (u < lineNumber.length) { - writeShort( - b, - u, - getNewOffset(allIndexes, allSizes, 0, - readUnsignedShort(b, u))); - u += 4; - } - } - // updates the labels of the other attributes - Attribute attr = cattrs; - while (attr != null) { - Label[] labels = attr.getLabels(); - if (labels != null) { - for (i = labels.length - 1; i >= 0; --i) { - getNewOffset(allIndexes, allSizes, labels[i]); - } - } - attr = attr.next; - } - - // replaces old bytecodes with new ones - code = newCode; - } - - /** - * Reads an unsigned short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static int readUnsignedShort(final byte[] b, final int index) { - return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); - } - - /** - * Reads a signed short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static short readShort(final byte[] b, final int index) { - return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF)); - } - - /** - * Reads a signed int value in the given byte array. - * - * @param b - * a byte array. - * @param index - * the start index of the value to be read. - * @return the read value. - */ - static int readInt(final byte[] b, final int index) { - return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) - | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); - } - - /** - * Writes a short value in the given byte array. - * - * @param b - * a byte array. - * @param index - * where the first byte of the short value must be written. - * @param s - * the value to be written in the given byte array. - */ - static void writeShort(final byte[] b, final int index, final int s) { - b[index] = (byte) (s >>> 8); - b[index + 1] = (byte) s; - } - - /** - * Computes the future value of a bytecode offset. - *

    - * Note: it is possible to have several entries for the same instruction in - * the indexes and sizes: two entries (index=a,size=b) and - * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b'). - * - * @param indexes - * current positions of the instructions to be resized. Each - * instruction must be designated by the index of its last - * byte, plus one (or, in other words, by the index of the - * first byte of the next instruction). - * @param sizes - * the number of bytes to be added to the above - * instructions. More precisely, for each i < len, - * sizes[i] bytes will be added at the end of the - * instruction designated by indexes[i] or, if - * sizes[i] is negative, the last | - * sizes[i]| bytes of the instruction will be removed - * (the instruction size must not become negative or - * null). - * @param begin - * index of the first byte of the source instruction. - * @param end - * index of the first byte of the target instruction. - * @return the future value of the given bytecode offset. - */ - static int getNewOffset(final int[] indexes, final int[] sizes, - final int begin, final int end) { - int offset = end - begin; - for (int i = 0; i < indexes.length; ++i) { - if (begin < indexes[i] && indexes[i] <= end) { - // forward jump - offset += sizes[i]; - } else if (end < indexes[i] && indexes[i] <= begin) { - // backward jump - offset -= sizes[i]; - } - } - return offset; - } - - /** - * Updates the offset of the given label. - * - * @param indexes - * current positions of the instructions to be resized. Each - * instruction must be designated by the index of its last - * byte, plus one (or, in other words, by the index of the - * first byte of the next instruction). - * @param sizes - * the number of bytes to be added to the above - * instructions. More precisely, for each i < len, - * sizes[i] bytes will be added at the end of the - * instruction designated by indexes[i] or, if - * sizes[i] is negative, the last | - * sizes[i]| bytes of the instruction will be removed - * (the instruction size must not become negative or - * null). - * @param label - * the label whose offset must be updated. - */ - static void getNewOffset(final int[] indexes, final int[] sizes, - final Label label) { - if ((label.status & Label.RESIZED) == 0) { - label.position = getNewOffset(indexes, sizes, 0, label.position); - label.status |= Label.RESIZED; - } - } } diff --git a/src/java/nginx/clojure/asm/Opcodes.java b/src/java/nginx/clojure/asm/Opcodes.java index 656c1295..556a7a57 100644 --- a/src/java/nginx/clojure/asm/Opcodes.java +++ b/src/java/nginx/clojure/asm/Opcodes.java @@ -46,6 +46,7 @@ public interface Opcodes { // ASM API versions int ASM4 = 4 << 16 | 0 << 8 | 0; + int ASM5 = 5 << 16 | 0 << 8 | 0; // versions @@ -64,7 +65,7 @@ public interface Opcodes { int ACC_PRIVATE = 0x0002; // class, field, method int ACC_PROTECTED = 0x0004; // class, field, method int ACC_STATIC = 0x0008; // field, method - int ACC_FINAL = 0x0010; // class, field, method + int ACC_FINAL = 0x0010; // class, field, method, parameter int ACC_SUPER = 0x0020; // class int ACC_SYNCHRONIZED = 0x0020; // method int ACC_VOLATILE = 0x0040; // field @@ -75,9 +76,10 @@ public interface Opcodes { int ACC_INTERFACE = 0x0200; // class int ACC_ABSTRACT = 0x0400; // class, method int ACC_STRICT = 0x0800; // method - int ACC_SYNTHETIC = 0x1000; // class, field, method + int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter int ACC_ANNOTATION = 0x2000; // class int ACC_ENUM = 0x4000; // class(?) field inner + int ACC_MANDATED = 0x8000; // parameter // ASM specific pseudo access flags @@ -144,13 +146,17 @@ public interface Opcodes { */ int F_SAME1 = 4; - Integer TOP = new Integer(0); - Integer INTEGER = new Integer(1); - Integer FLOAT = new Integer(2); - Integer DOUBLE = new Integer(3); - Integer LONG = new Integer(4); - Integer NULL = new Integer(5); - Integer UNINITIALIZED_THIS = new Integer(6); + // Do not try to change the following code to use auto-boxing, + // these values are compared by reference and not by value + // The constructor of Integer was deprecated in 9 + // but we are stuck with it by backward compatibility + @SuppressWarnings("deprecation") Integer TOP = new Integer(0); + @SuppressWarnings("deprecation") Integer INTEGER = new Integer(1); + @SuppressWarnings("deprecation") Integer FLOAT = new Integer(2); + @SuppressWarnings("deprecation") Integer DOUBLE = new Integer(3); + @SuppressWarnings("deprecation") Integer LONG = new Integer(4); + @SuppressWarnings("deprecation") Integer NULL = new Integer(5); + @SuppressWarnings("deprecation") Integer UNINITIALIZED_THIS = new Integer(6); // opcodes // visit method (- = idem) diff --git a/src/java/nginx/clojure/asm/Type.java b/src/java/nginx/clojure/asm/Type.java index 2ad1387e..602c22e3 100644 --- a/src/java/nginx/clojure/asm/Type.java +++ b/src/java/nginx/clojure/asm/Type.java @@ -377,7 +377,16 @@ public static Type[] getArgumentTypes(final Method method) { */ public static Type getReturnType(final String methodDescriptor) { char[] buf = methodDescriptor.toCharArray(); - return getType(buf, methodDescriptor.indexOf(')') + 1); + int off = 1; + while (true) { + char car = buf[off++]; + if (car == ')') { + return getType(buf, off); + } else if (car == 'L') { + while (buf[off++] != ';') { + } + } + } } /** @@ -401,8 +410,8 @@ public static Type getReturnType(final Method method) { * @return the size of the arguments of the method (plus one for the * implicit this argument), argSize, and the size of its return * value, retSize, packed into a single int i = - * (argSize << 2) | retSize (argSize is therefore equal to - * i >> 2, and retSize to i & 0x03). + * (argSize << 2) | retSize (argSize is therefore equal to + * i >> 2, and retSize to i & 0x03). */ public static int getArgumentsAndReturnSizes(final String desc) { int n = 1; @@ -556,11 +565,11 @@ public String getClassName() { case DOUBLE: return "double"; case ARRAY: - StringBuffer b = new StringBuffer(getElementType().getClassName()); + StringBuilder sb = new StringBuilder(getElementType().getClassName()); for (int i = getDimensions(); i > 0; --i) { - b.append("[]"); + sb.append("[]"); } - return b.toString(); + return sb.toString(); case OBJECT: return new String(buf, off, len).replace('/', '.'); default: @@ -606,9 +615,10 @@ public Type getReturnType() { * * @return the size of the arguments (plus one for the implicit this * argument), argSize, and the size of the return value, retSize, - * packed into a single int i = (argSize << 2) | retSize - * (argSize is therefore equal to i >> 2, and retSize to - * i & 0x03). + * packed into a single + * int i = (argSize << 2) | retSize + * (argSize is therefore equal to i >> 2, + * and retSize to i & 0x03). */ public int getArgumentsAndReturnSizes() { return getArgumentsAndReturnSizes(getDescriptor()); @@ -624,7 +634,7 @@ public int getArgumentsAndReturnSizes() { * @return the descriptor corresponding to this Java type. */ public String getDescriptor() { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); getDescriptor(buf); return buf.toString(); } @@ -642,7 +652,7 @@ public String getDescriptor() { */ public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); buf.append('('); for (int i = 0; i < argumentTypes.length; ++i) { argumentTypes[i].getDescriptor(buf); @@ -659,7 +669,7 @@ public static String getMethodDescriptor(final Type returnType, * @param buf * the string buffer to which the descriptor must be appended. */ - private void getDescriptor(final StringBuffer buf) { + private void getDescriptor(final StringBuilder buf) { if (this.buf == null) { // descriptor is in byte 3 of 'off' for primitive types (buf == // null) @@ -699,7 +709,7 @@ public static String getInternalName(final Class c) { * @return the descriptor corresponding to the given class. */ public static String getDescriptor(final Class c) { - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); getDescriptor(buf, c); return buf.toString(); } @@ -713,7 +723,7 @@ public static String getDescriptor(final Class c) { */ public static String getConstructorDescriptor(final Constructor c) { Class[] parameters = c.getParameterTypes(); - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); buf.append('('); for (int i = 0; i < parameters.length; ++i) { getDescriptor(buf, parameters[i]); @@ -730,7 +740,7 @@ public static String getConstructorDescriptor(final Constructor c) { */ public static String getMethodDescriptor(final Method m) { Class[] parameters = m.getParameterTypes(); - StringBuffer buf = new StringBuffer(); + StringBuilder buf = new StringBuilder(); buf.append('('); for (int i = 0; i < parameters.length; ++i) { getDescriptor(buf, parameters[i]); @@ -748,7 +758,7 @@ public static String getMethodDescriptor(final Method m) { * @param c * the class whose descriptor must be computed. */ - private static void getDescriptor(final StringBuffer buf, final Class c) { + private static void getDescriptor(final StringBuilder buf, final Class c) { Class d = c; while (true) { if (d.isPrimitive()) { diff --git a/src/java/nginx/clojure/asm/TypePath.java b/src/java/nginx/clojure/asm/TypePath.java new file mode 100644 index 00000000..a553b594 --- /dev/null +++ b/src/java/nginx/clojure/asm/TypePath.java @@ -0,0 +1,196 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2013 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package nginx.clojure.asm; + +/** + * The path to a type argument, wildcard bound, array element type, or static + * inner type within an enclosing type. + * + * @author Eric Bruneton + */ +public class TypePath { + + /** + * A type path step that steps into the element type of an array type. See + * {@link #getStep getStep}. + */ + public final static int ARRAY_ELEMENT = 0; + + /** + * A type path step that steps into the nested type of a class type. See + * {@link #getStep getStep}. + */ + public final static int INNER_TYPE = 1; + + /** + * A type path step that steps into the bound of a wildcard type. See + * {@link #getStep getStep}. + */ + public final static int WILDCARD_BOUND = 2; + + /** + * A type path step that steps into a type argument of a generic type. See + * {@link #getStep getStep}. + */ + public final static int TYPE_ARGUMENT = 3; + + /** + * The byte array where the path is stored, in Java class file format. + */ + byte[] b; + + /** + * The offset of the first byte of the type path in 'b'. + */ + int offset; + + /** + * Creates a new type path. + * + * @param b + * the byte array containing the type path in Java class file + * format. + * @param offset + * the offset of the first byte of the type path in 'b'. + */ + TypePath(byte[] b, int offset) { + this.b = b; + this.offset = offset; + } + + /** + * Returns the length of this path. + * + * @return the length of this path. + */ + public int getLength() { + return b[offset]; + } + + /** + * Returns the value of the given step of this path. + * + * @param index + * an index between 0 and {@link #getLength()}, exclusive. + * @return {@link #ARRAY_ELEMENT ARRAY_ELEMENT}, {@link #INNER_TYPE + * INNER_TYPE}, {@link #WILDCARD_BOUND WILDCARD_BOUND}, or + * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}. + */ + public int getStep(int index) { + return b[offset + 2 * index + 1]; + } + + /** + * Returns the index of the type argument that the given step is stepping + * into. This method should only be used for steps whose value is + * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}. + * + * @param index + * an index between 0 and {@link #getLength()}, exclusive. + * @return the index of the type argument that the given step is stepping + * into. + */ + public int getStepArgument(int index) { + return b[offset + 2 * index + 2]; + } + + /** + * Converts a type path in string form, in the format used by + * {@link #toString()}, into a TypePath object. + * + * @param typePath + * a type path in string form, in the format used by + * {@link #toString()}. May be null or empty. + * @return the corresponding TypePath object, or null if the path is empty. + */ + public static TypePath fromString(final String typePath) { + if (typePath == null || typePath.length() == 0) { + return null; + } + int n = typePath.length(); + ByteVector out = new ByteVector(n); + out.putByte(0); + for (int i = 0; i < n;) { + char c = typePath.charAt(i++); + if (c == '[') { + out.put11(ARRAY_ELEMENT, 0); + } else if (c == '.') { + out.put11(INNER_TYPE, 0); + } else if (c == '*') { + out.put11(WILDCARD_BOUND, 0); + } else if (c >= '0' && c <= '9') { + int typeArg = c - '0'; + while (i < n && (c = typePath.charAt(i)) >= '0' && c <= '9') { + typeArg = typeArg * 10 + c - '0'; + i += 1; + } + if (i < n && typePath.charAt(i) == ';') { + i += 1; + } + out.put11(TYPE_ARGUMENT, typeArg); + } + } + out.data[0] = (byte) (out.length / 2); + return new TypePath(out.data, 0); + } + + /** + * Returns a string representation of this type path. {@link #ARRAY_ELEMENT + * ARRAY_ELEMENT} steps are represented with '[', {@link #INNER_TYPE + * INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND WILDCARD_BOUND} steps + * with '*' and {@link #TYPE_ARGUMENT TYPE_ARGUMENT} steps with their type + * argument index in decimal form followed by ';'. + */ + @Override + public String toString() { + int length = getLength(); + StringBuilder result = new StringBuilder(length * 2); + for (int i = 0; i < length; ++i) { + switch (getStep(i)) { + case ARRAY_ELEMENT: + result.append('['); + break; + case INNER_TYPE: + result.append('.'); + break; + case WILDCARD_BOUND: + result.append('*'); + break; + case TYPE_ARGUMENT: + result.append(getStepArgument(i)).append(';'); + break; + default: + result.append('_'); + } + } + return result.toString(); + } +} diff --git a/src/java/nginx/clojure/asm/TypeReference.java b/src/java/nginx/clojure/asm/TypeReference.java new file mode 100644 index 00000000..7a3be791 --- /dev/null +++ b/src/java/nginx/clojure/asm/TypeReference.java @@ -0,0 +1,452 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2013 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package nginx.clojure.asm; + +/** + * A reference to a type appearing in a class, field or method declaration, or + * on an instruction. Such a reference designates the part of the class where + * the referenced type is appearing (e.g. an 'extends', 'implements' or 'throws' + * clause, a 'new' instruction, a 'catch' clause, a type cast, a local variable + * declaration, etc). + * + * @author Eric Bruneton + */ +public class TypeReference { + + /** + * The sort of type references that target a type parameter of a generic + * class. See {@link #getSort getSort}. + */ + public final static int CLASS_TYPE_PARAMETER = 0x00; + + /** + * The sort of type references that target a type parameter of a generic + * method. See {@link #getSort getSort}. + */ + public final static int METHOD_TYPE_PARAMETER = 0x01; + + /** + * The sort of type references that target the super class of a class or one + * of the interfaces it implements. See {@link #getSort getSort}. + */ + public final static int CLASS_EXTENDS = 0x10; + + /** + * The sort of type references that target a bound of a type parameter of a + * generic class. See {@link #getSort getSort}. + */ + public final static int CLASS_TYPE_PARAMETER_BOUND = 0x11; + + /** + * The sort of type references that target a bound of a type parameter of a + * generic method. See {@link #getSort getSort}. + */ + public final static int METHOD_TYPE_PARAMETER_BOUND = 0x12; + + /** + * The sort of type references that target the type of a field. See + * {@link #getSort getSort}. + */ + public final static int FIELD = 0x13; + + /** + * The sort of type references that target the return type of a method. See + * {@link #getSort getSort}. + */ + public final static int METHOD_RETURN = 0x14; + + /** + * The sort of type references that target the receiver type of a method. + * See {@link #getSort getSort}. + */ + public final static int METHOD_RECEIVER = 0x15; + + /** + * The sort of type references that target the type of a formal parameter of + * a method. See {@link #getSort getSort}. + */ + public final static int METHOD_FORMAL_PARAMETER = 0x16; + + /** + * The sort of type references that target the type of an exception declared + * in the throws clause of a method. See {@link #getSort getSort}. + */ + public final static int THROWS = 0x17; + + /** + * The sort of type references that target the type of a local variable in a + * method. See {@link #getSort getSort}. + */ + public final static int LOCAL_VARIABLE = 0x40; + + /** + * The sort of type references that target the type of a resource variable + * in a method. See {@link #getSort getSort}. + */ + public final static int RESOURCE_VARIABLE = 0x41; + + /** + * The sort of type references that target the type of the exception of a + * 'catch' clause in a method. See {@link #getSort getSort}. + */ + public final static int EXCEPTION_PARAMETER = 0x42; + + /** + * The sort of type references that target the type declared in an + * 'instanceof' instruction. See {@link #getSort getSort}. + */ + public final static int INSTANCEOF = 0x43; + + /** + * The sort of type references that target the type of the object created by + * a 'new' instruction. See {@link #getSort getSort}. + */ + public final static int NEW = 0x44; + + /** + * The sort of type references that target the receiver type of a + * constructor reference. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_REFERENCE = 0x45; + + /** + * The sort of type references that target the receiver type of a method + * reference. See {@link #getSort getSort}. + */ + public final static int METHOD_REFERENCE = 0x46; + + /** + * The sort of type references that target the type declared in an explicit + * or implicit cast instruction. See {@link #getSort getSort}. + */ + public final static int CAST = 0x47; + + /** + * The sort of type references that target a type parameter of a generic + * constructor in a constructor call. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48; + + /** + * The sort of type references that target a type parameter of a generic + * method in a method call. See {@link #getSort getSort}. + */ + public final static int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49; + + /** + * The sort of type references that target a type parameter of a generic + * constructor in a constructor reference. See {@link #getSort getSort}. + */ + public final static int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A; + + /** + * The sort of type references that target a type parameter of a generic + * method in a method reference. See {@link #getSort getSort}. + */ + public final static int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B; + + /** + * The type reference value in Java class file format. + */ + private int value; + + /** + * Creates a new TypeReference. + * + * @param typeRef + * the int encoded value of the type reference, as received in a + * visit method related to type annotations, like + * visitTypeAnnotation. + */ + public TypeReference(int typeRef) { + this.value = typeRef; + } + + /** + * Returns a type reference of the given sort. + * + * @param sort + * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN}, + * {@link #METHOD_RECEIVER METHOD_RECEIVER}, + * {@link #LOCAL_VARIABLE LOCAL_VARIABLE}, + * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE}, + * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW}, + * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, or + * {@link #METHOD_REFERENCE METHOD_REFERENCE}. + * @return a type reference of the given sort. + */ + public static TypeReference newTypeReference(int sort) { + return new TypeReference(sort << 24); + } + + /** + * Returns a reference to a type parameter of a generic class or method. + * + * @param sort + * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}. + * @param paramIndex + * the type parameter index. + * @return a reference to the given generic class or method type parameter. + */ + public static TypeReference newTypeParameterReference(int sort, + int paramIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to a type parameter bound of a generic class or + * method. + * + * @param sort + * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}. + * @param paramIndex + * the type parameter index. + * @param boundIndex + * the type bound index within the above type parameters. + * @return a reference to the given generic class or method type parameter + * bound. + */ + public static TypeReference newTypeParameterBoundReference(int sort, + int paramIndex, int boundIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16) + | (boundIndex << 8)); + } + + /** + * Returns a reference to the super class or to an interface of the + * 'implements' clause of a class. + * + * @param itfIndex + * the index of an interface in the 'implements' clause of a + * class, or -1 to reference the super class of the class. + * @return a reference to the given super type of a class. + */ + public static TypeReference newSuperTypeReference(int itfIndex) { + itfIndex &= 0xFFFF; + return new TypeReference((CLASS_EXTENDS << 24) | (itfIndex << 8)); + } + + /** + * Returns a reference to the type of a formal parameter of a method. + * + * @param paramIndex + * the formal parameter index. + * + * @return a reference to the type of the given method formal parameter. + */ + public static TypeReference newFormalParameterReference(int paramIndex) { + return new TypeReference((METHOD_FORMAL_PARAMETER << 24) + | (paramIndex << 16)); + } + + /** + * Returns a reference to the type of an exception, in a 'throws' clause of + * a method. + * + * @param exceptionIndex + * the index of an exception in a 'throws' clause of a method. + * + * @return a reference to the type of the given exception. + */ + public static TypeReference newExceptionReference(int exceptionIndex) { + return new TypeReference((THROWS << 24) | (exceptionIndex << 8)); + } + + /** + * Returns a reference to the type of the exception declared in a 'catch' + * clause of a method. + * + * @param tryCatchBlockIndex + * the index of a try catch block (using the order in which they + * are visited with visitTryCatchBlock). + * + * @return a reference to the type of the given exception. + */ + public static TypeReference newTryCatchReference(int tryCatchBlockIndex) { + return new TypeReference((EXCEPTION_PARAMETER << 24) + | (tryCatchBlockIndex << 8)); + } + + /** + * Returns a reference to the type of a type argument in a constructor or + * method call or reference. + * + * @param sort + * {@link #CAST CAST}, + * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. + * @param argIndex + * the type argument index. + * + * @return a reference to the type of the given type argument. + */ + public static TypeReference newTypeArgumentReference(int sort, int argIndex) { + return new TypeReference((sort << 24) | argIndex); + } + + /** + * Returns the sort of this type reference. + * + * @return {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER}, + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}, + * {@link #CLASS_EXTENDS CLASS_EXTENDS}, + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND}, + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}, + * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN}, + * {@link #METHOD_RECEIVER METHOD_RECEIVER}, + * {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER}, + * {@link #THROWS THROWS}, {@link #LOCAL_VARIABLE LOCAL_VARIABLE}, + * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE}, + * {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER}, + * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW}, + * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, + * {@link #METHOD_REFERENCE METHOD_REFERENCE}, {@link #CAST CAST}, + * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT + * METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT + * METHOD_REFERENCE_TYPE_ARGUMENT}. + */ + public int getSort() { + return value >>> 24; + } + + /** + * Returns the index of the type parameter referenced by this type + * reference. This method must only be used for type references whose sort + * is {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER}, + * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}, + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter index. + */ + public int getTypeParameterIndex() { + return (value & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the type parameter bound, within the type parameter + * {@link #getTypeParameterIndex}, referenced by this type reference. This + * method must only be used for type references whose sort is + * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or + * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter bound index. + */ + public int getTypeParameterBoundIndex() { + return (value & 0x0000FF00) >> 8; + } + + /** + * Returns the index of the "super type" of a class that is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #CLASS_EXTENDS CLASS_EXTENDS}. + * + * @return the index of an interface in the 'implements' clause of a class, + * or -1 if this type reference references the type of the super + * class. + */ + public int getSuperTypeIndex() { + return (short) ((value & 0x00FFFF00) >> 8); + } + + /** + * Returns the index of the formal parameter whose type is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER}. + * + * @return a formal parameter index. + */ + public int getFormalParameterIndex() { + return (value & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the exception, in a 'throws' clause of a method, + * whose type is referenced by this type reference. This method must only be + * used for type references whose sort is {@link #THROWS THROWS}. + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getExceptionIndex() { + return (value & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the try catch block (using the order in which they + * are visited with visitTryCatchBlock), whose 'catch' type is referenced by + * this type reference. This method must only be used for type references + * whose sort is {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER} . + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getTryCatchBlockIndex() { + return (value & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the type argument referenced by this type reference. + * This method must only be used for type references whose sort is + * {@link #CAST CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link #METHOD_INVOCATION_TYPE_ARGUMENT METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or + * {@link #METHOD_REFERENCE_TYPE_ARGUMENT METHOD_REFERENCE_TYPE_ARGUMENT}. + * + * @return a type parameter index. + */ + public int getTypeArgumentIndex() { + return value & 0xFF; + } + + /** + * Returns the int encoded value of this type reference, suitable for use in + * visit methods related to type annotations, like visitTypeAnnotation. + * + * @return the int encoded value of this type reference. + */ + public int getValue() { + return value; + } +} diff --git a/src/java/nginx/clojure/asm/commons/AdviceAdapter.java b/src/java/nginx/clojure/asm/commons/AdviceAdapter.java index 9e983dc8..f11e2631 100644 --- a/src/java/nginx/clojure/asm/commons/AdviceAdapter.java +++ b/src/java/nginx/clojure/asm/commons/AdviceAdapter.java @@ -83,7 +83,7 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param mv * the method visitor to which this adapter delegates calls. * @param access @@ -359,10 +359,10 @@ public void visitFieldInsn(final int opcode, final String owner, } break; case PUTFIELD: + popValue(); popValue(); if (longOrDouble) { popValue(); - popValue(); } break; // case GETFIELD: @@ -413,10 +413,31 @@ public void visitTypeInsn(final int opcode, final String type) { } } + @Deprecated @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { - mv.visitMethodInsn(opcode, owner, name, desc); + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(int opcode, final String owner, + final String name, final String desc, final boolean itf) { + mv.visitMethodInsn(opcode, owner, name, desc, itf); if (constructor) { Type[] types = Type.getArgumentTypes(desc); for (int i = 0; i < types.length; i++) { @@ -569,7 +590,7 @@ private void pushValue(final Object o) { } /** - * Called at the beginning of the method or after super class class call in + * Called at the beginning of the method or after super class call in * the constructor.
    *
    * diff --git a/src/java/nginx/clojure/asm/commons/AnalyzerAdapter.java b/src/java/nginx/clojure/asm/commons/AnalyzerAdapter.java index 1371a248..656718e0 100644 --- a/src/java/nginx/clojure/asm/commons/AnalyzerAdapter.java +++ b/src/java/nginx/clojure/asm/commons/AnalyzerAdapter.java @@ -136,10 +136,15 @@ public class AnalyzerAdapter extends MethodVisitor { * @param mv * the method visitor to which this adapter delegates calls. May * be null. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public AnalyzerAdapter(final String owner, final int access, final String name, final String desc, final MethodVisitor mv) { - this(Opcodes.ASM4, owner, access, name, desc, mv); + this(Opcodes.ASM5, owner, access, name, desc, mv); + if (getClass() != AnalyzerAdapter.class) { + throw new IllegalStateException(); + } } /** @@ -147,7 +152,7 @@ public AnalyzerAdapter(final String owner, final int access, * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param owner * the owner's class name. * @param access @@ -206,6 +211,7 @@ protected AnalyzerAdapter(final int api, final String owner, locals.add(types[i].getInternalName()); } } + maxLocals = locals.size(); } @Override @@ -302,11 +308,32 @@ public void visitFieldInsn(final int opcode, final String owner, execute(opcode, 0, desc); } + @Deprecated @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(int opcode, final String owner, + final String name, final String desc, final boolean itf) { if (mv != null) { - mv.visitMethodInsn(opcode, owner, name, desc); + mv.visitMethodInsn(opcode, owner, name, desc, itf); } if (this.locals == null) { labels = null; @@ -464,12 +491,12 @@ public void visitMaxs(final int maxStack, final int maxLocals) { // ------------------------------------------------------------------------ private Object get(final int local) { - maxLocals = Math.max(maxLocals, local); + maxLocals = Math.max(maxLocals, local + 1); return local < locals.size() ? locals.get(local) : Opcodes.TOP; } private void set(final int local, final Object type) { - maxLocals = Math.max(maxLocals, local); + maxLocals = Math.max(maxLocals, local + 1); while (local >= locals.size()) { locals.add(Opcodes.TOP); } diff --git a/src/java/nginx/clojure/asm/commons/AnnotationRemapper.java b/src/java/nginx/clojure/asm/commons/AnnotationRemapper.java new file mode 100644 index 00000000..b95e7f60 --- /dev/null +++ b/src/java/nginx/clojure/asm/commons/AnnotationRemapper.java @@ -0,0 +1,79 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package nginx.clojure.asm.commons; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.Opcodes; + +/** + * An {@link AnnotationVisitor} adapter for type remapping. + * + * @author Eugene Kuleshov + */ +public class AnnotationRemapper extends AnnotationVisitor { + + protected final Remapper remapper; + + public AnnotationRemapper(final AnnotationVisitor av, + final Remapper remapper) { + this(Opcodes.ASM5, av, remapper); + } + + protected AnnotationRemapper(final int api, final AnnotationVisitor av, + final Remapper remapper) { + super(api, av); + this.remapper = remapper; + } + + @Override + public void visit(String name, Object value) { + av.visit(name, remapper.mapValue(value)); + } + + @Override + public void visitEnum(String name, String desc, String value) { + av.visitEnum(name, remapper.mapDesc(desc), value); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + AnnotationVisitor v = av.visitAnnotation(name, remapper.mapDesc(desc)); + return v == null ? null : (v == av ? this : new AnnotationRemapper(v, + remapper)); + } + + @Override + public AnnotationVisitor visitArray(String name) { + AnnotationVisitor v = av.visitArray(name); + return v == null ? null : (v == av ? this : new AnnotationRemapper(v, + remapper)); + } +} diff --git a/src/java/nginx/clojure/asm/commons/ClassRemapper.java b/src/java/nginx/clojure/asm/commons/ClassRemapper.java new file mode 100644 index 00000000..15f743af --- /dev/null +++ b/src/java/nginx/clojure/asm/commons/ClassRemapper.java @@ -0,0 +1,132 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package nginx.clojure.asm.commons; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.ClassVisitor; +import nginx.clojure.asm.FieldVisitor; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; + +/** + * A {@link ClassVisitor} for type remapping. + * + * @author Eugene Kuleshov + */ +public class ClassRemapper extends ClassVisitor { + + protected final Remapper remapper; + + protected String className; + + public ClassRemapper(final ClassVisitor cv, final Remapper remapper) { + this(Opcodes.ASM5, cv, remapper); + } + + protected ClassRemapper(final int api, final ClassVisitor cv, + final Remapper remapper) { + super(api, cv); + this.remapper = remapper; + } + + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + this.className = name; + super.visit(version, access, remapper.mapType(name), remapper + .mapSignature(signature, false), remapper.mapType(superName), + interfaces == null ? null : remapper.mapTypes(interfaces)); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationVisitor av = super.visitAnnotation(remapper.mapDesc(desc), + visible); + return av == null ? null : createAnnotationRemapper(av); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? null : createAnnotationRemapper(av); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, + String signature, Object value) { + FieldVisitor fv = super.visitField(access, + remapper.mapFieldName(className, name, desc), + remapper.mapDesc(desc), remapper.mapSignature(signature, true), + remapper.mapValue(value)); + return fv == null ? null : createFieldRemapper(fv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + String newDesc = remapper.mapMethodDesc(desc); + MethodVisitor mv = super.visitMethod(access, remapper.mapMethodName( + className, name, desc), newDesc, remapper.mapSignature( + signature, false), + exceptions == null ? null : remapper.mapTypes(exceptions)); + return mv == null ? null : createMethodRemapper(mv); + } + + @Override + public void visitInnerClass(String name, String outerName, + String innerName, int access) { + // TODO should innerName be changed? + super.visitInnerClass(remapper.mapType(name), outerName == null ? null + : remapper.mapType(outerName), innerName, access); + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + super.visitOuterClass(remapper.mapType(owner), name == null ? null + : remapper.mapMethodName(owner, name, desc), + desc == null ? null : remapper.mapMethodDesc(desc)); + } + + protected FieldVisitor createFieldRemapper(FieldVisitor fv) { + return new FieldRemapper(fv, remapper); + } + + protected MethodVisitor createMethodRemapper(MethodVisitor mv) { + return new MethodRemapper(mv, remapper); + } + + protected AnnotationVisitor createAnnotationRemapper(AnnotationVisitor av) { + return new AnnotationRemapper(av, remapper); + } +} diff --git a/src/java/nginx/clojure/asm/commons/CodeSizeEvaluator.java b/src/java/nginx/clojure/asm/commons/CodeSizeEvaluator.java index 5454e60b..4ba0727c 100644 --- a/src/java/nginx/clojure/asm/commons/CodeSizeEvaluator.java +++ b/src/java/nginx/clojure/asm/commons/CodeSizeEvaluator.java @@ -46,7 +46,7 @@ public class CodeSizeEvaluator extends MethodVisitor implements Opcodes { private int maxSize; public CodeSizeEvaluator(final MethodVisitor mv) { - this(Opcodes.ASM4, mv); + this(Opcodes.ASM5, mv); } protected CodeSizeEvaluator(final int api, final MethodVisitor mv) { @@ -120,9 +120,30 @@ public void visitFieldInsn(final int opcode, final String owner, } } + @Deprecated @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(int opcode, final String owner, + final String name, final String desc, final boolean itf) { if (opcode == INVOKEINTERFACE) { minSize += 5; maxSize += 5; @@ -131,7 +152,7 @@ public void visitMethodInsn(final int opcode, final String owner, maxSize += 3; } if (mv != null) { - mv.visitMethodInsn(opcode, owner, name, desc); + mv.visitMethodInsn(opcode, owner, name, desc, itf); } } diff --git a/src/java/nginx/clojure/asm/commons/FieldRemapper.java b/src/java/nginx/clojure/asm/commons/FieldRemapper.java new file mode 100644 index 00000000..c637cb43 --- /dev/null +++ b/src/java/nginx/clojure/asm/commons/FieldRemapper.java @@ -0,0 +1,71 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package nginx.clojure.asm.commons; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.FieldVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; + +/** + * A {@link FieldVisitor} adapter for type remapping. + * + * @author Eugene Kuleshov + */ +public class FieldRemapper extends FieldVisitor { + + private final Remapper remapper; + + public FieldRemapper(final FieldVisitor fv, final Remapper remapper) { + this(Opcodes.ASM5, fv, remapper); + } + + protected FieldRemapper(final int api, final FieldVisitor fv, + final Remapper remapper) { + super(api, fv); + this.remapper = remapper; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationVisitor av = fv.visitAnnotation(remapper.mapDesc(desc), + visible); + return av == null ? null : new AnnotationRemapper(av, remapper); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? null : new AnnotationRemapper(av, remapper); + } +} diff --git a/src/java/nginx/clojure/asm/commons/GeneratorAdapter.java b/src/java/nginx/clojure/asm/commons/GeneratorAdapter.java index 5aca835a..5ce30e7f 100644 --- a/src/java/nginx/clojure/asm/commons/GeneratorAdapter.java +++ b/src/java/nginx/clojure/asm/commons/GeneratorAdapter.java @@ -255,10 +255,15 @@ public class GeneratorAdapter extends LocalVariablesSorter { * the method's name. * @param desc * the method's descriptor (see {@link Type Type}). + * @throws IllegalStateException + * If a subclass calls this constructor. */ public GeneratorAdapter(final MethodVisitor mv, final int access, final String name, final String desc) { - this(Opcodes.ASM4, mv, access, name, desc); + this(Opcodes.ASM5, mv, access, name, desc); + if (getClass() != GeneratorAdapter.class) { + throw new IllegalStateException(); + } } /** @@ -266,7 +271,7 @@ public GeneratorAdapter(final MethodVisitor mv, final int access, * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param mv * the method visitor to which this adapter delegates calls. * @param access @@ -374,7 +379,7 @@ public void push(final int value) { } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { mv.visitIntInsn(Opcodes.SIPUSH, value); } else { - mv.visitLdcInsn(new Integer(value)); + mv.visitLdcInsn(value); } } @@ -388,7 +393,7 @@ public void push(final long value) { if (value == 0L || value == 1L) { mv.visitInsn(Opcodes.LCONST_0 + (int) value); } else { - mv.visitLdcInsn(new Long(value)); + mv.visitLdcInsn(value); } } @@ -403,7 +408,7 @@ public void push(final float value) { if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 mv.visitInsn(Opcodes.FCONST_0 + (int) value); } else { - mv.visitLdcInsn(new Float(value)); + mv.visitLdcInsn(value); } } @@ -418,7 +423,7 @@ public void push(final double value) { if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d mv.visitInsn(Opcodes.DCONST_0 + (int) value); } else { - mv.visitLdcInsn(new Double(value)); + mv.visitLdcInsn(value); } } @@ -1371,11 +1376,11 @@ public void putField(final Type owner, final String name, final Type type) { * the method to be invoked. */ private void invokeInsn(final int opcode, final Type type, - final Method method) { + final Method method, final boolean itf) { String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName(); mv.visitMethodInsn(opcode, owner, method.getName(), - method.getDescriptor()); + method.getDescriptor(), itf); } /** @@ -1387,7 +1392,7 @@ private void invokeInsn(final int opcode, final Type type, * the method to be invoked. */ public void invokeVirtual(final Type owner, final Method method) { - invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method); + invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method, false); } /** @@ -1399,7 +1404,7 @@ public void invokeVirtual(final Type owner, final Method method) { * the constructor to be invoked. */ public void invokeConstructor(final Type type, final Method method) { - invokeInsn(Opcodes.INVOKESPECIAL, type, method); + invokeInsn(Opcodes.INVOKESPECIAL, type, method, false); } /** @@ -1411,7 +1416,7 @@ public void invokeConstructor(final Type type, final Method method) { * the method to be invoked. */ public void invokeStatic(final Type owner, final Method method) { - invokeInsn(Opcodes.INVOKESTATIC, owner, method); + invokeInsn(Opcodes.INVOKESTATIC, owner, method, false); } /** @@ -1423,7 +1428,7 @@ public void invokeStatic(final Type owner, final Method method) { * the method to be invoked. */ public void invokeInterface(final Type owner, final Method method) { - invokeInsn(Opcodes.INVOKEINTERFACE, owner, method); + invokeInsn(Opcodes.INVOKEINTERFACE, owner, method, true); } /** @@ -1613,11 +1618,13 @@ public void endMethod() { */ public void catchException(final Label start, final Label end, final Type exception) { + Label doCatch = new Label(); if (exception == null) { - mv.visitTryCatchBlock(start, end, mark(), null); + mv.visitTryCatchBlock(start, end, doCatch, null); } else { - mv.visitTryCatchBlock(start, end, mark(), + mv.visitTryCatchBlock(start, end, doCatch, exception.getInternalName()); } + mark(doCatch); } } diff --git a/src/java/nginx/clojure/asm/commons/InstructionAdapter.java b/src/java/nginx/clojure/asm/commons/InstructionAdapter.java index 4810ed09..f660481a 100644 --- a/src/java/nginx/clojure/asm/commons/InstructionAdapter.java +++ b/src/java/nginx/clojure/asm/commons/InstructionAdapter.java @@ -53,9 +53,14 @@ public class InstructionAdapter extends MethodVisitor { * * @param mv * the method visitor to which this adapter delegates calls. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public InstructionAdapter(final MethodVisitor mv) { - this(Opcodes.ASM4, mv); + this(Opcodes.ASM5, mv); + if (getClass() != InstructionAdapter.class) { + throw new IllegalStateException(); + } } /** @@ -63,7 +68,7 @@ public InstructionAdapter(final MethodVisitor mv) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param mv * the method visitor to which this adapter delegates calls. */ @@ -507,18 +512,39 @@ public void visitFieldInsn(final int opcode, final String owner, } } + @Deprecated @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(int opcode, final String owner, + final String name, final String desc, final boolean itf) { switch (opcode) { case Opcodes.INVOKESPECIAL: - invokespecial(owner, name, desc); + invokespecial(owner, name, desc, itf); break; case Opcodes.INVOKEVIRTUAL: - invokevirtual(owner, name, desc); + invokevirtual(owner, name, desc, itf); break; case Opcodes.INVOKESTATIC: - invokestatic(owner, name, desc); + invokestatic(owner, name, desc, itf); break; case Opcodes.INVOKEINTERFACE: invokeinterface(owner, name, desc); @@ -682,7 +708,7 @@ public void iconst(final int cst) { } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { mv.visitIntInsn(Opcodes.SIPUSH, cst); } else { - mv.visitLdcInsn(new Integer(cst)); + mv.visitLdcInsn(cst); } } @@ -690,7 +716,7 @@ public void lconst(final long cst) { if (cst == 0L || cst == 1L) { mv.visitInsn(Opcodes.LCONST_0 + (int) cst); } else { - mv.visitLdcInsn(new Long(cst)); + mv.visitLdcInsn(cst); } } @@ -699,7 +725,7 @@ public void fconst(final float cst) { if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2 mv.visitInsn(Opcodes.FCONST_0 + (int) cst); } else { - mv.visitLdcInsn(new Float(cst)); + mv.visitLdcInsn(cst); } } @@ -708,7 +734,7 @@ public void dconst(final double cst) { if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d mv.visitInsn(Opcodes.DCONST_0 + (int) cst); } else { - mv.visitLdcInsn(new Double(cst)); + mv.visitLdcInsn(cst); } } @@ -985,24 +1011,78 @@ public void putfield(final String owner, final String name, mv.visitFieldInsn(Opcodes.PUTFIELD, owner, name, desc); } + @Deprecated public void invokevirtual(final String owner, final String name, final String desc) { + if (api >= Opcodes.ASM5) { + invokevirtual(owner, name, desc, false); + return; + } mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc); } + public void invokevirtual(final String owner, final String name, + final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + if (itf) { + throw new IllegalArgumentException( + "INVOKEVIRTUAL on interfaces require ASM 5"); + } + invokevirtual(owner, name, desc); + return; + } + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf); + } + + @Deprecated public void invokespecial(final String owner, final String name, final String desc) { - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc); + if (api >= Opcodes.ASM5) { + invokespecial(owner, name, desc, false); + return; + } + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, false); + } + + public void invokespecial(final String owner, final String name, + final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + if (itf) { + throw new IllegalArgumentException( + "INVOKESPECIAL on interfaces require ASM 5"); + } + invokespecial(owner, name, desc); + return; + } + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf); } + @Deprecated public void invokestatic(final String owner, final String name, final String desc) { - mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc); + if (api >= Opcodes.ASM5) { + invokestatic(owner, name, desc, false); + return; + } + mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false); + } + + public void invokestatic(final String owner, final String name, + final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + if (itf) { + throw new IllegalArgumentException( + "INVOKESTATIC on interfaces require ASM 5"); + } + invokestatic(owner, name, desc); + return; + } + mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, itf); } public void invokeinterface(final String owner, final String name, final String desc) { - mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc); + mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true); } public void invokedynamic(String name, String desc, Handle bsm, diff --git a/src/java/nginx/clojure/asm/commons/JSRInlinerAdapter.java b/src/java/nginx/clojure/asm/commons/JSRInlinerAdapter.java index 15ae01a2..593b2f99 100644 --- a/src/java/nginx/clojure/asm/commons/JSRInlinerAdapter.java +++ b/src/java/nginx/clojure/asm/commons/JSRInlinerAdapter.java @@ -107,11 +107,16 @@ public class JSRInlinerAdapter extends MethodNode implements Opcodes { * the internal names of the method's exception classes (see * {@link Type#getInternalName() getInternalName}). May be * null. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public JSRInlinerAdapter(final MethodVisitor mv, final int access, final String name, final String desc, final String signature, final String[] exceptions) { - this(Opcodes.ASM4, mv, access, name, desc, signature, exceptions); + this(Opcodes.ASM5, mv, access, name, desc, signature, exceptions); + if (getClass() != JSRInlinerAdapter.class) { + throw new IllegalStateException(); + } } /** @@ -119,7 +124,7 @@ public JSRInlinerAdapter(final MethodVisitor mv, final int access, * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param mv * the MethodVisitor to send the resulting inlined * method code to (use null for none). diff --git a/src/java/nginx/clojure/asm/commons/LocalVariablesSorter.java b/src/java/nginx/clojure/asm/commons/LocalVariablesSorter.java index 04373ba4..73dc44fd 100644 --- a/src/java/nginx/clojure/asm/commons/LocalVariablesSorter.java +++ b/src/java/nginx/clojure/asm/commons/LocalVariablesSorter.java @@ -29,10 +29,12 @@ */ package nginx.clojure.asm.commons; +import nginx.clojure.asm.AnnotationVisitor; import nginx.clojure.asm.Label; import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; import nginx.clojure.asm.Type; +import nginx.clojure.asm.TypePath; /** * A {@link MethodVisitor} that renumbers local variables in their order of @@ -73,11 +75,6 @@ public class LocalVariablesSorter extends MethodVisitor { */ protected int nextLocal; - /** - * Indicates if at least one local variable has moved due to remapping. - */ - private boolean changed; - /** * Creates a new {@link LocalVariablesSorter}. Subclasses must not use * this constructor. Instead, they must use the @@ -89,10 +86,15 @@ public class LocalVariablesSorter extends MethodVisitor { * the method's descriptor (see {@link Type Type}). * @param mv * the method visitor to which this adapter delegates calls. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public LocalVariablesSorter(final int access, final String desc, final MethodVisitor mv) { - this(Opcodes.ASM4, access, desc, mv); + this(Opcodes.ASM5, access, desc, mv); + if (getClass() != LocalVariablesSorter.class) { + throw new IllegalStateException(); + } } /** @@ -100,7 +102,7 @@ public LocalVariablesSorter(final int access, final String desc, * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param access * access flags of the adapted method. * @param desc @@ -171,6 +173,19 @@ public void visitLocalVariable(final String name, final String desc, mv.visitLocalVariable(name, desc, signature, start, end, newIndex); } + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + Type t = Type.getType(desc); + int[] newIndex = new int[index.length]; + for (int i = 0; i < newIndex.length; ++i) { + newIndex[i] = remap(index[i], t); + } + return mv.visitLocalVariableAnnotation(typeRef, typePath, start, end, + newIndex, desc, visible); + } + @Override public void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack) { @@ -179,11 +194,6 @@ public void visitFrame(final int type, final int nLocal, "ClassReader.accept() should be called with EXPAND_FRAMES flag"); } - if (!changed) { // optimization for the case where mapping = identity - mv.visitFrame(type, nLocal, local, nStack, stack); - return; - } - // creates a copy of newLocals Object[] oldLocals = new Object[newLocals.length]; System.arraycopy(newLocals, 0, oldLocals, 0, oldLocals.length); @@ -279,7 +289,6 @@ public int newLocal(final Type type) { int local = newLocalMapping(type); setLocalType(local, type); setFrameLocal(local, t); - changed = true; return local; } @@ -304,7 +313,7 @@ public int newLocal(final Type type) { */ protected void updateNewLocals(Object[] newLocals) { } - + /** * Notifies subclasses that a local variable has been added or remapped. The * default implementation of this method does nothing. @@ -347,9 +356,6 @@ private int remap(final int var, final Type type) { } else { value--; } - if (value != var) { - changed = true; - } return value; } diff --git a/src/java/nginx/clojure/asm/commons/Method.java b/src/java/nginx/clojure/asm/commons/Method.java index b5cc0264..1b44123d 100644 --- a/src/java/nginx/clojure/asm/commons/Method.java +++ b/src/java/nginx/clojure/asm/commons/Method.java @@ -176,7 +176,7 @@ public static Method getMethod(final String method, } String returnType = method.substring(0, space); String methodName = method.substring(space + 1, start - 1).trim(); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); sb.append('('); int p; do { @@ -200,7 +200,7 @@ private static String map(final String type, final boolean defaultPackage) { return type; } - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); int index = 0; while ((index = type.indexOf("[]", index) + 1) > 0) { sb.append('['); diff --git a/src/java/nginx/clojure/asm/commons/MethodRemapper.java b/src/java/nginx/clojure/asm/commons/MethodRemapper.java new file mode 100644 index 00000000..53046699 --- /dev/null +++ b/src/java/nginx/clojure/asm/commons/MethodRemapper.java @@ -0,0 +1,223 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package nginx.clojure.asm.commons; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.Handle; +import nginx.clojure.asm.Label; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; + +/** + * A {@link LocalVariablesSorter} for type mapping. + * + * @author Eugene Kuleshov + */ +public class MethodRemapper extends MethodVisitor { + + protected final Remapper remapper; + + public MethodRemapper(final MethodVisitor mv, final Remapper remapper) { + this(Opcodes.ASM5, mv, remapper); + } + + protected MethodRemapper(final int api, final MethodVisitor mv, + final Remapper remapper) { + super(api, mv); + this.remapper = remapper; + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + AnnotationVisitor av = super.visitAnnotationDefault(); + return av == null ? av : new AnnotationRemapper(av, remapper); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationVisitor av = super.visitAnnotation(remapper.mapDesc(desc), + visible); + return av == null ? av : new AnnotationRemapper(av, remapper); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? av : new AnnotationRemapper(av, remapper); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, + String desc, boolean visible) { + AnnotationVisitor av = super.visitParameterAnnotation(parameter, + remapper.mapDesc(desc), visible); + return av == null ? av : new AnnotationRemapper(av, remapper); + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, + Object[] stack) { + super.visitFrame(type, nLocal, remapEntries(nLocal, local), nStack, + remapEntries(nStack, stack)); + } + + private Object[] remapEntries(int n, Object[] entries) { + for (int i = 0; i < n; i++) { + if (entries[i] instanceof String) { + Object[] newEntries = new Object[n]; + if (i > 0) { + System.arraycopy(entries, 0, newEntries, 0, i); + } + do { + Object t = entries[i]; + newEntries[i++] = t instanceof String ? remapper + .mapType((String) t) : t; + } while (i < n); + return newEntries; + } + } + return entries; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, + String desc) { + super.visitFieldInsn(opcode, remapper.mapType(owner), + remapper.mapFieldName(owner, name, desc), + remapper.mapDesc(desc)); + } + + @Deprecated + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + // Calling super.visitMethodInsn requires to call the correct version + // depending on this.api (otherwise infinite loops can occur). To + // simplify and to make it easier to automatically remove the backward + // compatibility code, we inline the code of the overridden method here. + // IMPORTANT: THIS ASSUMES THAT visitMethodInsn IS NOT OVERRIDDEN IN + // LocalVariableSorter. + if (mv != null) { + mv.visitMethodInsn(opcode, remapper.mapType(owner), + remapper.mapMethodName(owner, name, desc), + remapper.mapMethodDesc(desc), itf); + } + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, + Object... bsmArgs) { + for (int i = 0; i < bsmArgs.length; i++) { + bsmArgs[i] = remapper.mapValue(bsmArgs[i]); + } + super.visitInvokeDynamicInsn( + remapper.mapInvokeDynamicMethodName(name, desc), + remapper.mapMethodDesc(desc), (Handle) remapper.mapValue(bsm), + bsmArgs); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + super.visitTypeInsn(opcode, remapper.mapType(type)); + } + + @Override + public void visitLdcInsn(Object cst) { + super.visitLdcInsn(remapper.mapValue(cst)); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + super.visitMultiANewArrayInsn(remapper.mapDesc(desc), dims); + } + + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitInsnAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? av : new AnnotationRemapper(av, remapper); + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, + String type) { + super.visitTryCatchBlock(start, end, handler, type == null ? null + : remapper.mapType(type)); + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitTryCatchAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? av : new AnnotationRemapper(av, remapper); + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, + Label start, Label end, int index) { + super.visitLocalVariable(name, remapper.mapDesc(desc), + remapper.mapSignature(signature, true), start, end, index); + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + AnnotationVisitor av = super.visitLocalVariableAnnotation(typeRef, + typePath, start, end, index, remapper.mapDesc(desc), visible); + return av == null ? av : new AnnotationRemapper(av, remapper); + } +} diff --git a/src/java/nginx/clojure/asm/commons/Remapper.java b/src/java/nginx/clojure/asm/commons/Remapper.java index a4119cab..3ed1ea93 100644 --- a/src/java/nginx/clojure/asm/commons/Remapper.java +++ b/src/java/nginx/clojure/asm/commons/Remapper.java @@ -118,17 +118,17 @@ public String mapMethodDesc(String desc) { } Type[] args = Type.getArgumentTypes(desc); - StringBuffer s = new StringBuffer("("); + StringBuilder sb = new StringBuilder("("); for (int i = 0; i < args.length; i++) { - s.append(mapDesc(args[i].getDescriptor())); + sb.append(mapDesc(args[i].getDescriptor())); } Type returnType = Type.getReturnType(desc); if (returnType == Type.VOID_TYPE) { - s.append(")V"); - return s.toString(); + sb.append(")V"); + return sb.toString(); } - s.append(')').append(mapDesc(returnType.getDescriptor())); - return s.toString(); + sb.append(')').append(mapDesc(returnType.getDescriptor())); + return sb.toString(); } public Object mapValue(Object value) { @@ -139,17 +139,19 @@ public Object mapValue(Object value) { Handle h = (Handle) value; return new Handle(h.getTag(), mapType(h.getOwner()), mapMethodName( h.getOwner(), h.getName(), h.getDesc()), - mapMethodDesc(h.getDesc())); + mapMethodDesc(h.getDesc()), h.isInterface()); } return value; } /** - * + * @param signature + * signature for mapper * @param typeSignature * true if signature is a FieldTypeSignature, such as the * signature parameter of the ClassVisitor.visitField or * MethodVisitor.visitLocalVariable methods + * @return signature rewritten as a string */ public String mapSignature(String signature, boolean typeSignature) { if (signature == null) { @@ -157,7 +159,7 @@ public String mapSignature(String signature, boolean typeSignature) { } SignatureReader r = new SignatureReader(signature); SignatureWriter w = new SignatureWriter(); - SignatureVisitor a = createRemappingSignatureAdapter(w); + SignatureVisitor a = createSignatureRemapper(w); if (typeSignature) { r.acceptType(a); } else { @@ -166,9 +168,18 @@ public String mapSignature(String signature, boolean typeSignature) { return w.toString(); } + /** + * @deprecated use {@link #createSignatureRemapper} instead. + */ + @Deprecated protected SignatureVisitor createRemappingSignatureAdapter( SignatureVisitor v) { - return new RemappingSignatureAdapter(v, this); + return new SignatureRemapper(v, this); + } + + protected SignatureVisitor createSignatureRemapper( + SignatureVisitor v) { + return createRemappingSignatureAdapter(v); } /** @@ -216,6 +227,10 @@ public String mapFieldName(String owner, String name, String desc) { /** * Map type name to the new name. Subclasses can override. + * + * @param typeName + * the type name + * @return new name, default implementation is the identity. */ public String map(String typeName) { return typeName; diff --git a/src/java/nginx/clojure/asm/commons/RemappingAnnotationAdapter.java b/src/java/nginx/clojure/asm/commons/RemappingAnnotationAdapter.java index 055b6c9e..ccd62c10 100644 --- a/src/java/nginx/clojure/asm/commons/RemappingAnnotationAdapter.java +++ b/src/java/nginx/clojure/asm/commons/RemappingAnnotationAdapter.java @@ -36,15 +36,17 @@ /** * An {@link AnnotationVisitor} adapter for type remapping. * + * @deprecated use {@link AnnotationRemapper} instead. * @author Eugene Kuleshov */ +@Deprecated public class RemappingAnnotationAdapter extends AnnotationVisitor { protected final Remapper remapper; public RemappingAnnotationAdapter(final AnnotationVisitor av, final Remapper remapper) { - this(Opcodes.ASM4, av, remapper); + this(Opcodes.ASM5, av, remapper); } protected RemappingAnnotationAdapter(final int api, diff --git a/src/java/nginx/clojure/asm/commons/RemappingClassAdapter.java b/src/java/nginx/clojure/asm/commons/RemappingClassAdapter.java index 3ace8a22..a0d29332 100644 --- a/src/java/nginx/clojure/asm/commons/RemappingClassAdapter.java +++ b/src/java/nginx/clojure/asm/commons/RemappingClassAdapter.java @@ -35,12 +35,15 @@ import nginx.clojure.asm.FieldVisitor; import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * A {@link ClassVisitor} for type remapping. * + * @deprecated use {@link ClassRemapper} instead. * @author Eugene Kuleshov */ +@Deprecated public class RemappingClassAdapter extends ClassVisitor { protected final Remapper remapper; @@ -48,7 +51,7 @@ public class RemappingClassAdapter extends ClassVisitor { protected String className; public RemappingClassAdapter(final ClassVisitor cv, final Remapper remapper) { - this(Opcodes.ASM4, cv, remapper); + this(Opcodes.ASM5, cv, remapper); } protected RemappingClassAdapter(final int api, final ClassVisitor cv, @@ -68,8 +71,16 @@ public void visit(int version, int access, String name, String signature, @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - AnnotationVisitor av; - av = super.visitAnnotation(remapper.mapDesc(desc), visible); + AnnotationVisitor av = super.visitAnnotation(remapper.mapDesc(desc), + visible); + return av == null ? null : createRemappingAnnotationAdapter(av); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); return av == null ? null : createRemappingAnnotationAdapter(av); } diff --git a/src/java/nginx/clojure/asm/commons/RemappingFieldAdapter.java b/src/java/nginx/clojure/asm/commons/RemappingFieldAdapter.java index bb7cf3af..e69da422 100644 --- a/src/java/nginx/clojure/asm/commons/RemappingFieldAdapter.java +++ b/src/java/nginx/clojure/asm/commons/RemappingFieldAdapter.java @@ -33,18 +33,21 @@ import nginx.clojure.asm.AnnotationVisitor; import nginx.clojure.asm.FieldVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * A {@link FieldVisitor} adapter for type remapping. * + * @deprecated use {@link FieldRemapper} instead. * @author Eugene Kuleshov */ +@Deprecated public class RemappingFieldAdapter extends FieldVisitor { private final Remapper remapper; public RemappingFieldAdapter(final FieldVisitor fv, final Remapper remapper) { - this(Opcodes.ASM4, fv, remapper); + this(Opcodes.ASM5, fv, remapper); } protected RemappingFieldAdapter(final int api, final FieldVisitor fv, @@ -59,4 +62,12 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { visible); return av == null ? null : new RemappingAnnotationAdapter(av, remapper); } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? null : new RemappingAnnotationAdapter(av, remapper); + } } diff --git a/src/java/nginx/clojure/asm/commons/RemappingMethodAdapter.java b/src/java/nginx/clojure/asm/commons/RemappingMethodAdapter.java index 7763be22..f7f5f616 100644 --- a/src/java/nginx/clojure/asm/commons/RemappingMethodAdapter.java +++ b/src/java/nginx/clojure/asm/commons/RemappingMethodAdapter.java @@ -35,19 +35,22 @@ import nginx.clojure.asm.Label; import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * A {@link LocalVariablesSorter} for type mapping. * + * @deprecated use {@link MethodRemapper} instead. * @author Eugene Kuleshov */ +@Deprecated public class RemappingMethodAdapter extends LocalVariablesSorter { protected final Remapper remapper; public RemappingMethodAdapter(final int access, final String desc, final MethodVisitor mv, final Remapper remapper) { - this(Opcodes.ASM4, access, desc, mv, remapper); + this(Opcodes.ASM5, access, desc, mv, remapper); } protected RemappingMethodAdapter(final int api, final int access, @@ -58,21 +61,29 @@ protected RemappingMethodAdapter(final int api, final int access, @Override public AnnotationVisitor visitAnnotationDefault() { - AnnotationVisitor av = mv.visitAnnotationDefault(); + AnnotationVisitor av = super.visitAnnotationDefault(); return av == null ? av : new RemappingAnnotationAdapter(av, remapper); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - AnnotationVisitor av = mv.visitAnnotation(remapper.mapDesc(desc), + AnnotationVisitor av = super.visitAnnotation(remapper.mapDesc(desc), visible); return av == null ? av : new RemappingAnnotationAdapter(av, remapper); } + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? av : new RemappingAnnotationAdapter(av, remapper); + } + @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { - AnnotationVisitor av = mv.visitParameterAnnotation(parameter, + AnnotationVisitor av = super.visitParameterAnnotation(parameter, remapper.mapDesc(desc), visible); return av == null ? av : new RemappingAnnotationAdapter(av, remapper); } @@ -110,12 +121,41 @@ public void visitFieldInsn(int opcode, String owner, String name, remapper.mapDesc(desc)); } + @Deprecated @Override - public void visitMethodInsn(int opcode, String owner, String name, - String desc) { - super.visitMethodInsn(opcode, remapper.mapType(owner), - remapper.mapMethodName(owner, name, desc), - remapper.mapMethodDesc(desc)); + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + // Calling super.visitMethodInsn requires to call the correct version + // depending on this.api (otherwise infinite loops can occur). To + // simplify and to make it easier to automatically remove the backward + // compatibility code, we inline the code of the overridden method here. + // IMPORTANT: THIS ASSUMES THAT visitMethodInsn IS NOT OVERRIDDEN IN + // LocalVariableSorter. + if (mv != null) { + mv.visitMethodInsn(opcode, remapper.mapType(owner), + remapper.mapMethodName(owner, name, desc), + remapper.mapMethodDesc(desc), itf); + } } @Override @@ -145,6 +185,14 @@ public void visitMultiANewArrayInsn(String desc, int dims) { super.visitMultiANewArrayInsn(remapper.mapDesc(desc), dims); } + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitInsnAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? av : new RemappingAnnotationAdapter(av, remapper); + } + @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { @@ -152,10 +200,27 @@ public void visitTryCatchBlock(Label start, Label end, Label handler, : remapper.mapType(type)); } + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + AnnotationVisitor av = super.visitTryCatchAnnotation(typeRef, typePath, + remapper.mapDesc(desc), visible); + return av == null ? av : new RemappingAnnotationAdapter(av, remapper); + } + @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { super.visitLocalVariable(name, remapper.mapDesc(desc), remapper.mapSignature(signature, true), start, end, index); } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + AnnotationVisitor av = super.visitLocalVariableAnnotation(typeRef, + typePath, start, end, index, remapper.mapDesc(desc), visible); + return av == null ? av : new RemappingAnnotationAdapter(av, remapper); + } } diff --git a/src/java/nginx/clojure/asm/commons/RemappingSignatureAdapter.java b/src/java/nginx/clojure/asm/commons/RemappingSignatureAdapter.java index 7624080c..4435bc5b 100644 --- a/src/java/nginx/clojure/asm/commons/RemappingSignatureAdapter.java +++ b/src/java/nginx/clojure/asm/commons/RemappingSignatureAdapter.java @@ -36,8 +36,10 @@ /** * A {@link SignatureVisitor} adapter for type mapping. * + * @deprecated use {@link SignatureRemapper} instead. * @author Eugene Kuleshov */ +@Deprecated public class RemappingSignatureAdapter extends SignatureVisitor { private final SignatureVisitor v; @@ -48,7 +50,7 @@ public class RemappingSignatureAdapter extends SignatureVisitor { public RemappingSignatureAdapter(final SignatureVisitor v, final Remapper remapper) { - this(Opcodes.ASM4, v, remapper); + this(Opcodes.ASM5, v, remapper); } protected RemappingSignatureAdapter(final int api, diff --git a/src/java/nginx/clojure/asm/commons/SerialVersionUIDAdder.java b/src/java/nginx/clojure/asm/commons/SerialVersionUIDAdder.java index 7fee4e35..38c5788d 100644 --- a/src/java/nginx/clojure/asm/commons/SerialVersionUIDAdder.java +++ b/src/java/nginx/clojure/asm/commons/SerialVersionUIDAdder.java @@ -166,9 +166,14 @@ public class SerialVersionUIDAdder extends ClassVisitor { * @param cv * a {@link ClassVisitor} to which this visitor will delegate * calls. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public SerialVersionUIDAdder(final ClassVisitor cv) { - this(Opcodes.ASM4, cv); + this(Opcodes.ASM5, cv); + if (getClass() != SerialVersionUIDAdder.class) { + throw new IllegalStateException(); + } } /** @@ -176,7 +181,7 @@ public SerialVersionUIDAdder(final ClassVisitor cv) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param cv * a {@link ClassVisitor} to which this visitor will delegate * calls. @@ -189,7 +194,7 @@ protected SerialVersionUIDAdder(final int api, final ClassVisitor cv) { } // ------------------------------------------------------------------------ - // Overriden methods + // Overridden methods // ------------------------------------------------------------------------ /* @@ -200,12 +205,14 @@ protected SerialVersionUIDAdder(final int api, final ClassVisitor cv) { public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { - computeSVUID = (access & Opcodes.ACC_INTERFACE) == 0; + computeSVUID = (access & Opcodes.ACC_ENUM) == 0; if (computeSVUID) { this.name = name; this.access = access; - this.interfaces = interfaces; + this.interfaces = new String[interfaces.length]; + System.arraycopy(interfaces, 0, this.interfaces, 0, + interfaces.length); } super.visit(version, access, name, signature, superName, interfaces); @@ -330,8 +337,7 @@ public boolean hasSVUID() { protected void addSVUID(long svuid) { FieldVisitor fv = super.visitField(Opcodes.ACC_FINAL - + Opcodes.ACC_STATIC, "serialVersionUID", "J", null, new Long( - svuid)); + + Opcodes.ACC_STATIC, "serialVersionUID", "J", null, svuid); if (fv != null) { fv.visitEnd(); } @@ -361,6 +367,11 @@ protected long computeSVUID() throws IOException { /* * 2. The class modifiers written as a 32-bit integer. */ + int access = this.access; + if ((access & Opcodes.ACC_INTERFACE) != 0) { + access = (svuidMethods.size() > 0) ? (access | Opcodes.ACC_ABSTRACT) + : (access & ~Opcodes.ACC_ABSTRACT); + } dos.writeInt(access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)); diff --git a/src/java/nginx/clojure/asm/commons/SignatureRemapper.java b/src/java/nginx/clojure/asm/commons/SignatureRemapper.java new file mode 100644 index 00000000..befc6945 --- /dev/null +++ b/src/java/nginx/clojure/asm/commons/SignatureRemapper.java @@ -0,0 +1,159 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package nginx.clojure.asm.commons; + +import java.util.Stack; + +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.signature.SignatureVisitor; + +/** + * A {@link SignatureVisitor} adapter for type mapping. + * + * @author Eugene Kuleshov + */ +public class SignatureRemapper extends SignatureVisitor { + + private final SignatureVisitor v; + + private final Remapper remapper; + + private Stack classNames = new Stack(); + + public SignatureRemapper(final SignatureVisitor v, final Remapper remapper) { + this(Opcodes.ASM5, v, remapper); + } + + protected SignatureRemapper(final int api, final SignatureVisitor v, + final Remapper remapper) { + super(api); + this.v = v; + this.remapper = remapper; + } + + @Override + public void visitClassType(String name) { + classNames.push(name); + v.visitClassType(remapper.mapType(name)); + } + + @Override + public void visitInnerClassType(String name) { + String outerClassName = classNames.pop(); + String className = outerClassName + '$' + name; + classNames.push(className); + String remappedOuter = remapper.mapType(outerClassName) + '$'; + String remappedName = remapper.mapType(className); + int index = remappedName.startsWith(remappedOuter) ? remappedOuter + .length() : remappedName.lastIndexOf('$') + 1; + v.visitInnerClassType(remappedName.substring(index)); + } + + @Override + public void visitFormalTypeParameter(String name) { + v.visitFormalTypeParameter(name); + } + + @Override + public void visitTypeVariable(String name) { + v.visitTypeVariable(name); + } + + @Override + public SignatureVisitor visitArrayType() { + v.visitArrayType(); + return this; + } + + @Override + public void visitBaseType(char descriptor) { + v.visitBaseType(descriptor); + } + + @Override + public SignatureVisitor visitClassBound() { + v.visitClassBound(); + return this; + } + + @Override + public SignatureVisitor visitExceptionType() { + v.visitExceptionType(); + return this; + } + + @Override + public SignatureVisitor visitInterface() { + v.visitInterface(); + return this; + } + + @Override + public SignatureVisitor visitInterfaceBound() { + v.visitInterfaceBound(); + return this; + } + + @Override + public SignatureVisitor visitParameterType() { + v.visitParameterType(); + return this; + } + + @Override + public SignatureVisitor visitReturnType() { + v.visitReturnType(); + return this; + } + + @Override + public SignatureVisitor visitSuperclass() { + v.visitSuperclass(); + return this; + } + + @Override + public void visitTypeArgument() { + v.visitTypeArgument(); + } + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + v.visitTypeArgument(wildcard); + return this; + } + + @Override + public void visitEnd() { + v.visitEnd(); + classNames.pop(); + } +} diff --git a/src/java/nginx/clojure/asm/commons/SimpleRemapper.java b/src/java/nginx/clojure/asm/commons/SimpleRemapper.java index 6e3818e1..9264cb0f 100644 --- a/src/java/nginx/clojure/asm/commons/SimpleRemapper.java +++ b/src/java/nginx/clojure/asm/commons/SimpleRemapper.java @@ -35,7 +35,7 @@ /** * A {@link Remapper} using a {@link Map} to define its mapping. - * + * * @author Eugene Kuleshov */ public class SimpleRemapper extends Remapper { @@ -56,6 +56,12 @@ public String mapMethodName(String owner, String name, String desc) { return s == null ? name : s; } + @Override + public String mapInvokeDynamicMethodName(String name, String desc) { + String s = map('.' + name + desc); + return s == null ? name : s; + } + @Override public String mapFieldName(String owner, String name, String desc) { String s = map(owner + '.' + name); diff --git a/src/java/nginx/clojure/asm/commons/StaticInitMerger.java b/src/java/nginx/clojure/asm/commons/StaticInitMerger.java index 7f0ba19b..4ecb20b4 100644 --- a/src/java/nginx/clojure/asm/commons/StaticInitMerger.java +++ b/src/java/nginx/clojure/asm/commons/StaticInitMerger.java @@ -49,7 +49,7 @@ public class StaticInitMerger extends ClassVisitor { private int counter; public StaticInitMerger(final String prefix, final ClassVisitor cv) { - this(Opcodes.ASM4, prefix, cv); + this(Opcodes.ASM5, prefix, cv); } protected StaticInitMerger(final int api, final String prefix, @@ -78,7 +78,8 @@ public MethodVisitor visitMethod(final int access, final String name, if (clinit == null) { clinit = cv.visitMethod(a, name, desc, null, null); } - clinit.visitMethodInsn(Opcodes.INVOKESTATIC, this.name, n, desc); + clinit.visitMethodInsn(Opcodes.INVOKESTATIC, this.name, n, desc, + false); } else { mv = cv.visitMethod(access, name, desc, signature, exceptions); } diff --git a/src/java/nginx/clojure/asm/commons/TryCatchBlockSorter.java b/src/java/nginx/clojure/asm/commons/TryCatchBlockSorter.java index 92739c43..65450797 100644 --- a/src/java/nginx/clojure/asm/commons/TryCatchBlockSorter.java +++ b/src/java/nginx/clojure/asm/commons/TryCatchBlockSorter.java @@ -57,7 +57,7 @@ public class TryCatchBlockSorter extends MethodNode { public TryCatchBlockSorter(final MethodVisitor mv, final int access, final String name, final String desc, final String signature, final String[] exceptions) { - this(Opcodes.ASM4, mv, access, name, desc, signature, exceptions); + this(Opcodes.ASM5, mv, access, name, desc, signature, exceptions); } protected TryCatchBlockSorter(final int api, final MethodVisitor mv, @@ -85,6 +85,10 @@ private int blockLength(TryCatchBlockNode block) { } }; Collections.sort(tryCatchBlocks, comp); + // Updates the 'target' of each try catch block annotation. + for (int i = 0; i < tryCatchBlocks.size(); ++i) { + tryCatchBlocks.get(i).updateIndex(i); + } if (mv != null) { accept(mv); } diff --git a/src/java/nginx/clojure/asm/commons/package.html b/src/java/nginx/clojure/asm/commons/package.html new file mode 100644 index 00000000..4ce0db85 --- /dev/null +++ b/src/java/nginx/clojure/asm/commons/package.html @@ -0,0 +1,48 @@ + + + +Provides some useful class and method adapters. The preferred way of using +these adapters is by chaining them together and to custom adapters (instead of +inheriting from them). Indeed this approach provides more combination +possibilities than inheritance. For instance, suppose you want to implement an +adapter MyAdapter than needs sorted local variables and intermediate stack map +frame values taking into account the local variables sort. By using inheritance, +this would require MyAdapter to extend AnalyzerAdapter, itself extending +LocalVariablesSorter. But AnalyzerAdapter is not a subclass of +LocalVariablesSorter, so this is not possible. On the contrary, by using +delegation, you can make LocalVariablesSorter delegate to AnalyzerAdapter, +itself delegating to MyAdapter. In this case AnalyzerAdapter computes +intermediate frames based on the output of LocalVariablesSorter, and MyAdapter +can add new locals by calling the newLocal method on LocalVariablesSorter, and +can get the stack map frame state before each instruction by reading the locals +and stack fields in AnalyzerAdapter (this requires references from MyAdapter +back to LocalVariablesSorter and AnalyzerAdapter). + \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/optimizer/AnnotationConstantsCollector.java b/src/java/nginx/clojure/asm/optimizer/AnnotationConstantsCollector.java new file mode 100644 index 00000000..0b4b35aa --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/AnnotationConstantsCollector.java @@ -0,0 +1,147 @@ +/*** + * 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.optimizer; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.Type; + +/** + * An {@link AnnotationVisitor} that collects the {@link Constant}s of the + * annotations it visits. + * + * @author Eric Bruneton + */ +public class AnnotationConstantsCollector extends AnnotationVisitor { + + private final ConstantPool cp; + + public AnnotationConstantsCollector(final AnnotationVisitor av, + final ConstantPool cp) { + super(Opcodes.ASM5, av); + this.cp = cp; + } + + @Override + public void visit(final String name, final Object value) { + if (name != null) { + cp.newUTF8(name); + } + if (value instanceof Byte) { + cp.newInteger(((Byte) value).byteValue()); + } else if (value instanceof Boolean) { + cp.newInteger(((Boolean) value).booleanValue() ? 1 : 0); + } else if (value instanceof Character) { + cp.newInteger(((Character) value).charValue()); + } else if (value instanceof Short) { + cp.newInteger(((Short) value).shortValue()); + } else if (value instanceof Type) { + cp.newUTF8(((Type) value).getDescriptor()); + } else if (value instanceof byte[]) { + byte[] v = (byte[]) value; + for (int i = 0; i < v.length; i++) { + cp.newInteger(v[i]); + } + } else if (value instanceof boolean[]) { + boolean[] v = (boolean[]) value; + for (int i = 0; i < v.length; i++) { + cp.newInteger(v[i] ? 1 : 0); + } + } else if (value instanceof short[]) { + short[] v = (short[]) value; + for (int i = 0; i < v.length; i++) { + cp.newInteger(v[i]); + } + } else if (value instanceof char[]) { + char[] v = (char[]) value; + for (int i = 0; i < v.length; i++) { + cp.newInteger(v[i]); + } + } else if (value instanceof int[]) { + int[] v = (int[]) value; + for (int i = 0; i < v.length; i++) { + cp.newInteger(v[i]); + } + } else if (value instanceof long[]) { + long[] v = (long[]) value; + for (int i = 0; i < v.length; i++) { + cp.newLong(v[i]); + } + } else if (value instanceof float[]) { + float[] v = (float[]) value; + for (int i = 0; i < v.length; i++) { + cp.newFloat(v[i]); + } + } else if (value instanceof double[]) { + double[] v = (double[]) value; + for (int i = 0; i < v.length; i++) { + cp.newDouble(v[i]); + } + } else { + cp.newConst(value); + } + av.visit(name, value); + } + + @Override + public void visitEnum(final String name, final String desc, + final String value) { + if (name != null) { + cp.newUTF8(name); + } + cp.newUTF8(desc); + cp.newUTF8(value); + av.visitEnum(name, desc, value); + } + + @Override + public AnnotationVisitor visitAnnotation(final String name, + final String desc) { + if (name != null) { + cp.newUTF8(name); + } + cp.newUTF8(desc); + return new AnnotationConstantsCollector(av.visitAnnotation(name, desc), + cp); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + if (name != null) { + cp.newUTF8(name); + } + return new AnnotationConstantsCollector(av.visitArray(name), cp); + } + + @Override + public void visitEnd() { + av.visitEnd(); + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/ClassConstantsCollector.java b/src/java/nginx/clojure/asm/optimizer/ClassConstantsCollector.java new file mode 100644 index 00000000..1a842084 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/ClassConstantsCollector.java @@ -0,0 +1,198 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package nginx.clojure.asm.optimizer; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.Attribute; +import nginx.clojure.asm.ClassVisitor; +import nginx.clojure.asm.FieldVisitor; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; + +/** + * A {@link ClassVisitor} that collects the {@link Constant}s of the classes it + * visits. + * + * @author Eric Bruneton + */ +public class ClassConstantsCollector extends ClassVisitor { + + private final ConstantPool cp; + + public ClassConstantsCollector(final ClassVisitor cv, final ConstantPool cp) { + super(Opcodes.ASM5, cv); + this.cp = cp; + } + + @Override + public void visit(final int version, final int access, final String name, + final String signature, final String superName, + final String[] interfaces) { + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cp.newUTF8("Deprecated"); + } + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + cp.newUTF8("Synthetic"); + } + cp.newClass(name); + if (signature != null) { + cp.newUTF8("Signature"); + cp.newUTF8(signature); + } + if (superName != null) { + cp.newClass(superName); + } + if (interfaces != null) { + for (int i = 0; i < interfaces.length; ++i) { + cp.newClass(interfaces[i]); + } + } + cv.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitSource(final String source, final String debug) { + if (source != null) { + cp.newUTF8("SourceFile"); + cp.newUTF8(source); + } + if (debug != null) { + cp.newUTF8("SourceDebugExtension"); + } + cv.visitSource(source, debug); + } + + @Override + public void visitOuterClass(final String owner, final String name, + final String desc) { + cp.newUTF8("EnclosingMethod"); + cp.newClass(owner); + if (name != null && desc != null) { + cp.newNameType(name, desc); + } + cv.visitOuterClass(owner, name, desc); + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleAnnotations"); + } + return new AnnotationConstantsCollector(cv.visitAnnotation(desc, + visible), cp); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleTypeAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleTypeAnnotations"); + } + return new AnnotationConstantsCollector(cv.visitAnnotation(desc, + visible), cp); + } + + @Override + public void visitAttribute(final Attribute attr) { + // can do nothing + cv.visitAttribute(attr); + } + + @Override + public void visitInnerClass(final String name, final String outerName, + final String innerName, final int access) { + cp.newUTF8("InnerClasses"); + if (name != null) { + cp.newClass(name); + } + if (outerName != null) { + cp.newClass(outerName); + } + if (innerName != null) { + cp.newUTF8(innerName); + } + cv.visitInnerClass(name, outerName, innerName, access); + } + + @Override + public FieldVisitor visitField(final int access, final String name, + final String desc, final String signature, final Object value) { + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + cp.newUTF8("Synthetic"); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cp.newUTF8("Deprecated"); + } + cp.newUTF8(name); + cp.newUTF8(desc); + if (signature != null) { + cp.newUTF8("Signature"); + cp.newUTF8(signature); + } + if (value != null) { + cp.newConst(value); + } + return new FieldConstantsCollector(cv.visitField(access, name, desc, + signature, value), cp); + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, + final String desc, final String signature, final String[] exceptions) { + if ((access & Opcodes.ACC_SYNTHETIC) != 0) { + cp.newUTF8("Synthetic"); + } + if ((access & Opcodes.ACC_DEPRECATED) != 0) { + cp.newUTF8("Deprecated"); + } + cp.newUTF8(name); + cp.newUTF8(desc); + if (signature != null) { + cp.newUTF8("Signature"); + cp.newUTF8(signature); + } + if (exceptions != null) { + cp.newUTF8("Exceptions"); + for (int i = 0; i < exceptions.length; ++i) { + cp.newClass(exceptions[i]); + } + } + return new MethodConstantsCollector(cv.visitMethod(access, name, desc, + signature, exceptions), cp); + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/ClassOptimizer.java b/src/java/nginx/clojure/asm/optimizer/ClassOptimizer.java new file mode 100644 index 00000000..1cbdc8ad --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/ClassOptimizer.java @@ -0,0 +1,259 @@ +/*** + * 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.optimizer; + +import java.util.ArrayList; +import java.util.List; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.Attribute; +import nginx.clojure.asm.ClassVisitor; +import nginx.clojure.asm.FieldVisitor; +import nginx.clojure.asm.Label; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; +import nginx.clojure.asm.commons.ClassRemapper; +import nginx.clojure.asm.commons.Remapper; + +/** + * A {@link ClassVisitor} that renames fields and methods, and removes debug + * info. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassOptimizer extends ClassRemapper { + + private String pkgName; + String clsName; + + boolean isInterface = false; + boolean hasClinitMethod = false; + List syntheticClassFields = new ArrayList(); + + public ClassOptimizer(final ClassVisitor cv, final Remapper remapper) { + super(Opcodes.ASM5, cv, remapper); + } + + FieldVisitor syntheticFieldVisitor(final int access, final String name, + final String desc) { + return super.visitField(access, name, desc, null, null); + } + + // ------------------------------------------------------------------------ + // Overridden methods + // ------------------------------------------------------------------------ + + @Override + public void visit(final int version, final int access, final String name, + final String signature, final String superName, + final String[] interfaces) { + super.visit(Opcodes.V1_2, access, name, null, superName, interfaces); + int index = name.lastIndexOf('/'); + if (index > 0) { + pkgName = name.substring(0, index); + } else { + pkgName = ""; + } + clsName = name; + isInterface = (access & Opcodes.ACC_INTERFACE) != 0; + } + + @Override + public void visitSource(final String source, final String debug) { + // remove debug info + } + + @Override + public void visitOuterClass(final String owner, final String name, + final String desc) { + // remove debug info + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + // remove annotations + return null; + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + // remove annotations + return null; + } + + @Override + public void visitAttribute(final Attribute attr) { + // remove non standard attributes + } + + @Override + public void visitInnerClass(final String name, final String outerName, + final String innerName, final int access) { + // remove debug info + } + + @Override + public FieldVisitor visitField(final int access, final String name, + final String desc, final String signature, final Object value) { + String s = remapper.mapFieldName(className, name, desc); + if ("-".equals(s)) { + return null; + } + if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) { + if ((access & Opcodes.ACC_FINAL) != 0 + && (access & Opcodes.ACC_STATIC) != 0 && desc.length() == 1) { + return null; + } + if ("org/objectweb/asm".equals(pkgName) && s.equals(name)) { + System.out.println("INFO: " + clsName + "." + s + + " could be renamed"); + } + super.visitField(access, name, desc, null, value); + } else { + if (!s.equals(name)) { + throw new RuntimeException("The public or protected field " + + className + '.' + name + " must not be renamed."); + } + super.visitField(access, name, desc, null, value); + } + return null; // remove debug info + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, + final String desc, final String signature, final String[] exceptions) { + String s = remapper.mapMethodName(className, name, desc); + if ("-".equals(s)) { + return null; + } + if (name.equals("") && !isInterface) { + hasClinitMethod = true; + MethodVisitor mv = super.visitMethod(access, name, desc, null, + exceptions); + return new MethodVisitor(Opcodes.ASM5, mv) { + @Override + public void visitCode() { + super.visitCode(); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, clsName, + "_clinit_", "()V", false); + } + }; + } + + if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) { + if ("org/objectweb/asm".equals(pkgName) && !name.startsWith("<") + && s.equals(name)) { + System.out.println("INFO: " + clsName + "." + s + + " could be renamed"); + } + return super.visitMethod(access, name, desc, null, exceptions); + } else { + if (!s.equals(name)) { + throw new RuntimeException("The public or protected method " + + className + '.' + name + desc + + " must not be renamed."); + } + return super.visitMethod(access, name, desc, null, exceptions); + } + } + + @Override + protected MethodVisitor createMethodRemapper(MethodVisitor mv) { + return new MethodOptimizer(this, mv, remapper); + } + + @Override + public void visitEnd() { + if (syntheticClassFields.isEmpty()) { + if (hasClinitMethod) { + MethodVisitor mv = cv.visitMethod(Opcodes.ACC_STATIC + | Opcodes.ACC_SYNTHETIC, "_clinit_", "()V", null, null); + mv.visitCode(); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + } else { + MethodVisitor mv = cv.visitMethod(Opcodes.ACC_STATIC + | Opcodes.ACC_SYNTHETIC, "class$", + "(Ljava/lang/String;)Ljava/lang/Class;", null, null); + mv.visitCode(); + Label l0 = new Label(); + Label l1 = new Label(); + Label l2 = new Label(); + mv.visitTryCatchBlock(l0, l1, l2, + "java/lang/ClassNotFoundException"); + mv.visitLabel(l0); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", + "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); + mv.visitLabel(l1); + mv.visitInsn(Opcodes.ARETURN); + mv.visitLabel(l2); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/lang/ClassNotFoundException", "getMessage", + "()Ljava/lang/String;", false); + mv.visitVarInsn(Opcodes.ASTORE, 1); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/NoClassDefFoundError"); + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, + "java/lang/NoClassDefFoundError", "", + "(Ljava/lang/String;)V", false); + mv.visitInsn(Opcodes.ATHROW); + mv.visitMaxs(3, 2); + mv.visitEnd(); + + if (hasClinitMethod) { + mv = cv.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE, + "_clinit_", "()V", null, null); + } else { + mv = cv.visitMethod(Opcodes.ACC_STATIC, "", "()V", + null, null); + } + for (String ldcName : syntheticClassFields) { + String fieldName = "class$" + ldcName.replace('/', '$'); + mv.visitLdcInsn(ldcName.replace('/', '.')); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, clsName, "class$", + "(Ljava/lang/String;)Ljava/lang/Class;", false); + mv.visitFieldInsn(Opcodes.PUTSTATIC, clsName, fieldName, + "Ljava/lang/Class;"); + } + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(1, 0); + mv.visitEnd(); + } + super.visitEnd(); + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/Constant.java b/src/java/nginx/clojure/asm/optimizer/Constant.java new file mode 100644 index 00000000..05d403c2 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/Constant.java @@ -0,0 +1,336 @@ +/*** + * 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.optimizer; + +import java.util.Arrays; + +import nginx.clojure.asm.ClassWriter; +import nginx.clojure.asm.Handle; + +/** + * A constant pool item. + * + * @author Eric Bruneton + */ +class Constant { + + /** + * Type of this constant pool item. A single class is used to represent all + * constant pool item types, in order to minimize the bytecode size of this + * package. The value of this field is I, J, F, D, S, s, C, T, G, M, N, y, + * t, [h..r] (for Constant Integer, Long, Float, Double, STR, UTF8, Class, + * NameType, Fieldref, Methodref, InterfaceMethodref, InvokeDynamic, + * MethodType and MethodHandle constant pool items respectively). + * + * The 9 variable of MethodHandle constants are stored between h and r + * following this table + * tag type interface + * H_GETFIELD 1 h false + * H_GETSTATIC 2 i false + * H_PUTFIELD 3 j false + * H_PUTSTATIC 4 k false + * H_INVOKEVIRTUAL 5 l false + * H_INVOKESTATIC 6 m false + * H_INVOKESPECIAL 7 n false + * H_NEWINVOKESPECIAL 8 o false + * H_INVOKEINTERFACE 9 p true + * H_INVOKESTATIC 6 q true + * H_INVOKESPECIAL 7 r true + */ + char type; + + /** + * Value of this item, for an integer item. + */ + int intVal; + + /** + * Value of this item, for a long item. + */ + long longVal; + + /** + * Value of this item, for a float item. + */ + float floatVal; + + /** + * Value of this item, for a double item. + */ + double doubleVal; + + /** + * First part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal1; + + /** + * Second part of the value of this item, for items that do not hold a + * primitive value. + */ + String strVal2; + + /** + * Third part of the value of this item, for items that do not hold a + * primitive value. + */ + Object objVal3; + + /** + * InvokeDynamic's constant values. + */ + Object[] objVals; + + /** + * The hash code value of this constant pool item. + */ + int hashCode; + + Constant() { + } + + Constant(final Constant i) { + type = i.type; + intVal = i.intVal; + longVal = i.longVal; + floatVal = i.floatVal; + doubleVal = i.doubleVal; + strVal1 = i.strVal1; + strVal2 = i.strVal2; + objVal3 = i.objVal3; + objVals = i.objVals; + hashCode = i.hashCode; + } + + /** + * Sets this item to an integer item. + * + * @param intVal + * the value of this item. + */ + void set(final int intVal) { + this.type = 'I'; + this.intVal = intVal; + this.hashCode = 0x7FFFFFFF & (type + intVal); + } + + /** + * Sets this item to a long item. + * + * @param longVal + * the value of this item. + */ + void set(final long longVal) { + this.type = 'J'; + this.longVal = longVal; + this.hashCode = 0x7FFFFFFF & (type + (int) longVal); + } + + /** + * Sets this item to a float item. + * + * @param floatVal + * the value of this item. + */ + void set(final float floatVal) { + this.type = 'F'; + this.floatVal = floatVal; + this.hashCode = 0x7FFFFFFF & (type + (int) floatVal); + } + + /** + * Sets this item to a double item. + * + * @param doubleVal + * the value of this item. + */ + void set(final double doubleVal) { + this.type = 'D'; + this.doubleVal = doubleVal; + this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal); + } + + /** + * Sets this item to an item that do not hold a primitive value. + * + * @param type + * the type of this item. + * @param strVal1 + * first part of the value of this item. + * @param strVal2 + * second part of the value of this item. + * @param strVal3 + * third part of the value of this item. + */ + void set(final char type, final String strVal1, final String strVal2, + final String strVal3) { + this.type = type; + this.strVal1 = strVal1; + this.strVal2 = strVal2; + this.objVal3 = strVal3; + switch (type) { + case 's': + case 'S': + case 'C': + case 't': + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()); + return; + case 'T': + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode()); + return; + // case 'G': + // case 'M': + // case 'N': + // case 'h' ... 'r': + default: + hashCode = 0x7FFFFFFF & (type + strVal1.hashCode() + * strVal2.hashCode() * strVal3.hashCode()); + } + } + + /** + * Set this item to an InvokeDynamic item. + * + * @param name + * invokedynamic's name. + * @param desc + * invokedynamic's descriptor. + * @param bsm + * bootstrap method. + * @param bsmArgs + * bootstrap method constant arguments. + */ + void set(final String name, final String desc, final Handle bsm, + final Object[] bsmArgs) { + this.type = 'y'; + this.strVal1 = name; + this.strVal2 = desc; + this.objVal3 = bsm; + this.objVals = bsmArgs; + + int hashCode = 'y' + name.hashCode() * desc.hashCode() * bsm.hashCode(); + for (int i = 0; i < bsmArgs.length; i++) { + hashCode *= bsmArgs[i].hashCode(); + } + this.hashCode = 0x7FFFFFFF & hashCode; + } + + void write(final ClassWriter cw) { + switch (type) { + case 'I': + cw.newConst(intVal); + break; + case 'J': + cw.newConst(longVal); + break; + case 'F': + cw.newConst(floatVal); + break; + case 'D': + cw.newConst(doubleVal); + break; + case 'S': + cw.newConst(strVal1); + break; + case 's': + cw.newUTF8(strVal1); + break; + case 'C': + cw.newClass(strVal1); + break; + case 'T': + cw.newNameType(strVal1, strVal2); + break; + case 'G': + cw.newField(strVal1, strVal2, (String) objVal3); + break; + case 'M': + cw.newMethod(strVal1, strVal2, (String) objVal3, false); + break; + case 'N': + cw.newMethod(strVal1, strVal2, (String) objVal3, true); + break; + case 'y': + cw.newInvokeDynamic(strVal1, strVal2, (Handle) objVal3, objVals); + break; + case 't': + cw.newMethodType(strVal1); + break; + default: // 'h' ... 'r' : handle + cw.newHandle(type - 'h' + 1 - ((type >= 'q')? 4: 0), strVal1, strVal2, (String) objVal3, type >= 'p'); + } + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof Constant)) { + return false; + } + Constant c = (Constant) o; + if (c.type == type) { + switch (type) { + case 'I': + return c.intVal == intVal; + case 'J': + return c.longVal == longVal; + case 'F': + return Float.compare(c.floatVal, floatVal) == 0; + case 'D': + return Double.compare(c.doubleVal, doubleVal) == 0; + case 's': + case 'S': + case 'C': + case 't': + return c.strVal1.equals(strVal1); + case 'T': + return c.strVal1.equals(strVal1) && c.strVal2.equals(strVal2); + case 'y': + return c.strVal1.equals(strVal1) && c.strVal2.equals(strVal2) + && c.objVal3.equals(objVal3) + && Arrays.equals(c.objVals, objVals); + // case 'G': + // case 'M': + // case 'N': + // case 'h' ... 'r': + default: + return c.strVal1.equals(strVal1) && c.strVal2.equals(strVal2) + && c.objVal3.equals(objVal3); + } + } + return false; + } + + @Override + public int hashCode() { + return hashCode; + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/ConstantPool.java b/src/java/nginx/clojure/asm/optimizer/ConstantPool.java new file mode 100644 index 00000000..72d97c10 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/ConstantPool.java @@ -0,0 +1,251 @@ +/*** + * 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.optimizer; + +import java.util.HashMap; + +import nginx.clojure.asm.Handle; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.Type; + +/** + * A constant pool. + * + * @author Eric Bruneton + */ +public class ConstantPool extends HashMap { + + private static final long serialVersionUID = 1L; + + private final Constant key1 = new Constant(); + + private final Constant key2 = new Constant(); + + private final Constant key3 = new Constant(); + + private final Constant key4 = new Constant(); + + private final Constant key5 = new Constant(); + + public Constant newInteger(final int value) { + key1.set(value); + Constant result = get(key1); + if (result == null) { + result = new Constant(key1); + put(result); + } + return result; + } + + public Constant newFloat(final float value) { + key1.set(value); + Constant result = get(key1); + if (result == null) { + result = new Constant(key1); + put(result); + } + return result; + } + + public Constant newLong(final long value) { + key1.set(value); + Constant result = get(key1); + if (result == null) { + result = new Constant(key1); + put(result); + } + return result; + } + + public Constant newDouble(final double value) { + key1.set(value); + Constant result = get(key1); + if (result == null) { + result = new Constant(key1); + put(result); + } + return result; + } + + public Constant newUTF8(final String value) { + key1.set('s', value, null, null); + Constant result = get(key1); + if (result == null) { + result = new Constant(key1); + put(result); + } + return result; + } + + private Constant newString(final String value) { + key2.set('S', value, null, null); + Constant result = get(key2); + if (result == null) { + newUTF8(value); + result = new Constant(key2); + put(result); + } + return result; + } + + public Constant newClass(final String value) { + key2.set('C', value, null, null); + Constant result = get(key2); + if (result == null) { + newUTF8(value); + result = new Constant(key2); + put(result); + } + return result; + } + + public Constant newMethodType(final String methodDescriptor) { + key2.set('t', methodDescriptor, null, null); + Constant result = get(key2); + if (result == null) { + newUTF8(methodDescriptor); + result = new Constant(key2); + put(result); + } + return result; + } + + public Constant newHandle(final int tag, final String owner, + final String name, final String desc, final boolean itf) { + key4.set((char) ('h' + tag - 1 + (itf && tag != Opcodes.H_INVOKEINTERFACE? 4: 0)), owner, name, desc); + Constant result = get(key4); + if (result == null) { + if (tag <= Opcodes.H_PUTSTATIC) { + newField(owner, name, desc); + } else { + newMethod(owner, name, desc, itf); + } + result = new Constant(key4); + put(result); + } + return result; + } + + public Constant newConst(final Object cst) { + if (cst instanceof Integer) { + int val = ((Integer) cst).intValue(); + return newInteger(val); + } else if (cst instanceof Float) { + float val = ((Float) cst).floatValue(); + return newFloat(val); + } else if (cst instanceof Long) { + long val = ((Long) cst).longValue(); + return newLong(val); + } else if (cst instanceof Double) { + double val = ((Double) cst).doubleValue(); + return newDouble(val); + } else if (cst instanceof String) { + return newString((String) cst); + } else if (cst instanceof Type) { + Type t = (Type) cst; + int s = t.getSort(); + if (s == Type.OBJECT) { + return newClass(t.getInternalName()); + } else if (s == Type.METHOD) { + return newMethodType(t.getDescriptor()); + } else { // s == primitive type or array + return newClass(t.getDescriptor()); + } + } else if (cst instanceof Handle) { + Handle h = (Handle) cst; + return newHandle(h.getTag(), h.getOwner(), h.getName(), h.getDesc(), h.isInterface()); + } else { + throw new IllegalArgumentException("value " + cst); + } + } + + public Constant newField(final String owner, final String name, + final String desc) { + key3.set('G', owner, name, desc); + Constant result = get(key3); + if (result == null) { + newClass(owner); + newNameType(name, desc); + result = new Constant(key3); + put(result); + } + return result; + } + + public Constant newMethod(final String owner, final String name, + final String desc, final boolean itf) { + key3.set(itf ? 'N' : 'M', owner, name, desc); + Constant result = get(key3); + if (result == null) { + newClass(owner); + newNameType(name, desc); + result = new Constant(key3); + put(result); + } + return result; + } + + public Constant newInvokeDynamic(String name, String desc, Handle bsm, + Object... bsmArgs) { + key5.set(name, desc, bsm, bsmArgs); + Constant result = get(key5); + if (result == null) { + newNameType(name, desc); + newHandle(bsm.getTag(), bsm.getOwner(), bsm.getName(), + bsm.getDesc(), bsm.isInterface()); + for (int i = 0; i < bsmArgs.length; i++) { + newConst(bsmArgs[i]); + } + result = new Constant(key5); + put(result); + } + return result; + } + + public Constant newNameType(final String name, final String desc) { + key2.set('T', name, desc, null); + Constant result = get(key2); + if (result == null) { + newUTF8(name); + newUTF8(desc); + result = new Constant(key2); + put(result); + } + return result; + } + + private Constant get(final Constant key) { + return get((Object) key); + } + + private void put(final Constant cst) { + put(cst, cst); + } +} \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/optimizer/FieldConstantsCollector.java b/src/java/nginx/clojure/asm/optimizer/FieldConstantsCollector.java new file mode 100644 index 00000000..cfb184c3 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/FieldConstantsCollector.java @@ -0,0 +1,89 @@ +/*** + * 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.optimizer; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.Attribute; +import nginx.clojure.asm.FieldVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; + +/** + * A {@link FieldVisitor} that collects the {@link Constant}s of the fields it + * visits. + * + * @author Eric Bruneton + */ +public class FieldConstantsCollector extends FieldVisitor { + + private final ConstantPool cp; + + public FieldConstantsCollector(final FieldVisitor fv, final ConstantPool cp) { + super(Opcodes.ASM5, fv); + this.cp = cp; + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleAnnotations"); + } + return new AnnotationConstantsCollector(fv.visitAnnotation(desc, + visible), cp); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleTypeAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleTypeAnnotations"); + } + return new AnnotationConstantsCollector(fv.visitAnnotation(desc, + visible), cp); + } + + @Override + public void visitAttribute(final Attribute attr) { + // can do nothing + fv.visitAttribute(attr); + } + + @Override + public void visitEnd() { + fv.visitEnd(); + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/JarOptimizer.java b/src/java/nginx/clojure/asm/optimizer/JarOptimizer.java new file mode 100644 index 00000000..2be7b358 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/JarOptimizer.java @@ -0,0 +1,235 @@ +/*** + * 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.optimizer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.zip.GZIPInputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import nginx.clojure.asm.ClassReader; +import nginx.clojure.asm.ClassVisitor; +import nginx.clojure.asm.FieldVisitor; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; + +/** + * A Jar file optimizer. + * + * @author Eric Bruneton + */ +public class JarOptimizer { + + static final Set API = new HashSet(); + static final Map HIERARCHY = new HashMap(); + static boolean nodebug = false; + + public static void main(final String[] args) throws IOException { + File f = new File(args[0]); + InputStream is = new GZIPInputStream(new FileInputStream(f)); + BufferedReader lnr = new LineNumberReader(new InputStreamReader(is)); + while (true) { + String line = lnr.readLine(); + if (line != null) { + if (line.startsWith("class")) { + String c = line.substring(6, line.lastIndexOf(' ')); + String sc = line.substring(line.lastIndexOf(' ') + 1); + HIERARCHY.put(c, sc); + } else { + API.add(line); + } + } else { + break; + } + } + + int argIndex = 1; + if (args[argIndex].equals("-nodebug")) { + nodebug = true; + argIndex++; + } + + optimize(new File(args[argIndex])); + } + + static void optimize(final File f) throws IOException { + if (nodebug && f.getName().contains("debug")) { + return; + } + + if (f.isDirectory()) { + File[] files = f.listFiles(); + for (int i = 0; i < files.length; ++i) { + optimize(files[i]); + } + } else if (f.getName().endsWith(".jar")) { + File g = new File(f.getParentFile(), f.getName() + ".new"); + ZipFile zf = new ZipFile(f); + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(g)); + Enumeration e = zf.entries(); + byte[] buf = new byte[10000]; + while (e.hasMoreElements()) { + ZipEntry ze = e.nextElement(); + if (ze.isDirectory()) { + out.putNextEntry(ze); + continue; + } + out.putNextEntry(ze); + if (ze.getName().endsWith(".class")) { + ClassReader cr = new ClassReader(zf.getInputStream(ze)); + // cr.accept(new ClassDump(), 0); + cr.accept(new ClassVerifier(), 0); + } + InputStream is = zf.getInputStream(ze); + int n; + do { + n = is.read(buf, 0, buf.length); + if (n != -1) { + out.write(buf, 0, n); + } + } while (n != -1); + out.closeEntry(); + } + out.close(); + zf.close(); + if (!f.delete()) { + throw new IOException("Cannot delete file " + f); + } + if (!g.renameTo(f)) { + throw new IOException("Cannot rename file " + g); + } + } + } + + static class ClassDump extends ClassVisitor { + + String owner; + + public ClassDump() { + super(Opcodes.ASM5); + } + + @Override + public void visit(final int version, final int access, + final String name, final String signature, + final String superName, final String[] interfaces) { + owner = name; + if (owner.startsWith("java/")) { + System.out.println("class " + name + ' ' + superName); + } + } + + @Override + public FieldVisitor visitField(final int access, final String name, + final String desc, final String signature, final Object value) { + if (owner.startsWith("java/")) { + System.out.println(owner + ' ' + name); + } + return null; + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, + final String desc, final String signature, + final String[] exceptions) { + if (owner.startsWith("java/")) { + System.out.println(owner + ' ' + name + desc); + } + return null; + } + } + + static class ClassVerifier extends ClassVisitor { + + String owner; + + String method; + + public ClassVerifier() { + super(Opcodes.ASM5); + } + + @Override + public void visit(final int version, final int access, + final String name, final String signature, + final String superName, final String[] interfaces) { + owner = name; + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, + final String desc, final String signature, + final String[] exceptions) { + method = name + desc; + return new MethodVisitor(Opcodes.ASM5) { + @Override + public void visitFieldInsn(final int opcode, + final String owner, final String name, final String desc) { + check(owner, name); + } + + @Override + public void visitMethodInsn(final int opcode, + final String owner, final String name, + final String desc, final boolean itf) { + check(owner, name + desc); + } + }; + } + + void check(String owner, String member) { + if (owner.startsWith("java/")) { + String o = owner; + while (o != null) { + if (API.contains(o + ' ' + member)) { + return; + } + o = HIERARCHY.get(o); + } + System.out.println("WARNING: " + owner + ' ' + member + + " called in " + this.owner + ' ' + method + + " is not defined in JDK 1.3 API"); + } + } + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/MethodConstantsCollector.java b/src/java/nginx/clojure/asm/optimizer/MethodConstantsCollector.java new file mode 100644 index 00000000..76aef7a4 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/MethodConstantsCollector.java @@ -0,0 +1,224 @@ +/*** + * 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.optimizer; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.Handle; +import nginx.clojure.asm.Label; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; + +/** + * An {@link MethodVisitor} that collects the {@link Constant}s of the methods + * it visits. + * + * @author Eric Bruneton + */ +public class MethodConstantsCollector extends MethodVisitor { + + private final ConstantPool cp; + + public MethodConstantsCollector(final MethodVisitor mv, + final ConstantPool cp) { + super(Opcodes.ASM5, mv); + this.cp = cp; + } + + @Override + public void visitParameter(String name, int access) { + cp.newUTF8("MethodParameters"); + if (name != null) { + cp.newUTF8(name); + } + mv.visitParameter(name, access); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + cp.newUTF8("AnnotationDefault"); + return new AnnotationConstantsCollector(mv.visitAnnotationDefault(), cp); + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, + final boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleAnnotations"); + } + return new AnnotationConstantsCollector(mv.visitAnnotation(desc, + visible), cp); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleTypeAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleTypeAnnotations"); + } + return new AnnotationConstantsCollector(mv.visitAnnotation(desc, + visible), cp); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, + final String desc, final boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleParameterAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleParameterAnnotations"); + } + return new AnnotationConstantsCollector(mv.visitParameterAnnotation( + parameter, desc, visible), cp); + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + cp.newClass(type); + mv.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, + final String name, final String desc) { + cp.newField(owner, name, desc); + mv.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + cp.newMethod(owner, name, desc, itf); + mv.visitMethodInsn(opcode, owner, name, desc, itf); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, + Object... bsmArgs) { + cp.newInvokeDynamic(name, desc, bsm, bsmArgs); + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + + @Override + public void visitLdcInsn(final Object cst) { + cp.newConst(cst); + mv.visitLdcInsn(cst); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + cp.newClass(desc); + mv.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleTypeAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleTypeAnnotations"); + } + return new AnnotationConstantsCollector(mv.visitInsnAnnotation(typeRef, + typePath, desc, visible), cp); + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, + final Label handler, final String type) { + if (type != null) { + cp.newClass(type); + } + mv.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleTypeAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleTypeAnnotations"); + } + return new AnnotationConstantsCollector(mv.visitTryCatchAnnotation( + typeRef, typePath, desc, visible), cp); + } + + @Override + public void visitLocalVariable(final String name, final String desc, + final String signature, final Label start, final Label end, + final int index) { + if (signature != null) { + cp.newUTF8("LocalVariableTypeTable"); + cp.newUTF8(name); + cp.newUTF8(signature); + } + cp.newUTF8("LocalVariableTable"); + cp.newUTF8(name); + cp.newUTF8(desc); + mv.visitLocalVariable(name, desc, signature, start, end, index); + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + cp.newUTF8(desc); + if (visible) { + cp.newUTF8("RuntimeVisibleTypeAnnotations"); + } else { + cp.newUTF8("RuntimeInvisibleTypeAnnotations"); + } + return new AnnotationConstantsCollector( + mv.visitLocalVariableAnnotation(typeRef, typePath, start, end, + index, desc, visible), cp); + } + + @Override + public void visitLineNumber(final int line, final Label start) { + cp.newUTF8("LineNumberTable"); + mv.visitLineNumber(line, start); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + cp.newUTF8("Code"); + mv.visitMaxs(maxStack, maxLocals); + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/MethodOptimizer.java b/src/java/nginx/clojure/asm/optimizer/MethodOptimizer.java new file mode 100644 index 00000000..397d3d23 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/MethodOptimizer.java @@ -0,0 +1,180 @@ +/*** + * 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.optimizer; + +import java.util.HashMap; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.Attribute; +import nginx.clojure.asm.FieldVisitor; +import nginx.clojure.asm.Label; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.Type; +import nginx.clojure.asm.TypePath; +import nginx.clojure.asm.commons.MethodRemapper; +import nginx.clojure.asm.commons.Remapper; + +/** + * A {@link MethodVisitor} that renames fields and methods, and removes debug + * info. + * + * @author Eugene Kuleshov + */ +public class MethodOptimizer extends MethodRemapper implements Opcodes { + + private final ClassOptimizer classOptimizer; + + public MethodOptimizer(ClassOptimizer classOptimizer, MethodVisitor mv, + Remapper remapper) { + super(Opcodes.ASM5, mv, remapper); + this.classOptimizer = classOptimizer; + } + + // ------------------------------------------------------------------------ + // Overridden methods + // ------------------------------------------------------------------------ + + @Override + public void visitParameter(String name, int access) { + // remove parameter info + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + // remove annotations + return null; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // remove annotations + return null; + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + return null; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, + final String desc, final boolean visible) { + // remove annotations + return null; + } + + @Override + public void visitLocalVariable(final String name, final String desc, + final String signature, final Label start, final Label end, + final int index) { + // remove debug info + } + + @Override + public void visitLineNumber(final int line, final Label start) { + // remove debug info + } + + @Override + public void visitFrame(int type, int local, Object[] local2, int stack, + Object[] stack2) { + // remove frame info + } + + @Override + public void visitAttribute(Attribute attr) { + // remove non standard attributes + } + + @Override + public void visitLdcInsn(Object cst) { + if (!(cst instanceof Type)) { + super.visitLdcInsn(cst); + return; + } + + // transform Foo.class for 1.2 compatibility + String ldcName = ((Type) cst).getInternalName(); + String fieldName = "class$" + ldcName.replace('/', '$'); + if (!classOptimizer.syntheticClassFields.contains(ldcName)) { + classOptimizer.syntheticClassFields.add(ldcName); + FieldVisitor fv = classOptimizer.syntheticFieldVisitor(ACC_STATIC + | ACC_SYNTHETIC, fieldName, "Ljava/lang/Class;"); + fv.visitEnd(); + } + + String clsName = classOptimizer.clsName; + mv.visitFieldInsn(GETSTATIC, clsName, fieldName, "Ljava/lang/Class;"); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + // rewrite boxing method call to use constructor to keep 1.3/1.4 + // compatibility + String[] constructorParams; + if (opcode == INVOKESTATIC && name.equals("valueOf") + && (constructorParams = BOXING_MAP.get(owner + desc)) != null) { + String type = constructorParams[0]; + String initDesc = constructorParams[1]; + super.visitTypeInsn(NEW, type); + super.visitInsn(DUP); + super.visitInsn((initDesc == "(J)V" || initDesc == "(D)V") ? DUP2_X2 + : DUP2_X1); + super.visitInsn(POP2); + super.visitMethodInsn(INVOKESPECIAL, type, "", initDesc, + false); + return; + } + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + + private static final HashMap BOXING_MAP; + static { + String[][] boxingNames = { + // Boolean.valueOf is 1.4 and is used by the xml package, so no + // rewrite + { "java/lang/Byte", "(B)V" }, { "java/lang/Short", "(S)V" }, + { "java/lang/Character", "(C)V" }, + { "java/lang/Integer", "(I)V" }, { "java/lang/Long", "(J)V" }, + { "java/lang/Float", "(F)V" }, { "java/lang/Double", "(D)V" }, }; + HashMap map = new HashMap(); + for (String[] boxingName : boxingNames) { + String wrapper = boxingName[0]; + String desc = boxingName[1]; + String boxingMethod = wrapper + '(' + desc.charAt(1) + ")L" + + wrapper + ';'; + map.put(boxingMethod, boxingName); + } + BOXING_MAP = map; + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/NameMapping.java b/src/java/nginx/clojure/asm/optimizer/NameMapping.java new file mode 100644 index 00000000..f01cd07d --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/NameMapping.java @@ -0,0 +1,114 @@ +/*** + * 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.optimizer; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import nginx.clojure.asm.Type; + +/** + * A MAPPING from names to names, used to rename classes, fields and methods. + * + * @author Eric Bruneton + */ +public class NameMapping { + + public final Properties mapping; + + public final Set unused; + + public NameMapping(final String file) throws IOException { + mapping = new Properties(); + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + mapping.load(is); + unused = new HashSet(mapping.keySet()); + } finally { + if (is != null) { + is.close(); + } + } + } + + public String map(final String name) { + String s = (String) mapping.get(name); + if (s == null) { + int p = name.indexOf('.'); + if (p == -1) { + s = name; + } else { + int q = name.indexOf('('); + if (q == -1) { + s = name.substring(p + 1); + } else { + s = name.substring(p + 1, q); + } + } + } else { + unused.remove(name); + } + return s; + } + + public String fix(final String desc) { + if (desc.startsWith("(")) { + Type[] arguments = Type.getArgumentTypes(desc); + Type result = Type.getReturnType(desc); + for (int i = 0; i < arguments.length; ++i) { + arguments[i] = fix(arguments[i]); + } + result = fix(result); + return Type.getMethodDescriptor(result, arguments); + } else { + return fix(Type.getType(desc)).getDescriptor(); + } + } + + private Type fix(final Type t) { + if (t.getSort() == Type.OBJECT) { + return Type.getObjectType(map(t.getInternalName())); + } else if (t.getSort() == Type.ARRAY) { + String s = fix(t.getElementType()).getDescriptor(); + for (int i = 0; i < t.getDimensions(); ++i) { + s = '[' + s; + } + return Type.getType(s); + } else { + return t; + } + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/Shrinker.java b/src/java/nginx/clojure/asm/optimizer/Shrinker.java new file mode 100644 index 00000000..a9c0a58b --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/Shrinker.java @@ -0,0 +1,282 @@ +/*** + * 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.optimizer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; + +import nginx.clojure.asm.ClassReader; +import nginx.clojure.asm.ClassWriter; +import nginx.clojure.asm.Handle; +import nginx.clojure.asm.Type; +import nginx.clojure.asm.commons.Remapper; +import nginx.clojure.asm.commons.SimpleRemapper; + +/** + * A class file shrinker utility. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class Shrinker { + + static final HashMap MAPPING = new HashMap(); + + public static void main(final String[] args) throws IOException { + Properties properties = new Properties(); + int n = args.length - 1; + for (int i = 0; i < n - 1; ++i) { + properties.load(new FileInputStream(args[i])); + } + + for (Map.Entry entry : properties.entrySet()) { + MAPPING.put((String) entry.getKey(), (String) entry.getValue()); + } + + final Set unused = new HashSet(MAPPING.keySet()); + + File f = new File(args[n - 1]); + File d = new File(args[n]); + + optimize(f, d, new SimpleRemapper(MAPPING) { + @Override + public String map(String key) { + String s = super.map(key); + if (s != null) { + unused.remove(key); + } + return s; + } + }); + + Iterator i = unused.iterator(); + while (i.hasNext()) { + String s = i.next(); + if (!s.endsWith("/remove")) { + System.out.println("INFO: unused mapping " + s); + } + } + } + + static void optimize(final File f, final File d, final Remapper remapper) + throws IOException { + if (f.isDirectory()) { + File[] files = f.listFiles(); + for (int i = 0; i < files.length; ++i) { + optimize(files[i], d, remapper); + } + } else if (f.getName().endsWith(".class")) { + ConstantPool cp = new ConstantPool(); + ClassReader cr = new ClassReader(new FileInputStream(f)); + // auto-boxing removal requires to recompute the maxs + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + ClassConstantsCollector ccc = new ClassConstantsCollector(cw, cp); + ClassOptimizer co = new ClassOptimizer(ccc, remapper); + cr.accept(co, ClassReader.SKIP_DEBUG); + + Set constants = new TreeSet( + new ConstantComparator()); + constants.addAll(cp.values()); + + cr = new ClassReader(cw.toByteArray()); + cw = new ClassWriter(0); + Iterator i = constants.iterator(); + while (i.hasNext()) { + Constant c = i.next(); + c.write(cw); + } + cr.accept(cw, ClassReader.SKIP_DEBUG); + + if (MAPPING.get(cr.getClassName() + "/remove") != null) { + return; + } + String n = remapper.mapType(cr.getClassName()); + File g = new File(d, n + ".class"); + if (!g.exists() || g.lastModified() < f.lastModified()) { + if (!g.getParentFile().exists() && !g.getParentFile().mkdirs()) { + throw new IOException("Cannot create directory " + + g.getParentFile()); + } + OutputStream os = new FileOutputStream(g); + try { + os.write(cw.toByteArray()); + } finally { + os.close(); + } + } + } + } + + static class ConstantComparator implements Comparator { + + public int compare(final Constant c1, final Constant c2) { + int d = getSort(c1) - getSort(c2); + if (d == 0) { + switch (c1.type) { + case 'I': + return ((Integer)c1.intVal).compareTo(c2.intVal); + case 'J': + return ((Long)c1.longVal).compareTo(c2.longVal); + case 'F': + return ((Float)c1.floatVal).compareTo(c2.floatVal); + case 'D': + return ((Double)c1.doubleVal).compareTo(c2.doubleVal); + case 's': + case 'S': + case 'C': + case 't': + return c1.strVal1.compareTo(c2.strVal1); + case 'T': + d = c1.strVal1.compareTo(c2.strVal1); + if (d == 0) { + d = c1.strVal2.compareTo(c2.strVal2); + } + break; + case 'y': + d = c1.strVal1.compareTo(c2.strVal1); + if (d == 0) { + d = c1.strVal2.compareTo(c2.strVal2); + if (d == 0) { + Handle bsm1 = (Handle) c1.objVal3; + Handle bsm2 = (Handle) c2.objVal3; + d = compareHandle(bsm1, bsm2); + if (d == 0) { + d = compareObjects(c1.objVals, c2.objVals); + } + } + } + break; + + default: + d = c1.strVal1.compareTo(c2.strVal1); + if (d == 0) { + d = c1.strVal2.compareTo(c2.strVal2); + if (d == 0) { + d = ((String) c1.objVal3) + .compareTo((String) c2.objVal3); + } + } + } + } + return d; + } + + private static int compareHandle(Handle h1, Handle h2) { + int d = h1.getTag() - h2.getTag(); + if (d == 0) { + d = h1.getOwner().compareTo(h2.getOwner()); + if (d == 0) { + d = h1.getName().compareTo(h2.getName()); + if (d == 0) { + d = h1.getDesc().compareTo(h2.getDesc()); + } + } + } + return d; + } + + private static int compareType(Type mtype1, Type mtype2) { + return mtype1.getDescriptor().compareTo(mtype2.getDescriptor()); + } + + @SuppressWarnings("unchecked") + private static int compareObjects(Object[] objVals1, Object[] objVals2) { + int length = objVals1.length; + int d = length - objVals2.length; + if (d == 0) { + for (int i = 0; i < length; i++) { + Object objVal1 = objVals1[i]; + Object objVal2 = objVals2[i]; + d = objVal1.getClass().getName() + .compareTo(objVal2.getClass().getName()); + if (d == 0) { + if (objVal1 instanceof Type) { + d = compareType((Type) objVal1, (Type) objVal2); + } else if (objVal1 instanceof Handle) { + d = compareHandle((Handle) objVal1, + (Handle) objVal2); + } else { + d = ((Comparable) objVal1).compareTo(objVal2); + } + } + + if (d != 0) { + return d; + } + } + } + return 0; + } + + private static int getSort(final Constant c) { + switch (c.type) { + case 'I': + return 0; + case 'J': + return 1; + case 'F': + return 2; + case 'D': + return 3; + case 's': + return 4; + case 'S': + return 5; + case 'C': + return 6; + case 'T': + return 7; + case 'G': + return 8; + case 'M': + return 9; + case 'N': + return 10; + case 'y': + return 11; + case 't': + return 12; + default: + return 100 + c.type - 'h'; + } + } + } +} diff --git a/src/java/nginx/clojure/asm/optimizer/jdk1.2.2_017.txt.gz b/src/java/nginx/clojure/asm/optimizer/jdk1.2.2_017.txt.gz new file mode 100644 index 00000000..39cdf9d9 Binary files /dev/null and b/src/java/nginx/clojure/asm/optimizer/jdk1.2.2_017.txt.gz differ diff --git a/src/java/nginx/clojure/asm/optimizer/jdk1.3.1_19.txt.gz b/src/java/nginx/clojure/asm/optimizer/jdk1.3.1_19.txt.gz new file mode 100644 index 00000000..a3c7aa68 Binary files /dev/null and b/src/java/nginx/clojure/asm/optimizer/jdk1.3.1_19.txt.gz differ diff --git a/src/java/nginx/clojure/asm/optimizer/shrink-annotations.properties b/src/java/nginx/clojure/asm/optimizer/shrink-annotations.properties new file mode 100644 index 00000000..03fec2e1 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/shrink-annotations.properties @@ -0,0 +1,53 @@ +############################################################################### +#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. +############################################################################### + +# class mappings + +org/objectweb/asm/AnnotationWriter/remove=true + +# field mappings + +org/objectweb/asm/ClassWriter.anns=- +org/objectweb/asm/ClassWriter.ianns=- + +org/objectweb/asm/FieldWriter.anns=- +org/objectweb/asm/FieldWriter.ianns=- + +org/objectweb/asm/MethodWriter.annd=- +org/objectweb/asm/MethodWriter.anns=- +org/objectweb/asm/MethodWriter.ianns=- +org/objectweb/asm/MethodWriter.panns=- +org/objectweb/asm/MethodWriter.ipanns=- + +# method mappings + +org/objectweb/asm/ClassReader.readAnnotationValue(I[CLjava/lang/String;Lorg/objectweb/asm/AnnotationVisitor;)I=- +org/objectweb/asm/ClassReader.readAnnotationValues(I[CZLorg/objectweb/asm/AnnotationVisitor;)I=- +org/objectweb/asm/ClassReader.readParameterAnnotations(I[CZLorg/objectweb/asm/MethodVisitor;)V=- diff --git a/src/java/nginx/clojure/asm/optimizer/shrink-frames.properties b/src/java/nginx/clojure/asm/optimizer/shrink-frames.properties new file mode 100644 index 00000000..ecf580f0 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/shrink-frames.properties @@ -0,0 +1,62 @@ +############################################################################### +#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. +############################################################################### + +# class mappings + +org/objectweb/asm/Frame/remove=true + +# field mappings + +org/objectweb/asm/ClassWriter.typeCount=- +org/objectweb/asm/ClassWriter.typeTable=- + +org/objectweb/asm/Label.frame=- + +org/objectweb/asm/MethodWriter.frameCount=- +org/objectweb/asm/MethodWriter.stackMap=- +org/objectweb/asm/MethodWriter.previousFrameOffset=- +org/objectweb/asm/MethodWriter.previousFrame=- +org/objectweb/asm/MethodWriter.frameIndex=- +org/objectweb/asm/MethodWriter.frame=- + +# method mappings + +org/objectweb/asm/ClassReader.readFrameType([Ljava/lang/Object;II[C[Lorg/objectweb/asm/Label;)I=- + +org/objectweb/asm/ClassWriter.addType(Ljava/lang/String;)I=- +org/objectweb/asm/ClassWriter.addUninitializedType(Ljava/lang/String;I)I=- +org/objectweb/asm/ClassWriter.addType(Lorg/objectweb/asm/Item;)Lorg/objectweb/asm/Item;=- +org/objectweb/asm/ClassWriter.getMergedType(II)I=- + +org/objectweb/asm/MethodWriter.startFrame(III)V=- +org/objectweb/asm/MethodWriter.endFrame()V=- +org/objectweb/asm/MethodWriter.writeFrame()V=- +org/objectweb/asm/MethodWriter.writeFrameTypes(II)V=- +org/objectweb/asm/MethodWriter.writeFrameType(Ljava/lang/Object;)V=- diff --git a/src/java/nginx/clojure/asm/optimizer/shrink-resize.properties b/src/java/nginx/clojure/asm/optimizer/shrink-resize.properties new file mode 100644 index 00000000..97f7e34f --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/shrink-resize.properties @@ -0,0 +1,37 @@ +############################################################################### +#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. +############################################################################### + +# class mappings + +# field mappings + +# method mappings + +org/objectweb/asm/MethodWriter.resizeInstructions()V=- \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/optimizer/shrink-signatures.properties b/src/java/nginx/clojure/asm/optimizer/shrink-signatures.properties new file mode 100644 index 00000000..6a486234 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/shrink-signatures.properties @@ -0,0 +1,43 @@ +############################################################################### +#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. +############################################################################### + +# class mappings + +org/objectweb/asm/signature/SignatureReader/remove=true +org/objectweb/asm/signature/SignatureVisitor/remove=true +org/objectweb/asm/signature/SignatureWriter/remove=true + +# field mappings + +org/objectweb/asm/ClassWriter.signature=- + +org/objectweb/asm/FieldWriter.signature=- + +org/objectweb/asm/MethodWriter.signature=- diff --git a/src/java/nginx/clojure/asm/optimizer/shrink-writer.properties b/src/java/nginx/clojure/asm/optimizer/shrink-writer.properties new file mode 100644 index 00000000..1c83ca21 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/shrink-writer.properties @@ -0,0 +1,66 @@ +############################################################################### +#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. +############################################################################### + +# class mappings + +org/objectweb/asm/AnnotationWriter/remove=true +org/objectweb/asm/ByteVector/remove=true +org/objectweb/asm/ClassWriter/remove=true +org/objectweb/asm/Edge/remove=true +org/objectweb/asm/FieldWriter/remove=true +org/objectweb/asm/Frame/remove=true +org/objectweb/asm/Handler/remove=true +org/objectweb/asm/Item/remove=true +org/objectweb/asm/MethodWriter/remove=true + +# field mappings + +org/objectweb/asm/Label.position=- +org/objectweb/asm/Label.referenceCount=- +org/objectweb/asm/Label.srcAndRefPositions=- +org/objectweb/asm/Label.inputStackTop=- +org/objectweb/asm/Label.outputStackMax=- +org/objectweb/asm/Label.frame=- +org/objectweb/asm/Label.successor=- +org/objectweb/asm/Label.successors=- +org/objectweb/asm/Label.next=- + +# method mappings + +org/objectweb/asm/ClassReader.copyPool(Lorg/objectweb/asm/ClassWriter;)V=- + +org/objectweb/asm/Label.addReference(II)V=- +org/objectweb/asm/Label.put(Lorg/objectweb/asm/MethodWriter;Lorg/objectweb/asm/ByteVector;IZ)V=- +org/objectweb/asm/Label.resolve(Lorg/objectweb/asm/MethodWriter;I[B)Z=- +org/objectweb/asm/Label.getFirst()Lorg/objectweb/asm/Label;=- +org/objectweb/asm/Label.inSubroutine(J)Z=- +org/objectweb/asm/Label.inSameSubroutine(Lorg/objectweb/asm/Label;)Z=- +org/objectweb/asm/Label.addToSubroutine(JI)V=- +org/objectweb/asm/Label.visitSubroutine(Lorg/objectweb/asm/Label;JI)V=- diff --git a/src/java/nginx/clojure/asm/optimizer/shrink.properties b/src/java/nginx/clojure/asm/optimizer/shrink.properties new file mode 100644 index 00000000..f7caece5 --- /dev/null +++ b/src/java/nginx/clojure/asm/optimizer/shrink.properties @@ -0,0 +1,379 @@ +############################################################################### +#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. +############################################################################### + +# class mappings + +#org/objectweb/asm/Edge=org/objectweb/asm/a +#org/objectweb/asm/Item=org/objectweb/asm/b +#org/objectweb/asm/FieldWriter=org/objectweb/asm/c +#org/objectweb/asm/MethodWriter=org/objectweb/asm/d +#org/objectweb/asm/AnnotationWriter=org/objectweb/asm/e +#org/objectweb/asm/Context=org/objectweb/asm/f + +java/lang/StringBuilder=java/lang/StringBuffer + + +# field mappings + +org/objectweb/asm/AnnotationWriter.cw=a +org/objectweb/asm/AnnotationWriter.size=b +org/objectweb/asm/AnnotationWriter.named=c +org/objectweb/asm/AnnotationWriter.bv=d +org/objectweb/asm/AnnotationWriter.parent=e +org/objectweb/asm/AnnotationWriter.offset=f +org/objectweb/asm/AnnotationWriter.next=g +org/objectweb/asm/AnnotationWriter.prev=h + +org/objectweb/asm/Attribute.next=a +org/objectweb/asm/Attribute.value=b + +org/objectweb/asm/ByteVector.data=a +org/objectweb/asm/ByteVector.length=b + +org/objectweb/asm/ClassReader.items=a +org/objectweb/asm/ClassReader.strings=c +org/objectweb/asm/ClassReader.maxStringLength=d +#org/objectweb/asm/ClassReader.header=e + +org/objectweb/asm/Context.attrs=a +org/objectweb/asm/Context.flags=b +org/objectweb/asm/Context.buffer=c +org/objectweb/asm/Context.bootstrapMethods=d +org/objectweb/asm/Context.access=e +org/objectweb/asm/Context.name=f +org/objectweb/asm/Context.desc=g +org/objectweb/asm/Context.labels=h +org/objectweb/asm/Context.typeRef=i +org/objectweb/asm/Context.typePath=j +org/objectweb/asm/Context.offset=k +org/objectweb/asm/Context.start=l +org/objectweb/asm/Context.end=m +org/objectweb/asm/Context.index=n +org/objectweb/asm/Context.offset=o +org/objectweb/asm/Context.mode=p +org/objectweb/asm/Context.localCount=q +org/objectweb/asm/Context.localDiff=r +org/objectweb/asm/Context.local=s +org/objectweb/asm/Context.stackCount=t +org/objectweb/asm/Context.stack=u + +org/objectweb/asm/ClassWriter.TYPE=a +org/objectweb/asm/ClassWriter.version=b +org/objectweb/asm/ClassWriter.index=c +org/objectweb/asm/ClassWriter.pool=d +org/objectweb/asm/ClassWriter.items=e +org/objectweb/asm/ClassWriter.threshold=f +org/objectweb/asm/ClassWriter.key=g +org/objectweb/asm/ClassWriter.key2=h +org/objectweb/asm/ClassWriter.key3=i +org/objectweb/asm/ClassWriter.key4=j +org/objectweb/asm/ClassWriter.access=k +org/objectweb/asm/ClassWriter.name=l +org/objectweb/asm/ClassWriter.signature=m +org/objectweb/asm/ClassWriter.superName=n +org/objectweb/asm/ClassWriter.interfaceCount=o +org/objectweb/asm/ClassWriter.interfaces=p +org/objectweb/asm/ClassWriter.sourceFile=q +org/objectweb/asm/ClassWriter.sourceDebug=r +org/objectweb/asm/ClassWriter.enclosingMethodOwner=s +org/objectweb/asm/ClassWriter.enclosingMethod=t +org/objectweb/asm/ClassWriter.anns=u +org/objectweb/asm/ClassWriter.ianns=v +org/objectweb/asm/ClassWriter.tanns=N +org/objectweb/asm/ClassWriter.itanns=O +org/objectweb/asm/ClassWriter.attrs=w +org/objectweb/asm/ClassWriter.innerClassesCount=x +org/objectweb/asm/ClassWriter.innerClasses=y +org/objectweb/asm/ClassWriter.bootstrapMethodsCount=z +org/objectweb/asm/ClassWriter.bootstrapMethods=A +org/objectweb/asm/ClassWriter.firstField=B +org/objectweb/asm/ClassWriter.lastField=C +org/objectweb/asm/ClassWriter.firstMethod=D +org/objectweb/asm/ClassWriter.lastMethod=E +org/objectweb/asm/ClassWriter.compute=F +org/objectweb/asm/ClassWriter.typeCount=G +org/objectweb/asm/ClassWriter.typeTable=H +org/objectweb/asm/ClassWriter.thisName=I +org/objectweb/asm/ClassWriter.hasAsmInsns=J +org/objectweb/asm/ClassWriter.cr=K + +org/objectweb/asm/Edge.info=a +org/objectweb/asm/Edge.successor=b +org/objectweb/asm/Edge.next=c + +org/objectweb/asm/Handler.start=a +org/objectweb/asm/Handler.end=b +org/objectweb/asm/Handler.handler=c +org/objectweb/asm/Handler.desc=d +org/objectweb/asm/Handler.type=e +org/objectweb/asm/Handler.next=f + +org/objectweb/asm/FieldWriter.cw=b +org/objectweb/asm/FieldWriter.access=c +org/objectweb/asm/FieldWriter.name=d +org/objectweb/asm/FieldWriter.desc=e +org/objectweb/asm/FieldWriter.signature=f +org/objectweb/asm/FieldWriter.value=g +org/objectweb/asm/FieldWriter.anns=h +org/objectweb/asm/FieldWriter.ianns=i +org/objectweb/asm/FieldWriter.tanns=k +org/objectweb/asm/FieldWriter.itanns=l +org/objectweb/asm/FieldWriter.attrs=j + +org/objectweb/asm/Item.index=a +org/objectweb/asm/Item.type=b +org/objectweb/asm/Item.intVal=c +org/objectweb/asm/Item.longVal=d +org/objectweb/asm/Item.strVal1=g +org/objectweb/asm/Item.strVal2=h +org/objectweb/asm/Item.strVal3=i +org/objectweb/asm/Item.hashCode=j +org/objectweb/asm/Item.next=k + +org/objectweb/asm/Label.status=a +org/objectweb/asm/Label.line=b +org/objectweb/asm/Label.position=c +org/objectweb/asm/Label.referenceCount=d +org/objectweb/asm/Label.srcAndRefPositions=e +org/objectweb/asm/Label.inputStackTop=f +org/objectweb/asm/Label.outputStackMax=g +org/objectweb/asm/Label.frame=h +org/objectweb/asm/Label.successor=i +org/objectweb/asm/Label.successors=j +org/objectweb/asm/Label.next=k + +org/objectweb/asm/Frame.SIZE=a +org/objectweb/asm/Frame.owner=b +org/objectweb/asm/CurrentFrame.owner=b +org/objectweb/asm/Frame.inputLocals=c +org/objectweb/asm/Frame.inputStack=d +org/objectweb/asm/Frame.outputLocals=e +org/objectweb/asm/Frame.outputStack=f +org/objectweb/asm/Frame.outputStackTop=g +org/objectweb/asm/Frame.initializationCount=h +org/objectweb/asm/Frame.initializations=i + +org/objectweb/asm/MethodWriter.cw=b +org/objectweb/asm/MethodWriter.access=c +org/objectweb/asm/MethodWriter.name=d +org/objectweb/asm/MethodWriter.desc=e +org/objectweb/asm/MethodWriter.descriptor=f +org/objectweb/asm/MethodWriter.signature=g +org/objectweb/asm/MethodWriter.classReaderOffset=h +org/objectweb/asm/MethodWriter.classReaderLength=i +org/objectweb/asm/MethodWriter.exceptionCount=j +org/objectweb/asm/MethodWriter.exceptions=k +org/objectweb/asm/MethodWriter.annd=l +org/objectweb/asm/MethodWriter.anns=m +org/objectweb/asm/MethodWriter.ianns=n +org/objectweb/asm/MethodWriter.tanns=U +org/objectweb/asm/MethodWriter.itanns=V +org/objectweb/asm/MethodWriter.panns=o +org/objectweb/asm/MethodWriter.ipanns=p +org/objectweb/asm/MethodWriter.attrs=q +org/objectweb/asm/MethodWriter.code=r +org/objectweb/asm/MethodWriter.maxStack=s +org/objectweb/asm/MethodWriter.maxLocals=t +org/objectweb/asm/MethodWriter.currentLocals=T +org/objectweb/asm/MethodWriter.frameCount=u +org/objectweb/asm/MethodWriter.stackMap=v +org/objectweb/asm/MethodWriter.previousFrameOffset=w +org/objectweb/asm/MethodWriter.previousFrame=x +#org/objectweb/asm/MethodWriter.frameIndex=y +org/objectweb/asm/MethodWriter.frame=z +org/objectweb/asm/MethodWriter.handlerCount=A +org/objectweb/asm/MethodWriter.firstHandler=B +org/objectweb/asm/MethodWriter.lastHandler=C +org/objectweb/asm/MethodWriter.methodParametersCount=Z +org/objectweb/asm/MethodWriter.methodParameters=$ +org/objectweb/asm/MethodWriter.localVarCount=D +org/objectweb/asm/MethodWriter.localVar=E +org/objectweb/asm/MethodWriter.localVarTypeCount=F +org/objectweb/asm/MethodWriter.localVarType=G +org/objectweb/asm/MethodWriter.lineNumberCount=H +org/objectweb/asm/MethodWriter.lineNumber=I +org/objectweb/asm/MethodWriter.lastCodeOffset=Y +org/objectweb/asm/MethodWriter.ctanns=W +org/objectweb/asm/MethodWriter.ictanns=X +org/objectweb/asm/MethodWriter.cattrs=J +org/objectweb/asm/MethodWriter.subroutines=K +org/objectweb/asm/MethodWriter.compute=L +org/objectweb/asm/MethodWriter.labels=M +org/objectweb/asm/MethodWriter.previousBlock=N +org/objectweb/asm/MethodWriter.currentBlock=O +org/objectweb/asm/MethodWriter.stackSize=P +org/objectweb/asm/MethodWriter.maxStackSize=Q +org/objectweb/asm/MethodWriter.synthetics=R + +org/objectweb/asm/Type.sort=a +org/objectweb/asm/Type.buf=b +org/objectweb/asm/Type.off=c +org/objectweb/asm/Type.len=d + +org/objectweb/asm/TypeReference.value=a + +org/objectweb/asm/TypePath.b=a +org/objectweb/asm/TypePath.offset=b + +org/objectweb/asm/Handle.tag=a +org/objectweb/asm/Handle.owner=b +org/objectweb/asm/Handle.name=c +org/objectweb/asm/Handle.desc=d +org/objectweb/asm/Handle.itf=e + +org/objectweb/asm/signature/SignatureReader.signature=a + +org/objectweb/asm/signature/SignatureWriter.buf=a +org/objectweb/asm/signature/SignatureWriter.hasFormals=b +org/objectweb/asm/signature/SignatureWriter.hasParameters=c +org/objectweb/asm/signature/SignatureWriter.argumentStack=d + +# method mappings + +org/objectweb/asm/AnnotationWriter.getSize()I=a +org/objectweb/asm/AnnotationWriter.put([Lorg/objectweb/asm/AnnotationWriter;ILorg/objectweb/asm/ByteVector;)V=a +org/objectweb/asm/AnnotationWriter.put(Lorg/objectweb/asm/ByteVector;)V=a +org/objectweb/asm/AnnotationWriter.putTarget(ILorg/objectweb/asm/TypePath;Lorg/objectweb/asm/ByteVector;)V=a + +org/objectweb/asm/Attribute.getCount()I=a +org/objectweb/asm/Attribute.getSize(Lorg/objectweb/asm/ClassWriter;[BIII)I=a +org/objectweb/asm/Attribute.put(Lorg/objectweb/asm/ClassWriter;[BIIILorg/objectweb/asm/ByteVector;)V=a + +org/objectweb/asm/ByteVector.enlarge(I)V=a +org/objectweb/asm/ByteVector.put11(II)Lorg/objectweb/asm/ByteVector;=a +org/objectweb/asm/ByteVector.put12(II)Lorg/objectweb/asm/ByteVector;=b +org/objectweb/asm/ByteVector.encodeUTF8(Ljava/lang/String;II)Lorg/objectweb/asm/ByteVector;=c + +org/objectweb/asm/ClassReader.copyPool(Lorg/objectweb/asm/ClassWriter;)V=a +org/objectweb/asm/ClassReader.copyBootstrapMethods(Lorg/objectweb/asm/ClassWriter;[Lorg/objectweb/asm/Item;[C)V=a +org/objectweb/asm/ClassReader.readField(Lorg/objectweb/asm/ClassVisitor;Lorg/objectweb/asm/Context;I)I=a +org/objectweb/asm/ClassReader.readMethod(Lorg/objectweb/asm/ClassVisitor;Lorg/objectweb/asm/Context;I)I=b +org/objectweb/asm/ClassReader.readCode(Lorg/objectweb/asm/MethodVisitor;Lorg/objectweb/asm/Context;I)V=a +org/objectweb/asm/ClassReader.readTypeAnnotations(Lorg/objectweb/asm/MethodVisitor;Lorg/objectweb/asm/Context;IZ)[I=a +org/objectweb/asm/ClassReader.readAnnotationTarget(Lorg/objectweb/asm/Context;I)I=a +org/objectweb/asm/ClassReader.readAnnotationValues(I[CZLorg/objectweb/asm/AnnotationVisitor;)I=a +org/objectweb/asm/ClassReader.readAnnotationValue(I[CLjava/lang/String;Lorg/objectweb/asm/AnnotationVisitor;)I=a +org/objectweb/asm/ClassReader.getAttributes()I=a +org/objectweb/asm/ClassReader.readAttribute([Lorg/objectweb/asm/Attribute;Ljava/lang/String;II[CI[Lorg/objectweb/asm/Label;)Lorg/objectweb/asm/Attribute;=a +org/objectweb/asm/ClassReader.readClass(Ljava/io/InputStream;Z)[B=a +org/objectweb/asm/ClassReader.readParameterAnnotations(Lorg/objectweb/asm/MethodVisitor;Lorg/objectweb/asm/Context;IZ)V=b +org/objectweb/asm/ClassReader.readUTF(II[C)Ljava/lang/String;=a +org/objectweb/asm/ClassReader.getImplicitFrame(Lorg/objectweb/asm/Context;)V=a +org/objectweb/asm/ClassReader.readFrame(IZZLorg/objectweb/asm/Context;)I=a +org/objectweb/asm/ClassReader.readFrameType([Ljava/lang/Object;II[C[Lorg/objectweb/asm/Label;)I=a + +org/objectweb/asm/ClassWriter.get(Lorg/objectweb/asm/Item;)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newClassItem(Ljava/lang/String;)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newConstItem(Ljava/lang/Object;)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newDouble(D)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newFloat(F)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newInteger(I)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newLong(J)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newMethodItem(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newString(Ljava/lang/String;)Lorg/objectweb/asm/Item;=b +org/objectweb/asm/ClassWriter.put122(III)V=a +org/objectweb/asm/ClassWriter.put112(III)V=b +org/objectweb/asm/ClassWriter.put(Lorg/objectweb/asm/Item;)V=b +org/objectweb/asm/ClassWriter.newFieldItem(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.addType(Ljava/lang/String;)I=c +org/objectweb/asm/ClassWriter.addUninitializedType(Ljava/lang/String;I)I=a +org/objectweb/asm/ClassWriter.addType(Lorg/objectweb/asm/Item;)Lorg/objectweb/asm/Item;=c +org/objectweb/asm/ClassWriter.getMergedType(II)I=a +org/objectweb/asm/ClassWriter.newNameTypeItem(Ljava/lang/String;Ljava/lang/String;)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newMethodTypeItem(Ljava/lang/String;)Lorg/objectweb/asm/Item;=c +org/objectweb/asm/ClassWriter.newHandleItem(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Lorg/objectweb/asm/Item;=a +org/objectweb/asm/ClassWriter.newInvokeDynamicItem(Ljava/lang/String;Ljava/lang/String;Lorg/objectweb/asm/Handle;[Ljava/lang/Object;)Lorg/objectweb/asm/Item;=a + +org/objectweb/asm/FieldWriter.getSize()I=a +org/objectweb/asm/FieldWriter.put(Lorg/objectweb/asm/ByteVector;)V=a + +org/objectweb/asm/Frame.get(I)I=a +org/objectweb/asm/Frame.set(II)V=a +org/objectweb/asm/Frame.set(Lorg/objectweb/asm/Frame;)V=b +org/objectweb/asm/CurrentFrame.set(Lorg/objectweb/asm/Frame;)V=b +org/objectweb/asm/Frame.set(Lorg/objectweb/asm/ClassWriter;I[Ljava/lang/Object;I[Ljava/lang/Object;)V=a +org/objectweb/asm/Frame.convert(Lorg/objectweb/asm/ClassWriter;I[Ljava/lang/Object;[I)I=a +org/objectweb/asm/Frame.push(I)V=b +org/objectweb/asm/Frame.push(Lorg/objectweb/asm/ClassWriter;Ljava/lang/String;)V=a +org/objectweb/asm/Frame.type(Lorg/objectweb/asm/ClassWriter;Ljava/lang/String;)I=b +org/objectweb/asm/Frame.pop()I=a +org/objectweb/asm/Frame.pop(Ljava/lang/String;)V=a +org/objectweb/asm/Frame.pop(I)V=c +org/objectweb/asm/Frame.init(I)V=d +org/objectweb/asm/Frame.init(Lorg/objectweb/asm/ClassWriter;I)I=a +org/objectweb/asm/Frame.initInputFrame(Lorg/objectweb/asm/ClassWriter;I[Lorg/objectweb/asm/Type;I)V=a +org/objectweb/asm/Frame.execute(IILorg/objectweb/asm/ClassWriter;Lorg/objectweb/asm/Item;)V=a +org/objectweb/asm/CurrentFrame.execute(IILorg/objectweb/asm/ClassWriter;Lorg/objectweb/asm/Item;)V=a +org/objectweb/asm/Frame.merge(Lorg/objectweb/asm/ClassWriter;Lorg/objectweb/asm/Frame;I)Z=a +org/objectweb/asm/CurrentFrame.merge(Lorg/objectweb/asm/ClassWriter;Lorg/objectweb/asm/Frame;I)Z=a +org/objectweb/asm/Frame.merge(Lorg/objectweb/asm/ClassWriter;I[II)Z=a + +org/objectweb/asm/Handler.remove(Lorg/objectweb/asm/Handler;Lorg/objectweb/asm/Label;Lorg/objectweb/asm/Label;)Lorg/objectweb/asm/Handler;=a + +org/objectweb/asm/Item.isEqualTo(Lorg/objectweb/asm/Item;)Z=a +org/objectweb/asm/Item.set(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V=a +org/objectweb/asm/Item.set(D)V=a +org/objectweb/asm/Item.set(F)V=a +org/objectweb/asm/Item.set(I)V=a +org/objectweb/asm/Item.set(J)V=a +org/objectweb/asm/Item.set(Ljava/lang/String;Ljava/lang/String;I)V=a +org/objectweb/asm/Item.set(II)V=a + +org/objectweb/asm/Label.addReference(II)V=a +org/objectweb/asm/Label.put(Lorg/objectweb/asm/MethodWriter;Lorg/objectweb/asm/ByteVector;IZ)V=a +org/objectweb/asm/Label.resolve(Lorg/objectweb/asm/MethodWriter;I[B)Z=a +org/objectweb/asm/Label.getFirst()Lorg/objectweb/asm/Label;=a +org/objectweb/asm/Label.inSubroutine(J)Z=a +org/objectweb/asm/Label.inSameSubroutine(Lorg/objectweb/asm/Label;)Z=a +org/objectweb/asm/Label.addToSubroutine(JI)V=a +org/objectweb/asm/Label.visitSubroutine(Lorg/objectweb/asm/Label;JI)V=b + +org/objectweb/asm/MethodWriter.visitSwitchInsn(Lorg/objectweb/asm/Label;[Lorg/objectweb/asm/Label;)V=a +org/objectweb/asm/MethodWriter.addSuccessor(ILorg/objectweb/asm/Label;)V=a +org/objectweb/asm/MethodWriter.getSize()I=a +org/objectweb/asm/MethodWriter.put(Lorg/objectweb/asm/ByteVector;)V=a +org/objectweb/asm/MethodWriter.visitFrame(Lorg/objectweb/asm/Frame;)V=b +org/objectweb/asm/MethodWriter.visitImplicitFirstFrame()V=f +org/objectweb/asm/MethodWriter.startFrame(III)I=a +org/objectweb/asm/MethodWriter.endFrame()V=b +org/objectweb/asm/MethodWriter.writeFrame()V=c +org/objectweb/asm/MethodWriter.noSuccessor()V=e +org/objectweb/asm/MethodWriter.writeFrameTypes(II)V=a +org/objectweb/asm/MethodWriter.writeFrameType(Ljava/lang/Object;)V=a + +org/objectweb/asm/Type.getType([CI)Lorg/objectweb/asm/Type;=a +org/objectweb/asm/Type.getDescriptor(Ljava/lang/StringBuilder;)V=a +org/objectweb/asm/Type.getDescriptor(Ljava/lang/StringBuilder;Ljava/lang/Class;)V=a + +org/objectweb/asm/signature/SignatureReader.parseType(Ljava/lang/String;ILorg/objectweb/asm/signature/SignatureVisitor;)I=a + +org/objectweb/asm/signature/SignatureWriter.endFormals()V=a +org/objectweb/asm/signature/SignatureWriter.endArguments()V=b + \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/package.html b/src/java/nginx/clojure/asm/package.html new file mode 100644 index 00000000..ff1e1969 --- /dev/null +++ b/src/java/nginx/clojure/asm/package.html @@ -0,0 +1,87 @@ + + + +Provides a small and fast bytecode manipulation framework. + +

    +The ASM framework is organized +around the {@link nginx.clojure.asm.ClassVisitor ClassVisitor}, +{@link nginx.clojure.asm.FieldVisitor FieldVisitor}, +{@link nginx.clojure.asm.MethodVisitor MethodVisitor} and +{@link nginx.clojure.asm.AnnotationVisitor AnnotationVisitor} abstract classes, +which allow one to visit the fields, methods and annotations of a class, +including the bytecode instructions of each method. + +

    +In addition to these main abstract classes, ASM provides a {@link +nginx.clojure.asm.ClassReader ClassReader} class, that can parse an +existing class and make a given visitor visit it. ASM also provides +a {@link nginx.clojure.asm.ClassWriter ClassWriter} class, which is +a visitor that generates Java class files. + +

    +In order to generate a class from scratch, only the {@link +nginx.clojure.asm.ClassWriter ClassWriter} class is necessary. Indeed, +in order to generate a class, one must just call its visitXxx +methods with the appropriate arguments to generate the desired fields +and methods. See the "helloworld" example in the ASM distribution for +more details about class generation. + +

    +In order to modify existing classes, one must use a {@link +nginx.clojure.asm.ClassReader ClassReader} class to analyze +the original class, a class modifier, and a {@link nginx.clojure.asm.ClassWriter +ClassWriter} to construct the modified class. The class modifier +is just a {@link nginx.clojure.asm.ClassVisitor ClassVisitor} +that delegates most of the work to another {@link nginx.clojure.asm.ClassVisitor +ClassVisitor}, but that sometimes changes some parameter values, +or call additional methods, in order to implement the desired +modification process. In order to make it easier to implement such +class modifiers, the {@link nginx.clojure.asm.ClassVisitor +ClassVisitor} and {@link nginx.clojure.asm.MethodVisitor MethodVisitor} +classes delegate by default all the method calls they receive to an +optional visitor. See the "adapt" example in the ASM +distribution for more details about class modification. + +

    +The size of the core ASM library, asm.jar, is only 45KB, which is much +smaller than the size of the +BCEL library (504KB), and than the +size of the +SERP library (150KB). ASM is also +much faster than these tools. Indeed the overhead of a load time class +transformation process is of the order of 60% with ASM, 700% or more with BCEL, +and 1100% or more with SERP (see the test/perf directory in the ASM +distribution)! + +@since ASM 1.3 + + diff --git a/src/java/nginx/clojure/asm/signature/SignatureReader.java b/src/java/nginx/clojure/asm/signature/SignatureReader.java index 1186938c..d66ae2c8 100644 --- a/src/java/nginx/clojure/asm/signature/SignatureReader.java +++ b/src/java/nginx/clojure/asm/signature/SignatureReader.java @@ -60,10 +60,10 @@ public SignatureReader(final String signature) { * constructor (see {@link #SignatureReader(String) SignatureReader}). This * method is intended to be called on a {@link SignatureReader} that was * created using a ClassSignature (such as the signature - * parameter of the {@link org.objectweb.asm.ClassVisitor#visit + * parameter of the {@link nginx.clojure.asm.ClassVisitor#visit * ClassVisitor.visit} method) or a MethodTypeSignature (such as the * signature parameter of the - * {@link org.objectweb.asm.ClassVisitor#visitMethod + * {@link nginx.clojure.asm.ClassVisitor#visitMethod * ClassVisitor.visitMethod} method). * * @param v @@ -119,8 +119,8 @@ public void accept(final SignatureVisitor v) { * method is intended to be called on a {@link SignatureReader} that was * created using a FieldTypeSignature, such as the * signature parameter of the - * {@link org.objectweb.asm.ClassVisitor#visitField ClassVisitor.visitField} - * or {@link org.objectweb.asm.MethodVisitor#visitLocalVariable + * {@link nginx.clojure.asm.ClassVisitor#visitField ClassVisitor.visitField} + * or {@link nginx.clojure.asm.MethodVisitor#visitLocalVariable * MethodVisitor.visitLocalVariable} methods. * * @param v diff --git a/src/java/nginx/clojure/asm/signature/SignatureVisitor.java b/src/java/nginx/clojure/asm/signature/SignatureVisitor.java index 655093b8..01266586 100644 --- a/src/java/nginx/clojure/asm/signature/SignatureVisitor.java +++ b/src/java/nginx/clojure/asm/signature/SignatureVisitor.java @@ -39,7 +39,7 @@ *

      *
    • ClassSignature = ( visitFormalTypeParameter * visitClassBound? visitInterfaceBound* )* ( - * visitSuperClass visitInterface* )
    • + * visitSuperclass visitInterface* ) *
    • MethodSignature = ( visitFormalTypeParameter * visitClassBound? visitInterfaceBound* )* ( * visitParameterType* visitReturnType @@ -73,7 +73,7 @@ public abstract class SignatureVisitor { /** * The ASM API version implemented by this visitor. The value of this field - * must be one of {@link Opcodes#ASM4}. + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ protected final int api; @@ -82,9 +82,12 @@ public abstract class SignatureVisitor { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ public SignatureVisitor(final int api) { + if (api != Opcodes.ASM4 && api != Opcodes.ASM5) { + throw new IllegalArgumentException(); + } this.api = api; } diff --git a/src/java/nginx/clojure/asm/signature/SignatureWriter.java b/src/java/nginx/clojure/asm/signature/SignatureWriter.java index e5fd711b..b67f1117 100644 --- a/src/java/nginx/clojure/asm/signature/SignatureWriter.java +++ b/src/java/nginx/clojure/asm/signature/SignatureWriter.java @@ -40,9 +40,9 @@ public class SignatureWriter extends SignatureVisitor { /** - * Buffer used to construct the signature. + * Builder used to construct the signature. */ - private final StringBuffer buf = new StringBuffer(); + private final StringBuilder buf = new StringBuilder(); /** * Indicates if the signature contains formal type parameters. @@ -66,7 +66,7 @@ public class SignatureWriter extends SignatureVisitor { * Constructs a new {@link SignatureWriter} object. */ public SignatureWriter() { - super(Opcodes.ASM4); + super(Opcodes.ASM5); } // ------------------------------------------------------------------------ diff --git a/src/java/nginx/clojure/asm/signature/package.html b/src/java/nginx/clojure/asm/signature/package.html new file mode 100644 index 00000000..0c07d120 --- /dev/null +++ b/src/java/nginx/clojure/asm/signature/package.html @@ -0,0 +1,36 @@ + + + +Provides support for type signatures. + +@since ASM 2.0 + + diff --git a/src/java/nginx/clojure/asm/tree/AbstractInsnNode.java b/src/java/nginx/clojure/asm/tree/AbstractInsnNode.java index 3920e75d..a0c2eae0 100644 --- a/src/java/nginx/clojure/asm/tree/AbstractInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/AbstractInsnNode.java @@ -29,6 +29,7 @@ */ package nginx.clojure.asm.tree; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -127,6 +128,28 @@ public abstract class AbstractInsnNode { */ protected int opcode; + /** + * The runtime visible type annotations of this instruction. This field is + * only used for real instructions (i.e. not for labels, frames, or line + * number nodes). This list is a list of {@link TypeAnnotationNode} objects. + * May be null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label visible + */ + public List visibleTypeAnnotations; + + /** + * The runtime invisible type annotations of this instruction. This field is + * only used for real instructions (i.e. not for labels, frames, or line + * number nodes). This list is a list of {@link TypeAnnotationNode} objects. + * May be null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label invisible + */ + public List invisibleTypeAnnotations; + /** * Previous instruction in the list to which this instruction belongs. */ @@ -203,6 +226,29 @@ public AbstractInsnNode getNext() { */ public abstract void accept(final MethodVisitor cv); + /** + * Makes the given visitor visit the annotations of this instruction. + * + * @param mv + * a method visitor. + */ + protected final void acceptAnnotations(final MethodVisitor mv) { + int n = visibleTypeAnnotations == null ? 0 : visibleTypeAnnotations + .size(); + for (int i = 0; i < n; ++i) { + TypeAnnotationNode an = visibleTypeAnnotations.get(i); + an.accept(mv.visitInsnAnnotation(an.typeRef, an.typePath, an.desc, + true)); + } + n = invisibleTypeAnnotations == null ? 0 : invisibleTypeAnnotations + .size(); + for (int i = 0; i < n; ++i) { + TypeAnnotationNode an = invisibleTypeAnnotations.get(i); + an.accept(mv.visitInsnAnnotation(an.typeRef, an.typePath, an.desc, + false)); + } + } + /** * Returns a copy of this instruction. * @@ -245,4 +291,36 @@ static LabelNode[] clone(final List labels, } return clones; } + + /** + * Clones the annotations of the given instruction into this instruction. + * + * @param insn + * the source instruction. + * @return this instruction. + */ + protected final AbstractInsnNode cloneAnnotations( + final AbstractInsnNode insn) { + if (insn.visibleTypeAnnotations != null) { + this.visibleTypeAnnotations = new ArrayList(); + for (int i = 0; i < insn.visibleTypeAnnotations.size(); ++i) { + TypeAnnotationNode src = insn.visibleTypeAnnotations.get(i); + TypeAnnotationNode ann = new TypeAnnotationNode(src.typeRef, + src.typePath, src.desc); + src.accept(ann); + this.visibleTypeAnnotations.add(ann); + } + } + if (insn.invisibleTypeAnnotations != null) { + this.invisibleTypeAnnotations = new ArrayList(); + for (int i = 0; i < insn.invisibleTypeAnnotations.size(); ++i) { + TypeAnnotationNode src = insn.invisibleTypeAnnotations.get(i); + TypeAnnotationNode ann = new TypeAnnotationNode(src.typeRef, + src.typePath, src.desc); + src.accept(ann); + this.invisibleTypeAnnotations.add(ann); + } + } + return this; + } } diff --git a/src/java/nginx/clojure/asm/tree/AnnotationNode.java b/src/java/nginx/clojure/asm/tree/AnnotationNode.java index 632225a9..36670323 100644 --- a/src/java/nginx/clojure/asm/tree/AnnotationNode.java +++ b/src/java/nginx/clojure/asm/tree/AnnotationNode.java @@ -36,7 +36,7 @@ import nginx.clojure.asm.Opcodes; /** - * A node that represents an annotationn. + * A node that represents an annotation. * * @author Eric Bruneton */ @@ -52,8 +52,8 @@ public class AnnotationNode extends AnnotationVisitor { * as two consecutive elements in the list. The name is a {@link String}, * and the value may be a {@link Byte}, {@link Boolean}, {@link Character}, * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, - * {@link Double}, {@link String} or {@link nginx.clojure.asm.Type}, or an - * two elements String array (for enumeration values), a + * {@link Double}, {@link String} or {@link nginx.clojure.asm.Type}, or a + * two elements String array (for enumeration values), an * {@link AnnotationNode}, or a {@link List} of values of one of the * preceding types. The list may be null if there is no name value * pair. @@ -67,9 +67,14 @@ public class AnnotationNode extends AnnotationVisitor { * * @param desc * the class descriptor of the annotation class. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public AnnotationNode(final String desc) { - this(Opcodes.ASM4, desc); + this(Opcodes.ASM5, desc); + if (getClass() != AnnotationNode.class) { + throw new IllegalStateException(); + } } /** @@ -77,7 +82,7 @@ public AnnotationNode(final String desc) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param desc * the class descriptor of the annotation class. */ @@ -93,7 +98,7 @@ public AnnotationNode(final int api, final String desc) { * where the visited values must be stored. */ AnnotationNode(final List values) { - super(Opcodes.ASM4); + super(Opcodes.ASM5); this.values = values; } @@ -109,7 +114,65 @@ public void visit(final String name, final Object value) { if (this.desc != null) { values.add(name); } - values.add(value); + if (value instanceof byte[]) { + byte[] v = (byte[]) value; + ArrayList l = new ArrayList(v.length); + for (byte b : v) { + l.add(b); + } + values.add(l); + } else if (value instanceof boolean[]) { + boolean[] v = (boolean[]) value; + ArrayList l = new ArrayList(v.length); + for (boolean b : v) { + l.add(b); + } + values.add(l); + } else if (value instanceof short[]) { + short[] v = (short[]) value; + ArrayList l = new ArrayList(v.length); + for (short s : v) { + l.add(s); + } + values.add(l); + } else if (value instanceof char[]) { + char[] v = (char[]) value; + ArrayList l = new ArrayList(v.length); + for (char c : v) { + l.add(c); + } + values.add(l); + } else if (value instanceof int[]) { + int[] v = (int[]) value; + ArrayList l = new ArrayList(v.length); + for (int i : v) { + l.add(i); + } + values.add(l); + } else if (value instanceof long[]) { + long[] v = (long[]) value; + ArrayList l = new ArrayList(v.length); + for (long lng : v) { + l.add(lng); + } + values.add(l); + } else if (value instanceof float[]) { + float[] v = (float[]) value; + ArrayList l = new ArrayList(v.length); + for (float f : v) { + l.add(f); + } + values.add(l); + } else if (value instanceof double[]) { + double[] v = (double[]) value; + ArrayList l = new ArrayList(v.length); + for (double d : v) { + l.add(d); + } + values.add(l); + } else { + values.add(value); + } } @Override @@ -166,7 +229,8 @@ public void visitEnd() { * versions of the ASM API than the given version. * * @param api - * an ASM API version. Must be one of {@link Opcodes#ASM4}. + * an ASM API version. Must be one of {@link Opcodes#ASM4} or + * {@link Opcodes#ASM5}. */ public void check(final int api) { // nothing to do @@ -212,11 +276,13 @@ static void accept(final AnnotationVisitor av, final String name, an.accept(av.visitAnnotation(name, an.desc)); } else if (value instanceof List) { AnnotationVisitor v = av.visitArray(name); - List array = (List) value; - for (int j = 0; j < array.size(); ++j) { - accept(v, null, array.get(j)); + if (v != null) { + List array = (List) value; + for (int j = 0; j < array.size(); ++j) { + accept(v, null, array.get(j)); + } + v.visitEnd(); } - v.visitEnd(); } else { av.visit(name, value); } diff --git a/src/java/nginx/clojure/asm/tree/ClassNode.java b/src/java/nginx/clojure/asm/tree/ClassNode.java index 0f64f2b3..e5ca0454 100644 --- a/src/java/nginx/clojure/asm/tree/ClassNode.java +++ b/src/java/nginx/clojure/asm/tree/ClassNode.java @@ -39,6 +39,7 @@ import nginx.clojure.asm.FieldVisitor; import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * A node that represents a class. @@ -132,6 +133,24 @@ public class ClassNode extends ClassVisitor { */ public List invisibleAnnotations; + /** + * The runtime visible type annotations of this class. This list is a list + * of {@link TypeAnnotationNode} objects. May be null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label visible + */ + public List visibleTypeAnnotations; + + /** + * The runtime invisible type annotations of this class. This list is a list + * of {@link TypeAnnotationNode} objects. May be null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label invisible + */ + public List invisibleTypeAnnotations; + /** * The non standard attributes of this class. This list is a list of * {@link Attribute} objects. May be null. @@ -168,9 +187,15 @@ public class ClassNode extends ClassVisitor { * Constructs a new {@link ClassNode}. Subclasses must not use this * constructor. Instead, they must use the {@link #ClassNode(int)} * version. + * + * @throws IllegalStateException + * If a subclass calls this constructor. */ public ClassNode() { - this(Opcodes.ASM4); + this(Opcodes.ASM5); + if (getClass() != ClassNode.class) { + throw new IllegalStateException(); + } } /** @@ -178,7 +203,7 @@ public ClassNode() { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ public ClassNode(final int api) { super(api); @@ -238,6 +263,24 @@ public AnnotationVisitor visitAnnotation(final String desc, return an; } + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + TypeAnnotationNode an = new TypeAnnotationNode(typeRef, typePath, desc); + if (visible) { + if (visibleTypeAnnotations == null) { + visibleTypeAnnotations = new ArrayList(1); + } + visibleTypeAnnotations.add(an); + } else { + if (invisibleTypeAnnotations == null) { + invisibleTypeAnnotations = new ArrayList(1); + } + invisibleTypeAnnotations.add(an); + } + return an; + } + @Override public void visitAttribute(final Attribute attr) { if (attrs == null) { @@ -286,10 +329,26 @@ public void visitEnd() { * API than the given version. * * @param api - * an ASM API version. Must be one of {@link Opcodes#ASM4}. + * an ASM API version. Must be one of {@link Opcodes#ASM4} or + * {@link Opcodes#ASM5}. */ public void check(final int api) { - // nothing to do + if (api == Opcodes.ASM4) { + if (visibleTypeAnnotations != null + && visibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + if (invisibleTypeAnnotations != null + && invisibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + for (FieldNode f : fields) { + f.check(api); + } + for (MethodNode m : methods) { + m.check(api); + } + } } /** @@ -323,6 +382,19 @@ public void accept(final ClassVisitor cv) { AnnotationNode an = invisibleAnnotations.get(i); an.accept(cv.visitAnnotation(an.desc, false)); } + n = visibleTypeAnnotations == null ? 0 : visibleTypeAnnotations.size(); + for (i = 0; i < n; ++i) { + TypeAnnotationNode an = visibleTypeAnnotations.get(i); + an.accept(cv.visitTypeAnnotation(an.typeRef, an.typePath, an.desc, + true)); + } + n = invisibleTypeAnnotations == null ? 0 : invisibleTypeAnnotations + .size(); + for (i = 0; i < n; ++i) { + TypeAnnotationNode an = invisibleTypeAnnotations.get(i); + an.accept(cv.visitTypeAnnotation(an.typeRef, an.typePath, an.desc, + false)); + } n = attrs == null ? 0 : attrs.size(); for (i = 0; i < n; ++i) { cv.visitAttribute(attrs.get(i)); diff --git a/src/java/nginx/clojure/asm/tree/FieldInsnNode.java b/src/java/nginx/clojure/asm/tree/FieldInsnNode.java index 94ad13db..af231eb7 100644 --- a/src/java/nginx/clojure/asm/tree/FieldInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/FieldInsnNode.java @@ -97,12 +97,14 @@ public int getType() { } @Override - public void accept(final MethodVisitor cv) { - cv.visitFieldInsn(opcode, owner, name, desc); + public void accept(final MethodVisitor mv) { + mv.visitFieldInsn(opcode, owner, name, desc); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new FieldInsnNode(opcode, owner, name, desc); + return new FieldInsnNode(opcode, owner, name, desc) + .cloneAnnotations(this); } } diff --git a/src/java/nginx/clojure/asm/tree/FieldNode.java b/src/java/nginx/clojure/asm/tree/FieldNode.java index fbebce28..18bbe934 100644 --- a/src/java/nginx/clojure/asm/tree/FieldNode.java +++ b/src/java/nginx/clojure/asm/tree/FieldNode.java @@ -37,6 +37,7 @@ import nginx.clojure.asm.ClassVisitor; import nginx.clojure.asm.FieldVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * A node that represents a field. @@ -91,6 +92,24 @@ public class FieldNode extends FieldVisitor { */ public List invisibleAnnotations; + /** + * The runtime visible type annotations of this field. This list is a list + * of {@link TypeAnnotationNode} objects. May be null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label visible + */ + public List visibleTypeAnnotations; + + /** + * The runtime invisible type annotations of this field. This list is a list + * of {@link TypeAnnotationNode} objects. May be null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label invisible + */ + public List invisibleTypeAnnotations; + /** * The non standard attributes of this field. This list is a list of * {@link Attribute} objects. May be null. @@ -120,20 +139,24 @@ public class FieldNode extends FieldVisitor { * null if the field does not have an initial value, * must be an {@link Integer}, a {@link Float}, a {@link Long}, a * {@link Double} or a {@link String}. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public FieldNode(final int access, final String name, final String desc, final String signature, final Object value) { - this(Opcodes.ASM4, access, name, desc, signature, value); + this(Opcodes.ASM5, access, name, desc, signature, value); + if (getClass() != FieldNode.class) { + throw new IllegalStateException(); + } } /** * Constructs a new {@link FieldNode}. Subclasses must not use this - * constructor. Instead, they must use the - * {@link #FieldNode(int, int, String, String, String, Object)} version. + * constructor. * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param access * the field's access flags (see * {@link nginx.clojure.asm.Opcodes}). This parameter also @@ -183,6 +206,24 @@ public AnnotationVisitor visitAnnotation(final String desc, return an; } + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + TypeAnnotationNode an = new TypeAnnotationNode(typeRef, typePath, desc); + if (visible) { + if (visibleTypeAnnotations == null) { + visibleTypeAnnotations = new ArrayList(1); + } + visibleTypeAnnotations.add(an); + } else { + if (invisibleTypeAnnotations == null) { + invisibleTypeAnnotations = new ArrayList(1); + } + invisibleTypeAnnotations.add(an); + } + return an; + } + @Override public void visitAttribute(final Attribute attr) { if (attrs == null) { @@ -206,10 +247,20 @@ public void visitEnd() { * API than the given version. * * @param api - * an ASM API version. Must be one of {@link Opcodes#ASM4}. + * an ASM API version. Must be one of {@link Opcodes#ASM4} or + * {@link Opcodes#ASM5}. */ public void check(final int api) { - // nothing to do + if (api == Opcodes.ASM4) { + if (visibleTypeAnnotations != null + && visibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + if (invisibleTypeAnnotations != null + && invisibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + } } /** @@ -234,6 +285,19 @@ public void accept(final ClassVisitor cv) { AnnotationNode an = invisibleAnnotations.get(i); an.accept(fv.visitAnnotation(an.desc, false)); } + n = visibleTypeAnnotations == null ? 0 : visibleTypeAnnotations.size(); + for (i = 0; i < n; ++i) { + TypeAnnotationNode an = visibleTypeAnnotations.get(i); + an.accept(fv.visitTypeAnnotation(an.typeRef, an.typePath, an.desc, + true)); + } + n = invisibleTypeAnnotations == null ? 0 : invisibleTypeAnnotations + .size(); + for (i = 0; i < n; ++i) { + TypeAnnotationNode an = invisibleTypeAnnotations.get(i); + an.accept(fv.visitTypeAnnotation(an.typeRef, an.typePath, an.desc, + false)); + } n = attrs == null ? 0 : attrs.size(); for (i = 0; i < n; ++i) { fv.visitAttribute(attrs.get(i)); diff --git a/src/java/nginx/clojure/asm/tree/IincInsnNode.java b/src/java/nginx/clojure/asm/tree/IincInsnNode.java index cd0abbf5..67dfe503 100644 --- a/src/java/nginx/clojure/asm/tree/IincInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/IincInsnNode.java @@ -73,10 +73,11 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitIincInsn(var, incr); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new IincInsnNode(var, incr); + return new IincInsnNode(var, incr).cloneAnnotations(this); } } \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/tree/InsnList.java b/src/java/nginx/clojure/asm/tree/InsnList.java index 67656301..05431c4c 100644 --- a/src/java/nginx/clojure/asm/tree/InsnList.java +++ b/src/java/nginx/clojure/asm/tree/InsnList.java @@ -100,7 +100,7 @@ public AbstractInsnNode getLast() { * the index of the instruction that must be returned. * @return the instruction whose index is given. * @throws IndexOutOfBoundsException - * if (index < 0 || index >= size()). + * if (index < 0 || index >= size()). */ public AbstractInsnNode get(final int index) { if (index < 0 || index >= size) { @@ -176,6 +176,9 @@ public ListIterator iterator() { /** * Returns an iterator over the instructions in this list. * + * @param index + * index of instruction for the iterator to start at + * * @return an iterator over the instructions in this list. */ @SuppressWarnings("unchecked") @@ -521,12 +524,15 @@ public void resetLabels() { } // this class is not generified because it will create bridges + @SuppressWarnings("rawtypes") private final class InsnListIterator implements ListIterator { AbstractInsnNode next; AbstractInsnNode prev; + AbstractInsnNode remove; + InsnListIterator(int index) { if (index == size()) { next = null; @@ -548,12 +554,22 @@ public Object next() { AbstractInsnNode result = next; prev = result; next = result.next; + remove = result; return result; } public void remove() { - InsnList.this.remove(prev); - prev = prev.prev; + if (remove != null) { + if (remove == next) { + next = next.next; + } else { + prev = prev.prev; + } + InsnList.this.remove(remove); + remove = null; + } else { + throw new IllegalStateException(); + } } public boolean hasPrevious() { @@ -564,6 +580,7 @@ public Object previous() { AbstractInsnNode result = prev; next = result; prev = result.prev; + remove = result; return result; } @@ -588,13 +605,28 @@ public int previousIndex() { } public void add(Object o) { - InsnList.this.insertBefore(next, (AbstractInsnNode) o); + if (next != null) { + InsnList.this.insertBefore(next, (AbstractInsnNode) o); + } else if (prev != null) { + InsnList.this.insert(prev, (AbstractInsnNode) o); + } else { + InsnList.this.add((AbstractInsnNode) o); + } prev = (AbstractInsnNode) o; + remove = null; } public void set(Object o) { - InsnList.this.set(next.prev, (AbstractInsnNode) o); - prev = (AbstractInsnNode) o; + if (remove != null) { + InsnList.this.set(remove, (AbstractInsnNode) o); + if (remove == prev) { + prev = (AbstractInsnNode) o; + } else { + next = (AbstractInsnNode) o; + } + } else { + throw new IllegalStateException(); + } } } } diff --git a/src/java/nginx/clojure/asm/tree/InsnNode.java b/src/java/nginx/clojure/asm/tree/InsnNode.java index e23dded8..495bef67 100644 --- a/src/java/nginx/clojure/asm/tree/InsnNode.java +++ b/src/java/nginx/clojure/asm/tree/InsnNode.java @@ -78,10 +78,11 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitInsn(opcode); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new InsnNode(opcode); + return new InsnNode(opcode).cloneAnnotations(this); } } diff --git a/src/java/nginx/clojure/asm/tree/IntInsnNode.java b/src/java/nginx/clojure/asm/tree/IntInsnNode.java index fef8fba1..c3ef1d47 100644 --- a/src/java/nginx/clojure/asm/tree/IntInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/IntInsnNode.java @@ -78,10 +78,11 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitIntInsn(opcode, operand); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new IntInsnNode(opcode, operand); + return new IntInsnNode(opcode, operand).cloneAnnotations(this); } } diff --git a/src/java/nginx/clojure/asm/tree/InvokeDynamicInsnNode.java b/src/java/nginx/clojure/asm/tree/InvokeDynamicInsnNode.java index db473eb2..d2883d41 100644 --- a/src/java/nginx/clojure/asm/tree/InvokeDynamicInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/InvokeDynamicInsnNode.java @@ -91,10 +91,12 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new InvokeDynamicInsnNode(name, desc, bsm, bsmArgs); + return new InvokeDynamicInsnNode(name, desc, bsm, bsmArgs) + .cloneAnnotations(this); } } \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/tree/JumpInsnNode.java b/src/java/nginx/clojure/asm/tree/JumpInsnNode.java index 3fc367d7..170c7e7c 100644 --- a/src/java/nginx/clojure/asm/tree/JumpInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/JumpInsnNode.java @@ -86,10 +86,12 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitJumpInsn(opcode, label.getLabel()); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new JumpInsnNode(opcode, clone(label, labels)); + return new JumpInsnNode(opcode, clone(label, labels)) + .cloneAnnotations(this); } } diff --git a/src/java/nginx/clojure/asm/tree/LdcInsnNode.java b/src/java/nginx/clojure/asm/tree/LdcInsnNode.java index 80a47a5c..f33e4c5b 100644 --- a/src/java/nginx/clojure/asm/tree/LdcInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/LdcInsnNode.java @@ -69,10 +69,11 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitLdcInsn(cst); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new LdcInsnNode(cst); + return new LdcInsnNode(cst).cloneAnnotations(this); } } \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/tree/LocalVariableAnnotationNode.java b/src/java/nginx/clojure/asm/tree/LocalVariableAnnotationNode.java new file mode 100644 index 00000000..2ef3aa4e --- /dev/null +++ b/src/java/nginx/clojure/asm/tree/LocalVariableAnnotationNode.java @@ -0,0 +1,157 @@ +/*** + * 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.ArrayList; +import java.util.Arrays; +import java.util.List; + +import nginx.clojure.asm.Label; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; +import nginx.clojure.asm.TypeReference; + +/** + * A node that represents a type annotation on a local or resource variable. + * + * @author Eric Bruneton + */ +public class LocalVariableAnnotationNode extends TypeAnnotationNode { + + /** + * The fist instructions corresponding to the continuous ranges that make + * the scope of this local variable (inclusive). Must not be null. + */ + public List start; + + /** + * The last instructions corresponding to the continuous ranges that make + * the scope of this local variable (exclusive). This list must have the + * same size as the 'start' list. Must not be null. + */ + public List end; + + /** + * The local variable's index in each range. This list must have the same + * size as the 'start' list. Must not be null. + */ + public List index; + + /** + * Constructs a new {@link LocalVariableAnnotationNode}. Subclasses must + * not use this constructor. Instead, they must use the + * {@link #LocalVariableAnnotationNode(int, TypePath, LabelNode[], LabelNode[], int[], String)} + * version. + * + * @param typeRef + * a reference to the annotated type. 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 + * null if the annotation targets 'typeRef' as a whole. + * @param start + * the fist instructions corresponding to the continuous ranges + * that make the scope of this local variable (inclusive). + * @param end + * the last instructions corresponding to the continuous ranges + * that make the scope of this local variable (exclusive). This + * array must have the same size as the 'start' array. + * @param index + * the local variable's index in each range. This array must have + * the same size as the 'start' array. + * @param desc + * the class descriptor of the annotation class. + */ + public LocalVariableAnnotationNode(int typeRef, TypePath typePath, + LabelNode[] start, LabelNode[] end, int[] index, String desc) { + this(Opcodes.ASM5, typeRef, typePath, start, end, index, desc); + } + + /** + * Constructs a new {@link LocalVariableAnnotationNode}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param typeRef + * a reference to the annotated type. See {@link TypeReference}. + * @param start + * the fist instructions corresponding to the continuous ranges + * that make the scope of this local variable (inclusive). + * @param end + * the last instructions corresponding to the continuous ranges + * that make the scope of this local variable (exclusive). This + * array must have the same size as the 'start' array. + * @param index + * the local variable's index in each range. This array must have + * the same size as the 'start' array. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + */ + public LocalVariableAnnotationNode(int api, int typeRef, TypePath typePath, + LabelNode[] start, LabelNode[] end, int[] index, String desc) { + super(api, typeRef, typePath, desc); + this.start = new ArrayList(start.length); + this.start.addAll(Arrays.asList(start)); + this.end = new ArrayList(end.length); + this.end.addAll(Arrays.asList(end)); + this.index = new ArrayList(index.length); + for (int i : index) { + this.index.add(i); + } + } + + /** + * Makes the given visitor visit this type annotation. + * + * @param mv + * the visitor that must visit this annotation. + * @param visible + * true if the annotation is visible at runtime. + */ + public void accept(final MethodVisitor mv, boolean visible) { + Label[] start = new Label[this.start.size()]; + Label[] end = new Label[this.end.size()]; + int[] index = new int[this.index.size()]; + for (int i = 0; i < start.length; ++i) { + start[i] = this.start.get(i).getLabel(); + end[i] = this.end.get(i).getLabel(); + index[i] = this.index.get(i); + } + accept(mv.visitLocalVariableAnnotation(typeRef, typePath, start, end, + index, desc, true)); + } +} diff --git a/src/java/nginx/clojure/asm/tree/LookupSwitchInsnNode.java b/src/java/nginx/clojure/asm/tree/LookupSwitchInsnNode.java index 604c4b0f..5701944f 100644 --- a/src/java/nginx/clojure/asm/tree/LookupSwitchInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/LookupSwitchInsnNode.java @@ -81,7 +81,7 @@ public LookupSwitchInsnNode(final LabelNode dflt, final int[] keys, : labels.length); if (keys != null) { for (int i = 0; i < keys.length; ++i) { - this.keys.add(new Integer(keys[i])); + this.keys.add(keys[i]); } } if (labels != null) { @@ -105,6 +105,7 @@ public void accept(final MethodVisitor mv) { labels[i] = this.labels.get(i).getLabel(); } mv.visitLookupSwitchInsn(dflt.getLabel(), keys, labels); + acceptAnnotations(mv); } @Override @@ -112,6 +113,6 @@ public AbstractInsnNode clone(final Map labels) { LookupSwitchInsnNode clone = new LookupSwitchInsnNode(clone(dflt, labels), null, clone(this.labels, labels)); clone.keys.addAll(keys); - return clone; + return clone.cloneAnnotations(this); } } diff --git a/src/java/nginx/clojure/asm/tree/MethodInsnNode.java b/src/java/nginx/clojure/asm/tree/MethodInsnNode.java index da79f0b4..15e22660 100644 --- a/src/java/nginx/clojure/asm/tree/MethodInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/MethodInsnNode.java @@ -32,6 +32,7 @@ import java.util.Map; import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; /** * A node that represents a method instruction. A method instruction is an @@ -57,6 +58,11 @@ public class MethodInsnNode extends AbstractInsnNode { */ public String desc; + /** + * If the method's owner class if an interface. + */ + public boolean itf; + /** * Constructs a new {@link MethodInsnNode}. * @@ -73,12 +79,37 @@ public class MethodInsnNode extends AbstractInsnNode { * @param desc * the method's descriptor (see {@link nginx.clojure.asm.Type}). */ + @Deprecated public MethodInsnNode(final int opcode, final String owner, final String name, final String desc) { + this(opcode, owner, name, desc, opcode == Opcodes.INVOKEINTERFACE); + } + + /** + * Constructs a new {@link MethodInsnNode}. + * + * @param opcode + * the opcode of the type instruction to be constructed. This + * opcode must be INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link nginx.clojure.asm.Type#getInternalName() + * getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link nginx.clojure.asm.Type}). + * @param itf + * if the method's owner class is an interface. + */ + public MethodInsnNode(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { super(opcode); this.owner = owner; this.name = name; this.desc = desc; + this.itf = itf; } /** @@ -99,11 +130,12 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { - mv.visitMethodInsn(opcode, owner, name, desc); + mv.visitMethodInsn(opcode, owner, name, desc, itf); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new MethodInsnNode(opcode, owner, name, desc); + return new MethodInsnNode(opcode, owner, name, desc, itf); } } \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/tree/MethodNode.java b/src/java/nginx/clojure/asm/tree/MethodNode.java index e0f47099..139e3e21 100644 --- a/src/java/nginx/clojure/asm/tree/MethodNode.java +++ b/src/java/nginx/clojure/asm/tree/MethodNode.java @@ -41,6 +41,7 @@ import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; import nginx.clojure.asm.Type; +import nginx.clojure.asm.TypePath; /** * A node that represents a method. @@ -77,6 +78,11 @@ public class MethodNode extends MethodVisitor { */ public List exceptions; + /** + * The method parameter info (access flags and name) + */ + public List parameters; + /** * The runtime visible annotations of this method. This list is a list of * {@link AnnotationNode} objects. May be null. @@ -95,6 +101,24 @@ public class MethodNode extends MethodVisitor { */ public List invisibleAnnotations; + /** + * The runtime visible type annotations of this method. This list is a list + * of {@link TypeAnnotationNode} objects. May be null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label visible + */ + public List visibleTypeAnnotations; + + /** + * The runtime invisible type annotations of this method. This list is a + * list of {@link TypeAnnotationNode} objects. May be null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label invisible + */ + public List invisibleTypeAnnotations; + /** * The non standard attributes of this method. This list is a list of * {@link Attribute} objects. May be null. @@ -166,6 +190,22 @@ public class MethodNode extends MethodVisitor { */ public List localVariables; + /** + * The visible local variable annotations of this method. This list is a + * list of {@link LocalVariableAnnotationNode} objects. May be null + * + * @associates nginx.clojure.asm.tree.LocalVariableAnnotationNode + */ + public List visibleLocalVariableAnnotations; + + /** + * The invisible local variable annotations of this method. This list is a + * list of {@link LocalVariableAnnotationNode} objects. May be null + * + * @associates nginx.clojure.asm.tree.LocalVariableAnnotationNode + */ + public List invisibleLocalVariableAnnotations; + /** * If the accept method has been called on this object. */ @@ -175,9 +215,15 @@ public class MethodNode extends MethodVisitor { * Constructs an uninitialized {@link MethodNode}. Subclasses must not * use this constructor. Instead, they must use the * {@link #MethodNode(int)} version. + * + * @throws IllegalStateException + * If a subclass calls this constructor. */ public MethodNode() { - this(Opcodes.ASM4); + this(Opcodes.ASM5); + if (getClass() != MethodNode.class) { + throw new IllegalStateException(); + } } /** @@ -185,7 +231,7 @@ public MethodNode() { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ public MethodNode(final int api) { super(api); @@ -211,10 +257,15 @@ public MethodNode(final int api) { * the internal names of the method's exception classes (see * {@link Type#getInternalName() getInternalName}). May be * null. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public MethodNode(final int access, final String name, final String desc, final String signature, final String[] exceptions) { - this(Opcodes.ASM4, access, name, desc, signature, exceptions); + this(Opcodes.ASM5, access, name, desc, signature, exceptions); + if (getClass() != MethodNode.class) { + throw new IllegalStateException(); + } } /** @@ -222,7 +273,7 @@ public MethodNode(final int access, final String name, final String desc, * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param access * the method's access flags (see {@link Opcodes}). This * parameter also indicates if the method is synthetic and/or @@ -263,6 +314,15 @@ public MethodNode(final int api, final int access, final String name, // ------------------------------------------------------------------------ @Override + public void visitParameter(String name, int access) { + if (parameters == null) { + parameters = new ArrayList(5); + } + parameters.add(new ParameterNode(name, access)); + } + + @Override + @SuppressWarnings("serial") public AnnotationVisitor visitAnnotationDefault() { return new AnnotationNode(new ArrayList(0) { @Override @@ -292,6 +352,25 @@ public AnnotationVisitor visitAnnotation(final String desc, } @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + TypeAnnotationNode an = new TypeAnnotationNode(typeRef, typePath, desc); + if (visible) { + if (visibleTypeAnnotations == null) { + visibleTypeAnnotations = new ArrayList(1); + } + visibleTypeAnnotations.add(an); + } else { + if (invisibleTypeAnnotations == null) { + invisibleTypeAnnotations = new ArrayList(1); + } + invisibleTypeAnnotations.add(an); + } + return an; + } + + @Override + @SuppressWarnings("unchecked") public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { AnnotationNode an = new AnnotationNode(desc); @@ -365,12 +444,27 @@ public void visitFieldInsn(final int opcode, final String owner, instructions.add(new FieldInsnNode(opcode, owner, name, desc)); } + @Deprecated @Override - public void visitMethodInsn(final int opcode, final String owner, - final String name, final String desc) { + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } instructions.add(new MethodInsnNode(opcode, owner, name, desc)); } + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + instructions.add(new MethodInsnNode(opcode, owner, name, desc, itf)); + } + @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { @@ -416,6 +510,33 @@ public void visitMultiANewArrayInsn(final String desc, final int dims) { instructions.add(new MultiANewArrayInsnNode(desc, dims)); } + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + // Finds the last real instruction, i.e. the instruction targeted by + // this annotation. + AbstractInsnNode insn = instructions.getLast(); + while (insn.getOpcode() == -1) { + insn = insn.getPrevious(); + } + // Adds the annotation to this instruction. + TypeAnnotationNode an = new TypeAnnotationNode(typeRef, typePath, desc); + if (visible) { + if (insn.visibleTypeAnnotations == null) { + insn.visibleTypeAnnotations = new ArrayList( + 1); + } + insn.visibleTypeAnnotations.add(an); + } else { + if (insn.invisibleTypeAnnotations == null) { + insn.invisibleTypeAnnotations = new ArrayList( + 1); + } + insn.invisibleTypeAnnotations.add(an); + } + return an; + } + @Override public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { @@ -423,6 +544,27 @@ public void visitTryCatchBlock(final Label start, final Label end, getLabelNode(end), getLabelNode(handler), type)); } + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + TryCatchBlockNode tcb = tryCatchBlocks.get((typeRef & 0x00FFFF00) >> 8); + TypeAnnotationNode an = new TypeAnnotationNode(typeRef, typePath, desc); + if (visible) { + if (tcb.visibleTypeAnnotations == null) { + tcb.visibleTypeAnnotations = new ArrayList( + 1); + } + tcb.visibleTypeAnnotations.add(an); + } else { + if (tcb.invisibleTypeAnnotations == null) { + tcb.invisibleTypeAnnotations = new ArrayList( + 1); + } + tcb.invisibleTypeAnnotations.add(an); + } + return an; + } + @Override public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, @@ -431,6 +573,29 @@ public void visitLocalVariable(final String name, final String desc, getLabelNode(start), getLabelNode(end), index)); } + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + LocalVariableAnnotationNode an = new LocalVariableAnnotationNode( + typeRef, typePath, getLabelNodes(start), getLabelNodes(end), + index, desc); + if (visible) { + if (visibleLocalVariableAnnotations == null) { + visibleLocalVariableAnnotations = new ArrayList( + 1); + } + visibleLocalVariableAnnotations.add(an); + } else { + if (invisibleLocalVariableAnnotations == null) { + invisibleLocalVariableAnnotations = new ArrayList( + 1); + } + invisibleLocalVariableAnnotations.add(an); + } + return an; + } + @Override public void visitLineNumber(final int line, final Label start) { instructions.add(new LineNumberNode(line, getLabelNode(start))); @@ -494,10 +659,57 @@ private Object[] getLabelNodes(final Object[] objs) { * versions of the ASM API than the given version. * * @param api - * an ASM API version. Must be one of {@link Opcodes#ASM4}. + * an ASM API version. Must be one of {@link Opcodes#ASM4} or + * {@link Opcodes#ASM5}. */ public void check(final int api) { - // nothing to do + if (api == Opcodes.ASM4) { + if (visibleTypeAnnotations != null + && visibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + if (invisibleTypeAnnotations != null + && invisibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + int n = tryCatchBlocks == null ? 0 : tryCatchBlocks.size(); + for (int i = 0; i < n; ++i) { + TryCatchBlockNode tcb = tryCatchBlocks.get(i); + if (tcb.visibleTypeAnnotations != null + && tcb.visibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + if (tcb.invisibleTypeAnnotations != null + && tcb.invisibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + } + for (int i = 0; i < instructions.size(); ++i) { + AbstractInsnNode insn = instructions.get(i); + if (insn.visibleTypeAnnotations != null + && insn.visibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + if (insn.invisibleTypeAnnotations != null + && insn.invisibleTypeAnnotations.size() > 0) { + throw new RuntimeException(); + } + if (insn instanceof MethodInsnNode) { + boolean itf = ((MethodInsnNode) insn).itf; + if (itf != (insn.opcode == Opcodes.INVOKEINTERFACE)) { + throw new RuntimeException(); + } + } + } + if (visibleLocalVariableAnnotations != null + && visibleLocalVariableAnnotations.size() > 0) { + throw new RuntimeException(); + } + if (invisibleLocalVariableAnnotations != null + && invisibleLocalVariableAnnotations.size() > 0) { + throw new RuntimeException(); + } + } } /** @@ -523,8 +735,14 @@ public void accept(final ClassVisitor cv) { * a method visitor. */ public void accept(final MethodVisitor mv) { - // visits the method attributes + // visits the method parameters int i, j, n; + n = parameters == null ? 0 : parameters.size(); + for (i = 0; i < n; i++) { + ParameterNode parameter = parameters.get(i); + mv.visitParameter(parameter.name, parameter.access); + } + // visits the method attributes if (annotationDefault != null) { AnnotationVisitor av = mv.visitAnnotationDefault(); AnnotationNode.accept(av, null, annotationDefault); @@ -542,6 +760,19 @@ public void accept(final MethodVisitor mv) { AnnotationNode an = invisibleAnnotations.get(i); an.accept(mv.visitAnnotation(an.desc, false)); } + n = visibleTypeAnnotations == null ? 0 : visibleTypeAnnotations.size(); + for (i = 0; i < n; ++i) { + TypeAnnotationNode an = visibleTypeAnnotations.get(i); + an.accept(mv.visitTypeAnnotation(an.typeRef, an.typePath, an.desc, + true)); + } + n = invisibleTypeAnnotations == null ? 0 : invisibleTypeAnnotations + .size(); + for (i = 0; i < n; ++i) { + TypeAnnotationNode an = invisibleTypeAnnotations.get(i); + an.accept(mv.visitTypeAnnotation(an.typeRef, an.typePath, an.desc, + false)); + } n = visibleParameterAnnotations == null ? 0 : visibleParameterAnnotations.length; for (i = 0; i < n; ++i) { @@ -579,6 +810,7 @@ public void accept(final MethodVisitor mv) { // visits try catch blocks n = tryCatchBlocks == null ? 0 : tryCatchBlocks.size(); for (i = 0; i < n; ++i) { + tryCatchBlocks.get(i).updateIndex(i); tryCatchBlocks.get(i).accept(mv); } // visits instructions @@ -588,6 +820,17 @@ public void accept(final MethodVisitor mv) { for (i = 0; i < n; ++i) { localVariables.get(i).accept(mv); } + // visits local variable annotations + n = visibleLocalVariableAnnotations == null ? 0 + : visibleLocalVariableAnnotations.size(); + for (i = 0; i < n; ++i) { + visibleLocalVariableAnnotations.get(i).accept(mv, true); + } + n = invisibleLocalVariableAnnotations == null ? 0 + : invisibleLocalVariableAnnotations.size(); + for (i = 0; i < n; ++i) { + invisibleLocalVariableAnnotations.get(i).accept(mv, false); + } // visits maxs mv.visitMaxs(maxStack, maxLocals); visited = true; diff --git a/src/java/nginx/clojure/asm/tree/MultiANewArrayInsnNode.java b/src/java/nginx/clojure/asm/tree/MultiANewArrayInsnNode.java index 872b1b80..6cf0f02d 100644 --- a/src/java/nginx/clojure/asm/tree/MultiANewArrayInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/MultiANewArrayInsnNode.java @@ -73,11 +73,12 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitMultiANewArrayInsn(desc, dims); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new MultiANewArrayInsnNode(desc, dims); + return new MultiANewArrayInsnNode(desc, dims).cloneAnnotations(this); } } \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/tree/ParameterNode.java b/src/java/nginx/clojure/asm/tree/ParameterNode.java new file mode 100644 index 00000000..325ea3e7 --- /dev/null +++ b/src/java/nginx/clojure/asm/tree/ParameterNode.java @@ -0,0 +1,76 @@ +/*** + * 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 nginx.clojure.asm.MethodVisitor; + +/** + * A node that represents a parameter access and name. + * + * @author Remi Forax + */ +public class ParameterNode { + /** + * The parameter's name. + */ + public String name; + + /** + * The parameter's access flags (see {@link nginx.clojure.asm.Opcodes}). + * Valid values are ACC_FINAL, ACC_SYNTHETIC and + * ACC_MANDATED. + */ + public int access; + + /** + * Constructs a new {@link ParameterNode}. + * + * @param access + * The parameter's access flags. Valid values are + * ACC_FINAL, ACC_SYNTHETIC or/and + * ACC_MANDATED (see {@link nginx.clojure.asm.Opcodes}). + * @param name + * the parameter's name. + */ + public ParameterNode(final String name, final int access) { + this.name = name; + this.access = access; + } + + /** + * Makes the given visitor visit this parameter declaration. + * + * @param mv + * a method visitor. + */ + public void accept(final MethodVisitor mv) { + mv.visitParameter(name, access); + } +} diff --git a/src/java/nginx/clojure/asm/tree/TableSwitchInsnNode.java b/src/java/nginx/clojure/asm/tree/TableSwitchInsnNode.java index 1a7a9b3e..f1427702 100644 --- a/src/java/nginx/clojure/asm/tree/TableSwitchInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/TableSwitchInsnNode.java @@ -103,11 +103,12 @@ public void accept(final MethodVisitor mv) { labels[i] = this.labels.get(i).getLabel(); } mv.visitTableSwitchInsn(min, max, dflt.getLabel(), labels); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { return new TableSwitchInsnNode(min, max, clone(dflt, labels), clone( - this.labels, labels)); + this.labels, labels)).cloneAnnotations(this); } } \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/tree/TryCatchBlockNode.java b/src/java/nginx/clojure/asm/tree/TryCatchBlockNode.java index 9cd79ce1..508eba03 100644 --- a/src/java/nginx/clojure/asm/tree/TryCatchBlockNode.java +++ b/src/java/nginx/clojure/asm/tree/TryCatchBlockNode.java @@ -29,6 +29,8 @@ */ package nginx.clojure.asm.tree; +import java.util.List; + import nginx.clojure.asm.MethodVisitor; /** @@ -59,6 +61,26 @@ public class TryCatchBlockNode { */ public String type; + /** + * The runtime visible type annotations on the exception handler type. This + * list is a list of {@link TypeAnnotationNode} objects. May be + * null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label visible + */ + public List visibleTypeAnnotations; + + /** + * The runtime invisible type annotations on the exception handler type. + * This list is a list of {@link TypeAnnotationNode} objects. May be + * null. + * + * @associates nginx.clojure.asm.tree.TypeAnnotationNode + * @label invisible + */ + public List invisibleTypeAnnotations; + /** * Constructs a new {@link TryCatchBlockNode}. * @@ -81,6 +103,29 @@ public TryCatchBlockNode(final LabelNode start, final LabelNode end, this.type = type; } + /** + * Updates the index of this try catch block in the method's list of try + * catch block nodes. This index maybe stored in the 'target' field of the + * type annotations of this block. + * + * @param index + * the new index of this try catch block in the method's list of + * try catch block nodes. + */ + public void updateIndex(final int index) { + int newTypeRef = 0x42000000 | (index << 8); + if (visibleTypeAnnotations != null) { + for (TypeAnnotationNode tan : visibleTypeAnnotations) { + tan.typeRef = newTypeRef; + } + } + if (invisibleTypeAnnotations != null) { + for (TypeAnnotationNode tan : invisibleTypeAnnotations) { + tan.typeRef = newTypeRef; + } + } + } + /** * Makes the given visitor visit this try catch block. * @@ -90,5 +135,19 @@ public TryCatchBlockNode(final LabelNode start, final LabelNode end, public void accept(final MethodVisitor mv) { mv.visitTryCatchBlock(start.getLabel(), end.getLabel(), handler == null ? null : handler.getLabel(), type); + int n = visibleTypeAnnotations == null ? 0 : visibleTypeAnnotations + .size(); + for (int i = 0; i < n; ++i) { + TypeAnnotationNode an = visibleTypeAnnotations.get(i); + an.accept(mv.visitTryCatchAnnotation(an.typeRef, an.typePath, + an.desc, true)); + } + n = invisibleTypeAnnotations == null ? 0 : invisibleTypeAnnotations + .size(); + for (int i = 0; i < n; ++i) { + TypeAnnotationNode an = invisibleTypeAnnotations.get(i); + an.accept(mv.visitTryCatchAnnotation(an.typeRef, an.typePath, + an.desc, false)); + } } } diff --git a/src/java/nginx/clojure/asm/tree/TypeAnnotationNode.java b/src/java/nginx/clojure/asm/tree/TypeAnnotationNode.java new file mode 100644 index 00000000..ca59b4d5 --- /dev/null +++ b/src/java/nginx/clojure/asm/tree/TypeAnnotationNode.java @@ -0,0 +1,100 @@ +/*** + * 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 nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; +import nginx.clojure.asm.TypeReference; + +/** + * A node that represents a type annotationn. + * + * @author Eric Bruneton + */ +public class TypeAnnotationNode extends AnnotationNode { + + /** + * A reference to the annotated type. See {@link TypeReference}. + */ + public int typeRef; + + /** + * The path to the annotated type argument, wildcard bound, array element + * type, or static outer type within the referenced type. May be + * null if the annotation targets 'typeRef' as a whole. + */ + public TypePath typePath; + + /** + * Constructs a new {@link AnnotationNode}. Subclasses must not use this + * constructor. Instead, they must use the + * {@link #TypeAnnotationNode(int, int, TypePath, String)} version. + * + * @param typeRef + * a reference to the annotated type. 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @throws IllegalStateException + * If a subclass calls this constructor. + */ + public TypeAnnotationNode(final int typeRef, final TypePath typePath, + final String desc) { + this(Opcodes.ASM5, typeRef, typePath, desc); + if (getClass() != TypeAnnotationNode.class) { + throw new IllegalStateException(); + } + } + + /** + * Constructs a new {@link AnnotationNode}. + * + * @param api + * the ASM API version implemented by this visitor. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. + * @param typeRef + * a reference to the annotated type. 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + */ + public TypeAnnotationNode(final int api, final int typeRef, + final TypePath typePath, final String desc) { + super(api, desc); + this.typeRef = typeRef; + this.typePath = typePath; + } +} diff --git a/src/java/nginx/clojure/asm/tree/TypeInsnNode.java b/src/java/nginx/clojure/asm/tree/TypeInsnNode.java index 035d1ee1..178dc602 100644 --- a/src/java/nginx/clojure/asm/tree/TypeInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/TypeInsnNode.java @@ -81,10 +81,11 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitTypeInsn(opcode, desc); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new TypeInsnNode(opcode, desc); + return new TypeInsnNode(opcode, desc).cloneAnnotations(this); } } \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/tree/VarInsnNode.java b/src/java/nginx/clojure/asm/tree/VarInsnNode.java index 22f6a04d..66a6ae55 100644 --- a/src/java/nginx/clojure/asm/tree/VarInsnNode.java +++ b/src/java/nginx/clojure/asm/tree/VarInsnNode.java @@ -84,10 +84,11 @@ public int getType() { @Override public void accept(final MethodVisitor mv) { mv.visitVarInsn(opcode, var); + acceptAnnotations(mv); } @Override public AbstractInsnNode clone(final Map labels) { - return new VarInsnNode(opcode, var); + return new VarInsnNode(opcode, var).cloneAnnotations(this); } } \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/tree/analysis/Analyzer.java b/src/java/nginx/clojure/asm/tree/analysis/Analyzer.java index 4d60163d..6cacdd5d 100644 --- a/src/java/nginx/clojure/asm/tree/analysis/Analyzer.java +++ b/src/java/nginx/clojure/asm/tree/analysis/Analyzer.java @@ -102,6 +102,7 @@ public Analyzer(final Interpreter interpreter) { * @throws AnalyzerException * if a problem occurs during the analysis. */ + @SuppressWarnings("unchecked") public Frame[] analyze(final String owner, final MethodNode m) throws AnalyzerException { if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) { diff --git a/src/java/nginx/clojure/asm/tree/analysis/AnalyzerException.java b/src/java/nginx/clojure/asm/tree/analysis/AnalyzerException.java index 7e3e3848..83209b56 100644 --- a/src/java/nginx/clojure/asm/tree/analysis/AnalyzerException.java +++ b/src/java/nginx/clojure/asm/tree/analysis/AnalyzerException.java @@ -37,6 +37,7 @@ * @author Bing Ran * @author Eric Bruneton */ +@SuppressWarnings("serial") public class AnalyzerException extends Exception { public final AbstractInsnNode node; diff --git a/src/java/nginx/clojure/asm/tree/analysis/BasicInterpreter.java b/src/java/nginx/clojure/asm/tree/analysis/BasicInterpreter.java index c40df7a8..adb653ac 100644 --- a/src/java/nginx/clojure/asm/tree/analysis/BasicInterpreter.java +++ b/src/java/nginx/clojure/asm/tree/analysis/BasicInterpreter.java @@ -53,7 +53,7 @@ public class BasicInterpreter extends Interpreter implements Opcodes { public BasicInterpreter() { - super(ASM4); + super(ASM5); } protected BasicInterpreter(final int api) { diff --git a/src/java/nginx/clojure/asm/tree/analysis/BasicVerifier.java b/src/java/nginx/clojure/asm/tree/analysis/BasicVerifier.java index c14fb5a8..aaecd54e 100644 --- a/src/java/nginx/clojure/asm/tree/analysis/BasicVerifier.java +++ b/src/java/nginx/clojure/asm/tree/analysis/BasicVerifier.java @@ -47,7 +47,7 @@ public class BasicVerifier extends BasicInterpreter { public BasicVerifier() { - super(ASM4); + super(ASM5); } protected BasicVerifier(final int api) { diff --git a/src/java/nginx/clojure/asm/tree/analysis/Frame.java b/src/java/nginx/clojure/asm/tree/analysis/Frame.java index b06b399f..a4629427 100644 --- a/src/java/nginx/clojure/asm/tree/analysis/Frame.java +++ b/src/java/nginx/clojure/asm/tree/analysis/Frame.java @@ -83,6 +83,7 @@ public class Frame { * @param nStack * the maximum stack size of the frame. */ + @SuppressWarnings("unchecked") public Frame(final int nLocals, final int nStack) { this.values = (V[]) new Value[nLocals + nStack]; this.locals = nLocals; @@ -133,6 +134,15 @@ public int getLocals() { return locals; } + /** + * Returns the maximum stack size of this frame. + * + * @return the maximum stack size of this frame. + */ + public int getMaxStackSize() { + return values.length - locals; + } + /** * Returns the value of the given local variable. * @@ -716,14 +726,14 @@ public boolean merge(final Frame frame, final boolean[] access) { */ @Override public String toString() { - StringBuffer b = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < getLocals(); ++i) { - b.append(getLocal(i)); + sb.append(getLocal(i)); } - b.append(' '); + sb.append(' '); for (int i = 0; i < getStackSize(); ++i) { - b.append(getStack(i).toString()); + sb.append(getStack(i).toString()); } - return b.toString(); + return sb.toString(); } } diff --git a/src/java/nginx/clojure/asm/tree/analysis/SimpleVerifier.java b/src/java/nginx/clojure/asm/tree/analysis/SimpleVerifier.java index 595b5fa2..eb050037 100644 --- a/src/java/nginx/clojure/asm/tree/analysis/SimpleVerifier.java +++ b/src/java/nginx/clojure/asm/tree/analysis/SimpleVerifier.java @@ -107,7 +107,7 @@ public SimpleVerifier(final Type currentClass, public SimpleVerifier(final Type currentClass, final Type currentSuperClass, final List currentClassInterfaces, final boolean isInterface) { - this(ASM4, currentClass, currentSuperClass, currentClassInterfaces, + this(ASM5, currentClass, currentSuperClass, currentClassInterfaces, isInterface); } diff --git a/src/java/nginx/clojure/asm/tree/analysis/SourceInterpreter.java b/src/java/nginx/clojure/asm/tree/analysis/SourceInterpreter.java index 68115a1d..7eec875a 100644 --- a/src/java/nginx/clojure/asm/tree/analysis/SourceInterpreter.java +++ b/src/java/nginx/clojure/asm/tree/analysis/SourceInterpreter.java @@ -50,7 +50,7 @@ public class SourceInterpreter extends Interpreter implements Opcodes { public SourceInterpreter() { - super(ASM4); + super(ASM5); } protected SourceInterpreter(final int api) { diff --git a/src/java/nginx/clojure/asm/tree/analysis/package.html b/src/java/nginx/clojure/asm/tree/analysis/package.html new file mode 100644 index 00000000..228da023 --- /dev/null +++ b/src/java/nginx/clojure/asm/tree/analysis/package.html @@ -0,0 +1,67 @@ + + + + +

      +Provides a framework for static code analysis based on the asm.tree package. +

      + +

      +Basic usage: +

      + +
      +ClassReader cr = new ClassReader(bytecode);
      +ClassNode cn = new ClassNode();
      +cr.accept(cn, ClassReader.SKIP_DEBUG);
      +
      +List methods = cn.methods;
      +for (int i = 0; i < methods.size(); ++i) {
      +    MethodNode method = (MethodNode) methods.get(i);
      +    if (method.instructions.size() > 0) {
      +        Analyzer a = new Analyzer(new BasicInterpreter());
      +        a.analyze(cn.name, method);
      +        Frame[] frames = a.getFrames();
      +        // Elements of the frames arrray now contains info for each instruction
      +        // from the analyzed method. BasicInterpreter creates BasicValue, that
      +        // is using simplified type system that distinguishes the UNINITIALZED,
      +        // INT, FLOAT, LONG, DOUBLE, REFERENCE and RETURNADDRESS types.
      +        ...
      +    }
      +}
      +
      + +

      +@since ASM 1.4.3 +

      + + + diff --git a/src/java/nginx/clojure/asm/tree/package.html b/src/java/nginx/clojure/asm/tree/package.html new file mode 100644 index 00000000..940b8767 --- /dev/null +++ b/src/java/nginx/clojure/asm/tree/package.html @@ -0,0 +1,192 @@ + + + + +

      +Provides an ASM visitor that constructs a tree representation of the +classes it visits. This class adapter can be useful to implement "complex" +class manipulation operations, i.e., operations that would be very hard to +implement without using a tree representation (such as optimizing the number +of local variables used by a method). +

      + +

      +However, this class adapter has a cost: it makes ASM bigger and slower. Indeed +it requires more than twenty new classes, and multiplies the time needed to +transform a class by almost two (it is almost two times faster to read, "modify" +and write a class with a ClassVisitor than with a ClassNode). This is why +this package is bundled in an optional asm-tree.jar library that +is separated from (but requires) the asm.jar library, which contains +the core ASM framework. This is also why it is recommended +not to use this class adapter when it is possible. +

      + +

      +The root class is the ClassNode, that can be created from existing bytecode. For example: +

      + +
      +  ClassReader cr = new ClassReader(source);
      +  ClassNode cn = new ClassNode();
      +  cr.accept(cn, true);
      +
      + +

      +Now the content of ClassNode can be modified and then +serialized back into bytecode: +

      + +
      +  ClassWriter cw = new ClassWriter(true);
      +  cn.accept(cw);
      +
      + +

      +Using a simple ClassVisitor it is possible to create MethodNode instances per-method. +In this example MethodNode is acting as a buffer that is flushed out at visitEnd() call: +

      + +
      +  ClassReader cr = new ClassReader(source);
      +  ClassWriter cw = new ClassWriter();
      +  ClassVisitor cv = new ClassVisitor(cw) {
      +    public MethodVisitor visitMethod(int access, String name,
      +        String desc, String signature, String[] exceptions) {
      +      final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
      +      MethodNode mn = new MethodNode(access, name, desc, signature, exceptions) {
      +        public void visitEnd() {
      +          // transform or analyze method code using tree API
      +          accept(mv);
      +        }
      +      };
      +    }
      +  };
      +  cr.accept(cv, true);
      +
      + +

      +Several strategies can be used to construct method code from scratch. The first +option is to create a MethodNode, and then create XxxInsnNode instances and +add them to the instructions list: +

      + +
      +MethodNode m = new MethodNode(...);
      +m.instructions.add(new VarInsnNode(ALOAD, 0));
      +...
      +
      + +

      +Alternatively, you can use the fact that MethodNode is a MethodVisitor, and use +that to create the XxxInsnNode and add them to the instructions list through +the standard MethodVisitor methods: +

      + +
      +MethodNode m = new MethodNode(...);
      +m.visitVarInsn(ALOAD, 0);
      +...
      +
      + +

      +If you cannot generate all the instructions in sequential order, i.e. if you +need to save some pointer in the instruction list and then insert instructions +at that place after other instructions have been generated, you can use InsnList +methods insert() and insertBefore() to insert instructions at a saved pointer. +

      + +
      +MethodNode m = new MethodNode(...);
      +m.visitVarInsn(ALOAD, 0);
      +AbstractInsnNode ptr = m.instructions.getLast();
      +m.visitVarInsn(ALOAD, 1);
      +// inserts an instruction between ALOAD 0 and ALOAD 1
      +m.instructions.insert(ptr, new VarInsnNode(ALOAD, 0));
      +...
      +
      + +

      +If you need to insert instructions while iterating over an existing instruction +list, you can also use several strategies. The first one is to use a +ListIterator over the instruction list: +

      + +
      +ListIterator it = m.instructions.iterator();
      +while (it.hasNext()) {
      +    AbstractInsnNode n = (AbstractInsnNode) it.next();
      +    if (...) {
      +        it.add(new VarInsnNode(ALOAD, 0));
      +    }
      +}
      +
      + +

      +It is also possible to convert an instruction list into an array and iterate trough +array elements: +

      + +
      +AbstractInsnNode[] insns = m.instructions.toArray();
      +for(int i = 0; i<insns.length; i++) {
      +    AbstractInsnNode n = insns[i];
      +    if (...) {
      +        m.instructions.insert(n, new VarInsnNode(ALOAD, 0));
      +    }
      +}
      +
      + +

      +If you want to insert these instructions through the MethodVisitor methods, +you can use another instance of MethodNode as a MethodVisitor and then +insert instructions collected by that instance into the instruction list. +For example: +

      + +
      +AbstractInsnNode[] insns = m.instructions.toArray();
      +for(int i = 0; i<insns.length; i++) {
      +    AbstractInsnNode n = insns[i];
      +    if (...) {
      +        MethodNode mn = new MethodNode();
      +        mn.visitVarInsn(ALOAD, 0);
      +        mn.visitVarInsn(ALOAD, 1);
      +        m.instructions.insert(n, mn.instructions);
      +    }
      +}
      +
      + +

      +@since ASM 1.3.3 +

      + + + diff --git a/src/java/nginx/clojure/asm/util/ASMifier.java b/src/java/nginx/clojure/asm/util/ASMifier.java index 6f0fbd94..9226c1d1 100644 --- a/src/java/nginx/clojure/asm/util/ASMifier.java +++ b/src/java/nginx/clojure/asm/util/ASMifier.java @@ -40,6 +40,7 @@ import nginx.clojure.asm.Label; import nginx.clojure.asm.Opcodes; import nginx.clojure.asm.Type; +import nginx.clojure.asm.TypePath; /** * A {@link Printer} that prints the ASM code to generate the classes if visits. @@ -83,9 +84,15 @@ public class ASMifier extends Printer { * Constructs a new {@link ASMifier}. Subclasses must not use this * constructor. Instead, they must use the * {@link #ASMifier(int, String, int)} version. + * + * @throws IllegalStateException + * If a subclass calls this constructor. */ public ASMifier() { - this(Opcodes.ASM4, "cw", 0); + this(Opcodes.ASM5, "cw", 0); + if (getClass() != ASMifier.class) { + throw new IllegalStateException(); + } } /** @@ -93,7 +100,7 @@ public ASMifier() { * * @param api * the ASM API version implemented by this class. Must be one of - * {@link Opcodes#ASM4}. + * {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param name * the name of the visitor variable in the produced code. * @param id @@ -170,7 +177,6 @@ public void visit(final int version, final int access, final String name, } text.add("import java.util.*;\n"); text.add("import nginx.clojure.asm.*;\n"); - text.add("import nginx.clojure.asm.attrs.*;\n"); text.add("public class " + simpleName + "Dump implements Opcodes {\n\n"); text.add("public static byte[] dump () throws Exception {\n\n"); text.add("ClassWriter cw = new ClassWriter(0);\n"); @@ -201,10 +207,7 @@ public void visit(final int version, final int access, final String name, break; case Opcodes.V1_7: buf.append("V1_7"); - break; - case Opcodes.V1_8: - buf.append("V1_7"); - break; + break; default: buf.append(version); break; @@ -263,6 +266,12 @@ public ASMifier visitClassAnnotation(final String desc, return visitAnnotation(desc, visible); } + @Override + public ASMifier visitClassTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + return visitTypeAnnotation(typeRef, typePath, desc, visible); + } + @Override public void visitClassAttribute(final Attribute attr) { visitAttribute(attr); @@ -425,6 +434,12 @@ public ASMifier visitFieldAnnotation(final String desc, return visitAnnotation(desc, visible); } + @Override + public ASMifier visitFieldTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + return visitTypeAnnotation(typeRef, typePath, desc, visible); + } + @Override public void visitFieldAttribute(final Attribute attr) { visitAttribute(attr); @@ -441,6 +456,16 @@ public void visitFieldEnd() { // Methods // ------------------------------------------------------------------------ + @Override + public void visitParameter(String parameterName, int access) { + buf.setLength(0); + buf.append(name).append(".visitParameter("); + appendString(buf, parameterName); + buf.append(", "); + appendAccess(access); + text.add(buf.append(");\n").toString()); + } + @Override public ASMifier visitAnnotationDefault() { buf.setLength(0); @@ -459,6 +484,12 @@ public ASMifier visitMethodAnnotation(final String desc, return visitAnnotation(desc, visible); } + @Override + public ASMifier visitMethodTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + return visitTypeAnnotation(typeRef, typePath, desc, visible); + } + @Override public ASMifier visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { @@ -585,9 +616,30 @@ public void visitFieldInsn(final int opcode, final String owner, text.add(buf.toString()); } + @Deprecated @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { buf.setLength(0); buf.append(this.name).append(".visitMethodInsn(") .append(OPCODES[opcode]).append(", "); @@ -596,6 +648,8 @@ public void visitMethodInsn(final int opcode, final String owner, appendConstant(name); buf.append(", "); appendConstant(desc); + buf.append(", "); + buf.append(itf ? "true" : "false"); buf.append(");\n"); text.add(buf.toString()); } @@ -713,6 +767,13 @@ public void visitMultiANewArrayInsn(final String desc, final int dims) { text.add(buf.toString()); } + @Override + public ASMifier visitInsnAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + return visitTypeAnnotation("visitInsnAnnotation", typeRef, typePath, + desc, visible); + } + @Override public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { @@ -732,6 +793,13 @@ public void visitTryCatchBlock(final Label start, final Label end, text.add(buf.toString()); } + @Override + public ASMifier visitTryCatchAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + return visitTypeAnnotation("visitTryCatchAnnotation", typeRef, + typePath, desc, visible); + } + @Override public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, @@ -751,6 +819,43 @@ public void visitLocalVariable(final String name, final String desc, text.add(buf.toString()); } + @Override + public Printer visitLocalVariableAnnotation(int typeRef, TypePath typePath, + Label[] start, Label[] end, int[] index, String desc, + boolean visible) { + buf.setLength(0); + buf.append("{\n").append("av0 = ").append(name) + .append(".visitLocalVariableAnnotation("); + buf.append(typeRef); + if (typePath == null) { + buf.append(", null, "); + } else { + buf.append(", TypePath.fromString(\"").append(typePath).append("\"), "); + } + buf.append("new Label[] {"); + for (int i = 0; i < start.length; ++i) { + buf.append(i == 0 ? " " : ", "); + appendLabel(start[i]); + } + buf.append(" }, new Label[] {"); + for (int i = 0; i < end.length; ++i) { + buf.append(i == 0 ? " " : ", "); + appendLabel(end[i]); + } + buf.append(" }, new int[] {"); + for (int i = 0; i < index.length; ++i) { + buf.append(i == 0 ? " " : ", ").append(index[i]); + } + buf.append(" }, "); + appendConstant(desc); + buf.append(", ").append(visible).append(");\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("av", 0); + text.add(a.getText()); + text.add("}\n"); + return a; + } + @Override public void visitLineNumber(final int line, final Label start) { buf.setLength(0); @@ -792,6 +897,32 @@ public ASMifier visitAnnotation(final String desc, final boolean visible) { return a; } + public ASMifier visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + return visitTypeAnnotation("visitTypeAnnotation", typeRef, typePath, + desc, visible); + } + + public ASMifier visitTypeAnnotation(final String method, final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + buf.setLength(0); + buf.append("{\n").append("av0 = ").append(name).append(".") + .append(method).append("("); + buf.append(typeRef); + if (typePath == null) { + buf.append(", null, "); + } else { + buf.append(", TypePath.fromString(\"").append(typePath).append("\"), "); + } + appendConstant(desc); + buf.append(", ").append(visible).append(");\n"); + text.add(buf.toString()); + ASMifier a = createASMifier("av", 0); + text.add(a.getText()); + text.add("}\n"); + return a; + } + public void visitAttribute(final Attribute attr) { buf.setLength(0); buf.append("// ATTRIBUTE ").append(attr.type).append('\n'); @@ -812,7 +943,7 @@ public void visitAttribute(final Attribute attr) { // ------------------------------------------------------------------------ protected ASMifier createASMifier(final String name, final int id) { - return new ASMifier(Opcodes.ASM4, name, id); + return new ASMifier(Opcodes.ASM5, name, id); } /** @@ -953,6 +1084,13 @@ void appendAccess(final int access) { buf.append("ACC_DEPRECATED"); first = false; } + if ((access & Opcodes.ACC_MANDATED) != 0) { + if (!first) { + buf.append(" + "); + } + buf.append("ACC_MANDATED"); + first = false; + } if (first) { buf.append('0'); } diff --git a/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java b/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java index f2242f7f..e48a732b 100644 --- a/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java +++ b/src/java/nginx/clojure/asm/util/CheckAnnotationAdapter.java @@ -49,7 +49,7 @@ public CheckAnnotationAdapter(final AnnotationVisitor av) { } CheckAnnotationAdapter(final AnnotationVisitor av, final boolean named) { - super(Opcodes.ASM4, av); + super(Opcodes.ASM5, av); this.named = named; } @@ -70,7 +70,7 @@ public void visit(final String name, final Object value) { } if (value instanceof Type) { int sort = ((Type) value).getSort(); - if (sort != Type.OBJECT && sort != Type.ARRAY) { + if (sort == Type.METHOD) { throw new IllegalArgumentException("Invalid annotation value"); } } diff --git a/src/java/nginx/clojure/asm/util/CheckClassAdapter.java b/src/java/nginx/clojure/asm/util/CheckClassAdapter.java index 3f673650..9f7d5f23 100644 --- a/src/java/nginx/clojure/asm/util/CheckClassAdapter.java +++ b/src/java/nginx/clojure/asm/util/CheckClassAdapter.java @@ -46,6 +46,8 @@ import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; import nginx.clojure.asm.Type; +import nginx.clojure.asm.TypePath; +import nginx.clojure.asm.TypeReference; import nginx.clojure.asm.tree.ClassNode; import nginx.clojure.asm.tree.MethodNode; import nginx.clojure.asm.tree.analysis.Analyzer; @@ -106,7 +108,7 @@ * 00071 LinkedBlockingQueue$Itr . I . . . . . . : * ILOAD 1 * 00072 ? - * INVOKESPECIAL java/lang/Integer. (I)V + * INVOKESPECIAL java/lang/Integer.<init> (I)V * ... * * @@ -215,7 +217,7 @@ public static void verify(final ClassReader cr, final ClassLoader loader, List interfaces = new ArrayList(); for (Iterator i = cn.interfaces.iterator(); i.hasNext();) { - interfaces.add(Type.getObjectType(i.next().toString())); + interfaces.add(Type.getObjectType(i.next())); } for (int i = 0; i < methods.size(); ++i) { @@ -267,26 +269,26 @@ static void printAnalyzerResult(MethodNode method, Analyzer a, for (int j = 0; j < method.instructions.size(); ++j) { method.instructions.get(j).accept(mv); - StringBuffer s = new StringBuffer(); + StringBuilder sb = new StringBuilder(); Frame f = frames[j]; if (f == null) { - s.append('?'); + sb.append('?'); } else { for (int k = 0; k < f.getLocals(); ++k) { - s.append(getShortName(f.getLocal(k).toString())) + sb.append(getShortName(f.getLocal(k).toString())) .append(' '); } - s.append(" : "); + sb.append(" : "); for (int k = 0; k < f.getStackSize(); ++k) { - s.append(getShortName(f.getStack(k).toString())) + sb.append(getShortName(f.getStack(k).toString())) .append(' '); } } - while (s.length() < method.maxStack + method.maxLocals + 1) { - s.append(' '); + while (sb.length() < method.maxStack + method.maxLocals + 1) { + sb.append(' '); } pw.print(Integer.toString(j + 100000).substring(1)); - pw.print(" " + s + " : " + t.text.get(t.text.size() - 1)); + pw.print(" " + sb + " : " + t.text.get(t.text.size() - 1)); } for (int j = 0; j < method.tryCatchBlocks.size(); ++j) { method.tryCatchBlocks.get(j).accept(mv); @@ -328,9 +330,14 @@ public CheckClassAdapter(final ClassVisitor cv) { * false to not perform any data flow check (see * {@link CheckMethodAdapter}). This option requires valid * maxLocals and maxStack values. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public CheckClassAdapter(final ClassVisitor cv, final boolean checkDataFlow) { - this(Opcodes.ASM4, cv, checkDataFlow); + this(Opcodes.ASM5, cv, checkDataFlow); + if (getClass() != CheckClassAdapter.class) { + throw new IllegalStateException(); + } } /** @@ -338,7 +345,7 @@ public CheckClassAdapter(final ClassVisitor cv, final boolean checkDataFlow) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param cv * the class visitor to which this adapter must delegate calls. * @param checkDataFlow @@ -524,6 +531,23 @@ public AnnotationVisitor visitAnnotation(final String desc, return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible)); } + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + checkState(); + int sort = typeRef >>> 24; + if (sort != TypeReference.CLASS_TYPE_PARAMETER + && sort != TypeReference.CLASS_TYPE_PARAMETER_BOUND + && sort != TypeReference.CLASS_EXTENDS) { + throw new IllegalArgumentException("Invalid type reference sort 0x" + + Integer.toHexString(sort)); + } + checkTypeRefAndPath(typeRef, typePath); + CheckMethodAdapter.checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitTypeAnnotation(typeRef, + typePath, desc, visible)); + } + @Override public void visitAttribute(final Attribute attr) { checkState(); @@ -668,6 +692,77 @@ public static void checkFieldSignature(final String signature) { } } + /** + * Checks the reference to a type in a type annotation. + * + * @param typeRef + * a reference to an annotated type. + * @param typePath + * the path to the annotated type argument, wildcard bound, array + * element type, or static inner type within 'typeRef'. May be + * null if the annotation targets 'typeRef' as a whole. + */ + static void checkTypeRefAndPath(int typeRef, TypePath typePath) { + int mask = 0; + switch (typeRef >>> 24) { + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + mask = 0xFFFF0000; + break; + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + mask = 0xFF000000; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + mask = 0xFFFFFF00; + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + mask = 0xFF0000FF; + break; + default: + throw new IllegalArgumentException("Invalid type reference sort 0x" + + Integer.toHexString(typeRef >>> 24)); + } + if ((typeRef & ~mask) != 0) { + throw new IllegalArgumentException("Invalid type reference 0x" + + Integer.toHexString(typeRef)); + } + if (typePath != null) { + for (int i = 0; i < typePath.getLength(); ++i) { + int step = typePath.getStep(i); + if (step != TypePath.ARRAY_ELEMENT + && step != TypePath.INNER_TYPE + && step != TypePath.TYPE_ARGUMENT + && step != TypePath.WILDCARD_BOUND) { + throw new IllegalArgumentException( + "Invalid type path step " + i + " in " + typePath); + } + if (step != TypePath.TYPE_ARGUMENT + && typePath.getStepArgument(i) != 0) { + throw new IllegalArgumentException( + "Invalid type path step argument for step " + i + + " in " + typePath); + } + } + } + } + /** * Checks the formal type parameters of a class or method signature. * diff --git a/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java b/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java index 5a294f32..2450dcea 100644 --- a/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java +++ b/src/java/nginx/clojure/asm/util/CheckFieldAdapter.java @@ -33,6 +33,8 @@ import nginx.clojure.asm.Attribute; import nginx.clojure.asm.FieldVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; +import nginx.clojure.asm.TypeReference; /** * A {@link FieldVisitor} that checks that its methods are properly used. @@ -48,9 +50,14 @@ public class CheckFieldAdapter extends FieldVisitor { * * @param fv * the field visitor to which this adapter must delegate calls. + * @throws IllegalStateException + * If a subclass calls this constructor. */ public CheckFieldAdapter(final FieldVisitor fv) { - this(Opcodes.ASM4, fv); + this(Opcodes.ASM5, fv); + if (getClass() != CheckFieldAdapter.class) { + throw new IllegalStateException(); + } } /** @@ -58,7 +65,7 @@ public CheckFieldAdapter(final FieldVisitor fv) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param fv * the field visitor to which this adapter must delegate calls. */ @@ -74,6 +81,21 @@ public AnnotationVisitor visitAnnotation(final String desc, return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible)); } + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + checkEnd(); + int sort = typeRef >>> 24; + if (sort != TypeReference.FIELD) { + throw new IllegalArgumentException("Invalid type reference sort 0x" + + Integer.toHexString(sort)); + } + CheckClassAdapter.checkTypeRefAndPath(typeRef, typePath); + CheckMethodAdapter.checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitTypeAnnotation(typeRef, + typePath, desc, visible)); + } + @Override public void visitAttribute(final Attribute attr) { checkEnd(); diff --git a/src/java/nginx/clojure/asm/util/CheckMethodAdapter.java b/src/java/nginx/clojure/asm/util/CheckMethodAdapter.java index 1f6bdc9c..b31b1dbd 100644 --- a/src/java/nginx/clojure/asm/util/CheckMethodAdapter.java +++ b/src/java/nginx/clojure/asm/util/CheckMethodAdapter.java @@ -46,6 +46,8 @@ import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; import nginx.clojure.asm.Type; +import nginx.clojure.asm.TypePath; +import nginx.clojure.asm.TypeReference; import nginx.clojure.asm.tree.MethodNode; import nginx.clojure.asm.tree.analysis.Analyzer; import nginx.clojure.asm.tree.analysis.BasicValue; @@ -390,10 +392,15 @@ public CheckMethodAdapter(final MethodVisitor mv) { * the method visitor to which this adapter must delegate calls. * @param labels * a map of already visited labels (in other methods). + * @throws IllegalStateException + * If a subclass calls this constructor. */ public CheckMethodAdapter(final MethodVisitor mv, final Map labels) { - this(Opcodes.ASM4, mv, labels); + this(Opcodes.ASM5, mv, labels); + if (getClass() != CheckMethodAdapter.class) { + throw new IllegalStateException(); + } } /** @@ -401,6 +408,9 @@ public CheckMethodAdapter(final MethodVisitor mv, * will not perform any data flow check (see * {@link #CheckMethodAdapter(int,String,String,MethodVisitor,Map)}). * + * @param api + * the ASM API version implemented by this CheckMethodAdapter. + * Must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param mv * the method visitor to which this adapter must delegate calls. * @param labels @@ -434,7 +444,7 @@ protected CheckMethodAdapter(final int api, final MethodVisitor mv, public CheckMethodAdapter(final int access, final String name, final String desc, final MethodVisitor cmv, final Map labels) { - this(new MethodNode(access, name, desc, null, null) { + this(new MethodNode(Opcodes.ASM5, access, name, desc, null, null) { @Override public void visitEnd() { Analyzer a = new Analyzer( @@ -461,6 +471,16 @@ public void visitEnd() { this.access = access; } + @Override + public void visitParameter(String name, int access) { + if (name != null) { + checkUnqualifiedName(version, name, "name"); + } + CheckClassAdapter.checkAccess(access, Opcodes.ACC_FINAL + + Opcodes.ACC_MANDATED + Opcodes.ACC_SYNTHETIC); + super.visitParameter(name, access); + } + @Override public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { @@ -469,6 +489,26 @@ public AnnotationVisitor visitAnnotation(final String desc, return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible)); } + @Override + public AnnotationVisitor visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + checkEndMethod(); + int sort = typeRef >>> 24; + if (sort != TypeReference.METHOD_TYPE_PARAMETER + && sort != TypeReference.METHOD_TYPE_PARAMETER_BOUND + && sort != TypeReference.METHOD_RETURN + && sort != TypeReference.METHOD_RECEIVER + && sort != TypeReference.METHOD_FORMAL_PARAMETER + && sort != TypeReference.THROWS) { + throw new IllegalArgumentException("Invalid type reference sort 0x" + + Integer.toHexString(sort)); + } + CheckClassAdapter.checkTypeRefAndPath(typeRef, typePath); + CheckMethodAdapter.checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitTypeAnnotation(typeRef, + typePath, desc, visible)); + } + @Override public AnnotationVisitor visitAnnotationDefault() { checkEndMethod(); @@ -647,9 +687,30 @@ public void visitFieldInsn(final int opcode, final String owner, ++insnCount; } + @Deprecated @Override - public void visitMethodInsn(final int opcode, final String owner, - final String name, final String desc) { + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(int opcode, final String owner, + final String name, final String desc, final boolean itf) { checkStartCode(); checkEndCode(); checkOpcode(opcode, 5); @@ -658,7 +719,27 @@ public void visitMethodInsn(final int opcode, final String owner, } checkInternalName(owner, "owner"); checkMethodDesc(desc); - super.visitMethodInsn(opcode, owner, name, desc); + if (opcode == Opcodes.INVOKEVIRTUAL && itf) { + throw new IllegalArgumentException( + "INVOKEVIRTUAL can't be used with interfaces"); + } + if (opcode == Opcodes.INVOKEINTERFACE && !itf) { + throw new IllegalArgumentException( + "INVOKEINTERFACE can't be used with classes"); + } + if (opcode == Opcodes.INVOKESPECIAL && itf + && (version & 0xFFFF) < Opcodes.V1_8) { + throw new IllegalArgumentException( + "INVOKESPECIAL can't be used with interfaces prior to Java 8"); + } + + // Calling super.visitMethodInsn requires to call the correct version + // depending on this.api (otherwise infinite loops can occur). To + // simplify and to make it easier to automatically remove the backward + // compatibility code, we inline the code of the overridden method here. + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc, itf); + } ++insnCount; } @@ -701,7 +782,7 @@ public void visitLabel(final Label label) { if (labels.get(label) != null) { throw new IllegalArgumentException("Already visited label"); } - labels.put(label, new Integer(insnCount)); + labels.put(label, insnCount); super.visitLabel(label); } @@ -796,6 +877,29 @@ public void visitMultiANewArrayInsn(final String desc, final int dims) { ++insnCount; } + @Override + public AnnotationVisitor visitInsnAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + checkStartCode(); + checkEndCode(); + int sort = typeRef >>> 24; + if (sort != TypeReference.INSTANCEOF && sort != TypeReference.NEW + && sort != TypeReference.CONSTRUCTOR_REFERENCE + && sort != TypeReference.METHOD_REFERENCE + && sort != TypeReference.CAST + && sort != TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT + && sort != TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT + && sort != TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT + && sort != TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT) { + throw new IllegalArgumentException("Invalid type reference sort 0x" + + Integer.toHexString(sort)); + } + CheckClassAdapter.checkTypeRefAndPath(typeRef, typePath); + CheckMethodAdapter.checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitInsnAnnotation(typeRef, + typePath, desc, visible)); + } + @Override public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { @@ -820,6 +924,22 @@ public void visitTryCatchBlock(final Label start, final Label end, handlers.add(end); } + @Override + public AnnotationVisitor visitTryCatchAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + checkStartCode(); + checkEndCode(); + int sort = typeRef >>> 24; + if (sort != TypeReference.EXCEPTION_PARAMETER) { + throw new IllegalArgumentException("Invalid type reference sort 0x" + + Integer.toHexString(sort)); + } + CheckClassAdapter.checkTypeRefAndPath(typeRef, typePath); + CheckMethodAdapter.checkDesc(desc, false); + return new CheckAnnotationAdapter(super.visitTryCatchAnnotation( + typeRef, typePath, desc, visible)); + } + @Override public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, @@ -840,6 +960,40 @@ public void visitLocalVariable(final String name, final String desc, super.visitLocalVariable(name, desc, signature, start, end, index); } + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + checkStartCode(); + checkEndCode(); + int sort = typeRef >>> 24; + if (sort != TypeReference.LOCAL_VARIABLE + && sort != TypeReference.RESOURCE_VARIABLE) { + throw new IllegalArgumentException("Invalid type reference sort 0x" + + Integer.toHexString(sort)); + } + CheckClassAdapter.checkTypeRefAndPath(typeRef, typePath); + checkDesc(desc, false); + if (start == null || end == null || index == null + || end.length != start.length || index.length != start.length) { + throw new IllegalArgumentException( + "Invalid start, end and index arrays (must be non null and of identical length"); + } + for (int i = 0; i < start.length; ++i) { + checkLabel(start[i], true, "start label"); + checkLabel(end[i], true, "end label"); + checkUnsignedShort(index[i], "Invalid variable index"); + int s = labels.get(start[i]).intValue(); + int e = labels.get(end[i]).intValue(); + if (e < s) { + throw new IllegalArgumentException( + "Invalid start and end labels (end must be greater than start)"); + } + } + return super.visitLocalVariableAnnotation(typeRef, typePath, start, + end, index, desc, visible); + } + @Override public void visitLineNumber(final int line, final Label start) { checkStartCode(); @@ -1202,7 +1356,7 @@ static void checkInternalName(final String name, final int start, checkIdentifier(name, begin, slash, null); begin = slash + 1; } while (slash != max); - } catch (IllegalArgumentException _) { + } catch (IllegalArgumentException unused) { throw new IllegalArgumentException( "Invalid " + msg @@ -1280,7 +1434,7 @@ static int checkDesc(final String desc, final int start, } try { checkInternalName(desc, start + 1, index, null); - } catch (IllegalArgumentException _) { + } catch (IllegalArgumentException unused) { throw new IllegalArgumentException("Invalid descriptor: " + desc); } diff --git a/src/java/nginx/clojure/asm/util/CheckSignatureAdapter.java b/src/java/nginx/clojure/asm/util/CheckSignatureAdapter.java index 241a926a..980e0a9f 100644 --- a/src/java/nginx/clojure/asm/util/CheckSignatureAdapter.java +++ b/src/java/nginx/clojure/asm/util/CheckSignatureAdapter.java @@ -113,7 +113,7 @@ public class CheckSignatureAdapter extends SignatureVisitor { * null. */ public CheckSignatureAdapter(final int type, final SignatureVisitor sv) { - this(Opcodes.ASM4, type, sv); + this(Opcodes.ASM5, type, sv); } /** @@ -121,7 +121,7 @@ public CheckSignatureAdapter(final int type, final SignatureVisitor sv) { * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. * @param type * the type of signature to be checked. See * {@link #CLASS_SIGNATURE}, {@link #METHOD_SIGNATURE} and diff --git a/src/java/nginx/clojure/asm/util/Printer.java b/src/java/nginx/clojure/asm/util/Printer.java index 38202014..c16f9142 100644 --- a/src/java/nginx/clojure/asm/util/Printer.java +++ b/src/java/nginx/clojure/asm/util/Printer.java @@ -37,6 +37,7 @@ import nginx.clojure.asm.Handle; import nginx.clojure.asm.Label; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * An abstract converter from visit events to text. @@ -116,7 +117,7 @@ public abstract class Printer { /** * The ASM API version implemented by this class. The value of this field - * must be one of {@link Opcodes#ASM4}. + * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ protected final int api; @@ -141,6 +142,10 @@ public abstract class Printer { /** * Constructs a new {@link Printer}. + * + * @param api + * the ASM API version implemented by this printer. Must be one + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ protected Printer(final int api) { this.api = api; @@ -149,52 +154,189 @@ protected Printer(final int api) { } /** - * Class header. See {@link nginx.clojure.asm.ClassVisitor#visit}. + * Class header. + * See {@link nginx.clojure.asm.ClassVisitor#visit}. + * + * @param version + * the class version. + * @param access + * the class's access flags (see {@link Opcodes}). This parameter + * also indicates if the class is deprecated. + * @param name + * the internal name of the class (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). + * @param signature + * the signature of this class. May be null if the class + * is not a generic one, and does not extend or implement generic + * classes or interfaces. + * @param superName + * the internal of name of the super class (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). + * For interfaces, the super class is {@link Object}. May be + * null, but only for the {@link Object} class. + * @param interfaces + * the internal names of the class's interfaces (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). + * May be null. */ public abstract void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces); /** - * Class source. See {@link nginx.clojure.asm.ClassVisitor#visitSource}. + * Class source. + * See {@link nginx.clojure.asm.ClassVisitor#visitSource}. + * + * @param source + * the name of the source file from which the class was compiled. + * May be null. + * @param debug + * additional debug information to compute the correspondance + * between source and compiled elements of the class. May be + * null. */ - public abstract void visitSource(final String file, final String debug); + public abstract void visitSource(final String source, final String debug); /** - * Class outer class. See - * {@link nginx.clojure.asm.ClassVisitor#visitOuterClass}. + * Class outer class. + * See {@link nginx.clojure.asm.ClassVisitor#visitOuterClass}. + * + * Visits the enclosing class of the class. This method must be called only + * if the class has an enclosing class. + * + * @param owner + * internal name of the enclosing class of the class. + * @param name + * the name of the method that contains the class, or + * null if the class is not enclosed in a method of its + * enclosing class. + * @param desc + * the descriptor of the method that contains the class, or + * null if the class is not enclosed in a method of its + * enclosing class. */ public abstract void visitOuterClass(final String owner, final String name, final String desc); /** - * Class annotation. See - * {@link nginx.clojure.asm.ClassVisitor#visitAnnotation}. + * Class annotation. + * See {@link nginx.clojure.asm.ClassVisitor#visitAnnotation}. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer */ public abstract Printer visitClassAnnotation(final String desc, final boolean visible); /** - * Class attribute. See - * {@link nginx.clojure.asm.ClassVisitor#visitAttribute}. + * Class type annotation. + * See {@link nginx.clojure.asm.ClassVisitor#visitTypeAnnotation}. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be + * {@link nginx.clojure.asm.TypeReference#CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER}, + * {@link nginx.clojure.asm.TypeReference#CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} + * or {@link nginx.clojure.asm.TypeReference#CLASS_EXTENDS CLASS_EXTENDS}. + * 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer + */ + public Printer visitClassTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + throw new RuntimeException("Must be overriden"); + } + + /** + * Class attribute. + * See {@link nginx.clojure.asm.ClassVisitor#visitAttribute}. + * + * @param attr + * an attribute. */ public abstract void visitClassAttribute(final Attribute attr); /** - * Class inner name. See - * {@link nginx.clojure.asm.ClassVisitor#visitInnerClass}. + * Class inner name. + * See {@link nginx.clojure.asm.ClassVisitor#visitInnerClass}. + * + * @param name + * the internal name of an inner class (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). + * @param outerName + * the internal name of the class to which the inner class + * belongs (see {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). + * May be null for not member classes. + * @param innerName + * the (simple) name of the inner class inside its enclosing + * class. May be null for anonymous inner classes. + * @param access + * the access flags of the inner class as originally declared in + * the enclosing class. */ public abstract void visitInnerClass(final String name, final String outerName, final String innerName, final int access); /** - * Class field. See {@link nginx.clojure.asm.ClassVisitor#visitField}. + * Class field. + * See {@link nginx.clojure.asm.ClassVisitor#visitField}. + * + * @param access + * the field's access flags (see {@link Opcodes}). This parameter + * also indicates if the field is synthetic and/or deprecated. + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link nginx.clojure.asm.Type Type}). + * @param signature + * the field's signature. May be null if the field's + * type does not use generic types. + * @param value + * the field's initial value. This parameter, which may be + * null if the field does not have an initial value, + * must be an {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double} or a {@link String} (for int, + * float, long or String fields + * respectively). This parameter is only used for static + * fields. Its value is ignored for non static fields, which + * must be initialized through bytecode instructions in + * constructors or methods. + * @return the printer */ public abstract Printer visitField(final int access, final String name, final String desc, final String signature, final Object value); /** - * Class method. See {@link nginx.clojure.asm.ClassVisitor#visitMethod}. + * Class method. + * See {@link nginx.clojure.asm.ClassVisitor#visitMethod}. + * + * @param access + * the method's access flags (see {@link Opcodes}). This + * parameter also indicates if the method is synthetic and/or + * deprecated. + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link nginx.clojure.asm.Type Type}). + * @param signature + * the method's signature. May be null if the method + * parameters, return type and exceptions do not use generic + * types. + * @param exceptions + * the internal names of the method's exception classes (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). May be + * null. + * @return the printer */ public abstract Printer visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions); @@ -209,26 +351,64 @@ public abstract Printer visitMethod(final int access, final String name, // ------------------------------------------------------------------------ /** - * Annotation value. See {@link nginx.clojure.asm.AnnotationVisitor#visit}. + * Annotation value. + * See {@link nginx.clojure.asm.AnnotationVisitor#visit}. + * + * @param name + * the value name. + * @param value + * the actual value, whose type must be {@link Byte}, + * {@link Boolean}, {@link Character}, {@link Short}, + * {@link Integer} , {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link nginx.clojure.asm.Type} + * or OBJECT or ARRAY sort. + * This value can also be an array of byte, boolean, short, char, int, + * long, float or double values (this is equivalent to using + * {@link #visitArray visitArray} and visiting each array element + * in turn, but is more convenient). */ public abstract void visit(final String name, final Object value); /** - * Annotation enum value. See - * {@link nginx.clojure.asm.AnnotationVisitor#visitEnum}. + * Annotation enum value. + * See {@link nginx.clojure.asm.AnnotationVisitor#visitEnum}. + * + * Visits an enumeration value of the annotation. + * + * @param name + * the value name. + * @param desc + * the class descriptor of the enumeration class. + * @param value + * the actual enumeration value. */ public abstract void visitEnum(final String name, final String desc, final String value); /** - * Nested annotation value. See - * {@link nginx.clojure.asm.AnnotationVisitor#visitAnnotation}. + * Nested annotation value. + * See {@link nginx.clojure.asm.AnnotationVisitor#visitAnnotation}. + * + * @param name + * the value name. + * @param desc + * the class descriptor of the nested annotation class. + * @return the printer */ public abstract Printer visitAnnotation(final String name, final String desc); /** - * Annotation array value. See - * {@link nginx.clojure.asm.AnnotationVisitor#visitArray}. + * Annotation array value. + * See {@link nginx.clojure.asm.AnnotationVisitor#visitArray}. + * + * Visits an array value of the annotation. Note that arrays of primitive + * types (such as byte, boolean, short, char, int, long, float or double) + * can be passed as value to {@link #visit visit}. This is what + * {@link nginx.clojure.asm.ClassReader} does. + * + * @param name + * the value name. + * @return the printer */ public abstract Printer visitArray(final String name); @@ -242,20 +422,53 @@ public abstract void visitEnum(final String name, final String desc, // ------------------------------------------------------------------------ /** - * Field annotation. See - * {@link nginx.clojure.asm.FieldVisitor#visitAnnotation}. + * Field annotation. + * See {@link nginx.clojure.asm.FieldVisitor#visitAnnotation}. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer */ public abstract Printer visitFieldAnnotation(final String desc, final boolean visible); /** - * Field attribute. See - * {@link nginx.clojure.asm.FieldVisitor#visitAttribute}. + * Field type annotation. + * See {@link nginx.clojure.asm.FieldVisitor#visitTypeAnnotation}. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link nginx.clojure.asm.TypeReference#FIELD FIELD}. + * 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer + */ + public Printer visitFieldTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + throw new RuntimeException("Must be overriden"); + } + + /** + * Field attribute. + * See {@link nginx.clojure.asm.FieldVisitor#visitAttribute}. + * + * @param attr + * an attribute. */ public abstract void visitFieldAttribute(final Attribute attr); /** - * Field end. See {@link nginx.clojure.asm.FieldVisitor#visitEnd}. + * Field end. + * See {@link nginx.clojure.asm.FieldVisitor#visitEnd}. */ public abstract void visitFieldEnd(); @@ -264,161 +477,647 @@ public abstract Printer visitFieldAnnotation(final String desc, // ------------------------------------------------------------------------ /** - * Method default annotation. See - * {@link nginx.clojure.asm.MethodVisitor#visitAnnotationDefault}. + * Method parameter. + * See {@link nginx.clojure.asm.MethodVisitor#visitParameter(String, int)}. + * + * @param name + * parameter name or null if none is provided. + * @param access + * the parameter's access flags, only ACC_FINAL, + * ACC_SYNTHETIC or/and ACC_MANDATED are + * allowed (see {@link Opcodes}). + */ + public void visitParameter(String name, int access) { + throw new RuntimeException("Must be overriden"); + } + + /** + * Method default annotation. + * See {@link nginx.clojure.asm.MethodVisitor#visitAnnotationDefault}. + * + * @return the printer */ public abstract Printer visitAnnotationDefault(); /** - * Method annotation. See - * {@link nginx.clojure.asm.MethodVisitor#visitAnnotation}. + * Method annotation. + * See {@link nginx.clojure.asm.MethodVisitor#visitAnnotation}. + * + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer */ public abstract Printer visitMethodAnnotation(final String desc, final boolean visible); /** - * Method parameter annotation. See - * {@link nginx.clojure.asm.MethodVisitor#visitParameterAnnotation}. + * Method type annotation. + * See {@link nginx.clojure.asm.MethodVisitor#visitTypeAnnotation}. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link nginx.clojure.asm.TypeReference#FIELD FIELD}. + * 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer + */ + public Printer visitMethodTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + throw new RuntimeException("Must be overriden"); + } + + /** + * Method parameter annotation. + * See {@link nginx.clojure.asm.MethodVisitor#visitParameterAnnotation}. + * + * @param parameter + * the parameter index. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer */ public abstract Printer visitParameterAnnotation(final int parameter, final String desc, final boolean visible); /** - * Method attribute. See - * {@link nginx.clojure.asm.MethodVisitor#visitAttribute}. + * Method attribute. + * See {@link nginx.clojure.asm.MethodVisitor#visitAttribute}. + * + * @param attr + * an attribute. */ public abstract void visitMethodAttribute(final Attribute attr); /** - * Method start. See {@link nginx.clojure.asm.MethodVisitor#visitCode}. + * Method start. + * See {@link nginx.clojure.asm.MethodVisitor#visitCode}. */ public abstract void visitCode(); /** - * Method stack frame. See - * {@link nginx.clojure.asm.MethodVisitor#visitFrame}. + * Method stack frame. + * See {@link nginx.clojure.asm.MethodVisitor#visitFrame}. + * + * Visits the current state of the local variables and operand stack + * elements. This method must(*) be called just before any + * instruction i that follows an unconditional branch instruction + * such as GOTO or THROW, that is the target of a jump instruction, or that + * starts an exception handler block. The visited types must describe the + * values of the local variables and of the operand stack elements just + * before i is executed.
      + *
      + * (*) this is mandatory only for classes whose version is greater than or + * equal to {@link Opcodes#V1_6 V1_6}.
      + *
      + * The frames of a method must be given either in expanded form, or in + * compressed form (all frames must use the same format, i.e. you must not + * mix expanded and compressed frames within a single method): + *
        + *
      • In expanded form, all frames must have the F_NEW type.
      • + *
      • In compressed form, frames are basically "deltas" from the state of + * the previous frame: + *
          + *
        • {@link Opcodes#F_SAME} representing frame with exactly the same + * locals as the previous frame and with the empty stack.
        • + *
        • {@link Opcodes#F_SAME1} representing frame with exactly the same + * locals as the previous frame and with single value on the stack ( + * nStack is 1 and stack[0] contains value for the + * type of the stack item).
        • + *
        • {@link Opcodes#F_APPEND} representing frame with current locals are + * the same as the locals in the previous frame, except that additional + * locals are defined (nLocal is 1, 2 or 3 and + * local elements contains values representing added types).
        • + *
        • {@link Opcodes#F_CHOP} representing frame with current locals are the + * same as the locals in the previous frame, except that the last 1-3 locals + * are absent and with the empty stack (nLocals is 1, 2 or 3).
        • + *
        • {@link Opcodes#F_FULL} representing complete frame data.
        • + *
        + *
      • + *
      + *
      + * In both cases the first frame, corresponding to the method's parameters + * and access flags, is implicit and must not be visited. Also, it is + * illegal to visit two or more frames for the same code location (i.e., at + * least one instruction must be visited between two calls to visitFrame). + * + * @param type + * the type of this stack map frame. Must be + * {@link Opcodes#F_NEW} for expanded frames, or + * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, + * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or + * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for + * compressed frames. + * @param nLocal + * the number of local variables in the visited frame. + * @param local + * the local variable types in this frame. This array must not be + * modified. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, + * {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are + * represented by a single element). Reference types are + * represented by String objects (representing internal names), + * and uninitialized types by Label objects (this label + * designates the NEW instruction that created this uninitialized + * value). + * @param nStack + * the number of operand stack elements in the visited frame. + * @param stack + * the operand stack types in this frame. This array must not be + * modified. Its content has the same format as the "local" + * array. + * @throws IllegalStateException + * if a frame is visited just after another one, without any + * instruction between the two (unless this frame is a + * Opcodes#F_SAME frame, in which case it is silently ignored). */ public abstract void visitFrame(final int type, final int nLocal, final Object[] local, final int nStack, final Object[] stack); /** - * Method instruction. See {@link nginx.clojure.asm.MethodVisitor#visitInsn} - * . + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitInsn} + * + * @param opcode + * the opcode of the instruction to be visited. This opcode is + * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, + * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, + * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, + * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, + * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, + * SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, + * DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, + * IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, + * FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, + * IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, + * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, + * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, + * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, + * or MONITOREXIT. */ public abstract void visitInsn(final int opcode); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitIntInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitIntInsn}. + * + * @param opcode + * the opcode of the instruction to be visited. This opcode is + * either BIPUSH, SIPUSH or NEWARRAY. + * @param operand + * the operand of the instruction to be visited.
      + * When opcode is BIPUSH, operand value should be between + * Byte.MIN_VALUE and Byte.MAX_VALUE.
      + * When opcode is SIPUSH, operand value should be between + * Short.MIN_VALUE and Short.MAX_VALUE.
      + * When opcode is NEWARRAY, operand value should be one of + * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR}, + * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, + * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT}, + * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. */ public abstract void visitIntInsn(final int opcode, final int operand); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitVarInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitVarInsn}. + * + * @param opcode + * the opcode of the local variable instruction to be visited. + * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, + * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param var + * the operand of the instruction to be visited. This operand is + * the index of a local variable. */ public abstract void visitVarInsn(final int opcode, final int var); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitTypeInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitTypeInsn}. + * + /** + * Visits a type instruction. A type instruction is an instruction that + * takes the internal name of a class as parameter. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type + * the operand of the instruction to be visited. This operand + * must be the internal name of an object or array class (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). */ public abstract void visitTypeInsn(final int opcode, final String type); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitFieldInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitFieldInsn}. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner + * the internal name of the field's owner class (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). + * @param name + * the field's name. + * @param desc + * the field's descriptor (see {@link nginx.clojure.asm.Type Type}). */ public abstract void visitFieldInsn(final int opcode, final String owner, final String name, final String desc); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitMethodInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitMethodInsn}. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link nginx.clojure.asm.Type Type}). */ - public abstract void visitMethodInsn(final int opcode, final String owner, - final String name, final String desc); + @Deprecated + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc) { + if (api >= Opcodes.ASM5) { + boolean itf = opcode == Opcodes.INVOKEINTERFACE; + visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + throw new RuntimeException("Must be overriden"); + } + + /** + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitMethodInsn}. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or + * INVOKEINTERFACE. + * @param owner + * the internal name of the method's owner class (see + * {@link nginx.clojure.asm.Type#getInternalName() getInternalName}). + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link nginx.clojure.asm.Type Type}). + * @param itf + * if the method's owner class is an interface. + */ + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + if (itf != (opcode == Opcodes.INVOKEINTERFACE)) { + throw new IllegalArgumentException( + "INVOKESPECIAL/STATIC on interfaces require ASM 5"); + } + visitMethodInsn(opcode, owner, name, desc); + return; + } + throw new RuntimeException("Must be overriden"); + } /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitInvokeDynamicInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitInvokeDynamicInsn}. + * + * Visits an invokedynamic instruction. + * + * @param name + * the method's name. + * @param desc + * the method's descriptor (see {@link nginx.clojure.asm.Type Type}). + * @param bsm + * the bootstrap method. + * @param bsmArgs + * the bootstrap method constant arguments. Each argument must be + * an {@link Integer}, {@link Float}, {@link Long}, + * {@link Double}, {@link String}, {@link nginx.clojure.asm.Type} or {@link Handle} + * value. This method is allowed to modify the content of the + * array so a caller should expect that this array may change. */ public abstract void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitJumpInsn}. + * Method jump instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitJumpInsn}. + * + * @param opcode + * the opcode of the type instruction to be visited. This opcode + * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, + * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, + * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label + * the operand of the instruction to be visited. This operand is + * a label that designates the instruction to which the jump + * instruction may jump. */ public abstract void visitJumpInsn(final int opcode, final Label label); /** - * Method label. See {@link nginx.clojure.asm.MethodVisitor#visitLabel}. + * Method label. + * See {@link nginx.clojure.asm.MethodVisitor#visitLabel}. + * + * @param label + * a {@link Label Label} object. */ public abstract void visitLabel(final Label label); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitLdcInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitLdcInsn}. + * + * Visits a LDC instruction. Note that new constant types may be added in + * future versions of the Java Virtual Machine. To easily detect new + * constant types, implementations of this method should check for + * unexpected constant types, like this: + * + *
      +     * if (cst instanceof Integer) {
      +     *     // ...
      +     * } else if (cst instanceof Float) {
      +     *     // ...
      +     * } else if (cst instanceof Long) {
      +     *     // ...
      +     * } else if (cst instanceof Double) {
      +     *     // ...
      +     * } else if (cst instanceof String) {
      +     *     // ...
      +     * } else if (cst instanceof Type) {
      +     *     int sort = ((Type) cst).getSort();
      +     *     if (sort == Type.OBJECT) {
      +     *         // ...
      +     *     } else if (sort == Type.ARRAY) {
      +     *         // ...
      +     *     } else if (sort == Type.METHOD) {
      +     *         // ...
      +     *     } else {
      +     *         // throw an exception
      +     *     }
      +     * } else if (cst instanceof Handle) {
      +     *     // ...
      +     * } else {
      +     *     // throw an exception
      +     * }
      +     * 
      + * + * @param cst + * the constant to be loaded on the stack. This parameter must be + * a non null {@link Integer}, a {@link Float}, a {@link Long}, a + * {@link Double}, a {@link String}, a {@link nginx.clojure.asm.Type} + * of OBJECT or ARRAY sort for .class constants, for classes whose + * version is 49.0, a {@link nginx.clojure.asm.Type} of METHOD sort or a + * {@link Handle} for MethodType and MethodHandle constants, for + * classes whose version is 51.0. */ public abstract void visitLdcInsn(final Object cst); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitIincInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitIincInsn}. + * + * @param var + * index of the local variable to be incremented. + * @param increment + * amount to increment the local variable by. */ public abstract void visitIincInsn(final int var, final int increment); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitTableSwitchInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitTableSwitchInsn}. + * + * @param min + * the minimum key value. + * @param max + * the maximum key value. + * @param dflt + * beginning of the default handler block. + * @param labels + * beginnings of the handler blocks. labels[i] is the + * beginning of the handler block for the min + i key. */ public abstract void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitLookupSwitchInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitLookupSwitchInsn}. + * + * @param dflt + * beginning of the default handler block. + * @param keys + * the values of the keys. + * @param labels + * beginnings of the handler blocks. labels[i] is the + * beginning of the handler block for the keys[i] key. */ public abstract void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels); /** - * Method instruction. See - * {@link nginx.clojure.asm.MethodVisitor#visitMultiANewArrayInsn}. + * Method instruction. + * See {@link nginx.clojure.asm.MethodVisitor#visitMultiANewArrayInsn}. + * + * @param desc + * an array type descriptor (see {@link nginx.clojure.asm.Type Type}). + * @param dims + * number of dimensions of the array to allocate. */ public abstract void visitMultiANewArrayInsn(final String desc, final int dims); /** - * Method exception handler. See - * {@link nginx.clojure.asm.MethodVisitor#visitTryCatchBlock}. + * Instruction type annotation. + * See {@link nginx.clojure.asm.MethodVisitor#visitInsnAnnotation}. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link nginx.clojure.asm.TypeReference#INSTANCEOF INSTANCEOF}, + * {@link nginx.clojure.asm.TypeReference#NEW NEW}, + * {@link nginx.clojure.asm.TypeReference#CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, + * {@link nginx.clojure.asm.TypeReference#METHOD_REFERENCE METHOD_REFERENCE}, + * {@link nginx.clojure.asm.TypeReference#CAST CAST}, + * {@link nginx.clojure.asm.TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, + * {@link nginx.clojure.asm.TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT METHOD_INVOCATION_TYPE_ARGUMENT}, + * {@link nginx.clojure.asm.TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, + * or {@link nginx.clojure.asm.TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT METHOD_REFERENCE_TYPE_ARGUMENT}. + * 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer + */ + public Printer visitInsnAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + throw new RuntimeException("Must be overriden"); + } + + /** + * Method exception handler. + * See {@link nginx.clojure.asm.MethodVisitor#visitTryCatchBlock}. + * + * @param start + * beginning of the exception handler's scope (inclusive). + * @param end + * end of the exception handler's scope (exclusive). + * @param handler + * beginning of the exception handler's code. + * @param type + * internal name of the type of exceptions handled by the + * handler, or null to catch any exceptions (for + * "finally" blocks). + * @throws IllegalArgumentException + * if one of the labels has already been visited by this visitor + * (by the {@link #visitLabel visitLabel} method). */ public abstract void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type); /** - * Method debug info. See - * {@link nginx.clojure.asm.MethodVisitor#visitLocalVariable}. + * Try catch block type annotation. + * See {@link nginx.clojure.asm.MethodVisitor#visitTryCatchAnnotation}. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link nginx.clojure.asm.TypeReference#EXCEPTION_PARAMETER + * EXCEPTION_PARAMETER}. + * 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer + */ + public Printer visitTryCatchAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + throw new RuntimeException("Must be overriden"); + } + + /** + * Method debug info. + * See {@link nginx.clojure.asm.MethodVisitor#visitLocalVariable}. + * + * @param name + * the name of a local variable. + * @param desc + * the type descriptor of this local variable. + * @param signature + * the type signature of this local variable. May be + * null if the local variable type does not use generic + * types. + * @param start + * the first instruction corresponding to the scope of this local + * variable (inclusive). + * @param end + * the last instruction corresponding to the scope of this local + * variable (exclusive). + * @param index + * the local variable's index. + * @throws IllegalArgumentException + * if one of the labels has not already been visited by this + * visitor (by the {@link #visitLabel visitLabel} method). */ public abstract void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index); /** - * Method debug info. See - * {@link nginx.clojure.asm.MethodVisitor#visitLineNumber}. + * Local variable type annotation. + * See {@link nginx.clojure.asm.MethodVisitor#visitTryCatchAnnotation}. + * + * @param typeRef + * a reference to the annotated type. The sort of this type + * reference must be {@link nginx.clojure.asm.TypeReference#LOCAL_VARIABLE + * LOCAL_VARIABLE} or {@link nginx.clojure.asm.TypeReference#RESOURCE_VARIABLE + * RESOURCE_VARIABLE}. + * 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 + * null if the annotation targets 'typeRef' as a whole. + * @param start + * the fist instructions corresponding to the continuous ranges + * that make the scope of this local variable (inclusive). + * @param end + * the last instructions corresponding to the continuous ranges + * that make the scope of this local variable (exclusive). This + * array must have the same size as the 'start' array. + * @param index + * the local variable's index in each range. This array must have + * the same size as the 'start' array. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return the printer + */ + public Printer visitLocalVariableAnnotation(final int typeRef, + final TypePath typePath, final Label[] start, final Label[] end, + final int[] index, final String desc, final boolean visible) { + throw new RuntimeException("Must be overriden"); + } + + /** + * Method debug info. + * See {@link nginx.clojure.asm.MethodVisitor#visitLineNumber}. + * + * @param line + * a line number. This number refers to the source file from + * which the class was compiled. + * @param start + * the first instruction corresponding to this line number. + * @throws IllegalArgumentException + * if start has not already been visited by this + * visitor (by the {@link #visitLabel visitLabel} method). */ public abstract void visitLineNumber(final int line, final Label start); /** - * Method max stack and max locals. See - * {@link nginx.clojure.asm.MethodVisitor#visitMaxs}. + * Method max stack and max locals. + * See {@link nginx.clojure.asm.MethodVisitor#visitMaxs}. + * + * @param maxStack + * maximum stack size of the method. + * @param maxLocals + * maximum number of local variables for the method. */ public abstract void visitMaxs(final int maxStack, final int maxLocals); /** - * Method end. See {@link nginx.clojure.asm.MethodVisitor#visitEnd}. + * Method end. + * See {@link nginx.clojure.asm.MethodVisitor#visitEnd}. */ public abstract void visitMethodEnd(); @@ -497,4 +1196,4 @@ static void printList(final PrintWriter pw, final List l) { } } } -} +} \ No newline at end of file diff --git a/src/java/nginx/clojure/asm/util/Textifier.java b/src/java/nginx/clojure/asm/util/Textifier.java index 44743a99..0e6be8d5 100644 --- a/src/java/nginx/clojure/asm/util/Textifier.java +++ b/src/java/nginx/clojure/asm/util/Textifier.java @@ -40,11 +40,13 @@ import nginx.clojure.asm.Label; import nginx.clojure.asm.Opcodes; import nginx.clojure.asm.Type; +import nginx.clojure.asm.TypePath; +import nginx.clojure.asm.TypeReference; import nginx.clojure.asm.signature.SignatureReader; /** * A {@link Printer} that prints a disassembled view of the classes it visits. - * + * * @author Eric Bruneton */ public class Textifier extends Printer { @@ -135,23 +137,34 @@ public class Textifier extends Printer { */ protected Map labelNames; + /** + * Class access flags + */ + private int access; + private int valueNumber = 0; /** * Constructs a new {@link Textifier}. Subclasses must not use this * constructor. Instead, they must use the {@link #Textifier(int)} * version. + * + * @throws IllegalStateException + * If a subclass calls this constructor. */ public Textifier() { - this(Opcodes.ASM4); + this(Opcodes.ASM5); + if (getClass() != Textifier.class) { + throw new IllegalStateException(); + } } /** * Constructs a new {@link Textifier}. - * + * * @param api * the ASM API version implemented by this visitor. Must be one - * of {@link Opcodes#ASM4}. + * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}. */ protected Textifier(final int api) { super(api); @@ -161,10 +174,10 @@ protected Textifier(final int api) { * Prints a disassembled view of the given class to the standard output. *

      * Usage: Textifier [-debug] <binary class name or class file name > - * + * * @param args * the command line arguments. - * + * * @throws Exception * if the class cannot be found, or if an IO exception occurs. */ @@ -208,6 +221,7 @@ public static void main(final String[] args) throws Exception { public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { + this.access = access; int major = version & 0xFFFF; int minor = version >>> 16; buf.setLength(0); @@ -293,6 +307,13 @@ public Textifier visitClassAnnotation(final String desc, return visitAnnotation(desc, visible); } + @Override + public Printer visitClassTypeAnnotation(int typeRef, TypePath typePath, + String desc, boolean visible) { + text.add("\n"); + return visitTypeAnnotation(typeRef, typePath, desc, visible); + } + @Override public void visitClassAttribute(final Attribute attr) { text.add("\n"); @@ -393,7 +414,7 @@ public Textifier visitMethod(final int access, final String name, } buf.append(tab); - appendAccess(access); + appendAccess(access & ~Opcodes.ACC_VOLATILE); if ((access & Opcodes.ACC_NATIVE) != 0) { buf.append("native "); } @@ -403,6 +424,11 @@ public Textifier visitMethod(final int access, final String name, if ((access & Opcodes.ACC_BRIDGE) != 0) { buf.append("bridge "); } + if ((this.access & Opcodes.ACC_INTERFACE) != 0 + && (access & Opcodes.ACC_ABSTRACT) == 0 + && (access & Opcodes.ACC_STATIC) == 0) { + buf.append("default "); + } buf.append(name); appendDescriptor(METHOD_DESCRIPTOR, desc); @@ -616,6 +642,12 @@ public Textifier visitFieldAnnotation(final String desc, return visitAnnotation(desc, visible); } + @Override + public Printer visitFieldTypeAnnotation(int typeRef, TypePath typePath, + String desc, boolean visible) { + return visitTypeAnnotation(typeRef, typePath, desc, visible); + } + @Override public void visitFieldAttribute(final Attribute attr) { visitAttribute(attr); @@ -629,6 +661,16 @@ public void visitFieldEnd() { // Methods // ------------------------------------------------------------------------ + @Override + public void visitParameter(final String name, final int access) { + buf.setLength(0); + buf.append(tab2).append("// parameter "); + appendAccess(access); + buf.append(' ').append((name == null) ? "" : name) + .append('\n'); + text.add(buf.toString()); + } + @Override public Textifier visitAnnotationDefault() { text.add(tab2 + "default="); @@ -644,6 +686,12 @@ public Textifier visitMethodAnnotation(final String desc, return visitAnnotation(desc, visible); } + @Override + public Printer visitMethodTypeAnnotation(int typeRef, TypePath typePath, + String desc, boolean visible) { + return visitTypeAnnotation(typeRef, typePath, desc, visible); + } + @Override public Textifier visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { @@ -655,7 +703,7 @@ public Textifier visitParameterAnnotation(final int parameter, Textifier t = createTextifier(); text.add(t.getText()); text.add(visible ? ") // parameter " : ") // invisible, parameter "); - text.add(new Integer(parameter)); + text.add(parameter); text.add("\n"); return t; } @@ -761,9 +809,30 @@ public void visitFieldInsn(final int opcode, final String owner, text.add(buf.toString()); } + @Deprecated @Override public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, + opcode == Opcodes.INVOKEINTERFACE); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + doVisitMethodInsn(opcode, owner, name, desc, itf); + } + + private void doVisitMethodInsn(final int opcode, final String owner, + final String name, final String desc, final boolean itf) { buf.setLength(0); buf.append(tab2).append(OPCODES[opcode]).append(' '); appendDescriptor(INTERNAL_NAME, owner); @@ -781,26 +850,35 @@ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, buf.append(name); appendDescriptor(METHOD_DESCRIPTOR, desc); buf.append(" ["); + buf.append('\n'); + buf.append(tab3); appendHandle(bsm); + buf.append('\n'); buf.append(tab3).append("// arguments:"); if (bsmArgs.length == 0) { buf.append(" none"); } else { - buf.append('\n').append(tab3); + buf.append('\n'); for (int i = 0; i < bsmArgs.length; i++) { + buf.append(tab3); Object cst = bsmArgs[i]; if (cst instanceof String) { Printer.appendString(buf, (String) cst); } else if (cst instanceof Type) { - buf.append(((Type) cst).getDescriptor()).append(".class"); + Type type = (Type) cst; + if(type.getSort() == Type.METHOD){ + appendDescriptor(METHOD_DESCRIPTOR, type.getDescriptor()); + } else { + buf.append(type.getDescriptor()).append(".class"); + } } else if (cst instanceof Handle) { appendHandle((Handle) cst); } else { buf.append(cst); } - buf.append(", "); + buf.append(", \n"); } - buf.setLength(buf.length() - 2); + buf.setLength(buf.length() - 3); } buf.append('\n'); buf.append(tab2).append("]\n"); @@ -889,6 +967,12 @@ public void visitMultiANewArrayInsn(final String desc, final int dims) { text.add(buf.toString()); } + @Override + public Printer visitInsnAnnotation(int typeRef, TypePath typePath, + String desc, boolean visible) { + return visitTypeAnnotation(typeRef, typePath, desc, visible); + } + @Override public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { @@ -905,6 +989,25 @@ public void visitTryCatchBlock(final Label start, final Label end, text.add(buf.toString()); } + @Override + public Printer visitTryCatchAnnotation(int typeRef, TypePath typePath, + String desc, boolean visible) { + buf.setLength(0); + buf.append(tab2).append("TRYCATCHBLOCK @"); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append('('); + text.add(buf.toString()); + Textifier t = createTextifier(); + text.add(t.getText()); + buf.setLength(0); + buf.append(") : "); + appendTypeReference(typeRef); + buf.append(", ").append(typePath); + buf.append(visible ? "\n" : " // invisible\n"); + text.add(buf.toString()); + return t; + } + @Override public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, @@ -931,6 +1034,33 @@ public void visitLocalVariable(final String name, final String desc, text.add(buf.toString()); } + @Override + public Printer visitLocalVariableAnnotation(int typeRef, TypePath typePath, + Label[] start, Label[] end, int[] index, String desc, + boolean visible) { + buf.setLength(0); + buf.append(tab2).append("LOCALVARIABLE @"); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append('('); + text.add(buf.toString()); + Textifier t = createTextifier(); + text.add(t.getText()); + buf.setLength(0); + buf.append(") : "); + appendTypeReference(typeRef); + buf.append(", ").append(typePath); + for (int i = 0; i < start.length; ++i) { + buf.append(" [ "); + appendLabel(start[i]); + buf.append(" - "); + appendLabel(end[i]); + buf.append(" - ").append(index[i]).append(" ]"); + } + buf.append(visible ? "\n" : " // invisible\n"); + text.add(buf.toString()); + return t; + } + @Override public void visitLineNumber(final int line, final Label start) { buf.setLength(0); @@ -961,7 +1091,7 @@ public void visitMethodEnd() { /** * Prints a disassembled view of the given annotation. - * + * * @param desc * the class descriptor of the annotation class. * @param visible @@ -980,9 +1110,42 @@ public Textifier visitAnnotation(final String desc, final boolean visible) { return t; } + /** + * Prints a disassembled view of the given type annotation. + * + * @param typeRef + * a reference to the annotated type. 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 + * null if the annotation targets 'typeRef' as a whole. + * @param desc + * the class descriptor of the annotation class. + * @param visible + * true if the annotation is visible at runtime. + * @return a visitor to visit the annotation values. + */ + public Textifier visitTypeAnnotation(final int typeRef, + final TypePath typePath, final String desc, final boolean visible) { + buf.setLength(0); + buf.append(tab).append('@'); + appendDescriptor(FIELD_DESCRIPTOR, desc); + buf.append('('); + text.add(buf.toString()); + Textifier t = createTextifier(); + text.add(t.getText()); + buf.setLength(0); + buf.append(") : "); + appendTypeReference(typeRef); + buf.append(", ").append(typePath); + buf.append(visible ? "\n" : " // invisible\n"); + text.add(buf.toString()); + return t; + } + /** * Prints a disassembled view of the given attribute. - * + * * @param attr * an attribute. */ @@ -1006,7 +1169,7 @@ public void visitAttribute(final Attribute attr) { /** * Creates a new TraceVisitor instance. - * + * * @return a new TraceVisitor. */ protected Textifier createTextifier() { @@ -1016,7 +1179,7 @@ protected Textifier createTextifier() { /** * Appends an internal name, a type descriptor or a type signature to * {@link #buf buf}. - * + * * @param type * indicates if desc is an internal name, a field descriptor, a * method descriptor, a class signature, ... @@ -1038,7 +1201,7 @@ protected void appendDescriptor(final int type, final String desc) { /** * Appends the name of the given label to {@link #buf buf}. Creates a new * label name if the given label does not yet have one. - * + * * @param l * a label. */ @@ -1056,15 +1219,15 @@ protected void appendLabel(final Label l) { /** * Appends the information about the given handle to {@link #buf buf}. - * + * * @param h * a handle, non null. */ protected void appendHandle(final Handle h) { - buf.append('\n').append(tab3); int tag = h.getTag(); buf.append("// handle kind 0x").append(Integer.toHexString(tag)) .append(" : "); + boolean isMethodHandle = false; switch (tag) { case Opcodes.H_GETFIELD: buf.append("GETFIELD"); @@ -1080,18 +1243,23 @@ protected void appendHandle(final Handle h) { break; case Opcodes.H_INVOKEINTERFACE: buf.append("INVOKEINTERFACE"); + isMethodHandle = true; break; case Opcodes.H_INVOKESPECIAL: buf.append("INVOKESPECIAL"); + isMethodHandle = true; break; case Opcodes.H_INVOKESTATIC: buf.append("INVOKESTATIC"); + isMethodHandle = true; break; case Opcodes.H_INVOKEVIRTUAL: buf.append("INVOKEVIRTUAL"); + isMethodHandle = true; break; case Opcodes.H_NEWINVOKESPECIAL: buf.append("NEWINVOKESPECIAL"); + isMethodHandle = true; break; } buf.append('\n'); @@ -1099,15 +1267,19 @@ protected void appendHandle(final Handle h) { appendDescriptor(INTERNAL_NAME, h.getOwner()); buf.append('.'); buf.append(h.getName()); - buf.append('('); + if(!isMethodHandle){ + buf.append('('); + } appendDescriptor(HANDLE_DESCRIPTOR, h.getDesc()); - buf.append(')').append('\n'); + if(!isMethodHandle){ + buf.append(')'); + } } /** * Appends a string representation of the given access modifiers to * {@link #buf buf}. - * + * * @param access * some access modifiers. */ @@ -1145,6 +1317,9 @@ private void appendAccess(final int access) { if ((access & Opcodes.ACC_SYNTHETIC) != 0) { buf.append("synthetic "); } + if ((access & Opcodes.ACC_MANDATED) != 0) { + buf.append("mandated "); + } if ((access & Opcodes.ACC_ENUM) != 0) { buf.append("enum "); } @@ -1156,6 +1331,90 @@ private void appendComa(final int i) { } } + private void appendTypeReference(final int typeRef) { + TypeReference ref = new TypeReference(typeRef); + switch (ref.getSort()) { + case TypeReference.CLASS_TYPE_PARAMETER: + buf.append("CLASS_TYPE_PARAMETER ").append( + ref.getTypeParameterIndex()); + break; + case TypeReference.METHOD_TYPE_PARAMETER: + buf.append("METHOD_TYPE_PARAMETER ").append( + ref.getTypeParameterIndex()); + break; + case TypeReference.CLASS_EXTENDS: + buf.append("CLASS_EXTENDS ").append(ref.getSuperTypeIndex()); + break; + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + buf.append("CLASS_TYPE_PARAMETER_BOUND ") + .append(ref.getTypeParameterIndex()).append(", ") + .append(ref.getTypeParameterBoundIndex()); + break; + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + buf.append("METHOD_TYPE_PARAMETER_BOUND ") + .append(ref.getTypeParameterIndex()).append(", ") + .append(ref.getTypeParameterBoundIndex()); + break; + case TypeReference.FIELD: + buf.append("FIELD"); + break; + case TypeReference.METHOD_RETURN: + buf.append("METHOD_RETURN"); + break; + case TypeReference.METHOD_RECEIVER: + buf.append("METHOD_RECEIVER"); + break; + case TypeReference.METHOD_FORMAL_PARAMETER: + buf.append("METHOD_FORMAL_PARAMETER ").append( + ref.getFormalParameterIndex()); + break; + case TypeReference.THROWS: + buf.append("THROWS ").append(ref.getExceptionIndex()); + break; + case TypeReference.LOCAL_VARIABLE: + buf.append("LOCAL_VARIABLE"); + break; + case TypeReference.RESOURCE_VARIABLE: + buf.append("RESOURCE_VARIABLE"); + break; + case TypeReference.EXCEPTION_PARAMETER: + buf.append("EXCEPTION_PARAMETER ").append( + ref.getTryCatchBlockIndex()); + break; + case TypeReference.INSTANCEOF: + buf.append("INSTANCEOF"); + break; + case TypeReference.NEW: + buf.append("NEW"); + break; + case TypeReference.CONSTRUCTOR_REFERENCE: + buf.append("CONSTRUCTOR_REFERENCE"); + break; + case TypeReference.METHOD_REFERENCE: + buf.append("METHOD_REFERENCE"); + break; + case TypeReference.CAST: + buf.append("CAST ").append(ref.getTypeArgumentIndex()); + break; + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + buf.append("CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT ").append( + ref.getTypeArgumentIndex()); + break; + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + buf.append("METHOD_INVOCATION_TYPE_ARGUMENT ").append( + ref.getTypeArgumentIndex()); + break; + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + buf.append("CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT ").append( + ref.getTypeArgumentIndex()); + break; + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + buf.append("METHOD_REFERENCE_TYPE_ARGUMENT ").append( + ref.getTypeArgumentIndex()); + break; + } + } + private void appendFrameTypes(final int n, final Object[] o) { for (int i = 0; i < n; ++i) { if (i > 0) { diff --git a/src/java/nginx/clojure/asm/util/TraceAnnotationVisitor.java b/src/java/nginx/clojure/asm/util/TraceAnnotationVisitor.java index 6c4bae55..d2635a67 100644 --- a/src/java/nginx/clojure/asm/util/TraceAnnotationVisitor.java +++ b/src/java/nginx/clojure/asm/util/TraceAnnotationVisitor.java @@ -47,7 +47,7 @@ public TraceAnnotationVisitor(final Printer p) { } public TraceAnnotationVisitor(final AnnotationVisitor av, final Printer p) { - super(Opcodes.ASM4, av); + super(Opcodes.ASM5, av); this.p = p; } diff --git a/src/java/nginx/clojure/asm/util/TraceClassVisitor.java b/src/java/nginx/clojure/asm/util/TraceClassVisitor.java index 069b8b77..d598bd30 100644 --- a/src/java/nginx/clojure/asm/util/TraceClassVisitor.java +++ b/src/java/nginx/clojure/asm/util/TraceClassVisitor.java @@ -37,6 +37,7 @@ import nginx.clojure.asm.FieldVisitor; import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * A {@link ClassVisitor} that prints the classes it visits with a @@ -130,7 +131,7 @@ public TraceClassVisitor(final ClassVisitor cv, final PrintWriter pw) { */ public TraceClassVisitor(final ClassVisitor cv, final Printer p, final PrintWriter pw) { - super(Opcodes.ASM4, cv); + super(Opcodes.ASM5, cv); this.pw = pw; this.p = p; } @@ -165,6 +166,16 @@ public AnnotationVisitor visitAnnotation(final String desc, return new TraceAnnotationVisitor(av, p); } + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + Printer p = this.p.visitClassTypeAnnotation(typeRef, typePath, desc, + visible); + AnnotationVisitor av = cv == null ? null : cv.visitTypeAnnotation( + typeRef, typePath, desc, visible); + return new TraceAnnotationVisitor(av, p); + } + @Override public void visitAttribute(final Attribute attr) { p.visitClassAttribute(attr); diff --git a/src/java/nginx/clojure/asm/util/TraceFieldVisitor.java b/src/java/nginx/clojure/asm/util/TraceFieldVisitor.java index ad3a680f..4f4a3e25 100644 --- a/src/java/nginx/clojure/asm/util/TraceFieldVisitor.java +++ b/src/java/nginx/clojure/asm/util/TraceFieldVisitor.java @@ -33,6 +33,7 @@ import nginx.clojure.asm.Attribute; import nginx.clojure.asm.FieldVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * A {@link FieldVisitor} that prints the fields it visits with a @@ -49,7 +50,7 @@ public TraceFieldVisitor(final Printer p) { } public TraceFieldVisitor(final FieldVisitor fv, final Printer p) { - super(Opcodes.ASM4, fv); + super(Opcodes.ASM5, fv); this.p = p; } @@ -62,6 +63,16 @@ public AnnotationVisitor visitAnnotation(final String desc, return new TraceAnnotationVisitor(av, p); } + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + Printer p = this.p.visitFieldTypeAnnotation(typeRef, typePath, desc, + visible); + AnnotationVisitor av = fv == null ? null : fv.visitTypeAnnotation( + typeRef, typePath, desc, visible); + return new TraceAnnotationVisitor(av, p); + } + @Override public void visitAttribute(final Attribute attr) { p.visitFieldAttribute(attr); diff --git a/src/java/nginx/clojure/asm/util/TraceMethodVisitor.java b/src/java/nginx/clojure/asm/util/TraceMethodVisitor.java index 0bf4573a..e3e7229a 100644 --- a/src/java/nginx/clojure/asm/util/TraceMethodVisitor.java +++ b/src/java/nginx/clojure/asm/util/TraceMethodVisitor.java @@ -35,6 +35,7 @@ import nginx.clojure.asm.Label; import nginx.clojure.asm.MethodVisitor; import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.TypePath; /** * A {@link MethodVisitor} that prints the methods it visits with a @@ -51,10 +52,16 @@ public TraceMethodVisitor(final Printer p) { } public TraceMethodVisitor(final MethodVisitor mv, final Printer p) { - super(Opcodes.ASM4, mv); + super(Opcodes.ASM5, mv); this.p = p; } + @Override + public void visitParameter(String name, int access) { + p.visitParameter(name, access); + super.visitParameter(name, access); + } + @Override public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { @@ -64,6 +71,16 @@ public AnnotationVisitor visitAnnotation(final String desc, return new TraceAnnotationVisitor(av, p); } + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + Printer p = this.p.visitMethodTypeAnnotation(typeRef, typePath, desc, + visible); + AnnotationVisitor av = mv == null ? null : mv.visitTypeAnnotation( + typeRef, typePath, desc, visible); + return new TraceAnnotationVisitor(av, p); + } + @Override public void visitAttribute(final Attribute attr) { p.visitMethodAttribute(attr); @@ -130,11 +147,31 @@ public void visitFieldInsn(final int opcode, final String owner, super.visitFieldInsn(opcode, owner, name, desc); } + @Deprecated @Override - public void visitMethodInsn(final int opcode, final String owner, - final String name, final String desc) { + public void visitMethodInsn(int opcode, String owner, String name, + String desc) { + if (api >= Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc); + return; + } p.visitMethodInsn(opcode, owner, name, desc); - super.visitMethodInsn(opcode, owner, name, desc); + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String desc, boolean itf) { + if (api < Opcodes.ASM5) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + p.visitMethodInsn(opcode, owner, name, desc, itf); + if (mv != null) { + mv.visitMethodInsn(opcode, owner, name, desc, itf); + } } @Override @@ -188,6 +225,16 @@ public void visitMultiANewArrayInsn(final String desc, final int dims) { super.visitMultiANewArrayInsn(desc, dims); } + @Override + public AnnotationVisitor visitInsnAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + Printer p = this.p + .visitInsnAnnotation(typeRef, typePath, desc, visible); + AnnotationVisitor av = mv == null ? null : mv.visitInsnAnnotation( + typeRef, typePath, desc, visible); + return new TraceAnnotationVisitor(av, p); + } + @Override public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { @@ -195,6 +242,16 @@ public void visitTryCatchBlock(final Label start, final Label end, super.visitTryCatchBlock(start, end, handler, type); } + @Override + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, + TypePath typePath, String desc, boolean visible) { + Printer p = this.p.visitTryCatchAnnotation(typeRef, typePath, desc, + visible); + AnnotationVisitor av = mv == null ? null : mv.visitTryCatchAnnotation( + typeRef, typePath, desc, visible); + return new TraceAnnotationVisitor(av, p); + } + @Override public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, @@ -203,6 +260,18 @@ public void visitLocalVariable(final String name, final String desc, super.visitLocalVariable(name, desc, signature, start, end, index); } + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, + TypePath typePath, Label[] start, Label[] end, int[] index, + String desc, boolean visible) { + Printer p = this.p.visitLocalVariableAnnotation(typeRef, typePath, + start, end, index, desc, visible); + AnnotationVisitor av = mv == null ? null : mv + .visitLocalVariableAnnotation(typeRef, typePath, start, end, + index, desc, visible); + return new TraceAnnotationVisitor(av, p); + } + @Override public void visitLineNumber(final int line, final Label start) { p.visitLineNumber(line, start); diff --git a/src/java/nginx/clojure/asm/util/TraceSignatureVisitor.java b/src/java/nginx/clojure/asm/util/TraceSignatureVisitor.java index 2456f658..74760435 100644 --- a/src/java/nginx/clojure/asm/util/TraceSignatureVisitor.java +++ b/src/java/nginx/clojure/asm/util/TraceSignatureVisitor.java @@ -41,7 +41,7 @@ */ public final class TraceSignatureVisitor extends SignatureVisitor { - private final StringBuffer declaration; + private final StringBuilder declaration; private boolean isInterface; @@ -53,9 +53,9 @@ public final class TraceSignatureVisitor extends SignatureVisitor { private boolean seenInterface; - private StringBuffer returnType; + private StringBuilder returnType; - private StringBuffer exceptions; + private StringBuilder exceptions; /** * Stack used to keep track of class types that have arguments. Each element @@ -75,13 +75,13 @@ public final class TraceSignatureVisitor extends SignatureVisitor { private String separator = ""; public TraceSignatureVisitor(final int access) { - super(Opcodes.ASM4); + super(Opcodes.ASM5); isInterface = (access & Opcodes.ACC_INTERFACE) != 0; - this.declaration = new StringBuffer(); + this.declaration = new StringBuilder(); } - private TraceSignatureVisitor(final StringBuffer buf) { - super(Opcodes.ASM4); + private TraceSignatureVisitor(final StringBuilder buf) { + super(Opcodes.ASM5); this.declaration = buf; } @@ -146,14 +146,14 @@ public SignatureVisitor visitReturnType() { declaration.append('('); } declaration.append(')'); - returnType = new StringBuffer(); + returnType = new StringBuilder(); return new TraceSignatureVisitor(returnType); } @Override public SignatureVisitor visitExceptionType() { if (exceptions == null) { - exceptions = new StringBuffer(); + exceptions = new StringBuilder(); } else { exceptions.append(", "); } diff --git a/src/java/nginx/clojure/asm/util/package.html b/src/java/nginx/clojure/asm/util/package.html new file mode 100644 index 00000000..91d74204 --- /dev/null +++ b/src/java/nginx/clojure/asm/util/package.html @@ -0,0 +1,40 @@ + + + +Provides ASM visitors that can be useful for programming and +debugging purposes. These class visitors are normally not used by applications +at runtime. This is why they are bundled in an optional asm-util.jar +library that is separated from (but requires) the asm.jar library, +which contains the core ASM framework. + +@since ASM 1.3.2 + + diff --git a/src/java/nginx/clojure/asm/xml/ASMContentHandler.java b/src/java/nginx/clojure/asm/xml/ASMContentHandler.java new file mode 100644 index 00000000..7cc77b8d --- /dev/null +++ b/src/java/nginx/clojure/asm/xml/ASMContentHandler.java @@ -0,0 +1,1464 @@ +/*** + * ASM XML Adapter + * Copyright (c) 2004-2011, Eugene Kuleshov + * 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.xml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import nginx.clojure.asm.AnnotationVisitor; +import nginx.clojure.asm.ClassVisitor; +import nginx.clojure.asm.FieldVisitor; +import nginx.clojure.asm.Handle; +import nginx.clojure.asm.Label; +import nginx.clojure.asm.MethodVisitor; +import nginx.clojure.asm.Opcodes; +import nginx.clojure.asm.Type; +import nginx.clojure.asm.TypePath; + +/** + * A {@link org.xml.sax.ContentHandler ContentHandler} that transforms XML + * document into Java class file. This class can be feeded by any kind of SAX + * 2.0 event producers, e.g. XML parser, XSLT or XPath engines, or custom code. + * + * @see nginx.clojure.asm.xml.SAXClassAdapter + * @see nginx.clojure.asm.xml.Processor + * + * @author Eugene Kuleshov + */ +public class ASMContentHandler extends DefaultHandler implements Opcodes { + + /** + * Stack of the intermediate processing contexts. + */ + private final ArrayList stack = new ArrayList(); + + /** + * Complete name of the current element. + */ + String match = ""; + + /** + * Current instance of the {@link ClassVisitor ClassVisitor} used to visit + * classfile bytecode. + */ + protected ClassVisitor cv; + + /** + * Map of the active {@link Label Label} instances for current method. + */ + protected Map labels; + + private static final String BASE = "class"; + + private final RuleSet RULES = new RuleSet(); + { + RULES.add(BASE, new ClassRule()); + RULES.add(BASE + "/interfaces/interface", new InterfaceRule()); + RULES.add(BASE + "/interfaces", new InterfacesRule()); + RULES.add(BASE + "/outerclass", new OuterClassRule()); + RULES.add(BASE + "/innerclass", new InnerClassRule()); + RULES.add(BASE + "/source", new SourceRule()); + RULES.add(BASE + "/field", new FieldRule()); + + RULES.add(BASE + "/method", new MethodRule()); + RULES.add(BASE + "/method/exceptions/exception", new ExceptionRule()); + RULES.add(BASE + "/method/exceptions", new ExceptionsRule()); + + RULES.add(BASE + "/method/parameter", new MethodParameterRule()); + RULES.add(BASE + "/method/annotationDefault", + new AnnotationDefaultRule()); + + RULES.add(BASE + "/method/code/*", new OpcodesRule()); // opcodes + + RULES.add(BASE + "/method/code/frame", new FrameRule()); + RULES.add(BASE + "/method/code/frame/local", new FrameTypeRule()); + RULES.add(BASE + "/method/code/frame/stack", new FrameTypeRule()); + + RULES.add(BASE + "/method/code/TABLESWITCH", new TableSwitchRule()); + RULES.add(BASE + "/method/code/TABLESWITCH/label", + new TableSwitchLabelRule()); + RULES.add(BASE + "/method/code/LOOKUPSWITCH", new LookupSwitchRule()); + RULES.add(BASE + "/method/code/LOOKUPSWITCH/label", + new LookupSwitchLabelRule()); + + RULES.add(BASE + "/method/code/INVOKEDYNAMIC", new InvokeDynamicRule()); + RULES.add(BASE + "/method/code/INVOKEDYNAMIC/bsmArg", + new InvokeDynamicBsmArgumentsRule()); + + RULES.add(BASE + "/method/code/Label", new LabelRule()); + RULES.add(BASE + "/method/code/TryCatch", new TryCatchRule()); + RULES.add(BASE + "/method/code/LineNumber", new LineNumberRule()); + RULES.add(BASE + "/method/code/LocalVar", new LocalVarRule()); + RULES.add(BASE + "/method/code/Max", new MaxRule()); + + RULES.add("*/annotation", new AnnotationRule()); + RULES.add("*/typeAnnotation", new TypeAnnotationRule()); + RULES.add("*/parameterAnnotation", new AnnotationParameterRule()); + RULES.add("*/insnAnnotation", new InsnAnnotationRule()); + RULES.add("*/tryCatchAnnotation", new TryCatchAnnotationRule()); + RULES.add("*/localVariableAnnotation", + new LocalVariableAnnotationRule()); + RULES.add("*/annotationValue", new AnnotationValueRule()); + RULES.add("*/annotationValueAnnotation", + new AnnotationValueAnnotationRule()); + RULES.add("*/annotationValueEnum", new AnnotationValueEnumRule()); + RULES.add("*/annotationValueArray", new AnnotationValueArrayRule()); + } + + private static interface OpcodeGroup { + public static final int INSN = 0; + public static final int INSN_INT = 1; + public static final int INSN_VAR = 2; + public static final int INSN_TYPE = 3; + public static final int INSN_FIELD = 4; + public static final int INSN_METHOD = 5; + public static final int INSN_JUMP = 6; + public static final int INSN_LDC = 7; + public static final int INSN_IINC = 8; + public static final int INSN_MULTIANEWARRAY = 9; + } + + /** + * Map of the opcode names to opcode and opcode group + */ + static final HashMap OPCODES = new HashMap(); + static { + addOpcode("NOP", NOP, OpcodeGroup.INSN); + addOpcode("ACONST_NULL", ACONST_NULL, OpcodeGroup.INSN); + addOpcode("ICONST_M1", ICONST_M1, OpcodeGroup.INSN); + addOpcode("ICONST_0", ICONST_0, OpcodeGroup.INSN); + addOpcode("ICONST_1", ICONST_1, OpcodeGroup.INSN); + addOpcode("ICONST_2", ICONST_2, OpcodeGroup.INSN); + addOpcode("ICONST_3", ICONST_3, OpcodeGroup.INSN); + addOpcode("ICONST_4", ICONST_4, OpcodeGroup.INSN); + addOpcode("ICONST_5", ICONST_5, OpcodeGroup.INSN); + addOpcode("LCONST_0", LCONST_0, OpcodeGroup.INSN); + addOpcode("LCONST_1", LCONST_1, OpcodeGroup.INSN); + addOpcode("FCONST_0", FCONST_0, OpcodeGroup.INSN); + addOpcode("FCONST_1", FCONST_1, OpcodeGroup.INSN); + addOpcode("FCONST_2", FCONST_2, OpcodeGroup.INSN); + addOpcode("DCONST_0", DCONST_0, OpcodeGroup.INSN); + addOpcode("DCONST_1", DCONST_1, OpcodeGroup.INSN); + addOpcode("BIPUSH", BIPUSH, OpcodeGroup.INSN_INT); + addOpcode("SIPUSH", SIPUSH, OpcodeGroup.INSN_INT); + addOpcode("LDC", LDC, OpcodeGroup.INSN_LDC); + addOpcode("ILOAD", ILOAD, OpcodeGroup.INSN_VAR); + addOpcode("LLOAD", LLOAD, OpcodeGroup.INSN_VAR); + addOpcode("FLOAD", FLOAD, OpcodeGroup.INSN_VAR); + addOpcode("DLOAD", DLOAD, OpcodeGroup.INSN_VAR); + addOpcode("ALOAD", ALOAD, OpcodeGroup.INSN_VAR); + addOpcode("IALOAD", IALOAD, OpcodeGroup.INSN); + addOpcode("LALOAD", LALOAD, OpcodeGroup.INSN); + addOpcode("FALOAD", FALOAD, OpcodeGroup.INSN); + addOpcode("DALOAD", DALOAD, OpcodeGroup.INSN); + addOpcode("AALOAD", AALOAD, OpcodeGroup.INSN); + addOpcode("BALOAD", BALOAD, OpcodeGroup.INSN); + addOpcode("CALOAD", CALOAD, OpcodeGroup.INSN); + addOpcode("SALOAD", SALOAD, OpcodeGroup.INSN); + addOpcode("ISTORE", ISTORE, OpcodeGroup.INSN_VAR); + addOpcode("LSTORE", LSTORE, OpcodeGroup.INSN_VAR); + addOpcode("FSTORE", FSTORE, OpcodeGroup.INSN_VAR); + addOpcode("DSTORE", DSTORE, OpcodeGroup.INSN_VAR); + addOpcode("ASTORE", ASTORE, OpcodeGroup.INSN_VAR); + addOpcode("IASTORE", IASTORE, OpcodeGroup.INSN); + addOpcode("LASTORE", LASTORE, OpcodeGroup.INSN); + addOpcode("FASTORE", FASTORE, OpcodeGroup.INSN); + addOpcode("DASTORE", DASTORE, OpcodeGroup.INSN); + addOpcode("AASTORE", AASTORE, OpcodeGroup.INSN); + addOpcode("BASTORE", BASTORE, OpcodeGroup.INSN); + addOpcode("CASTORE", CASTORE, OpcodeGroup.INSN); + addOpcode("SASTORE", SASTORE, OpcodeGroup.INSN); + addOpcode("POP", POP, OpcodeGroup.INSN); + addOpcode("POP2", POP2, OpcodeGroup.INSN); + addOpcode("DUP", DUP, OpcodeGroup.INSN); + addOpcode("DUP_X1", DUP_X1, OpcodeGroup.INSN); + addOpcode("DUP_X2", DUP_X2, OpcodeGroup.INSN); + addOpcode("DUP2", DUP2, OpcodeGroup.INSN); + addOpcode("DUP2_X1", DUP2_X1, OpcodeGroup.INSN); + addOpcode("DUP2_X2", DUP2_X2, OpcodeGroup.INSN); + addOpcode("SWAP", SWAP, OpcodeGroup.INSN); + addOpcode("IADD", IADD, OpcodeGroup.INSN); + addOpcode("LADD", LADD, OpcodeGroup.INSN); + addOpcode("FADD", FADD, OpcodeGroup.INSN); + addOpcode("DADD", DADD, OpcodeGroup.INSN); + addOpcode("ISUB", ISUB, OpcodeGroup.INSN); + addOpcode("LSUB", LSUB, OpcodeGroup.INSN); + addOpcode("FSUB", FSUB, OpcodeGroup.INSN); + addOpcode("DSUB", DSUB, OpcodeGroup.INSN); + addOpcode("IMUL", IMUL, OpcodeGroup.INSN); + addOpcode("LMUL", LMUL, OpcodeGroup.INSN); + addOpcode("FMUL", FMUL, OpcodeGroup.INSN); + addOpcode("DMUL", DMUL, OpcodeGroup.INSN); + addOpcode("IDIV", IDIV, OpcodeGroup.INSN); + addOpcode("LDIV", LDIV, OpcodeGroup.INSN); + addOpcode("FDIV", FDIV, OpcodeGroup.INSN); + addOpcode("DDIV", DDIV, OpcodeGroup.INSN); + addOpcode("IREM", IREM, OpcodeGroup.INSN); + addOpcode("LREM", LREM, OpcodeGroup.INSN); + addOpcode("FREM", FREM, OpcodeGroup.INSN); + addOpcode("DREM", DREM, OpcodeGroup.INSN); + addOpcode("INEG", INEG, OpcodeGroup.INSN); + addOpcode("LNEG", LNEG, OpcodeGroup.INSN); + addOpcode("FNEG", FNEG, OpcodeGroup.INSN); + addOpcode("DNEG", DNEG, OpcodeGroup.INSN); + addOpcode("ISHL", ISHL, OpcodeGroup.INSN); + addOpcode("LSHL", LSHL, OpcodeGroup.INSN); + addOpcode("ISHR", ISHR, OpcodeGroup.INSN); + addOpcode("LSHR", LSHR, OpcodeGroup.INSN); + addOpcode("IUSHR", IUSHR, OpcodeGroup.INSN); + addOpcode("LUSHR", LUSHR, OpcodeGroup.INSN); + addOpcode("IAND", IAND, OpcodeGroup.INSN); + addOpcode("LAND", LAND, OpcodeGroup.INSN); + addOpcode("IOR", IOR, OpcodeGroup.INSN); + addOpcode("LOR", LOR, OpcodeGroup.INSN); + addOpcode("IXOR", IXOR, OpcodeGroup.INSN); + addOpcode("LXOR", LXOR, OpcodeGroup.INSN); + addOpcode("IINC", IINC, OpcodeGroup.INSN_IINC); + addOpcode("I2L", I2L, OpcodeGroup.INSN); + addOpcode("I2F", I2F, OpcodeGroup.INSN); + addOpcode("I2D", I2D, OpcodeGroup.INSN); + addOpcode("L2I", L2I, OpcodeGroup.INSN); + addOpcode("L2F", L2F, OpcodeGroup.INSN); + addOpcode("L2D", L2D, OpcodeGroup.INSN); + addOpcode("F2I", F2I, OpcodeGroup.INSN); + addOpcode("F2L", F2L, OpcodeGroup.INSN); + addOpcode("F2D", F2D, OpcodeGroup.INSN); + addOpcode("D2I", D2I, OpcodeGroup.INSN); + addOpcode("D2L", D2L, OpcodeGroup.INSN); + addOpcode("D2F", D2F, OpcodeGroup.INSN); + addOpcode("I2B", I2B, OpcodeGroup.INSN); + addOpcode("I2C", I2C, OpcodeGroup.INSN); + addOpcode("I2S", I2S, OpcodeGroup.INSN); + addOpcode("LCMP", LCMP, OpcodeGroup.INSN); + addOpcode("FCMPL", FCMPL, OpcodeGroup.INSN); + addOpcode("FCMPG", FCMPG, OpcodeGroup.INSN); + addOpcode("DCMPL", DCMPL, OpcodeGroup.INSN); + addOpcode("DCMPG", DCMPG, OpcodeGroup.INSN); + addOpcode("IFEQ", IFEQ, OpcodeGroup.INSN_JUMP); + addOpcode("IFNE", IFNE, OpcodeGroup.INSN_JUMP); + addOpcode("IFLT", IFLT, OpcodeGroup.INSN_JUMP); + addOpcode("IFGE", IFGE, OpcodeGroup.INSN_JUMP); + addOpcode("IFGT", IFGT, OpcodeGroup.INSN_JUMP); + addOpcode("IFLE", IFLE, OpcodeGroup.INSN_JUMP); + addOpcode("IF_ICMPEQ", IF_ICMPEQ, OpcodeGroup.INSN_JUMP); + addOpcode("IF_ICMPNE", IF_ICMPNE, OpcodeGroup.INSN_JUMP); + addOpcode("IF_ICMPLT", IF_ICMPLT, OpcodeGroup.INSN_JUMP); + addOpcode("IF_ICMPGE", IF_ICMPGE, OpcodeGroup.INSN_JUMP); + addOpcode("IF_ICMPGT", IF_ICMPGT, OpcodeGroup.INSN_JUMP); + addOpcode("IF_ICMPLE", IF_ICMPLE, OpcodeGroup.INSN_JUMP); + addOpcode("IF_ACMPEQ", IF_ACMPEQ, OpcodeGroup.INSN_JUMP); + addOpcode("IF_ACMPNE", IF_ACMPNE, OpcodeGroup.INSN_JUMP); + addOpcode("GOTO", GOTO, OpcodeGroup.INSN_JUMP); + addOpcode("JSR", JSR, OpcodeGroup.INSN_JUMP); + addOpcode("RET", RET, OpcodeGroup.INSN_VAR); + addOpcode("IRETURN", IRETURN, OpcodeGroup.INSN); + addOpcode("LRETURN", LRETURN, OpcodeGroup.INSN); + addOpcode("FRETURN", FRETURN, OpcodeGroup.INSN); + addOpcode("DRETURN", DRETURN, OpcodeGroup.INSN); + addOpcode("ARETURN", ARETURN, OpcodeGroup.INSN); + addOpcode("RETURN", RETURN, OpcodeGroup.INSN); + addOpcode("GETSTATIC", GETSTATIC, OpcodeGroup.INSN_FIELD); + addOpcode("PUTSTATIC", PUTSTATIC, OpcodeGroup.INSN_FIELD); + addOpcode("GETFIELD", GETFIELD, OpcodeGroup.INSN_FIELD); + addOpcode("PUTFIELD", PUTFIELD, OpcodeGroup.INSN_FIELD); + addOpcode("INVOKEVIRTUAL", INVOKEVIRTUAL, OpcodeGroup.INSN_METHOD); + addOpcode("INVOKESPECIAL", INVOKESPECIAL, OpcodeGroup.INSN_METHOD); + addOpcode("INVOKESTATIC", INVOKESTATIC, OpcodeGroup.INSN_METHOD); + addOpcode("INVOKEINTERFACE", INVOKEINTERFACE, OpcodeGroup.INSN_METHOD); + addOpcode("NEW", NEW, OpcodeGroup.INSN_TYPE); + addOpcode("NEWARRAY", NEWARRAY, OpcodeGroup.INSN_INT); + addOpcode("ANEWARRAY", ANEWARRAY, OpcodeGroup.INSN_TYPE); + addOpcode("ARRAYLENGTH", ARRAYLENGTH, OpcodeGroup.INSN); + addOpcode("ATHROW", ATHROW, OpcodeGroup.INSN); + addOpcode("CHECKCAST", CHECKCAST, OpcodeGroup.INSN_TYPE); + addOpcode("INSTANCEOF", INSTANCEOF, OpcodeGroup.INSN_TYPE); + addOpcode("MONITORENTER", MONITORENTER, OpcodeGroup.INSN); + addOpcode("MONITOREXIT", MONITOREXIT, OpcodeGroup.INSN); + addOpcode("MULTIANEWARRAY", MULTIANEWARRAY, + OpcodeGroup.INSN_MULTIANEWARRAY); + addOpcode("IFNULL", IFNULL, OpcodeGroup.INSN_JUMP); + addOpcode("IFNONNULL", IFNONNULL, OpcodeGroup.INSN_JUMP); + } + + private static void addOpcode(String operStr, int oper, int group) { + OPCODES.put(operStr, new Opcode(oper, group)); + } + + static final HashMap TYPES = new HashMap(); + static { + String[] types = SAXCodeAdapter.TYPES; + for (int i = 0; i < types.length; i++) { + TYPES.put(types[i], i); + } + } + + /** + * Constructs a new {@link ASMContentHandler ASMContentHandler} object. + * + * @param cv + * class visitor that will be called to reconstruct the classfile + * using the XML stream. + */ + public ASMContentHandler(final ClassVisitor cv) { + this.cv = cv; + } + + /** + * Process notification of the start of an XML element being reached. + * + * @param ns + * - The Namespace URI, or the empty string if the element has no + * Namespace URI or if Namespace processing is not being + * performed. + * @param lName + * - The local name (without prefix), or the empty string if + * Namespace processing is not being performed. + * @param qName + * - The qualified name (with prefix), or the empty string if + * qualified names are not available. + * @param list + * - The attributes attached to the element. If there are no + * attributes, it shall be an empty Attributes object. + * @exception SAXException + * if a parsing error is to be reported + */ + @Override + public final void startElement(final String ns, final String lName, + final String qName, final Attributes list) throws SAXException { + // the actual element name is either in lName or qName, depending + // on whether the parser is namespace aware + String name = lName == null || lName.length() == 0 ? qName : lName; + + // Compute the current matching rule + StringBuilder sb = new StringBuilder(match); + if (match.length() > 0) { + sb.append('/'); + } + sb.append(name); + match = sb.toString(); + + // Fire "begin" events for all relevant rules + Rule r = (Rule) RULES.match(match); + if (r != null) { + r.begin(name, list); + } + } + + /** + * Process notification of the end of an XML element being reached. + * + * @param ns + * - The Namespace URI, or the empty string if the element has no + * Namespace URI or if Namespace processing is not being + * performed. + * @param lName + * - The local name (without prefix), or the empty string if + * Namespace processing is not being performed. + * @param qName + * - The qualified XML 1.0 name (with prefix), or the empty + * string if qualified names are not available. + * + * @exception SAXException + * if a parsing error is to be reported + */ + @Override + public final void endElement(final String ns, final String lName, + final String qName) throws SAXException { + // the actual element name is either in lName or qName, depending + // on whether the parser is namespace aware + String name = lName == null || lName.length() == 0 ? qName : lName; + + // Fire "end" events for all relevant rules in reverse order + Rule r = (Rule) RULES.match(match); + if (r != null) { + r.end(name); + } + + // Recover the previous match expression + int slash = match.lastIndexOf('/'); + if (slash >= 0) { + match = match.substring(0, slash); + } else { + match = ""; + } + } + + /** + * Return the top object on the stack without removing it. If there are no + * objects on the stack, return null. + * + * @return the top object on the stack without removing it. + */ + final Object peek() { + int size = stack.size(); + return size == 0 ? null : stack.get(size - 1); + } + + /** + * Pop the top object off of the stack, and return it. If there are no + * objects on the stack, return null. + * + * @return the top object off of the stack. + */ + final Object pop() { + int size = stack.size(); + return size == 0 ? null : stack.remove(size - 1); + } + + /** + * Push a new object onto the top of the object stack. + * + * @param object + * The new object + */ + final void push(final Object object) { + stack.add(object); + } + + static final class RuleSet { + + private final HashMap rules = new HashMap(); + + private final ArrayList lpatterns = new ArrayList(); + + private final ArrayList rpatterns = new ArrayList(); + + public void add(final String path, final Object rule) { + String pattern = path; + if (path.startsWith("*/")) { + pattern = path.substring(1); + lpatterns.add(pattern); + } else if (path.endsWith("/*")) { + pattern = path.substring(0, path.length() - 1); + rpatterns.add(pattern); + } + rules.put(pattern, rule); + } + + public Object match(final String path) { + if (rules.containsKey(path)) { + return rules.get(path); + } + + int n = path.lastIndexOf('/'); + for (Iterator it = lpatterns.iterator(); it.hasNext();) { + String pattern = it.next(); + if (path.substring(n).endsWith(pattern)) { + return rules.get(pattern); + } + } + + for (Iterator it = rpatterns.iterator(); it.hasNext();) { + String pattern = it.next(); + if (path.startsWith(pattern)) { + return rules.get(pattern); + } + } + + return null; + } + } + + /** + * Rule + */ + protected abstract class Rule { + + public void begin(final String name, final Attributes attrs) + throws SAXException { + } + + public void end(final String name) { + } + + protected final Object getValue(final String desc, final String val) + throws SAXException { + Object value = null; + if (val != null) { + if ("Ljava/lang/String;".equals(desc)) { + value = decode(val); + } else if ("Ljava/lang/Integer;".equals(desc) + || "I".equals(desc) || "S".equals(desc) + || "B".equals(desc) || "C".equals(desc) + || "Z".equals(desc)) { + value = new Integer(val); + + } else if ("Ljava/lang/Short;".equals(desc)) { + value = new Short(val); + + } else if ("Ljava/lang/Byte;".equals(desc)) { + value = new Byte(val); + + } else if ("Ljava/lang/Character;".equals(desc)) { + value = new Character(decode(val).charAt(0)); + + } else if ("Ljava/lang/Boolean;".equals(desc)) { + value = Boolean.valueOf(val); + + } else if ("Ljava/lang/Long;".equals(desc) || "J".equals(desc)) { + value = new Long(val); + } else if ("Ljava/lang/Float;".equals(desc) || "F".equals(desc)) { + value = new Float(val); + } else if ("Ljava/lang/Double;".equals(desc) + || "D".equals(desc)) { + value = new Double(val); + } else if (Type.getDescriptor(Type.class).equals(desc)) { + value = Type.getType(val); + + } else if (Type.getDescriptor(Handle.class).equals(desc)) { + value = decodeHandle(val); + + } else { + // TODO use of default toString(). + throw new SAXException("Invalid value:" + val + " desc:" + + desc + " ctx:" + this); + } + } + return value; + } + + Handle decodeHandle(final String val) throws SAXException { + try { + int dotIndex = val.indexOf('.'); + int descIndex = val.indexOf('(', dotIndex + 1); + int tagIndex = val.lastIndexOf('('); + int itfIndex = val.indexOf(' ', tagIndex + 1); + + boolean itf = itfIndex != -1; + int tag = Integer.parseInt( + val.substring(tagIndex + 1, + itf? val.length() - 1: itfIndex)); + String owner = val.substring(0, dotIndex); + String name = val.substring(dotIndex + 1, descIndex); + String desc = val.substring(descIndex, tagIndex - 1); + return new Handle(tag, owner, name, desc, itf); + + } catch (RuntimeException e) { + throw new SAXException("Malformed handle " + val, e); + } + } + + private final String decode(final String val) throws SAXException { + StringBuilder sb = new StringBuilder(val.length()); + try { + int n = 0; + while (n < val.length()) { + char c = val.charAt(n); + if (c == '\\') { + n++; + c = val.charAt(n); + if (c == '\\') { + sb.append('\\'); + } else { + n++; // skip 'u' + sb.append((char) Integer.parseInt( + val.substring(n, n + 4), 16)); + n += 3; + } + } else { + sb.append(c); + } + n++; + } + + } catch (RuntimeException ex) { + throw new SAXException(ex); + } + return sb.toString(); + } + + protected final Label getLabel(final Object label) { + Label lbl = labels.get(label); + if (lbl == null) { + lbl = new Label(); + labels.put(label, lbl); + } + return lbl; + } + + // TODO verify move to stack + protected final MethodVisitor getCodeVisitor() { + return (MethodVisitor) peek(); + } + + protected final int getAccess(final String s) { + int access = 0; + if (s.indexOf("public") != -1) { + access |= ACC_PUBLIC; + } + if (s.indexOf("private") != -1) { + access |= ACC_PRIVATE; + } + if (s.indexOf("protected") != -1) { + access |= ACC_PROTECTED; + } + if (s.indexOf("static") != -1) { + access |= ACC_STATIC; + } + if (s.indexOf("final") != -1) { + access |= ACC_FINAL; + } + if (s.indexOf("super") != -1) { + access |= ACC_SUPER; + } + if (s.indexOf("synchronized") != -1) { + access |= ACC_SYNCHRONIZED; + } + if (s.indexOf("volatile") != -1) { + access |= ACC_VOLATILE; + } + if (s.indexOf("bridge") != -1) { + access |= ACC_BRIDGE; + } + if (s.indexOf("varargs") != -1) { + access |= ACC_VARARGS; + } + if (s.indexOf("transient") != -1) { + access |= ACC_TRANSIENT; + } + if (s.indexOf("native") != -1) { + access |= ACC_NATIVE; + } + if (s.indexOf("interface") != -1) { + access |= ACC_INTERFACE; + } + if (s.indexOf("abstract") != -1) { + access |= ACC_ABSTRACT; + } + if (s.indexOf("strict") != -1) { + access |= ACC_STRICT; + } + if (s.indexOf("synthetic") != -1) { + access |= ACC_SYNTHETIC; + } + if (s.indexOf("annotation") != -1) { + access |= ACC_ANNOTATION; + } + if (s.indexOf("enum") != -1) { + access |= ACC_ENUM; + } + if (s.indexOf("deprecated") != -1) { + access |= ACC_DEPRECATED; + } + if (s.indexOf("mandated") != -1) { + access |= ACC_MANDATED; + } + return access; + } + } + + /** + * ClassRule + */ + final class ClassRule extends Rule { + + @Override + public final void begin(final String name, final Attributes attrs) { + int major = Integer.parseInt(attrs.getValue("major")); + int minor = Integer.parseInt(attrs.getValue("minor")); + HashMap vals = new HashMap(); + vals.put("version", minor << 16 | major); + vals.put("access", attrs.getValue("access")); + vals.put("name", attrs.getValue("name")); + vals.put("parent", attrs.getValue("parent")); + vals.put("source", attrs.getValue("source")); + vals.put("signature", attrs.getValue("signature")); + vals.put("interfaces", new ArrayList()); + push(vals); + // values will be extracted in InterfacesRule.end(); + } + } + + final class SourceRule extends Rule { + + @Override + public void begin(final String name, final Attributes attrs) { + String file = attrs.getValue("file"); + String debug = attrs.getValue("debug"); + cv.visitSource(file, debug); + } + } + + /** + * InterfaceRule + */ + final class InterfaceRule extends Rule { + + @Override + @SuppressWarnings("unchecked") + public final void begin(final String name, final Attributes attrs) { + ((ArrayList) ((HashMap) peek()).get("interfaces")) + .add(attrs.getValue("name")); + } + } + + /** + * InterfacesRule + */ + final class InterfacesRule extends Rule { + + @Override + public final void end(final String element) { + HashMap vals = (HashMap) pop(); + int version = ((Integer) vals.get("version")).intValue(); + int access = getAccess((String) vals.get("access")); + String name = (String) vals.get("name"); + String signature = (String) vals.get("signature"); + String parent = (String) vals.get("parent"); + ArrayList infs = (ArrayList) vals.get("interfaces"); + String[] interfaces = infs.toArray(new String[infs.size()]); + cv.visit(version, access, name, signature, parent, interfaces); + push(cv); + } + } + + /** + * OuterClassRule + */ + final class OuterClassRule extends Rule { + + @Override + public final void begin(final String element, final Attributes attrs) { + String owner = attrs.getValue("owner"); + String name = attrs.getValue("name"); + String desc = attrs.getValue("desc"); + cv.visitOuterClass(owner, name, desc); + } + } + + /** + * InnerClassRule + */ + final class InnerClassRule extends Rule { + + @Override + public final void begin(final String element, final Attributes attrs) { + int access = getAccess(attrs.getValue("access")); + String name = attrs.getValue("name"); + String outerName = attrs.getValue("outerName"); + String innerName = attrs.getValue("innerName"); + cv.visitInnerClass(name, outerName, innerName, access); + } + } + + /** + * FieldRule + */ + final class FieldRule extends Rule { + + @Override + public final void begin(final String element, final Attributes attrs) + throws SAXException { + int access = getAccess(attrs.getValue("access")); + String name = attrs.getValue("name"); + String signature = attrs.getValue("signature"); + String desc = attrs.getValue("desc"); + Object value = getValue(desc, attrs.getValue("value")); + push(cv.visitField(access, name, desc, signature, value)); + } + + @Override + public void end(final String name) { + ((FieldVisitor) pop()).visitEnd(); + } + } + + /** + * MethodRule + */ + final class MethodRule extends Rule { + + @Override + public final void begin(final String name, final Attributes attrs) { + labels = new HashMap(); + HashMap vals = new HashMap(); + vals.put("access", attrs.getValue("access")); + vals.put("name", attrs.getValue("name")); + vals.put("desc", attrs.getValue("desc")); + vals.put("signature", attrs.getValue("signature")); + vals.put("exceptions", new ArrayList()); + push(vals); + // values will be extracted in ExceptionsRule.end(); + } + + @Override + public final void end(final String name) { + ((MethodVisitor) pop()).visitEnd(); + labels = null; + } + } + + /** + * ExceptionRule + */ + final class ExceptionRule extends Rule { + + @Override + @SuppressWarnings("unchecked") + public final void begin(final String name, final Attributes attrs) { + ((ArrayList) ((HashMap) peek()).get("exceptions")) + .add(attrs.getValue("name")); + } + } + + /** + * ExceptionsRule + */ + final class ExceptionsRule extends Rule { + + @Override + public final void end(final String element) { + HashMap vals = (HashMap) pop(); + int access = getAccess((String) vals.get("access")); + String name = (String) vals.get("name"); + String desc = (String) vals.get("desc"); + String signature = (String) vals.get("signature"); + ArrayList excs = (ArrayList) vals.get("exceptions"); + String[] exceptions = excs.toArray(new String[excs.size()]); + + push(cv.visitMethod(access, name, desc, signature, exceptions)); + } + } + + /** + * MethodParameterRule + */ + final class MethodParameterRule extends Rule { + @Override + public void begin(final String nm, final Attributes attrs) { + String name = attrs.getValue("name"); + int access = getAccess(attrs.getValue("access")); + getCodeVisitor().visitParameter(name, access); + } + } + + /** + * TableSwitchRule + */ + final class TableSwitchRule extends Rule { + + @Override + public final void begin(final String name, final Attributes attrs) { + HashMap vals = new HashMap(); + vals.put("min", attrs.getValue("min")); + vals.put("max", attrs.getValue("max")); + vals.put("dflt", attrs.getValue("dflt")); + vals.put("labels", new ArrayList()); + push(vals); + } + + @Override + public final void end(final String name) { + HashMap vals = (HashMap) pop(); + int min = Integer.parseInt((String) vals.get("min")); + int max = Integer.parseInt((String) vals.get("max")); + Label dflt = getLabel(vals.get("dflt")); + ArrayList lbls = (ArrayList) vals.get("labels"); + Label[] labels = lbls.toArray(new Label[lbls.size()]); + getCodeVisitor().visitTableSwitchInsn(min, max, dflt, labels); + } + } + + /** + * TableSwitchLabelRule + */ + final class TableSwitchLabelRule extends Rule { + + @Override + @SuppressWarnings("unchecked") + public final void begin(final String name, final Attributes attrs) { + ((ArrayList