1 /* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.bind.v2.runtime.reflect.opt; 27 28 import java.lang.reflect.InvocationTargetException; 29 import java.lang.reflect.Method; 30 import java.lang.ref.WeakReference; 31 import java.security.AccessController; 32 import java.security.PrivilegedAction; 33 import java.util.concurrent.locks.Lock; 34 import java.util.concurrent.locks.ReentrantReadWriteLock; 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.WeakHashMap; 38 import java.util.logging.Level; 39 import java.util.logging.Logger; 40 41 import com.sun.xml.internal.bind.Util; 42 import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor; 43 import java.lang.reflect.Field; 44 import java.security.CodeSource; 45 import java.security.PrivilegedActionException; 46 import java.security.PrivilegedExceptionAction; 47 import java.security.ProtectionDomain; 48 49 /** 50 * A {@link ClassLoader} used to "inject" optimized accessor classes 51 * into the VM. 52 * 53 * <p> 54 * Its parent class loader needs to be set to the one that can see the user 55 * class. 56 * 57 * @author Kohsuke Kawaguchi 58 */ 59 final class Injector { 60 61 /** 62 * {@link Injector}s keyed by their parent {@link ClassLoader}. 63 * 64 * We only need one injector per one user class loader. 65 */ 66 private static final ReentrantReadWriteLock irwl = new ReentrantReadWriteLock(); 67 private static final Lock ir = irwl.readLock(); 68 private static final Lock iw = irwl.writeLock(); 69 private static final Map<ClassLoader, WeakReference<Injector>> injectors = 70 new WeakHashMap<ClassLoader, WeakReference<Injector>>(); 71 private static final Logger logger = Util.getClassLogger(); 72 73 /** 74 * Injects a new class into the given class loader. 75 * 76 * @return null 77 * if it fails to inject. 78 */ 79 static Class inject(ClassLoader cl, String className, byte[] image) { 80 Injector injector = get(cl); 81 if (injector != null) { 82 return injector.inject(className, image); 83 } else { 84 return null; 85 } 86 } 87 88 /** 89 * Returns the already injected class, or null. 90 */ 91 static Class find(ClassLoader cl, String className) { 92 Injector injector = get(cl); 93 if (injector != null) { 94 return injector.find(className); 95 } else { 96 return null; 97 } 98 } 99 100 /** 101 * Gets or creates an {@link Injector} for the given class loader. 102 * 103 * @return null 104 * if it fails. 105 */ 106 private static Injector get(ClassLoader cl) { 107 Injector injector = null; 108 WeakReference<Injector> wr; 109 ir.lock(); 110 try { 111 wr = injectors.get(cl); 112 } finally { 113 ir.unlock(); 114 } 115 if (wr != null) { 116 injector = wr.get(); 117 } 118 if (injector == null) { 119 try { 120 wr = new WeakReference<Injector>(injector = new Injector(cl)); 121 iw.lock(); 122 try { 123 if (!injectors.containsKey(cl)) { 124 injectors.put(cl, wr); 125 } 126 } finally { 127 iw.unlock(); 128 } 129 } catch (SecurityException e) { 130 logger.log(Level.FINE, "Unable to set up a back-door for the injector", e); 131 return null; 132 } 133 } 134 return injector; 135 } 136 /** 137 * Injected classes keyed by their names. 138 */ 139 private final Map<String, Class> classes = new HashMap<>(); 140 private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 141 private final Lock r = rwl.readLock(); 142 private final Lock w = rwl.writeLock(); 143 private final ClassLoader parent; 144 /** 145 * True if this injector is capable of injecting accessors. 146 * False otherwise, which happens if this classloader can't see {@link Accessor}. 147 */ 148 private final boolean loadable; 149 private static Method defineClass; 150 private static Method resolveClass; 151 private static Method findLoadedClass; 152 private static Object U; 153 154 static { 155 try { 156 Method[] m = AccessController.doPrivileged( 157 new PrivilegedAction<Method[]>() { 158 @Override 159 public Method[] run() { 160 return new Method[]{ 161 getMethod(ClassLoader.class, "defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE), 162 getMethod(ClassLoader.class, "resolveClass", Class.class), 163 getMethod(ClassLoader.class, "findLoadedClass", String.class) 164 }; 165 } 166 } 167 ); 168 defineClass = m[0]; 169 resolveClass = m[1]; 170 findLoadedClass = m[2]; 171 } catch (Throwable t) { 172 try { 173 U = AccessController.doPrivileged(new PrivilegedExceptionAction() { 174 @Override 175 public Object run() throws Exception { 176 Class u = Class.forName("sun.misc.Unsafe"); 177 Field theUnsafe = u.getDeclaredField("theUnsafe"); 178 theUnsafe.setAccessible(true); 179 return theUnsafe.get(null); 180 } 181 }); 182 defineClass = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() { 183 @Override 184 public Method run() throws Exception { 185 try { 186 return U.getClass().getMethod("defineClass", 187 new Class[]{String.class, 188 byte[].class, 189 Integer.TYPE, 190 Integer.TYPE, 191 ClassLoader.class, 192 ProtectionDomain.class}); 193 } catch (NoSuchMethodException | SecurityException ex) { 194 throw ex; 195 } 196 } 197 }); 198 } catch (SecurityException | PrivilegedActionException ex) { 199 Logger.getLogger(Injector.class.getName()).log(Level.SEVERE, null, ex); 200 } 201 } 202 } 203 204 private static Method getMethod(final Class<?> c, final String methodname, final Class<?>... params) { 205 try { 206 Method m = c.getDeclaredMethod(methodname, params); 207 m.setAccessible(true); 208 return m; 209 } catch (NoSuchMethodException e) { 210 throw new NoSuchMethodError(e.getMessage()); 211 } 212 } 213 214 private Injector(ClassLoader parent) { 215 this.parent = parent; 216 assert parent != null; 217 218 boolean loadableCheck = false; 219 220 try { 221 loadableCheck = parent.loadClass(Accessor.class.getName()) == Accessor.class; 222 } catch (ClassNotFoundException e) { 223 // not loadable 224 } 225 226 this.loadable = loadableCheck; 227 } 228 229 @SuppressWarnings("LockAcquiredButNotSafelyReleased") 230 private Class inject(String className, byte[] image) { 231 if (!loadable) // this injector cannot inject anything 232 { 233 return null; 234 } 235 236 boolean wlocked = false; 237 boolean rlocked = false; 238 try { 239 240 r.lock(); 241 rlocked = true; 242 243 Class c = classes.get(className); 244 245 // Unlock now during the findLoadedClass process to avoid 246 // deadlocks 247 r.unlock(); 248 rlocked = false; 249 250 //find loaded class from classloader 251 if (c == null && findLoadedClass != null) { 252 253 try { 254 c = (Class) findLoadedClass.invoke(parent, className.replace('/', '.')); 255 } catch (IllegalArgumentException | IllegalAccessException e) { 256 logger.log(Level.FINE, "Unable to find " + className, e); 257 } catch (InvocationTargetException e) { 258 Throwable t = e.getTargetException(); 259 logger.log(Level.FINE, "Unable to find " + className, t); 260 } 261 262 if (c != null) { 263 264 w.lock(); 265 wlocked = true; 266 267 classes.put(className, c); 268 269 w.unlock(); 270 wlocked = false; 271 272 return c; 273 } 274 } 275 276 if (c == null) { 277 278 r.lock(); 279 rlocked = true; 280 281 c = classes.get(className); 282 283 // Unlock now during the define/resolve process to avoid 284 // deadlocks 285 r.unlock(); 286 rlocked = false; 287 288 if (c == null) { 289 290 // we need to inject a class into the 291 try { 292 if (resolveClass != null) { 293 c = (Class) defineClass.invoke(parent, className.replace('/', '.'), image, 0, image.length); 294 resolveClass.invoke(parent, c); 295 } else { 296 c = (Class) defineClass.invoke(U, className.replace('/', '.'), image, 0, image.length, parent, Injector.class.getProtectionDomain()); 297 } 298 } catch (IllegalAccessException e) { 299 logger.log(Level.FINE, "Unable to inject " + className, e); 300 return null; 301 } catch (InvocationTargetException e) { 302 Throwable t = e.getTargetException(); 303 if (t instanceof LinkageError) { 304 logger.log(Level.FINE, "duplicate class definition bug occured? Please report this : " + className, t); 305 } else { 306 logger.log(Level.FINE, "Unable to inject " + className, t); 307 } 308 return null; 309 } catch (SecurityException e) { 310 logger.log(Level.FINE, "Unable to inject " + className, e); 311 return null; 312 } catch (LinkageError e) { 313 logger.log(Level.FINE, "Unable to inject " + className, e); 314 return null; 315 } 316 317 w.lock(); 318 wlocked = true; 319 320 // During the time we were unlocked, we could have tried to 321 // load the class from more than one thread. Check now to see 322 // if someone else beat us to registering this class 323 if (!classes.containsKey(className)) { 324 classes.put(className, c); 325 } 326 327 w.unlock(); 328 wlocked = false; 329 } 330 } 331 return c; 332 } finally { 333 if (rlocked) { 334 r.unlock(); 335 } 336 if (wlocked) { 337 w.unlock(); 338 } 339 } 340 } 341 342 private Class find(String className) { 343 r.lock(); 344 try { 345 return classes.get(className); 346 } finally { 347 r.unlock(); 348 } 349 } 350 }