• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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