Home | History | Annotate | Download | only in binder
      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