You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DefineClassHelper.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. * Javassist, a Java-bytecode translator toolkit.
  3. * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. Alternatively, the contents of this file may be used under
  8. * the terms of the GNU Lesser General Public License Version 2.1 or later,
  9. * or the Apache License Version 2.0.
  10. *
  11. * Software distributed under the License is distributed on an "AS IS" basis,
  12. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13. * for the specific language governing rights and limitations under the
  14. * License.
  15. */
  16. package javassist.util.proxy;
  17. import java.lang.invoke.MethodHandle;
  18. import java.lang.invoke.MethodHandles;
  19. import java.lang.invoke.MethodHandles.Lookup;
  20. import java.lang.reflect.Method;
  21. import java.security.ProtectionDomain;
  22. import java.util.List;
  23. import javassist.CannotCompileException;
  24. import javassist.bytecode.ClassFile;
  25. /**
  26. * Helper class for invoking {@link ClassLoader#defineClass(String,byte[],int,int)}.
  27. *
  28. * @since 3.22
  29. */
  30. public class DefineClassHelper {
  31. private static abstract class Helper {
  32. abstract Class<?> defineClass(String name, byte[] b, int off, int len,
  33. ClassLoader loader, ProtectionDomain protectionDomain)
  34. throws ClassFormatError;
  35. }
  36. private static class Java9 extends Helper {
  37. final class ReferencedUnsafe {
  38. private final SecurityActions.TheUnsafe sunMiscUnsafeTheUnsafe;
  39. private final MethodHandle defineClass;
  40. ReferencedUnsafe(SecurityActions.TheUnsafe usf, MethodHandle meth) {
  41. this.sunMiscUnsafeTheUnsafe = usf;
  42. this.defineClass = meth;
  43. }
  44. Class<?> defineClass(String name, byte[] b, int off, int len,
  45. ClassLoader loader, ProtectionDomain protectionDomain)
  46. throws ClassFormatError
  47. {
  48. try {
  49. if (getCallerClass.invoke(stack) != Java9.class)
  50. throw new IllegalAccessError("Access denied for caller.");
  51. } catch (Exception e) {
  52. throw new RuntimeException("cannot initialize", e);
  53. }
  54. try {
  55. return (Class<?>) defineClass.invokeWithArguments(
  56. sunMiscUnsafeTheUnsafe.theUnsafe,
  57. name, b, off, len, loader, protectionDomain);
  58. } catch (Throwable e) {
  59. if (e instanceof RuntimeException) throw (RuntimeException) e;
  60. if (e instanceof ClassFormatError) throw (ClassFormatError) e;
  61. throw new ClassFormatError(e.getMessage());
  62. }
  63. }
  64. }
  65. private final Object stack;
  66. private final Method getCallerClass;
  67. private final ReferencedUnsafe sunMiscUnsafe = getReferencedUnsafe();
  68. Java9 () {
  69. Class<?> stackWalkerClass = null;
  70. try {
  71. stackWalkerClass = Class.forName("java.lang.StackWalker");
  72. } catch (ClassNotFoundException e) {
  73. // Skip initialization when the class doesn't exist i.e. we are on JDK < 9
  74. }
  75. if (stackWalkerClass != null) {
  76. try {
  77. Class<?> optionClass = Class.forName("java.lang.StackWalker$Option");
  78. stack = stackWalkerClass.getMethod("getInstance", optionClass)
  79. // The first one is RETAIN_CLASS_REFERENCE
  80. .invoke(null, optionClass.getEnumConstants()[0]);
  81. getCallerClass = stackWalkerClass.getMethod("getCallerClass");
  82. } catch (Throwable e) {
  83. throw new RuntimeException("cannot initialize", e);
  84. }
  85. } else {
  86. stack = null;
  87. getCallerClass = null;
  88. }
  89. }
  90. private final ReferencedUnsafe getReferencedUnsafe() {
  91. try {
  92. if (privileged != null && getCallerClass.invoke(stack) != this.getClass())
  93. throw new IllegalAccessError("Access denied for caller.");
  94. } catch (Exception e) {
  95. throw new RuntimeException("cannot initialize", e);
  96. }
  97. try {
  98. SecurityActions.TheUnsafe usf = SecurityActions.getSunMiscUnsafeAnonymously();
  99. List<Method> defineClassMethod = usf.methods.get("defineClass");
  100. // On Java 11+ the defineClass method does not exist anymore
  101. if (null == defineClassMethod)
  102. return null;
  103. MethodHandle meth = MethodHandles.lookup().unreflect(defineClassMethod.get(0));
  104. return new ReferencedUnsafe(usf, meth);
  105. } catch (Throwable e) {
  106. throw new RuntimeException("cannot initialize", e);
  107. }
  108. }
  109. @Override
  110. Class<?> defineClass(String name, byte[] b, int off, int len,
  111. ClassLoader loader, ProtectionDomain protectionDomain)
  112. throws ClassFormatError
  113. {
  114. try {
  115. if (getCallerClass.invoke(stack) != DefineClassHelper.class)
  116. throw new IllegalAccessError("Access denied for caller.");
  117. } catch (Exception e) {
  118. throw new RuntimeException("cannot initialize", e);
  119. }
  120. return sunMiscUnsafe.defineClass(name, b, off, len, loader,
  121. protectionDomain);
  122. }
  123. }
  124. private static class Java7 extends Helper {
  125. private final SecurityActions stack = SecurityActions.stack;
  126. private final MethodHandle defineClass = getDefineClassMethodHandle();
  127. private final MethodHandle getDefineClassMethodHandle() {
  128. if (privileged != null && stack.getCallerClass() != this.getClass())
  129. throw new IllegalAccessError("Access denied for caller.");
  130. try {
  131. return SecurityActions.getMethodHandle(ClassLoader.class, "defineClass",
  132. new Class[] {
  133. String.class, byte[].class, int.class, int.class,
  134. ProtectionDomain.class
  135. });
  136. } catch (NoSuchMethodException e) {
  137. throw new RuntimeException("cannot initialize", e);
  138. }
  139. }
  140. @Override
  141. Class<?> defineClass(String name, byte[] b, int off, int len,
  142. ClassLoader loader, ProtectionDomain protectionDomain)
  143. throws ClassFormatError
  144. {
  145. if (stack.getCallerClass() != DefineClassHelper.class)
  146. throw new IllegalAccessError("Access denied for caller.");
  147. try {
  148. return (Class<?>) defineClass.invokeWithArguments(
  149. loader, name, b, off, len, protectionDomain);
  150. } catch (Throwable e) {
  151. if (e instanceof RuntimeException) throw (RuntimeException) e;
  152. if (e instanceof ClassFormatError) throw (ClassFormatError) e;
  153. throw new ClassFormatError(e.getMessage());
  154. }
  155. }
  156. }
  157. private static class JavaOther extends Helper {
  158. private final Method defineClass = getDefineClassMethod();
  159. private final SecurityActions stack = SecurityActions.stack;
  160. private final Method getDefineClassMethod() {
  161. if (privileged != null && stack.getCallerClass() != this.getClass())
  162. throw new IllegalAccessError("Access denied for caller.");
  163. try {
  164. return SecurityActions.getDeclaredMethod(ClassLoader.class, "defineClass",
  165. new Class[] {
  166. String.class, byte[].class, int.class, int.class, ProtectionDomain.class
  167. });
  168. } catch (NoSuchMethodException e) {
  169. throw new RuntimeException("cannot initialize", e);
  170. }
  171. }
  172. @Override
  173. Class<?> defineClass(String name, byte[] b, int off, int len,
  174. ClassLoader loader, ProtectionDomain protectionDomain)
  175. throws ClassFormatError
  176. {
  177. if (stack.getCallerClass() != DefineClassHelper.class)
  178. throw new IllegalAccessError("Access denied for caller.");
  179. try {
  180. SecurityActions.setAccessible(defineClass, true);
  181. return (Class<?>) defineClass.invoke(loader, new Object[] {
  182. name, b, off, len, protectionDomain
  183. });
  184. } catch (Throwable e) {
  185. if (e instanceof ClassFormatError) throw (ClassFormatError) e;
  186. if (e instanceof RuntimeException) throw (RuntimeException) e;
  187. throw new ClassFormatError(e.getMessage());
  188. }
  189. finally {
  190. SecurityActions.setAccessible(defineClass, false);
  191. }
  192. }
  193. }
  194. // Java 11+ removed sun.misc.Unsafe.defineClass, so we fallback to invoking defineClass on
  195. // ClassLoader until we have an implementation that uses MethodHandles.Lookup.defineClass
  196. private static final Helper privileged = ClassFile.MAJOR_VERSION > ClassFile.JAVA_10
  197. ? new JavaOther()
  198. : ClassFile.MAJOR_VERSION >= ClassFile.JAVA_9
  199. ? new Java9()
  200. : ClassFile.MAJOR_VERSION >= ClassFile.JAVA_7 ? new Java7() : new JavaOther();
  201. /**
  202. * Loads a class file by a given class loader.
  203. *
  204. * <p>This first tries to use {@code sun.misc.Unsafe} to load a class.
  205. * Then it tries to use a {@code protected} method in {@code java.lang.ClassLoader}
  206. * via {@code PrivilegedAction}. Since the latter approach is not available
  207. * any longer by default in Java 9 or later, the JVM argument
  208. * {@code --add-opens java.base/java.lang=ALL-UNNAMED} must be given to the JVM.
  209. * If this JVM argument cannot be given, {@link #toPublicClass(String,byte[])}
  210. * should be used instead.
  211. * </p>
  212. *
  213. * @param domain if it is null, a default domain is used.
  214. * @since 3.22
  215. */
  216. public static Class<?> toClass(String className, ClassLoader loader,
  217. ProtectionDomain domain, byte[] bcode)
  218. throws CannotCompileException
  219. {
  220. try {
  221. return privileged.defineClass(className, bcode, 0, bcode.length, loader, domain);
  222. }
  223. catch (RuntimeException e) {
  224. throw e;
  225. }
  226. catch (ClassFormatError e) {
  227. Throwable t = e.getCause();
  228. throw new CannotCompileException(t == null ? e : t);
  229. }
  230. catch (Exception e) {
  231. throw new CannotCompileException(e);
  232. }
  233. }
  234. /**
  235. * Loads a class file by {@code java.lang.invoke.MethodHandles.Lookup}.
  236. *
  237. * @since 3.22
  238. */
  239. static Class<?> toPublicClass(String className, byte[] bcode)
  240. throws CannotCompileException
  241. {
  242. try {
  243. Lookup lookup = MethodHandles.lookup();
  244. lookup = lookup.dropLookupMode(java.lang.invoke.MethodHandles.Lookup.PRIVATE);
  245. return lookup.defineClass(bcode);
  246. }
  247. catch (Throwable t) {
  248. throw new CannotCompileException(t);
  249. }
  250. }
  251. private DefineClassHelper() {}
  252. }