diff options
author | Colin Cross <ccross@android.com> | 2021-07-16 18:15:37 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-07-16 18:15:37 +0000 |
commit | 88adfa1e21a5c8e806580f8a1edc6f3669d0d167 (patch) | |
tree | f3960bf41b61a8f29eb4ddc3bdc03da8c15976ab | |
parent | 2a4cb0ec252bf941a985428a368ca7a81707e7d6 (diff) | |
parent | aef8ef46bb44b43cb8b08f774a7f95561b1d219f (diff) | |
download | turbine-android-s-qpr3-beta-1.tar.gz |
Merge remote-tracking branch 'aosp/upstream-main' am: 54111aedb4 am: aef8ef46bbandroid-t-preview-2android-t-preview-1android-t-beta-3android-s-v2-preview-2android-s-v2-preview-1android-s-v2-beta-3android-s-v2-beta-2android-s-qpr3-beta-1android-t-preview-1android-s-v2-preview-1android-s-v2-beta-3android-s-qpr3-beta-1
Original change: https://android-review.googlesource.com/c/platform/external/turbine/+/1768552
Change-Id: I910296cd5aef4ac8205f5fcbad6be2c97dbae79b
101 files changed, 3219 insertions, 1551 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..87582f0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +# Copyright 2020 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + name: "JDK ${{ matrix.java }} on ${{ matrix.os }}" + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + java: [ 16, 11, 8 ] + experimental: [ false ] + include: + # Only test on macos and windows with a single recent JDK to avoid a + # combinatorial explosion of test configurations. + - os: macos-latest + java: 16 + experimental: false + - os: windows-latest + java: 16 + experimental: false + - os: ubuntu-latest + java: 17-ea + experimental: true + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + steps: + - name: Cancel previous + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + - name: 'Check out repository' + uses: actions/checkout@v2 + - name: 'Cache local Maven repository' + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: 'Set up JDK ${{ matrix.java }}' + uses: actions/setup-java@v2 + with: + java-version: ${{ matrix.java }} + distribution: 'adopt' + - name: 'Install' + shell: bash + run: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V + - name: 'Test' + shell: bash + run: mvn test -B + - name: 'Javadoc' + shell: bash + run: mvn javadoc:aggregate diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8113f5f..0000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: java - -jdk: - - openjdk8 - - openjdk9 - - openjdk10 - - openjdk11 - - openjdk-ea - -matrix: - allow_failures: - - jdk: openjdk-ea - -# see https://github.com/travis-ci/travis-ci/issues/8408 -before_install: -- unset _JAVA_OPTIONS - -# use travis-ci docker based infrastructure -sudo: false - -cache: - directories: - - $HOME/.m2 - -script: -- mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -- mvn test -B diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 44ff736..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,31 +0,0 @@ -os: Visual Studio 2015 - -environment: - matrix: - - JAVA_HOME: C:\Program Files\Java\jdk9 - - JAVA_HOME: C:\Program Files\Java\jdk10 - -install: - - ps: | - Add-Type -AssemblyName System.IO.Compression.FileSystem - if (!(Test-Path -Path "C:\maven" )) { - (new-object System.Net.WebClient).DownloadFile( - 'http://www.us.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip', - 'C:\maven-bin.zip' - ) - [System.IO.Compression.ZipFile]::ExtractToDirectory("C:\maven-bin.zip", "C:\maven") - } - - cmd: SET PATH=C:\maven\apache-maven-3.3.9\bin;%JAVA_HOME%\bin;%PATH% - - cmd: SET MAVEN_OPTS=-XX:MaxPermSize=2g -Xmx4g - - cmd: SET JAVA_OPTS=-XX:MaxPermSize=2g -Xmx4g - -build_script: - - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V - -test_script: - - mvn test -B - -cache: - - C:\maven\ - - C:\Users\appveyor\.m2 - diff --git a/java/com/google/common/escape/SourceCodeEscapers.java b/java/com/google/common/escape/SourceCodeEscapers.java index 4a1aa99..c0f9d6b 100644 --- a/java/com/google/common/escape/SourceCodeEscapers.java +++ b/java/com/google/common/escape/SourceCodeEscapers.java @@ -22,7 +22,7 @@ import java.util.Map; /** * A factory for Escaper instances used to escape strings for safe use in Java. * - * <p>This is a subset of source code escapers that are in the process of being open-sources as part + * <p>This is a subset of source code escapers that are in the process of being open-sourced as part * of guava, see: https://github.com/google/guava/issues/1620 */ // TODO(cushon): migrate to the guava version once it is open-sourced, and delete this @@ -43,8 +43,8 @@ public final class SourceCodeEscapers { * safely be included in either a Java character literal or string literal. This is the preferred * way to escape Java characters for use in String or character literals. * - * <p>See: <a href= "http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089" - * >The Java Language Specification</a> for more details. + * <p>See: <a href="https://docs.oracle.com/javase/specs/jls/se14/html/jls-3.html#jls-3.10.6" >The + * Java Language Specification</a> for more details. */ public static CharEscaper javaCharEscaper() { return JAVA_CHAR_ESCAPER; @@ -66,7 +66,7 @@ public final class SourceCodeEscapers { } // This escaper does not produce octal escape sequences. See: - // http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089 + // https://docs.oracle.com/javase/specs/jls/se14/html/jls-3.html#jls-3.10.6 // "Octal escapes are provided for compatibility with C, but can express // only Unicode values \u0000 through \u00FF, so Unicode escapes are // usually preferred." diff --git a/java/com/google/turbine/binder/Binder.java b/java/com/google/turbine/binder/Binder.java index 0e3f41f..6c828b3 100644 --- a/java/com/google/turbine/binder/Binder.java +++ b/java/com/google/turbine/binder/Binder.java @@ -54,6 +54,7 @@ import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.ModuleSymbol; import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineDiagnostic; import com.google.turbine.diag.TurbineError; import com.google.turbine.diag.TurbineError.ErrorKind; import com.google.turbine.diag.TurbineLog; @@ -68,7 +69,7 @@ import java.util.Optional; import javax.annotation.processing.Processor; /** The entry point for analysis. */ -public class Binder { +public final class Binder { /** Binds symbols and types to the given compilation units. */ public static BindingResult bind( @@ -87,19 +88,28 @@ public class Binder { ClassPath bootclasspath, Optional<String> moduleVersion) { TurbineLog log = new TurbineLog(); - BindingResult br = - bind( - log, - units, - /* generatedSources= */ ImmutableMap.of(), - /* generatedClasses= */ ImmutableMap.of(), - classpath, - bootclasspath, - moduleVersion); - if (!processorInfo.processors().isEmpty() && !units.isEmpty()) { + BindingResult br; + try { br = - Processing.process( - log, units, classpath, processorInfo, bootclasspath, br, moduleVersion); + bind( + log, + units, + /* generatedSources= */ ImmutableMap.of(), + /* generatedClasses= */ ImmutableMap.of(), + classpath, + bootclasspath, + moduleVersion); + if (!processorInfo.processors().isEmpty() && !units.isEmpty()) { + br = + Processing.process( + log, units, classpath, processorInfo, bootclasspath, br, moduleVersion); + } + } catch (TurbineError turbineError) { + throw new TurbineError( + ImmutableList.<TurbineDiagnostic>builder() + .addAll(log.diagnostics()) + .addAll(turbineError.diagnostics()) + .build()); } log.maybeThrow(); return br; @@ -540,4 +550,6 @@ public class Binder { units, modules, classPathEnv, tli, generatedSources, generatedClasses, statistics); } } + + private Binder() {} } diff --git a/java/com/google/turbine/binder/CanonicalTypeBinder.java b/java/com/google/turbine/binder/CanonicalTypeBinder.java index a2f045a..ae82b4f 100644 --- a/java/com/google/turbine/binder/CanonicalTypeBinder.java +++ b/java/com/google/turbine/binder/CanonicalTypeBinder.java @@ -38,19 +38,15 @@ import java.util.Map; /** * Canonicalizes all qualified types in a {@link SourceTypeBoundClass} using {@link Canonicalize}. */ -public class CanonicalTypeBinder { +public final class CanonicalTypeBinder { static SourceTypeBoundClass bind( ClassSymbol sym, SourceTypeBoundClass base, Env<ClassSymbol, TypeBoundClass> env) { - ClassTy superClassType = null; - if (base.superClassType() != null && base.superClassType().tyKind() == TyKind.CLASS_TY) { + Type superClassType = base.superClassType(); + if (superClassType != null && superClassType.tyKind() == TyKind.CLASS_TY) { superClassType = Canonicalize.canonicalizeClassTy( - base.source(), - base.decl().position(), - env, - base.owner(), - (ClassTy) base.superClassType()); + base.source(), base.decl().position(), env, base.owner(), (ClassTy) superClassType); } ImmutableList.Builder<Type> interfaceTypes = ImmutableList.builder(); for (Type i : base.interfaceTypes()) { @@ -133,9 +129,7 @@ public class CanonicalTypeBinder { base.defaultValue(), base.decl(), base.annotations(), - base.receiver() != null - ? param(source, base.decl().position(), env, sym, base.receiver()) - : null)); + base.receiver() != null ? param(source, pos, env, sym, base.receiver()) : null)); } return result.build(); } @@ -181,4 +175,6 @@ public class CanonicalTypeBinder { } return result.build(); } + + private CanonicalTypeBinder() {} } diff --git a/java/com/google/turbine/binder/ClassPathBinder.java b/java/com/google/turbine/binder/ClassPathBinder.java index 8aead80..1825c23 100644 --- a/java/com/google/turbine/binder/ClassPathBinder.java +++ b/java/com/google/turbine/binder/ClassPathBinder.java @@ -38,7 +38,7 @@ import java.util.LinkedHashMap; import java.util.Map; /** Sets up an environment for symbols on the classpath. */ -public class ClassPathBinder { +public final class ClassPathBinder { /** * The prefix for repackaged transitive dependencies; see {@link @@ -148,4 +148,6 @@ public class ClassPathBinder { } }); } + + private ClassPathBinder() {} } diff --git a/java/com/google/turbine/binder/CompUnitPreprocessor.java b/java/com/google/turbine/binder/CompUnitPreprocessor.java index ed70e88..9e9a0bb 100644 --- a/java/com/google/turbine/binder/CompUnitPreprocessor.java +++ b/java/com/google/turbine/binder/CompUnitPreprocessor.java @@ -45,7 +45,7 @@ import java.util.Set; * Processes compilation units before binding, creating symbols for type declarations and desugaring * access modifiers. */ -public class CompUnitPreprocessor { +public final class CompUnitPreprocessor { /** A pre-processed compilation unit. */ public static class PreprocessedCompUnit { @@ -222,4 +222,6 @@ public class CompUnitPreprocessor { TurbineTyKind.INTERFACE, /* javadoc= */ null); } + + private CompUnitPreprocessor() {} } diff --git a/java/com/google/turbine/binder/ConstBinder.java b/java/com/google/turbine/binder/ConstBinder.java index 3a41e94..8511183 100644 --- a/java/com/google/turbine/binder/ConstBinder.java +++ b/java/com/google/turbine/binder/ConstBinder.java @@ -16,6 +16,8 @@ package com.google.turbine.binder; +import static java.util.Objects.requireNonNull; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -196,6 +198,9 @@ public class ConstBinder { private static RetentionPolicy bindRetention(AnnoInfo annotation) { Const value = annotation.values().get("value"); + if (value == null) { + return null; + } if (value.kind() != Kind.ENUM_CONSTANT) { return null; } @@ -208,7 +213,8 @@ public class ConstBinder { private static ImmutableSet<TurbineElementType> bindTarget(AnnoInfo annotation) { ImmutableSet.Builder<TurbineElementType> result = ImmutableSet.builder(); - Const val = annotation.values().get("value"); + // requireNonNull is safe because java.lang.annotation.Target declares `value`. + Const val = requireNonNull(annotation.values().get("value")); switch (val.kind()) { case ARRAY: for (Const element : ((ArrayInitValue) val).elements()) { @@ -227,7 +233,8 @@ public class ConstBinder { } private static ClassSymbol bindRepeatable(AnnoInfo annotation) { - Const value = annotation.values().get("value"); + // requireNonNull is safe because java.lang.annotation.Repeatable declares `value`. + Const value = requireNonNull(annotation.values().get("value")); if (value.kind() != Kind.CLASS_LITERAL) { return null; } @@ -268,11 +275,12 @@ public class ConstBinder { if ((base.access() & TurbineFlag.ACC_FINAL) == 0) { return null; } - switch (base.type().tyKind()) { + Type type = base.type(); + switch (type.tyKind()) { case PRIM_TY: break; case CLASS_TY: - if (((Type.ClassTy) base.type()).sym().equals(ClassSymbol.STRING)) { + if (((Type.ClassTy) type).sym().equals(ClassSymbol.STRING)) { break; } // falls through @@ -280,8 +288,11 @@ public class ConstBinder { return null; } Value value = constantEnv.get(base.sym()); - if (value != null) { - value = (Value) ConstEvaluator.cast(base.type(), value); + if (value == null) { + return null; + } + if (type.tyKind().equals(TyKind.PRIM_TY)) { + value = ConstEvaluator.coerce(value, ((Type.PrimTy) type).primkind()); } return value; } diff --git a/java/com/google/turbine/binder/ConstEvaluator.java b/java/com/google/turbine/binder/ConstEvaluator.java index 9d5f042..bef98a7 100644 --- a/java/com/google/turbine/binder/ConstEvaluator.java +++ b/java/com/google/turbine/binder/ConstEvaluator.java @@ -47,6 +47,7 @@ import com.google.turbine.model.Const.ConstCastError; import com.google.turbine.model.Const.Value; import com.google.turbine.model.TurbineConstantTypeKind; import com.google.turbine.model.TurbineFlag; +import com.google.turbine.model.TurbineTyKind; import com.google.turbine.tree.Tree; import com.google.turbine.tree.Tree.ArrayInit; import com.google.turbine.tree.Tree.Binary; @@ -126,22 +127,13 @@ public strictfp class ConstEvaluator { } switch (a.constantTypeKind()) { case CHAR: - return new Const.CharValue(((com.google.turbine.model.Const.CharValue) a).value()); case INT: - return new Const.IntValue(((com.google.turbine.model.Const.IntValue) a).value()); case LONG: - return new Const.LongValue(((com.google.turbine.model.Const.LongValue) a).value()); case FLOAT: - return new Const.FloatValue(((com.google.turbine.model.Const.FloatValue) a).value()); case DOUBLE: - return new Const.DoubleValue( - ((com.google.turbine.model.Const.DoubleValue) a).value()); case BOOLEAN: - return new Const.BooleanValue( - ((com.google.turbine.model.Const.BooleanValue) a).value()); case STRING: - return new Const.StringValue( - ((com.google.turbine.model.Const.StringValue) a).value()); + return a; case SHORT: case BYTE: case NULL: @@ -318,20 +310,24 @@ public strictfp class ConstEvaluator { } /** Casts the value to the given type. */ - static Const cast(Type ty, Const value) { + private Const cast(int position, Type ty, Const value) { checkNotNull(value); switch (ty.tyKind()) { case CLASS_TY: case TY_VAR: return value; case PRIM_TY: + if (!value.kind().equals(Const.Kind.PRIMITIVE)) { + throw error(position, ErrorKind.EXPRESSION_ERROR); + } return coerce((Const.Value) value, ((Type.PrimTy) ty).primkind()); default: throw new AssertionError(ty.tyKind()); } } - private static Const.Value coerce(Const.Value value, TurbineConstantTypeKind kind) { + /** Casts the constant value to the given type. */ + static Const.Value coerce(Const.Value value, TurbineConstantTypeKind kind) { switch (kind) { case BOOLEAN: return value.asBoolean(); @@ -925,12 +921,16 @@ public strictfp class ConstEvaluator { if (info.sym() == null) { return info; } - - Map<String, Type> template = new LinkedHashMap<>(); TypeBoundClass annoClass = env.get(info.sym()); + if (annoClass.kind() != TurbineTyKind.ANNOTATION) { + // we've already reported an error for non-annotation symbols used as annotations, + // skip error handling for annotation arguments + return info; + } + Map<String, MethodInfo> template = new LinkedHashMap<>(); if (annoClass != null) { for (MethodInfo method : annoClass.methods()) { - template.put(method.name(), method.returnType()); + template.put(method.name(), method); } } @@ -947,20 +947,28 @@ public strictfp class ConstEvaluator { key = "value"; expr = arg; } - Type ty = template.get(key); - if (ty == null) { - throw error( + MethodInfo methodInfo = template.remove(key); + if (methodInfo == null) { + log.error( arg.position(), ErrorKind.CANNOT_RESOLVE, String.format("element %s() in %s", key, info.sym())); + continue; } - Const value = evalAnnotationValue(expr, ty); + Const value = evalAnnotationValue(expr, methodInfo.returnType()); if (value == null) { - throw error(expr.position(), ErrorKind.EXPRESSION_ERROR); + log.error(expr.position(), ErrorKind.EXPRESSION_ERROR); + continue; } Const existing = values.put(key, value); if (existing != null) { - throw error(arg.position(), ErrorKind.INVALID_ANNOTATION_ARGUMENT); + log.error(arg.position(), ErrorKind.INVALID_ANNOTATION_ARGUMENT); + continue; + } + } + for (MethodInfo methodInfo : template.values()) { + if (!methodInfo.hasDefaultValue()) { + log.error(info.tree().position(), ErrorKind.MISSING_ANNOTATION_ARGUMENT, methodInfo.name()); } } return info.withValues(ImmutableMap.copyOf(values)); @@ -969,8 +977,9 @@ public strictfp class ConstEvaluator { private TurbineAnnotationValue evalAnno(Tree.Anno t) { LookupResult result = scope.lookup(new LookupKey(t.name())); if (result == null) { - throw error( + log.error( t.name().get(0).position(), ErrorKind.CANNOT_RESOLVE, Joiner.on(".").join(t.name())); + return null; } ClassSymbol sym = (ClassSymbol) result.sym(); for (Ident name : result.remaining()) { @@ -982,6 +991,9 @@ public strictfp class ConstEvaluator { if (sym == null) { return null; } + if (env.get(sym).kind() != TurbineTyKind.ANNOTATION) { + log.error(t.position(), ErrorKind.NOT_AN_ANNOTATION, sym); + } AnnoInfo annoInfo = evaluateAnnotation(new AnnoInfo(source, sym, t, ImmutableMap.of())); return new TurbineAnnotationValue(annoInfo); } @@ -1004,7 +1016,8 @@ public strictfp class ConstEvaluator { } Const value = eval(tree); if (value == null) { - throw error(tree.position(), ErrorKind.EXPRESSION_ERROR); + log.error(tree.position(), ErrorKind.EXPRESSION_ERROR); + return null; } switch (ty.tyKind()) { case PRIM_TY: @@ -1024,7 +1037,7 @@ public strictfp class ConstEvaluator { : ImmutableList.of(value); ImmutableList.Builder<Const> coerced = ImmutableList.builder(); for (Const element : elements) { - coerced.add(cast(elementType, element)); + coerced.add(cast(tree.position(), elementType, element)); } return new Const.ArrayInitValue(coerced.build()); } @@ -1043,7 +1056,7 @@ public strictfp class ConstEvaluator { if (value == null || value.kind() != Const.Kind.PRIMITIVE) { return null; } - return (Const.Value) cast(type, value); + return (Const.Value) cast(expression.position(), type, value); } catch (TurbineError error) { for (TurbineDiagnostic diagnostic : error.diagnostics()) { switch (diagnostic.kind()) { diff --git a/java/com/google/turbine/binder/CtSymClassBinder.java b/java/com/google/turbine/binder/CtSymClassBinder.java index a6f1b3d..1d7ece7 100644 --- a/java/com/google/turbine/binder/CtSymClassBinder.java +++ b/java/com/google/turbine/binder/CtSymClassBinder.java @@ -16,11 +16,15 @@ package com.google.turbine.binder; +import static com.google.common.base.Ascii.toUpperCase; import static com.google.common.base.StandardSystemProperty.JAVA_HOME; +import static java.util.Objects.requireNonNull; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.Ints; import com.google.turbine.binder.bound.ModuleInfo; import com.google.turbine.binder.bytecode.BytecodeBinder; import com.google.turbine.binder.bytecode.BytecodeBoundClass; @@ -32,6 +36,7 @@ import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.ModuleSymbol; import com.google.turbine.zip.Zip; import java.io.IOException; +import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -40,12 +45,13 @@ import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; /** Constructs a platform {@link ClassPath} from the current JDK's ct.sym file. */ -public class CtSymClassBinder { +public final class CtSymClassBinder { @Nullable public static ClassPath bind(String version) throws IOException { - Path javaHome = Paths.get(JAVA_HOME.value()); - Path ctSym = javaHome.resolve("lib/ct.sym"); + String javaHome = JAVA_HOME.value(); + requireNonNull(javaHome, "attempted to use --release, but JAVA_HOME is not set"); + Path ctSym = Paths.get(javaHome).resolve("lib/ct.sym"); if (!Files.exists(ctSym)) { throw new IllegalStateException("lib/ct.sym does not exist in " + javaHome); } @@ -59,7 +65,9 @@ public class CtSymClassBinder { } }; // ct.sym contains directories whose names are the concatentation of a list of target versions - // (e.g. 789) and which contain interface class files with a .sig extension. + // formatted as a single character 0-9 or A-Z (e.g. 789A) and which contain interface class + // files with a .sig extension. + String releaseString = formatReleaseVersion(version); for (Zip.Entry ze : new Zip.ZipIterable(ctSym)) { String name = ze.name(); if (!name.endsWith(".sig")) { @@ -70,10 +78,13 @@ public class CtSymClassBinder { continue; } // check if the directory matches the desired release - // TODO(cushon): what happens when version numbers contain more than one digit? - if (!ze.name().substring(0, idx).contains(version)) { + if (!ze.name().substring(0, idx).contains(releaseString)) { continue; } + if (isAtLeastJDK12()) { + // JDK >= 12 includes the module name as a prefix + idx = name.indexOf('/', idx + 1); + } if (name.substring(name.lastIndexOf('/') + 1).equals("module-info.sig")) { ModuleInfo moduleInfo = BytecodeBinder.bindModuleInfo(name, toByteArrayOrDie(ze)); modules.put(new ModuleSymbol(moduleInfo.name()), moduleInfo); @@ -122,4 +133,28 @@ public class CtSymClassBinder { } }); } + + @VisibleForTesting + static String formatReleaseVersion(String version) { + Integer n = Ints.tryParse(version); + if (n == null || n <= 4 || n >= 36) { + throw new IllegalArgumentException("invalid release version: " + version); + } + return toUpperCase(Integer.toString(n, 36)); + } + + private static boolean isAtLeastJDK12() { + int major; + try { + Method versionMethod = Runtime.class.getMethod("version"); + Object version = versionMethod.invoke(null); + major = (int) version.getClass().getMethod("major").invoke(version); + } catch (ReflectiveOperationException e) { + // `Runtime.version()` was added in JDK 9 + return false; + } + return major >= 12; + } + + private CtSymClassBinder() {} } diff --git a/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java b/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java index 7e3fbda..c5de8c1 100644 --- a/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java +++ b/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java @@ -65,7 +65,7 @@ import java.util.Map; * constant binding is done, read the {@code @Target} meta-annotation for each ambiguous annotation, * and move it to the appropriate location. */ -public class DisambiguateTypeAnnotations { +public final class DisambiguateTypeAnnotations { public static SourceTypeBoundClass bind( SourceTypeBoundClass base, Env<ClassSymbol, TypeBoundClass> env) { return new SourceTypeBoundClass( @@ -317,4 +317,6 @@ public class DisambiguateTypeAnnotations { } return false; } + + private DisambiguateTypeAnnotations() {} } diff --git a/java/com/google/turbine/binder/FileManagerClassBinder.java b/java/com/google/turbine/binder/FileManagerClassBinder.java new file mode 100644 index 0000000..42a8162 --- /dev/null +++ b/java/com/google/turbine/binder/FileManagerClassBinder.java @@ -0,0 +1,235 @@ +/* + * Copyright 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.turbine.binder; + +import com.google.common.base.Joiner; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteStreams; +import com.google.turbine.binder.bound.ModuleInfo; +import com.google.turbine.binder.bytecode.BytecodeBoundClass; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.lookup.LookupKey; +import com.google.turbine.binder.lookup.LookupResult; +import com.google.turbine.binder.lookup.PackageScope; +import com.google.turbine.binder.lookup.Scope; +import com.google.turbine.binder.lookup.TopLevelIndex; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.ModuleSymbol; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import javax.tools.FileObject; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Binds a {@link StandardJavaFileManager} to an {@link ClassPath}. This can be used to share a + * filemanager (and associated IO costs) between turbine and javac when running both in the same + * process. + */ +public final class FileManagerClassBinder { + + public static ClassPath adapt(StandardJavaFileManager fileManager, StandardLocation location) { + PackageLookup packageLookup = new PackageLookup(fileManager, location); + Env<ClassSymbol, BytecodeBoundClass> env = + new Env<ClassSymbol, BytecodeBoundClass>() { + @Override + public BytecodeBoundClass get(ClassSymbol sym) { + return packageLookup.getPackage(this, sym.packageName()).get(sym); + } + }; + SimpleEnv<ModuleSymbol, ModuleInfo> moduleEnv = new SimpleEnv<>(ImmutableMap.of()); + TopLevelIndex tli = new FileManagerTopLevelIndex(env, packageLookup); + return new ClassPath() { + @Override + public Env<ClassSymbol, BytecodeBoundClass> env() { + return env; + } + + @Override + public Env<ModuleSymbol, ModuleInfo> moduleEnv() { + return moduleEnv; + } + + @Override + public TopLevelIndex index() { + return tli; + } + + @Override + public Supplier<byte[]> resource(String path) { + return packageLookup.resource(path); + } + }; + } + + private static class PackageLookup { + + private final Map<String, Map<ClassSymbol, BytecodeBoundClass>> packages = new HashMap<>(); + private final StandardJavaFileManager fileManager; + private final StandardLocation location; + + private PackageLookup(StandardJavaFileManager fileManager, StandardLocation location) { + this.fileManager = fileManager; + this.location = location; + } + + private ImmutableMap<ClassSymbol, BytecodeBoundClass> listPackage( + Env<ClassSymbol, BytecodeBoundClass> env, String packageName) throws IOException { + Map<ClassSymbol, BytecodeBoundClass> result = new HashMap<>(); + for (JavaFileObject jfo : + fileManager.list( + location, + packageName.replace('/', '.'), + EnumSet.of(JavaFileObject.Kind.CLASS), + false)) { + String binaryName = fileManager.inferBinaryName(location, jfo); + ClassSymbol sym = new ClassSymbol(binaryName.replace('.', '/')); + result.putIfAbsent( + sym, + new BytecodeBoundClass( + sym, + new Supplier<byte[]>() { + @Override + public byte[] get() { + try { + return ByteStreams.toByteArray(jfo.openInputStream()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + }, + env, + /* jarFile= */ null)); + } + return ImmutableMap.copyOf(result); + } + + private Map<ClassSymbol, BytecodeBoundClass> getPackage( + Env<ClassSymbol, BytecodeBoundClass> env, String key) { + return packages.computeIfAbsent( + key, + k -> { + try { + return listPackage(env, key); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + public Supplier<byte[]> resource(String resource) { + String dir; + String name; + int idx = resource.lastIndexOf('/'); + if (idx != -1) { + dir = resource.substring(0, idx + 1); + name = resource.substring(idx + 1, resource.length()); + } else { + dir = ""; + name = resource; + } + FileObject fileObject; + try { + fileObject = fileManager.getFileForInput(location, dir, name); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + if (fileObject == null) { + return null; + } + return new Supplier<byte[]>() { + @Override + public byte[] get() { + try { + return ByteStreams.toByteArray(fileObject.openInputStream()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + }; + } + } + + private static class FileManagerTopLevelIndex implements TopLevelIndex { + private final Env<ClassSymbol, BytecodeBoundClass> env; + private final PackageLookup packageLookup; + + public FileManagerTopLevelIndex( + Env<ClassSymbol, BytecodeBoundClass> env, PackageLookup packageLookup) { + this.env = env; + this.packageLookup = packageLookup; + } + + @Override + public Scope scope() { + return new Scope() { + @Override + public @Nullable LookupResult lookup(LookupKey lookupKey) { + for (int i = lookupKey.simpleNames().size(); i > 0; i--) { + String p = Joiner.on('/').join(lookupKey.simpleNames().subList(0, i)); + ClassSymbol sym = new ClassSymbol(p); + BytecodeBoundClass r = env.get(sym); + if (r != null) { + return new LookupResult( + sym, + new LookupKey( + lookupKey.simpleNames().subList(i - 1, lookupKey.simpleNames().size()))); + } + } + return null; + } + }; + } + + @Override + public PackageScope lookupPackage(Iterable<String> names) { + String packageName = Joiner.on('/').join(names); + Map<ClassSymbol, BytecodeBoundClass> pkg = packageLookup.getPackage(env, packageName); + if (pkg.isEmpty()) { + return null; + } + return new PackageScope() { + @Override + public Iterable<ClassSymbol> classes() { + return pkg.keySet(); + } + + @Override + public @Nullable LookupResult lookup(LookupKey lookupKey) { + String className = lookupKey.first().value(); + if (!packageName.isEmpty()) { + className = packageName + "/" + className; + } + ClassSymbol sym = new ClassSymbol(className); + if (!pkg.containsKey(sym)) { + return null; + } + return new LookupResult(sym, lookupKey); + } + }; + } + } + + private FileManagerClassBinder() {} +} diff --git a/java/com/google/turbine/binder/ModuleBinder.java b/java/com/google/turbine/binder/ModuleBinder.java index 748ff39..04ce81d 100644 --- a/java/com/google/turbine/binder/ModuleBinder.java +++ b/java/com/google/turbine/binder/ModuleBinder.java @@ -216,11 +216,12 @@ public class ModuleBinder { } ClassSymbol sym = (ClassSymbol) result.sym(); for (Tree.Ident name : result.remaining()) { - sym = Resolve.resolve(env, /* origin= */ null, sym, name); - if (sym == null) { + ClassSymbol next = Resolve.resolve(env, /* origin= */ null, sym, name); + if (next == null) { throw error( ErrorKind.SYMBOL_NOT_FOUND, pos, new ClassSymbol(sym.binaryName() + '$' + name)); } + sym = next; } return sym; } diff --git a/java/com/google/turbine/binder/Processing.java b/java/com/google/turbine/binder/Processing.java index ecdf195..16407aa 100644 --- a/java/com/google/turbine/binder/Processing.java +++ b/java/com/google/turbine/binder/Processing.java @@ -16,7 +16,10 @@ package com.google.turbine.binder; +import static java.util.Objects.requireNonNull; + import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Stopwatch; @@ -27,6 +30,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Sets; +import com.google.common.primitives.Ints; import com.google.turbine.binder.Binder.BindingResult; import com.google.turbine.binder.Binder.Statistics; import com.google.turbine.binder.bound.SourceTypeBoundClass; @@ -56,7 +60,6 @@ import java.nio.file.Paths; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -76,6 +79,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; /** Top level annotation processing logic, see also {@link Binder}. */ public class Processing { + @Nullable static BindingResult process( TurbineLog log, final ImmutableList<CompUnit> initialSources, @@ -131,19 +135,13 @@ public class Processing { try (Timers.Timer unused = timers.start(processor)) { processor.init(processingEnv); } catch (Throwable t) { - reportProcessorCrash(log, processor, t); + logProcessorCrash(log, processor, t); + return null; } } - Map<Processor, Pattern> wanted = new HashMap<>(); - for (Processor processor : processorInfo.processors()) { - List<String> patterns = new ArrayList<>(); - for (String supportedAnnotationType : processor.getSupportedAnnotationTypes()) { - // TODO(b/139026291): this handling of getSupportedAnnotationTypes isn't correct - patterns.add(supportedAnnotationType.replace("*", ".*")); - } - wanted.put(processor, Pattern.compile(Joiner.on('|').join(patterns))); - } + ImmutableMap<Processor, SupportedAnnotationTypes> wanted = + initializeSupportedAnnotationTypes(processorInfo); Set<ClassSymbol> allSymbols = new HashSet<>(); @@ -163,12 +161,14 @@ public class Processing { } ImmutableSetMultimap<ClassSymbol, Symbol> allAnnotations = getAllAnnotations(env, syms); TurbineRoundEnvironment roundEnv = null; - for (Processor processor : processorInfo.processors()) { + for (Map.Entry<Processor, SupportedAnnotationTypes> e : wanted.entrySet()) { + Processor processor = e.getKey(); + SupportedAnnotationTypes supportedAnnotationTypes = e.getValue(); Set<TypeElement> annotations = new HashSet<>(); - Pattern pattern = wanted.get(processor); - boolean run = toRun.contains(processor); + boolean run = supportedAnnotationTypes.everything() || toRun.contains(processor); for (ClassSymbol a : allAnnotations.keys()) { - if (pattern.matcher(a.toString()).matches()) { + if (supportedAnnotationTypes.everything() + || supportedAnnotationTypes.pattern().matcher(a.toString()).matches()) { annotations.add(factory.typeElement(a)); run = true; } @@ -184,7 +184,8 @@ public class Processing { // TODO(cushon): consider disallowing this, or reporting a diagnostic processor.process(annotations, roundEnv); } catch (Throwable t) { - reportProcessorCrash(log, processor, t); + logProcessorCrash(log, processor, t); + return null; } } } @@ -197,7 +198,7 @@ public class Processing { } errorRaised = log.errorRaised(); if (errorRaised) { - log.maybeThrow(); + break; } log.clear(); result = @@ -228,7 +229,8 @@ public class Processing { try (Timers.Timer unused = timers.start(processor)) { processor.process(ImmutableSet.of(), roundEnv); } catch (Throwable t) { - reportProcessorCrash(log, processor, t); + logProcessorCrash(log, processor, t); + return null; } } @@ -249,7 +251,9 @@ public class Processing { classpath, bootclasspath, moduleVersion); - log.maybeThrow(); + if (log.anyErrors()) { + return null; + } } if (!filer.generatedClasses().isEmpty()) { @@ -267,13 +271,44 @@ public class Processing { return result; } - private static void reportProcessorCrash(TurbineLog log, Processor processor, Throwable t) { + private static ImmutableMap<Processor, SupportedAnnotationTypes> + initializeSupportedAnnotationTypes(ProcessorInfo processorInfo) { + ImmutableMap.Builder<Processor, SupportedAnnotationTypes> result = ImmutableMap.builder(); + for (Processor processor : processorInfo.processors()) { + result.put(processor, SupportedAnnotationTypes.create(processor)); + } + return result.build(); + } + + @AutoValue + abstract static class SupportedAnnotationTypes { + + abstract boolean everything(); + + abstract Pattern pattern(); + + static SupportedAnnotationTypes create(Processor processor) { + List<String> patterns = new ArrayList<>(); + boolean everything = false; + for (String supportedAnnotationType : processor.getSupportedAnnotationTypes()) { + if (supportedAnnotationType.equals("*")) { + everything = true; + } else { + // TODO(b/139026291): this handling of getSupportedAnnotationTypes isn't correct + patterns.add(supportedAnnotationType); + } + } + return new AutoValue_Processing_SupportedAnnotationTypes( + everything, Pattern.compile(Joiner.on('|').join(patterns))); + } + } + + private static void logProcessorCrash(TurbineLog log, Processor processor, Throwable t) { log.diagnostic( Diagnostic.Kind.ERROR, String.format( "An exception occurred in %s:\n%s", processor.getClass().getCanonicalName(), Throwables.getStackTraceAsString(t))); - log.maybeThrow(); } /** Returns a map from annotations present in the compilation to the annotated elements. */ @@ -313,7 +348,7 @@ public class Processing { } // TODO(cushon): consider memoizing this (or isAnnotationInherited) if they show up in profiles - private static Set<ClassSymbol> inheritedAnnotations( + private static ImmutableSet<ClassSymbol> inheritedAnnotations( Set<ClassSymbol> seen, ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env) { ImmutableSet.Builder<ClassSymbol> result = ImmutableSet.builder(); ClassSymbol curr = sym; @@ -360,87 +395,110 @@ public class Processing { public static ProcessorInfo initializeProcessors( ImmutableList<String> javacopts, - ImmutableList<String> processorPath, ImmutableSet<String> processorNames, - ImmutableSet<String> builtinProcessors) - throws MalformedURLException { - ClassLoader processorLoader = null; + ClassLoader processorLoader) { + if (processorNames.isEmpty() || javacopts.contains("-proc:none")) { + return ProcessorInfo.empty(); + } + ImmutableList<Processor> processors = instantiateProcessors(processorNames, processorLoader); + ImmutableMap<String, String> processorOptions = processorOptions(javacopts); + SourceVersion sourceVersion = parseSourceVersion(javacopts); + return ProcessorInfo.create(processors, processorLoader, processorOptions, sourceVersion); + } + + private static ImmutableList<Processor> instantiateProcessors( + ImmutableSet<String> processorNames, ClassLoader processorLoader) { ImmutableList.Builder<Processor> processors = ImmutableList.builder(); - ImmutableMap<String, String> processorOptions; - if (!processorNames.isEmpty() && !javacopts.contains("-proc:none")) { - if (!processorPath.isEmpty()) { - processorLoader = - new URLClassLoader( - toUrls(processorPath), - new ClassLoader(getPlatformClassLoader()) { - @Override - protected Class<?> findClass(String name) throws ClassNotFoundException { - if (name.startsWith("com.sun.source.") - || name.startsWith("com.sun.tools.") - || name.startsWith("com.google.common.collect.") - || name.startsWith("com.google.common.base.") - || name.startsWith("com.google.common.graph.") - || name.startsWith("com.google.devtools.build.buildjar.javac.statistics.") - || name.startsWith("dagger.model.") - || name.startsWith("dagger.spi.") - || name.equals("com.google.turbine.processing.TurbineProcessingEnvironment") - || builtinProcessors.contains(name)) { - return Class.forName(name); - } - throw new ClassNotFoundException(name); - } - }); - } else { - processorLoader = Processing.class.getClassLoader(); - } - for (String processor : processorNames) { - try { - Class<? extends Processor> clazz = - Class.forName(processor, false, processorLoader).asSubclass(Processor.class); - processors.add(clazz.getConstructor().newInstance()); - } catch (ReflectiveOperationException e) { - throw new LinkageError(e.getMessage(), e); - } + for (String processor : processorNames) { + try { + Class<? extends Processor> clazz = + Class.forName(processor, false, processorLoader).asSubclass(Processor.class); + processors.add(clazz.getConstructor().newInstance()); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); } - processorOptions = processorOptions(javacopts); - } else { - processorOptions = ImmutableMap.of(); } + return processors.build(); + } + + public static ClassLoader processorLoader( + ImmutableList<String> processorPath, ImmutableSet<String> builtinProcessors) + throws MalformedURLException { + if (processorPath.isEmpty()) { + return Processing.class.getClassLoader(); + } + return new URLClassLoader( + toUrls(processorPath), + new ClassLoader(getPlatformClassLoader()) { + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + if (name.equals("com.google.turbine.processing.TurbineProcessingEnvironment")) { + return Class.forName(name); + } + if (!builtinProcessors.isEmpty()) { + if (name.startsWith("com.sun.source.") + || name.startsWith("com.sun.tools.") + || name.startsWith("com.google.common.collect.") + || name.startsWith("com.google.common.base.") + || name.startsWith("com.google.common.graph.") + || name.startsWith("com.google.devtools.build.buildjar.javac.statistics.") + || name.startsWith("dagger.model.") + || name.startsWith("dagger.spi.") + || builtinProcessors.contains(name)) { + return Class.forName(name); + } + } + throw new ClassNotFoundException(name); + } + }); + } + + @VisibleForTesting + static SourceVersion parseSourceVersion(ImmutableList<String> javacopts) { SourceVersion sourceVersion = SourceVersion.latestSupported(); Iterator<String> it = javacopts.iterator(); while (it.hasNext()) { String option = it.next(); switch (option) { - case "-target": - if (it.hasNext()) { - String value = it.next(); - switch (value) { - case "5": - case "1.5": - sourceVersion = SourceVersion.RELEASE_5; - break; - case "6": - case "1.6": - sourceVersion = SourceVersion.RELEASE_6; - break; - case "7": - case "1.7": - sourceVersion = SourceVersion.RELEASE_7; - break; - case "8": - sourceVersion = SourceVersion.RELEASE_8; - break; - default: - break; - } + case "-source": + if (!it.hasNext()) { + throw new IllegalArgumentException("-source requires an argument"); } + sourceVersion = parseSourceVersion(it.next()); break; default: break; } } - return ProcessorInfo.create( - processors.build(), processorLoader, processorOptions, sourceVersion); + return sourceVersion; + } + + private static SourceVersion parseSourceVersion(String value) { + boolean hasPrefix = value.startsWith("1."); + Integer version = Ints.tryParse(hasPrefix ? value.substring("1.".length()) : value); + if (!isValidSourceVersion(version, hasPrefix)) { + throw new IllegalArgumentException("invalid -source version: " + value); + } + try { + return SourceVersion.valueOf("RELEASE_" + version); + } catch (IllegalArgumentException unused) { + throw new IllegalArgumentException("invalid -source version: " + value); + } + } + + private static boolean isValidSourceVersion(Integer version, boolean hasPrefix) { + if (version == null) { + return false; + } + if (version < 5) { + // the earliest source version supported by JDK 8 is Java 5 + return false; + } + if (hasPrefix && version > 10) { + // javac supports legacy `1.*` version numbers for source versions up to Java 10 + return false; + } + return true; } private static URL[] toUrls(ImmutableList<String> processorPath) throws MalformedURLException { @@ -548,9 +606,12 @@ public class Processing { ImmutableMap<String, Duration> build() { ImmutableMap.Builder<String, Duration> result = ImmutableMap.builder(); for (Map.Entry<Class<?>, Stopwatch> e : processorTimers.entrySet()) { - result.put(e.getKey().getCanonicalName(), e.getValue().elapsed()); + // requireNonNull is safe, barring bizarre processor implementations (e.g., anonymous class) + result.put(requireNonNull(e.getKey().getCanonicalName()), e.getValue().elapsed()); } return result.build(); } } + + private Processing() {} } diff --git a/java/com/google/turbine/binder/Resolve.java b/java/com/google/turbine/binder/Resolve.java index 28a8be3..66e1036 100644 --- a/java/com/google/turbine/binder/Resolve.java +++ b/java/com/google/turbine/binder/Resolve.java @@ -33,7 +33,7 @@ import java.util.Objects; import java.util.Set; /** Qualified name resolution. */ -public class Resolve { +public final class Resolve { /** * Performs JLS 6.5.5.2 qualified type name resolution of a type with the given simple name, @@ -213,4 +213,6 @@ public class Resolve { } throw new AssertionError(visibility); } + + private Resolve() {} } diff --git a/java/com/google/turbine/binder/TypeBinder.java b/java/com/google/turbine/binder/TypeBinder.java index 7b01856..a28acd9 100644 --- a/java/com/google/turbine/binder/TypeBinder.java +++ b/java/com/google/turbine/binder/TypeBinder.java @@ -16,6 +16,8 @@ package com.google.turbine.binder; +import static java.util.Objects.requireNonNull; + import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -54,6 +56,7 @@ import com.google.turbine.tree.TurbineModifier; import com.google.turbine.type.AnnoInfo; import com.google.turbine.type.Type; import com.google.turbine.type.Type.IntersectionTy; +import com.google.turbine.types.Deannotate; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashSet; @@ -394,7 +397,8 @@ public class TypeBinder { ImmutableList<Tree.TyParam> trees, CompoundScope scope, Map<String, TyVarSymbol> symbols) { ImmutableMap.Builder<TyVarSymbol, TyVarInfo> result = ImmutableMap.builder(); for (Tree.TyParam tree : trees) { - TyVarSymbol sym = symbols.get(tree.name().value()); + // `symbols` is constructed to guarantee the requireNonNull call is safe. + TyVarSymbol sym = requireNonNull(symbols.get(tree.name().value())); ImmutableList.Builder<Type> bounds = ImmutableList.builder(); for (Tree bound : tree.bounds()) { bounds.add(bindTy(scope, bound)); @@ -493,6 +497,9 @@ public class TypeBinder { == 0) { access |= TurbineFlag.ACC_ABSTRACT; } + if ((access & TurbineFlag.ACC_FINAL) == TurbineFlag.ACC_FINAL) { + log.error(t.position(), ErrorKind.UNEXPECTED_MODIFIER, TurbineModifier.FINAL); + } break; case ENUM: if (name.equals("<init>")) { @@ -618,7 +625,14 @@ public class TypeBinder { case WILD_TY: return bindWildTy(scope, (Tree.WildTy) ty); default: - return bindTy(scope, ty); + Type result = bindTy(scope, ty); + if (result.tyKind().equals(Type.TyKind.PRIM_TY)) { + // Omit type annotations when printing the type in the diagnostic, since they're + // irrelevant and could be invalid if there were deferred errors. + // TODO(cushon): consider ensuring this is done for all diagnostics that mention types + log.error(ty.position(), ErrorKind.UNEXPECTED_TYPE, Deannotate.deannotate(result)); + } + return result; } } diff --git a/java/com/google/turbine/binder/bound/AnnotationMetadata.java b/java/com/google/turbine/binder/bound/AnnotationMetadata.java index 31860b6..a4d3037 100644 --- a/java/com/google/turbine/binder/bound/AnnotationMetadata.java +++ b/java/com/google/turbine/binder/bound/AnnotationMetadata.java @@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.EnumSet; /** - * Annotation metadata, e.g. from {@link @java.lang.annotation.Target}, {@link + * Annotation metadata, e.g. from {@link java.lang.annotation.Target}, {@link * java.lang.annotation.Retention}, and {@link java.lang.annotation.Repeatable}. */ public class AnnotationMetadata { diff --git a/java/com/google/turbine/binder/bound/HeaderBoundClass.java b/java/com/google/turbine/binder/bound/HeaderBoundClass.java index 14807bb..7aeb3d8 100644 --- a/java/com/google/turbine/binder/bound/HeaderBoundClass.java +++ b/java/com/google/turbine/binder/bound/HeaderBoundClass.java @@ -30,5 +30,5 @@ public interface HeaderBoundClass extends BoundClass { ImmutableList<ClassSymbol> interfaces(); /** Declared type parameters. */ - public ImmutableMap<String, TyVarSymbol> typeParameters(); + ImmutableMap<String, TyVarSymbol> typeParameters(); } diff --git a/java/com/google/turbine/binder/bound/TypeBoundClass.java b/java/com/google/turbine/binder/bound/TypeBoundClass.java index e8933ac..99d15bb 100644 --- a/java/com/google/turbine/binder/bound/TypeBoundClass.java +++ b/java/com/google/turbine/binder/bound/TypeBoundClass.java @@ -51,7 +51,7 @@ public interface TypeBoundClass extends HeaderBoundClass { ImmutableList<MethodInfo> methods(); /** - * Annotation metadata, e.g. from {@link @java.lang.annotation.Target}, {@link + * Annotation metadata, e.g. from {@link java.lang.annotation.Target}, {@link * java.lang.annotation.Retention}, and {@link java.lang.annotation.Repeatable}. */ AnnotationMetadata annotationMetadata(); @@ -229,6 +229,14 @@ public interface TypeBoundClass extends HeaderBoundClass { return defaultValue; } + /** + * Returns true for annotation members with a default value. The default value may not have been + * bound yet, in which case {@link #defaultValue} may still return {@code null}. + */ + public boolean hasDefaultValue() { + return decl() != null ? decl().defaultValue().isPresent() : defaultValue() != null; + } + /** The declaration. */ public MethDecl decl() { return decl; diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java index 66d4cf0..0f4bac1 100644 --- a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java +++ b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java @@ -49,7 +49,7 @@ import java.util.function.Function; import java.util.function.Supplier; /** Bind {@link Type}s from bytecode. */ -public class BytecodeBinder { +public final class BytecodeBinder { static Type.ClassTy bindClassTy(Sig.ClassTySig sig, Function<String, TyVarSymbol> scope) { StringBuilder sb = new StringBuilder(sig.pkg()); @@ -212,4 +212,6 @@ public class BytecodeBinder { /* uses= */ ImmutableList.of(), /* provides= */ ImmutableList.of()); } + + private BytecodeBinder() {} } diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java index b992643..82cefc1 100644 --- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java +++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java @@ -18,6 +18,7 @@ package com.google.turbine.binder.bytecode; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Verify.verify; +import static java.util.Objects.requireNonNull; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; @@ -25,8 +26,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.turbine.binder.bound.AnnotationMetadata; -import com.google.turbine.binder.bound.BoundClass; -import com.google.turbine.binder.bound.HeaderBoundClass; import com.google.turbine.binder.bound.TypeBoundClass; import com.google.turbine.binder.env.Env; import com.google.turbine.binder.sym.ClassSymbol; @@ -69,18 +68,18 @@ import org.checkerframework.checker.nullness.qual.Nullable; * resolved and canonicalized so there are no cycles. The laziness also minimizes the amount of work * done on the classpath. */ -public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBoundClass { +public class BytecodeBoundClass implements TypeBoundClass { private final ClassSymbol sym; private final Env<ClassSymbol, BytecodeBoundClass> env; private final Supplier<ClassFile> classFile; - private final String jarFile; + private final @Nullable String jarFile; public BytecodeBoundClass( ClassSymbol sym, Supplier<byte[]> bytes, Env<ClassSymbol, BytecodeBoundClass> env, - String jarFile) { + @Nullable String jarFile) { this.sym = sym; this.env = env; this.jarFile = jarFile; @@ -124,11 +123,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou return kind.get(); } - private final Supplier<ClassSymbol> owner = + private final Supplier<@Nullable ClassSymbol> owner = Suppliers.memoize( - new Supplier<ClassSymbol>() { + new Supplier<@Nullable ClassSymbol>() { @Override - public ClassSymbol get() { + public @Nullable ClassSymbol get() { for (ClassFile.InnerClass inner : classFile.get().innerClasses()) { if (sym.binaryName().equals(inner.innerClass())) { return new ClassSymbol(inner.outerClass()); @@ -188,11 +187,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou return access.get(); } - private final Supplier<ClassSig> sig = + private final Supplier<@Nullable ClassSig> sig = Suppliers.memoize( - new Supplier<ClassSig>() { + new Supplier<@Nullable ClassSig>() { @Override - public ClassSig get() { + public @Nullable ClassSig get() { String signature = classFile.get().signature(); if (signature == null) { return null; @@ -223,11 +222,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou return tyParams.get(); } - private final Supplier<ClassSymbol> superclass = + private final Supplier<@Nullable ClassSymbol> superclass = Suppliers.memoize( - new Supplier<ClassSymbol>() { + new Supplier<@Nullable ClassSymbol>() { @Override - public ClassSymbol get() { + public @Nullable ClassSymbol get() { String superclass = classFile.get().superName(); if (superclass == null) { return null; @@ -237,7 +236,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou }); @Override - public ClassSymbol superclass() { + public @Nullable ClassSymbol superclass() { return superclass.get(); } @@ -259,11 +258,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou return interfaces.get(); } - private final Supplier<ClassTy> superClassType = + private final Supplier<@Nullable ClassTy> superClassType = Suppliers.memoize( - new Supplier<ClassTy>() { + new Supplier<@Nullable ClassTy>() { @Override - public ClassTy get() { + public @Nullable ClassTy get() { if (superclass() == null) { return null; } @@ -276,7 +275,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou }); @Override - public ClassTy superClassType() { + public @Nullable ClassTy superClassType() { return superClassType.get(); } @@ -319,7 +318,8 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou ImmutableMap.Builder<TyVarSymbol, TyVarInfo> tparams = ImmutableMap.builder(); Function<String, TyVarSymbol> scope = makeScope(env, sym, typeParameters()); for (Sig.TyParamSig p : sig.get().tyParams()) { - tparams.put(typeParameters().get(p.name()), bindTyParam(p, scope)); + // typeParameters() is constructed to guarantee the requireNonNull call is safe. + tparams.put(requireNonNull(typeParameters().get(p.name())), bindTyParam(p, scope)); } return tparams.build(); } @@ -380,14 +380,19 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou public ImmutableList<MethodInfo> get() { ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder(); int idx = 0; - for (ClassFile.MethodInfo m : classFile.get().methods()) { - methods.add(bindMethod(idx++, m)); + ClassFile cf = classFile.get(); + for (ClassFile.MethodInfo m : cf.methods()) { + if (m.name().equals("<clinit>")) { + // Don't bother reading class initializers, which we don't need + continue; + } + methods.add(bindMethod(cf, idx++, m)); } return methods.build(); } }); - private MethodInfo bindMethod(int methodIdx, ClassFile.MethodInfo m) { + private MethodInfo bindMethod(ClassFile classFile, int methodIdx, ClassFile.MethodInfo m) { MethodSymbol methodSymbol = new MethodSymbol(methodIdx, sym, m.name()); Sig.MethodSig sig = new SigParser(firstNonNull(m.signature(), m.descriptor())).parseMethodSig(); @@ -405,7 +410,8 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou ImmutableMap.Builder<TyVarSymbol, TyVarInfo> tparams = ImmutableMap.builder(); Function<String, TyVarSymbol> scope = makeScope(env, sym, tyParams); for (Sig.TyParamSig p : sig.tyParams()) { - tparams.put(tyParams.get(p.name()), bindTyParam(p, scope)); + // tyParams is constructed to guarantee the requireNonNull call is safe. + tparams.put(requireNonNull(tyParams.get(p.name())), bindTyParam(p, scope)); } tyParamTypes = tparams.build(); } @@ -460,13 +466,19 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou ImmutableList<AnnoInfo> annotations = BytecodeBinder.bindAnnotations(m.annotations()); + int access = m.access(); + if (((classFile.access() & TurbineFlag.ACC_INTERFACE) == TurbineFlag.ACC_INTERFACE) + && (access & (TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_STATIC)) == 0) { + access |= TurbineFlag.ACC_DEFAULT; + } + return new MethodInfo( methodSymbol, tyParamTypes, ret, formals.build(), exceptions.build(), - m.access(), + access, defaultValue, /* decl= */ null, annotations, @@ -478,11 +490,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou return methods.get(); } - private final Supplier<AnnotationMetadata> annotationMetadata = + private final Supplier<@Nullable AnnotationMetadata> annotationMetadata = Suppliers.memoize( - new Supplier<AnnotationMetadata>() { + new Supplier<@Nullable AnnotationMetadata>() { @Override - public AnnotationMetadata get() { + public @Nullable AnnotationMetadata get() { if ((access() & TurbineFlag.ACC_ANNOTATION) != TurbineFlag.ACC_ANNOTATION) { return null; } @@ -508,8 +520,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou } }); - private static RetentionPolicy bindRetention(AnnotationInfo annotation) { + private static @Nullable RetentionPolicy bindRetention(AnnotationInfo annotation) { ElementValue val = annotation.elementValuePairs().get("value"); + if (val == null) { + return null; + } if (val.kind() != Kind.ENUM) { return null; } @@ -523,6 +538,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou private static ImmutableSet<TurbineElementType> bindTarget(AnnotationInfo annotation) { ImmutableSet.Builder<TurbineElementType> result = ImmutableSet.builder(); ElementValue val = annotation.elementValuePairs().get("value"); + requireNonNull(val); switch (val.kind()) { case ARRAY: for (ElementValue element : ((ArrayValue) val).elements()) { @@ -547,8 +563,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou } } - private static ClassSymbol bindRepeatable(AnnotationInfo annotation) { + private static @Nullable ClassSymbol bindRepeatable(AnnotationInfo annotation) { ElementValue val = annotation.elementValuePairs().get("value"); + if (val == null) { + return null; + } switch (val.kind()) { case CLASS: String className = ((ConstTurbineClassValue) val).className(); @@ -560,7 +579,7 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou } @Override - public AnnotationMetadata annotationMetadata() { + public @Nullable AnnotationMetadata annotationMetadata() { return annotationMetadata.get(); } @@ -611,7 +630,11 @@ public class BytecodeBoundClass implements BoundClass, HeaderBoundClass, TypeBou } /** The jar file the symbol was loaded from. */ - public String jarFile() { + public @Nullable String jarFile() { + String transitiveJar = classFile.get().transitiveJar(); + if (transitiveJar != null) { + return transitiveJar; + } return jarFile; } diff --git a/java/com/google/turbine/binder/bytecode/package-info.java b/java/com/google/turbine/binder/bytecode/package-info.java new file mode 100644 index 0000000..23c59f0 --- /dev/null +++ b/java/com/google/turbine/binder/bytecode/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.turbine.binder.bytecode; diff --git a/java/com/google/turbine/binder/env/Env.java b/java/com/google/turbine/binder/env/Env.java index 6ee38a4..a78d3e6 100644 --- a/java/com/google/turbine/binder/env/Env.java +++ b/java/com/google/turbine/binder/env/Env.java @@ -20,10 +20,10 @@ import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.Symbol; /** - * An environment that maps {@link Symbols} {@code S} to bound nodes {@code V}. + * An environment that maps {@link Symbol}s {@code S} to bound nodes {@code V}. * - * <p>For example, {@link BoundClass} represents superclasses as a {@link ClassSymbol}, which only - * contains the binary name of the type. To get the {@link BoundClass} for that supertype, an {@code + * <p>For example, {@code BoundClass} represents superclasses as a {@link ClassSymbol}, which only + * contains the binary name of the type. To get the {@code BoundClass} for that supertype, an {@code * Env<BoundClass>} is used. * * <p>The indirection through env makes it possible to represent a graph with cycles using immutable diff --git a/java/com/google/turbine/binder/env/LazyEnv.java b/java/com/google/turbine/binder/env/LazyEnv.java index 9e8afd5..a9c3bd1 100644 --- a/java/com/google/turbine/binder/env/LazyEnv.java +++ b/java/com/google/turbine/binder/env/LazyEnv.java @@ -27,17 +27,17 @@ import java.util.Map; * An env that permits an analysis pass to access information about symbols from the current pass, * recursively. Cycles are detected, and result in an {@link LazyBindingError} being thrown. * - * <p>This is used primarily for resolving the supertype hierarchy in {@link HierarchyBinder}. The - * supertype hierarchy forms a directed acyclic graph, and {@link HierarchyBinder} needs to process + * <p>This is used primarily for resolving the supertype hierarchy in {@code HierarchyBinder}. The + * supertype hierarchy forms a directed acyclic graph, and {@code HierarchyBinder} needs to process * classes in a topological sort order of that graph. Unfortuntately, we can't produce a suitable * sort order until the graph exists. * * @param <T> the interface type of the bound node {@link V}, shared by any underlying environments. - * @param <V> a specific implementation of {@code T}. For example, during hierarchy binding {@link + * @param <V> a specific implementation of {@code T}. For example, during hierarchy binding {@code * SourceHeaderBoundClass} nodes are being completed from the sources being compiled, and the - * analysis of a given symbol may require looking up {@link HeaderBoundClass} nodes that will - * either be backed by other {@link SourceHeaderBoundClass} nodes or {@link BytecodeBoundClass} - * nodes. So the phase uses an {@link LazyEnv<HeaderBoundClass, SourceHeaderBoundClass>}. + * analysis of a given symbol may require looking up {@code HeaderBoundClass} nodes that will + * either be backed by other {@code SourceHeaderBoundClass} nodes or {@code BytecodeBoundClass} + * nodes. So the phase uses an {@code LazyEnv<HeaderBoundClass, SourceHeaderBoundClass>}. */ public class LazyEnv<S extends Symbol, T, V extends T> implements Env<S, V> { diff --git a/java/com/google/turbine/bytecode/AnnotationWriter.java b/java/com/google/turbine/bytecode/AnnotationWriter.java index b547971..34d6262 100644 --- a/java/com/google/turbine/bytecode/AnnotationWriter.java +++ b/java/com/google/turbine/bytecode/AnnotationWriter.java @@ -34,7 +34,7 @@ import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TypeParameterBou import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TypeParameterTarget; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TypePath; import com.google.turbine.model.Const.Value; -import java.util.Map.Entry; +import java.util.Map; /** Writes an {@link AnnotationInfo} to a class file. */ public class AnnotationWriter { @@ -50,7 +50,7 @@ public class AnnotationWriter { public void writeAnnotation(AnnotationInfo annotation) { output.writeShort(pool.utf8(annotation.typeName())); output.writeShort(annotation.elementValuePairs().size()); - for (Entry<String, ElementValue> entry : annotation.elementValuePairs().entrySet()) { + for (Map.Entry<String, ElementValue> entry : annotation.elementValuePairs().entrySet()) { output.writeShort(pool.utf8(entry.getKey())); writeElementValue(entry.getValue()); } diff --git a/java/com/google/turbine/bytecode/Attribute.java b/java/com/google/turbine/bytecode/Attribute.java index 29efb60..7b415a7 100644 --- a/java/com/google/turbine/bytecode/Attribute.java +++ b/java/com/google/turbine/bytecode/Attribute.java @@ -41,7 +41,8 @@ interface Attribute { RUNTIME_VISIBLE_TYPE_ANNOTATIONS("RuntimeVisibleTypeAnnotations"), RUNTIME_INVISIBLE_TYPE_ANNOTATIONS("RuntimeInvisibleTypeAnnotations"), METHOD_PARAMETERS("MethodParameters"), - MODULE("Module"); + MODULE("Module"), + TURBINE_TRANSITIVE_JAR("TurbineTransitiveJar"); private final String signature; @@ -309,4 +310,19 @@ interface Attribute { return module; } } + + /** A custom attribute for recording the original jar of repackaged transitive classes. */ + class TurbineTransitiveJar implements Attribute { + + final String transitiveJar; + + public TurbineTransitiveJar(String transitiveJar) { + this.transitiveJar = transitiveJar; + } + + @Override + public Kind kind() { + return Kind.TURBINE_TRANSITIVE_JAR; + } + } } diff --git a/java/com/google/turbine/bytecode/AttributeWriter.java b/java/com/google/turbine/bytecode/AttributeWriter.java index c5ffd16..84ca55f 100644 --- a/java/com/google/turbine/bytecode/AttributeWriter.java +++ b/java/com/google/turbine/bytecode/AttributeWriter.java @@ -24,6 +24,7 @@ import com.google.turbine.bytecode.Attribute.ExceptionsAttribute; import com.google.turbine.bytecode.Attribute.InnerClasses; import com.google.turbine.bytecode.Attribute.MethodParameters; import com.google.turbine.bytecode.Attribute.Signature; +import com.google.turbine.bytecode.Attribute.TurbineTransitiveJar; import com.google.turbine.bytecode.Attribute.TypeAnnotations; import com.google.turbine.bytecode.ClassFile.AnnotationInfo; import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo; @@ -87,6 +88,9 @@ public class AttributeWriter { case MODULE: writeModule((Attribute.Module) attribute); break; + case TURBINE_TRANSITIVE_JAR: + writeTurbineTransitiveJar((Attribute.TurbineTransitiveJar) attribute); + break; } } @@ -266,4 +270,10 @@ public class AttributeWriter { output.writeInt(data.length); output.write(data); } + + private void writeTurbineTransitiveJar(TurbineTransitiveJar attribute) { + output.writeShort(pool.utf8(attribute.kind().signature())); + output.writeInt(2); + output.writeShort(pool.utf8(attribute.transitiveJar)); + } } diff --git a/java/com/google/turbine/bytecode/ClassFile.java b/java/com/google/turbine/bytecode/ClassFile.java index 8ee2aac..e979edc 100644 --- a/java/com/google/turbine/bytecode/ClassFile.java +++ b/java/com/google/turbine/bytecode/ClassFile.java @@ -42,6 +42,7 @@ public class ClassFile { private final List<InnerClass> innerClasses; private final ImmutableList<TypeAnnotationInfo> typeAnnotations; @Nullable private final ModuleInfo module; + @Nullable private final String transitiveJar; public ClassFile( int access, @@ -54,7 +55,8 @@ public class ClassFile { List<AnnotationInfo> annotations, List<InnerClass> innerClasses, ImmutableList<TypeAnnotationInfo> typeAnnotations, - @Nullable ModuleInfo module) { + @Nullable ModuleInfo module, + @Nullable String transitiveJar) { this.access = access; this.name = name; this.signature = signature; @@ -66,6 +68,7 @@ public class ClassFile { this.innerClasses = innerClasses; this.typeAnnotations = typeAnnotations; this.module = module; + this.transitiveJar = transitiveJar; } /** Class access and property flags. */ @@ -124,6 +127,12 @@ public class ClassFile { return module; } + /** The original jar of a repackaged transitive class. */ + @Nullable + public String transitiveJar() { + return transitiveJar; + } + /** The contents of a JVMS §4.5 field_info structure. */ public static class FieldInfo { diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java index 9c79b42..ac8b1e1 100644 --- a/java/com/google/turbine/bytecode/ClassReader.java +++ b/java/com/google/turbine/bytecode/ClassReader.java @@ -106,6 +106,7 @@ public class ClassReader { List<ClassFile.InnerClass> innerclasses = ImmutableList.of(); ImmutableList.Builder<ClassFile.AnnotationInfo> annotations = ImmutableList.builder(); ClassFile.ModuleInfo module = null; + String transitiveJar = null; int attributesCount = reader.u2(); for (int j = 0; j < attributesCount; j++) { int attributeNameIndex = reader.u2(); @@ -124,6 +125,9 @@ public class ClassReader { case "Module": module = readModule(constantPool); break; + case "TurbineTransitiveJar": + transitiveJar = readTurbineTransitiveJar(constantPool); + break; default: reader.skip(reader.u4()); break; @@ -141,7 +145,8 @@ public class ClassReader { annotations.build(), innerclasses, ImmutableList.of(), - module); + module, + transitiveJar); } /** Reads a JVMS 4.7.9 Signature attribute. */ @@ -509,4 +514,9 @@ public class ClassReader { } return fields; } + + private String readTurbineTransitiveJar(ConstantPoolReader constantPool) { + reader.u4(); // length + return constantPool.utf8(reader.u2()); + } } diff --git a/java/com/google/turbine/bytecode/ClassWriter.java b/java/com/google/turbine/bytecode/ClassWriter.java index c3490ca..de975f2 100644 --- a/java/com/google/turbine/bytecode/ClassWriter.java +++ b/java/com/google/turbine/bytecode/ClassWriter.java @@ -27,7 +27,7 @@ import com.google.turbine.model.Const.Value; import java.util.List; /** Class file writing. */ -public class ClassWriter { +public final class ClassWriter { private static final int MAGIC = 0xcafebabe; private static final int MINOR_VERSION = 0; @@ -124,4 +124,6 @@ public class ClassWriter { result.write(body.toByteArray()); return result.toByteArray(); } + + private ClassWriter() {} } diff --git a/java/com/google/turbine/bytecode/LowerAttributes.java b/java/com/google/turbine/bytecode/LowerAttributes.java index 67ef2b4..5ae42af 100644 --- a/java/com/google/turbine/bytecode/LowerAttributes.java +++ b/java/com/google/turbine/bytecode/LowerAttributes.java @@ -29,7 +29,7 @@ import java.util.ArrayList; import java.util.List; /** Lower information in {@link ClassFile} structures to attributes. */ -public class LowerAttributes { +public final class LowerAttributes { /** Collects the {@link Attribute}s for a {@link ClassFile}. */ static List<Attribute> classAttributes(ClassFile classfile) { @@ -45,6 +45,9 @@ public class LowerAttributes { if (classfile.module() != null) { attributes.add(new Attribute.Module(classfile.module())); } + if (classfile.transitiveJar() != null) { + attributes.add(new Attribute.TurbineTransitiveJar(classfile.transitiveJar())); + } return attributes; } @@ -146,4 +149,6 @@ public class LowerAttributes { attributes.add(new Attribute.RuntimeInvisibleParameterAnnotations(invisibles)); } } + + private LowerAttributes() {} } diff --git a/java/com/google/turbine/bytecode/sig/Sig.java b/java/com/google/turbine/bytecode/sig/Sig.java index e85740f..f759269 100644 --- a/java/com/google/turbine/bytecode/sig/Sig.java +++ b/java/com/google/turbine/bytecode/sig/Sig.java @@ -21,7 +21,7 @@ import com.google.turbine.model.TurbineConstantTypeKind; import org.checkerframework.checker.nullness.qual.Nullable; /** JVMS 4.7.9.1 signatures. */ -public class Sig { +public final class Sig { /** A JVMS 4.7.9.1 ClassSignature. */ public static class ClassSig { @@ -343,4 +343,6 @@ public class Sig { return exceptions; } } + + private Sig() {} } diff --git a/java/com/google/turbine/deps/Dependencies.java b/java/com/google/turbine/deps/Dependencies.java index 92193e8..ef1eea9 100644 --- a/java/com/google/turbine/deps/Dependencies.java +++ b/java/com/google/turbine/deps/Dependencies.java @@ -51,7 +51,7 @@ import java.util.Optional; import java.util.Set; /** Support for Bazel jdeps dependency output. */ -public class Dependencies { +public final class Dependencies { /** Creates a jdeps proto for the current compilation. */ public static DepsProto.Dependencies collectDeps( Optional<String> targetLabel, ClassPath bootclasspath, BindingResult bound, Lowered lowered) { @@ -219,4 +219,6 @@ public class Dependencies { // preserve the order of entries in the transitive classpath return Collections2.filter(transitiveClasspath, Predicates.in(reduced)); } + + private Dependencies() {} } diff --git a/java/com/google/turbine/deps/Transitive.java b/java/com/google/turbine/deps/Transitive.java index 8b0d44d..75d23f6 100644 --- a/java/com/google/turbine/deps/Transitive.java +++ b/java/com/google/turbine/deps/Transitive.java @@ -33,13 +33,14 @@ import com.google.turbine.bytecode.ClassWriter; import com.google.turbine.model.TurbineFlag; import java.util.LinkedHashSet; import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Collects the minimal compile-time API for symbols in the supertype closure of compiled classes. * This allows header compilations to be performed against a classpath containing only direct * dependencies and no transitive dependencies. */ -public class Transitive { +public final class Transitive { public static ImmutableMap<String, byte[]> collectDeps( ClassPath bootClassPath, BindingResult bound) { @@ -54,7 +55,8 @@ public class Transitive { // don't export symbols loaded from the bootclasspath continue; } - transitive.put(sym.binaryName(), ClassWriter.writeClass(trimClass(info.classFile()))); + transitive.put( + sym.binaryName(), ClassWriter.writeClass(trimClass(info.classFile(), info.jarFile()))); } return transitive.build(); } @@ -62,7 +64,7 @@ public class Transitive { /** * Removes information from repackaged classes that will not be needed by upstream compilations. */ - public static ClassFile trimClass(ClassFile cf) { + public static ClassFile trimClass(ClassFile cf, @Nullable String jarFile) { // drop non-constant fields ImmutableList.Builder<FieldInfo> fields = ImmutableList.builder(); for (FieldInfo f : cf.fields()) { @@ -80,6 +82,12 @@ public class Transitive { innerClasses.add(i); } } + // Include the original jar file name when repackaging transitive deps. If the same transitive + // dep is repackaged more than once, keep the original name. + String transitiveJar = cf.transitiveJar(); + if (transitiveJar == null) { + transitiveJar = jarFile; + } return new ClassFile( cf.access(), cf.name(), @@ -96,7 +104,8 @@ public class Transitive { cf.annotations(), innerClasses.build(), cf.typeAnnotations(), - /* module= */ null); + /* module= */ null, + /* transitiveJar = */ transitiveJar); } private static Set<ClassSymbol> superClosure(BindingResult bound) { @@ -134,4 +143,6 @@ public class Transitive { addSuperTypes(closure, env, i); } } + + private Transitive() {} } diff --git a/java/com/google/turbine/diag/LineMap.java b/java/com/google/turbine/diag/LineMap.java index 7a39aba..37d055b 100644 --- a/java/com/google/turbine/diag/LineMap.java +++ b/java/com/google/turbine/diag/LineMap.java @@ -17,6 +17,7 @@ package com.google.turbine.diag; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; import com.google.common.collect.ImmutableRangeMap; import com.google.common.collect.Range; @@ -64,19 +65,22 @@ public class LineMap { /** The zero-indexed column number of the given source position. */ public int column(int position) { checkArgument(0 <= position && position < source.length(), "%s", position); - return position - lines.getEntry(position).getKey().lowerEndpoint(); + // requireNonNull is safe because `lines` covers the whole file length. + return position - requireNonNull(lines.getEntry(position)).getKey().lowerEndpoint(); } /** The one-indexed line number of the given source position. */ public int lineNumber(int position) { checkArgument(0 <= position && position < source.length(), "%s", position); - return lines.get(position); + // requireNonNull is safe because `lines` covers the whole file length. + return requireNonNull(lines.get(position)); } /** The one-indexed line of the given source position. */ public String line(int position) { checkArgument(0 <= position && position < source.length(), "%s", position); - Range<Integer> range = lines.getEntry(position).getKey(); + // requireNonNull is safe because `lines` covers the whole file length. + Range<Integer> range = requireNonNull(lines.getEntry(position)).getKey(); return source.substring(range.lowerEndpoint(), range.upperEndpoint()); } } diff --git a/java/com/google/turbine/diag/TurbineDiagnostic.java b/java/com/google/turbine/diag/TurbineDiagnostic.java index ccbaa7f..ed04a5d 100644 --- a/java/com/google/turbine/diag/TurbineDiagnostic.java +++ b/java/com/google/turbine/diag/TurbineDiagnostic.java @@ -64,6 +64,10 @@ public class TurbineDiagnostic { return severity; } + boolean isError() { + return severity.equals(Diagnostic.Kind.ERROR); + } + /** The diagnostic message. */ public String diagnostic() { StringBuilder sb = new StringBuilder(path()); @@ -71,7 +75,7 @@ public class TurbineDiagnostic { sb.append(':').append(line()); } sb.append(": error: "); - sb.append(message().trim()).append(System.lineSeparator()); + sb.append(message()).append(System.lineSeparator()); if (line() != -1 && column() != -1) { sb.append(CharMatcher.breakingWhitespace().trimTrailingFrom(source.lineMap().line(position))) .append(System.lineSeparator()); diff --git a/java/com/google/turbine/diag/TurbineError.java b/java/com/google/turbine/diag/TurbineError.java index 39244b5..f3ebf08 100644 --- a/java/com/google/turbine/diag/TurbineError.java +++ b/java/com/google/turbine/diag/TurbineError.java @@ -26,15 +26,17 @@ public class TurbineError extends Error { /** A diagnostic kind. */ public enum ErrorKind { - UNEXPECTED_INPUT("unexpected input: %c"), + UNEXPECTED_INPUT("unexpected input: %s"), UNEXPECTED_IDENTIFIER("unexpected identifier '%s'"), UNEXPECTED_EOF("unexpected end of input"), UNTERMINATED_STRING("unterminated string literal"), UNTERMINATED_CHARACTER_LITERAL("unterminated char literal"), + UNPAIRED_SURROGATE("unpaired surrogate 0x%x"), UNTERMINATED_EXPRESSION("unterminated expression, expected ';' not found"), INVALID_UNICODE("illegal unicode escape"), EMPTY_CHARACTER_LITERAL("empty char literal"), EXPECTED_TOKEN("expected token %s"), + EXTENDS_AFTER_IMPLEMENTS("'extends' must come before 'implements'"), INVALID_LITERAL("invalid literal: %s"), UNEXPECTED_TYPE_PARAMETER("unexpected type parameter %s"), SYMBOL_NOT_FOUND("symbol not found %s"), @@ -42,6 +44,7 @@ public class TurbineError extends Error { TYPE_PARAMETER_QUALIFIER("type parameter used as type qualifier"), UNEXPECTED_TOKEN("unexpected token: %s"), INVALID_ANNOTATION_ARGUMENT("invalid annotation argument"), + MISSING_ANNOTATION_ARGUMENT("missing required annotation argument: %s"), CANNOT_RESOLVE("could not resolve %s"), EXPRESSION_ERROR("could not evaluate constant expression"), OPERAND_TYPE("bad operand type %s"), @@ -51,6 +54,8 @@ public class TurbineError extends Error { DUPLICATE_DECLARATION("duplicate declaration of %s"), BAD_MODULE_INFO("unexpected declaration found in module-info"), UNCLOSED_COMMENT("unclosed comment"), + UNEXPECTED_TYPE("unexpected type %s"), + UNEXPECTED_MODIFIER("unexpected modifier: %s"), PROC("%s"); private final String message; diff --git a/java/com/google/turbine/diag/TurbineLog.java b/java/com/google/turbine/diag/TurbineLog.java index b336e25..565b9ea 100644 --- a/java/com/google/turbine/diag/TurbineLog.java +++ b/java/com/google/turbine/diag/TurbineLog.java @@ -18,7 +18,6 @@ package com.google.turbine.diag; import com.google.common.collect.ImmutableList; import com.google.turbine.diag.TurbineError.ErrorKind; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import javax.tools.Diagnostic; @@ -26,20 +25,24 @@ import javax.tools.Diagnostic; /** A log that collects diagnostics. */ public class TurbineLog { - private final Set<TurbineDiagnostic> errors = new LinkedHashSet<>(); + private final Set<TurbineDiagnostic> diagnostics = new LinkedHashSet<>(); public TurbineLogWithSource withSource(SourceFile source) { return new TurbineLogWithSource(source); } + public ImmutableList<TurbineDiagnostic> diagnostics() { + return ImmutableList.copyOf(diagnostics); + } + public void maybeThrow() { if (anyErrors()) { - throw new TurbineError(ImmutableList.copyOf(errors)); + throw new TurbineError(diagnostics()); } } - private boolean anyErrors() { - for (TurbineDiagnostic error : errors) { + public boolean anyErrors() { + for (TurbineDiagnostic error : diagnostics) { if (error.severity().equals(Diagnostic.Kind.ERROR)) { return true; } @@ -55,7 +58,7 @@ public class TurbineLog { * code generated in later processing rounds. */ public boolean errorRaised() { - for (TurbineDiagnostic error : errors) { + for (TurbineDiagnostic error : diagnostics) { if (error.kind().equals(ErrorKind.PROC) && error.severity().equals(Diagnostic.Kind.ERROR)) { return true; } @@ -65,17 +68,12 @@ public class TurbineLog { /** Reset the log between annotation processing rounds. */ public void clear() { - Iterator<TurbineDiagnostic> it = errors.iterator(); - while (it.hasNext()) { - if (it.next().severity().equals(Diagnostic.Kind.ERROR)) { - it.remove(); - } - } + diagnostics.removeIf(TurbineDiagnostic::isError); } /** Reports an annotation processing diagnostic with no position information. */ public void diagnostic(Diagnostic.Kind severity, String message) { - errors.add(TurbineDiagnostic.format(severity, ErrorKind.PROC, message)); + diagnostics.add(TurbineDiagnostic.format(severity, ErrorKind.PROC, message)); } /** A log for a specific source file. */ @@ -88,7 +86,7 @@ public class TurbineLog { } public void diagnostic(Diagnostic.Kind severity, int position, ErrorKind kind, Object... args) { - errors.add(TurbineDiagnostic.format(severity, source, position, kind, args)); + diagnostics.add(TurbineDiagnostic.format(severity, source, position, kind, args)); } public void error(int position, ErrorKind kind, Object... args) { diff --git a/java/com/google/turbine/lower/Lower.java b/java/com/google/turbine/lower/Lower.java index 0f7bb90..971bbe4 100644 --- a/java/com/google/turbine/lower/Lower.java +++ b/java/com/google/turbine/lower/Lower.java @@ -185,7 +185,8 @@ public class Lower { annotations, innerClasses.build(), /* typeAnnotations= */ ImmutableList.of(), - moduleInfo); + moduleInfo, + /* transitiveJar= */ null); symbols.addAll(sig.classes); return ClassWriter.writeClass(classfile); } @@ -279,7 +280,8 @@ public class Lower { annotations, inners, typeAnnotations, - /* module= */ null); + /* module= */ null, + /* transitiveJar= */ null); symbols.addAll(sig.classes); diff --git a/java/com/google/turbine/lower/LowerSignature.java b/java/com/google/turbine/lower/LowerSignature.java index 13a7b9f..a08c7e8 100644 --- a/java/com/google/turbine/lower/LowerSignature.java +++ b/java/com/google/turbine/lower/LowerSignature.java @@ -128,15 +128,13 @@ public class LowerSignature { * unnecessary. */ public String methodSignature( - Env<ClassSymbol, TypeBoundClass> env, - SourceTypeBoundClass.MethodInfo method, - ClassSymbol sym) { + Env<ClassSymbol, TypeBoundClass> env, TypeBoundClass.MethodInfo method, ClassSymbol sym) { if (!needsMethodSig(sym, env, method)) { return null; } ImmutableList<Sig.TyParamSig> typarams = tyParamSig(method.tyParams(), env); ImmutableList.Builder<Sig.TySig> fparams = ImmutableList.builder(); - for (SourceTypeBoundClass.ParamInfo t : method.parameters()) { + for (TypeBoundClass.ParamInfo t : method.parameters()) { if (t.synthetic()) { continue; } @@ -161,7 +159,7 @@ public class LowerSignature { } private boolean needsMethodSig( - ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env, SourceTypeBoundClass.MethodInfo m) { + ClassSymbol sym, Env<ClassSymbol, TypeBoundClass> env, TypeBoundClass.MethodInfo m) { if ((env.get(sym).access() & TurbineFlag.ACC_ENUM) == TurbineFlag.ACC_ENUM && m.name().equals("<init>")) { // JDK-8024694: javac always expects signature attribute for enum constructors @@ -176,7 +174,7 @@ public class LowerSignature { if (m.returnType() != null && needsSig(m.returnType())) { return true; } - for (SourceTypeBoundClass.ParamInfo t : m.parameters()) { + for (TypeBoundClass.ParamInfo t : m.parameters()) { if (t.synthetic()) { continue; } @@ -262,14 +260,14 @@ public class LowerSignature { private ImmutableList<Sig.TyParamSig> tyParamSig( Map<TyVarSymbol, TyVarInfo> px, Env<ClassSymbol, TypeBoundClass> env) { ImmutableList.Builder<Sig.TyParamSig> result = ImmutableList.builder(); - for (Map.Entry<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> entry : px.entrySet()) { + for (Map.Entry<TyVarSymbol, TyVarInfo> entry : px.entrySet()) { result.add(tyParamSig(entry.getKey(), entry.getValue(), env)); } return result.build(); } private Sig.TyParamSig tyParamSig( - TyVarSymbol sym, SourceTypeBoundClass.TyVarInfo info, Env<ClassSymbol, TypeBoundClass> env) { + TyVarSymbol sym, TyVarInfo info, Env<ClassSymbol, TypeBoundClass> env) { String identifier = sym.name(); Sig.TySig cbound = null; diff --git a/java/com/google/turbine/main/Main.java b/java/com/google/turbine/main/Main.java index 1e60ae6..59563b6 100644 --- a/java/com/google/turbine/main/Main.java +++ b/java/com/google/turbine/main/Main.java @@ -70,7 +70,7 @@ import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** Main entry point for the turbine CLI. */ -public class Main { +public final class Main { private static final int BUFFER_SIZE = 65536; @@ -256,9 +256,10 @@ public class Main { ClassPathBinder.bindClasspath(toPaths(classpath)), Processing.initializeProcessors( /* javacopts= */ options.javacOpts(), - /* processorPath= */ options.processorPath(), /* processorNames= */ options.processors(), - /* builtinProcessors= */ options.builtinProcessors()), + Processing.processorLoader( + /* processorPath= */ options.processorPath(), + /* builtinProcessors= */ options.builtinProcessors())), bootclasspath, /* moduleVersion=*/ Optional.empty()); } @@ -332,6 +333,14 @@ public class Main { return; } Path path = Paths.get(options.gensrcOutput().get()); + if (Files.isDirectory(path)) { + for (SourceFile source : generatedSources.values()) { + Path to = path.resolve(source.path()); + Files.createDirectories(to.getParent()); + Files.write(to, source.source().getBytes(UTF_8)); + } + return; + } try (OutputStream os = Files.newOutputStream(path); BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE); JarOutputStream jos = new JarOutputStream(bos)) { @@ -349,6 +358,14 @@ public class Main { return; } Path path = Paths.get(options.resourceOutput().get()); + if (Files.isDirectory(path)) { + for (Map.Entry<String, byte[]> resource : generatedResources.entrySet()) { + Path to = path.resolve(resource.getKey()); + Files.createDirectories(to.getParent()); + Files.write(to, resource.getValue()); + } + return; + } try (OutputStream os = Files.newOutputStream(path); BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE); JarOutputStream jos = new JarOutputStream(bos)) { @@ -465,4 +482,6 @@ public class Main { } return result.build(); } + + private Main() {} } diff --git a/java/com/google/turbine/model/TurbineFlag.java b/java/com/google/turbine/model/TurbineFlag.java index 48e88e7..c138d46 100644 --- a/java/com/google/turbine/model/TurbineFlag.java +++ b/java/com/google/turbine/model/TurbineFlag.java @@ -22,7 +22,7 @@ package com.google.turbine.model; * <p>See tables 4.1-A, 4.5-A, 4.6-A, and 4.7.6-A in JVMS 4: * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html */ -public class TurbineFlag { +public final class TurbineFlag { public static final int ACC_PUBLIC = 0x0001; public static final int ACC_PRIVATE = 0x0002; public static final int ACC_PROTECTED = 0x0004; @@ -54,4 +54,6 @@ public class TurbineFlag { /** Synthetic constructors (e.g. of inner classes and enums). */ public static final int ACC_SYNTH_CTOR = 1 << 18; + + private TurbineFlag() {} } diff --git a/java/com/google/turbine/options/TurbineOptions.java b/java/com/google/turbine/options/TurbineOptions.java index 4dcc408..c104c54 100644 --- a/java/com/google/turbine/options/TurbineOptions.java +++ b/java/com/google/turbine/options/TurbineOptions.java @@ -70,17 +70,6 @@ public abstract class TurbineOptions { /** The output jar. */ public abstract Optional<String> output(); - /** - * The output jar. - * - * @deprecated use {@link #output} instead. - */ - @Deprecated - @Nullable - public String outputFile() { - return output().orElse(null); - } - /** Paths to annotation processor artifacts. */ public abstract ImmutableList<String> processorPath(); @@ -160,56 +149,20 @@ public abstract class TurbineOptions { public abstract static class Builder { public abstract Builder setOutput(String output); - /** @deprecated use {@link #setClassPath(ImmutableList)} instead. */ - @Deprecated - public Builder addClassPathEntries(Iterable<String> sources) { - return setClassPath(ImmutableList.copyOf(sources)); - } - public abstract Builder setClassPath(ImmutableList<String> classPath); public abstract Builder setBootClassPath(ImmutableList<String> bootClassPath); - /** @deprecated use {@link #setBootClassPath(ImmutableList)} instead. */ - @Deprecated - public Builder addBootClassPathEntries(Iterable<String> sources) { - return setBootClassPath(ImmutableList.copyOf(sources)); - } - public abstract Builder setRelease(String release); public abstract Builder setSystem(String system); public abstract Builder setSources(ImmutableList<String> sources); - /** @deprecated use {@link #setSources(ImmutableList)} instead. */ - @Deprecated - public Builder addSources(Iterable<String> sources) { - return setSources(ImmutableList.copyOf(sources)); - } - - /** @deprecated use {@link #setProcessorPath(ImmutableList)} instead. */ - @Deprecated - public Builder addProcessorPathEntries(Iterable<String> processorPath) { - return setProcessorPath(ImmutableList.copyOf(processorPath)); - } - public abstract Builder setProcessorPath(ImmutableList<String> processorPath); - /** @deprecated use {@link #setProcessors(ImmutableList)} instead. */ - @Deprecated - public Builder addProcessors(Iterable<String> processors) { - return setProcessors(ImmutableList.copyOf(processors)); - } - public abstract Builder setProcessors(ImmutableList<String> processors); - /** @deprecated use {@link #setBuiltinProcessors(ImmutableList)} instead. */ - @Deprecated - public Builder addBuiltinProcessors(Iterable<String> builtinProcessors) { - return setBuiltinProcessors(ImmutableList.copyOf(builtinProcessors)); - } - public abstract Builder setBuiltinProcessors(ImmutableList<String> builtinProcessors); public abstract Builder setSourceJars(ImmutableList<String> sourceJars); @@ -222,12 +175,6 @@ public abstract class TurbineOptions { public abstract Builder setInjectingRuleKind(String injectingRuleKind); - /** @deprecated use {@link #setDepsArtifacts(ImmutableList)} instead. */ - @Deprecated - public Builder addAllDepsArtifacts(Iterable<String> depsArtifacts) { - return setDepsArtifacts(ImmutableList.copyOf(depsArtifacts)); - } - public abstract Builder setDepsArtifacts(ImmutableList<String> depsArtifacts); public abstract Builder setHelp(boolean help); @@ -241,12 +188,6 @@ public abstract class TurbineOptions { public abstract Builder setReducedClasspathMode(ReducedClasspathMode reducedClasspathMode); - /** @deprecated use {@link #setDirectJars(ImmutableList)} instead. */ - @Deprecated - public Builder addDirectJars(Iterable<String> directJars) { - return setDirectJars(ImmutableList.copyOf(directJars)); - } - public abstract Builder setDirectJars(ImmutableList<String> jars); public abstract Builder setProfile(String profile); @@ -261,4 +202,11 @@ public abstract class TurbineOptions { public abstract TurbineOptions build(); } + + // TODO(b/188833569): remove when AutoValue adds @Nullable to Object if its on the classpath + @Override + public abstract boolean equals(@Nullable Object other); + + @Override + public abstract int hashCode(); } diff --git a/java/com/google/turbine/options/TurbineOptionsParser.java b/java/com/google/turbine/options/TurbineOptionsParser.java index 17d4bf6..4a8ff16 100644 --- a/java/com/google/turbine/options/TurbineOptionsParser.java +++ b/java/com/google/turbine/options/TurbineOptionsParser.java @@ -30,10 +30,9 @@ import java.nio.file.Paths; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; -import org.checkerframework.checker.nullness.qual.Nullable; /** A command line options parser for {@link TurbineOptions}. */ -public class TurbineOptionsParser { +public final class TurbineOptionsParser { /** * Parses command line options into {@link TurbineOptions}, expanding any {@code @params} files. @@ -57,17 +56,17 @@ public class TurbineOptionsParser { private static void parse(TurbineOptions.Builder builder, Deque<String> argumentDeque) { while (!argumentDeque.isEmpty()) { - String next = argumentDeque.pollFirst(); + String next = argumentDeque.removeFirst(); switch (next) { case "--output": - builder.setOutput(readOne(argumentDeque)); + builder.setOutput(readOne(next, argumentDeque)); break; case "--source_jars": builder.setSourceJars(readList(argumentDeque)); break; case "--temp_dir": // TODO(cushon): remove this when Bazel no longer passes the flag - readOne(argumentDeque); + readOne(next, argumentDeque); break; case "--processors": builder.setProcessors(readList(argumentDeque)); @@ -85,10 +84,10 @@ public class TurbineOptionsParser { builder.setBootClassPath(readList(argumentDeque)); break; case "--release": - builder.setRelease(readOne(argumentDeque)); + builder.setRelease(readOne(next, argumentDeque)); break; case "--system": - builder.setSystem(readOne(argumentDeque)); + builder.setSystem(readOne(next, argumentDeque)); break; case "--javacopts": { @@ -100,11 +99,12 @@ public class TurbineOptionsParser { case "--sources": builder.setSources(readList(argumentDeque)); break; + case "--output_deps_proto": case "--output_deps": - builder.setOutputDeps(readOne(argumentDeque)); + builder.setOutputDeps(readOne(next, argumentDeque)); break; case "--output_manifest_proto": - builder.setOutputManifest(readOne(argumentDeque)); + builder.setOutputManifest(readOne(next, argumentDeque)); break; case "--direct_dependencies": builder.setDirectJars(readList(argumentDeque)); @@ -113,10 +113,10 @@ public class TurbineOptionsParser { builder.setDepsArtifacts(readList(argumentDeque)); break; case "--target_label": - builder.setTargetLabel(readOne(argumentDeque)); + builder.setTargetLabel(readOne(next, argumentDeque)); break; case "--injecting_rule_kind": - builder.setInjectingRuleKind(readOne(argumentDeque)); + builder.setInjectingRuleKind(readOne(next, argumentDeque)); break; case "--javac_fallback": case "--nojavac_fallback": @@ -129,26 +129,37 @@ public class TurbineOptionsParser { builder.setReducedClasspathMode(ReducedClasspathMode.NONE); break; case "--reduce_classpath_mode": - builder.setReducedClasspathMode(ReducedClasspathMode.valueOf(readOne(argumentDeque))); + builder.setReducedClasspathMode( + ReducedClasspathMode.valueOf(readOne(next, argumentDeque))); break; case "--full_classpath_length": - builder.setFullClasspathLength(Integer.parseInt(readOne(argumentDeque))); + builder.setFullClasspathLength(Integer.parseInt(readOne(next, argumentDeque))); break; case "--reduced_classpath_length": - builder.setReducedClasspathLength(Integer.parseInt(readOne(argumentDeque))); + builder.setReducedClasspathLength(Integer.parseInt(readOne(next, argumentDeque))); break; case "--profile": - builder.setProfile(readOne(argumentDeque)); + builder.setProfile(readOne(next, argumentDeque)); break; + case "--generated_sources_output": case "--gensrc_output": - builder.setGensrcOutput(readOne(argumentDeque)); + builder.setGensrcOutput(readOne(next, argumentDeque)); break; case "--resource_output": - builder.setResourceOutput(readOne(argumentDeque)); + builder.setResourceOutput(readOne(next, argumentDeque)); break; case "--help": builder.setHelp(true); break; + case "--experimental_fix_deps_tool": + case "--strict_java_deps": + case "--native_header_output": + // accepted (and ignored) for compatibility with JavaBuilder command lines + readOne(next, argumentDeque); + break; + case "--compress_jar": + // accepted (and ignored) for compatibility with JavaBuilder command lines + break; default: throw new IllegalArgumentException("unknown option: " + next); } @@ -190,20 +201,22 @@ public class TurbineOptionsParser { } } - /** Returns the value of an option, or {@code null}. */ - @Nullable - private static String readOne(Deque<String> argumentDeque) { - if (argumentDeque.isEmpty() || argumentDeque.peekFirst().startsWith("-")) { - return null; + /** + * Returns the value of an option, or throws {@link IllegalArgumentException} if the value is not + * present. + */ + private static String readOne(String flag, Deque<String> argumentDeque) { + if (argumentDeque.isEmpty() || argumentDeque.getFirst().startsWith("-")) { + throw new IllegalArgumentException("missing required argument for: " + flag); } - return argumentDeque.pollFirst(); + return argumentDeque.removeFirst(); } /** Returns a list of option values. */ private static ImmutableList<String> readList(Deque<String> argumentDeque) { ImmutableList.Builder<String> result = ImmutableList.builder(); - while (!argumentDeque.isEmpty() && !argumentDeque.peekFirst().startsWith("--")) { - result.add(argumentDeque.pollFirst()); + while (!argumentDeque.isEmpty() && !argumentDeque.getFirst().startsWith("--")) { + result.add(argumentDeque.removeFirst()); } return result.build(); } @@ -215,7 +228,7 @@ public class TurbineOptionsParser { private static ImmutableList<String> readJavacopts(Deque<String> argumentDeque) { ImmutableList.Builder<String> result = ImmutableList.builder(); while (!argumentDeque.isEmpty()) { - String arg = argumentDeque.pollFirst(); + String arg = argumentDeque.removeFirst(); if (arg.equals("--")) { return result.build(); } @@ -237,4 +250,6 @@ public class TurbineOptionsParser { } } } + + private TurbineOptionsParser() {} } diff --git a/java/com/google/turbine/options/package-info.java b/java/com/google/turbine/options/package-info.java new file mode 100644 index 0000000..9c12bf8 --- /dev/null +++ b/java/com/google/turbine/options/package-info.java @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.turbine.options; diff --git a/java/com/google/turbine/parse/ConstExpressionParser.java b/java/com/google/turbine/parse/ConstExpressionParser.java index e49d51c..ba51814 100644 --- a/java/com/google/turbine/parse/ConstExpressionParser.java +++ b/java/com/google/turbine/parse/ConstExpressionParser.java @@ -506,12 +506,15 @@ public class ConstExpressionParser { return term1; } eat(); - if (op == TurbineOperatorKind.TERNARY) { - term1 = ternary(term1); - } else if (op == TurbineOperatorKind.ASSIGN) { - term1 = assign(term1, op); - } else { - term1 = new Tree.Binary(position, term1, expression(op.prec()), op); + switch (op) { + case TERNARY: + term1 = ternary(term1); + break; + case ASSIGN: + term1 = assign(term1, op); + break; + default: + term1 = new Tree.Binary(position, term1, expression(op.prec()), op); } if (term1 == null) { return null; @@ -568,6 +571,7 @@ public class ConstExpressionParser { throw new AssertionError(); } eat(); + int pos = position; Tree.ConstVarName constVarName = (Tree.ConstVarName) qualIdent(); if (constVarName == null) { return null; @@ -577,10 +581,10 @@ public class ConstExpressionParser { if (token == Token.LPAREN) { eat(); while (token != Token.RPAREN) { - int pos = position; + int argPos = position; Tree.Expression expression = expression(); if (expression == null) { - throw TurbineError.format(lexer.source(), pos, ErrorKind.INVALID_ANNOTATION_ARGUMENT); + throw TurbineError.format(lexer.source(), argPos, ErrorKind.INVALID_ANNOTATION_ARGUMENT); } args.add(expression); if (token != Token.COMMA) { @@ -592,11 +596,19 @@ public class ConstExpressionParser { eat(); } } - return new Tree.AnnoExpr(position, new Tree.Anno(position, name, args.build())); + return new Tree.AnnoExpr(pos, new Tree.Anno(pos, name, args.build())); } @CheckReturnValue private TurbineError error(ErrorKind kind, Object... args) { return TurbineError.format(lexer.source(), lexer.position(), kind, args); } + + public int f() { + return helper(1, 2); + } + + private int helper(int x, int y) { + return x + y; + } } diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java index 4a090b3..af1eabf 100644 --- a/java/com/google/turbine/parse/Parser.java +++ b/java/com/google/turbine/parse/Parser.java @@ -519,7 +519,15 @@ public class Parser { interfaces.add(classty()); } while (maybe(Token.COMMA)); } - eat(Token.LBRACE); + switch (token) { + case LBRACE: + next(); + break; + case EXTENDS: + throw error(ErrorKind.EXTENDS_AFTER_IMPLEMENTS); + default: + throw error(ErrorKind.EXPECTED_TOKEN, Token.LBRACE); + } ImmutableList<Tree> members = classMembers(); eat(Token.RBRACE); return new TyDecl( @@ -748,7 +756,9 @@ public class Parser { } if (token == Token.DOT) { next(); - // TODO(cushon): is this cast OK? + if (!result.kind().equals(Kind.CLASS_TY)) { + throw error(token); + } result = classty((ClassTy) result); } result = maybeDims(maybeAnnos(), result); diff --git a/java/com/google/turbine/parse/StreamLexer.java b/java/com/google/turbine/parse/StreamLexer.java index 2e20c26..991b5fd 100644 --- a/java/com/google/turbine/parse/StreamLexer.java +++ b/java/com/google/turbine/parse/StreamLexer.java @@ -29,7 +29,7 @@ public class StreamLexer implements Lexer { private final UnicodeEscapePreprocessor reader; /** The current input character. */ - private char ch; + private int ch; /** The start position of the current token. */ private int position; @@ -353,7 +353,7 @@ public class StreamLexer implements Lexer { eat(); return Token.ELLIPSIS; } else { - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } } case '0': @@ -384,7 +384,7 @@ public class StreamLexer implements Lexer { case '\'': throw error(ErrorKind.EMPTY_CHARACTER_LITERAL); default: - value = ch; + value = (char) ch; eat(); } if (ch == '\'') { @@ -419,7 +419,7 @@ public class StreamLexer implements Lexer { } // falls through default: - sb.append(ch); + sb.appendCodePoint(ch); eat(); continue STRING; } @@ -430,7 +430,7 @@ public class StreamLexer implements Lexer { // TODO(cushon): the style guide disallows non-ascii identifiers return identifier(); } - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } } } @@ -511,7 +511,7 @@ public class StreamLexer implements Lexer { } } default: - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } } @@ -623,7 +623,7 @@ public class StreamLexer implements Lexer { eat(); break; default: - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } OUTER: while (true) { @@ -658,7 +658,7 @@ public class StreamLexer implements Lexer { case '9': continue OUTER; default: - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } } case 'A': @@ -695,7 +695,7 @@ public class StreamLexer implements Lexer { if ('0' <= ch && ch <= '9') { eat(); } else { - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } OUTER: while (true) { @@ -707,7 +707,7 @@ public class StreamLexer implements Lexer { if ('0' <= ch && ch <= '9') { continue OUTER; } else { - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } case '0': case '1': @@ -746,7 +746,7 @@ public class StreamLexer implements Lexer { eat(); break; default: - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } OUTER: while (true) { @@ -760,7 +760,7 @@ public class StreamLexer implements Lexer { case '1': continue OUTER; default: - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } case '0': case '1': @@ -798,7 +798,7 @@ public class StreamLexer implements Lexer { eat(); break; default: - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } OUTER: while (true) { @@ -818,7 +818,7 @@ public class StreamLexer implements Lexer { case '7': continue OUTER; default: - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } case '0': case '1': @@ -992,7 +992,7 @@ public class StreamLexer implements Lexer { } case '/': // handled with comments - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); case '%': eat(); @@ -1011,7 +1011,7 @@ public class StreamLexer implements Lexer { return Token.XOR; } default: - throw error(ErrorKind.UNEXPECTED_INPUT, ch); + throw inputError(); } } @@ -1141,6 +1141,12 @@ public class StreamLexer implements Lexer { } } + private TurbineError inputError() { + return error( + ErrorKind.UNEXPECTED_INPUT, + Character.isBmpCodePoint(ch) ? Character.toString((char) ch) : String.format("U+%X", ch)); + } + private TurbineError error(ErrorKind kind, Object... args) { return TurbineError.format(reader.source(), reader.position(), kind, args); } diff --git a/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java b/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java index 3f38561..4146ca5 100644 --- a/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java +++ b/java/com/google/turbine/parse/UnicodeEscapePreprocessor.java @@ -30,7 +30,7 @@ public class UnicodeEscapePreprocessor { private final String input; private int idx = 0; - private char ch; + private int ch; private boolean evenLeadingSlashes = true; public UnicodeEscapePreprocessor(SourceFile source) { @@ -49,7 +49,7 @@ public class UnicodeEscapePreprocessor { } /** Returns the next unescaped Unicode input character. */ - public char next() { + public int next() { eat(); if (ch == '\\' && evenLeadingSlashes) { unicodeEscape(); @@ -88,7 +88,7 @@ public class UnicodeEscapePreprocessor { } /** Consumes a hex digit. */ - private int hexDigit(char d) { + private int hexDigit(int d) { switch (d) { case '0': case '1': @@ -130,8 +130,20 @@ public class UnicodeEscapePreprocessor { * it terminates the input avoids some bounds checks in the lexer. */ private void eat() { - ch = done() ? ASCII_SUB : input.charAt(idx); + char hi = done() ? ASCII_SUB : input.charAt(idx); idx++; + if (!Character.isHighSurrogate(hi)) { + ch = hi; + return; + } + if (done()) { + throw error(ErrorKind.UNPAIRED_SURROGATE, (int) hi); + } + char lo = input.charAt(idx++); + if (!Character.isLowSurrogate(lo)) { + throw error(ErrorKind.UNPAIRED_SURROGATE, (int) hi); + } + ch = Character.toCodePoint(hi, lo); } public SourceFile source() { diff --git a/java/com/google/turbine/parse/VariableInitializerParser.java b/java/com/google/turbine/parse/VariableInitializerParser.java index 4ad9272..7f4d40e 100644 --- a/java/com/google/turbine/parse/VariableInitializerParser.java +++ b/java/com/google/turbine/parse/VariableInitializerParser.java @@ -40,10 +40,10 @@ import java.util.List; * <p>That handles everything except multi-variable declarations (int x = 1, y = 2;), which in * hindsight were probably a mistake. Multi-variable declarations contain a list of name and * initializer pairs separated by commas. The initializer expressions may also contain commas, so - * it's non-trivial to split on initializer boundaries. For example, consider `int x = a < b, c = - * d;`. We can't tell looking at the prefix `a < b, c` whether that's a less-than expression - * followed by another initializer, or the start of a generic type: `a<b, c>.foo()`. Distinguishing - * between these cases requires arbitrary lookahead. + * it's non-trivial to split on initializer boundaries. For example, consider {@code int x = a < b, + * c = d;}. We can't tell looking at the prefix {@code a < b, c} whether that's a less-than + * expression followed by another initializer, or the start of a generic type: {@code a<b, c>.foo(}. + * Distinguishing between these cases requires arbitrary lookahead. * * <p>The preprocessor seems to be operationally correct. It's possible there are edge cases that it * doesn't handle, but it's extremely rare for compile-time constant multi-variable declarations to @@ -330,6 +330,8 @@ public class VariableInitializerParser { depth--; next(); break; + case EOF: + throw error(ErrorKind.UNEXPECTED_EOF); default: next(); break; diff --git a/java/com/google/turbine/processing/TurbineAnnotationMirror.java b/java/com/google/turbine/processing/TurbineAnnotationMirror.java index 5ea3de1..df3bd19 100644 --- a/java/com/google/turbine/processing/TurbineAnnotationMirror.java +++ b/java/com/google/turbine/processing/TurbineAnnotationMirror.java @@ -18,6 +18,7 @@ package com.google.turbine.processing; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getLast; +import static java.util.Objects.requireNonNull; import com.google.common.base.Joiner; import com.google.common.base.Supplier; @@ -115,8 +116,13 @@ class TurbineAnnotationMirror implements TurbineAnnotationValueMirror, Annotatio ImmutableMap.Builder<ExecutableElement, AnnotationValue> result = ImmutableMap.builder(); for (Map.Entry<String, Const> value : anno.values().entrySet()) { + // requireNonNull is safe because `elements` contains an entry for every method. + // Any element values pairs without a corresponding method in the annotation + // definition are weeded out in ConstEvaluator.evaluateAnnotation, and don't + // appear in the AnnoInfo. + MethodInfo methodInfo = requireNonNull(elements.get().get(value.getKey())); result.put( - factory.executableElement(elements.get().get(value.getKey()).sym()), + factory.executableElement(methodInfo.sym()), annotationValue(factory, value.getValue())); } return result.build(); diff --git a/java/com/google/turbine/processing/TurbineAnnotationProxy.java b/java/com/google/turbine/processing/TurbineAnnotationProxy.java index c39f310..967ead9 100644 --- a/java/com/google/turbine/processing/TurbineAnnotationProxy.java +++ b/java/com/google/turbine/processing/TurbineAnnotationProxy.java @@ -17,6 +17,7 @@ package com.google.turbine.processing; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; import com.google.turbine.binder.bound.EnumConstantValue; import com.google.turbine.binder.bound.TurbineAnnotationValue; @@ -131,14 +132,15 @@ class TurbineAnnotationProxy implements InvocationHandler { private static Object constArrayValue( Class<?> returnType, ModelFactory factory, ClassLoader loader, ArrayInitValue value) { - if (returnType.getComponentType().equals(Class.class)) { + Class<?> componentType = requireNonNull(returnType.getComponentType()); + if (componentType.equals(Class.class)) { List<TypeMirror> result = new ArrayList<>(); for (Const element : value.elements()) { result.add(factory.asTypeMirror(((TurbineClassValue) element).type())); } throw new MirroredTypesException(result); } - Object result = Array.newInstance(returnType.getComponentType(), value.elements().size()); + Object result = Array.newInstance(componentType, value.elements().size()); int idx = 0; for (Const element : value.elements()) { Object v = constValue(returnType, factory, loader, element); diff --git a/java/com/google/turbine/processing/TurbineElement.java b/java/com/google/turbine/processing/TurbineElement.java index c22a442..f4f1675 100644 --- a/java/com/google/turbine/processing/TurbineElement.java +++ b/java/com/google/turbine/processing/TurbineElement.java @@ -46,7 +46,7 @@ import com.google.turbine.diag.TurbineError.ErrorKind; import com.google.turbine.model.Const; import com.google.turbine.model.Const.ArrayInitValue; import com.google.turbine.model.TurbineFlag; -import com.google.turbine.model.TurbineTyKind; +import com.google.turbine.tree.Tree; import com.google.turbine.tree.Tree.MethDecl; import com.google.turbine.tree.Tree.TyDecl; import com.google.turbine.tree.Tree.VarDecl; @@ -158,7 +158,8 @@ public abstract class TurbineElement implements Element { continue; } if (anno.sym().equals(metadata.repeatable())) { - ArrayInitValue arrayValue = (ArrayInitValue) anno.values().get("value"); + // requireNonNull is safe because java.lang.annotation.Repeatable declares `value`. + ArrayInitValue arrayValue = (ArrayInitValue) requireNonNull(anno.values().get("value")); for (Const element : arrayValue.elements()) { result.add( TurbineAnnotationProxy.create( @@ -262,11 +263,16 @@ public abstract class TurbineElement implements Element { return factory.asTypeMirror(info.superClassType()); } if (info instanceof SourceTypeBoundClass) { - // support simple name for stuff that doesn't exist + // support simple names for stuff that doesn't exist TyDecl decl = ((SourceTypeBoundClass) info).decl(); if (decl.xtnds().isPresent()) { - return factory.asTypeMirror( - ErrorTy.create(decl.xtnds().get().name().value())); + ArrayDeque<Tree.Ident> flat = new ArrayDeque<>(); + for (Tree.ClassTy curr = decl.xtnds().get(); + curr != null; + curr = curr.base().orElse(null)) { + flat.addFirst(curr.name()); + } + return factory.asTypeMirror(ErrorTy.create(flat)); } } return factory.noType(); @@ -785,18 +791,12 @@ public abstract class TurbineElement implements Element { @Override public ElementKind getKind() { - return info().name().equals("<init>") ? ElementKind.CONSTRUCTOR : ElementKind.METHOD; + return sym.name().equals("<init>") ? ElementKind.CONSTRUCTOR : ElementKind.METHOD; } @Override public Set<Modifier> getModifiers() { - int access = info().access(); - if (factory.getSymbol(info().sym().owner()).kind() == TurbineTyKind.INTERFACE) { - if ((access & (TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_STATIC)) == 0) { - access |= TurbineFlag.ACC_DEFAULT; - } - } - return asModifierSet(ModifierOwner.METHOD, access); + return asModifierSet(ModifierOwner.METHOD, info().access()); } @Override diff --git a/java/com/google/turbine/processing/TurbineElements.java b/java/com/google/turbine/processing/TurbineElements.java index 9da210e..7ede6e3 100644 --- a/java/com/google/turbine/processing/TurbineElements.java +++ b/java/com/google/turbine/processing/TurbineElements.java @@ -131,7 +131,7 @@ public class TurbineElements implements Elements { if (!(element instanceof TurbineElement)) { throw new IllegalArgumentException(element.toString()); } - for (AnnoInfo a : ((TurbineTypeElement) element).annos()) { + for (AnnoInfo a : ((TurbineElement) element).annos()) { if (a.sym().equals(ClassSymbol.DEPRECATED)) { return true; } @@ -265,8 +265,8 @@ public class TurbineElements implements Elements { } /** - * Returns true if an element with the given {@code visibility} and located in package {@from} is - * visible to elements in package {@code to}. + * Returns true if an element with the given {@code visibility} and located in package {@code + * from} is visible to elements in package {@code to}. */ private static boolean isVisible( PackageSymbol from, PackageSymbol to, TurbineVisibility visibility) { diff --git a/java/com/google/turbine/processing/TurbineFiler.java b/java/com/google/turbine/processing/TurbineFiler.java index 186eb7f..45cdc22 100644 --- a/java/com/google/turbine/processing/TurbineFiler.java +++ b/java/com/google/turbine/processing/TurbineFiler.java @@ -18,6 +18,7 @@ package com.google.turbine.processing; import static com.google.common.base.Preconditions.checkArgument; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import com.google.common.base.Function; import com.google.common.base.Supplier; @@ -361,7 +362,7 @@ public class TurbineFiler implements Filer { @Override public URI toUri() { try { - return loader.getResource(path).toURI(); + return requireNonNull(loader.getResource(path)).toURI(); } catch (URISyntaxException e) { throw new AssertionError(e); } diff --git a/java/com/google/turbine/processing/TurbineProcessingEnvironment.java b/java/com/google/turbine/processing/TurbineProcessingEnvironment.java index 726d075..8b44e75 100644 --- a/java/com/google/turbine/processing/TurbineProcessingEnvironment.java +++ b/java/com/google/turbine/processing/TurbineProcessingEnvironment.java @@ -26,7 +26,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import org.checkerframework.checker.nullness.qual.Nullable; -/** Turbine's {@link ProcessingEnvironment). */ +/** Turbine's {@link ProcessingEnvironment}. */ public class TurbineProcessingEnvironment implements ProcessingEnvironment { private final Filer filer; diff --git a/java/com/google/turbine/processing/TurbineTypes.java b/java/com/google/turbine/processing/TurbineTypes.java index f65f921..7d2e6c0 100644 --- a/java/com/google/turbine/processing/TurbineTypes.java +++ b/java/com/google/turbine/processing/TurbineTypes.java @@ -825,7 +825,7 @@ public class TurbineTypes implements Types { @Override public List<? extends TypeMirror> directSupertypes(TypeMirror m) { - return factory.asTypeMirrors(directSupertypes(asTurbineType(m))); + return factory.asTypeMirrors(deannotate(directSupertypes(asTurbineType(m)))); } public ImmutableList<Type> directSupertypes(Type t) { @@ -882,7 +882,12 @@ public class TurbineTypes implements Types { @Override public TypeMirror erasure(TypeMirror typeMirror) { - return factory.asTypeMirror(erasure(asTurbineType(typeMirror))); + Type t = erasure(asTurbineType(typeMirror)); + if (t.tyKind() == TyKind.CLASS_TY) { + // bug-parity with javac + t = deannotate(t); + } + return factory.asTypeMirror(t); } private Type erasure(Type type) { @@ -896,6 +901,50 @@ public class TurbineTypes implements Types { }); } + /** + * Remove some type annotation metadata for bug-compatibility with javac, which does this + * inconsistently (see https://bugs.openjdk.java.net/browse/JDK-8042981). + */ + private static Type deannotate(Type ty) { + switch (ty.tyKind()) { + case CLASS_TY: + return deannotateClassTy((Type.ClassTy) ty); + case ARRAY_TY: + return deannotateArrayTy((Type.ArrayTy) ty); + case TY_VAR: + case INTERSECTION_TY: + case WILD_TY: + case METHOD_TY: + case PRIM_TY: + case VOID_TY: + case ERROR_TY: + case NONE_TY: + return ty; + } + throw new AssertionError(ty.tyKind()); + } + + private static ImmutableList<Type> deannotate(ImmutableList<Type> types) { + ImmutableList.Builder<Type> result = ImmutableList.builder(); + for (Type type : types) { + result.add(deannotate(type)); + } + return result.build(); + } + + private static Type.ArrayTy deannotateArrayTy(Type.ArrayTy ty) { + return ArrayTy.create(deannotate(ty.elementType()), /* annos= */ ImmutableList.of()); + } + + public static Type.ClassTy deannotateClassTy(Type.ClassTy ty) { + ImmutableList.Builder<Type.ClassTy.SimpleClassTy> classes = ImmutableList.builder(); + for (Type.ClassTy.SimpleClassTy c : ty.classes()) { + classes.add( + SimpleClassTy.create(c.sym(), deannotate(c.targs()), /* annos= */ ImmutableList.of())); + } + return ClassTy.create(classes.build()); + } + @Override public TypeElement boxedClass(PrimitiveType p) { return factory.typeElement(boxedClass(((PrimTy) asTurbineType(p)).primkind())); diff --git a/java/com/google/turbine/type/Type.java b/java/com/google/turbine/type/Type.java index daba2ae..bdddc6c 100644 --- a/java/com/google/turbine/type/Type.java +++ b/java/com/google/turbine/type/Type.java @@ -246,11 +246,14 @@ public interface Type { @Override public final String toString() { StringBuilder sb = new StringBuilder(); - for (AnnoInfo anno : annos()) { - sb.append(anno); + sb.append(elementType()); + if (!annos().isEmpty()) { sb.append(' '); + for (AnnoInfo anno : annos()) { + sb.append(anno); + sb.append(' '); + } } - sb.append(elementType()); sb.append("[]"); return sb.toString(); } diff --git a/java/com/google/turbine/types/Deannotate.java b/java/com/google/turbine/types/Deannotate.java new file mode 100644 index 0000000..1edb11f --- /dev/null +++ b/java/com/google/turbine/types/Deannotate.java @@ -0,0 +1,88 @@ +/* + * Copyright 2021 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.turbine.types; + +import com.google.common.collect.ImmutableList; +import com.google.turbine.type.Type; + +/** Removes type annotation metadata. */ +public class Deannotate { + public static Type deannotate(Type ty) { + switch (ty.tyKind()) { + case CLASS_TY: + return deannotateClassTy((Type.ClassTy) ty); + case ARRAY_TY: + return Type.ArrayTy.create( + deannotate(((Type.ArrayTy) ty).elementType()), ImmutableList.of()); + case INTERSECTION_TY: + return Type.IntersectionTy.create(deannotate(((Type.IntersectionTy) ty).bounds())); + case WILD_TY: + return deannotateWildTy((Type.WildTy) ty); + case METHOD_TY: + return deannotateMethodTy((Type.MethodTy) ty); + case PRIM_TY: + return Type.PrimTy.create(((Type.PrimTy) ty).primkind(), ImmutableList.of()); + case TY_VAR: + return Type.TyVar.create(((Type.TyVar) ty).sym(), ImmutableList.of()); + case VOID_TY: + case ERROR_TY: + case NONE_TY: + return ty; + } + throw new AssertionError(ty.tyKind()); + } + + private static ImmutableList<Type> deannotate(ImmutableList<Type> types) { + ImmutableList.Builder<Type> result = ImmutableList.builder(); + for (Type type : types) { + result.add(deannotate(type)); + } + return result.build(); + } + + public static Type.ClassTy deannotateClassTy(Type.ClassTy ty) { + ImmutableList.Builder<Type.ClassTy.SimpleClassTy> classes = ImmutableList.builder(); + for (Type.ClassTy.SimpleClassTy c : ty.classes()) { + classes.add( + Type.ClassTy.SimpleClassTy.create(c.sym(), deannotate(c.targs()), ImmutableList.of())); + } + return Type.ClassTy.create(classes.build()); + } + + private static Type deannotateWildTy(Type.WildTy ty) { + switch (ty.boundKind()) { + case NONE: + return Type.WildUnboundedTy.create(ImmutableList.of()); + case LOWER: + return Type.WildLowerBoundedTy.create(ty.bound(), ImmutableList.of()); + case UPPER: + return Type.WildUpperBoundedTy.create(ty.bound(), ImmutableList.of()); + } + throw new AssertionError(ty.boundKind()); + } + + private static Type deannotateMethodTy(Type.MethodTy ty) { + return Type.MethodTy.create( + ty.tyParams(), + deannotate(ty.returnType()), + ty.receiverType() != null ? deannotate(ty.receiverType()) : null, + deannotate(ty.parameters()), + deannotate(ty.thrown())); + } + + private Deannotate() {} +} diff --git a/java/com/google/turbine/types/Erasure.java b/java/com/google/turbine/types/Erasure.java index 9042897..4b6fbc1 100644 --- a/java/com/google/turbine/types/Erasure.java +++ b/java/com/google/turbine/types/Erasure.java @@ -19,7 +19,6 @@ package com.google.turbine.types; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.turbine.binder.bound.SourceTypeBoundClass; import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo; import com.google.turbine.binder.sym.TyVarSymbol; import com.google.turbine.type.Type; @@ -32,8 +31,8 @@ import com.google.turbine.type.Type.TyVar; import com.google.turbine.type.Type.WildTy; /** Generic type erasure. */ -public class Erasure { - public static Type erase(Type ty, Function<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tenv) { +public final class Erasure { + public static Type erase(Type ty, Function<TyVarSymbol, TyVarInfo> tenv) { switch (ty.tyKind()) { case CLASS_TY: return eraseClassTy((Type.ClassTy) ty); @@ -70,14 +69,12 @@ public class Erasure { return ty.bounds().isEmpty() ? ClassTy.OBJECT : erase(ty.bounds().get(0), tenv); } - private static Type eraseTyVar( - TyVar ty, Function<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tenv) { - SourceTypeBoundClass.TyVarInfo info = tenv.apply(ty.sym()); + private static Type eraseTyVar(TyVar ty, Function<TyVarSymbol, TyVarInfo> tenv) { + TyVarInfo info = tenv.apply(ty.sym()); return erase(info.upperBound(), tenv); } - private static Type.ArrayTy eraseArrayTy( - Type.ArrayTy ty, Function<TyVarSymbol, SourceTypeBoundClass.TyVarInfo> tenv) { + private static Type.ArrayTy eraseArrayTy(Type.ArrayTy ty, Function<TyVarSymbol, TyVarInfo> tenv) { return ArrayTy.create(erase(ty.elementType(), tenv), ty.annos()); } @@ -112,4 +109,6 @@ public class Erasure { erase(ty.parameters(), tenv), erase(ty.thrown(), tenv)); } + + private Erasure() {} } diff --git a/java/com/google/turbine/zip/Zip.java b/java/com/google/turbine/zip/Zip.java index 48d4697..fa0f0e0 100644 --- a/java/com/google/turbine/zip/Zip.java +++ b/java/com/google/turbine/zip/Zip.java @@ -71,7 +71,7 @@ import java.util.zip.ZipException; * header is present only if ENDTOT in EOCD header is 0xFFFF. * </ul> */ -public class Zip { +public final class Zip { static final int ZIP64_ENDSIG = 0x06064b50; @@ -335,4 +335,6 @@ public class Zip { && (buf.get(index + 2) == i) && (buf.get(index + 3) == j); } + + private Zip() {} } diff --git a/javatests/com/google/turbine/binder/BinderErrorTest.java b/javatests/com/google/turbine/binder/BinderErrorTest.java index 15b54eb..e6e30cb 100644 --- a/javatests/com/google/turbine/binder/BinderErrorTest.java +++ b/javatests/com/google/turbine/binder/BinderErrorTest.java @@ -18,7 +18,7 @@ package com.google.turbine.binder; import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -93,6 +93,9 @@ public class BinderErrorTest { "<>:2: error: could not resolve element foo() in Anno", // "@Anno(foo=100, bar=200) class Test {}", " ^", + "<>:2: error: could not resolve element bar() in Anno", // + "@Anno(foo=100, bar=200) class Test {}", + " ^", }, }, { @@ -558,9 +561,12 @@ public class BinderErrorTest { "class T {}", }, { - "<>:7: error: could not resolve B", // + "<>:7: error: could not resolve B", "@One.A(b = {@B})", " ^", + "<>:7: error: could not evaluate constant expression", + "@One.A(b = {@B})", + " ^", }, }, { @@ -700,6 +706,112 @@ public class BinderErrorTest { " ^", }, }, + { + { + "import java.util.List;", + "class T {", // + " List<int> xs = new ArrayList<>();", + "}", + }, + { + "<>:3: error: unexpected type int", // + " List<int> xs = new ArrayList<>();", + " ^", + }, + }, + { + { + "@interface A {", + " int[] xs() default {};", + "}", + "@A(xs = Object.class)", + "class T {", + "}", + }, + { + "<>:4: error: could not evaluate constant expression", + "@A(xs = Object.class)", + " ^", + }, + }, + { + { + "package foobar;", + "import java.lang.annotation.Retention;", + "@Retention", + "@interface Test {}", + }, + { + "<>:3: error: missing required annotation argument: value", // + "@Retention", + "^", + }, + }, + { + { + "interface Test {", // + " static final void f() {}", + "}", + }, + { + "<>:2: error: unexpected modifier: final", // + " static final void f() {}", + " ^", + }, + }, + { + { + "package foobar;", + "import java.lang.annotation.Retention;", + "@Retention", + "@Retention", + "@interface Test {}", + }, + { + "<>:3: error: missing required annotation argument: value", + "@Retention", + "^", + "<>:4: error: missing required annotation argument: value", + "@Retention", + "^", + "<>:3: error: java.lang.annotation.Retention is not @Repeatable", + "@Retention", + "^", + }, + }, + { + { + "import java.util.List;", // + "class Test {", + " @interface A {}", + " void f(List<@NoSuch int> xs) {}", + "}", + }, + { + "<>:4: error: could not resolve NoSuch", + " void f(List<@NoSuch int> xs) {}", + " ^", + "<>:4: error: unexpected type int", + " void f(List<@NoSuch int> xs) {}", + " ^", + }, + }, + { + { + "@interface B {}", + "@interface A {", + " B[] value() default @B;", + "}", + "interface C {}", + "@A(value = @C)", + "class T {}", + }, + { + "<>:6: error: C is not an annotation", // + "@A(value = @C)", + " ^", + }, + }, }; return Arrays.asList((Object[][]) testCases); } @@ -714,17 +826,18 @@ public class BinderErrorTest { @Test public void test() throws Exception { - try { - Binder.bind( - ImmutableList.of(parseLines(source)), - ClassPathBinder.bindClasspath(ImmutableList.of()), - TURBINE_BOOTCLASSPATH, - /* moduleVersion=*/ Optional.empty()) - .units(); - fail(Joiner.on('\n').join(source)); - } catch (TurbineError e) { - assertThat(e).hasMessageThat().isEqualTo(lines(expected)); - } + TurbineError e = + assertThrows( + Joiner.on('\n').join(source), + TurbineError.class, + () -> + Binder.bind( + ImmutableList.of(parseLines(source)), + ClassPathBinder.bindClasspath(ImmutableList.of()), + TURBINE_BOOTCLASSPATH, + /* moduleVersion=*/ Optional.empty()) + .units()); + assertThat(e).hasMessageThat().isEqualTo(lines(expected)); } @SupportedAnnotationTypes("*") @@ -744,22 +857,23 @@ public class BinderErrorTest { // exercise error reporting with annotation enabled, which should be identical @Test public void testWithProcessors() throws Exception { - try { - Binder.bind( - ImmutableList.of(parseLines(source)), - ClassPathBinder.bindClasspath(ImmutableList.of()), - ProcessorInfo.create( - ImmutableList.of(new HelloWorldProcessor()), - /* loader= */ getClass().getClassLoader(), - /* options= */ ImmutableMap.of(), - SourceVersion.latestSupported()), - TURBINE_BOOTCLASSPATH, - /* moduleVersion=*/ Optional.empty()) - .units(); - fail(Joiner.on('\n').join(source)); - } catch (TurbineError e) { - assertThat(e).hasMessageThat().isEqualTo(lines(expected)); - } + TurbineError e = + assertThrows( + Joiner.on('\n').join(source), + TurbineError.class, + () -> + Binder.bind( + ImmutableList.of(parseLines(source)), + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new HelloWorldProcessor()), + /* loader= */ getClass().getClassLoader(), + /* options= */ ImmutableMap.of(), + SourceVersion.latestSupported()), + TURBINE_BOOTCLASSPATH, + /* moduleVersion=*/ Optional.empty()) + .units()); + assertThat(e).hasMessageThat().isEqualTo(lines(expected)); } private static CompUnit parseLines(String... lines) { diff --git a/javatests/com/google/turbine/binder/BinderTest.java b/javatests/com/google/turbine/binder/BinderTest.java index e238ee0..820fe22 100644 --- a/javatests/com/google/turbine/binder/BinderTest.java +++ b/javatests/com/google/turbine/binder/BinderTest.java @@ -19,7 +19,8 @@ package com.google.turbine.binder; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; -import static org.junit.Assert.fail; +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -84,17 +85,16 @@ public class BinderTest { new ClassSymbol("a/A$Inner2"), new ClassSymbol("b/B")); - SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A")); + SourceTypeBoundClass a = getBoundClass(bound, "a/A"); assertThat(a.superclass()).isEqualTo(new ClassSymbol("java/lang/Object")); assertThat(a.interfaces()).isEmpty(); - assertThat(bound.get(new ClassSymbol("a/A$Inner1")).superclass()) - .isEqualTo(new ClassSymbol("b/B")); + assertThat(getBoundClass(bound, "a/A$Inner1").superclass()).isEqualTo(new ClassSymbol("b/B")); - assertThat(bound.get(new ClassSymbol("a/A$Inner2")).superclass()) + assertThat(getBoundClass(bound, "a/A$Inner2").superclass()) .isEqualTo(new ClassSymbol("a/A$Inner1")); - SourceTypeBoundClass b = bound.get(new ClassSymbol("b/B")); + SourceTypeBoundClass b = getBoundClass(bound, "b/B"); assertThat(b.superclass()).isEqualTo(new ClassSymbol("a/A")); } @@ -129,12 +129,12 @@ public class BinderTest { new ClassSymbol("b/B"), new ClassSymbol("b/B$BInner")); - assertThat(bound.get(new ClassSymbol("b/B")).interfaces()) + assertThat(getBoundClass(bound, "b/B").interfaces()) .containsExactly(new ClassSymbol("com/i/I")); - assertThat(bound.get(new ClassSymbol("b/B$BInner")).superclass()) + assertThat(getBoundClass(bound, "b/B$BInner").superclass()) .isEqualTo(new ClassSymbol("com/i/I$IInner")); - assertThat(bound.get(new ClassSymbol("b/B$BInner")).interfaces()).isEmpty(); + assertThat(getBoundClass(bound, "b/B$BInner").interfaces()).isEmpty(); } @Test @@ -161,7 +161,7 @@ public class BinderTest { /* moduleVersion=*/ Optional.empty()) .units(); - assertThat(bound.get(new ClassSymbol("other/Foo")).superclass()) + assertThat(getBoundClass(bound, "other/Foo").superclass()) .isEqualTo(new ClassSymbol("com/test/Test$Inner")); } @@ -182,16 +182,16 @@ public class BinderTest { " class Inner {}", "}")); - try { - Binder.bind( - units, - ClassPathBinder.bindClasspath(ImmutableList.of()), - TURBINE_BOOTCLASSPATH, - /* moduleVersion=*/ Optional.empty()); - fail(); - } catch (TurbineError e) { - assertThat(e).hasMessageThat().contains("cycle in class hierarchy: a.A -> b.B -> a.A"); - } + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + TURBINE_BOOTCLASSPATH, + /* moduleVersion=*/ Optional.empty())); + assertThat(e).hasMessageThat().contains("cycle in class hierarchy: a.A -> b.B -> a.A"); } @Test @@ -211,7 +211,7 @@ public class BinderTest { /* moduleVersion=*/ Optional.empty()) .units(); - SourceTypeBoundClass a = bound.get(new ClassSymbol("com/test/Annotation")); + SourceTypeBoundClass a = getBoundClass(bound, "com/test/Annotation"); assertThat(a.access()) .isEqualTo( TurbineFlag.ACC_PUBLIC @@ -240,7 +240,7 @@ public class BinderTest { /* moduleVersion=*/ Optional.empty()) .units(); - SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A")); + SourceTypeBoundClass a = getBoundClass(bound, "a/A"); assertThat(a.interfaces()).containsExactly(new ClassSymbol("java/util/Map$Entry")); } @@ -259,7 +259,7 @@ public class BinderTest { try (OutputStream os = Files.newOutputStream(libJar); JarOutputStream jos = new JarOutputStream(os)) { jos.putNextEntry(new JarEntry("B.class")); - jos.write(lib.get("B")); + jos.write(requireNonNull(lib.get("B"))); } ImmutableList<Tree.CompUnit> units = @@ -280,7 +280,7 @@ public class BinderTest { /* moduleVersion=*/ Optional.empty()) .units(); - SourceTypeBoundClass a = bound.get(new ClassSymbol("C$A")); + SourceTypeBoundClass a = getBoundClass(bound, "C$A"); assertThat(a.annotationMetadata().target()).containsExactly(TurbineElementType.TYPE_USE); } @@ -306,7 +306,7 @@ public class BinderTest { assertThat(bound.keySet()).containsExactly(new ClassSymbol("a/A")); - SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A")); + SourceTypeBoundClass a = getBoundClass(bound, "a/A"); FieldInfo f = getOnlyElement(a.fields()); assertThat(f.name()).isEqualTo("b"); assertThat(f.value()).isNull(); @@ -315,4 +315,10 @@ public class BinderTest { private Tree.CompUnit parseLines(String... lines) { return Parser.parse(Joiner.on('\n').join(lines)); } + + private static SourceTypeBoundClass getBoundClass( + Map<ClassSymbol, SourceTypeBoundClass> bound, String name) { + // requireNonNull is safe as long as we call this method with classes that exist in our sources. + return requireNonNull(bound.get(new ClassSymbol(name))); + } } diff --git a/javatests/com/google/turbine/binder/ClassPathBinderTest.java b/javatests/com/google/turbine/binder/ClassPathBinderTest.java index c11d814..6c6bc3e 100644 --- a/javatests/com/google/turbine/binder/ClassPathBinderTest.java +++ b/javatests/com/google/turbine/binder/ClassPathBinderTest.java @@ -16,17 +16,22 @@ package com.google.turbine.binder; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; +import static com.google.turbine.testing.TestResources.getResourceBytes; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; +import static java.util.Locale.ENGLISH; +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertThrows; import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; -import com.google.common.io.ByteStreams; import com.google.common.io.MoreFiles; import com.google.turbine.binder.bound.EnumConstantValue; import com.google.turbine.binder.bound.TypeBoundClass; @@ -34,6 +39,7 @@ import com.google.turbine.binder.bytecode.BytecodeBoundClass; import com.google.turbine.binder.env.Env; import com.google.turbine.binder.lookup.LookupKey; import com.google.turbine.binder.lookup.LookupResult; +import com.google.turbine.binder.lookup.PackageScope; import com.google.turbine.binder.lookup.Scope; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; @@ -42,40 +48,100 @@ import com.google.turbine.model.TurbineTyKind; import com.google.turbine.tree.Tree.Ident; import com.google.turbine.type.AnnoInfo; import com.google.turbine.type.Type.ClassTy; -import java.io.IOError; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.runners.Parameterized; -@RunWith(JUnit4.class) +@RunWith(Parameterized.class) public class ClassPathBinderTest { + @Parameterized.Parameters + public static ImmutableCollection<Object[]> parameters() { + Object[] testCases = { + TURBINE_BOOTCLASSPATH, + FileManagerClassBinder.adapt( + ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, ENGLISH, UTF_8), + StandardLocation.PLATFORM_CLASS_PATH), + }; + return Arrays.stream(testCases).map(x -> new Object[] {x}).collect(toImmutableList()); + } + + private final ClassPath classPath; + + public ClassPathBinderTest(ClassPath classPath) { + this.classPath = classPath; + } + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + private static Ident ident(String string) { + return new Ident(/* position= */ -1, string); + } + @Test - public void classPathLookup() throws IOException { + public void classPathLookup() { - Scope javaLang = TURBINE_BOOTCLASSPATH.index().lookupPackage(ImmutableList.of("java", "lang")); + Scope javaLang = classPath.index().lookupPackage(ImmutableList.of("java", "lang")); - LookupResult result = javaLang.lookup(new LookupKey(ImmutableList.of(new Ident(-1, "String")))); + final String string = "String"; + LookupResult result = javaLang.lookup(new LookupKey(ImmutableList.of(ident(string)))); assertThat(result.remaining()).isEmpty(); assertThat(result.sym()).isEqualTo(new ClassSymbol("java/lang/String")); - result = javaLang.lookup(new LookupKey(ImmutableList.of(new Ident(-1, "Object")))); + result = javaLang.lookup(new LookupKey(ImmutableList.of(ident("Object")))); assertThat(result.remaining()).isEmpty(); assertThat(result.sym()).isEqualTo(new ClassSymbol("java/lang/Object")); } @Test - public void classPathClasses() throws IOException { - Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env(); + public void packageScope() { + + PackageScope result = classPath.index().lookupPackage(ImmutableList.of("java", "nosuch")); + assertThat(result).isNull(); + + result = classPath.index().lookupPackage(ImmutableList.of("java", "lang")); + assertThat(result.classes()).contains(new ClassSymbol("java/lang/String")); + + assertThat(result.lookup(new LookupKey(ImmutableList.of(ident("NoSuch"))))).isNull(); + } + + @Test + public void scope() { + Scope scope = classPath.index().scope(); + LookupResult result; + + result = + scope.lookup( + new LookupKey( + ImmutableList.of(ident("java"), ident("util"), ident("Map"), ident("Entry")))); + assertThat(result.sym()).isEqualTo(new ClassSymbol("java/util/Map")); + assertThat(result.remaining().stream().map(Ident::value)).containsExactly("Entry"); + + result = + scope.lookup(new LookupKey(ImmutableList.of(ident("java"), ident("util"), ident("Map")))); + assertThat(result.sym()).isEqualTo(new ClassSymbol("java/util/Map")); + assertThat(result.remaining()).isEmpty(); + + result = + scope.lookup( + new LookupKey(ImmutableList.of(ident("java"), ident("util"), ident("NoSuch")))); + assertThat(result).isNull(); + } + + @Test + public void classPathClasses() { + Env<ClassSymbol, BytecodeBoundClass> env = classPath.env(); TypeBoundClass c = env.get(new ClassSymbol("java/util/Map$Entry")); assertThat(c.owner()).isEqualTo(new ClassSymbol("java/util/Map")); @@ -96,7 +162,7 @@ public class ClassPathBinderTest { @Test public void interfaces() { - Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env(); + Env<ClassSymbol, BytecodeBoundClass> env = classPath.env(); TypeBoundClass c = env.get(new ClassSymbol("java/lang/annotation/Retention")); assertThat(c.interfaceTypes()).hasSize(1); @@ -114,7 +180,7 @@ public class ClassPathBinderTest { @Test public void annotations() { - Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env(); + Env<ClassSymbol, BytecodeBoundClass> env = classPath.env(); TypeBoundClass c = env.get(new ClassSymbol("java/lang/annotation/Retention")); AnnoInfo anno = @@ -122,34 +188,25 @@ public class ClassPathBinderTest { .filter(a -> a.sym().equals(new ClassSymbol("java/lang/annotation/Retention"))) .collect(onlyElement()); assertThat(anno.values().keySet()).containsExactly("value"); - assertThat(((EnumConstantValue) anno.values().get("value")).sym()) + // requireNonNull is safe because we checked that the keySet contains `"value"`. + assertThat(((EnumConstantValue) requireNonNull(anno.values().get("value"))).sym()) .isEqualTo( new FieldSymbol(new ClassSymbol("java/lang/annotation/RetentionPolicy"), "RUNTIME")); } @Test public void byteCodeBoundClassName() { + Env<ClassSymbol, BytecodeBoundClass> env = classPath.env(); BytecodeBoundClass c = new BytecodeBoundClass( new ClassSymbol("java/util/List"), - () -> { - try { - return ByteStreams.toByteArray( - getClass().getClassLoader().getResourceAsStream("java/util/ArrayList.class")); - } catch (IOException e) { - throw new IOError(e); - } - }, - null, + () -> getResourceBytes(getClass(), "/java/util/ArrayList.class"), + env, null); - try { - c.owner(); - fail(); - } catch (VerifyException e) { - assertThat(e) - .hasMessageThat() - .contains("expected class data for java/util/List, saw java/util/ArrayList instead"); - } + VerifyException e = assertThrows(VerifyException.class, () -> c.owner()); + assertThat(e) + .hasMessageThat() + .contains("expected class data for java/util/List, saw java/util/ArrayList instead"); } @Test @@ -157,12 +214,9 @@ public class ClassPathBinderTest { Path lib = temporaryFolder.newFile("NOT_A_JAR").toPath(); MoreFiles.asCharSink(lib, UTF_8).write("hello"); - try { - ClassPathBinder.bindClasspath(ImmutableList.of(lib)); - fail(); - } catch (IOException e) { - assertThat(e).hasMessageThat().contains("NOT_A_JAR"); - } + IOException e = + assertThrows(IOException.class, () -> ClassPathBinder.bindClasspath(ImmutableList.of(lib))); + assertThat(e).hasMessageThat().contains("NOT_A_JAR"); } @Test @@ -178,4 +232,21 @@ public class ClassPathBinderTest { assertThat(new String(classPath.resource("foo/bar/hello.txt").get(), UTF_8)).isEqualTo("hello"); assertThat(classPath.resource("foo/bar/Baz.class")).isNull(); } + + @Test + public void resourcesFileManager() throws Exception { + Path path = temporaryFolder.newFile("tmp.jar").toPath(); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) { + jos.putNextEntry(new JarEntry("foo/bar/hello.txt")); + jos.write("hello".getBytes(UTF_8)); + jos.putNextEntry(new JarEntry("foo/bar/Baz.class")); + jos.write("goodbye".getBytes(UTF_8)); + } + StandardJavaFileManager fileManager = + ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, ENGLISH, UTF_8); + fileManager.setLocation(StandardLocation.CLASS_PATH, ImmutableList.of(path.toFile())); + ClassPath classPath = FileManagerClassBinder.adapt(fileManager, StandardLocation.CLASS_PATH); + assertThat(new String(classPath.resource("foo/bar/hello.txt").get(), UTF_8)).isEqualTo("hello"); + assertThat(classPath.resource("foo/bar/NoSuch.class")).isNull(); + } } diff --git a/javatests/com/google/turbine/binder/CtSymClassBinderTest.java b/javatests/com/google/turbine/binder/CtSymClassBinderTest.java new file mode 100644 index 0000000..2da9f4c --- /dev/null +++ b/javatests/com/google/turbine/binder/CtSymClassBinderTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.turbine.binder; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CtSymClassBinderTest { + @Test + public void formatReleaseVersion() { + ImmutableList.of("5", "6", "7", "8", "9") + .forEach(x -> assertThat(CtSymClassBinder.formatReleaseVersion(x)).isEqualTo(x)); + ImmutableMap.of( + "10", "A", + "11", "B", + "12", "C", + "35", "Z") + .forEach((k, v) -> assertThat(CtSymClassBinder.formatReleaseVersion(k)).isEqualTo(v)); + ImmutableList.of("4", "36") + .forEach( + x -> + assertThrows( + x, + IllegalArgumentException.class, + () -> CtSymClassBinder.formatReleaseVersion(x))); + } +} diff --git a/javatests/com/google/turbine/binder/ProcessingTest.java b/javatests/com/google/turbine/binder/ProcessingTest.java new file mode 100644 index 0000000..b7091e8 --- /dev/null +++ b/javatests/com/google/turbine/binder/ProcessingTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2020 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.turbine.binder; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import javax.lang.model.SourceVersion; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ProcessingTest { + @Test + public void parseSourceVersion() { + assertThat(Processing.parseSourceVersion(ImmutableList.of())) + .isEqualTo(SourceVersion.latestSupported()); + assertThat(Processing.parseSourceVersion(ImmutableList.of("-source", "8", "-target", "11"))) + .isEqualTo(SourceVersion.RELEASE_8); + assertThat(Processing.parseSourceVersion(ImmutableList.of("-source", "8", "-source", "7"))) + .isEqualTo(SourceVersion.RELEASE_7); + } + + @Test + public void withPrefix() { + assertThat(Processing.parseSourceVersion(ImmutableList.of("-source", "1.7"))) + .isEqualTo(SourceVersion.RELEASE_7); + assertThat(Processing.parseSourceVersion(ImmutableList.of("-source", "1.8"))) + .isEqualTo(SourceVersion.RELEASE_8); + } + + @Test + public void invalidPrefix() { + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> Processing.parseSourceVersion(ImmutableList.of("-source", "1.11"))); + assertThat(expected).hasMessageThat().contains("invalid -source version: 1.11"); + } + + @Test + public void latestSupported() { + String latest = SourceVersion.latestSupported().toString(); + assertThat(latest).startsWith("RELEASE_"); + latest = latest.substring("RELEASE_".length()); + assertThat(Processing.parseSourceVersion(ImmutableList.of("-source", latest))) + .isEqualTo(SourceVersion.latestSupported()); + } + + @Test + public void missingArgument() { + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> Processing.parseSourceVersion(ImmutableList.of("-source"))); + assertThat(expected).hasMessageThat().contains("-source requires an argument"); + } + + @Test + public void invalidSourceVersion() { + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> Processing.parseSourceVersion(ImmutableList.of("-source", "NOSUCH"))); + assertThat(expected).hasMessageThat().contains("invalid -source version: NOSUCH"); + } +} diff --git a/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java b/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java index 3e841a5..ec2ebbf 100644 --- a/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java +++ b/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java @@ -17,6 +17,7 @@ package com.google.turbine.binder.bytecode; import static com.google.common.collect.Iterables.getLast; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; @@ -31,6 +32,7 @@ import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; import com.google.turbine.binder.env.CompoundEnv; import com.google.turbine.binder.env.Env; import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.model.TurbineFlag; import com.google.turbine.type.Type; import com.google.turbine.type.Type.ClassTy; import java.io.IOException; @@ -40,6 +42,7 @@ import java.io.UncheckedIOException; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -81,9 +84,11 @@ public class BytecodeBoundClassTest { .isEqualTo(new ClassSymbol("java/lang/String")); } + @SuppressWarnings({"deprecation", "TypeNameShadowing", "InlineMeSuggester"}) static class HasMethod { @Deprecated - <X, Y extends X, Z extends Throwable> X foo(@Deprecated X bar, Y baz) throws IOException, Z { + <X, Y extends X, Z extends Throwable> @Nullable X foo(@Deprecated X bar, Y baz) + throws IOException, Z { return null; } @@ -175,6 +180,19 @@ public class BytecodeBoundClassTest { assertThat(getBytecodeBoundClass(C.class, B.class, A.class).methods()).hasSize(1); } + interface D { + default void f() {} + } + + @Test + public void defaultMethods() { + assertThat( + (getOnlyElement(getBytecodeBoundClass(D.class).methods()).access() + & TurbineFlag.ACC_DEFAULT) + == TurbineFlag.ACC_DEFAULT) + .isTrue(); + } + private static byte[] toByteArrayOrDie(InputStream is) { try { return ByteStreams.toByteArray(is); @@ -201,7 +219,7 @@ public class BytecodeBoundClassTest { .append( new Env<ClassSymbol, BytecodeBoundClass>() { @Override - public BytecodeBoundClass get(ClassSymbol sym) { + public @Nullable BytecodeBoundClass get(ClassSymbol sym) { return map.get(sym); } }); diff --git a/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java b/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java index 022e47c..861bfef 100644 --- a/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java +++ b/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java @@ -18,7 +18,7 @@ package com.google.turbine.binder.lookup; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.turbine.binder.sym.ClassSymbol; @@ -105,15 +105,8 @@ public class TopLevelIndexTest { @Test public void emptyLookup() { - LookupKey key = lookupKey(ImmutableList.of("java", "util", "List")); - key = key.rest(); - key = key.rest(); - try { - key.rest(); - fail("expected exception"); - } catch (NoSuchElementException e) { - // expected - } + LookupKey key = lookupKey(ImmutableList.of("java", "util", "List")).rest().rest(); + assertThrows(NoSuchElementException.class, () -> key.rest()); } private LookupKey lookupKey(ImmutableList<String> names) { diff --git a/javatests/com/google/turbine/bytecode/ClassReaderTest.java b/javatests/com/google/turbine/bytecode/ClassReaderTest.java index fb64541..9a9fdb1 100644 --- a/javatests/com/google/turbine/bytecode/ClassReaderTest.java +++ b/javatests/com/google/turbine/bytecode/ClassReaderTest.java @@ -18,6 +18,7 @@ package com.google.turbine.bytecode; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; +import static java.util.Objects.requireNonNull; import com.google.common.base.Strings; import com.google.common.collect.Iterables; @@ -34,6 +35,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ByteVector; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.ModuleVisitor; @@ -136,7 +139,7 @@ public class ClassReaderTest { assertThat(annotation.typeName()).isEqualTo("Ljava/lang/annotation/Retention;"); assertThat(annotation.elementValuePairs()).hasSize(1); assertThat(annotation.elementValuePairs()).containsKey("value"); - ElementValue value = annotation.elementValuePairs().get("value"); + ElementValue value = requireNonNull(annotation.elementValuePairs().get("value")); assertThat(value.kind()).isEqualTo(ElementValue.Kind.ENUM); ElementValue.EnumConstValue enumValue = (ElementValue.EnumConstValue) value; assertThat(enumValue.typeName()).isEqualTo("Ljava/lang/annotation/RetentionPolicy;"); @@ -335,4 +338,28 @@ public class ClassReaderTest { assertThat(p2.descriptor()).isEqualTo("p2"); assertThat(p2.implDescriptors()).containsExactly("p2i1", "p2i2", "p2i3"); } + + @Test + public void transitiveJar() { + ClassWriter cw = new ClassWriter(0); + cw.visit( + 52, + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, + "Hello", + null, + "java/lang/Object", + null); + cw.visitAttribute( + new Attribute("TurbineTransitiveJar") { + @Override + protected ByteVector write( + ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) { + ByteVector result = new ByteVector(); + result.putShort(classWriter.newUTF8("path/to/transitive.jar")); + return result; + } + }); + ClassFile cf = ClassReader.read(null, cw.toByteArray()); + assertThat(cf.transitiveJar()).isEqualTo("path/to/transitive.jar"); + } } diff --git a/javatests/com/google/turbine/bytecode/ClassWriterTest.java b/javatests/com/google/turbine/bytecode/ClassWriterTest.java index 71cf356..f488bbe 100644 --- a/javatests/com/google/turbine/bytecode/ClassWriterTest.java +++ b/javatests/com/google/turbine/bytecode/ClassWriterTest.java @@ -90,7 +90,8 @@ public class ClassWriterTest { byte[] original = Files.readAllBytes(out.resolve("test/Test.class")); byte[] actual = ClassWriter.writeClass(ClassReader.read(null, original)); - assertThat(AsmUtils.textify(original)).isEqualTo(AsmUtils.textify(actual)); + assertThat(AsmUtils.textify(original, /* skipDebug= */ true)) + .isEqualTo(AsmUtils.textify(actual, /* skipDebug= */ true)); } // Test that >Short.MAX_VALUE constants round-trip through the constant pool. @@ -145,10 +146,12 @@ public class ClassWriterTest { byte[] inputBytes = cw.toByteArray(); byte[] outputBytes = ClassWriter.writeClass(ClassReader.read("module-info", inputBytes)); - assertThat(AsmUtils.textify(inputBytes)).isEqualTo(AsmUtils.textify(outputBytes)); + assertThat(AsmUtils.textify(inputBytes, /* skipDebug= */ true)) + .isEqualTo(AsmUtils.textify(outputBytes, /* skipDebug= */ true)); // test a round trip outputBytes = ClassWriter.writeClass(ClassReader.read("module-info", outputBytes)); - assertThat(AsmUtils.textify(inputBytes)).isEqualTo(AsmUtils.textify(outputBytes)); + assertThat(AsmUtils.textify(inputBytes, /* skipDebug= */ true)) + .isEqualTo(AsmUtils.textify(outputBytes, /* skipDebug= */ true)); } } diff --git a/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java b/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java index f3ab8e7..8602fe5 100644 --- a/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java +++ b/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java @@ -17,6 +17,7 @@ package com.google.turbine.bytecode.sig; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.io.MoreFiles.getFileExtension; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Splitter; @@ -70,7 +71,7 @@ public class SigIntegrationTest { Stream<Path> stream = Files.walk(jarfs.getPath("/"))) { stream .filter(Files::isRegularFile) - .filter(p -> p.getFileName().toString().endsWith(".class")) + .filter(p -> getFileExtension(p).equals("class")) .forEachOrdered(consumer); } } @@ -80,7 +81,7 @@ public class SigIntegrationTest { Map<String, ?> env = new HashMap<>(); try (FileSystem fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env); Stream<Path> stream = Files.walk(fileSystem.getPath("/modules"))) { - stream.filter(p -> p.getFileName().toString().endsWith(".class")).forEachOrdered(consumer); + stream.filter(p -> getFileExtension(p).equals("class")).forEachOrdered(consumer); } } } @@ -93,7 +94,7 @@ public class SigIntegrationTest { try { new ClassReader(Files.newInputStream(path)) .accept( - new ClassVisitor(Opcodes.ASM7) { + new ClassVisitor(Opcodes.ASM9) { @Override public void visit( int version, diff --git a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java deleted file mode 100644 index c5b68ff..0000000 --- a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.turbine.deps; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.common.truth.Truth.assertThat; -import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.io.ByteStreams; -import com.google.turbine.bytecode.ClassFile; -import com.google.turbine.bytecode.ClassFile.InnerClass; -import com.google.turbine.bytecode.ClassReader; -import com.google.turbine.main.Main; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; - -public abstract class AbstractTransitiveTest { - - protected abstract Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath) - throws IOException; - - @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - - class SourceBuilder { - private final Path lib; - private final ImmutableList.Builder<Path> sources = ImmutableList.builder(); - - SourceBuilder() throws IOException { - lib = temporaryFolder.newFolder().toPath(); - } - - SourceBuilder addSourceLines(String name, String... lines) throws IOException { - Path path = lib.resolve(name); - Files.createDirectories(path.getParent()); - Files.write(path, Arrays.asList(lines), UTF_8); - sources.add(path); - return this; - } - - ImmutableList<Path> build() { - return sources.build(); - } - } - - private Map<String, byte[]> readJar(Path libb) throws IOException { - Map<String, byte[]> jarEntries = new LinkedHashMap<>(); - try (JarFile jf = new JarFile(libb.toFile())) { - Enumeration<JarEntry> entries = jf.entries(); - while (entries.hasMoreElements()) { - JarEntry je = entries.nextElement(); - jarEntries.put(je.getName(), ByteStreams.toByteArray(jf.getInputStream(je))); - } - } - return jarEntries; - } - - @Test - public void transitive() throws Exception { - Path liba = - runTurbine( - new SourceBuilder() - .addSourceLines( - "a/A.java", - "package a;", - "import java.util.Map;", - "public class A {", - " public @interface Anno {", - " int x() default 42;", - " }", - " public static class Inner {}", - " public static final int CONST = 42;", - " public int mutable = 42;", - " public Map.Entry<String, String> f(Map<String, String> m) {", - " return m.entrySet().iterator().next();", - " }", - "}") - .build(), - ImmutableList.of()); - - Path libb = - runTurbine( - new SourceBuilder() - .addSourceLines("b/B.java", "package b;", "public class B extends a.A {}") - .build(), - ImmutableList.of(liba)); - - // libb repackages A, and any member types - assertThat(readJar(libb).keySet()) - .containsExactly( - "b/B.class", - "META-INF/TRANSITIVE/a/A.class", - "META-INF/TRANSITIVE/a/A$Anno.class", - "META-INF/TRANSITIVE/a/A$Inner.class"); - - ClassFile a = ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A.class")); - // methods and non-constant fields are removed - assertThat(getOnlyElement(a.fields()).name()).isEqualTo("CONST"); - assertThat(a.methods()).isEmpty(); - assertThat(Iterables.transform(a.innerClasses(), InnerClass::innerClass)) - .containsExactly("a/A$Anno", "a/A$Inner"); - - // annotation interface methods are preserved - assertThat( - ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A$Anno.class")) - .methods()) - .hasSize(1); - - // A class that references members of the transitive supertype A by simple name - // compiles cleanly against the repackaged version of A. - // Explicitly use turbine; javac-turbine doesn't support direct-classpath compilations. - - Path libc = temporaryFolder.newFolder().toPath().resolve("out.jar"); - ImmutableList<String> sources = - new SourceBuilder() - .addSourceLines( - "c/C.java", - "package c;", - "public class C extends b.B {", - " @Anno(x = 2) static final Inner i; // a.A$Inner ", - " static final int X = CONST; // a.A#CONST", - "}") - .build() - .stream() - .map(Path::toString) - .collect(toImmutableList()); - Main.compile( - optionsWithBootclasspath() - .setSources(sources) - .setClassPath( - ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList())) - .setOutput(libc.toString()) - .build()); - - assertThat(readJar(libc).keySet()) - .containsExactly( - "c/C.class", - "META-INF/TRANSITIVE/b/B.class", - "META-INF/TRANSITIVE/a/A.class", - "META-INF/TRANSITIVE/a/A$Anno.class", - "META-INF/TRANSITIVE/a/A$Inner.class"); - } - - @Test - public void anonymous() throws Exception { - Path liba = temporaryFolder.newFolder().toPath().resolve("out.jar"); - try (OutputStream os = Files.newOutputStream(liba); - JarOutputStream jos = new JarOutputStream(os)) { - { - jos.putNextEntry(new JarEntry("a/A.class")); - ClassWriter cw = new ClassWriter(0); - cw.visit(52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A", null, "java/lang/Object", null); - cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); - cw.visitInnerClass("a/A$1", "a/A", null, Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC); - cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC); - jos.write(cw.toByteArray()); - } - { - jos.putNextEntry(new JarEntry("a/A$1.class")); - ClassWriter cw = new ClassWriter(0); - cw.visit( - 52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$1", null, "java/lang/Object", null); - cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); - cw.visitInnerClass("a/A$1", "a/A", "I", Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC); - jos.write(cw.toByteArray()); - } - { - jos.putNextEntry(new JarEntry("a/A$I.class")); - ClassWriter cw = new ClassWriter(0); - cw.visit( - 52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$I", null, "java/lang/Object", null); - cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); - cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC); - jos.write(cw.toByteArray()); - } - } - Path libb = - runTurbine( - new SourceBuilder() - .addSourceLines( - "b/B.java", // - "package b;", - "public class B extends a.A {}") - .build(), - ImmutableList.of(liba)); - - // libb repackages A and any named member types - assertThat(readJar(libb).keySet()) - .containsExactly( - "b/B.class", "META-INF/TRANSITIVE/a/A.class", "META-INF/TRANSITIVE/a/A$I.class"); - } - - @Test - public void childClass() throws Exception { - Path liba = - runTurbine( - new SourceBuilder() - .addSourceLines( - "a/S.java", // - "package a;", - "public class S {}") - .addSourceLines( - "a/A.java", // - "package a;", - "public class A {", - " public class I extends S {}", - "}") - .build(), - ImmutableList.of()); - - Path libb = - runTurbine( - new SourceBuilder() - .addSourceLines( - "b/B.java", // - "package b;", - "public class B extends a.A {", - " class I extends a.A.I {", - " }", - "}") - .build(), - ImmutableList.of(liba)); - - assertThat(readJar(libb).keySet()) - .containsExactly( - "b/B.class", - "b/B$I.class", - "META-INF/TRANSITIVE/a/A.class", - "META-INF/TRANSITIVE/a/A$I.class", - "META-INF/TRANSITIVE/a/S.class"); - } -} diff --git a/javatests/com/google/turbine/deps/TransitiveTest.java b/javatests/com/google/turbine/deps/TransitiveTest.java index 2c9f807..f08e899 100644 --- a/javatests/com/google/turbine/deps/TransitiveTest.java +++ b/javatests/com/google/turbine/deps/TransitiveTest.java @@ -17,20 +17,281 @@ package com.google.turbine.deps; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.io.ByteStreams; +import com.google.protobuf.ExtensionRegistry; +import com.google.turbine.bytecode.ClassFile; +import com.google.turbine.bytecode.ClassFile.InnerClass; +import com.google.turbine.bytecode.ClassReader; import com.google.turbine.main.Main; +import com.google.turbine.proto.DepsProto; +import com.google.turbine.proto.DepsProto.Dependency.Kind; +import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; @RunWith(JUnit4.class) -public class TransitiveTest extends AbstractTransitiveTest { +public class TransitiveTest { - @Override - protected Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath) + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + class SourceBuilder { + private final Path lib; + private final ImmutableList.Builder<Path> sources = ImmutableList.builder(); + + SourceBuilder() throws IOException { + lib = temporaryFolder.newFolder().toPath(); + } + + SourceBuilder addSourceLines(String name, String... lines) throws IOException { + Path path = lib.resolve(name); + Files.createDirectories(path.getParent()); + Files.write(path, Arrays.asList(lines), UTF_8); + sources.add(path); + return this; + } + + ImmutableList<Path> build() { + return sources.build(); + } + } + + private static Map<String, byte[]> readJar(Path libb) throws IOException { + Map<String, byte[]> jarEntries = new LinkedHashMap<>(); + try (JarFile jf = new JarFile(libb.toFile())) { + Enumeration<JarEntry> entries = jf.entries(); + while (entries.hasMoreElements()) { + JarEntry je = entries.nextElement(); + jarEntries.put(je.getName(), ByteStreams.toByteArray(jf.getInputStream(je))); + } + } + return jarEntries; + } + + @Test + public void transitive() throws Exception { + Path liba = + runTurbine( + new SourceBuilder() + .addSourceLines( + "a/A.java", + "package a;", + "import java.util.Map;", + "public class A {", + " public @interface Anno {", + " int x() default 42;", + " }", + " public static class Inner {}", + " public static final int CONST = 42;", + " public int mutable = 42;", + " public Map.Entry<String, String> f(Map<String, String> m) {", + " return m.entrySet().iterator().next();", + " }", + "}") + .build(), + ImmutableList.of()); + + Path libb = + runTurbine( + new SourceBuilder() + .addSourceLines("b/B.java", "package b;", "public class B extends a.A {}") + .build(), + ImmutableList.of(liba)); + + // libb repackages A, and any member types + assertThat(readJar(libb).keySet()) + .containsExactly( + "b/B.class", + "META-INF/TRANSITIVE/a/A.class", + "META-INF/TRANSITIVE/a/A$Anno.class", + "META-INF/TRANSITIVE/a/A$Inner.class"); + + ClassFile a = ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A.class")); + // methods and non-constant fields are removed + assertThat(getOnlyElement(a.fields()).name()).isEqualTo("CONST"); + assertThat(a.methods()).isEmpty(); + assertThat(Iterables.transform(a.innerClasses(), InnerClass::innerClass)) + .containsExactly("a/A$Anno", "a/A$Inner"); + + // annotation interface methods are preserved + assertThat( + ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A$Anno.class")) + .methods()) + .hasSize(1); + + // When a.A is repackaged as a transitive class in libb, its 'transitive jar' attribute + // should record the path to the original liba jar. + assertThat(a.transitiveJar()).isEqualTo(liba.toString()); + // The transitive jar attribute is only set for transitive classes, not e.g. b.B in libb: + ClassFile b = ClassReader.read(null, readJar(libb).get("b/B.class")); + assertThat(b.transitiveJar()).isNull(); + + // A class that references members of the transitive supertype A by simple name + // compiles cleanly against the repackaged version of A. + // Explicitly use turbine; javac-turbine doesn't support direct-classpath compilations. + + Path libc = temporaryFolder.newFolder().toPath().resolve("out.jar"); + Path libcDeps = temporaryFolder.newFolder().toPath().resolve("out.jdeps"); + ImmutableList<String> sources = + new SourceBuilder() + .addSourceLines( + "c/C.java", + "package c;", + "public class C extends b.B {", + " @Anno(x = 2) static final Inner i; // a.A$Inner ", + " static final int X = CONST; // a.A#CONST", + "}") + .build() + .stream() + .map(Path::toString) + .collect(toImmutableList()); + Main.compile( + optionsWithBootclasspath() + .setSources(sources) + .setClassPath( + ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList())) + .setOutput(libc.toString()) + .setOutputDeps(libcDeps.toString()) + .build()); + + assertThat(readJar(libc).keySet()) + .containsExactly( + "c/C.class", + "META-INF/TRANSITIVE/b/B.class", + "META-INF/TRANSITIVE/a/A.class", + "META-INF/TRANSITIVE/a/A$Anno.class", + "META-INF/TRANSITIVE/a/A$Inner.class"); + + // liba is recorded as an explicit dep, even thought it's only present as a transitive class + // repackaged in lib + assertThat(readDeps(libcDeps)) + .containsExactly(liba.toString(), Kind.EXPLICIT, libb.toString(), Kind.EXPLICIT); + } + + private static ImmutableMap<String, Kind> readDeps(Path libcDeps) throws IOException { + DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder(); + try (InputStream is = new BufferedInputStream(Files.newInputStream(libcDeps))) { + deps.mergeFrom(is, ExtensionRegistry.getEmptyRegistry()); + } + return deps.getDependencyList().stream() + .collect(toImmutableMap(d -> d.getPath(), d -> d.getKind())); + } + + @Test + public void anonymous() throws Exception { + Path liba = temporaryFolder.newFolder().toPath().resolve("out.jar"); + try (OutputStream os = Files.newOutputStream(liba); + JarOutputStream jos = new JarOutputStream(os)) { + { + jos.putNextEntry(new JarEntry("a/A.class")); + ClassWriter cw = new ClassWriter(0); + cw.visit(52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A", null, "java/lang/Object", null); + cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); + cw.visitInnerClass("a/A$1", "a/A", null, Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC); + cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC); + jos.write(cw.toByteArray()); + } + { + jos.putNextEntry(new JarEntry("a/A$1.class")); + ClassWriter cw = new ClassWriter(0); + cw.visit( + 52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$1", null, "java/lang/Object", null); + cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); + cw.visitInnerClass("a/A$1", "a/A", "I", Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC); + jos.write(cw.toByteArray()); + } + { + jos.putNextEntry(new JarEntry("a/A$I.class")); + ClassWriter cw = new ClassWriter(0); + cw.visit( + 52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$I", null, "java/lang/Object", null); + cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); + cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC); + jos.write(cw.toByteArray()); + } + } + Path libb = + runTurbine( + new SourceBuilder() + .addSourceLines( + "b/B.java", // + "package b;", + "public class B extends a.A {}") + .build(), + ImmutableList.of(liba)); + + // libb repackages A and any named member types + assertThat(readJar(libb).keySet()) + .containsExactly( + "b/B.class", "META-INF/TRANSITIVE/a/A.class", "META-INF/TRANSITIVE/a/A$I.class"); + } + + @Test + public void childClass() throws Exception { + Path liba = + runTurbine( + new SourceBuilder() + .addSourceLines( + "a/S.java", // + "package a;", + "public class S {}") + .addSourceLines( + "a/A.java", // + "package a;", + "public class A {", + " public class I extends S {}", + "}") + .build(), + ImmutableList.of()); + + Path libb = + runTurbine( + new SourceBuilder() + .addSourceLines( + "b/B.java", // + "package b;", + "public class B extends a.A {", + " class I extends a.A.I {", + " }", + "}") + .build(), + ImmutableList.of(liba)); + + assertThat(readJar(libb).keySet()) + .containsExactly( + "b/B.class", + "b/B$I.class", + "META-INF/TRANSITIVE/a/A.class", + "META-INF/TRANSITIVE/a/A$I.class", + "META-INF/TRANSITIVE/a/S.class"); + } + + private Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath) throws IOException { Path out = temporaryFolder.newFolder().toPath().resolve("out.jar"); Main.compile( diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java index a03473d..744f341 100644 --- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java +++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java @@ -17,8 +17,10 @@ package com.google.turbine.lower; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.io.MoreFiles.getFileExtension; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; @@ -82,7 +84,7 @@ import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeAnnotationNode; /** Support for bytecode diffing-integration tests. */ -public class IntegrationTestSupport { +public final class IntegrationTestSupport { /** * Normalizes order of members, attributes, and constant pool entries, to allow diffing bytecode. @@ -410,20 +412,21 @@ public class IntegrationTestSupport { final Set<String> classes1 = classes; new SignatureReader(signature) .accept( - new SignatureVisitor(Opcodes.ASM7) { + new SignatureVisitor(Opcodes.ASM9) { private final Set<String> classes = classes1; // class signatures may contain type arguments that contain class signatures Deque<List<String>> pieces = new ArrayDeque<>(); @Override public void visitInnerClassType(String name) { - pieces.peek().add(name); + pieces.element().add(name); } @Override public void visitClassType(String name) { - pieces.push(new ArrayList<>()); - pieces.peek().add(name); + List<String> classType = new ArrayList<>(); + classType.add(name); + pieces.push(classType); } @Override @@ -510,7 +513,7 @@ public class IntegrationTestSupport { @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { - if (path.getFileName().toString().endsWith(".class")) { + if (getFileExtension(path).equals("class")) { classes.add(path); } return FileVisitResult.CONTINUE; @@ -551,7 +554,9 @@ public class IntegrationTestSupport { fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, ImmutableList.of(out)); fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, classpath); fileManager.setLocationFromPaths(StandardLocation.locationFor("MODULE_PATH"), classpath); - if (inputs.stream().filter(i -> i.getFileName().toString().equals("module-info.java")).count() + if (inputs.stream() + .filter(i -> requireNonNull(i.getFileName()).toString().equals("module-info.java")) + .count() > 1) { // multi-module mode fileManager.setLocationFromPaths( @@ -578,7 +583,7 @@ public class IntegrationTestSupport { na = na.substring(1); } sb.append(String.format("=== %s ===\n", na)); - sb.append(AsmUtils.textify(compiled.get(key))); + sb.append(AsmUtils.textify(compiled.get(key), /* skipDebug= */ true)); } return sb.toString(); } @@ -634,4 +639,6 @@ public class IntegrationTestSupport { return new TestInput(sources, classes); } } + + private IntegrationTestSupport() {} } diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java index 85c3450..ab4e0ee 100644 --- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java +++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java @@ -17,12 +17,11 @@ package com.google.turbine.lower; import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; +import static com.google.turbine.testing.TestResources.getResource; import static java.util.stream.Collectors.toList; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; -import com.google.common.io.ByteStreams; import java.io.IOError; import java.io.IOException; import java.nio.file.Files; @@ -45,26 +44,103 @@ public class LowerIntegrationTest { @Parameters(name = "{index}: {0}") public static Iterable<Object[]> parameters() { String[] testCases = { + // keep-sorted start + "B33513475.test", + "B33513475b.test", + "B33513475c.test", + "B70953542.test", + "B8056066.test", + "B8056066b.test", + "B8075274.test", + "B8148131.test", "abstractenum.test", "access1.test", + "anno_const_coerce.test", + "anno_const_scope.test", + "anno_nested.test", + "anno_repeated.test", + "anno_self_const.test", + "anno_void.test", + "annoconstvis.test", + "annotation_bool_default.test", + "annotation_class_default.test", + "annotation_clinit.test", + "annotation_declaration.test", + "annotation_enum_default.test", + "annotation_scope.test", + "annotations_default.test", + "annouse.test", + "annouse10.test", + "annouse11.test", + "annouse12.test", + "annouse13.test", + "annouse14.test", + "annouse15.test", + "annouse16.test", + "annouse17.test", + "annouse2.test", + "annouse3.test", + "annouse4.test", + "annouse5.test", + "annouse6.test", + "annouse7.test", + "annouse8.test", + "annouse9.test", + "annovis.test", "anonymous.test", + "array_class_literal.test", + "ascii_sub.test", "asset.test", - "outerparam.test", "basic_field.test", "basic_nested.test", "bcp.test", + "bmethod.test", + "bounds.test", + "boxed_const.test", "builder.test", "byte.test", "byte2.test", + "bytecode_boolean_const.test", + "bytenoncanon.test", + "c_array.test", + "canon.test", + "canon_class_header.test", + "canon_recursive.test", + "cast_tail.test", "circ_cvar.test", "clash.test", + "complex_param_anno.test", + "concat.test", + "const.test", + "const_all.test", + "const_arith.test", + "const_boxed.test", + "const_byte.test", + "const_char.test", + "const_conditional.test", + "const_conv.test", + "const_field.test", + "const_hiding.test", + "const_moreexpr.test", + "const_multi.test", + "const_nonfinal.test", + "const_octal_underscore.test", + "const_types.test", + "const_underscore.test", + "constlevel.test", + "constpack.test", + "ctor_anno.test", "ctorvis.test", "cvar_qualified.test", "cycle.test", "default_fbound.test", "default_rawfbound.test", "default_simple.test", + "deficient_types_classfile.test", + "dollar.test", "enum1.test", + "enum_abstract.test", + "enum_final.test", "enumctor.test", "enumctor2.test", "enumimpl.test", @@ -78,6 +154,7 @@ public class LowerIntegrationTest { "enumint_objectmethod2.test", "enumint_objectmethod_raw.test", "enuminthacks.test", + "enummemberanno.test", "enumstat.test", "erasurebound.test", "existingctor.test", @@ -87,230 +164,156 @@ public class LowerIntegrationTest { "extendsandimplements.test", "extrainnerclass.test", "fbound.test", + "field_anno.test", "firstcomparator.test", + "float_exponent.test", "fuse.test", "genericarrayfield.test", "genericexn.test", "genericexn2.test", + "genericnoncanon.test", + "genericnoncanon1.test", + "genericnoncanon10.test", + "genericnoncanon2.test", + "genericnoncanon3.test", + "genericnoncanon4.test", + "genericnoncanon5.test", + "genericnoncanon6.test", + "genericnoncanon8.test", + "genericnoncanon9.test", + "genericnoncanon_byte.test", + "genericnoncanon_method3.test", "genericret.test", + "hex_int.test", "hierarchy.test", "ibound.test", "icu.test", "icu2.test", + "import_wild_order.test", + "importconst.test", "importinner.test", + "inner_static.test", + "innerannodecl.test", + "innerclassanno.test", "innerctor.test", "innerenum.test", "innerint.test", "innerstaticgeneric.test", + "interface_field.test", + "interface_member_public.test", + "interface_method.test", "interfacemem.test", "interfaces.test", + // TODO(cushon): crashes ASM, see: + // https://gitlab.ow2.org/asm/asm/issues/317776 + // "canon_array.test", + "java_lang_object.test", + "javadoc_deprecated.test", "lexical.test", "lexical2.test", "lexical4.test", "list.test", + "local.test", + "long_expression.test", "loopthroughb.test", "mapentry.test", + "marker.test", "member.test", + "member_import_clash.test", + // TODO(cushon): support for source level 9 in integration tests + // "B74332665.test", + "memberimport.test", "mods.test", "morefields.test", "moremethods.test", "multifield.test", "nested.test", "nested2.test", + "nested_member_import.test", + "nested_member_import_noncanon.test", + "non_const.test", + "noncanon.test", + "noncanon_static_wild.test", + "nonconst_unary_expression.test", "one.test", "outer.test", + "outerparam.test", + "package_info.test", + "packagedecl.test", "packageprivateprotectedinner.test", "param_bound.test", + "prim_class.test", + "private_member.test", "privateinner.test", "proto.test", "proto2.test", "qual.test", "raw.test", "raw2.test", + "raw_canon.test", + "rawcanon.test", "rawfbound.test", + "receiver_param.test", "rek.test", "samepkg.test", "self.test", "semi.test", + // https://bugs.openjdk.java.net/browse/JDK-8054064 ? + "shadow_inherited.test", "simple.test", "simplemethod.test", + "source_anno_retention.test", + "source_bootclasspath_order.test", + "static_final_boxed.test", + "static_member_type_import.test", + "static_member_type_import_recursive.test", + "static_type_import.test", + "strictfp.test", "string.test", "superabstract.test", "supplierfunction.test", "tbound.test", + "tyanno_inner.test", + "tyanno_varargs.test", "typaram.test", + "typaram_lookup.test", + "typaram_lookup_enclosing.test", + "type_anno_ambiguous.test", + "type_anno_ambiguous_param.test", + "type_anno_ambiguous_qualified.test", + "type_anno_array_bound.test", + "type_anno_array_dims.test", + "type_anno_c_array.test", + "type_anno_cstyle_array_dims.test", + "type_anno_hello.test", + "type_anno_order.test", + "type_anno_parameter_index.test", + "type_anno_qual.test", + "type_anno_raw.test", + "type_anno_receiver.test", + "type_anno_retention.test", + "type_anno_return.test", + "tyvar_bound.test", "tyvarfield.test", + "unary.test", + "underscore_literal.test", + "unicode.test", + "unicode_pkg.test", "useextend.test", "vanillaexception.test", "varargs.test", - "wild.test", - "bytenoncanon.test", - "canon.test", - "genericnoncanon.test", - "genericnoncanon1.test", - "genericnoncanon10.test", - "genericnoncanon2.test", - "genericnoncanon3.test", - "genericnoncanon4.test", - "genericnoncanon5.test", - "genericnoncanon6.test", - "genericnoncanon8.test", - "genericnoncanon9.test", - "genericnoncanon_byte.test", - "genericnoncanon_method3.test", - "noncanon.test", - "rawcanon.test", - "wildboundcanon.test", - "wildcanon.test", - "annoconstvis.test", - "const_byte.test", - "const_char.test", - "const_field.test", - "const_types.test", - "const_underscore.test", - "constlevel.test", - "constpack.test", - "importconst.test", - "const.test", - "const_all.test", - "const_arith.test", - "const_conditional.test", - "const_moreexpr.test", - "const_multi.test", - "field_anno.test", - "annotation_bool_default.test", - "annotation_class_default.test", - "annotation_declaration.test", - "annotation_enum_default.test", - "annotations_default.test", - "annouse.test", - "annouse10.test", - "annouse11.test", - "annouse12.test", - "annouse13.test", - "annouse14.test", - "annouse15.test", - "annouse16.test", - "annouse17.test", - "annouse2.test", - "annouse3.test", - "annouse4.test", - "annouse5.test", - "annouse6.test", - "annouse7.test", - "annouse8.test", - "annouse9.test", - "annovis.test", - "complex_param_anno.test", - "enummemberanno.test", - "innerannodecl.test", - "source_anno_retention.test", - "anno_nested.test", - "nested_member_import.test", - "nested_member_import_noncanon.test", - "unary.test", - "hex_int.test", - "const_conv.test", - "bmethod.test", - "prim_class.test", - "wild2.test", - "wild3.test", - "const_hiding.test", - "interface_field.test", - "concat.test", - "static_type_import.test", - "non_const.test", - "bounds.test", - "cast_tail.test", - "marker.test", - "interface_method.test", - "raw_canon.test", - "float_exponent.test", - "boxed_const.test", - "package_info.test", - "import_wild_order.test", - "canon_recursive.test", - // TODO(cushon): crashes ASM, see: - // https://gitlab.ow2.org/asm/asm/issues/317776 - // "canon_array.test", - "java_lang_object.test", + "visible_nested.test", "visible_package.test", + "visible_package_private_toplevel.test", "visible_private.test", - "visible_same_package.test", - "private_member.test", - "visible_nested.test", "visible_qualified.test", - "ascii_sub.test", - "bytecode_boolean_const.test", - "tyvar_bound.test", - "type_anno_hello.test", - "type_anno_array_dims.test", - "nonconst_unary_expression.test", - "type_anno_ambiguous.test", - "type_anno_ambiguous_param.test", - "unicode.test", - "annotation_scope.test", - "visible_package_private_toplevel.test", - "receiver_param.test", - "static_member_type_import.test", - "type_anno_qual.test", - "array_class_literal.test", - "underscore_literal.test", - "c_array.test", - "type_anno_retention.test", - "member_import_clash.test", - "anno_repeated.test", - "long_expression.test", - "const_nonfinal.test", - "enum_abstract.test", - "deficient_types_classfile.test", - "ctor_anno.test", - "anno_const_coerce.test", - "const_octal_underscore.test", - "const_boxed.test", - "interface_member_public.test", - "javadoc_deprecated.test", - "strictfp.test", - "type_anno_raw.test", - "inner_static.test", - "innerclassanno.test", - "type_anno_parameter_index.test", - "anno_const_scope.test", - "type_anno_ambiguous_qualified.test", - "type_anno_array_bound.test", - "type_anno_return.test", - "type_anno_order.test", - "canon_class_header.test", - "type_anno_receiver.test", - "enum_final.test", - "dollar.test", - "typaram_lookup.test", - "typaram_lookup_enclosing.test", - "B33513475.test", - "B33513475b.test", - "B33513475c.test", - "noncanon_static_wild.test", - "B8075274.test", - "B8148131.test", - "B8056066.test", - "B8056066b.test", - "source_bootclasspath_order.test", - "anno_self_const.test", - "type_anno_cstyle_array_dims.test", - "packagedecl.test", - "static_member_type_import_recursive.test", - "B70953542.test", - // TODO(cushon): support for source level 9 in integration tests - // "B74332665.test", - "memberimport.test", - "type_anno_c_array.test", - // https://bugs.openjdk.java.net/browse/JDK-8054064 ? - "shadow_inherited.test", - "static_final_boxed.test", - "anno_void.test", - "tyanno_varargs.test", - "tyanno_inner.test", - "local.test", + "visible_same_package.test", + "wild.test", + "wild2.test", + "wild3.test", + "wildboundcanon.test", + "wildcanon.test", + // keep-sorted end }; List<Object[]> tests = ImmutableList.copyOf(testCases).stream().map(x -> new Object[] {x}).collect(toList()); @@ -344,10 +347,7 @@ public class LowerIntegrationTest { public void test() throws Exception { IntegrationTestSupport.TestInput input = - IntegrationTestSupport.TestInput.parse( - new String( - ByteStreams.toByteArray(getClass().getResourceAsStream("testdata/" + test)), - UTF_8)); + IntegrationTestSupport.TestInput.parse(getResource(getClass(), "testdata/" + test)); ImmutableList<Path> classpathJar = ImmutableList.of(); if (!input.classes.isEmpty()) { diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java index 8151e81..d74e829 100644 --- a/javatests/com/google/turbine/lower/LowerTest.java +++ b/javatests/com/google/turbine/lower/LowerTest.java @@ -18,13 +18,13 @@ package com.google.turbine.lower; import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; +import static com.google.turbine.testing.TestResources.getResource; +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.io.ByteStreams; import com.google.turbine.binder.Binder; import com.google.turbine.binder.Binder.BindingResult; import com.google.turbine.binder.ClassPathBinder; @@ -233,18 +233,10 @@ public class LowerTest { TURBINE_BOOTCLASSPATH.env()) .bytes(); - assertThat(AsmUtils.textify(bytes.get("test/Test"))) - .isEqualTo( - new String( - ByteStreams.toByteArray( - LowerTest.class.getResourceAsStream("testdata/golden/outer.txt")), - UTF_8)); - assertThat(AsmUtils.textify(bytes.get("test/Test$Inner"))) - .isEqualTo( - new String( - ByteStreams.toByteArray( - LowerTest.class.getResourceAsStream("testdata/golden/inner.txt")), - UTF_8)); + assertThat(AsmUtils.textify(bytes.get("test/Test"), /* skipDebug= */ false)) + .isEqualTo(getResource(LowerTest.class, "testdata/golden/outer.txt")); + assertThat(AsmUtils.textify(bytes.get("test/Test$Inner"), /* skipDebug= */ false)) + .isEqualTo(getResource(LowerTest.class, "testdata/golden/inner.txt")); } @Test @@ -268,7 +260,7 @@ public class LowerTest { List<String> attributes = new ArrayList<>(); new ClassReader(lowered.get("Test$Inner$InnerMost")) .accept( - new ClassVisitor(Opcodes.ASM7) { + new ClassVisitor(Opcodes.ASM9) { @Override public void visitInnerClass( String name, String outerName, String innerName, int access) { @@ -285,10 +277,7 @@ public class LowerTest { public void wildArrayElement() throws Exception { IntegrationTestSupport.TestInput input = IntegrationTestSupport.TestInput.parse( - new String( - ByteStreams.toByteArray( - getClass().getResourceAsStream("testdata/canon_array.test")), - UTF_8)); + getResource(getClass(), "testdata/canon_array.test")); Map<String, byte[]> actual = IntegrationTestSupport.runTurbine(input.sources, ImmutableList.of()); @@ -346,11 +335,11 @@ public class LowerTest { TypePath[] path = new TypePath[1]; new ClassReader(lowered.get("Test")) .accept( - new ClassVisitor(Opcodes.ASM7) { + new ClassVisitor(Opcodes.ASM9) { @Override public FieldVisitor visitField( int access, String name, String desc, String signature, Object value) { - return new FieldVisitor(Opcodes.ASM7) { + return new FieldVisitor(Opcodes.ASM9) { @Override public AnnotationVisitor visitTypeAnnotation( int typeRef, TypePath typePath, String desc, boolean visible) { @@ -397,7 +386,7 @@ public class LowerTest { Map<String, Object> values = new LinkedHashMap<>(); new ClassReader(actual.get("Test")) .accept( - new ClassVisitor(Opcodes.ASM7) { + new ClassVisitor(Opcodes.ASM9) { @Override public FieldVisitor visitField( int access, String name, String desc, String signature, Object value) { @@ -424,7 +413,7 @@ public class LowerTest { int[] acc = {0}; new ClassReader(lowered.get("Test")) .accept( - new ClassVisitor(Opcodes.ASM7) { + new ClassVisitor(Opcodes.ASM9) { @Override public void visit( int version, @@ -522,16 +511,11 @@ public class LowerTest { Path libJar = temporaryFolder.newFile("lib.jar").toPath(); try (OutputStream os = Files.newOutputStream(libJar); JarOutputStream jos = new JarOutputStream(os)) { - jos.putNextEntry(new JarEntry("A$M.class")); - jos.write(lib.get("A$M")); - jos.putNextEntry(new JarEntry("A$M$I.class")); - jos.write(lib.get("A$M$I")); - jos.putNextEntry(new JarEntry("B.class")); - jos.write(lib.get("B")); - jos.putNextEntry(new JarEntry("B$BM.class")); - jos.write(lib.get("B$BM")); - jos.putNextEntry(new JarEntry("B$BM$BI.class")); - jos.write(lib.get("B$BM$BI")); + write(jos, lib, "A$M"); + write(jos, lib, "A$M$I"); + write(jos, lib, "B"); + write(jos, lib, "B$BM"); + write(jos, lib, "B$BM$BI"); } ImmutableMap<String, String> sources = @@ -544,14 +528,13 @@ public class LowerTest { "}")) .build(); - try { - IntegrationTestSupport.runTurbine(sources, ImmutableList.of(libJar)); - fail(); - } catch (TurbineError error) { - assertThat(error) - .hasMessageThat() - .contains("Test.java: error: could not locate class file for A"); - } + TurbineError error = + assertThrows( + TurbineError.class, + () -> IntegrationTestSupport.runTurbine(sources, ImmutableList.of(libJar))); + assertThat(error) + .hasMessageThat() + .contains("Test.java: error: could not locate class file for A"); } @Test @@ -579,16 +562,11 @@ public class LowerTest { Path libJar = temporaryFolder.newFile("lib.jar").toPath(); try (OutputStream os = Files.newOutputStream(libJar); JarOutputStream jos = new JarOutputStream(os)) { - jos.putNextEntry(new JarEntry("A$M.class")); - jos.write(lib.get("A$M")); - jos.putNextEntry(new JarEntry("A$M$I.class")); - jos.write(lib.get("A$M$I")); - jos.putNextEntry(new JarEntry("B.class")); - jos.write(lib.get("B")); - jos.putNextEntry(new JarEntry("B$BM.class")); - jos.write(lib.get("B$BM")); - jos.putNextEntry(new JarEntry("B$BM$BI.class")); - jos.write(lib.get("B$BM$BI")); + write(jos, lib, "A$M"); + write(jos, lib, "A$M$I"); + write(jos, lib, "B"); + write(jos, lib, "B$BM"); + write(jos, lib, "B$BM$BI"); } ImmutableMap<String, String> sources = @@ -603,18 +581,15 @@ public class LowerTest { "}")) .build(); - try { - IntegrationTestSupport.runTurbine(sources, ImmutableList.of(libJar)); - fail(); - } catch (TurbineError error) { - assertThat(error) - .hasMessageThat() - .contains( - lines( - "Test.java:3: error: could not locate class file for A", - " I i;", - " ^")); - } + TurbineError error = + assertThrows( + TurbineError.class, + () -> IntegrationTestSupport.runTurbine(sources, ImmutableList.of(libJar))); + assertThat(error) + .hasMessageThat() + .contains( + lines( + "Test.java:3: error: could not locate class file for A", " I i;", " ^")); } // If an element incorrectly has multiple visibility modifiers, pick one, and rely on javac to @@ -629,7 +604,7 @@ public class LowerTest { int[] testAccess = {0}; new ClassReader(lowered.get("Test")) .accept( - new ClassVisitor(Opcodes.ASM7) { + new ClassVisitor(Opcodes.ASM9) { @Override public void visit( int version, @@ -649,4 +624,9 @@ public class LowerTest { static String lines(String... lines) { return Joiner.on(System.lineSeparator()).join(lines); } + + static void write(JarOutputStream jos, Map<String, byte[]> lib, String name) throws IOException { + jos.putNextEntry(new JarEntry(name + ".class")); + jos.write(requireNonNull(lib.get(name))); + } } diff --git a/javatests/com/google/turbine/lower/ModuleIntegrationTest.java b/javatests/com/google/turbine/lower/ModuleIntegrationTest.java index 03c6fb7..f2c0bbf 100644 --- a/javatests/com/google/turbine/lower/ModuleIntegrationTest.java +++ b/javatests/com/google/turbine/lower/ModuleIntegrationTest.java @@ -18,12 +18,11 @@ package com.google.turbine.lower; import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION; import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static java.nio.charset.StandardCharsets.UTF_8; +import static com.google.turbine.testing.TestResources.getResource; import static java.util.stream.Collectors.toList; import static org.junit.Assert.assertEquals; import com.google.common.collect.ImmutableList; -import com.google.common.io.ByteStreams; import com.google.turbine.binder.CtSymClassBinder; import com.google.turbine.binder.JimageClassBinder; import java.nio.file.Files; @@ -68,10 +67,7 @@ public class ModuleIntegrationTest { } IntegrationTestSupport.TestInput input = - IntegrationTestSupport.TestInput.parse( - new String( - ByteStreams.toByteArray(getClass().getResourceAsStream("moduletestdata/" + test)), - UTF_8)); + IntegrationTestSupport.TestInput.parse(getResource(getClass(), "moduletestdata/" + test)); ImmutableList<Path> classpathJar = ImmutableList.of(); if (!input.classes.isEmpty()) { diff --git a/javatests/com/google/turbine/lower/testdata/annotation_clinit.test b/javatests/com/google/turbine/lower/testdata/annotation_clinit.test new file mode 100644 index 0000000..7419ed6 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/annotation_clinit.test @@ -0,0 +1,18 @@ +%%% pkg/Anno.java %%% +package pkg; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Anno { + String CONSTANT = Anno.class.toString(); + + String value() default ""; +} + +=== pkg/T.java === +package pkg; + +@Anno +class T {} diff --git a/javatests/com/google/turbine/lower/testdata/unicode_pkg.test b/javatests/com/google/turbine/lower/testdata/unicode_pkg.test new file mode 100644 index 0000000..85d38d9 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/unicode_pkg.test @@ -0,0 +1,4 @@ +=== Test.java === +package pkg𐀀.test; + +class Test {} diff --git a/javatests/com/google/turbine/main/MainTest.java b/javatests/com/google/turbine/main/MainTest.java index 5d47632..57940f3 100644 --- a/javatests/com/google/turbine/main/MainTest.java +++ b/javatests/com/google/turbine/main/MainTest.java @@ -23,7 +23,8 @@ import static com.google.common.truth.Truth8.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -40,8 +41,11 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UncheckedIOException; import java.io.Writer; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Enumeration; @@ -88,16 +92,16 @@ public class MainTest { } Path output = temporaryFolder.newFile("output.jar").toPath(); - try { - Main.compile( - optionsWithBootclasspath() - .setSourceJars(ImmutableList.of(sourcesa.toString(), sourcesb.toString())) - .setOutput(output.toString()) - .build()); - fail(); - } catch (TurbineError e) { - assertThat(e).hasMessageThat().contains("error: duplicate declaration of Test"); - } + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Main.compile( + optionsWithBootclasspath() + .setSourceJars(ImmutableList.of(sourcesa.toString(), sourcesb.toString())) + .setOutput(output.toString()) + .build())); + assertThat(e).hasMessageThat().contains("error: duplicate declaration of Test"); } @Test @@ -204,8 +208,8 @@ public class MainTest { assertThat(entries.map(JarEntry::getName)) .containsAtLeast("META-INF/", "META-INF/MANIFEST.MF"); } - Manifest manifest = jarFile.getManifest(); - Attributes attributes = manifest.getMainAttributes(); + Manifest manifest = requireNonNull(jarFile.getManifest()); + Attributes attributes = requireNonNull(manifest.getMainAttributes()); ImmutableMap<String, ?> entries = attributes.entrySet().stream() .collect(toImmutableMap(e -> e.getKey().toString(), Map.Entry::getValue)); @@ -215,12 +219,15 @@ public class MainTest { "Manifest-Version", "1.0", "Target-Label", "//foo:foo", "Injecting-Rule-Kind", "foo_library"); - assertThat(jarFile.getEntry(JarFile.MANIFEST_NAME).getLastModifiedTime().toInstant()) + assertThat( + requireNonNull(jarFile.getEntry(JarFile.MANIFEST_NAME)) + .getLastModifiedTime() + .toInstant()) .isEqualTo( LocalDateTime.of(2010, 1, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()); } try (JarFile jarFile = new JarFile(gensrcOutput.toFile())) { - Manifest manifest = jarFile.getManifest(); + Manifest manifest = requireNonNull(jarFile.getManifest()); Attributes attributes = manifest.getMainAttributes(); ImmutableMap<String, ?> entries = attributes.entrySet().stream() @@ -257,16 +264,16 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - try { - Main.compile( - TurbineOptions.builder() - .setSources(ImmutableList.of(src.toString())) - .setOutput(output.toString()) - .build()); - fail(); - } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessageThat().contains("java.lang"); - } + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> + Main.compile( + TurbineOptions.builder() + .setSources(ImmutableList.of(src.toString())) + .setOutput(output.toString()) + .build())); + assertThat(expected).hasMessageThat().contains("java.lang"); } @Test @@ -274,14 +281,17 @@ public class MainTest { Path src = temporaryFolder.newFile("Test.java").toPath(); MoreFiles.asCharSink(src, UTF_8).write("public class Test {}"); - try { - Main.compile(optionsWithBootclasspath().setSources(ImmutableList.of(src.toString())).build()); - fail(); - } catch (UsageException expected) { - assertThat(expected) - .hasMessageThat() - .contains("at least one of --output, --gensrc_output, or --resource_output is required"); - } + UsageException expected = + assertThrows( + UsageException.class, + () -> + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .build())); + assertThat(expected) + .hasMessageThat() + .contains("at least one of --output, --gensrc_output, or --resource_output is required"); } @Test @@ -471,4 +481,60 @@ public class MainTest { assertThat(entries.map(JarEntry::getName)).containsExactly("g/Gen.class"); } } + + @Test + public void testGensrcDirectoryOutput() throws IOException { + Path src = temporaryFolder.newFile("Foo.java").toPath(); + MoreFiles.asCharSink(src, UTF_8).write("package f; @Deprecated class Foo {}"); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + Path gensrc = temporaryFolder.newFolder("gensrcOutput").toPath(); + + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .setTargetLabel("//foo:foo") + .setInjectingRuleKind("foo_library") + .setOutput(output.toString()) + .setGensrcOutput(gensrc.toString()) + .setProcessors(ImmutableList.of(SourceGeneratingProcessor.class.getName())) + .build()); + + assertThat(listDirectoryContents(gensrc)).containsExactly(gensrc.resolve("g/Gen.java")); + } + + @Test + public void testResourceDirectoryOutput() throws IOException { + Path src = temporaryFolder.newFile("Foo.java").toPath(); + MoreFiles.asCharSink(src, UTF_8).write("package f; @Deprecated class Foo {}"); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + Path resources = temporaryFolder.newFolder("resources").toPath(); + + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .setTargetLabel("//foo:foo") + .setInjectingRuleKind("foo_library") + .setOutput(output.toString()) + .setResourceOutput(resources.toString()) + .setProcessors(ImmutableList.of(ClassGeneratingProcessor.class.getName())) + .build()); + + assertThat(listDirectoryContents(resources)).containsExactly(resources.resolve("g/Gen.class")); + } + + private static ImmutableList<Path> listDirectoryContents(Path output) throws IOException { + ImmutableList.Builder<Path> paths = ImmutableList.builder(); + Files.walkFileTree( + output, + new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + paths.add(path); + return FileVisitResult.CONTINUE; + } + }); + return paths.build(); + } } diff --git a/javatests/com/google/turbine/main/ReducedClasspathTest.java b/javatests/com/google/turbine/main/ReducedClasspathTest.java index d74c640..2810481 100644 --- a/javatests/com/google/turbine/main/ReducedClasspathTest.java +++ b/javatests/com/google/turbine/main/ReducedClasspathTest.java @@ -20,7 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -106,7 +107,7 @@ public class ReducedClasspathTest { try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) { for (String className : classNames) { jos.putNextEntry(new JarEntry(className + ".class")); - jos.write(compiled.get(className)); + jos.write(requireNonNull(compiled.get(className), className)); } } return lib; @@ -231,19 +232,19 @@ public class ReducedClasspathTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - try { - Main.compile( - optionsWithBootclasspath() - .setOutput(output.toString()) - .setSources(ImmutableList.of(src.toString())) - .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) - .setClassPath(ImmutableList.of(libc.toString())) - .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) - .build()); - fail(); - } catch (TurbineError e) { - assertThat(e).hasMessageThat().contains("could not resolve I"); - } + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) + .setClassPath(ImmutableList.of(libc.toString())) + .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) + .build())); + assertThat(e).hasMessageThat().contains("could not resolve I"); } static String lines(String... lines) { diff --git a/javatests/com/google/turbine/options/TurbineOptionsTest.java b/javatests/com/google/turbine/options/TurbineOptionsTest.java index d4b468b..5d892c5 100644 --- a/javatests/com/google/turbine/options/TurbineOptionsTest.java +++ b/javatests/com/google/turbine/options/TurbineOptionsTest.java @@ -18,6 +18,7 @@ package com.google.turbine.options; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; @@ -283,23 +284,23 @@ public class TurbineOptionsTest { @Test public void unknownOption() throws Exception { - try { - TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList("--nosuch"))); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains("unknown option"); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> + TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList("--nosuch")))); + assertThat(e).hasMessageThat().contains("unknown option"); } @Test public void unterminatedJavacopts() throws Exception { - try { - TurbineOptionsParser.parse( - Iterables.concat(BASE_ARGS, Arrays.asList("--javacopts", "--release", "8"))); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessageThat().contains("javacopts should be terminated by `--`"); - } + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> + TurbineOptionsParser.parse( + Iterables.concat(BASE_ARGS, Arrays.asList("--javacopts", "--release", "8")))); + assertThat(e).hasMessageThat().contains("javacopts should be terminated by `--`"); } @Test @@ -348,11 +349,9 @@ public class TurbineOptionsTest { @Test public void invalidUnescape() throws Exception { String[] lines = {"--sources", "'Foo$Bar.java"}; - try { - TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows( + IllegalArgumentException.class, + () -> TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines)))); } @Test @@ -373,4 +372,37 @@ public class TurbineOptionsTest { assertThat(options.reducedClasspathMode()).isEqualTo(mode); } } + + @Test + public void javaBuilderCompatibility() throws Exception { + TurbineOptions options = + TurbineOptionsParser.parse( + Iterables.concat( + BASE_ARGS, + ImmutableList.of( + "--output_deps_proto", + "output_deps.proto", + "--generated_sources_output", + "generated_sources.jar", + "--experimental_fix_deps_tool", + "ignored", + "--strict_java_deps", + "ignored", + "--native_header_output", + "ignored", + "--compress_jar"))); + assertThat(options.outputDeps()).hasValue("output_deps.proto"); + assertThat(options.gensrcOutput()).hasValue("generated_sources.jar"); + } + + @Test + public void requiredValue() throws Exception { + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> + TurbineOptionsParser.parse( + Iterables.concat(BASE_ARGS, ImmutableList.of("--output", "--system")))); + assertThat(e).hasMessageThat().contains("missing required argument for: --output"); + } } diff --git a/javatests/com/google/turbine/parse/JavacLexer.java b/javatests/com/google/turbine/parse/JavacLexer.java index d8939f1..6e1a984 100644 --- a/javatests/com/google/turbine/parse/JavacLexer.java +++ b/javatests/com/google/turbine/parse/JavacLexer.java @@ -27,7 +27,7 @@ import java.util.ArrayList; import java.util.List; /** A javac-based reference lexer. */ -public class JavacLexer { +public final class JavacLexer { static List<String> javacLex(final String input) { Context context = new Context(); @@ -283,4 +283,6 @@ public class JavacLexer { } return token.kind.toString(); } + + private JavacLexer() {} } diff --git a/javatests/com/google/turbine/parse/LexerTest.java b/javatests/com/google/turbine/parse/LexerTest.java index 8530d52..c3d7804 100644 --- a/javatests/com/google/turbine/parse/LexerTest.java +++ b/javatests/com/google/turbine/parse/LexerTest.java @@ -328,6 +328,11 @@ public class LexerTest { lexerComparisonTest("foo /*/*/ bar"); } + @Test + public void unicode() { + lexerComparisonTest("import pkg\uD800\uDC00.test;"); + } + private void lexerComparisonTest(String s) { assertThat(lex(s)).containsExactlyElementsIn(JavacLexer.javacLex(s)); } diff --git a/javatests/com/google/turbine/parse/ParseErrorTest.java b/javatests/com/google/turbine/parse/ParseErrorTest.java index 6a9ad11..eeb3923 100644 --- a/javatests/com/google/turbine/parse/ParseErrorTest.java +++ b/javatests/com/google/turbine/parse/ParseErrorTest.java @@ -17,7 +17,7 @@ package com.google.turbine.parse; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.turbine.diag.SourceFile; @@ -36,12 +36,8 @@ public class ParseErrorTest { new StreamLexer( new UnicodeEscapePreprocessor(new SourceFile("<>", String.valueOf("2147483648")))); ConstExpressionParser parser = new ConstExpressionParser(lexer, lexer.next()); - try { - parser.expression(); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e).hasMessageThat().contains("invalid literal"); - } + TurbineError e = assertThrows(TurbineError.class, () -> parser.expression()); + assertThat(e).hasMessageThat().contains("invalid literal"); } @Test @@ -50,233 +46,252 @@ public class ParseErrorTest { new StreamLexer( new UnicodeEscapePreprocessor(new SourceFile("<>", String.valueOf("0x100000000")))); ConstExpressionParser parser = new ConstExpressionParser(lexer, lexer.next()); - try { - parser.expression(); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e).hasMessageThat().contains("invalid literal"); - } + TurbineError e = assertThrows(TurbineError.class, () -> parser.expression()); + assertThat(e).hasMessageThat().contains("invalid literal"); } @Test public void unexpectedTopLevel() { String input = "public static void main(String[] args) {}"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: unexpected token: void", - "public static void main(String[] args) {}", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unexpected token: void", + "public static void main(String[] args) {}", + " ^")); } @Test public void unexpectedIdentifier() { String input = "public clas Test {}"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: unexpected identifier 'clas'", // - "public clas Test {}", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unexpected identifier 'clas'", // + "public clas Test {}", + " ^")); } @Test public void missingTrailingCloseBrace() { String input = "public class Test {\n\n"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:2: error: unexpected end of input", // - "", - "^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:2: error: unexpected end of input", // + "", + "^")); } @Test public void annotationArgument() { String input = "@A(x = System.err.println()) class Test {}\n"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: invalid annotation argument", // - "@A(x = System.err.println()) class Test {}", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: invalid annotation argument", // + "@A(x = System.err.println()) class Test {}", + " ^")); } @Test public void dropParens() { String input = "enum E { ONE("; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: unexpected end of input", // - "enum E { ONE(", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unexpected end of input", // + "enum E { ONE(", + " ^")); } @Test public void dropBlocks() { String input = "class T { Object f = new Object() {"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: unexpected end of input", // - "class T { Object f = new Object() {", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unexpected end of input", // + "class T { Object f = new Object() {", + " ^")); } @Test public void unterminatedString() { String input = "class T { String s = \"hello\nworld\"; }"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: unterminated string literal", // - "class T { String s = \"hello", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unterminated string literal", // + "class T { String s = \"hello", + " ^")); } @Test public void emptyChar() { String input = "class T { char c = ''; }"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: empty char literal", // - "class T { char c = ''; }", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: empty char literal", // + "class T { char c = ''; }", + " ^")); } @Test public void unterminatedChar() { String input = "class T { char c = '; }"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: unterminated char literal", // - "class T { char c = '; }", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unterminated char literal", // + "class T { char c = '; }", + " ^")); } @Test public void unterminatedExpr() { String input = "class T { String s = hello + world }"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: unterminated expression, expected ';' not found", // - "class T { String s = hello + world }", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unterminated expression, expected ';' not found", // + "class T { String s = hello + world }", + " ^")); } @Test public void abruptMultivariableDeclaration() { String input = "class T { int x,; }"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: expected token <identifier>", // - "class T { int x,; }", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: expected token <identifier>", // + "class T { int x,; }", + " ^")); } @Test public void invalidAnnotation() { String input = "@Foo(x = @E [] x) class T {}"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: invalid annotation argument", // - "@Foo(x = @E [] x) class T {}", - " ^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: invalid annotation argument", // + "@Foo(x = @E [] x) class T {}", + " ^")); } @Test public void unclosedComment() { String input = "/** *\u001a/ class Test {}"; - try { - Parser.parse(input); - fail("expected parsing to fail"); - } catch (TurbineError e) { - assertThat(e) - .hasMessageThat() - .isEqualTo( - lines( - "<>:1: error: unclosed comment", // - "/** *\u001a/ class Test {}", - "^")); - } + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unclosed comment", // + "/** *\u001a/ class Test {}", + "^")); + } + + @Test + public void unclosedGenerics() { + String input = "enum\te{l;p u@.<@"; + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unexpected end of input", // + "enum\te{l;p u@.<@", + " ^")); + } + + @Test + public void arrayDot() { + String input = "enum\te{p;ullt[].<~>>>L\0"; + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unexpected token: <", // + "enum\te{p;ullt[].<~>>>L\0", + " ^")); + } + + @Test + public void implementsBeforeExtends() { + String input = "class T implements A extends B {}"; + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: 'extends' must come before 'implements'", + "class T implements A extends B {}", + " ^")); + } + + @Test + public void unpairedSurrogate() { + String input = "import pkg\uD800.PackageTest;"; + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unpaired surrogate 0xd800", + "import pkg\uD800.PackageTest;", + " ^")); + } + + @Test + public void abruptSurrogate() { + String input = "import pkg\uD800"; + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines("<>:1: error: unpaired surrogate 0xd800", "import pkg\uD800", " ^")); + } + + @Test + public void unexpectedSurrogate() { + String input = "..\uD800\uDC00"; + TurbineError e = assertThrows(TurbineError.class, () -> Parser.parse(input)); + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unexpected input: U+10000", // + "..\uD800\uDC00", + " ^")); } private static String lines(String... lines) { diff --git a/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java b/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java index e3f7b63..b3e09b8 100644 --- a/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java +++ b/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java @@ -18,7 +18,7 @@ package com.google.turbine.parse; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.turbine.diag.SourceFile; import com.google.turbine.diag.TurbineError; @@ -57,19 +57,11 @@ public class UnicodeEscapePreprocessorTest { @Test public void abruptEnd() { - try { - readAll("\\u00"); - fail(); - } catch (TurbineError e) { - assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF); - } + TurbineError e = assertThrows(TurbineError.class, () -> readAll("\\u00")); + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF); - try { - readAll("\\u"); - fail(); - } catch (TurbineError e) { - assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF); - } + e = assertThrows(TurbineError.class, () -> readAll("\\u")); + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF); } @Test @@ -79,19 +71,16 @@ public class UnicodeEscapePreprocessorTest { @Test public void invalidEscape() { - try { - readAll("\\uUUUU"); - fail(); - } catch (TurbineError e) { - assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.INVALID_UNICODE); - } + TurbineError e = assertThrows(TurbineError.class, () -> readAll("\\uUUUU")); + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.INVALID_UNICODE); } private List<Character> readAll(String input) { UnicodeEscapePreprocessor reader = new UnicodeEscapePreprocessor(new SourceFile(null, input)); List<Character> result = new ArrayList<>(); - for (char ch = reader.next(); ch != UnicodeEscapePreprocessor.ASCII_SUB; ch = reader.next()) { - result.add(ch); + for (int ch = reader.next(); ch != UnicodeEscapePreprocessor.ASCII_SUB; ch = reader.next()) { + assertThat(Character.isBmpCodePoint(ch)).isTrue(); + result.add((char) ch); } return result; } diff --git a/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java index e6a59bf..d3b3836 100644 --- a/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java +++ b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java @@ -21,6 +21,7 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import com.google.common.base.Joiner; @@ -231,6 +232,14 @@ class AbstractTurbineTypesTest { "Float", "Double", }, + // type annotations + { + "@A List<@B Integer>", + "@A List", + "@A int @B []", + "@A List<@A int @B []>", + "Map.@A Entry<@B Integer, @C Number>", + }, }; List<String> files = new ArrayList<>(); AtomicInteger idx = new AtomicInteger(); @@ -242,6 +251,7 @@ class AbstractTurbineTypesTest { "package p;", "import java.util.*;", "import java.io.*;", + "import java.lang.annotation.*;", String.format("abstract class Test%s {", idx.getAndIncrement()), Streams.mapWithIndex( Arrays.stream(group), (x, i) -> String.format(" %s f%d;\n", x, i)) @@ -250,6 +260,9 @@ class AbstractTurbineTypesTest { " abstract <V extends List<V>> V g();", " abstract <W extends ArrayList> W h();", " abstract <X extends Serializable> X i();", + " @Target(ElementType.TYPE_USE) @interface A {}", + " @Target(ElementType.TYPE_USE) @interface B {}", + " @Target(ElementType.TYPE_USE) @interface C {}", "}"); String content = sb.toString(); files.add(content); @@ -397,8 +410,11 @@ class AbstractTurbineTypesTest { ListMultimap<String, TypeMirror> turbineInputs = MultimapBuilder.linkedHashKeys().arrayListValues().build(); - turbineElements - .get(name) + /* + * requireNonNull is safe because `name` is from `javacElements`, which we checked has the + * same keys as `turbineElements`. + */ + requireNonNull(turbineElements.get(name)) .getEnclosedElements() .forEach(e -> getTypes(turbineTypes, e, turbineInputs)); diff --git a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java index ed5af6a..96664d2 100644 --- a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java +++ b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java @@ -19,9 +19,11 @@ package com.google.turbine.processing; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.base.Splitter; @@ -33,6 +35,7 @@ import com.google.turbine.binder.ClassPathBinder; import com.google.turbine.binder.Processing; import com.google.turbine.binder.Processing.ProcessorInfo; import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineDiagnostic; import com.google.turbine.diag.TurbineError; import com.google.turbine.lower.IntegrationTestSupport; import com.google.turbine.parse.Parser; @@ -45,9 +48,11 @@ import java.io.Writer; import java.util.Optional; import java.util.Set; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; @@ -73,39 +78,33 @@ public class ProcessingIntegrationTest { } } - private static final IntegrationTestSupport.TestInput SOURCES = - IntegrationTestSupport.TestInput.parse( - Joiner.on('\n') - .join( - "=== Test.java ===", // - "@Deprecated", - "class Test extends NoSuch {", - "}")); - @Test public void crash() throws IOException { ImmutableList<Tree.CompUnit> units = - SOURCES.sources.entrySet().stream() - .map(e -> new SourceFile(e.getKey(), e.getValue())) - .map(Parser::parse) - .collect(toImmutableList()); - try { - Binder.bind( - units, - ClassPathBinder.bindClasspath(ImmutableList.of()), - Processing.ProcessorInfo.create( - ImmutableList.of(new CrashingProcessor()), - getClass().getClassLoader(), - ImmutableMap.of(), - SourceVersion.latestSupported()), - TestClassPaths.TURBINE_BOOTCLASSPATH, - Optional.empty()); - fail(); - } catch (TurbineError e) { - assertThat(e.diagnostics()).hasSize(2); - assertThat(e.diagnostics().get(0).message()).contains("could not resolve NoSuch"); - assertThat(e.diagnostics().get(1).message()).contains("crash!"); - } + parseUnit( + "=== Test.java ===", // + "@Deprecated", + "class Test extends NoSuch {", + "}"); + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new CrashingProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty())); + ImmutableList<String> messages = + e.diagnostics().stream().map(TurbineDiagnostic::message).collect(toImmutableList()); + assertThat(messages).hasSize(2); + assertThat(messages.get(0)).contains("could not resolve NoSuch"); + assertThat(messages.get(1)).contains("crash!"); } @SupportedAnnotationTypes("*") @@ -142,38 +141,30 @@ public class ProcessingIntegrationTest { @Test public void warnings() throws IOException { ImmutableList<Tree.CompUnit> units = - IntegrationTestSupport.TestInput.parse( - Joiner.on('\n') - .join( - "=== Test.java ===", // - "@Deprecated", - "class Test {", - "}")) - .sources - .entrySet() - .stream() - .map(e -> new SourceFile(e.getKey(), e.getValue())) - .map(Parser::parse) - .collect(toImmutableList()); - try { - Binder.bind( - units, - ClassPathBinder.bindClasspath(ImmutableList.of()), - Processing.ProcessorInfo.create( - ImmutableList.of(new WarningProcessor()), - getClass().getClassLoader(), - ImmutableMap.of(), - SourceVersion.latestSupported()), - TestClassPaths.TURBINE_BOOTCLASSPATH, - Optional.empty()); - fail(); - } catch (TurbineError e) { - ImmutableList<String> diags = - e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList()); - assertThat(diags).hasSize(2); - assertThat(diags.get(0)).contains("proc warning"); - assertThat(diags.get(1)).contains("proc error"); - } + parseUnit( + "=== Test.java ===", // + "@Deprecated", + "class Test {", + "}"); + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new WarningProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty())); + ImmutableList<String> diags = + e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList()); + assertThat(diags).hasSize(2); + assertThat(diags.get(0)).contains("proc warning"); + assertThat(diags.get(1)).contains("proc error"); } @SupportedAnnotationTypes("*") @@ -219,19 +210,11 @@ public class ProcessingIntegrationTest { @Test public void resources() throws IOException { ImmutableList<Tree.CompUnit> units = - IntegrationTestSupport.TestInput.parse( - Joiner.on('\n') - .join( - "=== Test.java ===", // - "@Deprecated", - "class Test {", - "}")) - .sources - .entrySet() - .stream() - .map(e -> new SourceFile(e.getKey(), e.getValue())) - .map(Parser::parse) - .collect(toImmutableList()); + parseUnit( + "=== Test.java ===", // + "@Deprecated", + "class Test {", + "}"); BindingResult bound = Binder.bind( units, @@ -247,34 +230,27 @@ public class ProcessingIntegrationTest { assertThat(bound.generatedSources().keySet()).containsExactly("Gen.java", "source.txt"); assertThat(bound.generatedClasses().keySet()).containsExactly("class.txt"); - assertThat(bound.generatedSources().get("source.txt").source()) + // The requireNonNull calls are safe because of the keySet checks above. + assertThat(requireNonNull(bound.generatedSources().get("source.txt")).source()) .isEqualTo("hello source output"); - assertThat(new String(bound.generatedClasses().get("class.txt"), UTF_8)) + assertThat(new String(requireNonNull(bound.generatedClasses().get("class.txt")), UTF_8)) .isEqualTo("hello class output"); } @Test public void getAllAnnotations() throws IOException { ImmutableList<Tree.CompUnit> units = - IntegrationTestSupport.TestInput.parse( - Joiner.on('\n') - .join( - "=== A.java ===", // - "import java.lang.annotation.Inherited;", - "@Inherited", - "@interface A {}", - "=== B.java ===", // - "@interface B {}", - "=== One.java ===", // - "@A @B class One {}", - "=== Two.java ===", // - "class Two extends One {}")) - .sources - .entrySet() - .stream() - .map(e -> new SourceFile(e.getKey(), e.getValue())) - .map(Parser::parse) - .collect(toImmutableList()); + parseUnit( + "=== A.java ===", // + "import java.lang.annotation.Inherited;", + "@Inherited", + "@interface A {}", + "=== B.java ===", // + "@interface B {}", + "=== One.java ===", // + "@A @B class One {}", + "=== Two.java ===", // + "class Two extends One {}"); BindingResult bound = Binder.bind( units, @@ -343,4 +319,308 @@ public class ProcessingIntegrationTest { .collect(joining(", "))); } } + + private static void logError( + ProcessingEnvironment processingEnv, + RoundEnvironment roundEnv, + Class<?> processorClass, + int round) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + String.format( + "%d: %s {errorRaised=%s, processingOver=%s}", + round, + processorClass.getSimpleName(), + roundEnv.errorRaised(), + roundEnv.processingOver())); + } + + @SupportedAnnotationTypes("*") + public static class ErrorProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + int round = 0; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + int round = ++this.round; + logError(processingEnv, roundEnv, getClass(), round); + String name = "Gen" + round; + try (Writer writer = processingEnv.getFiler().createSourceFile(name).openWriter()) { + writer.write(String.format("class %s {}", name)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return false; + } + } + + @SupportedAnnotationTypes("*") + public static class FinalRoundErrorProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + int round = 0; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + int round = ++this.round; + if (roundEnv.processingOver()) { + logError(processingEnv, roundEnv, getClass(), round); + } + return false; + } + } + + @Test + public void errorsAndFinalRound() throws IOException { + ImmutableList<Tree.CompUnit> units = + parseUnit( + "=== Test.java ===", // + "@Deprecated", + "class Test {", + "}"); + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new ErrorProcessor(), new FinalRoundErrorProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty())); + ImmutableList<String> diags = + e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList()); + assertThat(diags) + .containsExactly( + "1: ErrorProcessor {errorRaised=false, processingOver=false}", + "2: ErrorProcessor {errorRaised=true, processingOver=true}", + "2: FinalRoundErrorProcessor {errorRaised=true, processingOver=true}") + .inOrder(); + } + + @SupportedAnnotationTypes("*") + public static class SuperTypeProcessor extends AbstractProcessor { + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + TypeElement typeElement = processingEnv.getElementUtils().getTypeElement("T"); + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + typeElement.getSuperclass() + + " " + + processingEnv.getTypeUtils().directSupertypes(typeElement.asType())); + return false; + } + } + + @Test + public void superType() throws IOException { + ImmutableList<Tree.CompUnit> units = + parseUnit( + "=== T.java ===", // + "@Deprecated", + "class T extends S {", + "}"); + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new SuperTypeProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty())); + ImmutableList<String> diags = + e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList()); + assertThat(diags).containsExactly("could not resolve S", "S [S]").inOrder(); + } + + @SupportedAnnotationTypes("*") + public static class GenerateAnnotationProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try { + JavaFileObject file = processingEnv.getFiler().createSourceFile("A"); + try (Writer writer = file.openWriter()) { + writer.write("@interface A {}"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + } + + @Test + public void generatedAnnotationDefinition() throws IOException { + ImmutableList<Tree.CompUnit> units = + parseUnit( + "=== T.java ===", // + "@interface B {", + " A value() default @A;", + "}", + "@B(value = @A)", + "class T {", + "}"); + BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new GenerateAnnotationProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + assertThat(bound.generatedSources()).containsKey("A.java"); + } + + @SupportedAnnotationTypes("*") + public static class GenerateQualifiedProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + String superType = + processingEnv.getElementUtils().getTypeElement("T").getSuperclass().toString(); + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, superType); + return false; + } + } + + @Test + public void qualifiedErrorType() throws IOException { + ImmutableList<Tree.CompUnit> units = + parseUnit( + "=== T.java ===", // + "class T extends G.I {", + "}"); + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new GenerateQualifiedProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty())); + assertThat( + e.diagnostics().stream() + .filter(d -> d.severity().equals(Diagnostic.Kind.NOTE)) + .map(d -> d.message())) + .containsExactly("G.I"); + } + + @SupportedAnnotationTypes("*") + public static class ElementValueInspector extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + TypeElement element = processingEnv.getElementUtils().getTypeElement("T"); + for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.NOTE, + String.format("@Deprecated(%s)", annotationMirror.getElementValues()), + element, + annotationMirror); + } + return false; + } + } + + @Test + public void badElementValue() throws IOException { + ImmutableList<Tree.CompUnit> units = + parseUnit( + "=== T.java ===", // + "@Deprecated(noSuch = 42) class T {}"); + TurbineError e = + assertThrows( + TurbineError.class, + () -> + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new ElementValueInspector()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty())); + assertThat( + e.diagnostics().stream() + .filter(d -> d.severity().equals(Diagnostic.Kind.ERROR)) + .map(d -> d.message())) + .containsExactly("could not resolve element noSuch() in java.lang.Deprecated"); + assertThat( + e.diagnostics().stream() + .filter(d -> d.severity().equals(Diagnostic.Kind.NOTE)) + .map(d -> d.message())) + .containsExactly("@Deprecated({})"); + } + + private static ImmutableList<Tree.CompUnit> parseUnit(String... lines) { + return IntegrationTestSupport.TestInput.parse(Joiner.on('\n').join(lines)) + .sources + .entrySet() + .stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + } } diff --git a/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java b/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java index d339700..a8c00aa 100644 --- a/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java +++ b/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java @@ -18,11 +18,11 @@ package com.google.turbine.processing; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static com.google.turbine.testing.TestResources.getResourceBytes; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; import com.google.common.testing.EqualsTester; import com.google.turbine.binder.Binder; @@ -39,7 +39,6 @@ import com.google.turbine.processing.TurbineElement.TurbineTypeElement; import com.google.turbine.testing.TestClassPaths; import com.google.turbine.tree.Tree.CompUnit; import java.io.IOException; -import java.io.InputStream; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; @@ -161,17 +160,13 @@ public class TurbineAnnotationProxyTest { assertThat(a.b().value()).isEqualTo(-1); assertThat(a.e()).isEqualTo(ElementType.PACKAGE); - try { - a.c(); - fail(); - } catch (MirroredTypeException e) { + { + MirroredTypeException e = assertThrows(MirroredTypeException.class, () -> a.c()); assertThat(e.getTypeMirror().getKind()).isEqualTo(TypeKind.DECLARED); assertThat(getQualifiedName(e.getTypeMirror())).contains("java.lang.String"); } - try { - a.cx(); - fail(); - } catch (MirroredTypesException e) { + { + MirroredTypesException e = assertThrows(MirroredTypesException.class, () -> a.cx()); assertThat( e.getTypeMirrors().stream().map(m -> getQualifiedName(m)).collect(toImmutableList())) .containsExactly("java.lang.Integer", "java.lang.Long"); @@ -208,9 +203,7 @@ public class TurbineAnnotationProxyTest { private static void addClass(JarOutputStream jos, Class<?> clazz) throws IOException { String entryPath = clazz.getName().replace('.', '/') + ".class"; jos.putNextEntry(new JarEntry(entryPath)); - try (InputStream is = clazz.getClassLoader().getResourceAsStream(entryPath)) { - ByteStreams.copy(is, jos); - } + jos.write(getResourceBytes(clazz, "/" + entryPath)); } private static String getQualifiedName(TypeMirror typeMirror) { diff --git a/javatests/com/google/turbine/processing/TurbineElementsTest.java b/javatests/com/google/turbine/processing/TurbineElementsTest.java index 770e6f6..281bde4 100644 --- a/javatests/com/google/turbine/processing/TurbineElementsTest.java +++ b/javatests/com/google/turbine/processing/TurbineElementsTest.java @@ -20,6 +20,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.truth.TruthJUnit.assume; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -225,6 +227,12 @@ public class TurbineElementsTest { .isFalse(); assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("One"))).isFalse(); assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("Test"))).isTrue(); + for (Element e : turbineElements.getTypeElement("java.lang.Object").getEnclosedElements()) { + assume().that(e.getSimpleName().contentEquals("finalize")).isFalse(); + assertWithMessage(e.getSimpleName().toString()) + .that(turbineElements.isDeprecated(e)) + .isFalse(); + } } @Test diff --git a/javatests/com/google/turbine/processing/TurbineFilerTest.java b/javatests/com/google/turbine/processing/TurbineFilerTest.java index 40b78ea..d433428 100644 --- a/javatests/com/google/turbine/processing/TurbineFilerTest.java +++ b/javatests/com/google/turbine/processing/TurbineFilerTest.java @@ -19,7 +19,7 @@ package com.google.turbine.processing; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.base.Function; import com.google.common.base.Supplier; @@ -98,19 +98,13 @@ public class TurbineFilerTest { seen.add("com/foo/Bar.java"); seen.add("com/foo/Baz.class"); - try { - filer.createSourceFile("com.foo.Bar", (Element[]) null); - fail(); - } catch (FilerException expected) { - } + assertThrows( + FilerException.class, () -> filer.createSourceFile("com.foo.Bar", (Element[]) null)); filer.createSourceFile("com.foo.Baz", (Element[]) null); filer.createClassFile("com.foo.Bar", (Element[]) null); - try { - filer.createClassFile("com.foo.Baz", (Element[]) null); - fail(); - } catch (FilerException expected) { - } + assertThrows( + FilerException.class, () -> filer.createClassFile("com.foo.Baz", (Element[]) null)); } @Test @@ -121,11 +115,7 @@ public class TurbineFilerTest { StandardLocation.SOURCE_OUTPUT, StandardLocation.ANNOTATION_PROCESSOR_PATH, StandardLocation.CLASS_PATH)) { - try { - filer.getResource(location, "", "NoSuch"); - fail(); - } catch (FileNotFoundException expected) { - } + assertThrows(FileNotFoundException.class, () -> filer.getResource(location, "", "NoSuch")); } } diff --git a/javatests/com/google/turbine/processing/TurbineMessagerTest.java b/javatests/com/google/turbine/processing/TurbineMessagerTest.java index c1e6401..017012c 100644 --- a/javatests/com/google/turbine/processing/TurbineMessagerTest.java +++ b/javatests/com/google/turbine/processing/TurbineMessagerTest.java @@ -19,6 +19,7 @@ package com.google.turbine.processing; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNull; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -242,7 +243,7 @@ public class TurbineMessagerTest { private static String shortPath(Diagnostic<? extends JavaFileObject> d) { return d.getSource() != null - ? Paths.get(d.getSource().getName()).getFileName().toString() + ? requireNonNull(Paths.get(d.getSource().getName()).getFileName()).toString() : "<>"; } } diff --git a/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java b/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java index 0f9e6a6..b028a81 100644 --- a/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java +++ b/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java @@ -17,7 +17,7 @@ package com.google.turbine.processing; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -83,11 +83,7 @@ public class TurbineTypesFactoryTest { PrimitiveType type = turbineTypes.getPrimitiveType(kind); assertThat(type.getKind()).isEqualTo(kind); } else { - try { - turbineTypes.getPrimitiveType(kind); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> turbineTypes.getPrimitiveType(kind)); } } } @@ -167,11 +163,7 @@ public class TurbineTypesFactoryTest { public void noType() { assertThat(turbineTypes.getNoType(TypeKind.VOID).getKind()).isEqualTo(TypeKind.VOID); assertThat(turbineTypes.getNoType(TypeKind.NONE).getKind()).isEqualTo(TypeKind.NONE); - try { - turbineTypes.getNoType(TypeKind.DECLARED); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> turbineTypes.getNoType(TypeKind.DECLARED)); } @Test diff --git a/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java b/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java index eb5ee6c..00eb571 100644 --- a/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java +++ b/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java @@ -18,10 +18,11 @@ package com.google.turbine.processing; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.TruthJUnit.assume; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; +import com.google.turbine.types.Deannotate; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -68,12 +69,10 @@ public class TurbineTypesUnaryTest extends AbstractTurbineTypesTest { thrown = e; } if (thrown != null) { - try { - turbineTypes.unboxedType(turbineA).toString(); - fail(String.format("expected unboxedType(`%s`) to throw", turbineA)); - } catch (IllegalArgumentException expected) { - // expected - } + assertThrows( + String.format("expected unboxedType(`%s`) to throw", turbineA), + IllegalArgumentException.class, + () -> turbineTypes.unboxedType(turbineA).toString()); } else { String actual = turbineTypes.unboxedType(turbineA).toString(); assertWithMessage("unboxedClass(`%s`) = unboxedClass(`%s`)", javacA, turbineA) @@ -121,16 +120,8 @@ public class TurbineTypesUnaryTest extends AbstractTurbineTypesTest { public void directSupertypesThrows() { assume().that(UNSUPPORTED_BY_DIRECT_SUPERTYPES).contains(javacA.getKind()); - try { - javacTypes.directSupertypes(turbineA); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - turbineTypes.directSupertypes(turbineA); - fail(); - } catch (IllegalArgumentException expected) { - } + assertThrows(IllegalArgumentException.class, () -> javacTypes.directSupertypes(turbineA)); + assertThrows(IllegalArgumentException.class, () -> turbineTypes.directSupertypes(turbineA)); } @Test @@ -144,4 +135,16 @@ public class TurbineTypesUnaryTest extends AbstractTurbineTypesTest { .that(actual) .isEqualTo(expected); } + + @Test + public void deannotate() { + String toString = turbineA.toString(); + String deannotated = + Deannotate.deannotate(((TurbineTypeMirror) turbineA).asTurbineType()).toString(); + if (toString.contains("@")) { + assertWithMessage("deannotate(`%s`) = `%s`", toString, deannotated) + .that(deannotated) + .doesNotContain("@"); + } + } } diff --git a/javatests/com/google/turbine/testing/AsmUtils.java b/javatests/com/google/turbine/testing/AsmUtils.java index 5b5e102..b7e77bc 100644 --- a/javatests/com/google/turbine/testing/AsmUtils.java +++ b/javatests/com/google/turbine/testing/AsmUtils.java @@ -27,14 +27,18 @@ import org.objectweb.asm.util.TraceClassVisitor; * ASM-based test utilities, in their own class mostly to avoid namespace issues with e.g. {@link * com.google.turbine.bytecode.ClassReader}. */ -public class AsmUtils { - public static String textify(byte[] bytes) { +public final class AsmUtils { + public static String textify(byte[] bytes, boolean skipDebug) { Printer textifier = new Textifier(); StringWriter sw = new StringWriter(); new ClassReader(bytes) .accept( new TraceClassVisitor(null, textifier, new PrintWriter(sw, true)), - ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE); + ClassReader.SKIP_FRAMES + | ClassReader.SKIP_CODE + | (skipDebug ? ClassReader.SKIP_DEBUG : 0)); return sw.toString(); } + + private AsmUtils() {} } diff --git a/javatests/com/google/turbine/testing/TestClassPaths.java b/javatests/com/google/turbine/testing/TestClassPaths.java index 93be916..55e8b9e 100644 --- a/javatests/com/google/turbine/testing/TestClassPaths.java +++ b/javatests/com/google/turbine/testing/TestClassPaths.java @@ -20,10 +20,9 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Streams; import com.google.turbine.binder.ClassPath; import com.google.turbine.binder.ClassPathBinder; -import com.google.turbine.binder.CtSymClassBinder; +import com.google.turbine.binder.JimageClassBinder; import com.google.turbine.options.TurbineOptions; import java.io.File; import java.io.IOException; @@ -33,15 +32,14 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; -public class TestClassPaths { +public final class TestClassPaths { private static final Splitter CLASS_PATH_SPLITTER = Splitter.on(File.pathSeparatorChar).omitEmptyStrings(); - private static final ImmutableList<Path> BOOTCLASSPATH = - Streams.stream( - CLASS_PATH_SPLITTER.split( - Optional.ofNullable(System.getProperty("sun.boot.class.path")).orElse(""))) + public static final ImmutableList<Path> BOOTCLASSPATH = + CLASS_PATH_SPLITTER + .splitToStream(Optional.ofNullable(System.getProperty("sun.boot.class.path")).orElse("")) .map(Paths::get) .filter(Files::exists) .collect(toImmutableList()); @@ -53,7 +51,7 @@ public class TestClassPaths { if (!BOOTCLASSPATH.isEmpty()) { return ClassPathBinder.bindClasspath(BOOTCLASSPATH); } - return CtSymClassBinder.bind("8"); + return JimageClassBinder.bindDefault(); } catch (IOException e) { e.printStackTrace(); throw new UncheckedIOException(e); @@ -74,4 +72,6 @@ public class TestClassPaths { } return options; } + + private TestClassPaths() {} } diff --git a/javatests/com/google/turbine/testing/TestResources.java b/javatests/com/google/turbine/testing/TestResources.java new file mode 100644 index 0000000..86c7632 --- /dev/null +++ b/javatests/com/google/turbine/testing/TestResources.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.turbine.testing; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; + +import com.google.common.io.ByteStreams; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; + +public final class TestResources { + + public static String getResource(Class<?> clazz, String resource) { + return new String(getResourceBytes(clazz, resource), UTF_8); + } + + public static byte[] getResourceBytes(Class<?> clazz, String resource) { + try (InputStream is = requireNonNull(clazz.getResourceAsStream(resource), resource)) { + return ByteStreams.toByteArray(is); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private TestResources() {} +} diff --git a/javatests/com/google/turbine/zip/ZipTest.java b/javatests/com/google/turbine/zip/ZipTest.java index bfc9cdf..0d49e1a 100644 --- a/javatests/com/google/turbine/zip/ZipTest.java +++ b/javatests/com/google/turbine/zip/ZipTest.java @@ -18,7 +18,7 @@ package com.google.turbine.zip; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableMap; import com.google.common.hash.Hashing; @@ -161,11 +161,7 @@ public class ZipTest { } Files.write(path, "trailing garbage".getBytes(UTF_8), StandardOpenOption.APPEND); - try { - actual(path); - fail(); - } catch (ZipException e) { - assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17"); - } + ZipException e = assertThrows(ZipException.class, () -> actual(path)); + assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17"); } } @@ -1,4 +1,20 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright 2020 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" @@ -7,7 +23,7 @@ <groupId>com.google.turbine</groupId> <artifactId>turbine</artifactId> - <version>0.1-SNAPSHOT</version> + <version>HEAD-SNAPSHOT</version> <name>turbine</name> <description> @@ -15,9 +31,13 @@ </description> <properties> - <asm.version>7.0</asm.version> + <asm.version>9.1</asm.version> <javac.version>9+181-r4173-1</javac.version> - <guava.version>27.0.1-jre</guava.version> + <guava.version>30.0-jre</guava.version> + <errorprone.version>2.7.1</errorprone.version> + <maven-javadoc-plugin.version>3.1.0</maven-javadoc-plugin.version> + <maven-source-plugin.version>3.2.1</maven-source-plugin.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> @@ -27,19 +47,20 @@ <version>${guava.version}</version> </dependency> <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>jsr305</artifactId> - <version>2.0.1</version> - </dependency> - <dependency> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_annotations</artifactId> - <version>2.0.12</version> + <version>${errorprone.version}</version> + </dependency> + <dependency> + <groupId>org.checkerframework</groupId> + <artifactId>checker-qual</artifactId> + <version>3.9.1</version> + <optional>true</optional> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> - <version>3.1.0</version> + <version>3.10.0</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> @@ -68,31 +89,31 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.12</version> + <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.truth</groupId> <artifactId>truth</artifactId> - <version>1.0</version> + <version>1.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.truth.extensions</groupId> <artifactId>truth-proto-extension</artifactId> - <version>1.0</version> + <version>1.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.truth.extensions</groupId> <artifactId>truth-java8-extension</artifactId> - <version>1.0</version> + <version>1.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.jimfs</groupId> <artifactId>jimfs</artifactId> - <version>1.0</version> + <version>1.2</version> <scope>test</scope> </dependency> <dependency> @@ -103,8 +124,8 @@ </dependency> <dependency> <groupId>com.google.auto.value</groupId> - <artifactId>auto-value</artifactId> - <version>1.5.3</version> + <artifactId>auto-value-annotations</artifactId> + <version>1.7.4</version> <scope>provided</scope> </dependency> </dependencies> @@ -132,14 +153,38 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.6.2</version> + <version>3.8.0</version> <configuration> - <fork>true</fork> - <source>1.8</source> - <target>1.8</target> + <source>8</source> + <target>8</target> <encoding>UTF-8</encoding> - <compilerArgument>-parameters</compilerArgument> - <testCompilerArgument>-parameters</testCompilerArgument> + <fork>true</fork> + <compilerArgs> + <arg>-parameters</arg> + <arg>-XDcompilePolicy=simple</arg> + <arg>-Xplugin:ErrorProne</arg> + <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg> + <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg> + <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg> + <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg> + <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg> + <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg> + <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg> + <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg> + <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg> + </compilerArgs> + <annotationProcessorPaths> + <path> + <groupId>com.google.errorprone</groupId> + <artifactId>error_prone_core</artifactId> + <version>${errorprone.version}</version> + </path> + <path> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <version>1.7.4</version> + </path> + </annotationProcessorPaths> </configuration> </plugin> <plugin> @@ -167,7 +212,19 @@ <version>2.19.1</version> <configuration> <!-- set heap size to work around http://github.com/travis-ci/travis-ci/issues/3396 --> - <argLine>-Xmx2g</argLine> + <argLine> + -Xmx2g + --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + </argLine> </configuration> </plugin> <plugin> @@ -200,9 +257,33 @@ </execution> </executions> </plugin> - + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>3.1.1</version> + <configuration> + <source>8</source> + <detectJavaApiLink>false</detectJavaApiLink> + <notimestamp>true</notimestamp> + <doctitle>turbine ${project.version} API</doctitle> + </configuration> + </plugin> </plugins> </build> + + <distributionManagement> + <snapshotRepository> + <id>sonatype-nexus-snapshots</id> + <name>Sonatype Nexus Snapshots</name> + <url>https://oss.sonatype.org/content/repositories/snapshots/</url> + </snapshotRepository> + <repository> + <id>sonatype-nexus-staging</id> + <name>Nexus Release Repository</name> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> + <profiles> <profile> <id>java-8</id> @@ -219,6 +300,66 @@ <argLine>-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${javac.version}/javac-${javac.version}.jar</argLine> </configuration> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <fork>true</fork> + <compilerArgs> + <arg>-parameters</arg> + <arg>-XDcompilePolicy=simple</arg> + <arg>-Xplugin:ErrorProne</arg> + <arg>-J-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${javac.version}/javac-${javac.version}.jar</arg> + </compilerArgs> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>sonatype-oss-release</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>${maven-source-plugin.version}</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar-no-fork</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>${maven-javadoc-plugin.version}</version> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <version>1.6</version> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> </build> </profile> |