1 /* 2 * Copyright 2016 Google Inc. All Rights Reserved. 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.turbine.binder; 18 19 import static com.google.common.collect.Iterables.getOnlyElement; 20 import static com.google.common.truth.Truth.assertThat; 21 import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; 22 import static org.junit.Assert.fail; 23 24 import com.google.common.base.Joiner; 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.ImmutableMap; 27 import com.google.turbine.binder.bound.SourceTypeBoundClass; 28 import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; 29 import com.google.turbine.binder.sym.ClassSymbol; 30 import com.google.turbine.diag.TurbineError; 31 import com.google.turbine.lower.IntegrationTestSupport; 32 import com.google.turbine.model.TurbineElementType; 33 import com.google.turbine.model.TurbineFlag; 34 import com.google.turbine.parse.Parser; 35 import com.google.turbine.tree.Tree; 36 import java.io.OutputStream; 37 import java.nio.file.Files; 38 import java.nio.file.Path; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Optional; 43 import java.util.jar.JarEntry; 44 import java.util.jar.JarOutputStream; 45 import org.junit.Rule; 46 import org.junit.Test; 47 import org.junit.rules.TemporaryFolder; 48 import org.junit.runner.RunWith; 49 import org.junit.runners.JUnit4; 50 51 @RunWith(JUnit4.class) 52 public class BinderTest { 53 54 @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); 55 56 @Test 57 public void hello() throws Exception { 58 List<Tree.CompUnit> units = new ArrayList<>(); 59 units.add( 60 parseLines( 61 "package a;", // 62 "public class A {", 63 " public class Inner1 extends b.B {", 64 " }", 65 " public class Inner2 extends A.Inner1 {", 66 " }", 67 "}")); 68 units.add( 69 parseLines( 70 "package b;", // 71 "import a.A;", 72 "public class B extends A {", 73 "}")); 74 75 ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = 76 Binder.bind( 77 units, 78 ClassPathBinder.bindClasspath(ImmutableList.of()), 79 TURBINE_BOOTCLASSPATH, 80 /* moduleVersion=*/ Optional.empty()) 81 .units(); 82 83 assertThat(bound.keySet()) 84 .containsExactly( 85 new ClassSymbol("a/A"), 86 new ClassSymbol("a/A$Inner1"), 87 new ClassSymbol("a/A$Inner2"), 88 new ClassSymbol("b/B")); 89 90 SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A")); 91 assertThat(a.superclass()).isEqualTo(new ClassSymbol("java/lang/Object")); 92 assertThat(a.interfaces()).isEmpty(); 93 94 assertThat(bound.get(new ClassSymbol("a/A$Inner1")).superclass()) 95 .isEqualTo(new ClassSymbol("b/B")); 96 97 assertThat(bound.get(new ClassSymbol("a/A$Inner2")).superclass()) 98 .isEqualTo(new ClassSymbol("a/A$Inner1")); 99 100 SourceTypeBoundClass b = bound.get(new ClassSymbol("b/B")); 101 assertThat(b.superclass()).isEqualTo(new ClassSymbol("a/A")); 102 } 103 104 @Test 105 public void interfaces() throws Exception { 106 List<Tree.CompUnit> units = new ArrayList<>(); 107 units.add( 108 parseLines( 109 "package com.i;", // 110 "public interface I {", 111 " public class IInner {", 112 " }", 113 "}")); 114 units.add( 115 parseLines( 116 "package b;", // 117 "class B implements com.i.I {", 118 " class BInner extends IInner {}", 119 "}")); 120 121 ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = 122 Binder.bind( 123 units, 124 ClassPathBinder.bindClasspath(ImmutableList.of()), 125 TURBINE_BOOTCLASSPATH, 126 /* moduleVersion=*/ Optional.empty()) 127 .units(); 128 129 assertThat(bound.keySet()) 130 .containsExactly( 131 new ClassSymbol("com/i/I"), 132 new ClassSymbol("com/i/I$IInner"), 133 new ClassSymbol("b/B"), 134 new ClassSymbol("b/B$BInner")); 135 136 assertThat(bound.get(new ClassSymbol("b/B")).interfaces()) 137 .containsExactly(new ClassSymbol("com/i/I")); 138 139 assertThat(bound.get(new ClassSymbol("b/B$BInner")).superclass()) 140 .isEqualTo(new ClassSymbol("com/i/I$IInner")); 141 assertThat(bound.get(new ClassSymbol("b/B$BInner")).interfaces()).isEmpty(); 142 } 143 144 @Test 145 public void imports() throws Exception { 146 List<Tree.CompUnit> units = new ArrayList<>(); 147 units.add( 148 parseLines( 149 "package com.test;", // 150 "public class Test {", 151 " public static class Inner {}", 152 "}")); 153 units.add( 154 parseLines( 155 "package other;", // 156 "import com.test.Test.Inner;", 157 "import no.such.Class;", // imports are resolved lazily on-demand 158 "public class Foo extends Inner {", 159 "}")); 160 161 ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = 162 Binder.bind( 163 units, 164 ClassPathBinder.bindClasspath(ImmutableList.of()), 165 TURBINE_BOOTCLASSPATH, 166 /* moduleVersion=*/ Optional.empty()) 167 .units(); 168 169 assertThat(bound.get(new ClassSymbol("other/Foo")).superclass()) 170 .isEqualTo(new ClassSymbol("com/test/Test$Inner")); 171 } 172 173 @Test 174 public void cycle() throws Exception { 175 List<Tree.CompUnit> units = new ArrayList<>(); 176 units.add( 177 parseLines( 178 "package a;", // 179 "import b.B;", 180 "public class A extends B.Inner {", 181 " class Inner {}", 182 "}")); 183 units.add( 184 parseLines( 185 "package b;", // 186 "import a.A;", 187 "public class B extends A.Inner {", 188 " class Inner {}", 189 "}")); 190 191 try { 192 Binder.bind( 193 units, 194 ClassPathBinder.bindClasspath(ImmutableList.of()), 195 TURBINE_BOOTCLASSPATH, 196 /* moduleVersion=*/ Optional.empty()); 197 fail(); 198 } catch (TurbineError e) { 199 assertThat(e).hasMessageThat().contains("cycle in class hierarchy: a.A -> b.B -> a.A"); 200 } 201 } 202 203 @Test 204 public void annotationDeclaration() throws Exception { 205 List<Tree.CompUnit> units = new ArrayList<>(); 206 units.add( 207 parseLines( 208 "package com.test;", // 209 "public @interface Annotation {", 210 "}")); 211 212 ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = 213 Binder.bind( 214 units, 215 ClassPathBinder.bindClasspath(ImmutableList.of()), 216 TURBINE_BOOTCLASSPATH, 217 /* moduleVersion=*/ Optional.empty()) 218 .units(); 219 220 SourceTypeBoundClass a = bound.get(new ClassSymbol("com/test/Annotation")); 221 assertThat(a.access()) 222 .isEqualTo( 223 TurbineFlag.ACC_PUBLIC 224 | TurbineFlag.ACC_INTERFACE 225 | TurbineFlag.ACC_ABSTRACT 226 | TurbineFlag.ACC_ANNOTATION); 227 assertThat(a.superclass()).isEqualTo(new ClassSymbol("java/lang/Object")); 228 assertThat(a.interfaces()).containsExactly(new ClassSymbol("java/lang/annotation/Annotation")); 229 } 230 231 @Test 232 public void helloBytecode() throws Exception { 233 List<Tree.CompUnit> units = new ArrayList<>(); 234 units.add( 235 parseLines( 236 "package a;", // 237 "import java.util.Map.Entry;", 238 "public class A implements Entry {", 239 "}")); 240 241 ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = 242 Binder.bind( 243 units, 244 ClassPathBinder.bindClasspath(ImmutableList.of()), 245 TURBINE_BOOTCLASSPATH, 246 /* moduleVersion=*/ Optional.empty()) 247 .units(); 248 249 SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A")); 250 assertThat(a.interfaces()).containsExactly(new ClassSymbol("java/util/Map$Entry")); 251 } 252 253 @Test 254 public void incompleteClasspath() throws Exception { 255 256 Map<String, byte[]> lib = 257 IntegrationTestSupport.runJavac( 258 ImmutableMap.of( 259 "A.java", "class A {}", 260 "B.java", "class B extends A {}"), 261 ImmutableList.of()); 262 263 // create a jar containing only B 264 Path libJar = temporaryFolder.newFile("lib.jar").toPath(); 265 try (OutputStream os = Files.newOutputStream(libJar); 266 JarOutputStream jos = new JarOutputStream(os)) { 267 jos.putNextEntry(new JarEntry("B.class")); 268 jos.write(lib.get("B")); 269 } 270 271 List<Tree.CompUnit> units = new ArrayList<>(); 272 units.add( 273 parseLines( 274 "import java.lang.annotation.Target;", 275 "import java.lang.annotation.ElementType;", 276 "public class C implements B {", 277 " @Target(ElementType.TYPE_USE)", 278 " @interface A {};", 279 "}")); 280 281 ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = 282 Binder.bind( 283 units, 284 ClassPathBinder.bindClasspath(ImmutableList.of(libJar)), 285 TURBINE_BOOTCLASSPATH, 286 /* moduleVersion=*/ Optional.empty()) 287 .units(); 288 289 SourceTypeBoundClass a = bound.get(new ClassSymbol("C$A")); 290 assertThat(a.annotationMetadata().target()).containsExactly(TurbineElementType.TYPE_USE); 291 } 292 293 // Test that we don't crash on invalid constant field initializers. 294 // (Error reporting is deferred to javac.) 295 @Test 296 public void invalidConst() throws Exception { 297 List<Tree.CompUnit> units = new ArrayList<>(); 298 units.add( 299 parseLines( 300 "package a;", // 301 "public class A {", 302 " public static final boolean b = true == 42;", 303 "}")); 304 305 ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = 306 Binder.bind( 307 units, 308 ClassPathBinder.bindClasspath(ImmutableList.of()), 309 TURBINE_BOOTCLASSPATH, 310 /* moduleVersion=*/ Optional.empty()) 311 .units(); 312 313 assertThat(bound.keySet()).containsExactly(new ClassSymbol("a/A")); 314 315 SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A")); 316 FieldInfo f = getOnlyElement(a.fields()); 317 assertThat(f.name()).isEqualTo("b"); 318 assertThat(f.value()).isNull(); 319 } 320 321 private Tree.CompUnit parseLines(String... lines) { 322 return Parser.parse(Joiner.on('\n').join(lines)); 323 } 324 } 325