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 17package com.google.turbine.lower; 18 19import static com.google.common.truth.Truth.assertThat; 20import static java.nio.charset.StandardCharsets.UTF_8; 21 22import com.google.common.base.Joiner; 23import com.google.common.collect.ImmutableList; 24import com.google.common.collect.ImmutableMap; 25import com.google.common.io.ByteStreams; 26import com.google.turbine.binder.Binder; 27import com.google.turbine.binder.Binder.BindingResult; 28import com.google.turbine.binder.ClassPathBinder; 29import com.google.turbine.binder.bound.SourceTypeBoundClass; 30import com.google.turbine.binder.bytecode.BytecodeBoundClass; 31import com.google.turbine.binder.env.CompoundEnv; 32import com.google.turbine.binder.env.SimpleEnv; 33import com.google.turbine.binder.lookup.TopLevelIndex; 34import com.google.turbine.binder.sym.ClassSymbol; 35import com.google.turbine.binder.sym.FieldSymbol; 36import com.google.turbine.binder.sym.MethodSymbol; 37import com.google.turbine.binder.sym.TyVarSymbol; 38import com.google.turbine.bytecode.AsmUtils; 39import com.google.turbine.bytecode.ByteReader; 40import com.google.turbine.bytecode.ConstantPoolReader; 41import com.google.turbine.model.TurbineConstantTypeKind; 42import com.google.turbine.model.TurbineFlag; 43import com.google.turbine.model.TurbineTyKind; 44import com.google.turbine.parse.Parser; 45import com.google.turbine.type.Type; 46import java.io.IOException; 47import java.io.OutputStream; 48import java.nio.file.Files; 49import java.nio.file.Path; 50import java.nio.file.Paths; 51import java.util.ArrayList; 52import java.util.LinkedHashMap; 53import java.util.List; 54import java.util.Map; 55import java.util.jar.JarEntry; 56import java.util.jar.JarOutputStream; 57import org.junit.Rule; 58import org.junit.Test; 59import org.junit.rules.TemporaryFolder; 60import org.junit.runner.RunWith; 61import org.junit.runners.JUnit4; 62import org.objectweb.asm.AnnotationVisitor; 63import org.objectweb.asm.ClassReader; 64import org.objectweb.asm.ClassVisitor; 65import org.objectweb.asm.ClassWriter; 66import org.objectweb.asm.FieldVisitor; 67import org.objectweb.asm.Opcodes; 68import org.objectweb.asm.TypePath; 69 70@RunWith(JUnit4.class) 71public class LowerTest { 72 73 @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); 74 75 private static final ImmutableList<Path> BOOTCLASSPATH = 76 ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar")); 77 78 @Test 79 public void hello() throws Exception { 80 CompoundEnv<ClassSymbol, BytecodeBoundClass> classpath = 81 ClassPathBinder.bind(ImmutableList.of(), BOOTCLASSPATH, TopLevelIndex.builder()); 82 83 ImmutableList<Type.ClassTy> interfaceTypes = 84 ImmutableList.of( 85 new Type.ClassTy( 86 ImmutableList.of( 87 new Type.ClassTy.SimpleClassTy( 88 new ClassSymbol("java/util/List"), 89 ImmutableList.of( 90 new Type.TyVar( 91 new TyVarSymbol(new ClassSymbol("test/Test"), "V"), 92 ImmutableList.of())), 93 ImmutableList.of())))); 94 Type.ClassTy xtnds = Type.ClassTy.OBJECT; 95 ImmutableMap<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tps = 96 ImmutableMap.of( 97 new TyVarSymbol(new ClassSymbol("test/Test"), "V"), 98 new SourceTypeBoundClass.TyVarInfo( 99 new Type.ClassTy( 100 ImmutableList.of( 101 new Type.ClassTy.SimpleClassTy( 102 new ClassSymbol("test/Test$Inner"), 103 ImmutableList.of(), 104 ImmutableList.of()))), 105 ImmutableList.of(), 106 ImmutableList.of())); 107 int access = TurbineFlag.ACC_SUPER | TurbineFlag.ACC_PUBLIC; 108 ImmutableList<SourceTypeBoundClass.MethodInfo> methods = 109 ImmutableList.of( 110 new SourceTypeBoundClass.MethodInfo( 111 new MethodSymbol(new ClassSymbol("test/Test"), "f"), 112 ImmutableMap.of(), 113 new Type.PrimTy(TurbineConstantTypeKind.INT, ImmutableList.of()), 114 ImmutableList.of(), 115 ImmutableList.of(), 116 TurbineFlag.ACC_STATIC | TurbineFlag.ACC_PUBLIC, 117 null, 118 null, 119 ImmutableList.of(), 120 null), 121 new SourceTypeBoundClass.MethodInfo( 122 new MethodSymbol(new ClassSymbol("test/Test"), "g"), 123 ImmutableMap.of( 124 new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "V"), 125 new SourceTypeBoundClass.TyVarInfo( 126 null, 127 ImmutableList.of( 128 new Type.ClassTy( 129 ImmutableList.of( 130 new Type.ClassTy.SimpleClassTy( 131 new ClassSymbol("java/lang/Runnable"), 132 ImmutableList.of(), 133 ImmutableList.of())))), 134 ImmutableList.of()), 135 new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "E"), 136 new SourceTypeBoundClass.TyVarInfo( 137 new Type.ClassTy( 138 ImmutableList.of( 139 new Type.ClassTy.SimpleClassTy( 140 new ClassSymbol("java/lang/Error"), 141 ImmutableList.of(), 142 ImmutableList.of()))), 143 ImmutableList.of(), 144 ImmutableList.of())), 145 Type.VOID, 146 ImmutableList.of( 147 new SourceTypeBoundClass.ParamInfo( 148 new Type.PrimTy(TurbineConstantTypeKind.INT, ImmutableList.of()), 149 "foo", 150 ImmutableList.of(), 151 0)), 152 ImmutableList.of( 153 new Type.TyVar( 154 new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "E"), 155 ImmutableList.of())), 156 TurbineFlag.ACC_PUBLIC, 157 null, 158 null, 159 ImmutableList.of(), 160 null)); 161 ImmutableList<SourceTypeBoundClass.FieldInfo> fields = 162 ImmutableList.of( 163 new SourceTypeBoundClass.FieldInfo( 164 new FieldSymbol(new ClassSymbol("test/Test"), "theField"), 165 Type.ClassTy.asNonParametricClassTy(new ClassSymbol("test/Test$Inner")), 166 TurbineFlag.ACC_STATIC | TurbineFlag.ACC_FINAL | TurbineFlag.ACC_PUBLIC, 167 ImmutableList.of(), 168 null, 169 null)); 170 ClassSymbol owner = null; 171 TurbineTyKind kind = TurbineTyKind.CLASS; 172 ImmutableMap<String, ClassSymbol> children = ImmutableMap.of(); 173 ImmutableMap<String, TyVarSymbol> tyParams = 174 ImmutableMap.of("V", new TyVarSymbol(new ClassSymbol("test/Test"), "V")); 175 176 SourceTypeBoundClass c = 177 new SourceTypeBoundClass( 178 interfaceTypes, 179 xtnds, 180 tps, 181 access, 182 methods, 183 fields, 184 owner, 185 kind, 186 children, 187 tyParams, 188 null, 189 null, 190 null, 191 null, 192 ImmutableList.of(), 193 null); 194 195 SourceTypeBoundClass i = 196 new SourceTypeBoundClass( 197 ImmutableList.of(), 198 Type.ClassTy.OBJECT, 199 ImmutableMap.of(), 200 TurbineFlag.ACC_STATIC | TurbineFlag.ACC_PROTECTED, 201 ImmutableList.of(), 202 ImmutableList.of(), 203 new ClassSymbol("test/Test"), 204 TurbineTyKind.CLASS, 205 ImmutableMap.of("Inner", new ClassSymbol("test/Test$Inner")), 206 ImmutableMap.of(), 207 null, 208 null, 209 null, 210 null, 211 ImmutableList.of(), 212 null); 213 214 SimpleEnv.Builder<ClassSymbol, SourceTypeBoundClass> b = SimpleEnv.builder(); 215 b.put(new ClassSymbol("test/Test"), c); 216 b.put(new ClassSymbol("test/Test$Inner"), i); 217 218 Map<String, byte[]> bytes = 219 Lower.lowerAll( 220 ImmutableMap.of( 221 new ClassSymbol("test/Test"), c, new ClassSymbol("test/Test$Inner"), i), 222 classpath) 223 .bytes(); 224 225 assertThat(AsmUtils.textify(bytes.get("test/Test"))) 226 .isEqualTo( 227 new String( 228 ByteStreams.toByteArray( 229 LowerTest.class.getResourceAsStream("testdata/golden/outer.txt")), 230 UTF_8)); 231 assertThat(AsmUtils.textify(bytes.get("test/Test$Inner"))) 232 .isEqualTo( 233 new String( 234 ByteStreams.toByteArray( 235 LowerTest.class.getResourceAsStream("testdata/golden/inner.txt")), 236 UTF_8)); 237 } 238 239 @Test 240 public void innerClassAttributeOrder() throws IOException { 241 BindingResult bound = 242 Binder.bind( 243 ImmutableList.of( 244 Parser.parse( 245 Joiner.on('\n') 246 .join( 247 "class Test {", // 248 " class Inner {", 249 " class InnerMost {}", 250 " }", 251 "}"))), 252 ImmutableList.of(), 253 BOOTCLASSPATH); 254 Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes(); 255 List<String> attributes = new ArrayList<>(); 256 new ClassReader(lowered.get("Test$Inner$InnerMost")) 257 .accept( 258 new ClassVisitor(Opcodes.ASM5) { 259 @Override 260 public void visitInnerClass( 261 String name, String outerName, String innerName, int access) { 262 attributes.add(String.format("%s %s %s", name, outerName, innerName)); 263 } 264 }, 265 0); 266 assertThat(attributes) 267 .containsExactly("Test$Inner Test Inner", "Test$Inner$InnerMost Test$Inner InnerMost") 268 .inOrder(); 269 } 270 271 @Test 272 public void wildArrayElement() throws Exception { 273 IntegrationTestSupport.TestInput input = 274 IntegrationTestSupport.TestInput.parse( 275 new String( 276 ByteStreams.toByteArray( 277 getClass().getResourceAsStream("testdata/canon_array.test")), 278 UTF_8)); 279 280 Map<String, byte[]> actual = 281 IntegrationTestSupport.runTurbine(input.sources, ImmutableList.of(), BOOTCLASSPATH); 282 283 ByteReader reader = new ByteReader(actual.get("Test"), 0); 284 assertThat(reader.u4()).isEqualTo(0xcafebabe); // magic 285 assertThat(reader.u2()).isEqualTo(0); // minor 286 assertThat(reader.u2()).isEqualTo(52); // major 287 ConstantPoolReader pool = ConstantPoolReader.readConstantPool(reader); 288 assertThat(reader.u2()).isEqualTo(TurbineFlag.ACC_SUPER); // access 289 assertThat(pool.classInfo(reader.u2())).isEqualTo("Test"); // this 290 assertThat(pool.classInfo(reader.u2())).isEqualTo("java/lang/Object"); // super 291 assertThat(reader.u2()).isEqualTo(0); // interfaces 292 assertThat(reader.u2()).isEqualTo(1); // field count 293 assertThat(reader.u2()).isEqualTo(0); // access 294 assertThat(pool.utf8(reader.u2())).isEqualTo("i"); // name 295 assertThat(pool.utf8(reader.u2())).isEqualTo("LA$I;"); // descriptor 296 int attributesCount = reader.u2(); 297 String signature = null; 298 for (int j = 0; j < attributesCount; j++) { 299 String attributeName = pool.utf8(reader.u2()); 300 switch (attributeName) { 301 case "Signature": 302 reader.u4(); // length 303 signature = pool.utf8(reader.u2()); 304 break; 305 default: 306 reader.skip(reader.u4()); 307 break; 308 } 309 } 310 assertThat(signature).isEqualTo("LA<[*>.I;"); 311 } 312 313 @Test 314 public void typePath() throws Exception { 315 BindingResult bound = 316 Binder.bind( 317 ImmutableList.of( 318 Parser.parse( 319 Joiner.on('\n') 320 .join( 321 "import java.lang.annotation.ElementType;", 322 "import java.lang.annotation.Target;", 323 "import java.util.List;", 324 "@Target({ElementType.TYPE_USE}) @interface Anno {}", 325 "class Test {", 326 " public @Anno int[][] xs;", 327 "}"))), 328 ImmutableList.of(), 329 BOOTCLASSPATH); 330 Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes(); 331 TypePath[] path = new TypePath[1]; 332 new ClassReader(lowered.get("Test")) 333 .accept( 334 new ClassVisitor(Opcodes.ASM5) { 335 @Override 336 public FieldVisitor visitField( 337 int access, String name, String desc, String signature, Object value) { 338 return new FieldVisitor(Opcodes.ASM5) { 339 @Override 340 public AnnotationVisitor visitTypeAnnotation( 341 int typeRef, TypePath typePath, String desc, boolean visible) { 342 path[0] = typePath; 343 return null; 344 }; 345 }; 346 } 347 }, 348 0); 349 assertThat(path[0].getLength()).isEqualTo(2); 350 assertThat(path[0].getStep(0)).isEqualTo(TypePath.ARRAY_ELEMENT); 351 assertThat(path[0].getStepArgument(0)).isEqualTo(0); 352 assertThat(path[0].getStep(1)).isEqualTo(TypePath.ARRAY_ELEMENT); 353 assertThat(path[0].getStepArgument(1)).isEqualTo(0); 354 } 355 356 @Test 357 public void invalidConstants() throws Exception { 358 Path lib = temporaryFolder.newFile("lib.jar").toPath(); 359 try (OutputStream os = Files.newOutputStream(lib); 360 JarOutputStream jos = new JarOutputStream(os)) { 361 jos.putNextEntry(new JarEntry("Lib.class")); 362 363 ClassWriter cw = new ClassWriter(0); 364 cw.visit(52, Opcodes.ACC_SUPER, "Lib", null, "java/lang/Object", null); 365 cw.visitField(Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, "ZCONST", "Z", null, Integer.MAX_VALUE); 366 cw.visitField(Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, "SCONST", "S", null, Integer.MAX_VALUE); 367 jos.write(cw.toByteArray()); 368 } 369 370 ImmutableMap<String, String> input = 371 ImmutableMap.of( 372 "Test.java", 373 Joiner.on('\n') 374 .join( 375 "class Test {", 376 " static final short SCONST = Lib.SCONST + 0;", 377 " static final boolean ZCONST = Lib.ZCONST || false;", 378 "}")); 379 380 Map<String, byte[]> actual = 381 IntegrationTestSupport.runTurbine(input, ImmutableList.of(lib), BOOTCLASSPATH); 382 383 Map<String, Object> values = new LinkedHashMap<>(); 384 new ClassReader(actual.get("Test")) 385 .accept( 386 new ClassVisitor(Opcodes.ASM5) { 387 @Override 388 public FieldVisitor visitField( 389 int access, String name, String desc, String signature, Object value) { 390 values.put(name, value); 391 return super.visitField(access, name, desc, signature, value); 392 } 393 }, 394 0); 395 396 assertThat(values).containsEntry("SCONST", -1); 397 assertThat(values).containsEntry("ZCONST", 1); 398 } 399 400 @Test 401 public void deprecated() throws Exception { 402 BindingResult bound = 403 Binder.bind( 404 ImmutableList.of(Parser.parse("@Deprecated class Test {}")), 405 ImmutableList.of(), 406 BOOTCLASSPATH); 407 Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes(); 408 int[] acc = {0}; 409 new ClassReader(lowered.get("Test")) 410 .accept( 411 new ClassVisitor(Opcodes.ASM5) { 412 @Override 413 public void visit( 414 int version, 415 int access, 416 String name, 417 String signature, 418 String superName, 419 String[] interfaces) { 420 acc[0] = access; 421 } 422 }, 423 0); 424 assertThat((acc[0] & Opcodes.ACC_DEPRECATED) == Opcodes.ACC_DEPRECATED).isTrue(); 425 } 426 427 @Test 428 public void lazyImports() throws Exception { 429 ImmutableMap<String, String> sources = 430 ImmutableMap.<String, String>builder() 431 .put( 432 "b/B.java", 433 lines( 434 "package b;", // 435 "public class B {", 436 " public static class A {", 437 " public static final int X = 0;", 438 " }", 439 " public static class C {}", 440 "}")) 441 .put( 442 "anno/Anno.java", 443 lines( 444 "package anno;", // 445 "public @interface Anno {", 446 " int value() default 0;", 447 "}")) 448 .put( 449 "a/A.java", 450 lines( 451 "package a;", // 452 "import b.B;", 453 "import anno.Anno;", 454 "import static b.B.nosuch.A;", 455 "@Anno(A.X)", 456 "public class A extends B {", 457 " public A a;", 458 " public static final int X = 1;", 459 "}")) 460 .put( 461 "a/C.java", 462 lines( 463 "package c;", // 464 "import static b.B.nosuch.C;", 465 "class C {", 466 " C c;", 467 "}")) 468 .build(); 469 470 ImmutableMap<String, String> noImports; 471 { 472 ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); 473 sources.forEach( 474 (k, v) -> builder.put(k, v.replaceAll("import static b\\.B\\.nosuch\\..*;", ""))); 475 noImports = builder.build(); 476 } 477 478 Map<String, byte[]> expected = 479 IntegrationTestSupport.runJavac(noImports, ImmutableList.of(), BOOTCLASSPATH); 480 Map<String, byte[]> actual = 481 IntegrationTestSupport.runTurbine(sources, ImmutableList.of(), BOOTCLASSPATH); 482 assertThat(IntegrationTestSupport.dump(IntegrationTestSupport.sortMembers(actual))) 483 .isEqualTo(IntegrationTestSupport.dump(IntegrationTestSupport.canonicalize(expected))); 484 } 485 486 static String lines(String... lines) { 487 return Joiner.on("\n").join(lines); 488 } 489} 490