• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.googlecode.guice;
18 
19 import static com.google.inject.Asserts.getClassPathUrls;
20 import static com.google.inject.matcher.Matchers.any;
21 
22 import com.google.common.testing.GcFinalization;
23 import com.google.inject.AbstractModule;
24 import com.google.inject.Binder;
25 import com.google.inject.Guice;
26 import com.google.inject.Injector;
27 import com.google.inject.Module;
28 import com.googlecode.guice.PackageVisibilityTestModule.PublicUserOfPackagePrivate;
29 import java.lang.ref.WeakReference;
30 import java.lang.reflect.Constructor;
31 import java.lang.reflect.Method;
32 import java.net.URLClassLoader;
33 import javax.inject.Inject;
34 import junit.framework.TestCase;
35 import org.aopalliance.intercept.MethodInterceptor;
36 import org.aopalliance.intercept.MethodInvocation;
37 
38 /**
39  * This test is in a separate package so we can test package-level visibility with confidence.
40  *
41  * @author mcculls@gmail.com (Stuart McCulloch)
42  */
43 public class BytecodeGenTest extends TestCase {
44 
45   private final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
46 
47   private final Module interceptorModule =
48       new AbstractModule() {
49         @Override
50         protected void configure() {
51           bindInterceptor(
52               any(),
53               any(),
54               new MethodInterceptor() {
55                 @Override
56                 public Object invoke(MethodInvocation chain) throws Throwable {
57                   return chain.proceed() + " WORLD";
58                 }
59               });
60         }
61       };
62 
63   private final Module noopInterceptorModule =
64       new AbstractModule() {
65         @Override
66         protected void configure() {
67           bindInterceptor(
68               any(),
69               any(),
70               new MethodInterceptor() {
71                 @Override
72                 public Object invoke(MethodInvocation chain) throws Throwable {
73                   return chain.proceed();
74                 }
75               });
76         }
77       };
78 
testPackageVisibility()79   public void testPackageVisibility() {
80     Injector injector = Guice.createInjector(new PackageVisibilityTestModule());
81     injector.getInstance(PublicUserOfPackagePrivate.class); // This must pass.
82   }
83 
testInterceptedPackageVisibility()84   public void testInterceptedPackageVisibility() {
85     Injector injector = Guice.createInjector(interceptorModule, new PackageVisibilityTestModule());
86     injector.getInstance(PublicUserOfPackagePrivate.class); // This must pass.
87   }
88 
testEnhancerNaming()89   public void testEnhancerNaming() {
90     Injector injector = Guice.createInjector(interceptorModule, new PackageVisibilityTestModule());
91     PublicUserOfPackagePrivate pupp = injector.getInstance(PublicUserOfPackagePrivate.class);
92     assertTrue(
93         pupp.getClass()
94             .getName()
95             .startsWith(PublicUserOfPackagePrivate.class.getName() + "$$EnhancerByGuice$$"));
96   }
97 
98   // TODO(sameb): Figure out how to test FastClass naming tests.
99 
100   /** Custom URL classloader with basic visibility rules */
101   static class TestVisibilityClassLoader extends URLClassLoader {
102 
103     final boolean hideInternals;
104 
TestVisibilityClassLoader(boolean hideInternals)105     TestVisibilityClassLoader(boolean hideInternals) {
106       this(TestVisibilityClassLoader.class.getClassLoader(), hideInternals);
107     }
108 
TestVisibilityClassLoader(ClassLoader classloader, boolean hideInternals)109     TestVisibilityClassLoader(ClassLoader classloader, boolean hideInternals) {
110       super(getClassPathUrls(), classloader);
111       this.hideInternals = hideInternals;
112     }
113 
114     /**
115      * Classic parent-delegating classloaders are meant to override findClass. However,
116      * non-delegating classloaders (as used in OSGi) instead override loadClass to provide support
117      * for "class-space" separation.
118      */
119     @Override
loadClass(final String name, final boolean resolve)120     protected Class<?> loadClass(final String name, final boolean resolve)
121         throws ClassNotFoundException {
122 
123       synchronized (this) {
124         // check our local cache to avoid duplicates
125         final Class<?> clazz = findLoadedClass(name);
126         if (clazz != null) {
127           return clazz;
128         }
129       }
130 
131       if (name.startsWith("java.")) {
132 
133         // standard bootdelegation of java.*
134         return super.loadClass(name, resolve);
135 
136       } else if (!name.contains(".internal.") && !name.contains(".cglib.")) {
137 
138         /*
139          * load public and test classes directly from the classpath - we don't
140          * delegate to our parent because then the loaded classes would also be
141          * able to see private internal Guice classes, as they are also loaded
142          * by the parent classloader.
143          */
144         final Class<?> clazz = findClass(name);
145         if (resolve) {
146           resolveClass(clazz);
147         }
148         return clazz;
149       }
150 
151       // hide internal non-test classes
152       if (hideInternals) {
153         throw new ClassNotFoundException();
154       }
155       return super.loadClass(name, resolve);
156     }
157   }
158 
159   /** as loaded by another class loader */
160   private Class<ProxyTest> proxyTestClass;
161 
162   private Class<ProxyTestImpl> realClass;
163   private Module testModule;
164 
165   @Override
166   @SuppressWarnings("unchecked")
setUp()167   protected void setUp() throws Exception {
168     super.setUp();
169 
170     ClassLoader testClassLoader = new TestVisibilityClassLoader(true);
171     proxyTestClass = (Class<ProxyTest>) testClassLoader.loadClass(ProxyTest.class.getName());
172     realClass = (Class<ProxyTestImpl>) testClassLoader.loadClass(ProxyTestImpl.class.getName());
173 
174     testModule =
175         new AbstractModule() {
176           @Override
177           public void configure() {
178             bind(proxyTestClass).to(realClass);
179           }
180         };
181   }
182 
183   interface ProxyTest {
sayHello()184     String sayHello();
185   }
186 
187   /**
188    * Note: this class must be marked as public or protected so that the Guice custom classloader
189    * will intercept it. Private and implementation classes are not intercepted by the custom
190    * classloader.
191    *
192    * @see com.google.inject.internal.BytecodeGen.Visibility
193    */
194   public static class ProxyTestImpl implements ProxyTest {
195 
196     static {
197       //System.out.println(ProxyTestImpl.class.getClassLoader());
198     }
199 
200     @Override
sayHello()201     public String sayHello() {
202       return "HELLO";
203     }
204   }
205 
testProxyClassLoading()206   public void testProxyClassLoading() throws Exception {
207     Object testObject =
208         Guice.createInjector(interceptorModule, testModule).getInstance(proxyTestClass);
209 
210     // verify method interception still works
211     Method m = realClass.getMethod("sayHello");
212     assertEquals("HELLO WORLD", m.invoke(testObject));
213   }
214 
testSystemClassLoaderIsUsedIfProxiedClassUsesIt()215   public void testSystemClassLoaderIsUsedIfProxiedClassUsesIt() {
216     ProxyTest testProxy =
217         Guice.createInjector(
218                 interceptorModule,
219                 new Module() {
220                   @Override
221                   public void configure(Binder binder) {
222                     binder.bind(ProxyTest.class).to(ProxyTestImpl.class);
223                   }
224                 })
225             .getInstance(ProxyTest.class);
226 
227     if (ProxyTest.class.getClassLoader() == systemClassLoader) {
228       assertSame(testProxy.getClass().getClassLoader(), systemClassLoader);
229     } else {
230       assertNotSame(testProxy.getClass().getClassLoader(), systemClassLoader);
231     }
232   }
233 
testProxyClassUnloading()234   public void testProxyClassUnloading() {
235     Object testObject =
236         Guice.createInjector(interceptorModule, testModule).getInstance(proxyTestClass);
237     assertNotNull(testObject.getClass().getClassLoader());
238     assertNotSame(testObject.getClass().getClassLoader(), systemClassLoader);
239 
240     // take a weak reference to the generated proxy class
241     WeakReference<Class<?>> clazzRef = new WeakReference<Class<?>>(testObject.getClass());
242 
243     assertNotNull(clazzRef.get());
244 
245     // null the proxy
246     testObject = null;
247 
248     /*
249      * this should be enough to queue the weak reference
250      * unless something is holding onto it accidentally.
251      */
252     GcFinalization.awaitClear(clazzRef);
253 
254     // This test could be somewhat flaky when the GC isn't working.
255     // If it fails, run the test again to make sure it's failing reliably.
256     assertNull("Proxy class was not unloaded.", clazzRef.get());
257   }
258 
testProxyingPackagePrivateMethods()259   public void testProxyingPackagePrivateMethods() {
260     Injector injector = Guice.createInjector(interceptorModule);
261     assertEquals("HI WORLD", injector.getInstance(PackageClassPackageMethod.class).sayHi());
262     assertEquals("HI WORLD", injector.getInstance(PublicClassPackageMethod.class).sayHi());
263     assertEquals("HI WORLD", injector.getInstance(ProtectedClassProtectedMethod.class).sayHi());
264   }
265 
266   static class PackageClassPackageMethod {
sayHi()267     String sayHi() {
268       return "HI";
269     }
270   }
271 
272   public static class PublicClassPackageMethod {
sayHi()273     String sayHi() {
274       return "HI";
275     }
276   }
277 
278   protected static class ProtectedClassProtectedMethod {
sayHi()279     protected String sayHi() {
280       return "HI";
281     }
282   }
283 
284   static class Hidden {}
285 
286   public static class HiddenMethodReturn {
method()287     public Hidden method() {
288       return new Hidden();
289     }
290   }
291 
292   public static class HiddenMethodParameter {
method(Hidden h)293     public void method(Hidden h) {}
294   }
295 
testClassLoaderBridging()296   public void testClassLoaderBridging() throws Exception {
297     ClassLoader testClassLoader = new TestVisibilityClassLoader(false);
298 
299     Class hiddenMethodReturnClass = testClassLoader.loadClass(HiddenMethodReturn.class.getName());
300     Class hiddenMethodParameterClass =
301         testClassLoader.loadClass(HiddenMethodParameter.class.getName());
302 
303     Injector injector = Guice.createInjector(noopInterceptorModule);
304 
305     Class hiddenClass = testClassLoader.loadClass(Hidden.class.getName());
306     Constructor ctor = hiddenClass.getDeclaredConstructor();
307 
308     ctor.setAccessible(true);
309 
310     // don't use bridging for proxies with private parameters
311     Object o1 = injector.getInstance(hiddenMethodParameterClass);
312     o1.getClass().getDeclaredMethod("method", hiddenClass).invoke(o1, ctor.newInstance());
313 
314     // don't use bridging for proxies with private return types
315     Object o2 = injector.getInstance(hiddenMethodReturnClass);
316     o2.getClass().getDeclaredMethod("method").invoke(o2);
317   }
318 
319   // This tests for a situation where a osgi bundle contains a version of guice.  When guice
320   // generates a fast class it will use a bridge classloader
testFastClassUsesBridgeClassloader()321   public void testFastClassUsesBridgeClassloader() throws Throwable {
322     Injector injector = Guice.createInjector();
323     // These classes are all in the same classloader as guice itself, so other than the private one
324     // they can all be fast class invoked
325     injector.getInstance(PublicInject.class).assertIsFastClassInvoked();
326     injector.getInstance(ProtectedInject.class).assertIsFastClassInvoked();
327     injector.getInstance(PackagePrivateInject.class).assertIsFastClassInvoked();
328     injector.getInstance(PrivateInject.class).assertIsReflectionInvoked();
329 
330     // This classloader will load the types in an loader with a different version of guice/cglib
331     // this prevents the use of fastclass for all but the public types (where the bridge
332     // classloader can be used).
333     MultipleVersionsOfGuiceClassLoader fakeLoader = new MultipleVersionsOfGuiceClassLoader();
334     injector
335         .getInstance(fakeLoader.loadLogCreatorType(PublicInject.class))
336         .assertIsFastClassInvoked();
337     injector
338         .getInstance(fakeLoader.loadLogCreatorType(ProtectedInject.class))
339         .assertIsReflectionInvoked();
340     injector
341         .getInstance(fakeLoader.loadLogCreatorType(PackagePrivateInject.class))
342         .assertIsReflectionInvoked();
343     injector
344         .getInstance(fakeLoader.loadLogCreatorType(PrivateInject.class))
345         .assertIsReflectionInvoked();
346   }
347 
348   // This classloader simulates an OSGI environment where a bundle has a conflicting definition of
349   // cglib (or guice).  This is sort of the opposite of the BridgeClassloader and is meant to test
350   // its use.
351   static class MultipleVersionsOfGuiceClassLoader extends URLClassLoader {
MultipleVersionsOfGuiceClassLoader()352     MultipleVersionsOfGuiceClassLoader() {
353       this(MultipleVersionsOfGuiceClassLoader.class.getClassLoader());
354     }
355 
MultipleVersionsOfGuiceClassLoader(ClassLoader classloader)356     MultipleVersionsOfGuiceClassLoader(ClassLoader classloader) {
357       super(getClassPathUrls(), classloader);
358     }
359 
loadLogCreatorType(Class<? extends LogCreator> cls)360     public Class<? extends LogCreator> loadLogCreatorType(Class<? extends LogCreator> cls)
361         throws ClassNotFoundException {
362       return loadClass(cls.getName()).asSubclass(LogCreator.class);
363     }
364 
365     /**
366      * Classic parent-delegating classloaders are meant to override findClass. However,
367      * non-delegating classloaders (as used in OSGi) instead override loadClass to provide support
368      * for "class-space" separation.
369      */
370     @Override
loadClass(final String name, final boolean resolve)371     protected Class<?> loadClass(final String name, final boolean resolve)
372         throws ClassNotFoundException {
373 
374       synchronized (this) {
375         // check our local cache to avoid duplicates
376         final Class<?> clazz = findLoadedClass(name);
377         if (clazz != null) {
378           return clazz;
379         }
380       }
381 
382       if (name.startsWith("java.")
383           || name.startsWith("javax.")
384           || name.equals(LogCreator.class.getName())
385           || (!name.startsWith("com.google.inject.")
386               && !name.contains(".cglib.")
387               && !name.startsWith("com.googlecode.guice"))) {
388 
389         // standard parent delegation
390         return super.loadClass(name, resolve);
391 
392       } else {
393         // load a new copy of the class
394         final Class<?> clazz = findClass(name);
395         if (resolve) {
396           resolveClass(clazz);
397         }
398         return clazz;
399       }
400     }
401   }
402 
403   public static class LogCreator {
404     final Throwable caller;
405 
LogCreator()406     public LogCreator() {
407       this.caller = new Throwable();
408     }
409 
assertIsFastClassInvoked()410     void assertIsFastClassInvoked() throws Throwable {
411       // 2 because the first 2 elements are
412       // LogCreator.<init>()
413       // Subclass.<init>()
414       if (!caller.getStackTrace()[2].getClassName().contains("$$FastClassByGuice$$")) {
415         throw new AssertionError("Caller was not FastClass").initCause(caller);
416       }
417     }
418 
assertIsReflectionInvoked()419     void assertIsReflectionInvoked() throws Throwable {
420       // Scan for a call to Constructor.newInstance, but stop if we see the test itself.
421       for (StackTraceElement element : caller.getStackTrace()) {
422         if (element.getClassName().equals(BytecodeGenTest.class.getName())) {
423           // break when we hit the test method.
424           break;
425         }
426         if (element.getClassName().equals(Constructor.class.getName())
427             && element.getMethodName().equals("newInstance")) {
428           return;
429         }
430       }
431       throw new AssertionError("Caller was not Constructor.newInstance").initCause(caller);
432     }
433   }
434 
435   public static class PublicInject extends LogCreator {
436     @Inject
PublicInject()437     public PublicInject() {}
438   }
439 
440   static class PackagePrivateInject extends LogCreator {
441     @Inject
PackagePrivateInject()442     PackagePrivateInject() {}
443   }
444 
445   protected static class ProtectedInject extends LogCreator {
446     @Inject
ProtectedInject()447     protected ProtectedInject() {}
448   }
449 
450   private static class PrivateInject extends LogCreator {
451     @Inject
PrivateInject()452     private PrivateInject() {}
453   }
454 }
455