Merge remote-tracking branch 'aosp/upstream-master'
* aosp/upstream-master: (38 commits)
Support empty bootclasspaths
Tolerate empty params files in turbine
Don't crash on private interface methods
Change the strict deps plugin to read jar owner from manifest.
Remove support for --rule_kind
Remove deprecated rule_kind argument from Turbine
Use a different date time when normalizing zip entries
Accept --target_label, --injecting_rule_kind in Turbine.
Stop skipping module-infos
Don't error out if modules can't be resolved
Refactor TurbineOptions to make jarToTarget/directJars the source of truth
Change Turbine command lines to not require CustomMultiArgv.
Initial end-to-end support for module-infos
Class writing support for module attributes
Reduce coupling between ConstBinder and SourceTypeBoundClass
Class reading support for module attributes
Initial support for parsing module-infos.
Allow --bootclasspath and --release to be used together
Propagate --release flags from --javacopts to turbine's --release flag
Add JDK 9 travis build
...
Bug: 74332665
Bug: 74339924
Test: m checkbuild
Change-Id: I26280b1fd483e92b5075f985d3169edb63303c5b
diff --git a/.travis.yml b/.travis.yml
index 37f3cc8..273556e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,15 @@
language: java
-jdk:
- - oraclejdk8
+matrix:
+ allow_failures:
+ - jdk: oraclejdk9
+ include:
+# JDK 8
+ - jdk: oraclejdk8
+ env: JDK_RELEASE='8'
+# JDK 9
+ - jdk: oraclejdk9
+ env: JDK_RELEASE='9'
# use travis-ci docker based infrastructure
sudo: false
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..bcbf5dc
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,26 @@
+os: Visual Studio 2015
+
+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/turbine/binder/Binder.java b/java/com/google/turbine/binder/Binder.java
index 205326a..cbafdef 100644
--- a/java/com/google/turbine/binder/Binder.java
+++ b/java/com/google/turbine/binder/Binder.java
@@ -16,7 +16,6 @@
package com.google.turbine.binder;
-import static com.google.common.base.Verify.verifyNotNull;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
@@ -27,7 +26,9 @@
import com.google.turbine.binder.Resolve.CanonicalResolver;
import com.google.turbine.binder.bound.BoundClass;
import com.google.turbine.binder.bound.HeaderBoundClass;
+import com.google.turbine.binder.bound.ModuleInfo;
import com.google.turbine.binder.bound.PackageSourceBoundClass;
+import com.google.turbine.binder.bound.PackageSourceBoundModule;
import com.google.turbine.binder.bound.SourceBoundClass;
import com.google.turbine.binder.bound.SourceHeaderBoundClass;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
@@ -40,22 +41,25 @@
import com.google.turbine.binder.env.SimpleEnv;
import com.google.turbine.binder.lookup.CanonicalSymbolResolver;
import com.google.turbine.binder.lookup.CompoundScope;
+import com.google.turbine.binder.lookup.CompoundTopLevelIndex;
import com.google.turbine.binder.lookup.ImportIndex;
import com.google.turbine.binder.lookup.ImportScope;
import com.google.turbine.binder.lookup.MemberImportIndex;
import com.google.turbine.binder.lookup.Scope;
+import com.google.turbine.binder.lookup.SimpleTopLevelIndex;
import com.google.turbine.binder.lookup.TopLevelIndex;
import com.google.turbine.binder.lookup.WildImportIndex;
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.TurbineError;
+import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.tree.Tree;
import com.google.turbine.tree.Tree.CompUnit;
+import com.google.turbine.tree.Tree.ModDecl;
import com.google.turbine.type.Type;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Collection;
import java.util.List;
/** The entry point for analysis. */
@@ -63,31 +67,35 @@
/** Binds symbols and types to the given compilation units. */
public static BindingResult bind(
- List<CompUnit> units, Collection<Path> classpath, Collection<Path> bootclasspath)
- throws IOException {
+ List<CompUnit> units,
+ ClassPath classpath,
+ ClassPath bootclasspath,
+ Optional<String> moduleVersion) {
ImmutableList<PreprocessedCompUnit> preProcessedUnits = CompUnitPreprocessor.preprocess(units);
- TopLevelIndex.Builder tliBuilder = TopLevelIndex.builder();
-
- SimpleEnv<ClassSymbol, SourceBoundClass> ienv =
- bindSourceBoundClasses(preProcessedUnits, tliBuilder);
+ SimpleEnv<ClassSymbol, SourceBoundClass> ienv = bindSourceBoundClasses(preProcessedUnits);
ImmutableSet<ClassSymbol> syms = ienv.asMap().keySet();
+ CompoundTopLevelIndex tli =
+ CompoundTopLevelIndex.of(
+ SimpleTopLevelIndex.of(ienv.asMap().keySet()),
+ bootclasspath.index(),
+ classpath.index());
+
CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv =
- ClassPathBinder.bind(classpath, bootclasspath, tliBuilder);
+ CompoundEnv.of(classpath.env()).append(bootclasspath.env());
- // Insertion order into the top-level index is important:
- // * the first insert into the TLI wins
- // * we search sources, bootclasspath, and classpath in that order
- // * the first entry within a location wins.
+ CompoundEnv<ModuleSymbol, ModuleInfo> classPathModuleEnv =
+ CompoundEnv.of(classpath.moduleEnv()).append(bootclasspath.moduleEnv());
- TopLevelIndex tli = tliBuilder.build();
-
- SimpleEnv<ClassSymbol, PackageSourceBoundClass> psenv =
+ BindPackagesResult bindPackagesResult =
bindPackages(ienv, tli, preProcessedUnits, classPathEnv);
+ SimpleEnv<ClassSymbol, PackageSourceBoundClass> psenv = bindPackagesResult.classes;
+ SimpleEnv<ModuleSymbol, PackageSourceBoundModule> modules = bindPackagesResult.modules;
+
Env<ClassSymbol, SourceHeaderBoundClass> henv = bindHierarchy(syms, psenv, classPathEnv);
Env<ClassSymbol, SourceTypeBoundClass> tenv =
@@ -104,36 +112,64 @@
canonicalizeTypes(
syms, tenv, CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv));
+ ImmutableList<ModuleInfo> boundModules =
+ bindModules(
+ modules,
+ CompoundEnv.<ClassSymbol, TypeBoundClass>of(classPathEnv).append(tenv),
+ classPathModuleEnv,
+ moduleVersion);
+
ImmutableMap.Builder<ClassSymbol, SourceTypeBoundClass> result = ImmutableMap.builder();
for (ClassSymbol sym : syms) {
result.put(sym, tenv.get(sym));
}
- return new BindingResult(result.build(), classPathEnv);
+ return new BindingResult(result.build(), boundModules, classPathEnv);
}
/** Records enclosing declarations of member classes, and group classes by compilation unit. */
static SimpleEnv<ClassSymbol, SourceBoundClass> bindSourceBoundClasses(
- ImmutableList<PreprocessedCompUnit> units, TopLevelIndex.Builder tliBuilder) {
+ ImmutableList<PreprocessedCompUnit> units) {
SimpleEnv.Builder<ClassSymbol, SourceBoundClass> envBuilder = SimpleEnv.builder();
for (PreprocessedCompUnit unit : units) {
for (SourceBoundClass type : unit.types()) {
- envBuilder.put(type.sym(), type);
- tliBuilder.insert(type.sym());
+ SourceBoundClass prev = envBuilder.put(type.sym(), type);
+ if (prev != null) {
+ throw TurbineError.format(
+ unit.source(), type.decl().position(), ErrorKind.DUPLICATE_DECLARATION, type.sym());
+ }
}
}
return envBuilder.build();
}
+ static class BindPackagesResult {
+ final SimpleEnv<ClassSymbol, PackageSourceBoundClass> classes;
+ final SimpleEnv<ModuleSymbol, PackageSourceBoundModule> modules;
+
+ BindPackagesResult(
+ SimpleEnv<ClassSymbol, PackageSourceBoundClass> classes,
+ SimpleEnv<ModuleSymbol, PackageSourceBoundModule> modules) {
+ this.classes = classes;
+ this.modules = modules;
+ }
+ }
+
/** Initializes scopes for compilation unit and package-level lookup. */
- private static SimpleEnv<ClassSymbol, PackageSourceBoundClass> bindPackages(
+ private static BindPackagesResult bindPackages(
Env<ClassSymbol, SourceBoundClass> ienv,
TopLevelIndex tli,
ImmutableList<PreprocessedCompUnit> units,
CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv) {
SimpleEnv.Builder<ClassSymbol, PackageSourceBoundClass> env = SimpleEnv.builder();
- Scope javaLang = verifyNotNull(tli.lookupPackage(ImmutableList.of("java", "lang")));
- CompoundScope topLevel = CompoundScope.base(tli).append(javaLang);
+ SimpleEnv.Builder<ModuleSymbol, PackageSourceBoundModule> modules = SimpleEnv.builder();
+ Scope javaLang = tli.lookupPackage(ImmutableList.of("java", "lang"));
+ if (javaLang == null) {
+ // TODO(cushon): add support for diagnostics without a source position, and make this one
+ // of those
+ throw new IllegalArgumentException("Could not find java.lang on bootclasspath");
+ }
+ CompoundScope topLevel = CompoundScope.base(tli.scope()).append(javaLang);
for (PreprocessedCompUnit unit : units) {
ImmutableList<String> packagename =
ImmutableList.copyOf(Splitter.on('/').omitEmptyStrings().split(unit.packageName()));
@@ -151,11 +187,17 @@
.append(wildImportScope)
.append(ImportScope.fromScope(packageScope))
.append(importScope);
+ if (unit.module().isPresent()) {
+ ModDecl module = unit.module().get();
+ modules.put(
+ new ModuleSymbol(module.moduleName()),
+ new PackageSourceBoundModule(module, scope, memberImports, unit.source()));
+ }
for (SourceBoundClass type : unit.types()) {
env.put(type.sym(), new PackageSourceBoundClass(type, scope, memberImports, unit.source()));
}
}
- return env.build();
+ return new BindPackagesResult(env.build(), modules.build());
}
/** Binds the type hierarchy (superclasses and interfaces) for all classes in the compilation. */
@@ -202,6 +244,43 @@
return builder.build();
}
+ private static ImmutableList<ModuleInfo> bindModules(
+ SimpleEnv<ModuleSymbol, PackageSourceBoundModule> modules,
+ CompoundEnv<ClassSymbol, TypeBoundClass> env,
+ CompoundEnv<ModuleSymbol, ModuleInfo> moduleEnv,
+ Optional<String> moduleVersion) {
+ // Allow resolution of modules in the current compilation. Currently this is only needed for
+ // version strings in requires directives.
+ moduleEnv =
+ moduleEnv.append(
+ new Env<ModuleSymbol, ModuleInfo>() {
+ @Override
+ public ModuleInfo get(ModuleSymbol sym) {
+ if (modules.asMap().containsKey(sym)) {
+ PackageSourceBoundModule info = modules.get(sym);
+ if (info != null) {
+ return new ModuleInfo(
+ info.module().moduleName(),
+ moduleVersion.orNull(),
+ /* flags= */ 0,
+ /* annos= */ ImmutableList.of(),
+ /* requires= */ ImmutableList.of(),
+ /* exports= */ ImmutableList.of(),
+ /* opens= */ ImmutableList.of(),
+ /* uses= */ ImmutableList.of(),
+ /* provides= */ ImmutableList.of());
+ }
+ }
+ return null;
+ }
+ });
+ ImmutableList.Builder<ModuleInfo> bound = ImmutableList.builder();
+ for (PackageSourceBoundModule module : modules.asMap().values()) {
+ bound.add(ModuleBinder.bind(module, env, moduleEnv, moduleVersion));
+ }
+ return bound.build();
+ }
+
private static Env<ClassSymbol, SourceTypeBoundClass> constants(
ImmutableSet<ClassSymbol> syms,
Env<ClassSymbol, SourceTypeBoundClass> env,
@@ -224,7 +303,14 @@
@Override
public Const.Value complete(Env<FieldSymbol, Const.Value> env1, FieldSymbol k) {
try {
- return new ConstEvaluator(sym, sym, info, info.scope(), env1, baseEnv)
+ return new ConstEvaluator(
+ sym,
+ sym,
+ info.memberImports(),
+ info.source(),
+ info.scope(),
+ env1,
+ baseEnv)
.evalFieldInitializer(field.decl().init().get(), field.type());
} catch (LazyEnv.LazyBindingError e) {
// fields initializers are allowed to reference the field being initialized,
@@ -292,12 +378,15 @@
/** The result of binding: bound nodes for sources in the compilation, and the classpath. */
public static class BindingResult {
private final ImmutableMap<ClassSymbol, SourceTypeBoundClass> units;
+ private final ImmutableList<ModuleInfo> modules;
private final CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv;
public BindingResult(
ImmutableMap<ClassSymbol, SourceTypeBoundClass> units,
+ ImmutableList<ModuleInfo> modules,
CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv) {
this.units = units;
+ this.modules = modules;
this.classPathEnv = classPathEnv;
}
@@ -306,6 +395,10 @@
return units;
}
+ public ImmutableList<ModuleInfo> modules() {
+ return modules;
+ }
+
/** The classpath. */
public CompoundEnv<ClassSymbol, BytecodeBoundClass> classPathEnv() {
return classPathEnv;
diff --git a/java/com/google/turbine/binder/ClassPath.java b/java/com/google/turbine/binder/ClassPath.java
new file mode 100644
index 0000000..d3461bf
--- /dev/null
+++ b/java/com/google/turbine/binder/ClassPath.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 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.turbine.binder.bound.ModuleInfo;
+import com.google.turbine.binder.bytecode.BytecodeBoundClass;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.lookup.TopLevelIndex;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.binder.sym.ModuleSymbol;
+
+/**
+ * A compilation classpath, e.g. the user or platform class path. Maybe backed by a search path of
+ * jar files, or a jrtfs filesystem.
+ */
+public interface ClassPath {
+ /** The classpath's environment. */
+ Env<ClassSymbol, BytecodeBoundClass> env();
+
+ /** The classpath's module environment. */
+ Env<ModuleSymbol, ModuleInfo> moduleEnv();
+
+ /** The classpath's top level index. */
+ TopLevelIndex index();
+}
diff --git a/java/com/google/turbine/binder/ClassPathBinder.java b/java/com/google/turbine/binder/ClassPathBinder.java
index 4542bbe..2b3a921 100644
--- a/java/com/google/turbine/binder/ClassPathBinder.java
+++ b/java/com/google/turbine/binder/ClassPathBinder.java
@@ -20,12 +20,15 @@
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
+import com.google.turbine.binder.bound.ModuleInfo;
+import com.google.turbine.binder.bytecode.BytecodeBinder;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
-import com.google.turbine.binder.env.CompoundEnv;
import com.google.turbine.binder.env.Env;
import com.google.turbine.binder.env.SimpleEnv;
+import com.google.turbine.binder.lookup.SimpleTopLevelIndex;
import com.google.turbine.binder.lookup.TopLevelIndex;
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.nio.file.Path;
@@ -43,24 +46,13 @@
*/
public static final String TRANSITIVE_PREFIX = "META-INF/TRANSITIVE/";
- /**
- * Creates an environment containing symbols in the given classpath and bootclasspath, and adds
- * them to the top-level index.
- */
- public static CompoundEnv<ClassSymbol, BytecodeBoundClass> bind(
- Collection<Path> classpath, Collection<Path> bootclasspath, TopLevelIndex.Builder tli)
- throws IOException {
+ /** Creates an environment containing symbols in the given classpath. */
+ public static ClassPath bindClasspath(Collection<Path> paths) throws IOException {
// TODO(cushon): this is going to require an env eventually,
// e.g. to look up type parameters in enclosing declarations
- Env<ClassSymbol, BytecodeBoundClass> cp = bindClasspath(tli, classpath);
- Env<ClassSymbol, BytecodeBoundClass> bcp = bindClasspath(tli, bootclasspath);
- return CompoundEnv.of(cp).append(bcp);
- }
-
- private static Env<ClassSymbol, BytecodeBoundClass> bindClasspath(
- TopLevelIndex.Builder tli, Collection<Path> paths) throws IOException {
Map<ClassSymbol, BytecodeBoundClass> transitive = new LinkedHashMap<>();
Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>();
+ Map<ModuleSymbol, ModuleInfo> modules = new HashMap<>();
Env<ClassSymbol, BytecodeBoundClass> benv =
new Env<ClassSymbol, BytecodeBoundClass>() {
@Override
@@ -70,7 +62,7 @@
};
for (Path path : paths) {
try {
- bindJar(tli, path, map, benv, transitive);
+ bindJar(path, map, modules, benv, transitive);
} catch (IOException e) {
throw new IOException("error reading " + path, e);
}
@@ -79,16 +71,33 @@
ClassSymbol symbol = entry.getKey();
if (!map.containsKey(symbol)) {
map.put(symbol, entry.getValue());
- tli.insert(symbol);
}
}
- return new SimpleEnv<>(ImmutableMap.copyOf(map));
+ SimpleEnv<ClassSymbol, BytecodeBoundClass> env = new SimpleEnv<>(ImmutableMap.copyOf(map));
+ SimpleEnv<ModuleSymbol, ModuleInfo> moduleEnv = new SimpleEnv<>(ImmutableMap.copyOf(modules));
+ TopLevelIndex index = SimpleTopLevelIndex.of(env.asMap().keySet());
+ return new ClassPath() {
+ @Override
+ public Env<ClassSymbol, BytecodeBoundClass> env() {
+ return env;
+ }
+
+ @Override
+ public Env<ModuleSymbol, ModuleInfo> moduleEnv() {
+ return moduleEnv;
+ }
+
+ @Override
+ public TopLevelIndex index() {
+ return index;
+ }
+ };
}
private static void bindJar(
- TopLevelIndex.Builder tli,
Path path,
Map<ClassSymbol, BytecodeBoundClass> env,
+ Map<ModuleSymbol, ModuleInfo> modules,
Env<ClassSymbol, BytecodeBoundClass> benv,
Map<ClassSymbol, BytecodeBoundClass> transitive)
throws IOException {
@@ -112,10 +121,15 @@
});
continue;
}
+ if (name.substring(name.lastIndexOf('/') + 1).equals("module-info.class")) {
+ ModuleInfo moduleInfo =
+ BytecodeBinder.bindModuleInfo(path.toString(), toByteArrayOrDie(ze));
+ modules.put(new ModuleSymbol(moduleInfo.name()), moduleInfo);
+ continue;
+ }
ClassSymbol sym = new ClassSymbol(name.substring(0, name.length() - ".class".length()));
if (!env.containsKey(sym)) {
env.put(sym, new BytecodeBoundClass(sym, toByteArrayOrDie(ze), benv, path.toString()));
- tli.insert(sym);
}
}
}
diff --git a/java/com/google/turbine/binder/CompUnitPreprocessor.java b/java/com/google/turbine/binder/CompUnitPreprocessor.java
index 4ec2d96..121b18d 100644
--- a/java/com/google/turbine/binder/CompUnitPreprocessor.java
+++ b/java/com/google/turbine/binder/CompUnitPreprocessor.java
@@ -25,15 +25,20 @@
import com.google.turbine.binder.bound.SourceBoundClass;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.diag.SourceFile;
+import com.google.turbine.diag.TurbineError;
+import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree;
import com.google.turbine.tree.Tree.CompUnit;
import com.google.turbine.tree.Tree.ImportDecl;
+import com.google.turbine.tree.Tree.ModDecl;
import com.google.turbine.tree.Tree.PkgDecl;
import com.google.turbine.tree.Tree.TyDecl;
import com.google.turbine.tree.TurbineModifier;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Processes compilation units before binding, creating symbols for type declarations and desugaring
@@ -45,16 +50,19 @@
public static class PreprocessedCompUnit {
private final ImmutableList<Tree.ImportDecl> imports;
private final ImmutableList<SourceBoundClass> types;
+ private final Optional<ModDecl> module;
private final SourceFile source;
private final String packageName;
public PreprocessedCompUnit(
ImmutableList<ImportDecl> imports,
ImmutableList<SourceBoundClass> types,
+ Optional<ModDecl> module,
SourceFile source,
String packageName) {
this.imports = imports;
this.types = types;
+ this.module = module;
this.source = source;
this.packageName = packageName;
}
@@ -67,6 +75,10 @@
return types;
}
+ Optional<ModDecl> module() {
+ return module;
+ }
+
public SourceFile source() {
return source;
}
@@ -104,28 +116,35 @@
new ClassSymbol((!packageName.isEmpty() ? packageName + "/" : "") + decl.name());
int access = access(decl.mods(), decl.tykind());
ImmutableMap<String, ClassSymbol> children =
- preprocessChildren(types, sym, decl.members(), access);
+ preprocessChildren(unit.source(), types, sym, decl.members(), access);
types.add(new SourceBoundClass(sym, null, children, access, decl));
}
- return new PreprocessedCompUnit(unit.imports(), types.build(), unit.source(), packageName);
+ return new PreprocessedCompUnit(
+ unit.imports(), types.build(), unit.mod(), unit.source(), packageName);
}
private static ImmutableMap<String, ClassSymbol> preprocessChildren(
+ SourceFile source,
ImmutableList.Builder<SourceBoundClass> types,
ClassSymbol owner,
ImmutableList<Tree> members,
int enclosing) {
ImmutableMap.Builder<String, ClassSymbol> result = ImmutableMap.builder();
+ Set<String> seen = new HashSet<>();
for (Tree member : members) {
if (member.kind() == Tree.Kind.TY_DECL) {
Tree.TyDecl decl = (Tree.TyDecl) member;
ClassSymbol sym = new ClassSymbol(owner.binaryName() + '$' + decl.name());
+ if (!seen.add(decl.name())) {
+ throw TurbineError.format(
+ source, member.position(), ErrorKind.DUPLICATE_DECLARATION, sym);
+ }
result.put(decl.name(), sym);
int access = innerClassAccess(enclosing, decl);
ImmutableMap<String, ClassSymbol> children =
- preprocessChildren(types, sym, decl.members(), access);
+ preprocessChildren(source, types, sym, decl.members(), access);
types.add(new SourceBoundClass(sym, owner, children, access, decl));
}
}
diff --git a/java/com/google/turbine/binder/ConstBinder.java b/java/com/google/turbine/binder/ConstBinder.java
index 3ac4d0a..4cb40a2 100644
--- a/java/com/google/turbine/binder/ConstBinder.java
+++ b/java/com/google/turbine/binder/ConstBinder.java
@@ -72,12 +72,21 @@
this.origin = origin;
this.base = base;
this.env = env;
- this.constEvaluator = new ConstEvaluator(origin, origin, base, base.scope(), constantEnv, env);
+ this.constEvaluator =
+ new ConstEvaluator(
+ origin, origin, base.memberImports(), base.source(), base.scope(), constantEnv, env);
}
public SourceTypeBoundClass bind() {
ImmutableList<AnnoInfo> annos =
- new ConstEvaluator(origin, base.owner(), base, base.enclosingScope(), constantEnv, env)
+ new ConstEvaluator(
+ origin,
+ base.owner(),
+ base.memberImports(),
+ base.source(),
+ base.enclosingScope(),
+ constantEnv,
+ env)
.evaluateAnnotations(base.annotations());
ImmutableList<TypeBoundClass.FieldInfo> fields = fields(base.fields());
ImmutableList<MethodInfo> methods = bindMethods(base.methods());
diff --git a/java/com/google/turbine/binder/ConstEvaluator.java b/java/com/google/turbine/binder/ConstEvaluator.java
index 0598d8e..d661fb9 100644
--- a/java/com/google/turbine/binder/ConstEvaluator.java
+++ b/java/com/google/turbine/binder/ConstEvaluator.java
@@ -24,17 +24,18 @@
import com.google.turbine.binder.bound.AnnotationValue;
import com.google.turbine.binder.bound.ClassValue;
import com.google.turbine.binder.bound.EnumConstantValue;
-import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
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.lookup.CompoundScope;
import com.google.turbine.binder.lookup.LookupKey;
import com.google.turbine.binder.lookup.LookupResult;
+import com.google.turbine.binder.lookup.MemberImportIndex;
+import com.google.turbine.binder.lookup.Scope;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
+import com.google.turbine.diag.SourceFile;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.model.Const;
@@ -72,8 +73,11 @@
/** The symbol of the enclosing class, for lexical field lookups. */
private final ClassSymbol owner;
- /** The bound node of the enclosing class. */
- private final SourceTypeBoundClass base;
+ /** Member imports of the enclosing compilation unit. */
+ private final MemberImportIndex memberImports;
+
+ /** The current source file. */
+ private final SourceFile source;
/** The constant variable environment. */
private final Env<FieldSymbol, Const.Value> values;
@@ -81,19 +85,21 @@
/** The class environment. */
private final CompoundEnv<ClassSymbol, TypeBoundClass> env;
- private final CompoundScope scope;
+ private final Scope scope;
public ConstEvaluator(
ClassSymbol origin,
ClassSymbol owner,
- SourceTypeBoundClass base,
- CompoundScope scope,
+ MemberImportIndex memberImports,
+ SourceFile source,
+ Scope scope,
Env<FieldSymbol, Const.Value> values,
CompoundEnv<ClassSymbol, TypeBoundClass> env) {
this.origin = origin;
this.owner = owner;
- this.base = base;
+ this.memberImports = memberImports;
+ this.source = source;
this.values = values;
this.env = env;
this.scope = scope;
@@ -230,14 +236,14 @@
if (field != null) {
return field;
}
- ClassSymbol classSymbol = base.memberImports().singleMemberImport(simpleName);
+ ClassSymbol classSymbol = memberImports.singleMemberImport(simpleName);
if (classSymbol != null) {
field = Resolve.resolveField(env, origin, classSymbol, simpleName);
if (field != null) {
return field;
}
}
- Iterator<ClassSymbol> it = base.memberImports().onDemandImports();
+ Iterator<ClassSymbol> it = memberImports.onDemandImports();
while (it.hasNext()) {
field = Resolve.resolveField(env, origin, it.next(), simpleName);
if (field == null) {
@@ -925,7 +931,7 @@
for (String name : result.remaining()) {
sym = Resolve.resolve(env, sym, sym, name);
}
- AnnoInfo annoInfo = evaluateAnnotation(new AnnoInfo(base.source(), sym, t, null));
+ AnnoInfo annoInfo = evaluateAnnotation(new AnnoInfo(source, sym, t, null));
return new AnnotationValue(annoInfo.sym(), annoInfo.values());
}
@@ -974,7 +980,7 @@
}
private TurbineError error(int position, ErrorKind kind, Object... args) {
- return TurbineError.format(base.source(), position, kind, args);
+ return TurbineError.format(source, position, kind, args);
}
public Const.Value evalFieldInitializer(Expression expression, Type type) {
diff --git a/java/com/google/turbine/binder/CtSymClassBinder.java b/java/com/google/turbine/binder/CtSymClassBinder.java
new file mode 100644
index 0000000..0d71b8d
--- /dev/null
+++ b/java/com/google/turbine/binder/CtSymClassBinder.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018 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.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+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.SimpleTopLevelIndex;
+import com.google.turbine.binder.lookup.TopLevelIndex;
+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.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/** Constructs a platform {@link ClassPath} from the current JDK's ct.sym file. */
+public class CtSymClassBinder {
+
+ @Nullable
+ public static ClassPath bind(String version) throws IOException {
+ Path javaHome = Paths.get(System.getProperty("java.home"));
+ Path ctSym = javaHome.resolve("lib/ct.sym");
+ if (!Files.exists(ctSym)) {
+ throw new IllegalStateException("lib/ct.sym does not exist in " + javaHome);
+ }
+ Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>();
+ Env<ClassSymbol, BytecodeBoundClass> benv =
+ new Env<ClassSymbol, BytecodeBoundClass>() {
+ @Override
+ public BytecodeBoundClass get(ClassSymbol sym) {
+ return map.get(sym);
+ }
+ };
+ // 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.
+ for (Zip.Entry ze : new Zip.ZipIterable(ctSym)) {
+ String name = ze.name();
+ if (!name.endsWith(".sig")) {
+ continue;
+ }
+ int idx = name.indexOf('/');
+ if (idx == -1) {
+ 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)) {
+ continue;
+ }
+ ClassSymbol sym = new ClassSymbol(name.substring(idx + 1, name.length() - ".sig".length()));
+ if (!map.containsKey(sym)) {
+ map.put(
+ sym, new BytecodeBoundClass(sym, toByteArrayOrDie(ze), benv, ctSym + "!" + ze.name()));
+ }
+ }
+ if (map.isEmpty()) {
+ // we didn't find any classes for the desired release
+ return null;
+ }
+ SimpleEnv<ClassSymbol, BytecodeBoundClass> env = new SimpleEnv<>(ImmutableMap.copyOf(map));
+ // TODO(cushon): support ct.sym module-infos once they exist (JDK 10?)
+ Env<ModuleSymbol, ModuleInfo> moduleEnv = new SimpleEnv<>(ImmutableMap.of());
+ TopLevelIndex index = SimpleTopLevelIndex.of(env.asMap().keySet());
+ return new ClassPath() {
+ @Override
+ public Env<ClassSymbol, BytecodeBoundClass> env() {
+ return env;
+ }
+
+ @Override
+ public Env<ModuleSymbol, ModuleInfo> moduleEnv() {
+ return moduleEnv;
+ }
+
+ @Override
+ public TopLevelIndex index() {
+ return index;
+ }
+ };
+ }
+
+ private static Supplier<byte[]> toByteArrayOrDie(Zip.Entry ze) {
+ return Suppliers.memoize(
+ new Supplier<byte[]>() {
+ @Override
+ public byte[] get() {
+ return ze.data();
+ }
+ });
+ }
+}
diff --git a/java/com/google/turbine/binder/JimageClassBinder.java b/java/com/google/turbine/binder/JimageClassBinder.java
new file mode 100644
index 0000000..40be3a3
--- /dev/null
+++ b/java/com/google/turbine/binder/JimageClassBinder.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2018 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.base.Suppliers;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Table;
+import com.google.turbine.binder.bound.ModuleInfo;
+import com.google.turbine.binder.bytecode.BytecodeBinder;
+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.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.net.URI;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/** Constructs a platform {@link ClassPath} from the current JDK's jimage file using jrtfs. */
+public class JimageClassBinder {
+
+ static JimageClassBinder create(FileSystem fileSystem) throws IOException {
+ Path modules = fileSystem.getPath("/modules");
+ Path packages = fileSystem.getPath("/packages");
+ ImmutableMultimap.Builder<String, String> packageMap = ImmutableMultimap.builder();
+ try (DirectoryStream<Path> ps = Files.newDirectoryStream(packages)) {
+ for (Path p : ps) {
+ String packageName = packages.relativize(p).toString().replace('.', '/');
+ try (DirectoryStream<Path> ms = Files.newDirectoryStream(p)) {
+ for (Path m : ms) {
+ packageMap.put(packageName, p.relativize(m).toString());
+ }
+ }
+ }
+ }
+ return new JimageClassBinder(packageMap.build(), modules);
+ }
+
+ /** Returns a platform classpath for the host JDK's jimage file. */
+ public static ClassPath bindDefault() throws IOException {
+ return JimageClassBinder.create(FileSystems.getFileSystem(URI.create("jrt:/")))
+ .new JimageClassPath();
+ }
+
+ /** Returns a platform classpath for the given JDK's jimage file. */
+ public static ClassPath bind(String javaHome) throws IOException {
+ if (javaHome.equals(System.getProperty("java.home"))) {
+ return bindDefault();
+ }
+ FileSystem fileSystem =
+ FileSystems.newFileSystem(URI.create("jrt:/"), ImmutableMap.of("java.home", javaHome));
+ return JimageClassBinder.create(fileSystem).new JimageClassPath();
+ }
+
+ private final Multimap<String, String> packageMap;
+ private final Path modulesRoot;
+
+ private final Set<String> loadedPackages = new HashSet<>();
+ private final Table<String, String, ClassSymbol> packageClassesBySimpleName =
+ HashBasedTable.create();
+ private final Map<String, ModuleInfo> moduleMap = new HashMap<>();
+ private final Map<ClassSymbol, BytecodeBoundClass> env = new HashMap<>();
+
+ public JimageClassBinder(ImmutableMultimap<String, String> packageMap, Path modules) {
+ this.packageMap = packageMap;
+ this.modulesRoot = modules;
+ }
+
+ Path modulePath(String moduleName) {
+ Path path = modulesRoot.resolve(moduleName);
+ return Files.exists(path) ? path : null;
+ }
+
+ ModuleInfo module(String moduleName) {
+ ModuleInfo result = moduleMap.get(moduleName);
+ if (result == null) {
+ Path path = modulePath(moduleName);
+ if (path == null) {
+ return null;
+ }
+ try {
+ path = path.resolve("module-info.class");
+ result = BytecodeBinder.bindModuleInfo(path.toString(), toByteArrayOrDie(path));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ moduleMap.put(moduleName, result);
+ }
+ return result;
+ }
+
+ boolean initPackage(String packageName) {
+ Collection<String> moduleNames = packageMap.get(packageName);
+ if (moduleNames.isEmpty()) {
+ return false;
+ }
+ if (!loadedPackages.add(packageName)) {
+ return true;
+ }
+ Env<ClassSymbol, BytecodeBoundClass> env =
+ new Env<ClassSymbol, BytecodeBoundClass>() {
+ @Override
+ public BytecodeBoundClass get(ClassSymbol sym) {
+ return JimageClassBinder.this.env.get(sym);
+ }
+ };
+ for (String moduleName : moduleNames) {
+ if (moduleName != null) {
+ Path modulePath = modulePath(moduleName);
+ Path modulePackagePath = modulePath.resolve(packageName);
+ try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulePackagePath)) {
+ for (Path path : ds) {
+ if (!Files.isRegularFile(path)
+ || path.getFileName().toString().equals("module-info.class")) {
+ continue;
+ }
+ String binaryName = modulePath.relativize(path).toString();
+ binaryName = binaryName.substring(0, binaryName.length() - ".class".length());
+ ClassSymbol sym = new ClassSymbol(binaryName);
+ packageClassesBySimpleName.put(packageName, simpleName(sym), sym);
+ JimageClassBinder.this.env.put(
+ sym, new BytecodeBoundClass(sym, toByteArrayOrDie(path), env, path.toString()));
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ }
+ return true;
+ }
+
+ private static Supplier<byte[]> toByteArrayOrDie(Path path) {
+ return Suppliers.memoize(
+ new Supplier<byte[]>() {
+ @Override
+ public byte[] get() {
+ try {
+ return Files.readAllBytes(path);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ });
+ }
+
+ private static String simpleName(ClassSymbol sym) {
+ int idx = sym.binaryName().lastIndexOf('/');
+ return idx != -1 ? sym.binaryName().substring(idx + 1) : sym.binaryName();
+ }
+
+ private static String packageName(ClassSymbol sym) {
+ int idx = sym.binaryName().lastIndexOf('/');
+ return idx != -1 ? sym.binaryName().substring(0, idx) : "";
+ }
+
+ private class JimageTopLevelIndex implements TopLevelIndex {
+
+ final Scope topLevelScope =
+ new Scope() {
+ @Nullable
+ @Override
+ public LookupResult lookup(LookupKey lookupKey) {
+ // Find the longest prefix of the key that corresponds to a package name.
+ // TODO(cushon): SimpleTopLevelIndex uses a prefix map for this, does it matter?
+ Scope scope = null;
+ ImmutableList<String> names = lookupKey.simpleNames();
+ int idx = -1;
+ for (int i = 1; i < names.size(); i++) {
+ Scope cand = lookupPackage(names.subList(0, i));
+ if (cand != null) {
+ scope = cand;
+ idx = i;
+ }
+ }
+ return scope != null
+ ? scope.lookup(new LookupKey(names.subList(idx, names.size())))
+ : null;
+ }
+ };
+
+ @Override
+ public Scope scope() {
+ return topLevelScope;
+ }
+
+ @Override
+ public Scope lookupPackage(ImmutableList<String> name) {
+ String packageName = Joiner.on('/').join(name);
+ if (!initPackage(packageName)) {
+ return null;
+ }
+ return new Scope() {
+ @Nullable
+ @Override
+ public LookupResult lookup(LookupKey lookupKey) {
+ ClassSymbol sym = packageClassesBySimpleName.get(packageName, lookupKey.first());
+ return sym != null ? new LookupResult(sym, lookupKey) : null;
+ }
+ };
+ }
+ }
+
+ private class JimageClassPath implements ClassPath {
+
+ final TopLevelIndex index = new JimageTopLevelIndex();
+
+ @Override
+ public Env<ClassSymbol, BytecodeBoundClass> env() {
+ return new Env<ClassSymbol, BytecodeBoundClass>() {
+ @Override
+ public BytecodeBoundClass get(ClassSymbol sym) {
+ return initPackage(packageName(sym)) ? env.get(sym) : null;
+ }
+ };
+ }
+
+ @Override
+ public Env<ModuleSymbol, ModuleInfo> moduleEnv() {
+ return new Env<ModuleSymbol, ModuleInfo>() {
+ @Override
+ public ModuleInfo get(ModuleSymbol module) {
+ return module(module.name());
+ }
+ };
+ }
+
+ @Override
+ public TopLevelIndex index() {
+ return index;
+ }
+ }
+}
diff --git a/java/com/google/turbine/binder/ModuleBinder.java b/java/com/google/turbine/binder/ModuleBinder.java
new file mode 100644
index 0000000..23c9624
--- /dev/null
+++ b/java/com/google/turbine/binder/ModuleBinder.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2018 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.base.Verify.verifyNotNull;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.turbine.binder.bound.ModuleInfo;
+import com.google.turbine.binder.bound.ModuleInfo.ExportInfo;
+import com.google.turbine.binder.bound.ModuleInfo.OpenInfo;
+import com.google.turbine.binder.bound.ModuleInfo.ProvideInfo;
+import com.google.turbine.binder.bound.ModuleInfo.RequireInfo;
+import com.google.turbine.binder.bound.ModuleInfo.UseInfo;
+import com.google.turbine.binder.bound.PackageSourceBoundModule;
+import com.google.turbine.binder.bound.TypeBoundClass;
+import com.google.turbine.binder.env.CompoundEnv;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.env.SimpleEnv;
+import com.google.turbine.binder.lookup.CompoundScope;
+import com.google.turbine.binder.lookup.LookupKey;
+import com.google.turbine.binder.lookup.LookupResult;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.binder.sym.ModuleSymbol;
+import com.google.turbine.diag.TurbineError;
+import com.google.turbine.diag.TurbineError.ErrorKind;
+import com.google.turbine.model.TurbineFlag;
+import com.google.turbine.tree.Tree;
+import com.google.turbine.tree.Tree.ModDirective;
+import com.google.turbine.tree.Tree.ModExports;
+import com.google.turbine.tree.Tree.ModOpens;
+import com.google.turbine.tree.Tree.ModProvides;
+import com.google.turbine.tree.Tree.ModRequires;
+import com.google.turbine.tree.Tree.ModUses;
+import com.google.turbine.tree.TurbineModifier;
+import com.google.turbine.type.AnnoInfo;
+
+/** Binding pass for modules. */
+public class ModuleBinder {
+
+ public static ModuleInfo bind(
+ PackageSourceBoundModule module,
+ CompoundEnv<ClassSymbol, TypeBoundClass> env,
+ Env<ModuleSymbol, ModuleInfo> moduleEnv,
+ Optional<String> moduleVersion) {
+ return new ModuleBinder(module, env, moduleEnv, moduleVersion).bind();
+ }
+
+ private final PackageSourceBoundModule module;
+ private final CompoundEnv<ClassSymbol, TypeBoundClass> env;
+ private final Env<ModuleSymbol, ModuleInfo> moduleEnv;
+ private final Optional<String> moduleVersion;
+ private final CompoundScope scope;
+
+ public ModuleBinder(
+ PackageSourceBoundModule module,
+ CompoundEnv<ClassSymbol, TypeBoundClass> env,
+ Env<ModuleSymbol, ModuleInfo> moduleEnv,
+ Optional<String> moduleVersion) {
+ this.module = module;
+ this.env = env;
+ this.moduleEnv = moduleEnv;
+ this.moduleVersion = moduleVersion;
+ this.scope = module.scope().toScope(Resolve.resolveFunction(env, /* origin= */ null));
+ }
+
+ private ModuleInfo bind() {
+ // bind annotations; constant fields are already bound
+ ConstEvaluator constEvaluator =
+ new ConstEvaluator(
+ /* origin= */ null,
+ /* owner= */ null,
+ module.memberImports(),
+ module.source(),
+ scope,
+ /* values= */ new SimpleEnv<>(ImmutableMap.of()),
+ env);
+ ImmutableList.Builder<AnnoInfo> annoInfos = ImmutableList.builder();
+ for (Tree.Anno annoTree : module.module().annos()) {
+ ClassSymbol sym = resolve(annoTree.position(), annoTree.name());
+ annoInfos.add(new AnnoInfo(module.source(), sym, annoTree, null));
+ }
+ ImmutableList<AnnoInfo> annos = constEvaluator.evaluateAnnotations(annoInfos.build());
+
+ int flags = module.module().open() ? TurbineFlag.ACC_OPEN : 0;
+
+ // bind directives
+ ImmutableList.Builder<ModuleInfo.RequireInfo> requires = ImmutableList.builder();
+ ImmutableList.Builder<ModuleInfo.ExportInfo> exports = ImmutableList.builder();
+ ImmutableList.Builder<ModuleInfo.OpenInfo> opens = ImmutableList.builder();
+ ImmutableList.Builder<ModuleInfo.UseInfo> uses = ImmutableList.builder();
+ ImmutableList.Builder<ModuleInfo.ProvideInfo> provides = ImmutableList.builder();
+ boolean requiresJavaBase = false;
+ for (ModDirective directive : module.module().directives()) {
+ switch (directive.directiveKind()) {
+ case REQUIRES:
+ {
+ ModRequires require = (ModRequires) directive;
+ requiresJavaBase |= require.moduleName().equals(ModuleSymbol.JAVA_BASE.name());
+ requires.add(bindRequires(require));
+ break;
+ }
+ case EXPORTS:
+ exports.add(bindExports((ModExports) directive));
+ break;
+ case OPENS:
+ opens.add(bindOpens((ModOpens) directive));
+ break;
+ case USES:
+ uses.add(bindUses((ModUses) directive));
+ break;
+ case PROVIDES:
+ provides.add(bindProvides((ModProvides) directive));
+ break;
+ default:
+ throw new AssertionError(directive.kind());
+ }
+ }
+ if (!requiresJavaBase) {
+ // everything requires java.base, either explicitly or implicitly
+ ModuleInfo javaBaseModule = moduleEnv.get(ModuleSymbol.JAVA_BASE);
+ verifyNotNull(javaBaseModule, ModuleSymbol.JAVA_BASE.name());
+ requires =
+ ImmutableList.<RequireInfo>builder()
+ .add(
+ new RequireInfo(
+ ModuleSymbol.JAVA_BASE.name(),
+ TurbineFlag.ACC_MANDATED,
+ javaBaseModule.version()))
+ .addAll(requires.build());
+ }
+
+ return new ModuleInfo(
+ module.module().moduleName(),
+ moduleVersion.orNull(),
+ flags,
+ annos,
+ requires.build(),
+ exports.build(),
+ opens.build(),
+ uses.build(),
+ provides.build());
+ }
+
+ private RequireInfo bindRequires(ModRequires directive) {
+ String moduleName = directive.moduleName();
+ int flags = 0;
+ for (TurbineModifier mod : directive.mods()) {
+ switch (mod) {
+ case TRANSITIVE:
+ flags |= mod.flag();
+ break;
+ case STATIC:
+ // the 'static' modifier on requires translates to ACC_STATIC_PHASE, not ACC_STATIC
+ flags |= TurbineFlag.ACC_STATIC_PHASE;
+ break;
+ default:
+ throw new AssertionError(mod);
+ }
+ }
+ ModuleInfo requires = moduleEnv.get(new ModuleSymbol(moduleName));
+ return new RequireInfo(moduleName, flags, requires != null ? requires.version() : null);
+ }
+
+ private ExportInfo bindExports(ModExports directive) {
+ return new ExportInfo(directive.packageName(), directive.moduleNames());
+ }
+
+ private OpenInfo bindOpens(ModOpens directive) {
+ return new OpenInfo(directive.packageName(), directive.moduleNames());
+ }
+
+ private UseInfo bindUses(ModUses directive) {
+ return new UseInfo(resolve(directive.position(), directive.typeName()));
+ }
+
+ private ProvideInfo bindProvides(ModProvides directive) {
+ ClassSymbol sym = resolve(directive.position(), directive.typeName());
+ ImmutableList.Builder<ClassSymbol> impls = ImmutableList.builder();
+ for (ImmutableList<String> impl : directive.implNames()) {
+ impls.add(resolve(directive.position(), impl));
+ }
+ return new ProvideInfo(sym, impls.build());
+ }
+
+ /* Resolves qualified class names. */
+ private ClassSymbol resolve(int pos, ImmutableList<String> simpleNames) {
+ LookupKey key = new LookupKey(simpleNames);
+ LookupResult result = scope.lookup(key);
+ if (result == null) {
+ throw error(ErrorKind.SYMBOL_NOT_FOUND, pos, Joiner.on('.').join(simpleNames));
+ }
+ ClassSymbol sym = (ClassSymbol) result.sym();
+ for (String name : result.remaining()) {
+ sym = Resolve.resolve(env, /* origin= */ null, sym, name);
+ if (sym == null) {
+ throw error(ErrorKind.SYMBOL_NOT_FOUND, pos, name);
+ }
+ }
+ return sym;
+ }
+
+ private TurbineError error(ErrorKind kind, int pos, Object... args) {
+ return TurbineError.format(module.source(), pos, kind, args);
+ }
+}
diff --git a/java/com/google/turbine/binder/TypeBinder.java b/java/com/google/turbine/binder/TypeBinder.java
index b8f6ecd..964958b 100644
--- a/java/com/google/turbine/binder/TypeBinder.java
+++ b/java/com/google/turbine/binder/TypeBinder.java
@@ -476,7 +476,10 @@
switch (base.kind()) {
case INTERFACE:
case ANNOTATION:
- access |= TurbineFlag.ACC_PUBLIC;
+ // interface members have default public visibility
+ if ((access & TurbineVisibility.VISIBILITY_MASK) == 0) {
+ access |= TurbineFlag.ACC_PUBLIC;
+ }
if ((access
& (TurbineFlag.ACC_DEFAULT | TurbineFlag.ACC_STATIC | TurbineFlag.ACC_SYNTHETIC))
== 0) {
diff --git a/java/com/google/turbine/binder/bound/ModuleInfo.java b/java/com/google/turbine/binder/bound/ModuleInfo.java
new file mode 100644
index 0000000..9afd474
--- /dev/null
+++ b/java/com/google/turbine/binder/bound/ModuleInfo.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2018 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.bound;
+
+import com.google.common.collect.ImmutableList;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.type.AnnoInfo;
+import javax.annotation.Nullable;
+
+/** A bound module declaration (see JLS §7.7). */
+public class ModuleInfo {
+
+ private final String name;
+ @Nullable private final String version;
+ private final int flags;
+ private final ImmutableList<AnnoInfo> annos;
+ private final ImmutableList<RequireInfo> requires;
+ private final ImmutableList<ExportInfo> exports;
+ private final ImmutableList<OpenInfo> opens;
+ private final ImmutableList<UseInfo> uses;
+ private final ImmutableList<ProvideInfo> provides;
+
+ public ModuleInfo(
+ String name,
+ @Nullable String version,
+ int flags,
+ ImmutableList<AnnoInfo> annos,
+ ImmutableList<RequireInfo> requires,
+ ImmutableList<ExportInfo> exports,
+ ImmutableList<OpenInfo> opens,
+ ImmutableList<UseInfo> uses,
+ ImmutableList<ProvideInfo> provides) {
+ this.name = name;
+ this.version = version;
+ this.flags = flags;
+ this.annos = annos;
+ this.requires = requires;
+ this.exports = exports;
+ this.opens = opens;
+ this.uses = uses;
+ this.provides = provides;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ @Nullable
+ public String version() {
+ return version;
+ }
+
+ public int flags() {
+ return flags;
+ }
+
+ public ImmutableList<AnnoInfo> annos() {
+ return annos;
+ }
+
+ public ImmutableList<RequireInfo> requires() {
+ return requires;
+ }
+
+ public ImmutableList<ExportInfo> exports() {
+ return exports;
+ }
+
+ public ImmutableList<OpenInfo> opens() {
+ return opens;
+ }
+
+ public ImmutableList<UseInfo> uses() {
+ return uses;
+ }
+
+ public ImmutableList<ProvideInfo> provides() {
+ return provides;
+ }
+
+ /** A JLS §7.7.1 requires directive. */
+ public static class RequireInfo {
+
+ private final String moduleName;
+ private final int flags;
+ private final String version;
+
+ public RequireInfo(String moduleName, int flags, String version) {
+ this.moduleName = moduleName;
+ this.flags = flags;
+ this.version = version;
+ }
+
+ public String moduleName() {
+ return moduleName;
+ }
+
+ public int flags() {
+ return flags;
+ }
+
+ public String version() {
+ return version;
+ }
+ }
+
+ /** A JLS §7.7.2 exports directive. */
+ public static class ExportInfo {
+
+ private final String packageName;
+ private final ImmutableList<String> modules;
+
+ public ExportInfo(String packageName, ImmutableList<String> modules) {
+ this.packageName = packageName;
+ this.modules = modules;
+ }
+
+ public String packageName() {
+ return packageName;
+ }
+
+ public ImmutableList<String> modules() {
+ return modules;
+ }
+ }
+
+ /** A JLS §7.7.2 opens directive. */
+ public static class OpenInfo {
+
+ private final String packageName;
+ private final ImmutableList<String> modules;
+
+ public OpenInfo(String packageName, ImmutableList<String> modules) {
+ this.packageName = packageName;
+ this.modules = modules;
+ }
+
+ public String packageName() {
+ return packageName;
+ }
+
+ public ImmutableList<String> modules() {
+ return modules;
+ }
+ }
+
+ /** A JLS §7.7.3 uses directive. */
+ public static class UseInfo {
+
+ private final ClassSymbol sym;
+
+ public UseInfo(ClassSymbol sym) {
+ this.sym = sym;
+ }
+
+ public ClassSymbol sym() {
+ return sym;
+ }
+ }
+
+ /** A JLS §7.7.4 provides directive. */
+ public static class ProvideInfo {
+
+ private final ClassSymbol sym;
+ private final ImmutableList<ClassSymbol> impls;
+
+ public ProvideInfo(ClassSymbol sym, ImmutableList<ClassSymbol> impls) {
+ this.sym = sym;
+ this.impls = impls;
+ }
+
+ public ClassSymbol sym() {
+ return sym;
+ }
+
+ public ImmutableList<ClassSymbol> impls() {
+ return impls;
+ }
+ }
+}
diff --git a/java/com/google/turbine/binder/bound/PackageSourceBoundModule.java b/java/com/google/turbine/binder/bound/PackageSourceBoundModule.java
new file mode 100644
index 0000000..c412b06
--- /dev/null
+++ b/java/com/google/turbine/binder/bound/PackageSourceBoundModule.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 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.bound;
+
+import com.google.turbine.binder.lookup.ImportScope;
+import com.google.turbine.binder.lookup.MemberImportIndex;
+import com.google.turbine.diag.SourceFile;
+import com.google.turbine.tree.Tree.ModDecl;
+
+/** Wraps a {@link ModDecl} with lookup scopes for the current compilation unit and package. */
+public class PackageSourceBoundModule {
+
+ private final ModDecl module;
+ private final ImportScope scope;
+ private final MemberImportIndex memberImports;
+ private final SourceFile source;
+
+ public PackageSourceBoundModule(
+ ModDecl module, ImportScope scope, MemberImportIndex memberImports, SourceFile source) {
+ this.module = module;
+ this.scope = scope;
+ this.memberImports = memberImports;
+ this.source = source;
+ }
+
+ public ModDecl module() {
+ return module;
+ }
+
+ public ImportScope scope() {
+ return scope;
+ }
+
+ public MemberImportIndex memberImports() {
+ return memberImports;
+ }
+
+ public SourceFile source() {
+ return source;
+ }
+}
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java
index ca66046..4b92f11 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBinder.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBinder.java
@@ -17,8 +17,11 @@
package com.google.turbine.binder.bytecode;
import com.google.common.collect.ImmutableList;
+import com.google.turbine.binder.bound.ModuleInfo;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
+import com.google.turbine.bytecode.ClassFile;
+import com.google.turbine.bytecode.ClassReader;
import com.google.turbine.bytecode.sig.Sig;
import com.google.turbine.bytecode.sig.Sig.LowerBoundTySig;
import com.google.turbine.bytecode.sig.Sig.UpperBoundTySig;
@@ -26,9 +29,11 @@
import com.google.turbine.type.Type;
import com.google.turbine.type.Type.ArrayTy;
import com.google.turbine.type.Type.TyVar;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
+import java.util.function.Supplier;
/** Bind {@link Type}s from bytecode. */
public class BytecodeBinder {
@@ -88,4 +93,24 @@
private static Type bindArrayTy(Sig.ArrayTySig arrayTySig, Function<String, TyVarSymbol> scope) {
return new ArrayTy(bindTy(arrayTySig.elementType(), scope), ImmutableList.of());
}
+
+ /**
+ * Returns a {@link ModuleInfo} given a module-info class file. Currently only the module's name,
+ * version, and flags are populated, since the directives are not needed by turbine at compile
+ * time.
+ */
+ public static ModuleInfo bindModuleInfo(String path, Supplier<byte[]> bytes) throws IOException {
+ ClassFile classFile = ClassReader.read(path, bytes.get());
+ ClassFile.ModuleInfo module = classFile.module();
+ return new ModuleInfo(
+ module.name(),
+ module.version(),
+ module.flags(),
+ /* annos= */ ImmutableList.of(),
+ /* requires= */ ImmutableList.of(),
+ /* exports= */ ImmutableList.of(),
+ /* opens= */ ImmutableList.of(),
+ /* uses= */ ImmutableList.of(),
+ /* provides= */ ImmutableList.of());
+ }
}
diff --git a/java/com/google/turbine/binder/env/CompoundEnv.java b/java/com/google/turbine/binder/env/CompoundEnv.java
index 44f34c7..43ce768 100644
--- a/java/com/google/turbine/binder/env/CompoundEnv.java
+++ b/java/com/google/turbine/binder/env/CompoundEnv.java
@@ -16,7 +16,10 @@
package com.google.turbine.binder.env;
+import static java.util.Objects.requireNonNull;
+
import com.google.turbine.binder.sym.Symbol;
+import javax.annotation.Nullable;
/** An {@link Env} that chains two existing envs together. */
public class CompoundEnv<S extends Symbol, V> implements Env<S, V> {
@@ -24,9 +27,9 @@
private final Env<S, ? extends V> base;
private final Env<S, ? extends V> env;
- private CompoundEnv(Env<S, ? extends V> base, Env<S, ? extends V> env) {
+ private CompoundEnv(@Nullable Env<S, ? extends V> base, Env<S, ? extends V> env) {
this.base = base;
- this.env = env;
+ this.env = requireNonNull(env);
}
@Override
diff --git a/java/com/google/turbine/binder/env/SimpleEnv.java b/java/com/google/turbine/binder/env/SimpleEnv.java
index 0b413ab..b07bf5f 100644
--- a/java/com/google/turbine/binder/env/SimpleEnv.java
+++ b/java/com/google/turbine/binder/env/SimpleEnv.java
@@ -18,6 +18,8 @@
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.sym.Symbol;
+import java.util.LinkedHashMap;
+import java.util.Map;
/** A simple {@link ImmutableMap}-backed {@link Env}. */
public class SimpleEnv<K extends Symbol, V> implements Env<K, V> {
@@ -38,14 +40,14 @@
/** A builder for {@link SimpleEnv}static. */
public static class Builder<K extends Symbol, V> {
- private final ImmutableMap.Builder<K, V> map = ImmutableMap.builder();
+ private final Map<K, V> map = new LinkedHashMap<>();
- public void put(K sym, V v) {
- map.put(sym, v);
+ public V put(K sym, V v) {
+ return map.put(sym, v);
}
public SimpleEnv<K, V> build() {
- return new SimpleEnv<>(map.build());
+ return new SimpleEnv<>(ImmutableMap.copyOf(map));
}
}
diff --git a/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java b/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java
new file mode 100644
index 0000000..526e493
--- /dev/null
+++ b/java/com/google/turbine/binder/lookup/CompoundTopLevelIndex.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 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.lookup;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableList;
+import javax.annotation.Nullable;
+
+/** A {@link TopLevelIndex} that aggregates multiple indices into one. */
+// Note: this implementation doesn't detect if the indices contain incompatible information,
+// e.g. a class name in one index that is a prefix of a package name in another index. This
+// shouldn't matter in practice because we rely on javac to reject invalid input, but it would
+// be nice to report an error in that case.
+// TODO(cushon): improve error handling
+public class CompoundTopLevelIndex implements TopLevelIndex {
+
+ private final ImmutableList<TopLevelIndex> indexes;
+
+ private CompoundTopLevelIndex(ImmutableList<TopLevelIndex> indexes) {
+ this.indexes = checkNotNull(indexes);
+ }
+
+ /** Creates a {@link CompoundTopLevelIndex}. */
+ public static CompoundTopLevelIndex of(TopLevelIndex... indexes) {
+ return new CompoundTopLevelIndex(ImmutableList.copyOf(indexes));
+ }
+
+ private final Scope scope =
+ new Scope() {
+ @Nullable
+ @Override
+ public LookupResult lookup(LookupKey lookupKey) {
+ // Return the first matching symbol.
+ for (TopLevelIndex index : indexes) {
+ LookupResult result = index.scope().lookup(lookupKey);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+ };
+
+ @Override
+ public Scope scope() {
+ return scope;
+ }
+
+ @Override
+ public Scope lookupPackage(ImmutableList<String> packagename) {
+ // When returning package scopes, build up a compound scope containing entries from all
+ // indices with matching packages.
+ CompoundScope result = null;
+ for (TopLevelIndex index : indexes) {
+ Scope packageScope = index.lookupPackage(packagename);
+ if (packageScope != null) {
+ result = result == null ? CompoundScope.base(packageScope) : result.append(packageScope);
+ }
+ }
+ return result;
+ }
+}
diff --git a/java/com/google/turbine/binder/lookup/ImportIndex.java b/java/com/google/turbine/binder/lookup/ImportIndex.java
index 48f9cc0..305bbfd 100644
--- a/java/com/google/turbine/binder/lookup/ImportIndex.java
+++ b/java/com/google/turbine/binder/lookup/ImportIndex.java
@@ -100,7 +100,7 @@
/** Fully resolve the canonical name of a non-static named import. */
private static ImportScope namedImport(
SourceFile source, TopLevelIndex cpi, ImportDecl i, CanonicalSymbolResolver resolve) {
- LookupResult result = cpi.lookup(new LookupKey(i.type()));
+ LookupResult result = cpi.scope().lookup(new LookupKey(i.type()));
if (result == null) {
throw TurbineError.format(
source, i.position(), ErrorKind.SYMBOL_NOT_FOUND, Joiner.on('.').join(i.type()));
@@ -122,7 +122,7 @@
* defer the rest.
*/
private static ImportScope staticNamedImport(SourceFile source, TopLevelIndex cpi, ImportDecl i) {
- LookupResult base = cpi.lookup(new LookupKey(i.type()));
+ LookupResult base = cpi.scope().lookup(new LookupKey(i.type()));
if (base == null) {
return null;
}
diff --git a/java/com/google/turbine/binder/lookup/MemberImportIndex.java b/java/com/google/turbine/binder/lookup/MemberImportIndex.java
index ed1a9bd..e2ce5cb 100644
--- a/java/com/google/turbine/binder/lookup/MemberImportIndex.java
+++ b/java/com/google/turbine/binder/lookup/MemberImportIndex.java
@@ -52,7 +52,7 @@
new Supplier<ClassSymbol>() {
@Override
public ClassSymbol get() {
- LookupResult result = tli.lookup(new LookupKey(i.type()));
+ LookupResult result = tli.scope().lookup(new LookupKey(i.type()));
return result != null ? resolve.resolve(source, i.position(), result) : null;
}
}));
@@ -63,13 +63,13 @@
new Supplier<ClassSymbol>() {
@Override
public ClassSymbol get() {
- LookupResult result1 = tli.lookup(new LookupKey(i.type()));
- if (result1 == null) {
+ LookupResult result = tli.scope().lookup(new LookupKey(i.type()));
+ if (result == null) {
return null;
}
- ClassSymbol sym = (ClassSymbol) result1.sym();
- for (int i = 0; i < result1.remaining().size() - 1; i++) {
- sym = resolve.resolveOne(sym, result1.remaining().get(i));
+ ClassSymbol sym = (ClassSymbol) result.sym();
+ for (int i = 0; i < result.remaining().size() - 1; i++) {
+ sym = resolve.resolveOne(sym, result.remaining().get(i));
}
return sym;
}
diff --git a/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java b/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java
new file mode 100644
index 0000000..403c53f
--- /dev/null
+++ b/java/com/google/turbine/binder/lookup/SimpleTopLevelIndex.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2018 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.lookup;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.turbine.binder.sym.ClassSymbol;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * An index of canonical type names where all members are known statically.
+ *
+ * <p>Qualified names are represented internally as a tree, where each package name part or class
+ * name is a node.
+ */
+public class SimpleTopLevelIndex implements TopLevelIndex {
+
+ /** A class symbol or package. */
+ public static class Node {
+
+ public Node lookup(String bit) {
+ return children.get(bit);
+ }
+
+ @Nullable private final ClassSymbol sym;
+
+ // TODO(cushon): the set of children is typically going to be small, consider optimizing this
+ // to use a denser representation where appropriate.
+ private final Map<String, Node> children = new HashMap<>();
+
+ Node(ClassSymbol sym) {
+ this.sym = sym;
+ }
+
+ /**
+ * Add a child with the given simple name. The given symbol will be null if a package is being
+ * inserted.
+ *
+ * @return {@code null} if an existing symbol with the same name has already been inserted.
+ */
+ private Node insert(String name, ClassSymbol sym) {
+ Node child;
+ if (children.containsKey(name)) {
+ child = children.get(name);
+ if (child.sym != null) {
+ return null;
+ }
+ } else {
+ child = new Node(sym);
+ children.put(name, child);
+ }
+ return child;
+ }
+ }
+
+ /** A builder for {@link TopLevelIndex}es. */
+ public static class Builder {
+
+ public TopLevelIndex build() {
+ // Freeze the index. The immutability of nodes is enforced by making insert private, doing
+ // a deep copy here isn't necessary.
+ return new SimpleTopLevelIndex(root);
+ }
+
+ /** The root of the lookup tree, effectively the package node of the default package. */
+ final Node root = new Node(null);
+
+ /** Inserts a {@link ClassSymbol} into the index, creating any needed packages. */
+ public boolean insert(ClassSymbol sym) {
+ Iterator<String> it = Splitter.on('/').split(sym.toString()).iterator();
+ Node curr = root;
+ while (it.hasNext()) {
+ String simpleName = it.next();
+ // if this is the last simple name in the qualified name of the top-level class being
+ // inserted, we are creating a node for the class symbol
+ ClassSymbol nodeSym = it.hasNext() ? null : sym;
+ curr = curr.insert(simpleName, nodeSym);
+ // If we've already inserted something with the current name (either a package or another
+ // symbol), bail out. When inserting elements from the classpath, this results in the
+ // expected first-match-wins semantics.
+ if (curr == null || !Objects.equals(curr.sym, nodeSym)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /** Returns a builder for {@link TopLevelIndex}es. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** Creates an index over the given symbols. */
+ public static TopLevelIndex of(Iterable<ClassSymbol> syms) {
+ Builder builder = builder();
+ for (ClassSymbol sym : syms) {
+ builder.insert(sym);
+ }
+ return builder.build();
+ }
+
+ private SimpleTopLevelIndex(Node root) {
+ this.root = root;
+ }
+
+ final Node root;
+
+ /** Looks up top-level qualified type names. */
+ final Scope scope =
+ new Scope() {
+ @Override
+ @Nullable
+ public LookupResult lookup(LookupKey lookupKey) {
+ Node curr = root;
+ while (true) {
+ curr = curr.lookup(lookupKey.first());
+ if (curr == null) {
+ return null;
+ }
+ if (curr.sym != null) {
+ return new LookupResult(curr.sym, lookupKey);
+ }
+ if (!lookupKey.hasNext()) {
+ return null;
+ }
+ lookupKey = lookupKey.rest();
+ }
+ }
+ };
+
+ @Override
+ public Scope scope() {
+ return scope;
+ }
+
+ /** Returns a {@link Scope} that performs lookups in the given qualified package name. */
+ @Override
+ public Scope lookupPackage(ImmutableList<String> packagename) {
+ Node curr = root;
+ for (String bit : packagename) {
+ curr = curr.lookup(bit);
+ if (curr == null || curr.sym != null) {
+ return null;
+ }
+ }
+ return new PackageIndex(curr);
+ }
+
+ static class PackageIndex implements Scope {
+
+ private final Node node;
+
+ public PackageIndex(Node node) {
+ this.node = node;
+ }
+
+ @Override
+ public LookupResult lookup(LookupKey lookupKey) {
+ Node result = node.lookup(lookupKey.first());
+ if (result != null && result.sym != null) {
+ return new LookupResult(result.sym, lookupKey);
+ }
+ return null;
+ }
+ }
+}
diff --git a/java/com/google/turbine/binder/lookup/TopLevelIndex.java b/java/com/google/turbine/binder/lookup/TopLevelIndex.java
index 2784e4c..95782f5 100644
--- a/java/com/google/turbine/binder/lookup/TopLevelIndex.java
+++ b/java/com/google/turbine/binder/lookup/TopLevelIndex.java
@@ -16,14 +16,7 @@
package com.google.turbine.binder.lookup;
-import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
-import com.google.turbine.binder.sym.ClassSymbol;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Objects;
-import javax.annotation.Nullable;
/**
* An index of canonical type names.
@@ -36,141 +29,12 @@
* are resolved separately with appropriate handling of non-canonical names. For bytecode we may end
* up storing desugared nested classes (e.g. {@code Map$Entry}), but we can't tell until the class
* file has been read and we have access to the InnerClasses attribtue.
- *
- * <p>Qualified names are represented internally as a tree, where each package name part or class
- * name is a node.
*/
-public class TopLevelIndex implements Scope {
+public interface TopLevelIndex {
- /** A class symbol or package. */
- public static class Node {
+ /** Returns a scope to look up top-level qualified type names. */
+ Scope scope();
- public Node lookup(String bit) {
- return children.get(bit);
- }
-
- @Nullable private final ClassSymbol sym;
-
- // TODO(cushon): the set of children is typically going to be small, consider optimizing this
- // to use a denser representation where appropriate.
- private final Map<String, Node> children = new HashMap<>();
-
- Node(ClassSymbol sym) {
- this.sym = sym;
- }
-
- /**
- * Add a child with the given simple name. The given symbol will be null if a package is being
- * inserted.
- *
- * @return {@code null} if an existing symbol with the same name has already been inserted.
- */
- private Node insert(String name, ClassSymbol sym) {
- Node child;
- if (children.containsKey(name)) {
- child = children.get(name);
- if (child.sym != null) {
- return null;
- }
- } else {
- child = new Node(sym);
- children.put(name, child);
- }
- return child;
- }
- }
-
- /** A builder for {@link TopLevelIndex}es. */
- public static class Builder {
-
- public TopLevelIndex build() {
- // Freeze the index. The immutability of nodes is enforced by making insert private, doing
- // a deep copy here isn't necessary.
- return new TopLevelIndex(root);
- }
-
- /** The root of the lookup tree, effectively the package node of the default package. */
- final Node root = new Node(null);
-
- /** Inserts a {@link ClassSymbol} into the index, creating any needed packages. */
- public boolean insert(ClassSymbol sym) {
- Iterator<String> it = Splitter.on('/').split(sym.toString()).iterator();
- Node curr = root;
- while (it.hasNext()) {
- String simpleName = it.next();
- // if this is the last simple name in the qualified name of the top-level class being
- // inserted, we are creating a node for the class symbol
- ClassSymbol nodeSym = it.hasNext() ? null : sym;
- curr = curr.insert(simpleName, nodeSym);
- // If we've already inserted something with the current name (either a package or another
- // symbol), bail out. When inserting elements from the classpath, this results in the
- // expected first-match-wins semantics.
- if (curr == null || !Objects.equals(curr.sym, nodeSym)) {
- return false;
- }
- }
- return true;
- }
- }
-
- /** Returns a builder for {@link TopLevelIndex}es. */
- public static Builder builder() {
- return new Builder();
- }
-
- private TopLevelIndex(Node root) {
- this.root = root;
- }
-
- final Node root;
-
- /** Looks up top-level qualified type names. */
- @Override
- @Nullable
- public LookupResult lookup(LookupKey lookupKey) {
- Node curr = root;
- while (true) {
- curr = curr.lookup(lookupKey.first());
- if (curr == null) {
- return null;
- }
- if (curr.sym != null) {
- return new LookupResult(curr.sym, lookupKey);
- }
- if (!lookupKey.hasNext()) {
- return null;
- }
- lookupKey = lookupKey.rest();
- }
- }
-
- /** Returns a {@link Scope} that performs lookups in the given qualified package name. */
- public Scope lookupPackage(ImmutableList<String> packagename) {
- Node curr = root;
- for (String bit : packagename) {
- curr = curr.lookup(bit);
- if (curr == null || curr.sym != null) {
- return null;
- }
- }
- return new PackageIndex(curr);
- }
-
- static class PackageIndex implements Scope {
-
- private final Node node;
-
- public PackageIndex(Node node) {
- this.node = node;
- }
-
- @Override
- public LookupResult lookup(LookupKey lookupKey) {
- Node result = node.children.get(lookupKey.first());
- if (result != null && result.sym != null) {
- return new LookupResult(result.sym, lookupKey);
- }
- return null;
- }
- }
+ /** Returns a scope to look up members of the given package. */
+ Scope lookupPackage(ImmutableList<String> packagename);
}
diff --git a/java/com/google/turbine/binder/lookup/WildImportIndex.java b/java/com/google/turbine/binder/lookup/WildImportIndex.java
index 85019aa..21e995d 100644
--- a/java/com/google/turbine/binder/lookup/WildImportIndex.java
+++ b/java/com/google/turbine/binder/lookup/WildImportIndex.java
@@ -79,7 +79,7 @@
}
};
}
- LookupResult result = cpi.lookup(new LookupKey(i.type()));
+ LookupResult result = cpi.scope().lookup(new LookupKey(i.type()));
if (result == null) {
return null;
}
@@ -104,7 +104,7 @@
TopLevelIndex cpi,
ImportDecl i,
final CanonicalSymbolResolver importResolver) {
- LookupResult result = cpi.lookup(new LookupKey(i.type()));
+ LookupResult result = cpi.scope().lookup(new LookupKey(i.type()));
if (result == null) {
return null;
}
diff --git a/java/com/google/turbine/binder/sym/ModuleSymbol.java b/java/com/google/turbine/binder/sym/ModuleSymbol.java
new file mode 100644
index 0000000..e442353
--- /dev/null
+++ b/java/com/google/turbine/binder/sym/ModuleSymbol.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 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.sym;
+
+import com.google.errorprone.annotations.Immutable;
+
+/** A module symbol. */
+@Immutable
+public class ModuleSymbol implements Symbol {
+
+ private final String name;
+
+ public ModuleSymbol(String name) {
+ this.name = name;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public Kind symKind() {
+ return Kind.MODULE;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof ModuleSymbol && name.equals(((ModuleSymbol) other).name);
+ }
+
+ public static final ModuleSymbol JAVA_BASE = new ModuleSymbol("java.base");
+}
diff --git a/java/com/google/turbine/binder/sym/Symbol.java b/java/com/google/turbine/binder/sym/Symbol.java
index 51b5fe3..b2a7723 100644
--- a/java/com/google/turbine/binder/sym/Symbol.java
+++ b/java/com/google/turbine/binder/sym/Symbol.java
@@ -26,7 +26,8 @@
CLASS,
TY_PARAM,
METHOD,
- FIELD
+ FIELD,
+ MODULE
}
/** The symbol kind. */
diff --git a/java/com/google/turbine/bytecode/Attribute.java b/java/com/google/turbine/bytecode/Attribute.java
index 0700744..29efb60 100644
--- a/java/com/google/turbine/bytecode/Attribute.java
+++ b/java/com/google/turbine/bytecode/Attribute.java
@@ -19,6 +19,7 @@
import com.google.common.collect.ImmutableList;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo;
import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo;
import com.google.turbine.model.Const.Value;
import java.util.List;
@@ -39,7 +40,8 @@
DEPRECATED("Deprecated"),
RUNTIME_VISIBLE_TYPE_ANNOTATIONS("RuntimeVisibleTypeAnnotations"),
RUNTIME_INVISIBLE_TYPE_ANNOTATIONS("RuntimeInvisibleTypeAnnotations"),
- METHOD_PARAMETERS("MethodParameters");
+ METHOD_PARAMETERS("MethodParameters"),
+ MODULE("Module");
private final String signature;
@@ -288,4 +290,23 @@
return Kind.METHOD_PARAMETERS;
}
}
+
+ /** A JVMS §4.7.25 Module attribute. */
+ class Module implements Attribute {
+
+ private final ModuleInfo module;
+
+ public Module(ModuleInfo module) {
+ this.module = module;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.MODULE;
+ }
+
+ public ModuleInfo module() {
+ return module;
+ }
+ }
}
diff --git a/java/com/google/turbine/bytecode/AttributeWriter.java b/java/com/google/turbine/bytecode/AttributeWriter.java
index 4eece56..5e3ea95 100644
--- a/java/com/google/turbine/bytecode/AttributeWriter.java
+++ b/java/com/google/turbine/bytecode/AttributeWriter.java
@@ -27,6 +27,12 @@
import com.google.turbine.bytecode.Attribute.TypeAnnotations;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo;
import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.ExportInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.OpenInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.ProvideInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.RequireInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.UseInfo;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo;
import com.google.turbine.model.Const;
import java.util.List;
@@ -78,6 +84,9 @@
case METHOD_PARAMETERS:
writeMethodParameters((Attribute.MethodParameters) attribute);
break;
+ case MODULE:
+ writeModule((Attribute.Module) attribute);
+ break;
default:
throw new AssertionError(attribute.kind());
}
@@ -203,4 +212,60 @@
output.writeShort(parameter.access());
}
}
+
+ private void writeModule(Attribute.Module attribute) {
+ ModuleInfo module = attribute.module();
+
+ ByteArrayDataOutput tmp = ByteStreams.newDataOutput();
+
+ tmp.writeShort(pool.moduleInfo(module.name()));
+ tmp.writeShort(module.flags());
+ tmp.writeShort(module.version() != null ? pool.utf8(module.version()) : 0);
+
+ tmp.writeShort(module.requires().size());
+ for (RequireInfo require : module.requires()) {
+ tmp.writeShort(pool.moduleInfo(require.moduleName()));
+ tmp.writeShort(require.flags());
+ tmp.writeShort(require.version() != null ? pool.utf8(require.version()) : 0);
+ }
+
+ tmp.writeShort(module.exports().size());
+ for (ExportInfo export : module.exports()) {
+ tmp.writeShort(pool.packageInfo(export.moduleName()));
+ tmp.writeShort(export.flags());
+ tmp.writeShort(export.modules().size());
+ for (String exportedModule : export.modules()) {
+ tmp.writeShort(pool.moduleInfo(exportedModule));
+ }
+ }
+
+ tmp.writeShort(module.opens().size());
+ for (OpenInfo opens : module.opens()) {
+ tmp.writeShort(pool.packageInfo(opens.moduleName()));
+ tmp.writeShort(opens.flags());
+ tmp.writeShort(opens.modules().size());
+ for (String openModule : opens.modules()) {
+ tmp.writeShort(pool.moduleInfo(openModule));
+ }
+ }
+
+ tmp.writeShort(module.uses().size());
+ for (UseInfo use : module.uses()) {
+ tmp.writeShort(pool.classInfo(use.descriptor()));
+ }
+
+ tmp.writeShort(module.provides().size());
+ for (ProvideInfo provide : module.provides()) {
+ tmp.writeShort(pool.classInfo(provide.descriptor()));
+ tmp.writeShort(provide.implDescriptors().size());
+ for (String impl : provide.implDescriptors()) {
+ tmp.writeShort(pool.classInfo(impl));
+ }
+ }
+
+ byte[] data = tmp.toByteArray();
+ output.writeShort(pool.utf8(attribute.kind().signature()));
+ output.writeInt(data.length);
+ output.write(data);
+ }
}
diff --git a/java/com/google/turbine/bytecode/ClassFile.java b/java/com/google/turbine/bytecode/ClassFile.java
index 2761fec..502e295 100644
--- a/java/com/google/turbine/bytecode/ClassFile.java
+++ b/java/com/google/turbine/bytecode/ClassFile.java
@@ -41,6 +41,7 @@
private final List<AnnotationInfo> annotations;
private final List<InnerClass> innerClasses;
private final ImmutableList<TypeAnnotationInfo> typeAnnotations;
+ @Nullable private final ModuleInfo module;
public ClassFile(
int access,
@@ -52,7 +53,8 @@
List<FieldInfo> fields,
List<AnnotationInfo> annotations,
List<InnerClass> innerClasses,
- ImmutableList<TypeAnnotationInfo> typeAnnotations) {
+ ImmutableList<TypeAnnotationInfo> typeAnnotations,
+ @Nullable ModuleInfo module) {
this.access = access;
this.name = name;
this.signature = signature;
@@ -63,6 +65,7 @@
this.annotations = annotations;
this.innerClasses = innerClasses;
this.typeAnnotations = typeAnnotations;
+ this.module = module;
}
/** Class access and property flags. */
@@ -115,6 +118,12 @@
return typeAnnotations;
}
+ /** A module attribute. */
+ @Nullable
+ public ModuleInfo module() {
+ return module;
+ }
+
/** The contents of a JVMS §4.5 field_info structure. */
public static class FieldInfo {
@@ -749,4 +758,180 @@
}
}
}
+
+ /** A JVMS 4.7.25 module attribute. */
+ public static class ModuleInfo {
+
+ private final String name;
+ private final String version;
+ private final int flags;
+ private final ImmutableList<RequireInfo> requires;
+ private final ImmutableList<ExportInfo> exports;
+ private final ImmutableList<OpenInfo> opens;
+ private final ImmutableList<UseInfo> uses;
+ private final ImmutableList<ProvideInfo> provides;
+
+ public ModuleInfo(
+ String name,
+ int flags,
+ String version,
+ ImmutableList<RequireInfo> requires,
+ ImmutableList<ExportInfo> exports,
+ ImmutableList<OpenInfo> opens,
+ ImmutableList<UseInfo> uses,
+ ImmutableList<ProvideInfo> provides) {
+ this.name = name;
+ this.flags = flags;
+ this.version = version;
+ this.requires = requires;
+ this.exports = exports;
+ this.opens = opens;
+ this.uses = uses;
+ this.provides = provides;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public int flags() {
+ return flags;
+ }
+
+ public String version() {
+ return version;
+ }
+
+ public ImmutableList<RequireInfo> requires() {
+ return requires;
+ }
+
+ public ImmutableList<ExportInfo> exports() {
+ return exports;
+ }
+
+ public ImmutableList<OpenInfo> opens() {
+ return opens;
+ }
+
+ public ImmutableList<UseInfo> uses() {
+ return uses;
+ }
+
+ public ImmutableList<ProvideInfo> provides() {
+ return provides;
+ }
+
+ /** A JVMS 4.7.25 module requires directive. */
+ public static class RequireInfo {
+
+ private final String moduleName;
+ private final int flags;
+ private final String version;
+
+ public RequireInfo(String moduleName, int flags, String version) {
+ this.moduleName = moduleName;
+ this.flags = flags;
+ this.version = version;
+ }
+
+ public String moduleName() {
+ return moduleName;
+ }
+
+ public int flags() {
+ return flags;
+ }
+
+ public String version() {
+ return version;
+ }
+ }
+
+ /** A JVMS 4.7.25 module exports directive. */
+ public static class ExportInfo {
+
+ private final String moduleName;
+ private final int flags;
+ private final ImmutableList<String> modules;
+
+ public ExportInfo(String moduleName, int flags, ImmutableList<String> modules) {
+ this.moduleName = moduleName;
+ this.flags = flags;
+ this.modules = modules;
+ }
+
+ public String moduleName() {
+ return moduleName;
+ }
+
+ public int flags() {
+ return flags;
+ }
+
+ public ImmutableList<String> modules() {
+ return modules;
+ }
+ }
+
+ /** A JVMS 4.7.25 module opens directive. */
+ public static class OpenInfo {
+
+ private final String moduleName;
+ private final int flags;
+ private final ImmutableList<String> modules;
+
+ public OpenInfo(String moduleName, int flags, ImmutableList<String> modules) {
+ this.moduleName = moduleName;
+ this.flags = flags;
+ this.modules = modules;
+ }
+
+ public String moduleName() {
+ return moduleName;
+ }
+
+ public int flags() {
+ return flags;
+ }
+
+ public ImmutableList<String> modules() {
+ return modules;
+ }
+ }
+
+ /** A JVMS 4.7.25 module uses directive. */
+ public static class UseInfo {
+
+ private final String descriptor;
+
+ public UseInfo(String descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ public String descriptor() {
+ return descriptor;
+ }
+ }
+
+ /** A JVMS 4.7.25 module provides directive. */
+ public static class ProvideInfo {
+
+ private final String descriptor;
+ private final ImmutableList<String> implDescriptors;
+
+ public ProvideInfo(String descriptor, ImmutableList<String> implDescriptors) {
+ this.descriptor = descriptor;
+ this.implDescriptors = implDescriptors;
+ }
+
+ public String descriptor() {
+ return descriptor;
+ }
+
+ public ImmutableList<String> implDescriptors() {
+ return implDescriptors;
+ }
+ }
+ }
}
diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java
index 96ad454..c8b4734 100644
--- a/java/com/google/turbine/bytecode/ClassReader.java
+++ b/java/com/google/turbine/bytecode/ClassReader.java
@@ -21,6 +21,12 @@
import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstClassValue;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.EnumConstValue;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.ExportInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.OpenInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.ProvideInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.RequireInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.UseInfo;
import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineFlag;
import java.util.ArrayList;
@@ -94,6 +100,7 @@
String signature = null;
List<ClassFile.InnerClass> innerclasses = Collections.emptyList();
List<ClassFile.AnnotationInfo> annotations = Collections.emptyList();
+ ClassFile.ModuleInfo module = null;
int attributesCount = reader.u2();
for (int j = 0; j < attributesCount; j++) {
int attributeNameIndex = reader.u2();
@@ -108,6 +115,9 @@
case "InnerClasses":
innerclasses = readInnerClasses(constantPool, thisClass);
break;
+ case "Module":
+ module = readModule(constantPool);
+ break;
default:
reader.skip(reader.u4());
break;
@@ -124,7 +134,8 @@
fieldinfos,
annotations,
innerclasses,
- ImmutableList.of());
+ ImmutableList.of(),
+ module);
}
/** Reads a JVMS 4.7.9 Signature attribute. */
@@ -182,6 +193,84 @@
return annotations;
}
+ /** Processes a JVMS 4.7.25 Module attribute. */
+ private ModuleInfo readModule(ConstantPoolReader constantPool) {
+ reader.u4(); // length
+ String name = constantPool.moduleInfo(reader.u2());
+ int flags = reader.u2();
+ int versionIndex = reader.u2();
+ String version = (versionIndex != 0) ? constantPool.utf8(versionIndex) : null;
+
+ ImmutableList.Builder<ClassFile.ModuleInfo.RequireInfo> requires = ImmutableList.builder();
+ int numRequires = reader.u2();
+ for (int i = 0; i < numRequires; i++) {
+ String requiresModule = constantPool.moduleInfo(reader.u2());
+ int requiresFlags = reader.u2();
+ int requiresVersionIndex = reader.u2();
+ String requiresVersion =
+ (requiresVersionIndex != 0) ? constantPool.utf8(requiresVersionIndex) : null;
+ requires.add(new RequireInfo(requiresModule, requiresFlags, requiresVersion));
+ }
+
+ ImmutableList.Builder<ClassFile.ModuleInfo.ExportInfo> exports = ImmutableList.builder();
+ int numExports = reader.u2();
+ for (int i = 0; i < numExports; i++) {
+ String exportsModule = constantPool.packageInfo(reader.u2());
+ int exportsFlags = reader.u2();
+ int numExportsTo = reader.u2();
+ ImmutableList.Builder<String> exportsToModules = ImmutableList.builder();
+ for (int n = 0; n < numExportsTo; n++) {
+ String exportsToModule = constantPool.moduleInfo(reader.u2());
+ exportsToModules.add(exportsToModule);
+ }
+ exports.add(new ExportInfo(exportsModule, exportsFlags, exportsToModules.build()));
+ }
+
+ ImmutableList.Builder<ClassFile.ModuleInfo.OpenInfo> opens = ImmutableList.builder();
+ int numOpens = reader.u2();
+ for (int i = 0; i < numOpens; i++) {
+ String opensModule = constantPool.packageInfo(reader.u2());
+ int opensFlags = reader.u2();
+ int numOpensTo = reader.u2();
+ ImmutableList.Builder<String> opensToModules = ImmutableList.builder();
+ for (int n = 0; n < numOpensTo; n++) {
+ String opensToModule = constantPool.moduleInfo(reader.u2());
+ opensToModules.add(opensToModule);
+ }
+ opens.add(new OpenInfo(opensModule, opensFlags, opensToModules.build()));
+ }
+
+ ImmutableList.Builder<ClassFile.ModuleInfo.UseInfo> uses = ImmutableList.builder();
+ int numUses = reader.u2();
+ for (int i = 0; i < numUses; i++) {
+ String use = constantPool.classInfo(reader.u2());
+ uses.add(new UseInfo(use));
+ }
+
+ ImmutableList.Builder<ClassFile.ModuleInfo.ProvideInfo> provides = ImmutableList.builder();
+ int numProvides = reader.u2();
+ for (int i = 0; i < numProvides; i++) {
+ String typeName = constantPool.classInfo(reader.u2());
+ int numProvidesWith = reader.u2();
+ ImmutableList.Builder<String> impls = ImmutableList.builder();
+ for (int n = 0; n < numProvidesWith; n++) {
+ String impl = constantPool.classInfo(reader.u2());
+ impls.add(impl);
+ }
+ provides.add(new ProvideInfo(typeName, impls.build()));
+ }
+
+ return new ClassFile.ModuleInfo(
+ name,
+ flags,
+ version,
+ requires.build(),
+ exports.build(),
+ opens.build(),
+ uses.build(),
+ provides.build());
+ }
+
/**
* Extracts an {@link @Retention} or {@link ElementType} {@link ClassFile.AnnotationInfo}, or else
* skips over the annotation.
diff --git a/java/com/google/turbine/bytecode/ClassWriter.java b/java/com/google/turbine/bytecode/ClassWriter.java
index 42aff6c..4a89ec8 100644
--- a/java/com/google/turbine/bytecode/ClassWriter.java
+++ b/java/com/google/turbine/bytecode/ClassWriter.java
@@ -31,8 +31,10 @@
private static final int MAGIC = 0xcafebabe;
private static final int MINOR_VERSION = 0;
- // TODO(cushon): configuration?
+ // use the lowest classfile version possible given the class file features
+ // TODO(cushon): is there a reason to support --release?
private static final int MAJOR_VERSION = 52;
+ private static final int MODULE_MAJOR_VERSION = 53;
/** Writes a {@link ClassFile} to bytecode. */
public static byte[] writeClass(ClassFile classfile) {
@@ -54,7 +56,7 @@
writeMethod(pool, output, m);
}
writeAttributes(pool, output, LowerAttributes.classAttributes(classfile));
- return finishClass(pool, output);
+ return finishClass(pool, output, classfile);
}
private static void writeMethod(
@@ -89,6 +91,8 @@
switch (e.kind()) {
case CLASS_INFO:
case STRING:
+ case MODULE:
+ case PACKAGE:
output.writeShort(((IntValue) value).value());
break;
case INTEGER:
@@ -112,11 +116,12 @@
}
}
- private static byte[] finishClass(ConstantPool pool, ByteArrayDataOutput body) {
+ private static byte[] finishClass(
+ ConstantPool pool, ByteArrayDataOutput body, ClassFile classfile) {
ByteArrayDataOutput result = ByteStreams.newDataOutput();
result.writeInt(MAGIC);
result.writeShort(MINOR_VERSION);
- result.writeShort(MAJOR_VERSION);
+ result.writeShort(classfile.module() != null ? MODULE_MAJOR_VERSION : MAJOR_VERSION);
writeConstantPool(pool, result);
result.write(body.toByteArray());
return result.toByteArray();
diff --git a/java/com/google/turbine/bytecode/ConstantPool.java b/java/com/google/turbine/bytecode/ConstantPool.java
index 2f3141a..b423cfc 100644
--- a/java/com/google/turbine/bytecode/ConstantPool.java
+++ b/java/com/google/turbine/bytecode/ConstantPool.java
@@ -40,6 +40,8 @@
private final Map<Double, Integer> doublePool = new HashMap<>();
private final Map<Float, Integer> floatPool = new HashMap<>();
private final Map<Long, Integer> longPool = new HashMap<>();
+ private final Map<Integer, Integer> modulePool = new HashMap<>();
+ private final Map<Integer, Integer> packagePool = new HashMap<>();
private final List<Entry> constants = new ArrayList<>();
@@ -56,6 +58,8 @@
case INTEGER:
case UTF8:
case FLOAT:
+ case MODULE:
+ case PACKAGE:
return 1;
case LONG:
case DOUBLE:
@@ -158,6 +162,30 @@
return index;
}
+ /** Adds a CONSTANT_Module_info entry to the pool. */
+ int moduleInfo(String value) {
+ Objects.requireNonNull(value);
+ int utf8 = utf8(value);
+ if (modulePool.containsKey(utf8)) {
+ return modulePool.get(utf8);
+ }
+ int index = insert(new Entry(Kind.MODULE, new IntValue(utf8)));
+ modulePool.put(utf8, index);
+ return index;
+ }
+
+ /** Adds a CONSTANT_Package_info entry to the pool. */
+ int packageInfo(String value) {
+ Objects.requireNonNull(value);
+ int utf8 = utf8(value);
+ if (packagePool.containsKey(utf8)) {
+ return packagePool.get(utf8);
+ }
+ int index = insert(new Entry(Kind.PACKAGE, new IntValue(utf8)));
+ packagePool.put(utf8, index);
+ return index;
+ }
+
private int insert(Entry key) {
int entry = nextEntry;
constants.add(key);
@@ -176,7 +204,9 @@
DOUBLE(6),
FLOAT(4),
LONG(5),
- UTF8(1);
+ UTF8(1),
+ MODULE(19),
+ PACKAGE(20);
private final short tag;
diff --git a/java/com/google/turbine/bytecode/ConstantPoolReader.java b/java/com/google/turbine/bytecode/ConstantPoolReader.java
index cea034e..b6a2091 100644
--- a/java/com/google/turbine/bytecode/ConstantPoolReader.java
+++ b/java/com/google/turbine/bytecode/ConstantPoolReader.java
@@ -37,6 +37,8 @@
static final int CONSTANT_METHOD_HANDLE = 15;
static final int CONSTANT_METHOD_TYPE = 16;
static final int CONSTANT_INVOKE_DYNAMIC = 18;
+ static final int CONSTANT_MODULE = 19;
+ static final int CONSTANT_PACKAGE = 20;
/** A table that maps constant pool entries to byte offsets in {@link #byteReader}. */
private final int[] constantPool;
@@ -70,6 +72,8 @@
case CONSTANT_CLASS:
case CONSTANT_METHOD_TYPE:
case CONSTANT_STRING:
+ case CONSTANT_MODULE:
+ case CONSTANT_PACKAGE:
reader.skip(2);
return 1;
case CONSTANT_DOUBLE:
@@ -119,6 +123,28 @@
return reader.readUTF();
}
+ /** Reads the CONSTANT_Module_info at the given index. */
+ public String moduleInfo(int index) {
+ ByteArrayDataInput reader = byteReader.seek(constantPool[index - 1]);
+ byte tag = reader.readByte();
+ if (tag != CONSTANT_MODULE) {
+ throw new AssertionError(String.format("bad tag: %x", tag));
+ }
+ int nameIndex = reader.readUnsignedShort();
+ return utf8(nameIndex);
+ }
+
+ /** Reads the CONSTANT_Package_info at the given index. */
+ public String packageInfo(int index) {
+ ByteArrayDataInput reader = byteReader.seek(constantPool[index - 1]);
+ byte tag = reader.readByte();
+ if (tag != CONSTANT_PACKAGE) {
+ throw new AssertionError(String.format("bad tag: %x", tag));
+ }
+ int nameIndex = reader.readUnsignedShort();
+ return utf8(nameIndex);
+ }
+
/**
* Reads a constant value at the given index, which must be one of CONSTANT_String_info,
* CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info, or CONSTANT_Double_info.
diff --git a/java/com/google/turbine/bytecode/LowerAttributes.java b/java/com/google/turbine/bytecode/LowerAttributes.java
index 1752456..67ef2b4 100644
--- a/java/com/google/turbine/bytecode/LowerAttributes.java
+++ b/java/com/google/turbine/bytecode/LowerAttributes.java
@@ -42,6 +42,9 @@
if (classfile.signature() != null) {
attributes.add(new Signature(classfile.signature()));
}
+ if (classfile.module() != null) {
+ attributes.add(new Attribute.Module(classfile.module()));
+ }
return attributes;
}
diff --git a/java/com/google/turbine/deps/Dependencies.java b/java/com/google/turbine/deps/Dependencies.java
index a6ac05f..a3c654e 100644
--- a/java/com/google/turbine/deps/Dependencies.java
+++ b/java/com/google/turbine/deps/Dependencies.java
@@ -20,9 +20,9 @@
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.binder.ClassPath;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.CompoundEnv;
@@ -46,10 +46,7 @@
public class Dependencies {
/** Creates a jdeps proto for the current compilation. */
public static DepsProto.Dependencies collectDeps(
- Optional<String> targetLabel,
- ImmutableSet<String> bootClassPath,
- BindingResult bound,
- Lowered lowered) {
+ Optional<String> targetLabel, ClassPath bootclasspath, BindingResult bound, Lowered lowered) {
DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
Set<ClassSymbol> closure = superTypeClosure(bound, lowered);
addPackageInfos(closure, bound);
@@ -61,7 +58,7 @@
continue;
}
String jarFile = info.jarFile();
- if (bootClassPath.contains(jarFile)) {
+ if (bootclasspath.env().get(sym) != null) {
// bootclasspath deps are not tracked
continue;
}
@@ -133,13 +130,13 @@
*/
public static Collection<String> reduceClasspath(
ImmutableList<String> transitiveClasspath,
- ImmutableMap<String, String> directJarsToTargets,
+ ImmutableSet<String> directJars,
ImmutableList<String> depsArtifacts) {
- if (directJarsToTargets.isEmpty()) {
+ if (directJars.isEmpty()) {
// the compilation doesn't support strict deps (e.g. proto libraries)
return transitiveClasspath;
}
- Set<String> reduced = new HashSet<>(directJarsToTargets.keySet());
+ Set<String> reduced = new HashSet<>(directJars);
for (String path : depsArtifacts) {
DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
try (InputStream is = new BufferedInputStream(Files.newInputStream(Paths.get(path)))) {
diff --git a/java/com/google/turbine/deps/Transitive.java b/java/com/google/turbine/deps/Transitive.java
index 5023159..f9a29a1 100644
--- a/java/com/google/turbine/deps/Transitive.java
+++ b/java/com/google/turbine/deps/Transitive.java
@@ -19,8 +19,8 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.binder.ClassPath;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
import com.google.turbine.binder.env.CompoundEnv;
@@ -43,7 +43,7 @@
public class Transitive {
public static ImmutableMap<String, byte[]> collectDeps(
- ImmutableSet<String> bootClassPath, BindingResult bound) {
+ ClassPath bootClassPath, BindingResult bound) {
ImmutableMap.Builder<String, byte[]> transitive = ImmutableMap.builder();
for (ClassSymbol sym : superClosure(bound)) {
BytecodeBoundClass info = bound.classPathEnv().get(sym);
@@ -51,8 +51,7 @@
// the symbol wasn't loaded from the classpath
continue;
}
- String jarFile = info.jarFile();
- if (bootClassPath.contains(jarFile)) {
+ if (bootClassPath.env().get(sym) != null) {
// don't export symbols loaded from the bootclasspath
continue;
}
@@ -97,7 +96,8 @@
// well-known @interface meta-annotations (e.g. @Retention, etc.)
cf.annotations(),
innerClasses.build(),
- cf.typeAnnotations());
+ cf.typeAnnotations(),
+ /* module= */ null);
}
private static Set<ClassSymbol> superClosure(BindingResult bound) {
diff --git a/java/com/google/turbine/diag/TurbineError.java b/java/com/google/turbine/diag/TurbineError.java
index 22abd3e..b8d6b65 100644
--- a/java/com/google/turbine/diag/TurbineError.java
+++ b/java/com/google/turbine/diag/TurbineError.java
@@ -41,7 +41,8 @@
CYCLIC_HIERARCHY("cycle in class hierarchy: %s"),
NOT_AN_ANNOTATION("%s is not an annotation"),
NONREPEATABLE_ANNOTATION("%s is not @Repeatable"),
- DUPLICATE_DECLARATION("duplicate declaration of %s");
+ DUPLICATE_DECLARATION("duplicate declaration of %s"),
+ BAD_MODULE_INFO("unexpected declaration found in module-info");
private final String message;
diff --git a/java/com/google/turbine/lower/Lower.java b/java/com/google/turbine/lower/Lower.java
index 31079c4..d8b464b 100644
--- a/java/com/google/turbine/lower/Lower.java
+++ b/java/com/google/turbine/lower/Lower.java
@@ -16,6 +16,7 @@
package com.google.turbine.lower;
+import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.turbine.binder.DisambiguateTypeAnnotations.groupRepeated;
import com.google.common.base.Function;
@@ -26,6 +27,12 @@
import com.google.turbine.binder.bound.AnnotationValue;
import com.google.turbine.binder.bound.ClassValue;
import com.google.turbine.binder.bound.EnumConstantValue;
+import com.google.turbine.binder.bound.ModuleInfo;
+import com.google.turbine.binder.bound.ModuleInfo.ExportInfo;
+import com.google.turbine.binder.bound.ModuleInfo.OpenInfo;
+import com.google.turbine.binder.bound.ModuleInfo.ProvideInfo;
+import com.google.turbine.binder.bound.ModuleInfo.RequireInfo;
+import com.google.turbine.binder.bound.ModuleInfo.UseInfo;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
@@ -102,7 +109,8 @@
/** Lowers all given classes to bytecode. */
public static Lowered lowerAll(
ImmutableMap<ClassSymbol, SourceTypeBoundClass> units,
- CompoundEnv<ClassSymbol, BytecodeBoundClass> classpath) {
+ ImmutableList<ModuleInfo> modules,
+ Env<ClassSymbol, BytecodeBoundClass> classpath) {
CompoundEnv<ClassSymbol, TypeBoundClass> env =
CompoundEnv.<ClassSymbol, TypeBoundClass>of(classpath).append(new SimpleEnv<>(units));
ImmutableMap.Builder<String, byte[]> result = ImmutableMap.builder();
@@ -110,6 +118,16 @@
for (ClassSymbol sym : units.keySet()) {
result.put(sym.binaryName(), lower(units.get(sym), env, sym, symbols));
}
+ if (modules.size() == 1) {
+ // single module mode: the module-info.class file is at the root
+ result.put("module-info", lower(getOnlyElement(modules), env, symbols));
+ } else {
+ // multi-module mode: the output module-info.class are in a directory corresponding to their
+ // package
+ for (ModuleInfo module : modules) {
+ result.put(module.name().replace('.', '/') + "/module-info", lower(module, env, symbols));
+ }
+ }
return new Lowered(result.build(), ImmutableSet.copyOf(symbols));
}
@@ -122,6 +140,11 @@
return new Lower(env).lower(info, sym, symbols);
}
+ private static byte[] lower(
+ ModuleInfo module, CompoundEnv<ClassSymbol, TypeBoundClass> env, Set<ClassSymbol> symbols) {
+ return new Lower(env).lower(module, symbols);
+ }
+
private final LowerSignature sig = new LowerSignature();
private final Env<ClassSymbol, TypeBoundClass> env;
@@ -129,6 +152,82 @@
this.env = env;
}
+ private byte[] lower(ModuleInfo module, Set<ClassSymbol> symbols) {
+ String name = "module-info";
+ ImmutableList<AnnotationInfo> annotations = lowerAnnotations(module.annos());
+ ClassFile.ModuleInfo moduleInfo = lowerModule(module);
+
+ ImmutableList.Builder<ClassFile.InnerClass> innerClasses = ImmutableList.builder();
+ {
+ Set<ClassSymbol> all = new LinkedHashSet<>();
+ for (ClassSymbol sym : sig.classes) {
+ addEnclosing(env, all, sym);
+ }
+ for (ClassSymbol innerSym : all) {
+ innerClasses.add(innerClass(env, innerSym));
+ }
+ }
+
+ ClassFile classfile =
+ new ClassFile(
+ /* access= */ TurbineFlag.ACC_MODULE,
+ name,
+ /* signature= */ null,
+ /* superClass= */ null,
+ /* interfaces= */ ImmutableList.of(),
+ /* methods= */ ImmutableList.of(),
+ /* fields= */ ImmutableList.of(),
+ annotations,
+ innerClasses.build(),
+ /* typeAnnotations= */ ImmutableList.of(),
+ moduleInfo);
+ symbols.addAll(sig.classes);
+ return ClassWriter.writeClass(classfile);
+ }
+
+ private ClassFile.ModuleInfo lowerModule(ModuleInfo module) {
+ ImmutableList.Builder<ClassFile.ModuleInfo.RequireInfo> requires = ImmutableList.builder();
+ for (RequireInfo require : module.requires()) {
+ requires.add(
+ new ClassFile.ModuleInfo.RequireInfo(
+ require.moduleName(), require.flags(), require.version()));
+ }
+ ImmutableList.Builder<ClassFile.ModuleInfo.ExportInfo> exports = ImmutableList.builder();
+ for (ExportInfo export : module.exports()) {
+ int exportAccess = 0; // not synthetic or mandated
+ exports.add(
+ new ClassFile.ModuleInfo.ExportInfo(
+ export.packageName(), exportAccess, export.modules()));
+ }
+ ImmutableList.Builder<ClassFile.ModuleInfo.OpenInfo> opens = ImmutableList.builder();
+ for (OpenInfo open : module.opens()) {
+ int openAccess = 0; // not synthetic or mandated
+ opens.add(new ClassFile.ModuleInfo.OpenInfo(open.packageName(), openAccess, open.modules()));
+ }
+ ImmutableList.Builder<ClassFile.ModuleInfo.UseInfo> uses = ImmutableList.builder();
+ for (UseInfo use : module.uses()) {
+ uses.add(new ClassFile.ModuleInfo.UseInfo(sig.descriptor(use.sym())));
+ }
+ ImmutableList.Builder<ClassFile.ModuleInfo.ProvideInfo> provides = ImmutableList.builder();
+ for (ProvideInfo provide : module.provides()) {
+ ImmutableList.Builder<String> impls = ImmutableList.builder();
+ for (ClassSymbol impl : provide.impls()) {
+ impls.add(sig.descriptor(impl));
+ }
+ provides.add(
+ new ClassFile.ModuleInfo.ProvideInfo(sig.descriptor(provide.sym()), impls.build()));
+ }
+ return new ClassFile.ModuleInfo(
+ module.name(),
+ module.flags(),
+ module.version(),
+ requires.build(),
+ exports.build(),
+ opens.build(),
+ uses.build(),
+ provides.build());
+ }
+
private byte[] lower(SourceTypeBoundClass info, ClassSymbol sym, Set<ClassSymbol> symbols) {
int access = classAccess(info);
String name = sig.descriptor(sym);
@@ -174,7 +273,8 @@
fields.build(),
annotations,
inners,
- typeAnnotations);
+ typeAnnotations,
+ /* module= */ null);
symbols.addAll(sig.classes);
@@ -307,6 +407,10 @@
*/
private void addEnclosing(
Env<ClassSymbol, TypeBoundClass> env, Set<ClassSymbol> all, ClassSymbol sym) {
+ TypeBoundClass info = env.get(sym);
+ if (info == null) {
+ throw new AssertionError(sym);
+ }
ClassSymbol owner = env.get(sym).owner();
if (owner != null) {
addEnclosing(env, all, owner);
diff --git a/java/com/google/turbine/main/Main.java b/java/com/google/turbine/main/Main.java
index 0fa2f83..31a36c4 100644
--- a/java/com/google/turbine/main/Main.java
+++ b/java/com/google/turbine/main/Main.java
@@ -18,11 +18,15 @@
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import com.google.turbine.binder.Binder;
import com.google.turbine.binder.Binder.BindingResult;
+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.deps.Dependencies;
import com.google.turbine.deps.Transitive;
import com.google.turbine.diag.SourceFile;
@@ -35,16 +39,22 @@
import com.google.turbine.tree.Tree.CompUnit;
import com.google.turbine.zip.Zip;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
+import java.util.jar.Attributes;
import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
/** Main entry point for the turbine CLI. */
@@ -52,6 +62,13 @@
private static final int BUFFER_SIZE = 65536;
+ // These attributes are used by JavaBuilder, Turbine, and ijar.
+ // They must all be kept in sync.
+ static final String MANIFEST_DIR = "META-INF/";
+ static final String MANIFEST_NAME = JarFile.MANIFEST_NAME;
+ static final Attributes.Name TARGET_LABEL = new Attributes.Name("Target-Label");
+ static final Attributes.Name INJECTING_RULE_KIND = new Attributes.Name("Injecting-Rule-Kind");
+
public static void main(String[] args) throws IOException {
compile(args);
}
@@ -68,51 +85,75 @@
ImmutableList<CompUnit> units = parseAll(options);
+ ClassPath bootclasspath = bootclasspath(options);
+
Collection<String> reducedClasspath =
Dependencies.reduceClasspath(
- options.classPath(), options.directJarsToTargets(), options.depsArtifacts());
+ options.classPath(), options.directJars(), options.depsArtifacts());
+ ClassPath classpath = ClassPathBinder.bindClasspath(toPaths(reducedClasspath));
BindingResult bound =
- Binder.bind(units, toPaths(reducedClasspath), toPaths(options.bootClassPath()));
+ Binder.bind(units, classpath, bootclasspath, /* moduleVersion=*/ Optional.absent());
// TODO(cushon): parallelize
- Lowered lowered = Lower.lowerAll(bound.units(), bound.classPathEnv());
+ Lowered lowered = Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv());
- Map<String, byte[]> transitive = Transitive.collectDeps(options.bootClassPath(), bound);
+ Map<String, byte[]> transitive = Transitive.collectDeps(bootclasspath, bound);
if (options.outputDeps().isPresent()) {
DepsProto.Dependencies deps =
- Dependencies.collectDeps(options.targetLabel(), options.bootClassPath(), bound, lowered);
+ Dependencies.collectDeps(options.targetLabel(), bootclasspath, bound, lowered);
try (OutputStream os =
new BufferedOutputStream(Files.newOutputStream(Paths.get(options.outputDeps().get())))) {
deps.writeTo(os);
}
}
- writeOutput(Paths.get(options.outputFile()), lowered.bytes(), transitive);
+ writeOutput(options, lowered.bytes(), transitive);
return true;
}
+ private static ClassPath bootclasspath(TurbineOptions options) throws IOException {
+ // if both --release and --bootclasspath are specified, --release wins
+ if (options.release().isPresent() && options.system().isPresent()) {
+ throw new IllegalArgumentException("expected at most one of --release and --system");
+ }
+
+ if (options.release().isPresent()) {
+ String release = options.release().get();
+ if (release.equals(System.getProperty("java.specification.version"))) {
+ // if --release matches the host JDK, use its jimage instead of ct.sym
+ return JimageClassBinder.bindDefault();
+ }
+ // ... otherwise, search ct.sym for a matching release
+ ClassPath bootclasspath = CtSymClassBinder.bind(release);
+ if (bootclasspath == null) {
+ throw new IllegalArgumentException("not a supported release: " + release);
+ }
+ return bootclasspath;
+ }
+
+ if (options.system().isPresent()) {
+ // look for a jimage in the given JDK
+ return JimageClassBinder.bind(options.system().get());
+ }
+
+ // the bootclasspath might be empty, e.g. when compiling java.lang
+ return ClassPathBinder.bindClasspath(toPaths(options.bootClassPath()));
+ }
+
/** Parse all source files and source jars. */
// TODO(cushon): parallelize
private static ImmutableList<CompUnit> parseAll(TurbineOptions options) throws IOException {
ImmutableList.Builder<CompUnit> units = ImmutableList.builder();
for (String source : options.sources()) {
Path path = Paths.get(source);
- if (path.getFileName().toString().equals(MODULE_INFO_FILE_NAME)) {
- continue;
- }
units.add(Parser.parse(new SourceFile(source, new String(Files.readAllBytes(path), UTF_8))));
}
for (String sourceJar : options.sourceJars()) {
for (Zip.Entry ze : new Zip.ZipIterable(Paths.get(sourceJar))) {
if (ze.name().endsWith(".java")) {
String name = ze.name();
- int idx = name.lastIndexOf('/');
- String fileName = idx != -1 ? name.substring(idx + 1) : name;
- if (fileName.equals(MODULE_INFO_FILE_NAME)) {
- continue;
- }
String source = new String(ze.data(), UTF_8);
units.add(Parser.parse(new SourceFile(name, source)));
}
@@ -121,14 +162,11 @@
return units.build();
}
- // turbine currently ignores module-info.java files, because they are not needed for header
- // compilation.
- // TODO(b/36109466): understand requirements for full Java 9 source support (e.g. module paths)
- static final String MODULE_INFO_FILE_NAME = "module-info.java";
-
/** Write bytecode to the output jar. */
private static void writeOutput(
- Path path, Map<String, byte[]> lowered, Map<String, byte[]> transitive) throws IOException {
+ TurbineOptions options, Map<String, byte[]> lowered, Map<String, byte[]> transitive)
+ throws IOException {
+ Path path = Paths.get(options.outputFile());
try (OutputStream os = Files.newOutputStream(path);
BufferedOutputStream bos = new BufferedOutputStream(os, BUFFER_SIZE);
JarOutputStream jos = new JarOutputStream(bos)) {
@@ -139,12 +177,24 @@
addEntry(
jos, ClassPathBinder.TRANSITIVE_PREFIX + entry.getKey() + ".class", entry.getValue());
}
+ if (options.targetLabel().isPresent()) {
+ addEntry(jos, MANIFEST_DIR, new byte[] {});
+ addEntry(jos, MANIFEST_NAME, manifestContent(options));
+ }
}
}
+ /** Normalize timestamps. */
+ static final long DEFAULT_TIMESTAMP =
+ LocalDateTime.of(2010, 1, 1, 0, 0, 0)
+ .atZone(ZoneId.systemDefault())
+ .toInstant()
+ .toEpochMilli();
+
private static void addEntry(JarOutputStream jos, String name, byte[] bytes) throws IOException {
JarEntry je = new JarEntry(name);
- je.setTime(0L); // normalize timestamps to the DOS epoch
+ // TODO(cushon): switch to setLocalTime after we migrate to JDK 9
+ je.setTime(DEFAULT_TIMESTAMP);
je.setMethod(ZipEntry.STORED);
je.setSize(bytes.length);
je.setCrc(Hashing.crc32().hashBytes(bytes).padToLong());
@@ -152,6 +202,25 @@
jos.write(bytes);
}
+ private static byte[] manifestContent(TurbineOptions turbineOptions) throws IOException {
+ Manifest manifest = new Manifest();
+ Attributes attributes = manifest.getMainAttributes();
+ attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ Attributes.Name createdBy = new Attributes.Name("Created-By");
+ if (attributes.getValue(createdBy) == null) {
+ attributes.put(createdBy, "bazel");
+ }
+ if (turbineOptions.targetLabel().isPresent()) {
+ attributes.put(TARGET_LABEL, turbineOptions.targetLabel().get());
+ }
+ if (turbineOptions.injectingRuleKind().isPresent()) {
+ attributes.put(INJECTING_RULE_KIND, turbineOptions.injectingRuleKind().get());
+ }
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ manifest.write(out);
+ return out.toByteArray();
+ }
+
private static ImmutableList<Path> toPaths(Iterable<String> paths) {
ImmutableList.Builder<Path> result = ImmutableList.builder();
for (String path : paths) {
diff --git a/java/com/google/turbine/model/TurbineFlag.java b/java/com/google/turbine/model/TurbineFlag.java
index 18fc81e..48e88e7 100644
--- a/java/com/google/turbine/model/TurbineFlag.java
+++ b/java/com/google/turbine/model/TurbineFlag.java
@@ -29,7 +29,10 @@
public static final int ACC_STATIC = 0x0008;
public static final int ACC_FINAL = 0x0010;
public static final int ACC_SYNCHRONIZED = 0x0020;
+ public static final int ACC_OPEN = 0x0020;
public static final int ACC_SUPER = 0x0020;
+ public static final int ACC_TRANSITIVE = 0x0020;
+ public static final int ACC_STATIC_PHASE = 0x0040;
public static final int ACC_BRIDGE = 0x0040;
public static final int ACC_VOLATILE = 0x0040;
public static final int ACC_VARARGS = 0x0080;
@@ -41,6 +44,7 @@
public static final int ACC_SYNTHETIC = 0x1000;
public static final int ACC_ANNOTATION = 0x2000;
public static final int ACC_ENUM = 0x4000;
+ public static final int ACC_MODULE = 0x8000;
public static final int ACC_MANDATED = 0x8000;
// TODO(cushon): the rest of these aren't spec'd access bits, put them somewhere else?
diff --git a/java/com/google/turbine/model/TurbineVisibility.java b/java/com/google/turbine/model/TurbineVisibility.java
index ce901ee..4f250c7 100644
--- a/java/com/google/turbine/model/TurbineVisibility.java
+++ b/java/com/google/turbine/model/TurbineVisibility.java
@@ -39,9 +39,11 @@
return flag;
}
+ public static final int VISIBILITY_MASK =
+ TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_PRIVATE | TurbineFlag.ACC_PROTECTED;
+
public static TurbineVisibility fromAccess(int access) {
- switch (access
- & (TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_PRIVATE | TurbineFlag.ACC_PROTECTED)) {
+ switch (access & VISIBILITY_MASK) {
case TurbineFlag.ACC_PUBLIC:
return PUBLIC;
case TurbineFlag.ACC_PRIVATE:
diff --git a/java/com/google/turbine/options/TurbineOptions.java b/java/com/google/turbine/options/TurbineOptions.java
index ba9396f..20d81fe 100644
--- a/java/com/google/turbine/options/TurbineOptions.java
+++ b/java/com/google/turbine/options/TurbineOptions.java
@@ -20,7 +20,6 @@
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import javax.annotation.Nullable;
@@ -30,16 +29,17 @@
private final String output;
private final ImmutableList<String> classPath;
private final ImmutableSet<String> bootClassPath;
+ private final Optional<String> release;
+ private final Optional<String> system;
private final ImmutableList<String> sources;
private final ImmutableList<String> processorPath;
private final ImmutableSet<String> processors;
private final ImmutableList<String> sourceJars;
private final Optional<String> outputDeps;
- private final ImmutableMap<String, String> directJarsToTargets;
- private final ImmutableMap<String, String> indirectJarsToTargets;
+ private final ImmutableSet<String> directJars;
private final Optional<String> targetLabel;
+ private final Optional<String> injectingRuleKind;
private final ImmutableList<String> depsArtifacts;
- private final Optional<String> ruleKind;
private final boolean javacFallback;
private final ImmutableList<String> javacOpts;
private final boolean shouldReduceClassPath;
@@ -48,34 +48,34 @@
String output,
ImmutableList<String> classPath,
ImmutableSet<String> bootClassPath,
+ String release,
+ String system,
ImmutableList<String> sources,
ImmutableList<String> processorPath,
ImmutableSet<String> processors,
ImmutableList<String> sourceJars,
@Nullable String outputDeps,
- ImmutableMap<String, String> directJarsToTargets,
- ImmutableMap<String, String> indirectJarsToTargets,
+ ImmutableSet<String> directJars,
@Nullable String targetLabel,
+ @Nullable String injectingRuleKind,
ImmutableList<String> depsArtifacts,
- @Nullable String ruleKind,
boolean javacFallback,
ImmutableList<String> javacOpts,
boolean shouldReduceClassPath) {
this.output = checkNotNull(output, "output must not be null");
this.classPath = checkNotNull(classPath, "classPath must not be null");
this.bootClassPath = checkNotNull(bootClassPath, "bootClassPath must not be null");
+ this.release = Optional.fromNullable(release);
+ this.system = Optional.fromNullable(system);
this.sources = checkNotNull(sources, "sources must not be null");
this.processorPath = checkNotNull(processorPath, "processorPath must not be null");
this.processors = checkNotNull(processors, "processors must not be null");
this.sourceJars = checkNotNull(sourceJars, "sourceJars must not be null");
this.outputDeps = Optional.fromNullable(outputDeps);
- this.directJarsToTargets =
- checkNotNull(directJarsToTargets, "directJarsToTargets must not be null");
- this.indirectJarsToTargets =
- checkNotNull(indirectJarsToTargets, "indirectJarsToTargets must not be null");
+ this.directJars = checkNotNull(directJars, "directJars must not be null");
this.targetLabel = Optional.fromNullable(targetLabel);
+ this.injectingRuleKind = Optional.fromNullable(injectingRuleKind);
this.depsArtifacts = checkNotNull(depsArtifacts, "depsArtifacts must not be null");
- this.ruleKind = Optional.fromNullable(ruleKind);
this.javacFallback = javacFallback;
this.javacOpts = checkNotNull(javacOpts, "javacOpts must not be null");
this.shouldReduceClassPath = shouldReduceClassPath;
@@ -96,6 +96,16 @@
return bootClassPath;
}
+ /** The target platform version. */
+ public Optional<String> release() {
+ return release;
+ }
+
+ /** The target platform's system modules. */
+ public Optional<String> system() {
+ return system;
+ }
+
/** The output jar. */
public String outputFile() {
return output;
@@ -121,14 +131,9 @@
return outputDeps;
}
- /** The mapping from the path to a direct dependency to its build label. */
- public ImmutableMap<String, String> directJarsToTargets() {
- return directJarsToTargets;
- }
-
- /** The mapping from the path to an indirect dependency to its build label. */
- public ImmutableMap<String, String> indirectJarsToTargets() {
- return indirectJarsToTargets;
+ /** The direct dependencies. */
+ public ImmutableSet<String> directJars() {
+ return directJars;
}
/** The label of the target being compiled. */
@@ -136,16 +141,20 @@
return targetLabel;
}
+ /**
+ * If present, the name of the rule that injected an aspect that compiles this target.
+ *
+ * <p>Note that this rule will have a completely different label to {@link #targetLabel} above.
+ */
+ public Optional<String> injectingRuleKind() {
+ return injectingRuleKind;
+ }
+
/** The .jdeps artifacts for direct dependencies. */
public ImmutableList<String> depsArtifacts() {
return depsArtifacts;
}
- /** The kind of the build rule being compiled (e.g. {@code java_library}). */
- public Optional<String> ruleKind() {
- return ruleKind;
- }
-
/** Fall back to javac-turbine for error reporting. */
public boolean javacFallback() {
return javacFallback;
@@ -175,13 +184,13 @@
private final ImmutableSet.Builder<String> processors = ImmutableSet.builder();
private final ImmutableList.Builder<String> sourceJars = ImmutableList.builder();
private final ImmutableSet.Builder<String> bootClassPath = ImmutableSet.builder();
+ @Nullable private String release;
+ @Nullable private String system;
private String outputDeps;
- private final ImmutableMap.Builder<String, String> directJarsToTargets = ImmutableMap.builder();
- private final ImmutableMap.Builder<String, String> indirectJarsToTargets =
- ImmutableMap.builder();
+ private final ImmutableSet.Builder<String> directJars = ImmutableSet.builder();
@Nullable private String targetLabel;
+ @Nullable private String injectingRuleKind;
private final ImmutableList.Builder<String> depsArtifacts = ImmutableList.builder();
- @Nullable private String ruleKind;
private boolean javacFallback = true;
private final ImmutableList.Builder<String> javacOpts = ImmutableList.builder();
private boolean shouldReduceClassPath = true;
@@ -191,16 +200,17 @@
output,
classPath.build(),
bootClassPath.build(),
+ release,
+ system,
sources.build(),
processorPath.build(),
processors.build(),
sourceJars.build(),
outputDeps,
- directJarsToTargets.build(),
- indirectJarsToTargets.build(),
+ directJars.build(),
targetLabel,
+ injectingRuleKind,
depsArtifacts.build(),
- ruleKind,
javacFallback,
javacOpts.build(),
shouldReduceClassPath);
@@ -221,6 +231,16 @@
return this;
}
+ public Builder setRelease(String release) {
+ this.release = release;
+ return this;
+ }
+
+ public Builder setSystem(String system) {
+ this.system = system;
+ return this;
+ }
+
public Builder addSources(Iterable<String> sources) {
this.sources.addAll(sources);
return this;
@@ -251,13 +271,9 @@
return this;
}
- public Builder addDirectJarToTarget(String jar, String target) {
- directJarsToTargets.put(jar, target);
- return this;
- }
-
- public Builder addIndirectJarToTarget(String jar, String target) {
- indirectJarsToTargets.put(jar, target);
+ // TODO(b/72379900): Remove this
+ public Builder addDirectJarToTarget(String jar) {
+ directJars.add(jar);
return this;
}
@@ -266,13 +282,13 @@
return this;
}
- public Builder addAllDepsArtifacts(Iterable<String> depsArtifacts) {
- this.depsArtifacts.addAll(depsArtifacts);
+ public Builder setInjectingRuleKind(String injectingRuleKind) {
+ this.injectingRuleKind = injectingRuleKind;
return this;
}
- public Builder setRuleKind(String ruleKind) {
- this.ruleKind = ruleKind;
+ public Builder addAllDepsArtifacts(Iterable<String> depsArtifacts) {
+ this.depsArtifacts.addAll(depsArtifacts);
return this;
}
@@ -290,5 +306,10 @@
this.shouldReduceClassPath = shouldReduceClassPath;
return this;
}
+
+ public Builder addDirectJars(ImmutableList<String> jars) {
+ this.directJars.addAll(jars);
+ return this;
+ }
}
}
diff --git a/java/com/google/turbine/options/TurbineOptionsParser.java b/java/com/google/turbine/options/TurbineOptionsParser.java
index 5d78201..419a04e 100644
--- a/java/com/google/turbine/options/TurbineOptionsParser.java
+++ b/java/com/google/turbine/options/TurbineOptionsParser.java
@@ -27,6 +27,7 @@
import java.nio.file.Paths;
import java.util.ArrayDeque;
import java.util.Deque;
+import java.util.Iterator;
import javax.annotation.Nullable;
/** A command line options parser for {@link TurbineOptions}. */
@@ -52,31 +53,6 @@
parse(builder, argumentDeque);
}
- private static final Splitter ARG_SPLITTER =
- Splitter.on(CharMatcher.breakingWhitespace()).omitEmptyStrings().trimResults();
-
- /**
- * Pre-processes an argument list, expanding arguments of the form {@code @filename} by reading
- * the content of the file and appending whitespace-delimited options to {@code argumentDeque}.
- */
- private static void expandParamsFiles(Deque<String> argumentDeque, Iterable<String> args)
- throws IOException {
- for (String arg : args) {
- if (arg.isEmpty()) {
- continue;
- }
- if (arg.startsWith("@@")) {
- argumentDeque.addLast(arg.substring(1));
- } else if (arg.startsWith("@")) {
- Path paramsPath = Paths.get(arg.substring(1));
- expandParamsFiles(
- argumentDeque, ARG_SPLITTER.split(new String(Files.readAllBytes(paramsPath), UTF_8)));
- } else {
- argumentDeque.addLast(arg);
- }
- }
- }
-
private static void parse(TurbineOptions.Builder builder, Deque<String> argumentDeque) {
while (!argumentDeque.isEmpty()) {
String next = argumentDeque.pollFirst();
@@ -95,35 +71,56 @@
builder.addProcessors(readList(argumentDeque));
break;
case "--processorpath":
- builder.addProcessorPathEntries(splitClasspath(readList(argumentDeque)));
+ builder.addProcessorPathEntries(readList(argumentDeque));
break;
+ // TODO(b/72379900): Remove this
case "--classpath":
- builder.addClassPathEntries(splitClasspath(readList(argumentDeque)));
+ builder.addClassPathEntries(readList(argumentDeque));
break;
case "--bootclasspath":
- builder.addBootClassPathEntries(splitClasspath(readList(argumentDeque)));
+ builder.addBootClassPathEntries(readList(argumentDeque));
+ break;
+ case "--release":
+ builder.setRelease(readOne(argumentDeque));
+ break;
+ case "--system":
+ builder.setSystem(readOne(argumentDeque));
break;
case "--javacopts":
- builder.addAllJavacOpts(readList(argumentDeque));
- break;
+ {
+ ImmutableList<String> javacopts = readJavacopts(argumentDeque);
+ setReleaseFromJavacopts(builder, javacopts);
+ builder.addAllJavacOpts(javacopts);
+ break;
+ }
case "--sources":
builder.addSources(readList(argumentDeque));
break;
case "--output_deps":
builder.setOutputDeps(readOne(argumentDeque));
break;
+ case "--direct_dependencies":
+ builder.addDirectJars(readList(argumentDeque));
+ break;
case "--direct_dependency":
{
+ // TODO(b/72379900): Remove this
String jar = readOne(argumentDeque);
- String target = readOne(argumentDeque);
- builder.addDirectJarToTarget(jar, target);
+ readOne(argumentDeque);
+ builder.addDirectJarToTarget(jar);
+ if (!argumentDeque.isEmpty() && !argumentDeque.peekFirst().startsWith("--")) {
+ argumentDeque.removeFirst(); // the aspect that created the dependency
+ }
break;
}
case "--indirect_dependency":
{
- String jar = readOne(argumentDeque);
- String target = readOne(argumentDeque);
- builder.addIndirectJarToTarget(jar, target);
+ // TODO(b/72379900): Remove this
+ readOne(argumentDeque);
+ readOne(argumentDeque);
+ if (!argumentDeque.isEmpty() && !argumentDeque.peekFirst().startsWith("--")) {
+ argumentDeque.removeFirst(); // the aspect that created the dependency
+ }
break;
}
case "--deps_artifacts":
@@ -132,8 +129,8 @@
case "--target_label":
builder.setTargetLabel(readOne(argumentDeque));
break;
- case "--rule_kind":
- builder.setRuleKind(readOne(argumentDeque));
+ case "--injecting_rule_kind":
+ builder.setInjectingRuleKind(readOne(argumentDeque));
break;
case "--javac_fallback":
builder.setJavacFallback(true);
@@ -142,9 +139,35 @@
builder.setJavacFallback(false);
break;
default:
- if (next.isEmpty() && !argumentDeque.isEmpty()) {
- throw new IllegalArgumentException("unknown option: " + next);
- }
+ throw new IllegalArgumentException("unknown option: " + next);
+ }
+ }
+ }
+
+ private static final Splitter ARG_SPLITTER =
+ Splitter.on(CharMatcher.breakingWhitespace()).omitEmptyStrings().trimResults();
+
+ /**
+ * Pre-processes an argument list, expanding arguments of the form {@code @filename} by reading
+ * the content of the file and appending whitespace-delimited options to {@code argumentDeque}.
+ */
+ private static void expandParamsFiles(Deque<String> argumentDeque, Iterable<String> args)
+ throws IOException {
+ for (String arg : args) {
+ if (arg.isEmpty()) {
+ continue;
+ }
+ if (arg.startsWith("@@")) {
+ argumentDeque.addLast(arg.substring(1));
+ } else if (arg.startsWith("@")) {
+ Path paramsPath = Paths.get(arg.substring(1));
+ if (!Files.exists(paramsPath)) {
+ throw new AssertionError("params file does not exist: " + paramsPath);
+ }
+ expandParamsFiles(
+ argumentDeque, ARG_SPLITTER.split(new String(Files.readAllBytes(paramsPath), UTF_8)));
+ } else {
+ argumentDeque.addLast(arg);
}
}
}
@@ -167,15 +190,33 @@
return result.build();
}
- private static final Splitter CLASSPATH_SPLITTER =
- Splitter.on(':').trimResults().omitEmptyStrings();
-
- // TODO(cushon): stop splitting classpaths once cl/127006119 is released
- private static ImmutableList<String> splitClasspath(Iterable<String> paths) {
- ImmutableList.Builder<String> classpath = ImmutableList.builder();
- for (String path : paths) {
- classpath.addAll(CLASSPATH_SPLITTER.split(path));
+ /**
+ * Returns a list of javacopts. Reads options until a terminating {@code "--"} is reached, to
+ * support parsing javacopts that start with {@code --} (e.g. --release).
+ */
+ private static ImmutableList<String> readJavacopts(Deque<String> argumentDeque) {
+ ImmutableList.Builder<String> result = ImmutableList.builder();
+ while (!argumentDeque.isEmpty()) {
+ String arg = argumentDeque.pollFirst();
+ if (arg.equals("--")) {
+ return result.build();
+ }
+ result.add(arg);
}
- return classpath.build();
+ throw new IllegalArgumentException("javacopts should be terminated by `--`");
+ }
+
+ /**
+ * Parses the given javacopts for {@code --release}, and if found sets turbine's {@code --release}
+ * flag.
+ */
+ private static void setReleaseFromJavacopts(
+ TurbineOptions.Builder builder, ImmutableList<String> javacopts) {
+ Iterator<String> it = javacopts.iterator();
+ while (it.hasNext()) {
+ if (it.next().equals("--release") && it.hasNext()) {
+ builder.setRelease(it.next());
+ }
+ }
}
}
diff --git a/java/com/google/turbine/parse/ConstExpressionParser.java b/java/com/google/turbine/parse/ConstExpressionParser.java
index 36a701f..e6a7f97 100644
--- a/java/com/google/turbine/parse/ConstExpressionParser.java
+++ b/java/com/google/turbine/parse/ConstExpressionParser.java
@@ -565,9 +565,10 @@
if (token == Token.LPAREN) {
eat();
while (token != Token.RPAREN) {
+ int pos = position;
Tree.Expression expression = expression();
if (expression == null) {
- throw new AssertionError("invalid annotation expression");
+ throw TurbineError.format(lexer.source(), pos, ErrorKind.INVALID_ANNOTATION_ARGUMENT);
}
args.add(expression);
if (token != Token.COMMA) {
diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java
index 88529de..7b3a92e 100644
--- a/java/com/google/turbine/parse/Parser.java
+++ b/java/com/google/turbine/parse/Parser.java
@@ -17,12 +17,15 @@
package com.google.turbine.parse;
import static com.google.turbine.parse.Token.COMMA;
+import static com.google.turbine.parse.Token.IDENT;
import static com.google.turbine.parse.Token.INTERFACE;
import static com.google.turbine.parse.Token.LPAREN;
import static com.google.turbine.parse.Token.RPAREN;
+import static com.google.turbine.parse.Token.STATIC;
import static com.google.turbine.tree.TurbineModifier.PROTECTED;
import static com.google.turbine.tree.TurbineModifier.PUBLIC;
+import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -40,6 +43,13 @@
import com.google.turbine.tree.Tree.ImportDecl;
import com.google.turbine.tree.Tree.Kind;
import com.google.turbine.tree.Tree.MethDecl;
+import com.google.turbine.tree.Tree.ModDecl;
+import com.google.turbine.tree.Tree.ModDirective;
+import com.google.turbine.tree.Tree.ModExports;
+import com.google.turbine.tree.Tree.ModOpens;
+import com.google.turbine.tree.Tree.ModProvides;
+import com.google.turbine.tree.Tree.ModRequires;
+import com.google.turbine.tree.Tree.ModUses;
import com.google.turbine.tree.Tree.PkgDecl;
import com.google.turbine.tree.Tree.PrimTy;
import com.google.turbine.tree.Tree.TyDecl;
@@ -52,6 +62,7 @@
import java.util.Deque;
import java.util.EnumSet;
import java.util.List;
+import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
/**
@@ -85,6 +96,7 @@
// and make it bug-compatible with javac:
// http://mail.openjdk.java.net/pipermail/compiler-dev/2013-August/006968.html
Optional<PkgDecl> pkg = Optional.absent();
+ Optional<ModDecl> mod = Optional.absent();
EnumSet<TurbineModifier> access = EnumSet.noneOf(TurbineModifier.class);
ImmutableList.Builder<ImportDecl> imports = ImmutableList.builder();
ImmutableList.Builder<TyDecl> decls = ImmutableList.builder();
@@ -165,11 +177,30 @@
break;
case EOF:
// TODO(cushon): check for dangling modifiers?
- return new CompUnit(position, pkg, imports.build(), decls.build(), lexer.source());
+ return new CompUnit(position, pkg, mod, imports.build(), decls.build(), lexer.source());
case SEMI:
// TODO(cushon): check for dangling modifiers?
next();
continue;
+ case IDENT:
+ {
+ String ident = lexer.stringValue();
+ if (access.isEmpty() && (ident.equals("module") || ident.equals("open"))) {
+ boolean open = false;
+ if (ident.equals("open")) {
+ ident = eatIdent();
+ open = true;
+ }
+ if (!ident.equals("module")) {
+ throw error(token);
+ }
+ next();
+ mod = Optional.of(moduleDeclaration(open, annos.build()));
+ annos = ImmutableList.builder();
+ break;
+ }
+ }
+ // fall through
default:
throw error(token);
}
@@ -183,6 +214,7 @@
private TyDecl interfaceDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
eat(Token.INTERFACE);
+ int pos = position;
String name = eatIdent();
ImmutableList<TyParam> typarams;
if (token == Token.LT) {
@@ -201,7 +233,7 @@
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
return new TyDecl(
- position,
+ pos,
access,
annos,
name,
@@ -214,12 +246,13 @@
private TyDecl annotationDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
eat(Token.INTERFACE);
+ int pos = position;
String name = eatIdent();
eat(Token.LBRACE);
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
return new TyDecl(
- position,
+ pos,
access,
annos,
name,
@@ -232,6 +265,7 @@
private TyDecl enumDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
eat(Token.ENUM);
+ int pos = position;
String name = eatIdent();
ImmutableList.Builder<ClassTy> interfaces = ImmutableList.builder();
if (token == Token.IMPLEMENTS) {
@@ -245,7 +279,7 @@
ImmutableList.<Tree>builder().addAll(enumMembers(name)).addAll(classMembers()).build();
eat(Token.RBRACE);
return new TyDecl(
- position,
+ pos,
access,
annos,
name,
@@ -256,6 +290,129 @@
TurbineTyKind.ENUM);
}
+ private String moduleName() {
+ return Joiner.on('.').join(qualIdent());
+ }
+
+ private String packageName() {
+ return Joiner.on('/').join(qualIdent());
+ }
+
+ private ModDecl moduleDeclaration(boolean open, ImmutableList<Anno> annos) {
+ int pos = position;
+ String moduleName = moduleName();
+ eat(Token.LBRACE);
+ ImmutableList.Builder<ModDirective> directives = ImmutableList.builder();
+ OUTER:
+ while (true) {
+ switch (token) {
+ case IDENT:
+ {
+ String ident = lexer.stringValue();
+ next();
+ switch (ident) {
+ case "requires":
+ directives.add(moduleRequires());
+ break;
+ case "exports":
+ directives.add(moduleExports());
+ break;
+ case "opens":
+ directives.add(moduleOpens());
+ break;
+ case "uses":
+ directives.add(moduleUses());
+ break;
+ case "provides":
+ directives.add(moduleProvides());
+ break;
+ default: // fall out
+ }
+ break;
+ }
+ case RBRACE:
+ break OUTER;
+ default:
+ throw error(token);
+ }
+ }
+ eat(Token.RBRACE);
+ return new ModDecl(pos, annos, open, moduleName, directives.build());
+ }
+
+ private ModRequires moduleRequires() {
+ int pos = position;
+ EnumSet<TurbineModifier> access = EnumSet.noneOf(TurbineModifier.class);
+ while (true) {
+ if (token == Token.IDENT && lexer.stringValue().equals("transitive")) {
+ next();
+ access.add(TurbineModifier.TRANSITIVE);
+ break;
+ }
+ if (token == Token.STATIC) {
+ next();
+ access.add(TurbineModifier.STATIC);
+ break;
+ }
+ break;
+ }
+ String moduleName = moduleName();
+ eat(Token.SEMI);
+ return new ModRequires(pos, ImmutableSet.copyOf(access), moduleName);
+ }
+
+ private ModExports moduleExports() {
+ int pos = position;
+ String packageName = packageName();
+ ImmutableList.Builder<String> moduleNames = ImmutableList.builder();
+ if (lexer.stringValue().equals("to")) {
+ next();
+ do {
+ String moduleName = moduleName();
+ moduleNames.add(moduleName);
+ } while (maybe(Token.COMMA));
+ }
+ eat(Token.SEMI);
+ return new ModExports(pos, packageName, moduleNames.build());
+ }
+
+ private ModOpens moduleOpens() {
+ int pos = position;
+ String packageName = packageName();
+ ImmutableList.Builder<String> moduleNames = ImmutableList.builder();
+ if (lexer.stringValue().equals("to")) {
+ next();
+ do {
+ String moduleName = moduleName();
+ moduleNames.add(moduleName);
+ } while (maybe(Token.COMMA));
+ }
+ eat(Token.SEMI);
+ return new ModOpens(pos, packageName, moduleNames.build());
+ }
+
+ private ModUses moduleUses() {
+ int pos = position;
+ ImmutableList<String> uses = qualIdent();
+ eat(Token.SEMI);
+ return new ModUses(pos, uses);
+ }
+
+ private ModProvides moduleProvides() {
+ int pos = position;
+ ImmutableList<String> typeName = qualIdent();
+ if (!eatIdent().equals("with")) {
+ throw error(token);
+ }
+ ImmutableList.Builder<ImmutableList<String>> implNames = ImmutableList.builder();
+ do {
+ ImmutableList<String> implName = qualIdent();
+ implNames.add(implName);
+ } while (maybe(Token.COMMA));
+ eat(Token.SEMI);
+ return new ModProvides(pos, typeName, implNames.build());
+ }
+
private static final ImmutableSet<TurbineModifier> ENUM_CONSTANT_MODIFIERS =
ImmutableSet.of(
TurbineModifier.PUBLIC,
@@ -316,6 +473,7 @@
private TyDecl classDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
eat(Token.CLASS);
+ int pos = position;
String name = eatIdent();
ImmutableList<TyParam> tyParams = ImmutableList.of();
if (token == Token.LT) {
@@ -337,7 +495,7 @@
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
return new TyDecl(
- position,
+ pos,
access,
annos,
name,
@@ -1177,6 +1335,7 @@
return false;
}
+ @CheckReturnValue
TurbineError error(Token token) {
switch (token) {
case IDENT:
@@ -1188,6 +1347,7 @@
}
}
+ @CheckReturnValue
private TurbineError error(ErrorKind kind, Object... args) {
return TurbineError.format(
lexer.source(),
diff --git a/java/com/google/turbine/tree/Pretty.java b/java/com/google/turbine/tree/Pretty.java
index 820126d..978be8d 100644
--- a/java/com/google/turbine/tree/Pretty.java
+++ b/java/com/google/turbine/tree/Pretty.java
@@ -22,6 +22,13 @@
import com.google.common.collect.ImmutableSet;
import com.google.turbine.tree.Tree.Anno;
import com.google.turbine.tree.Tree.ClassLiteral;
+import com.google.turbine.tree.Tree.ModDecl;
+import com.google.turbine.tree.Tree.ModDirective;
+import com.google.turbine.tree.Tree.ModExports;
+import com.google.turbine.tree.Tree.ModOpens;
+import com.google.turbine.tree.Tree.ModProvides;
+import com.google.turbine.tree.Tree.ModRequires;
+import com.google.turbine.tree.Tree.ModUses;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -237,6 +244,10 @@
for (Tree.ImportDecl i : compUnit.imports()) {
i.accept(this, null);
}
+ if (compUnit.mod().isPresent()) {
+ printLine();
+ compUnit.mod().get().accept(this, null);
+ }
for (Tree.TyDecl decl : compUnit.decls()) {
printLine();
decl.accept(this, null);
@@ -472,6 +483,7 @@
case NATIVE:
case TRANSIENT:
case DEFAULT:
+ case TRANSITIVE:
append(mod.toString()).append(' ');
break;
case ACC_SUPER:
@@ -515,4 +527,109 @@
append("package ").append(Joiner.on('.').join(pkgDecl.name())).append(';');
return null;
}
+
+ @Override
+ public Void visitModDecl(ModDecl modDecl, Void input) {
+ for (Tree.Anno anno : modDecl.annos()) {
+ anno.accept(this, null);
+ printLine();
+ }
+ if (modDecl.open()) {
+ append("open ");
+ }
+ append("module ").append(modDecl.moduleName()).append(" {");
+ indent++;
+ append('\n');
+ for (ModDirective directive : modDecl.directives()) {
+ directive.accept(this, null);
+ }
+ indent--;
+ append("}\n");
+ return null;
+ }
+
+ @Override
+ public Void visitModRequires(ModRequires modRequires, Void input) {
+ append("requires ");
+ printModifiers(modRequires.mods());
+ append(modRequires.moduleName());
+ append(";");
+ append('\n');
+ return null;
+ }
+
+ @Override
+ public Void visitModExports(ModExports modExports, Void input) {
+ append("exports ");
+ append(modExports.packageName().replace('/', '.'));
+ if (!modExports.moduleNames().isEmpty()) {
+ append(" to").append('\n');
+ indent += 2;
+ boolean first = true;
+ for (String moduleName : modExports.moduleNames()) {
+ if (!first) {
+ append(',').append('\n');
+ }
+ append(moduleName);
+ first = false;
+ }
+ indent -= 2;
+ }
+ append(";");
+ append('\n');
+ return null;
+ }
+
+ @Override
+ public Void visitModOpens(ModOpens modOpens, Void input) {
+ append("opens ");
+ append(modOpens.packageName().replace('/', '.'));
+ if (!modOpens.moduleNames().isEmpty()) {
+ append(" to").append('\n');
+ indent += 2;
+ boolean first = true;
+ for (String moduleName : modOpens.moduleNames()) {
+ if (!first) {
+ append(',').append('\n');
+ }
+ append(moduleName);
+ first = false;
+ }
+ indent -= 2;
+ }
+ append(";");
+ append('\n');
+ return null;
+ }
+
+ @Override
+ public Void visitModUses(ModUses modUses, Void input) {
+ append("uses ");
+ append(Joiner.on('.').join(modUses.typeName()));
+ append(";");
+ append('\n');
+ return null;
+ }
+
+ @Override
+ public Void visitModProvides(ModProvides modProvides, Void input) {
+ append("provides ");
+ append(Joiner.on('.').join(modProvides.typeName()));
+ if (!modProvides.implNames().isEmpty()) {
+ append(" with").append('\n');
+ indent += 2;
+ boolean first = true;
+ for (ImmutableList<String> implName : modProvides.implNames()) {
+ if (!first) {
+ append(',').append('\n');
+ }
+ append(Joiner.on('.').join(implName));
+ first = false;
+ }
+ indent -= 2;
+ }
+ append(";");
+ append('\n');
+ return null;
+ }
}
diff --git a/java/com/google/turbine/tree/Tree.java b/java/com/google/turbine/tree/Tree.java
index 6f46df5..a84c776 100644
--- a/java/com/google/turbine/tree/Tree.java
+++ b/java/com/google/turbine/tree/Tree.java
@@ -71,7 +71,13 @@
ANNO_EXPR,
TY_DECL,
TY_PARAM,
- PKG_DECL
+ PKG_DECL,
+ MOD_DECL,
+ MOD_REQUIRES,
+ MOD_EXPORTS,
+ MOD_OPENS,
+ MOD_USES,
+ MOD_PROVIDES
}
/** A type use. */
@@ -524,6 +530,7 @@
/** A JLS 7.3 compilation unit. */
public static class CompUnit extends Tree {
private final Optional<PkgDecl> pkg;
+ private final Optional<ModDecl> mod;
private final ImmutableList<ImportDecl> imports;
private final ImmutableList<TyDecl> decls;
private final SourceFile source;
@@ -531,11 +538,13 @@
public CompUnit(
int position,
Optional<PkgDecl> pkg,
+ Optional<ModDecl> mod,
ImmutableList<ImportDecl> imports,
ImmutableList<TyDecl> decls,
SourceFile source) {
super(position);
this.pkg = pkg;
+ this.mod = mod;
this.imports = imports;
this.decls = decls;
this.source = source;
@@ -555,6 +564,10 @@
return pkg;
}
+ public Optional<ModDecl> mod() {
+ return mod;
+ }
+
public ImmutableList<ImportDecl> imports() {
return imports;
}
@@ -936,6 +949,250 @@
}
}
+ /** A JLS 7.7 module declaration. */
+ public static class ModDecl extends Tree {
+
+ private final ImmutableList<Anno> annos;
+ private final boolean open;
+ private final String moduleName;
+ private final ImmutableList<ModDirective> directives;
+
+ public ModDecl(
+ int position,
+ ImmutableList<Anno> annos,
+ boolean open,
+ String moduleName,
+ ImmutableList<ModDirective> directives) {
+ super(position);
+ this.annos = annos;
+ this.open = open;
+ this.moduleName = moduleName;
+ this.directives = directives;
+ }
+
+ public boolean open() {
+ return open;
+ }
+
+ public ImmutableList<Anno> annos() {
+ return annos;
+ }
+
+ public String moduleName() {
+ return moduleName;
+ }
+
+ public ImmutableList<ModDirective> directives() {
+ return directives;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.MOD_DECL;
+ }
+
+ @Override
+ public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ return visitor.visitModDecl(this, input);
+ }
+ }
+
+ /** A kind of module directive. */
+ public abstract static class ModDirective extends Tree {
+
+ /** A module directive kind. */
+ public enum DirectiveKind {
+ REQUIRES,
+ EXPORTS,
+ OPENS,
+ USES,
+ PROVIDES
+ }
+
+ public abstract DirectiveKind directiveKind();
+
+ protected ModDirective(int position) {
+ super(position);
+ }
+ }
+
+ /** A JLS 7.7.1 module requires directive. */
+ public static class ModRequires extends ModDirective {
+
+ private final ImmutableSet<TurbineModifier> mods;
+ private final String moduleName;
+
+ @Override
+ public Kind kind() {
+ return Kind.MOD_REQUIRES;
+ }
+
+ @Override
+ public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ return visitor.visitModRequires(this, input);
+ }
+
+ public ModRequires(int position, ImmutableSet<TurbineModifier> mods, String moduleName) {
+ super(position);
+ this.mods = mods;
+ this.moduleName = moduleName;
+ }
+
+ public ImmutableSet<TurbineModifier> mods() {
+ return mods;
+ }
+
+ public String moduleName() {
+ return moduleName;
+ }
+
+ @Override
+ public DirectiveKind directiveKind() {
+ return DirectiveKind.REQUIRES;
+ }
+ }
+
+ /** A JLS 7.7.2 module exports directive. */
+ public static class ModExports extends ModDirective {
+
+ private final String packageName;
+ private final ImmutableList<String> moduleNames;
+
+ @Override
+ public Kind kind() {
+ return Kind.MOD_EXPORTS;
+ }
+
+ @Override
+ public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ return visitor.visitModExports(this, input);
+ }
+
+ public ModExports(int position, String packageName, ImmutableList<String> moduleNames) {
+ super(position);
+ this.packageName = packageName;
+ this.moduleNames = moduleNames;
+ }
+
+ public String packageName() {
+ return packageName;
+ }
+
+ public ImmutableList<String> moduleNames() {
+ return moduleNames;
+ }
+
+ @Override
+ public DirectiveKind directiveKind() {
+ return DirectiveKind.EXPORTS;
+ }
+ }
+
+ /** A JLS 7.7.2 module opens directive. */
+ public static class ModOpens extends ModDirective {
+
+ private final String packageName;
+ private final ImmutableList<String> moduleNames;
+
+ public ModOpens(int position, String packageName, ImmutableList<String> moduleNames) {
+ super(position);
+ this.packageName = packageName;
+ this.moduleNames = moduleNames;
+ }
+
+ public String packageName() {
+ return packageName;
+ }
+
+ public ImmutableList<String> moduleNames() {
+ return moduleNames;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.MOD_OPENS;
+ }
+
+ @Override
+ public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ return visitor.visitModOpens(this, input);
+ }
+
+ @Override
+ public DirectiveKind directiveKind() {
+ return DirectiveKind.OPENS;
+ }
+ }
+
+ /** A JLS 7.7.3 module uses directive. */
+ public static class ModUses extends ModDirective {
+
+ private final ImmutableList<String> typeName;
+
+ public ModUses(int position, ImmutableList<String> typeName) {
+ super(position);
+ this.typeName = typeName;
+ }
+
+ public ImmutableList<String> typeName() {
+ return typeName;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.MOD_USES;
+ }
+
+ @Override
+ public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ return visitor.visitModUses(this, input);
+ }
+
+ @Override
+ public DirectiveKind directiveKind() {
+ return DirectiveKind.USES;
+ }
+ }
+
+ /** A JLS 7.7.4 module uses directive. */
+ public static class ModProvides extends ModDirective {
+
+ private final ImmutableList<String> typeName;
+ private final ImmutableList<ImmutableList<String>> implNames;
+
+ public ModProvides(
+ int position,
+ ImmutableList<String> typeName,
+ ImmutableList<ImmutableList<String>> implNames) {
+ super(position);
+ this.typeName = typeName;
+ this.implNames = implNames;
+ }
+
+ public ImmutableList<String> typeName() {
+ return typeName;
+ }
+
+ public ImmutableList<ImmutableList<String>> implNames() {
+ return implNames;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.MOD_PROVIDES;
+ }
+
+ @Override
+ public <I, O> O accept(Visitor<I, O> visitor, I input) {
+ return visitor.visitModProvides(this, input);
+ }
+
+ @Override
+ public DirectiveKind directiveKind() {
+ return DirectiveKind.PROVIDES;
+ }
+ }
+
/** A visitor for {@link Tree}s. */
public interface Visitor<I, O> {
O visitWildTy(WildTy visitor, I input);
@@ -981,5 +1238,17 @@
O visitTyParam(TyParam tyParam, I input);
O visitPkgDecl(PkgDecl pkgDecl, I input);
+
+ O visitModDecl(ModDecl modDecl, I input);
+
+ O visitModRequires(ModRequires modRequires, I input);
+
+ O visitModExports(ModExports modExports, I input);
+
+ O visitModOpens(ModOpens modOpens, I input);
+
+ O visitModUses(ModUses modUses, I input);
+
+ O visitModProvides(ModProvides modProvides, I input);
}
}
diff --git a/java/com/google/turbine/tree/TurbineModifier.java b/java/com/google/turbine/tree/TurbineModifier.java
index 3d0e60d..35dc11c 100644
--- a/java/com/google/turbine/tree/TurbineModifier.java
+++ b/java/com/google/turbine/tree/TurbineModifier.java
@@ -44,7 +44,8 @@
ACC_ANNOTATION(TurbineFlag.ACC_ANNOTATION),
ACC_SYNTHETIC(TurbineFlag.ACC_SYNTHETIC),
ACC_BRIDGE(TurbineFlag.ACC_BRIDGE),
- DEFAULT(TurbineFlag.ACC_DEFAULT);
+ DEFAULT(TurbineFlag.ACC_DEFAULT),
+ TRANSITIVE(TurbineFlag.ACC_TRANSITIVE);
private final int flag;
diff --git a/java/com/google/turbine/types/Canonicalize.java b/java/com/google/turbine/types/Canonicalize.java
index b4e320d..fc5d907 100644
--- a/java/com/google/turbine/types/Canonicalize.java
+++ b/java/com/google/turbine/types/Canonicalize.java
@@ -195,7 +195,8 @@
}
break;
}
- curr = canon(env, curr.sym(), env.get(curr.sym()).superClassType());
+ TypeBoundClass info = env.get(curr.sym());
+ curr = canon(env, info.owner(), info.superClassType());
}
simples.add(ty);
return new ClassTy(simples.build());
diff --git a/javatests/com/google/turbine/binder/BinderErrorTest.java b/javatests/com/google/turbine/binder/BinderErrorTest.java
index 102db93..a4c2e25 100644
--- a/javatests/com/google/turbine/binder/BinderErrorTest.java
+++ b/javatests/com/google/turbine/binder/BinderErrorTest.java
@@ -17,15 +17,15 @@
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 com.google.common.base.Joiner;
+import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.parse.Parser;
import com.google.turbine.tree.Tree.CompUnit;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
@@ -36,9 +36,6 @@
@RunWith(Parameterized.class)
public class BinderErrorTest {
- private static final ImmutableList<Path> BOOTCLASSPATH =
- ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
-
@Parameters
public static Iterable<Object[]> parameters() {
String[][][] testCases = {
@@ -337,6 +334,32 @@
" ^",
},
},
+ {
+ {
+ "class Test {", //
+ "}",
+ "class Test {",
+ "}",
+ },
+ {
+ "<>:3: error: duplicate declaration of Test", //
+ "class Test {",
+ " ^",
+ },
+ },
+ {
+ {
+ "public class Test {", //
+ " static class Inner {}",
+ " static class Inner {}",
+ "}",
+ },
+ {
+ "<>:3: error: duplicate declaration of Test$Inner", //
+ " static class Inner {}",
+ " ^",
+ },
+ },
};
return Arrays.asList((Object[][]) testCases);
}
@@ -352,7 +375,11 @@
@Test
public void test() throws Exception {
try {
- Binder.bind(ImmutableList.of(parseLines(source)), Collections.emptyList(), BOOTCLASSPATH)
+ Binder.bind(
+ ImmutableList.of(parseLines(source)),
+ ClassPathBinder.bindClasspath(Collections.emptyList()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent())
.units();
fail();
} catch (TurbineError e) {
@@ -365,6 +392,6 @@
}
private static String lines(String... lines) {
- return Joiner.on('\n').join(lines);
+ return Joiner.on(System.lineSeparator()).join(lines);
}
}
diff --git a/javatests/com/google/turbine/binder/BinderTest.java b/javatests/com/google/turbine/binder/BinderTest.java
index 89a7c33..9ba5705 100644
--- a/javatests/com/google/turbine/binder/BinderTest.java
+++ b/javatests/com/google/turbine/binder/BinderTest.java
@@ -17,9 +17,11 @@
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 com.google.common.base.Joiner;
+import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
@@ -33,7 +35,6 @@
import java.lang.annotation.ElementType;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -51,9 +52,6 @@
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
- private static final ImmutableList<Path> BOOTCLASSPATH =
- ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
-
@Test
public void hello() throws Exception {
List<Tree.CompUnit> units = new ArrayList<>();
@@ -74,7 +72,12 @@
"}"));
ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound =
- Binder.bind(units, Collections.emptyList(), BOOTCLASSPATH).units();
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(Collections.emptyList()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent())
+ .units();
assertThat(bound.keySet())
.containsExactly(
@@ -115,7 +118,12 @@
"}"));
ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound =
- Binder.bind(units, Collections.emptyList(), BOOTCLASSPATH).units();
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(Collections.emptyList()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent())
+ .units();
assertThat(bound.keySet())
.containsExactly(
@@ -150,7 +158,12 @@
"}"));
ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound =
- Binder.bind(units, Collections.emptyList(), BOOTCLASSPATH).units();
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(Collections.emptyList()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent())
+ .units();
assertThat(bound.get(new ClassSymbol("other/Foo")).superclass())
.isEqualTo(new ClassSymbol("com/test/Test$Inner"));
@@ -175,7 +188,11 @@
"}"));
try {
- Binder.bind(units, Collections.emptyList(), BOOTCLASSPATH);
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(Collections.emptyList()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent());
fail();
} catch (TurbineError e) {
assertThat(e.getMessage()).contains("cycle in class hierarchy: a/A -> b/B -> a/A");
@@ -192,7 +209,12 @@
"}"));
ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound =
- Binder.bind(units, Collections.emptyList(), BOOTCLASSPATH).units();
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(Collections.emptyList()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent())
+ .units();
SourceTypeBoundClass a = bound.get(new ClassSymbol("com/test/Annotation"));
assertThat(a.access())
@@ -216,7 +238,12 @@
"}"));
ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound =
- Binder.bind(units, Collections.emptyList(), BOOTCLASSPATH).units();
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(Collections.emptyList()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent())
+ .units();
SourceTypeBoundClass a = bound.get(new ClassSymbol("a/A"));
assertThat(a.interfaces()).containsExactly(new ClassSymbol("java/util/Map$Entry"));
@@ -230,8 +257,7 @@
ImmutableMap.of(
"A.java", "class A {}",
"B.java", "class B extends A {}"),
- ImmutableList.of(),
- BOOTCLASSPATH);
+ ImmutableList.of());
// create a jar containing only B
Path libJar = temporaryFolder.newFile("lib.jar").toPath();
@@ -252,7 +278,12 @@
"}"));
ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound =
- Binder.bind(units, ImmutableList.of(libJar), BOOTCLASSPATH).units();
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(ImmutableList.of(libJar)),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent())
+ .units();
SourceTypeBoundClass a = bound.get(new ClassSymbol("C$A"));
assertThat(a.annotationMetadata().target()).containsExactly(ElementType.TYPE_USE);
diff --git a/javatests/com/google/turbine/binder/ClassPathBinderTest.java b/javatests/com/google/turbine/binder/ClassPathBinderTest.java
index 0c95885..bd9bde3 100644
--- a/javatests/com/google/turbine/binder/ClassPathBinderTest.java
+++ b/javatests/com/google/turbine/binder/ClassPathBinderTest.java
@@ -17,6 +17,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 java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.fail;
@@ -25,11 +26,10 @@
import com.google.common.io.ByteStreams;
import com.google.turbine.binder.bound.HeaderBoundClass;
import com.google.turbine.binder.bytecode.BytecodeBoundClass;
-import com.google.turbine.binder.env.CompoundEnv;
+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.Scope;
-import com.google.turbine.binder.lookup.TopLevelIndex;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineTyKind;
@@ -37,33 +37,22 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
-import java.util.jar.JarOutputStream;
-import java.util.zip.ZipEntry;
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 ClassPathBinderTest {
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
- private static final ImmutableList<Path> BOOTCLASSPATH =
- ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
-
@Test
public void classPathLookup() throws IOException {
- TopLevelIndex.Builder tliBuilder = TopLevelIndex.builder();
- ClassPathBinder.bind(ImmutableList.of(), BOOTCLASSPATH, tliBuilder);
- TopLevelIndex tli = tliBuilder.build();
- Scope javaLang = tli.lookupPackage(ImmutableList.of("java", "lang"));
+ Scope javaLang = TURBINE_BOOTCLASSPATH.index().lookupPackage(ImmutableList.of("java", "lang"));
LookupResult result = javaLang.lookup(new LookupKey(Arrays.asList("String")));
assertThat(result.remaining()).isEmpty();
@@ -76,8 +65,7 @@
@Test
public void classPathClasses() throws IOException {
- CompoundEnv<ClassSymbol, BytecodeBoundClass> env =
- ClassPathBinder.bind(ImmutableList.of(), BOOTCLASSPATH, TopLevelIndex.builder());
+ Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env();
HeaderBoundClass c = env.get(new ClassSymbol("java/util/Map$Entry"));
assertThat(c.owner()).isEqualTo(new ClassSymbol("java/util/Map"));
@@ -120,50 +108,13 @@
}
}
- // symbols can be located on the regular and boot-classpaths, and the bootclasspath wins
- @Test
- public void bootClassPathWins() throws Exception {
- Path lib = temporaryFolder.newFile("lib.jar").toPath();
- Path bcp = temporaryFolder.newFile("bcp.jar").toPath();
-
- try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) {
- {
- jos.putNextEntry(new ZipEntry("foo/Bar.class"));
- ClassWriter cw = new ClassWriter(0);
- cw.visit(52, Opcodes.ACC_PUBLIC, "foo/Bar", null, "bar/A", null);
- jos.write(cw.toByteArray());
- }
- {
- jos.putNextEntry(new ZipEntry("foo/Baz.class"));
- ClassWriter cw = new ClassWriter(0);
- cw.visit(52, Opcodes.ACC_PUBLIC, "foo/Baz", null, "bar/A", null);
- jos.write(cw.toByteArray());
- }
- }
-
- try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(bcp))) {
- jos.putNextEntry(new ZipEntry("foo/Bar.class"));
- ClassWriter cw = new ClassWriter(0);
- cw.visit(52, Opcodes.ACC_PUBLIC, "foo/Bar", null, "bar/B", null);
- jos.write(cw.toByteArray());
- }
-
- CompoundEnv<ClassSymbol, BytecodeBoundClass> env =
- ClassPathBinder.bind(ImmutableList.of(lib), ImmutableList.of(bcp), TopLevelIndex.builder());
-
- assertThat(env.get(new ClassSymbol("foo/Bar")).superclass())
- .isEqualTo(new ClassSymbol("bar/B"));
- assertThat(env.get(new ClassSymbol("foo/Baz")).superclass())
- .isEqualTo(new ClassSymbol("bar/A"));
- }
-
@Test
public void nonJarFile() throws Exception {
Path lib = temporaryFolder.newFile("NOT_A_JAR").toPath();
Files.write(lib, "hello".getBytes(UTF_8));
try {
- ClassPathBinder.bind(ImmutableList.of(lib), ImmutableList.of(), TopLevelIndex.builder());
+ ClassPathBinder.bindClasspath(ImmutableList.of(lib));
fail();
} catch (IOException e) {
assertThat(e.getMessage()).contains("NOT_A_JAR");
diff --git a/javatests/com/google/turbine/binder/JimageClassBinderTest.java b/javatests/com/google/turbine/binder/JimageClassBinderTest.java
new file mode 100644
index 0000000..ffbbf87
--- /dev/null
+++ b/javatests/com/google/turbine/binder/JimageClassBinderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 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 com.google.common.collect.ImmutableList;
+import com.google.turbine.binder.bytecode.BytecodeBoundClass;
+import com.google.turbine.binder.lookup.LookupKey;
+import com.google.turbine.binder.lookup.LookupResult;
+import com.google.turbine.binder.sym.ClassSymbol;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class JimageClassBinderTest {
+ @Test
+ public void testDefaultJimage() throws IOException {
+ if (Double.parseDouble(System.getProperty("java.class.version")) < 53) {
+ // only run on JDK 9 and later
+ return;
+ }
+ ClassPath binder = JimageClassBinder.bindDefault();
+
+ BytecodeBoundClass objectInfo = binder.env().get(new ClassSymbol("java/lang/Object"));
+ assertThat(objectInfo).isNotNull();
+ assertThat(objectInfo.jarFile()).isEqualTo("/modules/java.base/java/lang/Object.class");
+ assertThat(binder.env().get(new ClassSymbol("java/lang/NoSuch"))).isNull();
+
+ assertThat(binder.index().lookupPackage(ImmutableList.of("java", "nosuch"))).isNull();
+
+ LookupResult objectSym =
+ binder
+ .index()
+ .lookupPackage(ImmutableList.of("java", "lang"))
+ .lookup(new LookupKey(ImmutableList.of("Object")));
+ assertThat(((ClassSymbol) objectSym.sym()).binaryName()).isEqualTo("java/lang/Object");
+ assertThat(objectSym.remaining()).isEmpty();
+
+ LookupResult entrySym =
+ binder
+ .index()
+ .lookupPackage(ImmutableList.of("java", "util"))
+ .lookup(new LookupKey(ImmutableList.of("Map", "Entry")));
+ assertThat(((ClassSymbol) entrySym.sym()).binaryName()).isEqualTo("java/util/Map");
+ assertThat(entrySym.remaining()).containsExactly("Entry");
+
+ entrySym =
+ binder
+ .index()
+ .scope()
+ .lookup(new LookupKey(ImmutableList.of("java", "util", "Map", "Entry")));
+ assertThat(((ClassSymbol) entrySym.sym()).binaryName()).isEqualTo("java/util/Map");
+ assertThat(entrySym.remaining()).containsExactly("Entry");
+ }
+}
diff --git a/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java b/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java
index 2e4d02a..3dcf127 100644
--- a/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java
+++ b/javatests/com/google/turbine/binder/lookup/TopLevelIndexTest.java
@@ -32,16 +32,17 @@
private static final TopLevelIndex index = buildIndex();
private static TopLevelIndex buildIndex() {
- TopLevelIndex.Builder builder = TopLevelIndex.builder();
- builder.insert(new ClassSymbol("java/util/Map"));
- builder.insert(new ClassSymbol("java/util/List"));
- builder.insert(new ClassSymbol("com/google/common/base/Optional"));
- return builder.build();
+ return SimpleTopLevelIndex.of(
+ ImmutableList.of(
+ new ClassSymbol("java/util/Map"),
+ new ClassSymbol("java/util/List"),
+ new ClassSymbol("com/google/common/base/Optional")));
}
@Test
public void simple() {
- LookupResult result = index.lookup(new LookupKey(ImmutableList.of("java", "util", "Map")));
+ LookupResult result =
+ index.scope().lookup(new LookupKey(ImmutableList.of("java", "util", "Map")));
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/util/Map"));
assertThat(result.remaining()).isEmpty();
}
@@ -49,14 +50,15 @@
@Test
public void nested() {
LookupResult result =
- index.lookup(new LookupKey(ImmutableList.of("java", "util", "Map", "Entry")));
+ index.scope().lookup(new LookupKey(ImmutableList.of("java", "util", "Map", "Entry")));
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/util/Map"));
assertThat(result.remaining()).containsExactly("Entry");
}
@Test
public void empty() {
- assertThat(index.lookup(new LookupKey(ImmutableList.of("java", "NoSuch", "Entry")))).isNull();
+ assertThat(index.scope().lookup(new LookupKey(ImmutableList.of("java", "NoSuch", "Entry"))))
+ .isNull();
assertThat(index.lookupPackage(ImmutableList.of("java", "math"))).isNull();
assertThat(index.lookupPackage(ImmutableList.of("java", "util", "Map"))).isNull();
}
@@ -76,23 +78,21 @@
public void overrideClass() {
{
// the use of Foo as a class name in the package java is "sticky"
- TopLevelIndex.Builder builder = TopLevelIndex.builder();
- builder.insert(new ClassSymbol("java/Foo"));
- assertThat(builder.insert(new ClassSymbol("java/Foo/Bar"))).isFalse();
- TopLevelIndex index = builder.build();
+ TopLevelIndex index =
+ SimpleTopLevelIndex.of(
+ ImmutableList.of(new ClassSymbol("java/Foo"), new ClassSymbol("java/Foo/Bar")));
- LookupResult result = index.lookup(new LookupKey(ImmutableList.of("java", "Foo")));
+ LookupResult result = index.scope().lookup(new LookupKey(ImmutableList.of("java", "Foo")));
assertThat(result.sym()).isEqualTo(new ClassSymbol("java/Foo"));
assertThat(result.remaining()).isEmpty();
}
{
// the use of Foo as a package name under java is "sticky"
- TopLevelIndex.Builder builder = TopLevelIndex.builder();
- builder.insert(new ClassSymbol("java/Foo/Bar"));
- assertThat(builder.insert(new ClassSymbol("java/Foo"))).isFalse();
- TopLevelIndex index = builder.build();
+ TopLevelIndex index =
+ SimpleTopLevelIndex.of(
+ ImmutableList.of(new ClassSymbol("java/Foo/Bar"), new ClassSymbol("java/Foo")));
- assertThat(index.lookup(new LookupKey(ImmutableList.of("java", "Foo")))).isNull();
+ assertThat(index.scope().lookup(new LookupKey(ImmutableList.of("java", "Foo")))).isNull();
LookupResult packageResult =
index
.lookupPackage(ImmutableList.of("java", "Foo"))
diff --git a/javatests/com/google/turbine/bytecode/ClassReaderTest.java b/javatests/com/google/turbine/bytecode/ClassReaderTest.java
index dde37d5..5bce03d 100644
--- a/javatests/com/google/turbine/bytecode/ClassReaderTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassReaderTest.java
@@ -16,11 +16,17 @@
package com.google.turbine.bytecode;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.ExportInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.OpenInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.ProvideInfo;
+import com.google.turbine.bytecode.ClassFile.ModuleInfo.RequireInfo;
import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.model.TurbineFlag;
@@ -29,6 +35,7 @@
import org.junit.runners.JUnit4;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.ModuleVisitor;
import org.objectweb.asm.Opcodes;
@RunWith(JUnit4.class)
@@ -235,4 +242,93 @@
ClassFile cf = ClassReader.read(null, cw.toByteArray());
assertThat(cf.name()).isEqualTo("Hello");
}
+
+ @Test
+ public void module() {
+ ClassWriter cw = new ClassWriter(0);
+
+ cw.visit(53, /* access= */ 53, "module-info", null, null, null);
+
+ ModuleVisitor mv = cw.visitModule("mod", Opcodes.ACC_OPEN, "mod-ver");
+
+ mv.visitRequire("r1", Opcodes.ACC_TRANSITIVE, "r1-ver");
+ mv.visitRequire("r2", Opcodes.ACC_STATIC_PHASE, "r2-ver");
+ mv.visitRequire("r3", Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE, "r3-ver");
+
+ mv.visitExport("e1", Opcodes.ACC_SYNTHETIC, "e1m1", "e1m2", "e1m3");
+ mv.visitExport("e2", Opcodes.ACC_MANDATED, "e2m1", "e2m2");
+ mv.visitExport("e3", /* access= */ 0, "e3m1");
+
+ mv.visitOpen("o1", Opcodes.ACC_SYNTHETIC, "o1m1", "o1m2", "o1m3");
+ mv.visitOpen("o2", Opcodes.ACC_MANDATED, "o2m1", "o2m2");
+ mv.visitOpen("o3", /* access= */ 0, "o3m1");
+
+ mv.visitUse("u1");
+ mv.visitUse("u2");
+ mv.visitUse("u3");
+ mv.visitUse("u4");
+
+ mv.visitProvide("p1", "p1i1", "p1i2");
+ mv.visitProvide("p2", "p2i1", "p2i2", "p2i3");
+
+ ClassFile cf = ClassReader.read(null, cw.toByteArray());
+ ModuleInfo module = cf.module();
+ assertThat(module.name()).isEqualTo("mod");
+ assertThat(module.flags()).isEqualTo(Opcodes.ACC_OPEN);
+ assertThat(module.version()).isEqualTo("mod-ver");
+
+ assertThat(module.requires()).hasSize(3);
+ RequireInfo r1 = module.requires().get(0);
+ assertThat(r1.moduleName()).isEqualTo("r1");
+ assertThat(r1.flags()).isEqualTo(Opcodes.ACC_TRANSITIVE);
+ assertThat(r1.version()).isEqualTo("r1-ver");
+ RequireInfo r2 = module.requires().get(1);
+ assertThat(r2.moduleName()).isEqualTo("r2");
+ assertThat(r2.flags()).isEqualTo(Opcodes.ACC_STATIC_PHASE);
+ assertThat(r2.version()).isEqualTo("r2-ver");
+ RequireInfo r3 = module.requires().get(2);
+ assertThat(r3.moduleName()).isEqualTo("r3");
+ assertThat(r3.flags()).isEqualTo(Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE);
+ assertThat(r3.version()).isEqualTo("r3-ver");
+
+ assertThat(module.exports()).hasSize(3);
+ ExportInfo e1 = module.exports().get(0);
+ assertThat(e1.moduleName()).isEqualTo("e1");
+ assertThat(e1.flags()).isEqualTo(Opcodes.ACC_SYNTHETIC);
+ assertThat(e1.modules()).containsExactly("e1m1", "e1m2", "e1m3").inOrder();
+ ExportInfo e2 = module.exports().get(1);
+ assertThat(e2.moduleName()).isEqualTo("e2");
+ assertThat(e2.flags()).isEqualTo(Opcodes.ACC_MANDATED);
+ assertThat(e2.modules()).containsExactly("e2m1", "e2m2").inOrder();
+ ExportInfo e3 = module.exports().get(2);
+ assertThat(e3.moduleName()).isEqualTo("e3");
+ assertThat(e3.flags()).isEqualTo(0);
+ assertThat(e3.modules()).containsExactly("e3m1").inOrder();
+
+ assertThat(module.opens()).hasSize(3);
+ OpenInfo o1 = module.opens().get(0);
+ assertThat(o1.moduleName()).isEqualTo("o1");
+ assertThat(o1.flags()).isEqualTo(Opcodes.ACC_SYNTHETIC);
+ assertThat(o1.modules()).containsExactly("o1m1", "o1m2", "o1m3").inOrder();
+ OpenInfo o2 = module.opens().get(1);
+ assertThat(o2.moduleName()).isEqualTo("o2");
+ assertThat(o2.flags()).isEqualTo(Opcodes.ACC_MANDATED);
+ assertThat(o2.modules()).containsExactly("o2m1", "o2m2").inOrder();
+ OpenInfo o3 = module.opens().get(2);
+ assertThat(o3.moduleName()).isEqualTo("o3");
+ assertThat(o3.flags()).isEqualTo(0);
+ assertThat(o3.modules()).containsExactly("o3m1").inOrder();
+
+ assertThat(module.uses().stream().map(u -> u.descriptor()).collect(toImmutableList()))
+ .containsExactly("u1", "u2", "u3", "u4")
+ .inOrder();
+
+ assertThat(module.provides()).hasSize(2);
+ ProvideInfo p1 = module.provides().get(0);
+ assertThat(p1.descriptor()).isEqualTo("p1");
+ assertThat(p1.implDescriptors()).containsExactly("p1i1", "p1i2");
+ ProvideInfo p2 = module.provides().get(1);
+ assertThat(p2.descriptor()).isEqualTo("p2");
+ assertThat(p2.implDescriptors()).containsExactly("p2i1", "p2i2", "p2i3");
+ }
}
diff --git a/javatests/com/google/turbine/bytecode/ClassWriterTest.java b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
index a2544fc..e544c15 100644
--- a/javatests/com/google/turbine/bytecode/ClassWriterTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
@@ -24,6 +24,7 @@
import com.google.common.io.ByteStreams;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
+import com.google.turbine.testing.AsmUtils;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.file.JavacFileManager;
@@ -42,6 +43,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.objectweb.asm.ModuleVisitor;
+import org.objectweb.asm.Opcodes;
@RunWith(JUnit4.class)
public class ClassWriterTest {
@@ -77,8 +80,8 @@
new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
fileManager,
collector,
- ImmutableList.of(),
- ImmutableList.of(),
+ ImmutableList.of("-source", "8", "-target", "8"),
+ /* classes= */ null,
fileManager.getJavaFileObjects(path));
assertThat(task.call()).named(collector.getDiagnostics().toString()).isTrue();
@@ -108,4 +111,43 @@
assertThat(reader.classInfo(entry.getKey())).isEqualTo(entry.getValue());
}
}
+
+ @Test
+ public void module() throws Exception {
+
+ org.objectweb.asm.ClassWriter cw = new org.objectweb.asm.ClassWriter(0);
+
+ cw.visit(53, /* access= */ 53, "module-info", null, null, null);
+
+ ModuleVisitor mv = cw.visitModule("mod", Opcodes.ACC_OPEN, "mod-ver");
+
+ mv.visitRequire("r1", Opcodes.ACC_TRANSITIVE, "r1-ver");
+ mv.visitRequire("r2", Opcodes.ACC_STATIC_PHASE, "r2-ver");
+ mv.visitRequire("r3", Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE, "r3-ver");
+
+ mv.visitExport("e1", Opcodes.ACC_SYNTHETIC, "e1m1", "e1m2", "e1m3");
+ mv.visitExport("e2", Opcodes.ACC_MANDATED, "e2m1", "e2m2");
+ mv.visitExport("e3", /* access= */ 0, "e3m1");
+
+ mv.visitOpen("o1", Opcodes.ACC_SYNTHETIC, "o1m1", "o1m2", "o1m3");
+ mv.visitOpen("o2", Opcodes.ACC_MANDATED, "o2m1", "o2m2");
+ mv.visitOpen("o3", /* access= */ 0, "o3m1");
+
+ mv.visitUse("u1");
+ mv.visitUse("u2");
+ mv.visitUse("u3");
+ mv.visitUse("u4");
+
+ mv.visitProvide("p1", "p1i1", "p1i2");
+ mv.visitProvide("p2", "p2i1", "p2i2", "p2i3");
+
+ byte[] inputBytes = cw.toByteArray();
+ byte[] outputBytes = ClassWriter.writeClass(ClassReader.read("module-info", inputBytes));
+
+ assertThat(AsmUtils.textify(inputBytes)).isEqualTo(AsmUtils.textify(outputBytes));
+
+ // test a round trip
+ outputBytes = ClassWriter.writeClass(ClassReader.read("module-info", outputBytes));
+ assertThat(AsmUtils.textify(inputBytes)).isEqualTo(AsmUtils.textify(outputBytes));
+ }
}
diff --git a/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java b/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java
index 1a3e70b..1c0d5c4 100644
--- a/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java
+++ b/javatests/com/google/turbine/bytecode/sig/SigIntegrationTest.java
@@ -16,12 +16,26 @@
package com.google.turbine.bytecode.sig;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Enumeration;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -32,67 +46,101 @@
import org.objectweb.asm.Opcodes;
/**
- * Reads all field, class, and method signatures in rt.jar, and round-trips them through {@link
- * SigWriter} and {@link SigParser}.
+ * Reads all field, class, and method signatures in the bootclasspath, and round-trips them through
+ * {@link SigWriter} and {@link SigParser}.
*/
@RunWith(JUnit4.class)
public class SigIntegrationTest {
+ private static final Splitter CLASS_PATH_SPLITTER =
+ Splitter.on(File.pathSeparatorChar).omitEmptyStrings();
+
+ void forEachBootclass(Consumer<Path> consumer) throws IOException {
+ ImmutableList<Path> bootclasspath =
+ Streams.stream(
+ CLASS_PATH_SPLITTER.split(
+ Optional.ofNullable(System.getProperty("sun.boot.class.path")).orElse("")))
+ .map(Paths::get)
+ .filter(Files::exists)
+ .collect(toImmutableList());
+ if (!bootclasspath.isEmpty()) {
+ for (Path path : bootclasspath) {
+ Map<String, ?> env = new HashMap<>();
+ try (FileSystem jarfs = FileSystems.newFileSystem(URI.create("jar:" + path.toUri()), env);
+ Stream<Path> stream = Files.walk(jarfs.getPath("/"))) {
+ stream
+ .filter(Files::isRegularFile)
+ .filter(p -> p.getFileName().toString().endsWith(".class"))
+ .forEachOrdered(consumer);
+ }
+ }
+ return;
+ }
+ {
+ 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);
+ }
+ }
+ }
+
@Test
public void roundTrip() throws Exception {
int[] totalSignatures = {0};
- try (JarFile jarFile =
- new JarFile(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar").toFile())) {
- Enumeration<JarEntry> entries = jarFile.entries();
- while (entries.hasMoreElements()) {
- JarEntry entry = entries.nextElement();
- if (!entry.getName().endsWith(".class")) {
- continue;
- }
- new ClassReader(jarFile.getInputStream(entry))
- .accept(
- new ClassVisitor(Opcodes.ASM5) {
- @Override
- public void visit(
- int version,
- int access,
- String name,
- String signature,
- String superName,
- String[] interfaces) {
- if (signature != null) {
- assertThat(SigWriter.classSig(new SigParser(signature).parseClassSig()))
- .isEqualTo(signature);
- totalSignatures[0]++;
- }
- }
+ forEachBootclass(
+ path -> {
+ try {
+ new ClassReader(Files.newInputStream(path))
+ .accept(
+ new ClassVisitor(Opcodes.ASM6) {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ if (signature != null) {
+ assertThat(SigWriter.classSig(new SigParser(signature).parseClassSig()))
+ .isEqualTo(signature);
+ totalSignatures[0]++;
+ }
+ }
- @Override
- public FieldVisitor visitField(
- int access, String name, String desc, String signature, Object value) {
- if (signature != null) {
- assertThat(SigWriter.type(new SigParser(signature).parseFieldSig()))
- .isEqualTo(signature);
- totalSignatures[0]++;
- }
- return super.visitField(access, name, desc, signature, value);
- }
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String desc, String signature, Object value) {
+ if (signature != null) {
+ assertThat(SigWriter.type(new SigParser(signature).parseFieldSig()))
+ .isEqualTo(signature);
+ totalSignatures[0]++;
+ }
+ return super.visitField(access, name, desc, signature, value);
+ }
- @Override
- public MethodVisitor visitMethod(
- int access, String name, String desc, String signature, String[] exceptions) {
- if (signature != null) {
- assertThat(SigWriter.method(new SigParser(signature).parseMethodSig()))
- .isEqualTo(signature);
- totalSignatures[0]++;
- }
- return super.visitMethod(access, name, desc, signature, exceptions);
- }
- },
- ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
- }
- }
- // sanity-check that rt.jar contains a plausible number of signatures; 8u60 has >18k
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String desc,
+ String signature,
+ String[] exceptions) {
+ if (signature != null) {
+ assertThat(SigWriter.method(new SigParser(signature).parseMethodSig()))
+ .isEqualTo(signature);
+ totalSignatures[0]++;
+ }
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ },
+ ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
+ // sanity-check that the bootclasspath contains a plausible number of signatures; 8u60 has >18k
assertThat(totalSignatures[0]).isGreaterThan(10000);
}
}
diff --git a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java
index 6335216..1bd9349 100644
--- a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java
+++ b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java
@@ -19,6 +19,7 @@
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;
@@ -28,12 +29,10 @@
import com.google.turbine.bytecode.ClassFile.InnerClass;
import com.google.turbine.bytecode.ClassReader;
import com.google.turbine.main.Main;
-import com.google.turbine.options.TurbineOptions;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashMap;
@@ -53,9 +52,6 @@
protected abstract Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath)
throws IOException;
- protected static final ImmutableList<Path> BOOTCLASSPATH =
- ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
-
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
class SourceBuilder {
@@ -162,11 +158,10 @@
.collect(toImmutableList());
boolean ok =
Main.compile(
- TurbineOptions.builder()
+ optionsWithBootclasspath()
.addSources(sources)
.addClassPathEntries(
ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList()))
- .addBootClassPathEntries(Iterables.transform(BOOTCLASSPATH, Path::toString))
.setOutput(libc.toString())
.build());
assertThat(ok).isTrue();
diff --git a/javatests/com/google/turbine/deps/DependenciesTest.java b/javatests/com/google/turbine/deps/DependenciesTest.java
index dd94650..70377fb 100644
--- a/javatests/com/google/turbine/deps/DependenciesTest.java
+++ b/javatests/com/google/turbine/deps/DependenciesTest.java
@@ -23,15 +23,16 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
import com.google.turbine.binder.Binder;
import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.binder.ClassPathBinder;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.lower.IntegrationTestSupport;
import com.google.turbine.lower.Lower;
import com.google.turbine.lower.Lower.Lowered;
import com.google.turbine.parse.Parser;
import com.google.turbine.proto.DepsProto;
+import com.google.turbine.testing.TestClassPaths;
import com.google.turbine.tree.Tree.CompUnit;
import java.io.BufferedOutputStream;
import java.io.IOException;
@@ -56,9 +57,6 @@
@RunWith(JUnit4.class)
public class DependenciesTest {
- static final ImmutableSet<Path> BOOTCLASSPATH =
- ImmutableSet.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
-
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
class LibraryBuilder {
@@ -77,8 +75,7 @@
Path compileToJar(String path) throws Exception {
Path lib = temporaryFolder.newFile(path).toPath();
- Map<String, byte[]> classes =
- IntegrationTestSupport.runJavac(sources, classpath, BOOTCLASSPATH);
+ Map<String, byte[]> classes = IntegrationTestSupport.runJavac(sources, classpath);
try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) {
for (Map.Entry<String, byte[]> entry : classes.entrySet()) {
jos.putNextEntry(new JarEntry(entry.getKey() + ".class"));
@@ -104,15 +101,17 @@
}
DepsProto.Dependencies run() throws IOException {
- BindingResult bound = Binder.bind(units, classpath, BOOTCLASSPATH);
+ BindingResult bound =
+ Binder.bind(
+ units,
+ ClassPathBinder.bindClasspath(classpath),
+ TestClassPaths.TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent());
- Lowered lowered = Lower.lowerAll(bound.units(), bound.classPathEnv());
+ Lowered lowered = Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv());
return Dependencies.collectDeps(
- Optional.of("//test"),
- ImmutableSet.copyOf(Iterables.transform(BOOTCLASSPATH, Path::toString)),
- bound,
- lowered);
+ Optional.of("//test"), TestClassPaths.TURBINE_BOOTCLASSPATH, bound, lowered);
}
}
@@ -258,9 +257,9 @@
ImmutableList.of(
"a.jar", "b.jar", "c.jar", "d.jar", "e.jar", "f.jar", "g.jar", "h.jar", "i.jar",
"j.jar");
- ImmutableMap<String, String> directJarsToTargets = ImmutableMap.of();
+ ImmutableSet<String> directJars = ImmutableSet.of();
ImmutableList<String> depsArtifacts = ImmutableList.of();
- assertThat(Dependencies.reduceClasspath(classpath, directJarsToTargets, depsArtifacts))
+ assertThat(Dependencies.reduceClasspath(classpath, directJars, depsArtifacts))
.isEqualTo(classpath);
}
@@ -273,11 +272,7 @@
ImmutableList.of(
"a.jar", "b.jar", "c.jar", "d.jar", "e.jar", "f.jar", "g.jar", "h.jar", "i.jar",
"j.jar");
- ImmutableMap<String, String> directJarsToTargets =
- ImmutableMap.of(
- "c.jar", "//a",
- "d.jar", "//d",
- "g.jar", "//e");
+ ImmutableSet<String> directJars = ImmutableSet.of("c.jar", "d.jar", "g.jar");
ImmutableList<String> depsArtifacts =
ImmutableList.of(cdeps.toString(), ddeps.toString(), gdeps.toString());
writeDeps(
@@ -291,7 +286,7 @@
"f.jar", DepsProto.Dependency.Kind.UNUSED,
"j.jar", DepsProto.Dependency.Kind.UNUSED));
writeDeps(gdeps, ImmutableMap.of("i.jar", DepsProto.Dependency.Kind.IMPLICIT));
- assertThat(Dependencies.reduceClasspath(classpath, directJarsToTargets, depsArtifacts))
+ assertThat(Dependencies.reduceClasspath(classpath, directJars, depsArtifacts))
.containsExactly("b.jar", "c.jar", "d.jar", "e.jar", "g.jar", "i.jar")
.inOrder();
}
diff --git a/javatests/com/google/turbine/deps/TransitiveTest.java b/javatests/com/google/turbine/deps/TransitiveTest.java
index fd764f4..f8c5b50 100644
--- a/javatests/com/google/turbine/deps/TransitiveTest.java
+++ b/javatests/com/google/turbine/deps/TransitiveTest.java
@@ -18,11 +18,10 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import com.google.turbine.main.Main;
-import com.google.turbine.options.TurbineOptions;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.runner.RunWith;
@@ -37,11 +36,10 @@
Path out = temporaryFolder.newFolder().toPath().resolve("out.jar");
boolean ok =
Main.compile(
- TurbineOptions.builder()
+ optionsWithBootclasspath()
.addSources(sources.stream().map(Path::toString).collect(toImmutableList()))
.addClassPathEntries(
classpath.stream().map(Path::toString).collect(toImmutableList()))
- .addBootClassPathEntries(Iterables.transform(BOOTCLASSPATH, Path::toString))
.setOutput(out.toString())
.build());
assertThat(ok).isTrue();
diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java
index 4e64964..51b5a2e 100644
--- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java
+++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java
@@ -17,19 +17,23 @@
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 java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import com.google.turbine.binder.Binder;
-import com.google.turbine.bytecode.AsmUtils;
+import com.google.turbine.binder.ClassPath;
+import com.google.turbine.binder.ClassPathBinder;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.parse.Parser;
+import com.google.turbine.testing.AsmUtils;
import com.google.turbine.tree.Tree;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.JavacTool;
@@ -400,7 +404,7 @@
final Set<String> classes1 = classes;
new SignatureReader(signature)
.accept(
- new SignatureVisitor(Opcodes.ASM5) {
+ new SignatureVisitor(Opcodes.ASM6) {
private final Set<String> classes = classes1;
// class signatures may contain type arguments that contain class signatures
Deque<List<String>> pieces = new ArrayDeque<>();
@@ -424,8 +428,17 @@
});
}
+ static Map<String, byte[]> runTurbine(Map<String, String> input, ImmutableList<Path> classpath)
+ throws IOException {
+ return runTurbine(
+ input, classpath, TURBINE_BOOTCLASSPATH, /* moduleVersion= */ Optional.absent());
+ }
+
static Map<String, byte[]> runTurbine(
- Map<String, String> input, ImmutableList<Path> classpath, Collection<Path> bootclasspath)
+ Map<String, String> input,
+ ImmutableList<Path> classpath,
+ ClassPath bootClassPath,
+ Optional<String> moduleVersion)
throws IOException {
List<Tree.CompUnit> units =
input
@@ -435,14 +448,19 @@
.map(Parser::parse)
.collect(toList());
- Binder.BindingResult bound = Binder.bind(units, classpath, bootclasspath);
- return Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes();
+ Binder.BindingResult bound =
+ Binder.bind(units, ClassPathBinder.bindClasspath(classpath), bootClassPath, moduleVersion);
+ return Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes();
}
public static Map<String, byte[]> runJavac(
- Map<String, String> sources,
- Collection<Path> classpath,
- Collection<? extends Path> bootclasspath)
+ Map<String, String> sources, Collection<Path> classpath) throws Exception {
+ return runJavac(
+ sources, classpath, ImmutableList.of("-parameters", "-source", "8", "-target", "8"));
+ }
+
+ public static Map<String, byte[]> runJavac(
+ Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options)
throws Exception {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
@@ -465,16 +483,22 @@
JavacTool compiler = JavacTool.create();
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
JavacFileManager fileManager = new JavacFileManager(new Context(), true, UTF_8);
- fileManager.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, bootclasspath);
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()
+ > 1) {
+ // multi-module mode
+ fileManager.setLocationFromPaths(
+ StandardLocation.locationFor("MODULE_SOURCE_PATH"), ImmutableList.of(srcs));
+ }
JavacTask task =
compiler.getTask(
new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true),
fileManager,
collector,
- ImmutableList.of("-parameters"),
+ options,
ImmutableList.of(),
fileManager.getJavaFileObjectsFromPaths(inputs));
diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
index c5caea1..698627c 100644
--- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java
+++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java
@@ -299,6 +299,9 @@
"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",
};
List<Object[]> tests =
ImmutableList.copyOf(testCases).stream().map(x -> new Object[] {x}).collect(toList());
@@ -326,9 +329,6 @@
this.test = test;
}
- static final ImmutableList<Path> BOOTCLASSPATH =
- ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
-
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
@@ -343,7 +343,7 @@
ImmutableList<Path> classpathJar = ImmutableList.of();
if (!input.classes.isEmpty()) {
Map<String, byte[]> classpath =
- IntegrationTestSupport.runJavac(input.classes, ImmutableList.of(), BOOTCLASSPATH);
+ IntegrationTestSupport.runJavac(input.classes, ImmutableList.of());
Path lib = temporaryFolder.newFile("lib.jar").toPath();
try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) {
for (Map.Entry<String, byte[]> entry : classpath.entrySet()) {
@@ -354,11 +354,9 @@
classpathJar = ImmutableList.of(lib);
}
- Map<String, byte[]> expected =
- IntegrationTestSupport.runJavac(input.sources, classpathJar, BOOTCLASSPATH);
+ Map<String, byte[]> expected = IntegrationTestSupport.runJavac(input.sources, classpathJar);
- Map<String, byte[]> actual =
- IntegrationTestSupport.runTurbine(input.sources, classpathJar, BOOTCLASSPATH);
+ Map<String, byte[]> actual = IntegrationTestSupport.runTurbine(input.sources, classpathJar);
assertThat(IntegrationTestSupport.dump(IntegrationTestSupport.sortMembers(actual)))
.isEqualTo(IntegrationTestSupport.dump(IntegrationTestSupport.canonicalize(expected)));
diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java
index 7d916fd..6409d4d 100644
--- a/javatests/com/google/turbine/lower/LowerTest.java
+++ b/javatests/com/google/turbine/lower/LowerTest.java
@@ -17,9 +17,11 @@
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 com.google.common.base.Joiner;
+import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
@@ -27,27 +29,23 @@
import com.google.turbine.binder.Binder.BindingResult;
import com.google.turbine.binder.ClassPathBinder;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
-import com.google.turbine.binder.bytecode.BytecodeBoundClass;
-import com.google.turbine.binder.env.CompoundEnv;
import com.google.turbine.binder.env.SimpleEnv;
-import com.google.turbine.binder.lookup.TopLevelIndex;
import com.google.turbine.binder.sym.ClassSymbol;
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.MethodSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
-import com.google.turbine.bytecode.AsmUtils;
import com.google.turbine.bytecode.ByteReader;
import com.google.turbine.bytecode.ConstantPoolReader;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.parse.Parser;
+import com.google.turbine.testing.AsmUtils;
import com.google.turbine.type.Type;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -72,13 +70,8 @@
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
- private static final ImmutableList<Path> BOOTCLASSPATH =
- ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
-
@Test
public void hello() throws Exception {
- CompoundEnv<ClassSymbol, BytecodeBoundClass> classpath =
- ClassPathBinder.bind(ImmutableList.of(), BOOTCLASSPATH, TopLevelIndex.builder());
ImmutableList<Type.ClassTy> interfaceTypes =
ImmutableList.of(
@@ -219,7 +212,8 @@
Lower.lowerAll(
ImmutableMap.of(
new ClassSymbol("test/Test"), c, new ClassSymbol("test/Test$Inner"), i),
- classpath)
+ ImmutableList.of(),
+ TURBINE_BOOTCLASSPATH.env())
.bytes();
assertThat(AsmUtils.textify(bytes.get("test/Test")))
@@ -249,13 +243,15 @@
" class InnerMost {}",
" }",
"}"))),
- ImmutableList.of(),
- BOOTCLASSPATH);
- Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes();
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent());
+ Map<String, byte[]> lowered =
+ Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes();
List<String> attributes = new ArrayList<>();
new ClassReader(lowered.get("Test$Inner$InnerMost"))
.accept(
- new ClassVisitor(Opcodes.ASM5) {
+ new ClassVisitor(Opcodes.ASM6) {
@Override
public void visitInnerClass(
String name, String outerName, String innerName, int access) {
@@ -278,7 +274,7 @@
UTF_8));
Map<String, byte[]> actual =
- IntegrationTestSupport.runTurbine(input.sources, ImmutableList.of(), BOOTCLASSPATH);
+ IntegrationTestSupport.runTurbine(input.sources, ImmutableList.of());
ByteReader reader = new ByteReader(actual.get("Test"), 0);
assertThat(reader.u4()).isEqualTo(0xcafebabe); // magic
@@ -325,17 +321,19 @@
"class Test {",
" public @Anno int[][] xs;",
"}"))),
- ImmutableList.of(),
- BOOTCLASSPATH);
- Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes();
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent());
+ Map<String, byte[]> lowered =
+ Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes();
TypePath[] path = new TypePath[1];
new ClassReader(lowered.get("Test"))
.accept(
- new ClassVisitor(Opcodes.ASM5) {
+ new ClassVisitor(Opcodes.ASM6) {
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
- return new FieldVisitor(Opcodes.ASM5) {
+ return new FieldVisitor(Opcodes.ASM6) {
@Override
public AnnotationVisitor visitTypeAnnotation(
int typeRef, TypePath typePath, String desc, boolean visible) {
@@ -377,13 +375,12 @@
" static final boolean ZCONST = Lib.ZCONST || false;",
"}"));
- Map<String, byte[]> actual =
- IntegrationTestSupport.runTurbine(input, ImmutableList.of(lib), BOOTCLASSPATH);
+ Map<String, byte[]> actual = IntegrationTestSupport.runTurbine(input, ImmutableList.of(lib));
Map<String, Object> values = new LinkedHashMap<>();
new ClassReader(actual.get("Test"))
.accept(
- new ClassVisitor(Opcodes.ASM5) {
+ new ClassVisitor(Opcodes.ASM6) {
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
@@ -402,13 +399,15 @@
BindingResult bound =
Binder.bind(
ImmutableList.of(Parser.parse("@Deprecated class Test {}")),
- ImmutableList.of(),
- BOOTCLASSPATH);
- Map<String, byte[]> lowered = Lower.lowerAll(bound.units(), bound.classPathEnv()).bytes();
+ ClassPathBinder.bindClasspath(ImmutableList.of()),
+ TURBINE_BOOTCLASSPATH,
+ /* moduleVersion=*/ Optional.absent());
+ Map<String, byte[]> lowered =
+ Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes();
int[] acc = {0};
new ClassReader(lowered.get("Test"))
.accept(
- new ClassVisitor(Opcodes.ASM5) {
+ new ClassVisitor(Opcodes.ASM6) {
@Override
public void visit(
int version,
@@ -475,10 +474,8 @@
noImports = builder.build();
}
- Map<String, byte[]> expected =
- IntegrationTestSupport.runJavac(noImports, ImmutableList.of(), BOOTCLASSPATH);
- Map<String, byte[]> actual =
- IntegrationTestSupport.runTurbine(sources, ImmutableList.of(), BOOTCLASSPATH);
+ Map<String, byte[]> expected = IntegrationTestSupport.runJavac(noImports, ImmutableList.of());
+ Map<String, byte[]> actual = IntegrationTestSupport.runTurbine(sources, ImmutableList.of());
assertThat(IntegrationTestSupport.dump(IntegrationTestSupport.sortMembers(actual)))
.isEqualTo(IntegrationTestSupport.dump(IntegrationTestSupport.canonicalize(expected)));
}
diff --git a/javatests/com/google/turbine/lower/ModuleIntegrationTest.java b/javatests/com/google/turbine/lower/ModuleIntegrationTest.java
new file mode 100644
index 0000000..d1bcb74
--- /dev/null
+++ b/javatests/com/google/turbine/lower/ModuleIntegrationTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2018 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.lower;
+
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.turbine.binder.JimageClassBinder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.jar.JarEntry;
+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.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ModuleIntegrationTest {
+
+ @Parameters(name = "{index}: {0}")
+ public static Iterable<Object[]> parameters() {
+ String[] testCases = {
+ "module-info.test", //
+ "classpath.test",
+ "multimodule.test",
+ };
+ return ImmutableList.copyOf(testCases).stream().map(x -> new Object[] {x}).collect(toList());
+ }
+
+ final String test;
+
+ public ModuleIntegrationTest(String test) {
+ this.test = test;
+ }
+
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Test
+ public void test() throws Exception {
+ if (Double.parseDouble(System.getProperty("java.class.version")) < 53) {
+ // only run on JDK 9 and later
+ return;
+ }
+
+ IntegrationTestSupport.TestInput input =
+ IntegrationTestSupport.TestInput.parse(
+ new String(
+ ByteStreams.toByteArray(getClass().getResourceAsStream("moduletestdata/" + test)),
+ UTF_8));
+
+ ImmutableList<Path> classpathJar = ImmutableList.of();
+ if (!input.classes.isEmpty()) {
+ Map<String, byte[]> classpath =
+ IntegrationTestSupport.runJavac(
+ input.classes,
+ /* classpath= */ ImmutableList.of(),
+ ImmutableList.of("--release", "9", "--module-version=43"));
+ Path lib = temporaryFolder.newFile("lib.jar").toPath();
+ try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) {
+ for (Map.Entry<String, byte[]> entry : classpath.entrySet()) {
+ jos.putNextEntry(new JarEntry(entry.getKey() + ".class"));
+ jos.write(entry.getValue());
+ }
+ }
+ classpathJar = ImmutableList.of(lib);
+ }
+
+ Map<String, byte[]> expected =
+ IntegrationTestSupport.runJavac(
+ input.sources, classpathJar, ImmutableList.of("--release", "9", "--module-version=42"));
+
+ Map<String, byte[]> actual =
+ IntegrationTestSupport.runTurbine(
+ input.sources, classpathJar, JimageClassBinder.bindDefault(), Optional.of("42"));
+
+ assertEquals(dump(expected), dump(actual));
+ }
+
+ private String dump(Map<String, byte[]> map) throws Exception {
+ return IntegrationTestSupport.dump(
+ map.entrySet()
+ .stream()
+ .filter(e -> e.getKey().endsWith("module-info"))
+ .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
+ }
+}
diff --git a/javatests/com/google/turbine/lower/testdata/B70953542.test b/javatests/com/google/turbine/lower/testdata/B70953542.test
new file mode 100644
index 0000000..f9e7503
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/B70953542.test
@@ -0,0 +1,7 @@
+=== Test.java ===
+class Test<T> {
+ class B<U> extends Test<U> {}
+ class A<V> extends B<V> {
+ B<V> b;
+ }
+}
\ No newline at end of file
diff --git a/javatests/com/google/turbine/lower/testdata/B74332665.test b/javatests/com/google/turbine/lower/testdata/B74332665.test
new file mode 100644
index 0000000..93db33f
--- /dev/null
+++ b/javatests/com/google/turbine/lower/testdata/B74332665.test
@@ -0,0 +1,9 @@
+=== B74332665.java ===
+class B74332665 {
+ interface I {
+ private boolean f() {
+ return false;
+ }
+ boolean g();
+ }
+}
diff --git a/javatests/com/google/turbine/main/MainTest.java b/javatests/com/google/turbine/main/MainTest.java
index 64ac245..9307cb0 100644
--- a/javatests/com/google/turbine/main/MainTest.java
+++ b/javatests/com/google/turbine/main/MainTest.java
@@ -17,22 +17,27 @@
package com.google.turbine.main;
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 static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
+import com.google.turbine.diag.TurbineError;
import com.google.turbine.options.TurbineOptions;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -42,9 +47,6 @@
@RunWith(JUnit4.class)
public class MainTest {
- static final ImmutableList<String> BOOTCLASSPATH =
- ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar").toString());
-
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
@@ -63,14 +65,13 @@
try {
Main.compile(
- TurbineOptions.builder()
+ optionsWithBootclasspath()
.setSourceJars(ImmutableList.of(sourcesa.toString(), sourcesb.toString()))
- .addBootClassPathEntries(BOOTCLASSPATH)
.setOutput(output.toString())
.build());
fail();
- } catch (IllegalArgumentException e) {
- assertThat(e.getMessage()).contains("Multiple entries with same key: Test");
+ } catch (TurbineError e) {
+ assertThat(e.getMessage()).contains("error: duplicate declaration of Test");
}
}
@@ -83,9 +84,8 @@
boolean ok =
Main.compile(
- TurbineOptions.builder()
+ optionsWithBootclasspath()
.addSources(ImmutableList.of(src.toString()))
- .addBootClassPathEntries(BOOTCLASSPATH)
.setOutput(output.toString())
.build());
assertThat(ok).isTrue();
@@ -106,9 +106,8 @@
boolean ok =
Main.compile(
- TurbineOptions.builder()
+ optionsWithBootclasspath()
.setSourceJars(ImmutableList.of(srcjar.toString()))
- .addBootClassPathEntries(BOOTCLASSPATH)
.setOutput(output.toString())
.build());
assertThat(ok).isTrue();
@@ -131,6 +130,11 @@
@Test
public void moduleInfos() throws IOException {
+ if (Double.parseDouble(System.getProperty("java.class.version")) < 53) {
+ // only run on JDK 9 and later
+ return;
+ }
+
Path srcjar = temporaryFolder.newFile("lib.srcjar").toPath();
try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(srcjar))) {
jos.putNextEntry(new JarEntry("module-info.java"));
@@ -147,14 +151,82 @@
boolean ok =
Main.compile(
TurbineOptions.builder()
+ .setRelease("9")
.addSources(ImmutableList.of(src.toString()))
.setSourceJars(ImmutableList.of(srcjar.toString()))
- .addBootClassPathEntries(BOOTCLASSPATH)
.setOutput(output.toString())
.build());
assertThat(ok).isTrue();
Map<String, byte[]> data = readJar(output);
- assertThat(data.keySet()).isEmpty();
+ assertThat(data.keySet())
+ .containsExactly("foo/module-info.class", "bar/module-info.class", "baz/module-info.class");
+ }
+
+ @Test
+ public void testManifest() throws IOException {
+ Path src = temporaryFolder.newFile("Foo.java").toPath();
+ Files.write(src, "class Foo {}".getBytes(UTF_8));
+
+ Path output = temporaryFolder.newFile("output.jar").toPath();
+
+ boolean ok =
+ Main.compile(
+ optionsWithBootclasspath()
+ .addSources(ImmutableList.of(src.toString()))
+ .setTargetLabel("//foo:foo")
+ .setInjectingRuleKind("foo_library")
+ .setOutput(output.toString())
+ .build());
+ assertThat(ok).isTrue();
+
+ try (JarFile jarFile = new JarFile(output.toFile())) {
+ Manifest manifest = jarFile.getManifest();
+ Attributes attributes = manifest.getMainAttributes();
+ assertThat(attributes.getValue("Target-Label")).isEqualTo("//foo:foo");
+ assertThat(attributes.getValue("Injecting-Rule-Kind")).isEqualTo("foo_library");
+ assertThat(jarFile.getEntry(JarFile.MANIFEST_NAME).getLastModifiedTime().toInstant())
+ .isEqualTo(
+ LocalDateTime.of(2010, 1, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant());
+ }
+ }
+
+ @Test
+ public void emptyBootClassPath() throws IOException {
+ Path src = temporaryFolder.newFolder().toPath().resolve("java/lang/Object.java");
+ Files.createDirectories(src.getParent());
+ Files.write(src, "package java.lang; public class Object {}".getBytes(UTF_8));
+
+ Path output = temporaryFolder.newFile("output.jar").toPath();
+
+ boolean ok =
+ Main.compile(
+ TurbineOptions.builder()
+ .addSources(ImmutableList.of(src.toString()))
+ .setOutput(output.toString())
+ .build());
+ assertThat(ok).isTrue();
+
+ Map<String, byte[]> data = readJar(output);
+ assertThat(data.keySet()).containsExactly("java/lang/Object.class");
+ }
+
+ @Test
+ public void emptyBootClassPath_noJavaLang() throws IOException {
+ Path src = temporaryFolder.newFile("Test.java").toPath();
+ Files.write(src, "public class Test {}".getBytes(UTF_8));
+
+ Path output = temporaryFolder.newFile("output.jar").toPath();
+
+ try {
+ Main.compile(
+ TurbineOptions.builder()
+ .addSources(ImmutableList.of(src.toString()))
+ .setOutput(output.toString())
+ .build());
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().contains("java.lang");
+ }
}
}
diff --git a/javatests/com/google/turbine/options/TurbineOptionsTest.java b/javatests/com/google/turbine/options/TurbineOptionsTest.java
index fcde432..8342ccc 100644
--- a/javatests/com/google/turbine/options/TurbineOptionsTest.java
+++ b/javatests/com/google/turbine/options/TurbineOptionsTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -38,13 +37,7 @@
@Rule public final TemporaryFolder tmpFolder = new TemporaryFolder();
static final ImmutableList<String> BASE_ARGS =
- ImmutableList.of(
- "--output",
- "out.jar",
- "--target_label",
- "//java/com/google/test",
- "--rule_kind",
- "java_library");
+ ImmutableList.of("--output", "out.jar", "--target_label", "//java/com/google/test");
@Test
public void exhaustiveArgs() throws Exception {
@@ -58,16 +51,20 @@
"com.foo.MyProcessor",
"com.foo.OtherProcessor",
"--processorpath",
- "libproc1.jar:libproc2.jar",
+ "libproc1.jar",
+ "libproc2.jar",
"--classpath",
- "lib1.jar:lib2.jar",
+ "lib1.jar",
+ "lib2.jar",
"--bootclasspath",
- "rt.jar:zipfs.jar",
+ "rt.jar",
+ "zipfs.jar",
"--javacopts",
"-source",
"8",
"-target",
"8",
+ "--",
"--sources",
"Source1.java",
"Source2.java",
@@ -75,8 +72,8 @@
"out.jdeps",
"--target_label",
"//java/com/google/test",
- "--rule_kind",
- "java_library",
+ "--injecting_rule_kind",
+ "foo_library",
};
TurbineOptions options =
@@ -96,23 +93,19 @@
assertThat(options.sources()).containsExactly("Source1.java", "Source2.java");
assertThat(options.outputDeps()).hasValue("out.jdeps");
assertThat(options.targetLabel()).hasValue("//java/com/google/test");
- assertThat(options.ruleKind()).hasValue("java_library");
+ assertThat(options.injectingRuleKind()).hasValue("foo_library");
}
@Test
public void strictJavaDepsArgs() throws Exception {
String[] lines = {
- "--strict_java_deps",
- "OFF",
- "--direct_dependency",
+ "--classpath",
"blaze-out/foo/libbar.jar",
- "//foo/bar",
- "--indirect_dependency",
"blaze-out/foo/libbaz1.jar",
- "//foo/baz1",
- "--indirect_dependency",
"blaze-out/foo/libbaz2.jar",
- "//foo/baz2",
+ "blaze-out/proto/libproto.jar",
+ "--direct_dependencies",
+ "blaze-out/foo/libbar.jar",
"--deps_artifacts",
"foo.jdeps",
"bar.jdeps",
@@ -123,15 +116,38 @@
TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines)));
assertThat(options.targetLabel()).hasValue("//java/com/google/test");
- // TODO(cushon): containsExactlyEntriesIn once it makes a truth release
- assertThat(options.directJarsToTargets())
- .isEqualTo(ImmutableMap.of("blaze-out/foo/libbar.jar", "//foo/bar"));
- // TODO(cushon): containsExactlyEntriesIn once it makes a truth release
- assertThat(options.indirectJarsToTargets())
- .isEqualTo(
- ImmutableMap.of(
- "blaze-out/foo/libbaz1.jar", "//foo/baz1",
- "blaze-out/foo/libbaz2.jar", "//foo/baz2"));
+ assertThat(options.directJars()).containsExactly("blaze-out/foo/libbar.jar");
+ assertThat(options.depsArtifacts()).containsExactly("foo.jdeps", "bar.jdeps");
+ }
+
+ /** Makes sure turbine accepts old-style arguments. */
+ // TODO(b/72379900): Remove this.
+ @Test
+ public void testLegacyStrictJavaDepsArgs() throws Exception {
+ String[] lines = {
+ "--direct_dependency",
+ "blaze-out/foo/libbar.jar",
+ "//foo/bar",
+ "--indirect_dependency",
+ "blaze-out/foo/libbaz1.jar",
+ "//foo/baz1",
+ "--indirect_dependency",
+ "blaze-out/foo/libbaz2.jar",
+ "//foo/baz2",
+ "--indirect_dependency",
+ "blaze-out/proto/libproto.jar",
+ "//proto",
+ "java_proto_library",
+ "--deps_artifacts",
+ "foo.jdeps",
+ "bar.jdeps",
+ "",
+ };
+
+ TurbineOptions options =
+ TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines)));
+
+ assertThat(options.targetLabel()).hasValue("//java/com/google/test");
assertThat(options.depsArtifacts()).containsExactly("foo.jdeps", "bar.jdeps");
}
@@ -139,26 +155,9 @@
public void classpathArgs() throws Exception {
String[] lines = {
"--classpath",
- "liba.jar:libb.jar:libc.jar",
- "--processorpath",
- "libpa.jar:libpb.jar:libpc.jar",
- };
-
- TurbineOptions options =
- TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines)));
-
- assertThat(options.classPath()).containsExactly("liba.jar", "libb.jar", "libc.jar").inOrder();
- assertThat(options.processorPath())
- .containsExactly("libpa.jar", "libpb.jar", "libpc.jar")
- .inOrder();
- }
-
- @Test
- public void repeatedClasspath() throws Exception {
- String[] lines = {
- "--classpath",
"liba.jar",
- "libb.jar:libc.jar",
+ "libb.jar",
+ "libc.jar",
"--processorpath",
"libpa.jar",
"libpb.jar",
@@ -175,26 +174,53 @@
}
@Test
- public void optionalTargetLabelAndRuleKind() throws Exception {
+ public void repeatedClasspath() throws Exception {
+ String[] lines = {
+ "--classpath",
+ "liba.jar",
+ "libb.jar",
+ "libc.jar",
+ "--processorpath",
+ "libpa.jar",
+ "libpb.jar",
+ "libpc.jar",
+ };
+
+ TurbineOptions options =
+ TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines)));
+
+ assertThat(options.classPath()).containsExactly("liba.jar", "libb.jar", "libc.jar").inOrder();
+ assertThat(options.processorPath())
+ .containsExactly("libpa.jar", "libpb.jar", "libpc.jar")
+ .inOrder();
+ }
+
+ @Test
+ public void optionalTargetLabel() throws Exception {
String[] lines = {
"--output",
"out.jar",
"--classpath",
- "liba.jar:libb.jar:libc.jar",
+ "liba.jar",
+ "libb.jar",
+ "libc.jar",
"--processorpath",
- "libpa.jar:libpb.jar:libpc.jar",
+ "libpa.jar",
+ "libpb.jar",
+ "libpc.jar",
};
TurbineOptions options = TurbineOptionsParser.parse(Arrays.asList(lines));
- assertThat(options.ruleKind()).isAbsent();
assertThat(options.targetLabel()).isAbsent();
+ assertThat(options.injectingRuleKind()).isAbsent();
}
@Test
public void paramsFile() throws Exception {
Iterable<String> paramsArgs =
- Iterables.concat(BASE_ARGS, Arrays.asList("--javacopts", "-source", "8", "-target", "8"));
+ Iterables.concat(
+ BASE_ARGS, Arrays.asList("--javacopts", "-source", "8", "-target", "8", "--"));
Path params = tmpFolder.newFile("params.txt").toPath();
Files.write(params, paramsArgs, StandardCharsets.UTF_8);
@@ -235,4 +261,89 @@
assertThat(e).hasMessage("output must not be null");
}
}
+
+ @Test
+ public void paramsFileExists() throws Exception {
+ String[] lines = {
+ "@/NOSUCH", "--javacopts", "-source", "7", "--",
+ };
+ AssertionError expected = null;
+ try {
+ TurbineOptionsParser.parse(Arrays.asList(lines));
+ } catch (AssertionError e) {
+ expected = e;
+ }
+ if (expected == null) {
+ fail();
+ }
+ assertThat(expected).hasMessageThat().contains("params file does not exist");
+ }
+
+ @Test
+ public void emptyParamsFiles() throws Exception {
+ Path params = tmpFolder.newFile("params.txt").toPath();
+ Files.write(params, new byte[0]);
+ String[] lines = {
+ "--sources", "A.java", "@" + params.toAbsolutePath(), "B.java",
+ };
+ TurbineOptions options =
+ TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines)));
+ assertThat(options.sources()).containsExactly("A.java", "B.java").inOrder();
+ }
+
+ @Test
+ public void javacopts() throws Exception {
+ String[] lines = {
+ "--javacopts", "--release", "9", "--", "--sources", "Test.java",
+ };
+
+ TurbineOptions options =
+ TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines)));
+
+ assertThat(options.javacOpts()).containsExactly("--release", "9").inOrder();
+ assertThat(options.sources()).containsExactly("Test.java");
+ }
+
+ @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");
+ }
+ }
+
+ @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 `--`");
+ }
+ }
+
+ @Test
+ public void releaseJavacopts() throws Exception {
+ TurbineOptions options =
+ TurbineOptionsParser.parse(
+ Iterables.concat(
+ BASE_ARGS,
+ Arrays.asList(
+ "--release",
+ "9",
+ "--javacopts",
+ "--release",
+ "8",
+ "--release",
+ "7",
+ "--release",
+ "--")));
+ assertThat(options.release()).hasValue("7");
+ assertThat(options.javacOpts())
+ .containsExactly("--release", "8", "--release", "7", "--release")
+ .inOrder();
+ }
}
diff --git a/javatests/com/google/turbine/parse/ParseErrorTest.java b/javatests/com/google/turbine/parse/ParseErrorTest.java
index b2dc7a2..0ec6208 100644
--- a/javatests/com/google/turbine/parse/ParseErrorTest.java
+++ b/javatests/com/google/turbine/parse/ParseErrorTest.java
@@ -67,11 +67,10 @@
} catch (TurbineError e) {
assertThat(e.getMessage())
.isEqualTo(
- Joiner.on('\n')
- .join(
- "<>:1: error: unexpected token: void",
- "public static void main(String[] args) {}",
- " ^"));
+ lines(
+ "<>:1: error: unexpected token: void",
+ "public static void main(String[] args) {}",
+ " ^"));
}
}
@@ -84,11 +83,10 @@
} catch (TurbineError e) {
assertThat(e.getMessage())
.isEqualTo(
- Joiner.on('\n')
- .join(
- "<>:1: error: unexpected identifier 'clas'", //
- "public clas Test {}",
- " ^"));
+ lines(
+ "<>:1: error: unexpected identifier 'clas'", //
+ "public clas Test {}",
+ " ^"));
}
}
@@ -101,11 +99,30 @@
} catch (TurbineError e) {
assertThat(e.getMessage())
.isEqualTo(
- Joiner.on('\n')
- .join(
- "<>:2: error: unexpected end of input", //
- "",
- "^"));
+ 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.getMessage())
+ .isEqualTo(
+ lines(
+ "<>:1: error: invalid annotation argument", //
+ "@A(x = System.err.println()) class Test {}",
+ " ^"));
+ }
+ }
+
+ private static String lines(String... lines) {
+ return Joiner.on(System.lineSeparator()).join(lines);
+ }
}
diff --git a/javatests/com/google/turbine/parse/ParserIntegrationTest.java b/javatests/com/google/turbine/parse/ParserIntegrationTest.java
index d856cb3..2503553 100644
--- a/javatests/com/google/turbine/parse/ParserIntegrationTest.java
+++ b/javatests/com/google/turbine/parse/ParserIntegrationTest.java
@@ -21,6 +21,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Function;
+import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.io.CharStreams;
import com.google.turbine.tree.Tree;
@@ -28,6 +29,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -72,6 +74,7 @@
"packinfo1.input",
"weirdstring.input",
"type_annotations.input",
+ "module-info.input",
};
return Iterables.transform(
Arrays.asList(tests),
@@ -97,9 +100,9 @@
try (InputStreamReader in = new InputStreamReader(stream, UTF_8)) {
result = CharStreams.toString(in);
}
- String[] pieces = result.split("===+");
- String input = pieces[0].trim();
- String expected = pieces.length > 1 ? pieces[1].trim() : input;
+ List<String> pieces = Splitter.onPattern("===+").splitToList(result);
+ String input = pieces.get(0).trim();
+ String expected = pieces.size() > 1 ? pieces.get(1).trim() : input;
Tree.CompUnit unit = Parser.parse(input);
assertThat(unit.toString().trim()).isEqualTo(expected);
}
diff --git a/javatests/com/google/turbine/parse/testdata/module-info.input b/javatests/com/google/turbine/parse/testdata/module-info.input
new file mode 100644
index 0000000..892a3b1
--- /dev/null
+++ b/javatests/com/google/turbine/parse/testdata/module-info.input
@@ -0,0 +1,29 @@
+import a.A;
+import a.B;
+import com.google.Foo;
+import com.google.Baz;
+
+@A
+@B
+module com.google.m {
+ requires java.compiler;
+ requires transitive jdk.compiler;
+ requires static java.base;
+ exports com.google.p1;
+ exports com.google.p2 to
+ java.base;
+ exports com.google.p3 to
+ java.base,
+ java.compiler;
+ opens com.google.p1;
+ opens com.google.p2 to
+ java.base;
+ opens com.google.p3 to
+ java.base,
+ java.compiler;
+ uses Foo;
+ uses com.google.Bar;
+ provides com.google.Baz with
+ Foo,
+ com.google.Bar;
+}
diff --git a/javatests/com/google/turbine/bytecode/AsmUtils.java b/javatests/com/google/turbine/testing/AsmUtils.java
similarity index 97%
rename from javatests/com/google/turbine/bytecode/AsmUtils.java
rename to javatests/com/google/turbine/testing/AsmUtils.java
index 2591652..5b5e102 100644
--- a/javatests/com/google/turbine/bytecode/AsmUtils.java
+++ b/javatests/com/google/turbine/testing/AsmUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.turbine.bytecode;
+package com.google.turbine.testing;
import java.io.PrintWriter;
import java.io.StringWriter;
diff --git a/javatests/com/google/turbine/testing/TestClassPaths.java b/javatests/com/google/turbine/testing/TestClassPaths.java
new file mode 100644
index 0000000..bf38913
--- /dev/null
+++ b/javatests/com/google/turbine/testing/TestClassPaths.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 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 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.options.TurbineOptions;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+public 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("")))
+ .map(Paths::get)
+ .filter(Files::exists)
+ .collect(toImmutableList());
+
+ public static final ClassPath TURBINE_BOOTCLASSPATH = getTurbineBootclasspath();
+
+ private static ClassPath getTurbineBootclasspath() {
+ try {
+ if (!BOOTCLASSPATH.isEmpty()) {
+ return ClassPathBinder.bindClasspath(BOOTCLASSPATH);
+ }
+ return CtSymClassBinder.bind("8");
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ /**
+ * Return an {@link TurbineOptions} builder, with either {@code --bootclasspath} or {@link
+ * --release} set to a JDK 8 equivalent.
+ */
+ public static TurbineOptions.Builder optionsWithBootclasspath() {
+ TurbineOptions.Builder options = TurbineOptions.builder();
+ if (!BOOTCLASSPATH.isEmpty()) {
+ options.addBootClassPathEntries(
+ BOOTCLASSPATH.stream().map(Path::toString).collect(toImmutableList()));
+ } else {
+ options.setRelease("8");
+ }
+ return options;
+ }
+}
diff --git a/javatests/com/google/turbine/zip/ZipTest.java b/javatests/com/google/turbine/zip/ZipTest.java
index 90b2315..67dcfe7 100644
--- a/javatests/com/google/turbine/zip/ZipTest.java
+++ b/javatests/com/google/turbine/zip/ZipTest.java
@@ -127,7 +127,7 @@
Files.delete(path);
try (FileSystem fs =
FileSystems.newFileSystem(
- URI.create("jar:file:" + path.toAbsolutePath()), ImmutableMap.of("create", "true"))) {
+ URI.create("jar:" + path.toUri()), ImmutableMap.of("create", "true"))) {
for (int i = 0; i < 3; i++) {
String name = "entry" + i;
byte[] bytes = name.getBytes(UTF_8);
diff --git a/pom.xml b/pom.xml
index 6829bce..58bc5f4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,7 @@
<properties>
<javac.version>9-dev-r3297-4</javac.version>
+ <asm.version>6.0</asm.version>
</properties>
<dependencies>
@@ -41,8 +42,20 @@
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
- <artifactId>asm-debug-all</artifactId>
- <version>5.1</version>
+ <artifactId>asm</artifactId>
+ <version>${asm.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-tree</artifactId>
+ <version>${asm.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-util</artifactId>
+ <version>${asm.version}</version>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/turbine.iml b/turbine.iml
deleted file mode 100644
index 3e9b3e9..0000000
--- a/turbine.iml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
- <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
- <output url="file://$MODULE_DIR$/target/classes" />
- <output-test url="file://$MODULE_DIR$/target/test-classes" />
- <content url="file://$MODULE_DIR$">
- <sourceFolder url="file://$MODULE_DIR$/java" isTestSource="false" />
- <sourceFolder url="file://$MODULE_DIR$/javatests" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/target/generated-sources/protobuf/java" isTestSource="false" generated="true" />
- <excludeFolder url="file://$MODULE_DIR$/target/classes" />
- <excludeFolder url="file://$MODULE_DIR$/target/maven-archiver" />
- <excludeFolder url="file://$MODULE_DIR$/target/maven-status" />
- <excludeFolder url="file://$MODULE_DIR$/target/protoc-dependencies" />
- <excludeFolder url="file://$MODULE_DIR$/target/protoc-plugins" />
- <excludeFolder url="file://$MODULE_DIR$/target/surefire-reports" />
- <excludeFolder url="file://$MODULE_DIR$/target/test-classes" />
- </content>
- <orderEntry type="inheritedJdk" />
- <orderEntry type="sourceFolder" forTests="false" />
- <orderEntry type="library" name="Maven: com.google.protobuf:protobuf-java:3.0.2" level="project" />
- <orderEntry type="library" name="Maven: com.google.guava:guava:19.0" level="project" />
- <orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:2.0.1" level="project" />
- <orderEntry type="library" name="Maven: com.google.errorprone:error_prone_annotations:2.0.12" level="project" />
- <orderEntry type="library" name="Maven: com.google.protobuf:protobuf-java:3.1.0" level="project" />
- <orderEntry type="library" scope="TEST" name="Maven: org.ow2.asm:asm-debug-all:5.1" level="project" />
- <orderEntry type="library" scope="TEST" name="Maven: com.google.errorprone:javac:1.9.0-dev-r2973-2" level="project" />
- <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
- <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
- <orderEntry type="library" scope="TEST" name="Maven: com.google.truth:truth:0.25" level="project" />
- <orderEntry type="library" scope="TEST" name="Maven: com.google.jimfs:jimfs:1.0" level="project" />
- </component>
-</module>
\ No newline at end of file