1 /* 2 * Copyright (c) 2016 Mockito contributors 3 * This program is made available under the terms of the MIT License. 4 */ 5 package org.mockito.internal.creation.bytebuddy; 6 7 import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer; 8 import static org.mockito.internal.util.StringUtil.join; 9 10 import java.lang.reflect.Field; 11 import java.lang.reflect.Method; 12 import java.util.Random; 13 14 import net.bytebuddy.ByteBuddy; 15 import net.bytebuddy.description.modifier.Ownership; 16 import net.bytebuddy.description.modifier.Visibility; 17 import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; 18 import net.bytebuddy.implementation.Implementation; 19 import net.bytebuddy.implementation.MethodCall; 20 import net.bytebuddy.implementation.StubMethod; 21 import org.mockito.codegen.InjectionBase; 22 import org.mockito.exceptions.base.MockitoException; 23 24 abstract class ModuleHandler { 25 isOpened(Class<?> source, Class<?> target)26 abstract boolean isOpened(Class<?> source, Class<?> target); 27 canRead(Class<?> source, Class<?> target)28 abstract boolean canRead(Class<?> source, Class<?> target); 29 isExported(Class<?> source)30 abstract boolean isExported(Class<?> source); 31 isExported(Class<?> source, Class<?> target)32 abstract boolean isExported(Class<?> source, Class<?> target); 33 injectionBase(ClassLoader classLoader, String tyoeName)34 abstract Class<?> injectionBase(ClassLoader classLoader, String tyoeName); 35 adjustModuleGraph(Class<?> source, Class<?> target, boolean export, boolean read)36 abstract void adjustModuleGraph(Class<?> source, Class<?> target, boolean export, boolean read); 37 make(ByteBuddy byteBuddy, SubclassLoader loader, Random random)38 static ModuleHandler make(ByteBuddy byteBuddy, SubclassLoader loader, Random random) { 39 try { 40 return new ModuleSystemFound(byteBuddy, loader, random); 41 } catch (Exception ignored) { 42 return new NoModuleSystemFound(); 43 } 44 } 45 46 private static class ModuleSystemFound extends ModuleHandler { 47 48 private final ByteBuddy byteBuddy; 49 private final SubclassLoader loader; 50 private final Random random; 51 52 private final int injectonBaseSuffix; 53 54 private final Method getModule, 55 isOpen, 56 isExported, 57 isExportedUnqualified, 58 canRead, 59 addExports, 60 addReads, 61 addOpens, 62 forName; 63 ModuleSystemFound(ByteBuddy byteBuddy, SubclassLoader loader, Random random)64 private ModuleSystemFound(ByteBuddy byteBuddy, SubclassLoader loader, Random random) 65 throws Exception { 66 this.byteBuddy = byteBuddy; 67 this.loader = loader; 68 this.random = random; 69 injectonBaseSuffix = Math.abs(random.nextInt()); 70 Class<?> moduleType = Class.forName("java.lang.Module"); 71 getModule = Class.class.getMethod("getModule"); 72 isOpen = moduleType.getMethod("isOpen", String.class, moduleType); 73 isExported = moduleType.getMethod("isExported", String.class, moduleType); 74 isExportedUnqualified = moduleType.getMethod("isExported", String.class); 75 canRead = moduleType.getMethod("canRead", moduleType); 76 addExports = moduleType.getMethod("addExports", String.class, moduleType); 77 addReads = moduleType.getMethod("addReads", moduleType); 78 addOpens = moduleType.getMethod("addOpens", String.class, moduleType); 79 forName = Class.class.getMethod("forName", String.class); 80 } 81 82 @Override isOpened(Class<?> source, Class<?> target)83 boolean isOpened(Class<?> source, Class<?> target) { 84 if (source.getPackage() == null) { 85 return true; 86 } 87 return (Boolean) 88 invoke( 89 isOpen, 90 invoke(getModule, source), 91 source.getPackage().getName(), 92 invoke(getModule, target)); 93 } 94 95 @Override canRead(Class<?> source, Class<?> target)96 boolean canRead(Class<?> source, Class<?> target) { 97 return (Boolean) invoke(canRead, invoke(getModule, source), invoke(getModule, target)); 98 } 99 100 @Override isExported(Class<?> source)101 boolean isExported(Class<?> source) { 102 if (source.getPackage() == null) { 103 return true; 104 } 105 return (Boolean) 106 invoke( 107 isExportedUnqualified, 108 invoke(getModule, source), 109 source.getPackage().getName()); 110 } 111 112 @Override isExported(Class<?> source, Class<?> target)113 boolean isExported(Class<?> source, Class<?> target) { 114 if (source.getPackage() == null) { 115 return true; 116 } 117 return (Boolean) 118 invoke( 119 isExported, 120 invoke(getModule, source), 121 source.getPackage().getName(), 122 invoke(getModule, target)); 123 } 124 125 @Override injectionBase(ClassLoader classLoader, String typeName)126 Class<?> injectionBase(ClassLoader classLoader, String typeName) { 127 String packageName = typeName.substring(0, typeName.lastIndexOf('.')); 128 if (classLoader == InjectionBase.class.getClassLoader() 129 && InjectionBase.class.getPackage().getName().equals(packageName)) { 130 return InjectionBase.class; 131 } else { 132 synchronized (this) { 133 String name; 134 int suffix = injectonBaseSuffix; 135 do { 136 name = 137 packageName 138 + "." 139 + InjectionBase.class.getSimpleName() 140 + "$" 141 + suffix++; 142 try { 143 Class<?> type = Class.forName(name, false, classLoader); 144 // The injected type must be defined in the class loader that is target 145 // of the injection. Otherwise, 146 // the class's unnamed module would differ from the intended module. To 147 // avoid conflicts, we increment 148 // the suffix until we hit a class with a known name and generate one if 149 // it does not exist. 150 if (type.getClassLoader() == classLoader) { 151 return type; 152 } 153 } catch (ClassNotFoundException ignored) { 154 break; 155 } 156 } while (true); 157 return byteBuddy 158 .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS) 159 .name(name) 160 .make() 161 .load( 162 classLoader, 163 loader.resolveStrategy(InjectionBase.class, classLoader, false)) 164 .getLoaded(); 165 } 166 } 167 } 168 169 @Override adjustModuleGraph(Class<?> source, Class<?> target, boolean export, boolean read)170 void adjustModuleGraph(Class<?> source, Class<?> target, boolean export, boolean read) { 171 boolean needsExport = export && !isExported(source, target); 172 boolean needsRead = read && !canRead(source, target); 173 if (!needsExport && !needsRead) { 174 return; 175 } 176 ClassLoader classLoader = source.getClassLoader(); 177 if (classLoader == null) { 178 throw new MockitoException( 179 join( 180 "Cannot adjust module graph for modules in the bootstrap loader", 181 "", 182 source 183 + " is declared by the bootstrap loader and cannot be adjusted", 184 "Requires package export to " + target + ": " + needsExport, 185 "Requires adjusted reading of " + target + ": " + needsRead)); 186 } 187 boolean targetVisible = classLoader == target.getClassLoader(); 188 while (!targetVisible && classLoader != null) { 189 classLoader = classLoader.getParent(); 190 targetVisible = classLoader == target.getClassLoader(); 191 } 192 MethodCall targetLookup; 193 Implementation.Composable implementation; 194 if (targetVisible) { 195 targetLookup = 196 MethodCall.invoke(getModule) 197 .onMethodCall(MethodCall.invoke(forName).with(target.getName())); 198 implementation = StubMethod.INSTANCE; 199 } else { 200 Class<?> intermediate; 201 Field field; 202 try { 203 intermediate = 204 byteBuddy 205 .subclass( 206 Object.class, 207 ConstructorStrategy.Default.NO_CONSTRUCTORS) 208 .name( 209 String.format( 210 "%s$%d", 211 "org.mockito.codegen.MockitoTypeCarrier", 212 Math.abs(random.nextInt()))) 213 .defineField( 214 "mockitoType", 215 Class.class, 216 Visibility.PUBLIC, 217 Ownership.STATIC) 218 .make() 219 .load( 220 source.getClassLoader(), 221 loader.resolveStrategy( 222 source, source.getClassLoader(), false)) 223 .getLoaded(); 224 field = intermediate.getField("mockitoType"); 225 field.set(null, target); 226 } catch (Exception e) { 227 throw new MockitoException( 228 join( 229 "Could not create a carrier for making the Mockito type visible to " 230 + source, 231 "", 232 "This is required to adjust the module graph to enable mock creation"), 233 e); 234 } 235 targetLookup = MethodCall.invoke(getModule).onField(field); 236 implementation = 237 MethodCall.invoke(getModule) 238 .onMethodCall( 239 MethodCall.invoke(forName).with(intermediate.getName())); 240 } 241 MethodCall sourceLookup = 242 MethodCall.invoke(getModule) 243 .onMethodCall(MethodCall.invoke(forName).with(source.getName())); 244 if (needsExport) { 245 implementation = 246 implementation.andThen( 247 MethodCall.invoke(addExports) 248 .onMethodCall(sourceLookup) 249 .with(target.getPackage().getName()) 250 .withMethodCall(targetLookup)); 251 } 252 if (needsRead) { 253 implementation = 254 implementation.andThen( 255 MethodCall.invoke(addReads) 256 .onMethodCall(sourceLookup) 257 .withMethodCall(targetLookup)); 258 } 259 try { 260 Class.forName( 261 byteBuddy 262 .subclass(Object.class) 263 .name( 264 String.format( 265 "%s$%s$%d", 266 source.getName(), 267 "MockitoModuleProbe", 268 Math.abs(random.nextInt()))) 269 .invokable(isTypeInitializer()) 270 .intercept(implementation) 271 .make() 272 .load( 273 source.getClassLoader(), 274 loader.resolveStrategy( 275 source, source.getClassLoader(), false)) 276 .getLoaded() 277 .getName(), 278 true, 279 source.getClassLoader()); 280 } catch (Exception e) { 281 throw new MockitoException( 282 join( 283 "Could not force module adjustment of the module of " + source, 284 "", 285 "This is required to adjust the module graph to enable mock creation"), 286 e); 287 } 288 } 289 invoke(Method method, Object target, Object... args)290 private static Object invoke(Method method, Object target, Object... args) { 291 try { 292 return method.invoke(target, args); 293 } catch (Exception e) { 294 throw new MockitoException( 295 join( 296 "Could not invoke " + method + " using reflection", 297 "", 298 "Mockito attempted to interact with the Java module system but an unexpected method behavior was encountered"), 299 e); 300 } 301 } 302 } 303 304 private static class NoModuleSystemFound extends ModuleHandler { 305 306 @Override isOpened(Class<?> source, Class<?> target)307 boolean isOpened(Class<?> source, Class<?> target) { 308 return true; 309 } 310 311 @Override canRead(Class<?> source, Class<?> target)312 boolean canRead(Class<?> source, Class<?> target) { 313 return true; 314 } 315 316 @Override isExported(Class<?> source)317 boolean isExported(Class<?> source) { 318 return true; 319 } 320 321 @Override isExported(Class<?> source, Class<?> target)322 boolean isExported(Class<?> source, Class<?> target) { 323 return true; 324 } 325 326 @Override injectionBase(ClassLoader classLoader, String tyoeName)327 Class<?> injectionBase(ClassLoader classLoader, String tyoeName) { 328 return InjectionBase.class; 329 } 330 331 @Override adjustModuleGraph(Class<?> source, Class<?> target, boolean export, boolean read)332 void adjustModuleGraph(Class<?> source, Class<?> target, boolean export, boolean read) { 333 // empty 334 } 335 } 336 } 337