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