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