1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package lockedregioncodeinjection; 15 16 import org.junit.Assert; 17 import org.junit.Test; 18 19 /** 20 * To run the unit tests, first build the two necessary artifacts. Do this explicitly as they are 21 * not generally retained by a normal "build all". After lunching a target: 22 * m lockedregioncodeinjection 23 * m lockedregioncodeinjection_input 24 * 25 * <pre> 26 * <code> 27 * set -x 28 * 29 * croot frameworks/base/tools/locked_region_code_injection 30 * 31 * # Clean 32 * mkdir -p out 33 * rm -fr out/* 34 * 35 * # Paths to the build artifacts. These assume linux-x86; YMMV. 36 * ROOT=$TOP/out/host/linux-x86 37 * EXE=$ROOT/bin/lockedregioncodeinjection 38 * INPUT=$ROOT/frameworkd/lockedregioncodeinjection_input.jar 39 * 40 * # Run tool on unit tests. 41 * $EXE -i $INPUT -o out/test_output.jar \ 42 * --targets 'Llockedregioncodeinjection/TestTarget;' \ 43 * --pre 'lockedregioncodeinjection/TestTarget.boost' \ 44 * --post 'lockedregioncodeinjection/TestTarget.unboost' 45 * 46 * # Run unit tests. 47 * java -ea -cp out/test_output.jar \ 48 * org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain 49 * </code> 50 * OR 51 * <code> 52 * bash test/unit-test.sh 53 * </code> 54 * </pre> 55 */ 56 public class TestMain { 57 @Test testSimpleSynchronizedBlock()58 public void testSimpleSynchronizedBlock() { 59 TestTarget.resetCount(); 60 TestTarget t = new TestTarget(); 61 62 Assert.assertEquals(TestTarget.boostCount, 0); 63 Assert.assertEquals(TestTarget.unboostCount, 0); 64 Assert.assertEquals(TestTarget.invokeCount, 0); 65 Assert.assertEquals(TestTarget.boostCountLocked, 0); 66 Assert.assertEquals(TestTarget.unboostCountLocked, 0); 67 68 synchronized (t) { 69 Assert.assertEquals(TestTarget.boostCount, 1); 70 Assert.assertEquals(TestTarget.unboostCount, 0); 71 TestTarget.invoke(); 72 } 73 74 Assert.assertEquals(TestTarget.boostCount, 1); 75 Assert.assertEquals(TestTarget.unboostCount, 1); 76 Assert.assertEquals(TestTarget.invokeCount, 1); 77 Assert.assertEquals(TestTarget.boostCountLocked, 0); 78 Assert.assertEquals(TestTarget.unboostCountLocked, 0); 79 } 80 81 @Test testSimpleSynchronizedMethod()82 public void testSimpleSynchronizedMethod() { 83 TestTarget.resetCount(); 84 TestTarget t = new TestTarget(); 85 86 Assert.assertEquals(TestTarget.boostCount, 0); 87 Assert.assertEquals(TestTarget.unboostCount, 0); 88 Assert.assertEquals(TestTarget.boostCountLocked, 0); 89 Assert.assertEquals(TestTarget.unboostCountLocked, 0); 90 91 t.synchronizedCall(); 92 93 Assert.assertEquals(TestTarget.boostCount, 1); 94 Assert.assertEquals(TestTarget.unboostCount, 1); 95 Assert.assertEquals(TestTarget.invokeCount, 1); 96 Assert.assertEquals(TestTarget.boostCountLocked, 0); 97 Assert.assertEquals(TestTarget.unboostCountLocked, 0); 98 } 99 100 @Test testSimpleSynchronizedMethod2()101 public void testSimpleSynchronizedMethod2() { 102 TestTarget.resetCount(); 103 TestTarget t = new TestTarget(); 104 105 Assert.assertEquals(TestTarget.boostCount, 0); 106 Assert.assertEquals(TestTarget.unboostCount, 0); 107 Assert.assertEquals(TestTarget.boostCountLocked, 0); 108 Assert.assertEquals(TestTarget.unboostCountLocked, 0); 109 110 t.synchronizedCallReturnInt(); 111 112 Assert.assertEquals(TestTarget.boostCount, 1); 113 Assert.assertEquals(TestTarget.unboostCount, 1); 114 Assert.assertEquals(TestTarget.invokeCount, 1); 115 Assert.assertEquals(TestTarget.boostCountLocked, 0); 116 Assert.assertEquals(TestTarget.unboostCountLocked, 0); 117 } 118 119 @Test testSimpleSynchronizedMethod3()120 public void testSimpleSynchronizedMethod3() { 121 TestTarget.resetCount(); 122 TestTarget t = new TestTarget(); 123 124 Assert.assertEquals(TestTarget.boostCount, 0); 125 Assert.assertEquals(TestTarget.unboostCount, 0); 126 127 t.synchronizedCallReturnObject(); 128 129 Assert.assertEquals(TestTarget.boostCount, 1); 130 Assert.assertEquals(TestTarget.unboostCount, 1); 131 Assert.assertEquals(TestTarget.invokeCount, 1); 132 } 133 134 @SuppressWarnings("unused") 135 @Test testCaughtException()136 public void testCaughtException() { 137 TestTarget.resetCount(); 138 TestTarget t = new TestTarget(); 139 boolean caughtException = false; 140 141 Assert.assertEquals(TestTarget.boostCount, 0); 142 Assert.assertEquals(TestTarget.unboostCount, 0); 143 Assert.assertEquals(TestTarget.unboostCount, 0); 144 145 try { 146 synchronized (t) { 147 Assert.assertEquals(TestTarget.boostCount, 1); 148 Assert.assertEquals(TestTarget.unboostCount, 0); 149 if (true) { 150 throw new RuntimeException(); 151 } 152 TestTarget.invoke(); 153 } 154 } catch (Throwable e) { 155 caughtException = true; 156 } 157 158 Assert.assertEquals(TestTarget.boostCount, 1); 159 Assert.assertEquals(TestTarget.unboostCount, 1); 160 Assert.assertEquals(TestTarget.invokeCount, 0); // Not called 161 Assert.assertTrue(caughtException); 162 } 163 164 @SuppressWarnings("unused") testUncaughtException()165 private void testUncaughtException() { 166 TestTarget t = new TestTarget(); 167 synchronized (t) { 168 if (true) { 169 throw new RuntimeException(); 170 } 171 TestTarget.invoke(); 172 } 173 } 174 175 @SuppressWarnings("unused") 176 @Test testHandledFinally()177 public void testHandledFinally() { 178 TestTarget.resetCount(); 179 try { 180 testUncaughtException(); 181 } catch (Throwable t) { 182 183 } 184 Assert.assertEquals(TestTarget.boostCount, 1); 185 Assert.assertEquals(TestTarget.unboostCount, 1); 186 Assert.assertEquals(TestTarget.invokeCount, 0); // Not called 187 } 188 189 @Test testNestedSynchronizedBlock()190 public void testNestedSynchronizedBlock() { 191 TestTarget.resetCount(); 192 TestTarget t = new TestTarget(); 193 194 Assert.assertEquals(TestTarget.boostCount, 0); 195 Assert.assertEquals(TestTarget.unboostCount, 0); 196 Assert.assertEquals(TestTarget.unboostCount, 0); 197 198 synchronized (t) { 199 synchronized (t) { 200 synchronized (t) { 201 synchronized (t) { 202 synchronized (t) { 203 synchronized (t) { 204 Assert.assertEquals(TestTarget.boostCount, 6); 205 Assert.assertEquals(TestTarget.unboostCount, 0); 206 TestTarget.invoke(); 207 } 208 Assert.assertEquals(TestTarget.unboostCount, 1); 209 } 210 Assert.assertEquals(TestTarget.unboostCount, 2); 211 } 212 Assert.assertEquals(TestTarget.unboostCount, 3); 213 } 214 Assert.assertEquals(TestTarget.unboostCount, 4); 215 } 216 Assert.assertEquals(TestTarget.unboostCount, 5); 217 } 218 219 Assert.assertEquals(TestTarget.boostCount, 6); 220 Assert.assertEquals(TestTarget.unboostCount, 6); 221 Assert.assertEquals(TestTarget.invokeCount, 1); 222 } 223 224 @Test testMethodWithControlFlow()225 public void testMethodWithControlFlow() { 226 TestTarget.resetCount(); 227 TestTarget t = new TestTarget(); 228 229 Assert.assertEquals(TestTarget.boostCount, 0); 230 Assert.assertEquals(TestTarget.unboostCount, 0); 231 232 if ((t.hashCode() + " ").contains("1")) { 233 t.synchronizedCall(); 234 } else { 235 t.synchronizedCall(); 236 } 237 238 // Should only be boosted once. 239 Assert.assertEquals(TestTarget.boostCount, 1); 240 Assert.assertEquals(TestTarget.unboostCount, 1); 241 Assert.assertEquals(TestTarget.invokeCount, 1); 242 } 243 244 @Test testUnboostThatThrows()245 public void testUnboostThatThrows() { 246 TestTarget.resetCount(); 247 TestTarget t = new TestTarget(); 248 boolean asserted = false; 249 250 Assert.assertEquals(TestTarget.boostCount, 0); 251 Assert.assertEquals(TestTarget.unboostCount, 0); 252 253 try { 254 t.synchronizedThrowsOnUnboost(); 255 } catch (RuntimeException e) { 256 asserted = true; 257 } 258 259 Assert.assertEquals(asserted, true); 260 Assert.assertEquals(TestTarget.boostCount, 1); 261 Assert.assertEquals(TestTarget.unboostCount, 0); 262 Assert.assertEquals(TestTarget.invokeCount, 1); 263 } 264 265 @Test testScopedTarget()266 public void testScopedTarget() { 267 TestScopedTarget target = new TestScopedTarget(); 268 Assert.assertEquals(0, target.scopedLock().mLevel); 269 270 synchronized (target.scopedLock()) { 271 Assert.assertEquals(1, target.scopedLock().mLevel); 272 } 273 Assert.assertEquals(0, target.scopedLock().mLevel); 274 275 synchronized (target.scopedLock()) { 276 synchronized (target.scopedLock()) { 277 Assert.assertEquals(2, target.scopedLock().mLevel); 278 } 279 } 280 Assert.assertEquals(0, target.scopedLock().mLevel); 281 } 282 283 @Test testScopedExceptionHandling()284 public void testScopedExceptionHandling() { 285 TestScopedTarget target = new TestScopedTarget(); 286 Assert.assertEquals(0, target.scopedLock().mLevel); 287 288 boolean handled; 289 290 // 1: an exception inside the block properly releases the lock. 291 handled = false; 292 try { 293 synchronized (target.scopedLock()) { 294 Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); 295 Assert.assertEquals(1, target.scopedLock().mLevel); 296 throw new RuntimeException(); 297 } 298 } catch (RuntimeException e) { 299 Assert.assertEquals(0, target.scopedLock().mLevel); 300 handled = true; 301 } 302 Assert.assertEquals(0, target.scopedLock().mLevel); 303 Assert.assertEquals(true, handled); 304 // Just verify that the lock can still be taken 305 Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); 306 307 // 2: An exception inside the monitor enter function 308 handled = false; 309 target.throwOnEnter(true); 310 try { 311 synchronized (target.scopedLock()) { 312 // The exception was thrown inside monitorEnter(), so the code should 313 // never reach this point. 314 Assert.assertEquals(0, 1); 315 } 316 } catch (RuntimeException e) { 317 Assert.assertEquals(0, target.scopedLock().mLevel); 318 handled = true; 319 } 320 Assert.assertEquals(0, target.scopedLock().mLevel); 321 Assert.assertEquals(true, handled); 322 // Just verify that the lock can still be taken 323 Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); 324 325 // 3: An exception inside the monitor exit function 326 handled = false; 327 target.throwOnEnter(true); 328 try { 329 synchronized (target.scopedLock()) { 330 Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); 331 Assert.assertEquals(1, target.scopedLock().mLevel); 332 } 333 } catch (RuntimeException e) { 334 Assert.assertEquals(0, target.scopedLock().mLevel); 335 handled = true; 336 } 337 Assert.assertEquals(0, target.scopedLock().mLevel); 338 Assert.assertEquals(true, handled); 339 // Just verify that the lock can still be taken 340 Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); 341 } 342 343 // Provide an in-class type conversion for the scoped target. untypedLock(TestScopedTarget target)344 private Object untypedLock(TestScopedTarget target) { 345 return target.scopedLock(); 346 } 347 348 @Test testScopedLockTyping()349 public void testScopedLockTyping() { 350 TestScopedTarget target = new TestScopedTarget(); 351 Assert.assertEquals(target.scopedLock().mLevel, 0); 352 353 // Scoped lock injection works on the static type of an object. In general, it is 354 // a very bad idea to do type conversion on scoped locks, but the general rule is 355 // that conversions within a single method are recognized by the lock injection 356 // tool and injection occurs. Conversions outside a single method are not 357 // recognized and injection does not occur. 358 359 // 1. Conversion occurs outside the class. The visible type of the lock is Object 360 // in this block, so no injection takes place on 'untypedLock', even though the 361 // dynamic type is TestScopedLock. 362 synchronized (target.untypedLock()) { 363 Assert.assertEquals(0, target.scopedLock().mLevel); 364 Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock); 365 Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); 366 } 367 368 // 2. Conversion occurs inside the class but in another method. The visible type 369 // of the lock is Object in this block, so no injection takes place on 370 // 'untypedLock', even though the dynamic type is TestScopedLock. 371 synchronized (untypedLock(target)) { 372 Assert.assertEquals(0, target.scopedLock().mLevel); 373 Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock); 374 Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); 375 } 376 377 // 3. Conversion occurs inside the method. The compiler can determine the type of 378 // the lock within a single function, so injection does take place here. 379 Object untypedLock = target.scopedLock(); 380 synchronized (untypedLock) { 381 Assert.assertEquals(1, target.scopedLock().mLevel); 382 Assert.assertEquals(true, untypedLock instanceof TestScopedLock); 383 Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); 384 } 385 } 386 } 387