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