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