Home | History | Annotate | Download | only in lower
      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.lower;
     18 
     19 import static com.google.common.truth.Truth.assertThat;
     20 import static java.nio.charset.StandardCharsets.UTF_8;
     21 
     22 import com.google.common.base.Joiner;
     23 import com.google.common.collect.ImmutableList;
     24 import com.google.common.collect.ImmutableMap;
     25 import com.google.common.io.ByteStreams;
     26 import com.google.turbine.binder.Binder;
     27 import com.google.turbine.binder.Binder.BindingResult;
     28 import com.google.turbine.binder.ClassPathBinder;
     29 import com.google.turbine.binder.bound.SourceTypeBoundClass;
     30 import com.google.turbine.binder.bytecode.BytecodeBoundClass;
     31 import com.google.turbine.binder.env.CompoundEnv;
     32 import com.google.turbine.binder.env.SimpleEnv;
     33 import com.google.turbine.binder.lookup.TopLevelIndex;
     34 import com.google.turbine.binder.sym.ClassSymbol;
     35 import com.google.turbine.binder.sym.FieldSymbol;
     36 import com.google.turbine.binder.sym.MethodSymbol;
     37 import com.google.turbine.binder.sym.TyVarSymbol;
     38 import com.google.turbine.bytecode.AsmUtils;
     39 import com.google.turbine.bytecode.ByteReader;
     40 import com.google.turbine.bytecode.ConstantPoolReader;
     41 import com.google.turbine.model.TurbineConstantTypeKind;
     42 import com.google.turbine.model.TurbineFlag;
     43 import com.google.turbine.model.TurbineTyKind;
     44 import com.google.turbine.parse.Parser;
     45 import com.google.turbine.type.Type;
     46 import java.io.IOException;
     47 import java.io.OutputStream;
     48 import java.nio.file.Files;
     49 import java.nio.file.Path;
     50 import java.nio.file.Paths;
     51 import java.util.ArrayList;
     52 import java.util.LinkedHashMap;
     53 import java.util.List;
     54 import java.util.Map;
     55 import java.util.jar.JarEntry;
     56 import java.util.jar.JarOutputStream;
     57 import org.junit.Rule;
     58 import org.junit.Test;
     59 import org.junit.rules.TemporaryFolder;
     60 import org.junit.runner.RunWith;
     61 import org.junit.runners.JUnit4;
     62 import org.objectweb.asm.AnnotationVisitor;
     63 import org.objectweb.asm.ClassReader;
     64 import org.objectweb.asm.ClassVisitor;
     65 import org.objectweb.asm.ClassWriter;
     66 import org.objectweb.asm.FieldVisitor;
     67 import org.objectweb.asm.Opcodes;
     68 import org.objectweb.asm.TypePath;
     69 
     70 @RunWith(JUnit4.class)
     71 public 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