1 /* 2 * Copyright (C) 2020 The Guava Authors 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.google.common.util.concurrent; 18 19 20 import java.net.URLClassLoader; 21 import java.security.Permission; 22 import java.util.HashMap; 23 import java.util.Map; 24 import java.util.PropertyPermission; 25 import java.util.concurrent.CountDownLatch; 26 import java.util.concurrent.ForkJoinPool; 27 import java.util.concurrent.TimeUnit; 28 import javax.annotation.concurrent.GuardedBy; 29 import junit.framework.TestCase; 30 31 /** Tests for {@link AbstractFuture} using an innocuous thread. */ 32 33 public class AbstractFutureInnocuousThreadTest extends TestCase { 34 private ClassLoader oldClassLoader; 35 private URLClassLoader classReloader; 36 private Class<?> settableFutureClass; 37 private SecurityManager oldSecurityManager; 38 39 @Override setUp()40 protected void setUp() throws Exception { 41 // Load the "normal" copy of SettableFuture and related classes. 42 SettableFuture<?> unused = SettableFuture.create(); 43 // Hack to load AbstractFuture et. al. in a new classloader so that it tries to re-read the 44 // cancellation-cause system property. This allows us to test what happens if reading the 45 // property is forbidden and then continue running tests normally in one jvm without resorting 46 // to even crazier hacks to reset static final boolean fields. 47 final String concurrentPackage = SettableFuture.class.getPackage().getName(); 48 classReloader = 49 new URLClassLoader(ClassPathUtil.getClassPathUrls()) { 50 @GuardedBy("loadedClasses") 51 final Map<String, Class<?>> loadedClasses = new HashMap<>(); 52 53 @Override 54 public Class<?> loadClass(String name) throws ClassNotFoundException { 55 if (name.startsWith(concurrentPackage) 56 // Use other classloader for ListenableFuture, so that the objects can interact 57 && !ListenableFuture.class.getName().equals(name)) { 58 synchronized (loadedClasses) { 59 Class<?> toReturn = loadedClasses.get(name); 60 if (toReturn == null) { 61 toReturn = super.findClass(name); 62 loadedClasses.put(name, toReturn); 63 } 64 return toReturn; 65 } 66 } 67 return super.loadClass(name); 68 } 69 }; 70 oldClassLoader = Thread.currentThread().getContextClassLoader(); 71 Thread.currentThread().setContextClassLoader(classReloader); 72 73 oldSecurityManager = System.getSecurityManager(); 74 /* 75 * TODO(cpovirk): Why couldn't I get this to work with PermissionCollection and implies(), as 76 * used by ClassPathTest? 77 */ 78 final PropertyPermission readSystemProperty = 79 new PropertyPermission("guava.concurrent.generate_cancellation_cause", "read"); 80 SecurityManager disallowPropertySecurityManager = 81 new SecurityManager() { 82 @Override 83 public void checkPermission(Permission p) { 84 if (readSystemProperty.equals(p)) { 85 throw new SecurityException("Disallowed: " + p); 86 } 87 } 88 }; 89 System.setSecurityManager(disallowPropertySecurityManager); 90 91 settableFutureClass = classReloader.loadClass(SettableFuture.class.getName()); 92 93 /* 94 * We must keep the SecurityManager installed during the test body: It affects what kind of 95 * threads ForkJoinPool.commonPool() creates. 96 */ 97 } 98 99 @Override tearDown()100 protected void tearDown() throws Exception { 101 System.setSecurityManager(oldSecurityManager); 102 classReloader.close(); 103 Thread.currentThread().setContextClassLoader(oldClassLoader); 104 } 105 testAbstractFutureInitializationWithInnocuousThread_doesNotThrow()106 public void testAbstractFutureInitializationWithInnocuousThread_doesNotThrow() throws Exception { 107 CountDownLatch latch = new CountDownLatch(1); 108 // Setting a security manager causes the common ForkJoinPool to use InnocuousThreads with no 109 // permissions. 110 // submit()/join() causes this thread to execute the task instead, so we use a CountDownLatch as 111 // a barrier to synchronize. 112 // TODO(cpovirk): If some other test already initialized commonPool(), this won't work :( 113 // Maybe we should just run this test in its own VM. 114 ForkJoinPool.commonPool() 115 .execute( 116 () -> { 117 try { 118 settableFutureClass.getMethod("create").invoke(null); 119 latch.countDown(); 120 } catch (Exception e) { 121 throw new RuntimeException(e); 122 } 123 }); 124 // In the failure case, await() will timeout. 125 assertTrue(latch.await(2, TimeUnit.SECONDS)); 126 } 127 128 // TODO(cpovirk): Write a similar test that doesn't use ForkJoinPool (to run under Android)? 129 } 130