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 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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, Class<?> neighbor,
  33. ClassLoader loader, ProtectionDomain protectionDomain)
  34. throws ClassFormatError, CannotCompileException;
  35. }
  36. private static class Java11 extends JavaOther {
  37. Class<?> defineClass(String name, byte[] bcode, int off, int len, Class<?> neighbor,
  38. ClassLoader loader, ProtectionDomain protectionDomain)
  39. throws ClassFormatError, CannotCompileException
  40. {
  41. if (neighbor != null)
  42. return toClass(neighbor, bcode);
  43. else {
  44. // Lookup#defineClass() is not available. So fallback to invoking defineClass on
  45. // ClassLoader, which causes a warning message.
  46. return super.defineClass(name, bcode, off, len, neighbor, loader, protectionDomain);
  47. }
  48. }
  49. }
  50. private static class Java9 extends Helper {
  51. final class ReferencedUnsafe {
  52. private final SecurityActions.TheUnsafe sunMiscUnsafeTheUnsafe;
  53. private final MethodHandle defineClass;
  54. ReferencedUnsafe(SecurityActions.TheUnsafe usf, MethodHandle meth) {
  55. this.sunMiscUnsafeTheUnsafe = usf;
  56. this.defineClass = meth;
  57. }
  58. Class<?> defineClass(String name, byte[] b, int off, int len,
  59. ClassLoader loader, ProtectionDomain protectionDomain)
  60. throws ClassFormatError
  61. {
  62. try {
  63. if (getCallerClass.invoke(stack) != Java9.class)
  64. throw new IllegalAccessError("Access denied for caller.");
  65. } catch (Exception e) {
  66. throw new RuntimeException("cannot initialize", e);
  67. }
  68. try {
  69. return (Class<?>) defineClass.invokeWithArguments(
  70. sunMiscUnsafeTheUnsafe.theUnsafe,
  71. name, b, off, len, loader, protectionDomain);
  72. } catch (Throwable e) {
  73. if (e instanceof RuntimeException) throw (RuntimeException) e;
  74. if (e instanceof ClassFormatError) throw (ClassFormatError) e;
  75. throw new ClassFormatError(e.getMessage());
  76. }
  77. }
  78. }
  79. private final Object stack;
  80. private final Method getCallerClass;
  81. private final ReferencedUnsafe sunMiscUnsafe = getReferencedUnsafe();
  82. Java9 () {
  83. Class<?> stackWalkerClass = null;
  84. try {
  85. stackWalkerClass = Class.forName("java.lang.StackWalker");
  86. } catch (ClassNotFoundException e) {
  87. // Skip initialization when the class doesn't exist i.e. we are on JDK < 9
  88. }
  89. if (stackWalkerClass != null) {
  90. try {
  91. Class<?> optionClass = Class.forName("java.lang.StackWalker$Option");
  92. stack = stackWalkerClass.getMethod("getInstance", optionClass)
  93. // The first one is RETAIN_CLASS_REFERENCE
  94. .invoke(null, optionClass.getEnumConstants()[0]);
  95. getCallerClass = stackWalkerClass.getMethod("getCallerClass");
  96. } catch (Throwable e) {
  97. throw new RuntimeException("cannot initialize", e);
  98. }
  99. } else {
  100. stack = null;
  101. getCallerClass = null;
  102. }
  103. }
  104. private final ReferencedUnsafe getReferencedUnsafe() {
  105. try {
  106. if (privileged != null && getCallerClass.invoke(stack) != this.getClass())
  107. throw new IllegalAccessError("Access denied for caller.");
  108. } catch (Exception e) {
  109. throw new RuntimeException("cannot initialize", e);
  110. }
  111. try {
  112. SecurityActions.TheUnsafe usf = SecurityActions.getSunMiscUnsafeAnonymously();
  113. List<Method> defineClassMethod = usf.methods.get("defineClass");
  114. // On Java 11+ the defineClass method does not exist anymore
  115. if (null == defineClassMethod)
  116. return null;
  117. MethodHandle meth = MethodHandles.lookup().unreflect(defineClassMethod.get(0));
  118. return new ReferencedUnsafe(usf, meth);
  119. } catch (Throwable e) {
  120. throw new RuntimeException("cannot initialize", e);
  121. }
  122. }
  123. @Override
  124. Class<?> defineClass(String name, byte[] b, int off, int len, Class<?> neighbor,
  125. ClassLoader loader, ProtectionDomain protectionDomain)
  126. throws ClassFormatError
  127. {
  128. try {
  129. if (getCallerClass.invoke(stack) != DefineClassHelper.class)
  130. throw new IllegalAccessError("Access denied for caller.");
  131. } catch (Exception e) {
  132. throw new RuntimeException("cannot initialize", e);
  133. }
  134. return sunMiscUnsafe.defineClass(name, b, off, len, loader,
  135. protectionDomain);
  136. }
  137. }
  138. private static class Java7 extends Helper {
  139. private final SecurityActions stack = SecurityActions.stack;
  140. private final MethodHandle defineClass = getDefineClassMethodHandle();
  141. private final MethodHandle getDefineClassMethodHandle() {
  142. if (privileged != null && stack.getCallerClass() != this.getClass())
  143. throw new IllegalAccessError("Access denied for caller.");
  144. try {
  145. return SecurityActions.getMethodHandle(ClassLoader.class, "defineClass",
  146. new Class[] {
  147. String.class, byte[].class, int.class, int.class,
  148. ProtectionDomain.class
  149. });
  150. } catch (NoSuchMethodException e) {
  151. throw new RuntimeException("cannot initialize", e);
  152. }
  153. }
  154. @Override
  155. Class<?> defineClass(String name, byte[] b, int off, int len, Class<?> neighbor,
  156. ClassLoader loader, ProtectionDomain protectionDomain)
  157. throws ClassFormatError
  158. {
  159. if (stack.getCallerClass() != DefineClassHelper.class)
  160. throw new IllegalAccessError("Access denied for caller.");
  161. try {
  162. return (Class<?>) defineClass.invokeWithArguments(
  163. loader, name, b, off, len, protectionDomain);
  164. } catch (Throwable e) {
  165. if (e instanceof RuntimeException) throw (RuntimeException) e;
  166. if (e instanceof ClassFormatError) throw (ClassFormatError) e;
  167. throw new ClassFormatError(e.getMessage());
  168. }
  169. }
  170. }
  171. private static class JavaOther extends Helper {
  172. private final Method defineClass = getDefineClassMethod();
  173. private final SecurityActions stack = SecurityActions.stack;
  174. private final Method getDefineClassMethod() {
  175. if (privileged != null && stack.getCallerClass() != this.getClass())
  176. throw new IllegalAccessError("Access denied for caller.");
  177. try {
  178. return SecurityActions.getDeclaredMethod(ClassLoader.class, "defineClass",
  179. new Class[] {
  180. String.class, byte[].class, int.class, int.class, ProtectionDomain.class
  181. });
  182. } catch (NoSuchMethodException e) {
  183. throw new RuntimeException("cannot initialize", e);
  184. }
  185. }
  186. @Override
  187. Class<?> defineClass(String name, byte[] b, int off, int len, Class<?> neighbor,
  188. ClassLoader loader, ProtectionDomain protectionDomain)
  189. throws ClassFormatError, CannotCompileException
  190. {
  191. Class<?> klass = stack.getCallerClass();
  192. if (klass != DefineClassHelper.class && klass != this.getClass())
  193. throw new IllegalAccessError("Access denied for caller.");
  194. try {
  195. SecurityActions.setAccessible(defineClass, true);
  196. return (Class<?>) defineClass.invoke(loader, new Object[] {
  197. name, b, off, len, protectionDomain
  198. });
  199. } catch (Throwable e) {
  200. if (e instanceof ClassFormatError) throw (ClassFormatError) e;
  201. if (e instanceof RuntimeException) throw (RuntimeException) e;
  202. throw new CannotCompileException(e);
  203. }
  204. }
  205. }
  206. // Java 11+ removed sun.misc.Unsafe.defineClass, so we fallback to invoking defineClass on
  207. // ClassLoader until we have an implementation that uses MethodHandles.Lookup.defineClass
  208. private static final Helper privileged = ClassFile.MAJOR_VERSION > ClassFile.JAVA_10
  209. ? new Java11()
  210. : ClassFile.MAJOR_VERSION >= ClassFile.JAVA_9
  211. ? new Java9()
  212. : ClassFile.MAJOR_VERSION >= ClassFile.JAVA_7 ? new Java7() : new JavaOther();
  213. /**
  214. * Loads a class file by a given class loader.
  215. *
  216. * <p>This first tries to use {@code java.lang.invoke.MethodHandle} to load a class.
  217. * Otherwise, or if {@code neighbor} is null,
  218. * this tries to use {@code sun.misc.Unsafe} to load a class.
  219. * Then it tries to use a {@code protected} method in {@code java.lang.ClassLoader}
  220. * via {@code PrivilegedAction}. Since the latter approach is not available
  221. * any longer by default in Java 9 or later, the JVM argument
  222. * {@code --add-opens java.base/java.lang=ALL-UNNAMED} must be given to the JVM.
  223. * If this JVM argument cannot be given, {@link #toPublicClass(String,byte[])}
  224. * should be used instead.
  225. * </p>
  226. *
  227. * @param className the name of the loaded class.
  228. * @param neighbor the class contained in the same package as the loaded class.
  229. * @param loader the class loader. It can be null if {@code neighbor} is not null
  230. * and the JVM is Java 11 or later.
  231. * @param domain if it is null, a default domain is used.
  232. * @param bcode the bytecode for the loaded class.
  233. * @since 3.22
  234. */
  235. public static Class<?> toClass(String className, Class<?> neighbor, ClassLoader loader,
  236. ProtectionDomain domain, byte[] bcode)
  237. throws CannotCompileException
  238. {
  239. try {
  240. return privileged.defineClass(className, bcode, 0, bcode.length,
  241. neighbor, loader, domain);
  242. }
  243. catch (RuntimeException e) {
  244. throw e;
  245. }
  246. catch (CannotCompileException e) {
  247. throw e;
  248. }
  249. catch (ClassFormatError e) {
  250. Throwable t = e.getCause();
  251. throw new CannotCompileException(t == null ? e : t);
  252. }
  253. catch (Exception e) {
  254. throw new CannotCompileException(e);
  255. }
  256. }
  257. /**
  258. * Loads a class file by {@code java.lang.invoke.MethodHandles.Lookup}.
  259. * It is obtained by using {@code neighbor}.
  260. *
  261. * @param neighbor a class belonging to the same package that the loaded
  262. * class belogns to.
  263. * @param bcode the bytecode.
  264. * @since 3.24
  265. */
  266. public static Class<?> toClass(Class<?> neighbor, byte[] bcode)
  267. throws CannotCompileException
  268. {
  269. try {
  270. DefineClassHelper.class.getModule().addReads(neighbor.getModule());
  271. Lookup lookup = MethodHandles.lookup();
  272. Lookup prvlookup = MethodHandles.privateLookupIn(neighbor, lookup);
  273. return prvlookup.defineClass(bcode);
  274. } catch (IllegalAccessException | IllegalArgumentException e) {
  275. throw new CannotCompileException(e.getMessage() + ": " + neighbor.getName()
  276. + " has no permission to define the class");
  277. }
  278. }
  279. /**
  280. * Loads a class file by {@code java.lang.invoke.MethodHandles.Lookup}.
  281. * It can be obtained by {@code MethodHandles.lookup()} called from
  282. * somewhere in the package that the loaded class belongs to.
  283. *
  284. * @param bcode the bytecode.
  285. * @since 3.24
  286. */
  287. public static Class<?> toClass(Lookup lookup, byte[] bcode)
  288. throws CannotCompileException
  289. {
  290. try {
  291. return lookup.defineClass(bcode);
  292. } catch (IllegalAccessException | IllegalArgumentException e) {
  293. throw new CannotCompileException(e.getMessage());
  294. }
  295. }
  296. /**
  297. * Loads a class file by {@code java.lang.invoke.MethodHandles.Lookup}.
  298. *
  299. * @since 3.22
  300. */
  301. static Class<?> toPublicClass(String className, byte[] bcode)
  302. throws CannotCompileException
  303. {
  304. try {
  305. Lookup lookup = MethodHandles.lookup();
  306. lookup = lookup.dropLookupMode(java.lang.invoke.MethodHandles.Lookup.PRIVATE);
  307. return lookup.defineClass(bcode);
  308. }
  309. catch (Throwable t) {
  310. throw new CannotCompileException(t);
  311. }
  312. }
  313. private DefineClassHelper() {}
  314. }