1 // Copyright 2016 The Bazel Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // 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
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 package com.google.devtools.build.android.desugar;
15 
16 import static com.google.common.truth.Truth.assertThat;
17 import static org.junit.Assert.fail;
18 
19 import com.google.devtools.build.android.desugar.io.BitFlags;
20 import org.junit.Test;
21 import org.junit.runner.RunWith;
22 import org.junit.runners.JUnit4;
23 import org.objectweb.asm.ClassReader;
24 import org.objectweb.asm.ClassVisitor;
25 import org.objectweb.asm.MethodVisitor;
26 import org.objectweb.asm.Opcodes;
27 
28 @RunWith(JUnit4.class)
29 public class Java7CompatibilityTest {
30 
31   @Test
testJava7CompatibleInterface()32   public void testJava7CompatibleInterface() throws Exception {
33     ClassReader reader = new ClassReader(ExtendsDefault.class.getName());
34     ClassTester tester = new ClassTester();
35     reader.accept(new Java7Compatibility(tester, null, null), 0);
36     assertThat(tester.version).isEqualTo(Opcodes.V1_7);
37     assertThat(tester.bridgeMethods).isEqualTo(0); // make sure we strip bridge methods
38     assertThat(tester.clinitMethods).isEqualTo(1); // make sure we don't strip <clinit>
39   }
40 
41   @Test
testDefaultMethodFails()42   public void testDefaultMethodFails() throws Exception {
43     ClassReader reader = new ClassReader(WithDefault.class.getName());
44     try {
45       reader.accept(new Java7Compatibility(null, null, null), 0);
46       fail("IllegalArgumentException expected");
47     } catch (IllegalArgumentException expected) {
48       assertThat(expected).hasMessageThat().contains("getVersion()I");
49     }
50   }
51 
52   /**
53    * Tests that a class implementing interfaces with bridge methods redeclares those bridges.
54    * This is behavior of javac that we rely on.
55    */
56   @Test
testConcreteClassRedeclaresBridges()57   public void testConcreteClassRedeclaresBridges() throws Exception {
58     ClassReader reader = new ClassReader(Impl.class.getName());
59     ClassTester tester = new ClassTester();
60     reader.accept(new Java7Compatibility(tester, null, null), 0);
61     assertThat(tester.version).isEqualTo(Opcodes.V1_7);
62     assertThat(tester.bridgeMethods).isEqualTo(2);
63   }
64 
65   private static class ClassTester extends ClassVisitor {
66 
67     int version;
68     int bridgeMethods;
69     int clinitMethods;
70 
ClassTester()71     private ClassTester() {
72       super(Opcodes.ASM5, null);
73     }
74 
75     @Override
visit( int version, int access, String name, String signature, String superName, String[] interfaces)76     public void visit(
77         int version,
78         int access,
79         String name,
80         String signature,
81         String superName,
82         String[] interfaces) {
83       this.version = version;
84       super.visit(version, access, name, signature, superName, interfaces);
85     }
86 
87     @Override
visitMethod( int access, String name, String desc, String signature, String[] exceptions)88     public MethodVisitor visitMethod(
89         int access, String name, String desc, String signature, String[] exceptions) {
90       if (BitFlags.isSet(access, Opcodes.ACC_BRIDGE)) {
91         ++bridgeMethods;
92       }
93       if ("<clinit>".equals(name)) {
94         ++clinitMethods;
95       }
96       return super.visitMethod(access, name, desc, signature, exceptions);
97     }
98   }
99 
100   interface WithDefault<T> {
getVersion()101     default int getVersion() {
102       return 18;
103     }
get()104     T get();
105   }
106 
107   // Javac will generate a default bridge method "Object get()" that Java7Compatibility will remove
108   interface ExtendsDefault<T extends Number> extends WithDefault<T> {
109     public static final Integer X = Integer.valueOf(37);
name()110     String name();
get()111     @Override T get();
112   }
113 
114   // Javac will generate 2 bridge methods that we *don't* want to remove
115   static class Impl implements ExtendsDefault<Integer> {
get()116     @Override public Integer get() {
117       return X;
118     }
name()119     @Override public String name() {
120       return "test";
121     }
122   }
123 }
124