diff --git a/.classpath b/.classpath index b33b264..aa7f96d 100644 --- a/.classpath +++ b/.classpath @@ -2,7 +2,7 @@ - + diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 224e7f0..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.pc/ diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..f42de36 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/README.md b/README.md index 8c5af2b..7193a3d 100644 --- a/README.md +++ b/README.md @@ -27,27 +27,10 @@ To use reflectasm with maven, please use the following snippet in your pom.xml com.esotericsoftware reflectasm - 1.11.3 + 1.11.7 ``` -If you already have asm in a different version (than the one pulled in by reflectasm - see pom.xml) in your classpath, you should use the shaded reflectasm (contains relocated asm classes): - -```xml - - com.esotericsoftware - reflectasm - 1.11.3 - shaded - - - org.ow2.asm - asm - - - -``` - ## Usage Method reflection with ReflectASM: diff --git a/debian/changelog b/debian/changelog index 2d5afb9..291186b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,35 @@ +libreflectasm-java (1.11.9+dfsg-4) unstable; urgency=medium + + * Deactivating flaky assertion in ClassLoaderTest (Closes: #1031480) + * Raising Standards version to 4.6.2 (no change) + * Removing unneeded versioned B-D on javahelper + + -- Pierre Gruet Fri, 17 Feb 2023 21:51:44 +0100 + +libreflectasm-java (1.11.9+dfsg-3) unstable; urgency=medium + + * Stopping build-depending on dropped libasm-java-doc (Closes: #1025838) + + -- Pierre Gruet Sat, 10 Dec 2022 14:18:59 +0100 + +libreflectasm-java (1.11.9+dfsg-2) unstable; urgency=medium + + * Updating ConstructorAccessTest to fit the changes in class loading + brought by OpenJDK17 (Closes: #1012106) + + -- Pierre Gruet Fri, 11 Nov 2022 07:44:35 +0100 + +libreflectasm-java (1.11.9+dfsg-1) unstable; urgency=medium + + * New upstream version 1.11.9+dfsg + * Refreshing patches + * Raising Standards version to 4.6.1 (no change) + * Updating d/watch after a change in the Github layout + * Adding a Lintian override for the bad-jar-name warning + * Refreshing the Lintian override for the embedded Javascript + + -- Pierre Gruet Thu, 22 Sep 2022 16:23:33 +0200 + libreflectasm-java (1.11.6+dfsg-1) unstable; urgency=medium * Bumping Standards version to 4.6.0 (no change) diff --git a/debian/control b/debian/control index 079a5bc..fca7bf2 100644 --- a/debian/control +++ b/debian/control @@ -5,15 +5,14 @@ Maintainer: Debian Java Maintainers Build-Depends: debhelper-compat (= 13), default-jdk-headless, - javahelper (>= 0.43), + javahelper, maven-debian-helper, libmaven-assembly-plugin-java, libmaven-bundle-plugin-java, libmaven-javadoc-plugin-java, libasm-java, - libasm-java-doc, junit4 -Standards-Version: 4.6.0 +Standards-Version: 4.6.2 Vcs-Git: https://salsa.debian.org/java-team/libreflectasm-java.git Vcs-Browser: https://salsa.debian.org/java-team/libreflectasm-java Homepage: https://github.com/EsotericSoftware/reflectasm diff --git a/debian/libreflectasm-java-doc.lintian-overrides b/debian/libreflectasm-java-doc.lintian-overrides index b16c252..1c102f1 100644 --- a/debian/libreflectasm-java-doc.lintian-overrides +++ b/debian/libreflectasm-java-doc.lintian-overrides @@ -1,4 +1,4 @@ # It would be too complicated to remove the javascript that is included in # javadocs, see the discussion on Debian Java mailing list starting at # https://lists.debian.org/debian-java/2018/06/msg00020.html. -embedded-javascript-library usr/share/doc/libreflectasm-java/api/*/jquery*.*s * +embedded-javascript-library please use * [usr/share/doc/libreflectasm-java/api/*/jquery*.*s] diff --git a/debian/libreflectasm-java.lintian-overrides b/debian/libreflectasm-java.lintian-overrides new file mode 100644 index 0000000..aa9e915 --- /dev/null +++ b/debian/libreflectasm-java.lintian-overrides @@ -0,0 +1,3 @@ +# The indicated file is the real jar and not a symlink, but this really does +# not harm. +bad-jar-name [usr/share/java/reflectasm.jar] diff --git a/debian/patches/disable_flaky_assertion.patch b/debian/patches/disable_flaky_assertion.patch new file mode 100644 index 0000000..9e15cde --- /dev/null +++ b/debian/patches/disable_flaky_assertion.patch @@ -0,0 +1,26 @@ +Description: deactivating assertions that appear to be flaky +Author: Pierre Gruet +Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1031480 +Forwarded: https://github.com/EsotericSoftware/reflectasm/issues/91 +Last-Update: 2023-02-17 + +--- a/test/com/esotericsoftware/reflectasm/ClassLoaderTest.java ++++ b/test/com/esotericsoftware/reflectasm/ClassLoaderTest.java +@@ -60,7 +60,7 @@ + assertEquals(access1.getClass().toString(), access2.getClass().toString()); // Same class names + assertFalse(access1.getClass().equals(access2.getClass())); // But different classes + +- assertEquals(initialCount + 2, AccessClassLoader.activeAccessClassLoaders()); ++ //assertEquals(initialCount + 2, AccessClassLoader.activeAccessClassLoaders()); + + testClassLoader1 = null; + testClass1 = null; +@@ -74,7 +74,7 @@ + reclaimLoaders(); + + // Yeah, reclaimed! +- assertEquals(initialCount, AccessClassLoader.activeAccessClassLoaders()); ++ //assertEquals(initialCount, AccessClassLoader.activeAccessClassLoaders()); + } + + private void reclaimLoaders () throws Exception { diff --git a/debian/patches/generate_1_6_classes.patch b/debian/patches/generate_1_6_classes.patch index fefb712..1bb6387 100644 --- a/debian/patches/generate_1_6_classes.patch +++ b/debian/patches/generate_1_6_classes.patch @@ -7,34 +7,34 @@ Last-Update: 2021-05-18 --- a/src/com/esotericsoftware/reflectasm/ConstructorAccess.java +++ b/src/com/esotericsoftware/reflectasm/ConstructorAccess.java -@@ -91,7 +91,7 @@ - : "com/esotericsoftware/reflectasm/ConstructorAccess"; +@@ -89,7 +89,7 @@ + : "com/esotericsoftware/reflectasm/ConstructorAccess"; - ClassWriter cw = new ClassWriter(0); -- cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null); -+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null); + ClassWriter cw = new ClassWriter(0); +- cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null); ++ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null); - insertConstructor(cw, superclassNameInternal); - insertNewInstance(cw, classNameInternal); + insertConstructor(cw, superclassNameInternal); + insertNewInstance(cw, classNameInternal); --- a/src/com/esotericsoftware/reflectasm/FieldAccess.java +++ b/src/com/esotericsoftware/reflectasm/FieldAccess.java -@@ -148,7 +148,7 @@ - String classNameInternal = className.replace('.', '/'); +@@ -147,7 +147,7 @@ + String classNameInternal = className.replace('.', '/'); - ClassWriter cw = new ClassWriter(0); -- cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, -+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, - "com/esotericsoftware/reflectasm/FieldAccess", null); - insertConstructor(cw); - insertGetObject(cw, classNameInternal, fields); + ClassWriter cw = new ClassWriter(0); +- cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "com/esotericsoftware/reflectasm/FieldAccess", ++ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "com/esotericsoftware/reflectasm/FieldAccess", + null); + insertConstructor(cw); + insertGetObject(cw, classNameInternal, fields); --- a/src/com/esotericsoftware/reflectasm/MethodAccess.java +++ b/src/com/esotericsoftware/reflectasm/MethodAccess.java -@@ -121,7 +121,7 @@ +@@ -120,7 +120,7 @@ - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - MethodVisitor mv; -- cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, -+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, - "com/esotericsoftware/reflectasm/MethodAccess", null); - { - mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + MethodVisitor mv; +- cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "com/esotericsoftware/reflectasm/MethodAccess", ++ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "com/esotericsoftware/reflectasm/MethodAccess", + null); + { + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); diff --git a/debian/patches/javadoc.patch b/debian/patches/javadoc.patch index 55285e7..4d6f80e 100644 --- a/debian/patches/javadoc.patch +++ b/debian/patches/javadoc.patch @@ -5,7 +5,7 @@ Last-Update: 2021-05-17 --- a/pom.xml +++ b/pom.xml -@@ -128,6 +128,11 @@ +@@ -131,6 +131,11 @@ diff --git a/debian/patches/no_shading.patch b/debian/patches/no_shading.patch index 0ec161e..29c6926 100644 --- a/debian/patches/no_shading.patch +++ b/debian/patches/no_shading.patch @@ -5,8 +5,8 @@ Last-Update: 2021-05-17 --- a/pom.xml +++ b/pom.xml -@@ -93,7 +93,7 @@ - +@@ -95,7 +95,7 @@ + - @@ -14,7 +14,7 @@ Last-Update: 2021-05-17 org.apache.maven.plugins maven-shade-plugin 2.3 -@@ -103,9 +103,9 @@ +@@ -105,9 +105,9 @@ shade @@ -26,7 +26,7 @@ Last-Update: 2021-05-17 false -@@ -116,7 +116,7 @@ +@@ -118,7 +118,7 @@ diff --git a/debian/patches/series b/debian/patches/series index abaae31..39f6c46 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,4 +1,6 @@ javadoc.patch no_shading.patch generate_1_6_classes.patch -ignore_test_automatic_unloading.patch +#ignore_test_automatic_unloading.patch +switching_to_OpenJDK17.patch +disable_flaky_assertion.patch diff --git a/debian/patches/switching_to_OpenJDK17.patch b/debian/patches/switching_to_OpenJDK17.patch new file mode 100644 index 0000000..1c83ff4 --- /dev/null +++ b/debian/patches/switching_to_OpenJDK17.patch @@ -0,0 +1,119 @@ +Description: updating ConstructorAccessTest.java to fit the changes in class + loading brought by OpenJDK17. These changes will be in next upstream version. +Author: Pierre Gruet +Origin: upstream, https://github.com/EsotericSoftware/reflectasm/commit/cad4d755114e80169727fd030c85d55db3997bd8 +Bug: https://github.com/EsotericSoftware/reflectasm/issues/89 +Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1012106 +Last-Update: 2022-11-11 + +--- a/test/com/esotericsoftware/reflectasm/ConstructorAccessTest.java ++++ b/test/com/esotericsoftware/reflectasm/ConstructorAccessTest.java +@@ -14,11 +14,21 @@ + + package com.esotericsoftware.reflectasm; + +-import static junit.framework.Assert.assertEquals; +-import static junit.framework.Assert.assertTrue; ++import java.util.List; ++ + import junit.framework.TestCase; + + public class ConstructorAccessTest extends TestCase { ++ static private boolean java17; ++ static { ++ try { ++ Object version = Runtime.class.getDeclaredMethod("version").invoke(null); ++ java17 = ((List)version.getClass().getDeclaredMethod("version").invoke(version)).get(0) >= 17; ++ } catch (Exception ignored) { ++ java17 = false; ++ } ++ } ++ + public void testNewInstance () { + ConstructorAccess access = ConstructorAccess.get(SomeClass.class); + SomeClass someObject = new SomeClass(); +@@ -28,6 +38,7 @@ + } + + public void testPackagePrivateNewInstance () { ++ if (java17) return; + ConstructorAccess access = ConstructorAccess.get(PackagePrivateClass.class); + PackagePrivateClass someObject = new PackagePrivateClass(); + assertEquals(someObject, access.newInstance()); +@@ -44,7 +55,7 @@ + System.out.println("Expected exception happened: " + re); + } + catch (Throwable t) { +- System.out.println("Unexpected exception happened: " + t); ++ t.printStackTrace(); + assertTrue(false); + } + } +@@ -58,31 +69,33 @@ + System.out.println("Expected exception happened: " + re); + } + catch (Throwable t) { +- System.out.println("Unexpected exception happened: " + t); ++ t.printStackTrace(); + assertTrue(false); + } + } + + public void testHasProtectedConstructor () { ++ if (java17) return; + try { + ConstructorAccess access = ConstructorAccess.get(HasProtectedConstructor.class); + HasProtectedConstructor newInstance = access.newInstance(); + assertEquals("cow", newInstance.getMoo()); + } + catch (Throwable t) { +- System.out.println("Unexpected exception happened: " + t); ++ t.printStackTrace(); + assertTrue(false); + } + } + +- public void testHasPackageProtectedConstructor () { ++ public void testHasPackagePrivateConstructor () { ++ if (java17) return; + try { +- ConstructorAccess access = ConstructorAccess.get(HasPackageProtectedConstructor.class); +- HasPackageProtectedConstructor newInstance = access.newInstance(); ++ ConstructorAccess access = ConstructorAccess.get(HasPackagePrivateConstructor.class); ++ HasPackagePrivateConstructor newInstance = access.newInstance(); + assertEquals("cow", newInstance.getMoo()); + } + catch (Throwable t) { +- System.out.println("Unexpected exception happened: " + t); ++ t.printStackTrace(); + assertTrue(false); + } + } +@@ -94,7 +107,7 @@ + assertEquals("cow", newInstance.getMoo()); + } + catch (Throwable t) { +- System.out.println("Unexpected exception happened: " + t); ++ t.printStackTrace(); + assertTrue(false); + } + } +@@ -189,14 +202,14 @@ + } + } + +- static public class HasPackageProtectedConstructor extends HasProtectedConstructor { +- HasPackageProtectedConstructor () { ++ static public class HasPackagePrivateConstructor extends HasProtectedConstructor { ++ HasPackagePrivateConstructor () { + super(); + } + } + +- static public class HasPublicConstructor extends HasPackageProtectedConstructor { +- HasPublicConstructor () { ++ static public class HasPublicConstructor extends HasPackagePrivateConstructor { ++ public HasPublicConstructor () { + super(); + } + } diff --git a/debian/watch b/debian/watch index 4eb2ca1..187036f 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ version=4 opts="repack,repacksuffix=+dfsg,compression=xz,dversionmangle=s/\+dfsg//" \ - https://github.com/EsotericSoftware/reflectasm/releases/latest (?:.*/)?(?:reflectasm-)?([\d\.]+).tar.gz + https://github.com/EsotericSoftware/reflectasm/tags (?:.*/)?(?:reflectasm-)?([\d\.]+).tar.gz diff --git a/pom.xml b/pom.xml index 85d4961..b7cebb5 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 4.0.0 com.esotericsoftware reflectasm - 1.11.6 + 1.11.9 bundle ReflectASM High performance Java reflection using code generation @@ -88,9 +88,11 @@ maven-jar-plugin 2.4 - - **/.svn/* - + + + com.esotericsoftware.reflectasm + + @@ -125,6 +127,7 @@ com.esotericsoftware.reflectasm* + !org.objectweb.asm.* diff --git a/src/com/esotericsoftware/reflectasm/AccessClassLoader.java b/src/com/esotericsoftware/reflectasm/AccessClassLoader.java index b6270c9..4b90b32 100644 --- a/src/com/esotericsoftware/reflectasm/AccessClassLoader.java +++ b/src/com/esotericsoftware/reflectasm/AccessClassLoader.java @@ -102,14 +102,15 @@ static private ClassLoader getParentClassLoader (Class type) { } static private Method getDefineClassMethod () throws Exception { - // DCL on volatile if (defineClassMethod == null) { synchronized (accessClassLoaders) { - defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", - new Class[] {String.class, byte[].class, int.class, int.class, ProtectionDomain.class}); - try { - defineClassMethod.setAccessible(true); - } catch (Exception ignored) { + if (defineClassMethod == null) { + defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", + new Class[] {String.class, byte[].class, int.class, int.class, ProtectionDomain.class}); + try { + defineClassMethod.setAccessible(true); + } catch (Exception ignored) { + } } } } diff --git a/src/com/esotericsoftware/reflectasm/ConstructorAccess.java b/src/com/esotericsoftware/reflectasm/ConstructorAccess.java index 023e742..8cda997 100644 --- a/src/com/esotericsoftware/reflectasm/ConstructorAccess.java +++ b/src/com/esotericsoftware/reflectasm/ConstructorAccess.java @@ -49,57 +49,54 @@ static public ConstructorAccess get (Class type) { String accessClassName = className + "ConstructorAccess"; if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; + Class accessClass; AccessClassLoader loader = AccessClassLoader.get(type); - Class accessClass = loader.loadAccessClass(accessClassName); - if (accessClass == null) { - synchronized (loader) { - accessClass = loader.loadAccessClass(accessClassName); - if (accessClass == null) { - String accessClassNameInternal = accessClassName.replace('.', '/'); - String classNameInternal = className.replace('.', '/'); - String enclosingClassNameInternal; - Constructor constructor = null; - int modifiers = 0; - if (!isNonStaticMemberClass) { - enclosingClassNameInternal = null; - try { - constructor = type.getDeclaredConstructor((Class[])null); - modifiers = constructor.getModifiers(); - } catch (Exception ex) { - throw new RuntimeException("Class cannot be created (missing no-arg constructor): " + type.getName(), ex); - } - if (Modifier.isPrivate(modifiers)) { - throw new RuntimeException("Class cannot be created (the no-arg constructor is private): " + type.getName()); - } - } else { - enclosingClassNameInternal = enclosingType.getName().replace('.', '/'); - try { - constructor = type.getDeclaredConstructor(enclosingType); // Inner classes should have this. - modifiers = constructor.getModifiers(); - } catch (Exception ex) { - throw new RuntimeException( - "Non-static member class cannot be created (missing enclosing class constructor): " + type.getName(), ex); - } - if (Modifier.isPrivate(modifiers)) { - throw new RuntimeException( - "Non-static member class cannot be created (the enclosing class constructor is private): " - + type.getName()); - } + synchronized (loader) { + accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { + String accessClassNameInternal = accessClassName.replace('.', '/'); + String classNameInternal = className.replace('.', '/'); + String enclosingClassNameInternal; + Constructor constructor = null; + int modifiers = 0; + if (!isNonStaticMemberClass) { + enclosingClassNameInternal = null; + try { + constructor = type.getDeclaredConstructor((Class[])null); + modifiers = constructor.getModifiers(); + } catch (Exception ex) { + throw new RuntimeException("Class cannot be created (missing no-arg constructor): " + type.getName(), ex); } - String superclassNameInternal = Modifier.isPublic(modifiers) - ? "com/esotericsoftware/reflectasm/PublicConstructorAccess" - : "com/esotericsoftware/reflectasm/ConstructorAccess"; + if (Modifier.isPrivate(modifiers)) { + throw new RuntimeException("Class cannot be created (the no-arg constructor is private): " + type.getName()); + } + } else { + enclosingClassNameInternal = enclosingType.getName().replace('.', '/'); + try { + constructor = type.getDeclaredConstructor(enclosingType); // Inner classes should have this. + modifiers = constructor.getModifiers(); + } catch (Exception ex) { + throw new RuntimeException( + "Non-static member class cannot be created (missing enclosing class constructor): " + type.getName(), ex); + } + if (Modifier.isPrivate(modifiers)) { + throw new RuntimeException( + "Non-static member class cannot be created (the enclosing class constructor is private): " + type.getName()); + } + } + String superclassNameInternal = Modifier.isPublic(modifiers) + ? "com/esotericsoftware/reflectasm/PublicConstructorAccess" + : "com/esotericsoftware/reflectasm/ConstructorAccess"; - ClassWriter cw = new ClassWriter(0); - cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null); + ClassWriter cw = new ClassWriter(0); + cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null); - insertConstructor(cw, superclassNameInternal); - insertNewInstance(cw, classNameInternal); - insertNewInstanceInner(cw, classNameInternal, enclosingClassNameInternal); + insertConstructor(cw, superclassNameInternal); + insertNewInstance(cw, classNameInternal); + insertNewInstanceInner(cw, classNameInternal, enclosingClassNameInternal); - cw.visitEnd(); - accessClass = loader.defineAccessClass(accessClassName, cw.toByteArray()); - } + cw.visitEnd(); + accessClass = loader.defineAccessClass(accessClassName, cw.toByteArray()); } } ConstructorAccess access; diff --git a/src/com/esotericsoftware/reflectasm/FieldAccess.java b/src/com/esotericsoftware/reflectasm/FieldAccess.java index 7d9246c..acc9ce6 100644 --- a/src/com/esotericsoftware/reflectasm/FieldAccess.java +++ b/src/com/esotericsoftware/reflectasm/FieldAccess.java @@ -138,41 +138,39 @@ static public FieldAccess get (Class type) { String accessClassName = className + "FieldAccess"; if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; + Class accessClass; AccessClassLoader loader = AccessClassLoader.get(type); - Class accessClass = loader.loadAccessClass(accessClassName); - if (accessClass == null) { - synchronized (loader) { - accessClass = loader.loadAccessClass(accessClassName); - if (accessClass == null) { - String accessClassNameInternal = accessClassName.replace('.', '/'); - String classNameInternal = className.replace('.', '/'); - - ClassWriter cw = new ClassWriter(0); - cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, - "com/esotericsoftware/reflectasm/FieldAccess", null); - insertConstructor(cw); - insertGetObject(cw, classNameInternal, fields); - insertSetObject(cw, classNameInternal, fields); - insertGetPrimitive(cw, classNameInternal, fields, Type.BOOLEAN_TYPE); - insertSetPrimitive(cw, classNameInternal, fields, Type.BOOLEAN_TYPE); - insertGetPrimitive(cw, classNameInternal, fields, Type.BYTE_TYPE); - insertSetPrimitive(cw, classNameInternal, fields, Type.BYTE_TYPE); - insertGetPrimitive(cw, classNameInternal, fields, Type.SHORT_TYPE); - insertSetPrimitive(cw, classNameInternal, fields, Type.SHORT_TYPE); - insertGetPrimitive(cw, classNameInternal, fields, Type.INT_TYPE); - insertSetPrimitive(cw, classNameInternal, fields, Type.INT_TYPE); - insertGetPrimitive(cw, classNameInternal, fields, Type.LONG_TYPE); - insertSetPrimitive(cw, classNameInternal, fields, Type.LONG_TYPE); - insertGetPrimitive(cw, classNameInternal, fields, Type.DOUBLE_TYPE); - insertSetPrimitive(cw, classNameInternal, fields, Type.DOUBLE_TYPE); - insertGetPrimitive(cw, classNameInternal, fields, Type.FLOAT_TYPE); - insertSetPrimitive(cw, classNameInternal, fields, Type.FLOAT_TYPE); - insertGetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE); - insertSetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE); - insertGetString(cw, classNameInternal, fields); - cw.visitEnd(); - accessClass = loader.defineAccessClass(accessClassName, cw.toByteArray()); - } + synchronized (loader) { + accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { + String accessClassNameInternal = accessClassName.replace('.', '/'); + String classNameInternal = className.replace('.', '/'); + + ClassWriter cw = new ClassWriter(0); + cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "com/esotericsoftware/reflectasm/FieldAccess", + null); + insertConstructor(cw); + insertGetObject(cw, classNameInternal, fields); + insertSetObject(cw, classNameInternal, fields); + insertGetPrimitive(cw, classNameInternal, fields, Type.BOOLEAN_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.BOOLEAN_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.BYTE_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.BYTE_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.SHORT_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.SHORT_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.INT_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.INT_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.LONG_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.LONG_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.DOUBLE_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.DOUBLE_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.FLOAT_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.FLOAT_TYPE); + insertGetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE); + insertSetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE); + insertGetString(cw, classNameInternal, fields); + cw.visitEnd(); + accessClass = loader.defineAccessClass(accessClassName, cw.toByteArray()); } } try { diff --git a/src/com/esotericsoftware/reflectasm/MethodAccess.java b/src/com/esotericsoftware/reflectasm/MethodAccess.java index 5481411..bd6456f 100644 --- a/src/com/esotericsoftware/reflectasm/MethodAccess.java +++ b/src/com/esotericsoftware/reflectasm/MethodAccess.java @@ -79,11 +79,11 @@ public Class[] getReturnTypes () { } /** Creates a new MethodAccess for the specified type. - * @param type Must not be the Object class, a primitive type, or void. */ + * @param type Must not be a primitive type, or void. */ static public MethodAccess get (Class type) { boolean isInterface = type.isInterface(); - if (!isInterface && type.getSuperclass() == null) - throw new IllegalArgumentException("The type must not be the Object class, an interface, a primitive type, or void."); + if (!isInterface && type.getSuperclass() == null && type != Object.class) + throw new IllegalArgumentException("The type must not be an interface, a primitive type, or void."); ArrayList methods = new ArrayList(); if (!isInterface) { @@ -110,172 +110,170 @@ static public MethodAccess get (Class type) { String accessClassName = className + "MethodAccess"; if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; + Class accessClass; AccessClassLoader loader = AccessClassLoader.get(type); - Class accessClass = loader.loadAccessClass(accessClassName); - if (accessClass == null) { - synchronized (loader) { - accessClass = loader.loadAccessClass(accessClassName); - if (accessClass == null) { - String accessClassNameInternal = accessClassName.replace('.', '/'); - String classNameInternal = className.replace('.', '/'); + synchronized (loader) { + accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { + String accessClassNameInternal = accessClassName.replace('.', '/'); + String classNameInternal = className.replace('.', '/'); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - MethodVisitor mv; - cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, - "com/esotericsoftware/reflectasm/MethodAccess", null); - { - mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESPECIAL, "com/esotericsoftware/reflectasm/MethodAccess", "", "()V"); - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - { - mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invoke", - "(Ljava/lang/Object;I[Ljava/lang/Object;)Ljava/lang/Object;", null, null); - mv.visitCode(); - - if (!methods.isEmpty()) { - mv.visitVarInsn(ALOAD, 1); - mv.visitTypeInsn(CHECKCAST, classNameInternal); - mv.visitVarInsn(ASTORE, 4); - - mv.visitVarInsn(ILOAD, 2); - Label[] labels = new Label[n]; - for (int i = 0; i < n; i++) - labels[i] = new Label(); - Label defaultLabel = new Label(); - mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + MethodVisitor mv; + cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "com/esotericsoftware/reflectasm/MethodAccess", + null); + { + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "com/esotericsoftware/reflectasm/MethodAccess", "", "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invoke", + "(Ljava/lang/Object;I[Ljava/lang/Object;)Ljava/lang/Object;", null, null); + mv.visitCode(); - StringBuilder buffer = new StringBuilder(128); - for (int i = 0; i < n; i++) { - mv.visitLabel(labels[i]); - if (i == 0) - mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {classNameInternal}, 0, null); - else - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - mv.visitVarInsn(ALOAD, 4); + if (!methods.isEmpty()) { + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, classNameInternal); + mv.visitVarInsn(ASTORE, 4); - buffer.setLength(0); - buffer.append('('); + mv.visitVarInsn(ILOAD, 2); + Label[] labels = new Label[n]; + for (int i = 0; i < n; i++) + labels[i] = new Label(); + Label defaultLabel = new Label(); + mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels); - Class[] paramTypes = parameterTypes[i]; - Class returnType = returnTypes[i]; - for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { - mv.visitVarInsn(ALOAD, 3); - mv.visitIntInsn(BIPUSH, paramIndex); - mv.visitInsn(AALOAD); - Type paramType = Type.getType(paramTypes[paramIndex]); - switch (paramType.getSort()) { - case Type.BOOLEAN: - mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"); - break; - case Type.BYTE: - mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B"); - break; - case Type.CHAR: - mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"); - break; - case Type.SHORT: - mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S"); - break; - case Type.INT: - mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I"); - break; - case Type.FLOAT: - mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F"); - break; - case Type.LONG: - mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J"); - break; - case Type.DOUBLE: - mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D"); - break; - case Type.ARRAY: - mv.visitTypeInsn(CHECKCAST, paramType.getDescriptor()); - break; - case Type.OBJECT: - mv.visitTypeInsn(CHECKCAST, paramType.getInternalName()); - break; - } - buffer.append(paramType.getDescriptor()); - } + StringBuilder buffer = new StringBuilder(128); + for (int i = 0; i < n; i++) { + mv.visitLabel(labels[i]); + if (i == 0) + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {classNameInternal}, 0, null); + else + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitVarInsn(ALOAD, 4); - buffer.append(')'); - buffer.append(Type.getDescriptor(returnType)); - int invoke; - if (isInterface) - invoke = INVOKEINTERFACE; - else if (Modifier.isStatic(methods.get(i).getModifiers())) - invoke = INVOKESTATIC; - else - invoke = INVOKEVIRTUAL; - mv.visitMethodInsn(invoke, classNameInternal, methodNames[i], buffer.toString()); + buffer.setLength(0); + buffer.append('('); - switch (Type.getType(returnType).getSort()) { - case Type.VOID: - mv.visitInsn(ACONST_NULL); - break; + Class[] paramTypes = parameterTypes[i]; + Class returnType = returnTypes[i]; + for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { + mv.visitVarInsn(ALOAD, 3); + mv.visitIntInsn(BIPUSH, paramIndex); + mv.visitInsn(AALOAD); + Type paramType = Type.getType(paramTypes[paramIndex]); + switch (paramType.getSort()) { case Type.BOOLEAN: - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z"); break; case Type.BYTE: - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B"); break; case Type.CHAR: - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C"); break; case Type.SHORT: - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S"); break; case Type.INT: - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I"); break; case Type.FLOAT: - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F"); break; case Type.LONG: - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J"); break; case Type.DOUBLE: - mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"); + mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D"); + break; + case Type.ARRAY: + mv.visitTypeInsn(CHECKCAST, paramType.getDescriptor()); + break; + case Type.OBJECT: + mv.visitTypeInsn(CHECKCAST, paramType.getInternalName()); break; } + buffer.append(paramType.getDescriptor()); + } - mv.visitInsn(ARETURN); + buffer.append(')'); + buffer.append(Type.getDescriptor(returnType)); + int invoke; + if (isInterface) + invoke = INVOKEINTERFACE; + else if (Modifier.isStatic(methods.get(i).getModifiers())) + invoke = INVOKESTATIC; + else + invoke = INVOKEVIRTUAL; + mv.visitMethodInsn(invoke, classNameInternal, methodNames[i], buffer.toString()); + + switch (Type.getType(returnType).getSort()) { + case Type.VOID: + mv.visitInsn(ACONST_NULL); + break; + case Type.BOOLEAN: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"); + break; + case Type.BYTE: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"); + break; + case Type.CHAR: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"); + break; + case Type.SHORT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"); + break; + case Type.INT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"); + break; + case Type.FLOAT: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"); + break; + case Type.LONG: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"); + break; + case Type.DOUBLE: + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"); + break; } - mv.visitLabel(defaultLabel); - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitInsn(ARETURN); } - mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); - mv.visitInsn(DUP); - mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); - mv.visitInsn(DUP); - mv.visitLdcInsn("Method not found: "); - mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); - mv.visitVarInsn(ILOAD, 2); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); - mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V"); - mv.visitInsn(ATHROW); - mv.visitMaxs(0, 0); - mv.visitEnd(); + + mv.visitLabel(defaultLabel); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); } - cw.visitEnd(); - byte[] data = cw.toByteArray(); - accessClass = loader.defineAccessClass(accessClassName, data); + mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); + mv.visitInsn(DUP); + mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); + mv.visitInsn(DUP); + mv.visitLdcInsn("Method not found: "); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); + mv.visitVarInsn(ILOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V"); + mv.visitInsn(ATHROW); + mv.visitMaxs(0, 0); + mv.visitEnd(); } + cw.visitEnd(); + byte[] data = cw.toByteArray(); + accessClass = loader.defineAccessClass(accessClassName, data); } } try { diff --git a/test/com/esotericsoftware/reflectasm/ClassLoaderTest.java b/test/com/esotericsoftware/reflectasm/ClassLoaderTest.java index 9ca4889..794240d 100644 --- a/test/com/esotericsoftware/reflectasm/ClassLoaderTest.java +++ b/test/com/esotericsoftware/reflectasm/ClassLoaderTest.java @@ -36,8 +36,9 @@ public void testDifferentClassloaders () throws Exception { assertEquals("first", testObject.toString()); assertEquals("first", access.get(testObject, "name")); } - + public void testAutoUnloadClassloaders () throws Exception { + reclaimLoaders(); int initialCount = AccessClassLoader.activeAccessClassLoaders(); ClassLoader testClassLoader1 = new TestClassLoader1(); @@ -55,12 +56,12 @@ public void testAutoUnloadClassloaders () throws Exception { access2.set(testObject2, "name", "second"); assertEquals("second", testObject2.toString()); assertEquals("second", access2.get(testObject2, "name")); - + assertEquals(access1.getClass().toString(), access2.getClass().toString()); // Same class names assertFalse(access1.getClass().equals(access2.getClass())); // But different classes - - assertEquals(initialCount+2, AccessClassLoader.activeAccessClassLoaders()); - + + assertEquals(initialCount + 2, AccessClassLoader.activeAccessClassLoaders()); + testClassLoader1 = null; testClass1 = null; testObject1 = null; @@ -69,24 +70,28 @@ public void testAutoUnloadClassloaders () throws Exception { testClass2 = null; testObject2 = null; access2 = null; - + + reclaimLoaders(); + + // Yeah, reclaimed! + assertEquals(initialCount, AccessClassLoader.activeAccessClassLoaders()); + } + + private void reclaimLoaders () throws Exception { // Force GC to reclaim unreachable (or only weak-reachable) objects System.gc(); try { - Object[] array = new Object[(int) Runtime.getRuntime().maxMemory()]; + Object[] array = new Object[(int)Runtime.getRuntime().maxMemory()]; System.out.println(array.length); } catch (Throwable e) { // Ignore OME } System.gc(); int times = 0; - while (AccessClassLoader.activeAccessClassLoaders()>1 && times < 50) { // max 5 seconds, should be instant + while (AccessClassLoader.activeAccessClassLoaders() > 1 && times < 50) { // max 5 seconds, should be instant Thread.sleep(100); // test again times++; } - - // Yeah, both reclaimed! - assertEquals(Math.min(initialCount, 1), AccessClassLoader.activeAccessClassLoaders()); } public void testRemoveClassloaders () throws Exception { @@ -107,18 +112,18 @@ public void testRemoveClassloaders () throws Exception { access2.set(testObject2, "name", "second"); assertEquals("second", testObject2.toString()); assertEquals("second", access2.get(testObject2, "name")); - + assertEquals(access1.getClass().toString(), access2.getClass().toString()); // Same class names assertFalse(access1.getClass().equals(access2.getClass())); // But different classes - - assertEquals(initialCount+2, AccessClassLoader.activeAccessClassLoaders()); - + + assertEquals(initialCount + 2, AccessClassLoader.activeAccessClassLoaders()); + AccessClassLoader.remove(testObject1.getClass().getClassLoader()); - assertEquals(initialCount+1, AccessClassLoader.activeAccessClassLoaders()); + assertEquals(initialCount + 1, AccessClassLoader.activeAccessClassLoaders()); AccessClassLoader.remove(testObject2.getClass().getClassLoader()); - assertEquals(initialCount+0, AccessClassLoader.activeAccessClassLoaders()); + assertEquals(initialCount + 0, AccessClassLoader.activeAccessClassLoaders()); AccessClassLoader.remove(this.getClass().getClassLoader()); - assertEquals(initialCount-1, AccessClassLoader.activeAccessClassLoaders()); + assertEquals(initialCount - 1, AccessClassLoader.activeAccessClassLoaders()); } static public class Test { @@ -128,7 +133,7 @@ public String toString () { return name; } } - + static public class TestClassLoader1 extends ClassLoader { protected synchronized Class loadClass (String name, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name);