blob: c0e5748c233ff53d2b3abde85bcb0dcd41c7e86a [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.javac;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.CompilerContext;
import com.google.gwt.dev.jdt.TypeRefVisitor;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.util.arg.SourceLevel;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.thirdparty.guava.common.collect.ArrayListMultimap;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.io.BaseEncoding;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.Clinit;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.MissingTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* Manages the process of compiling {@link CompilationUnit}s.
*/
public class JdtCompiler {
/**
* A default processor that simply collects build units.
*/
public static final class DefaultUnitProcessor implements UnitProcessor {
private final List<CompilationUnit> results = new ArrayList<CompilationUnit>();
public DefaultUnitProcessor() {
}
public List<CompilationUnit> getResults() {
return Lists.normalizeUnmodifiable(results);
}
@Override
public void process(CompilationUnitBuilder builder, CompilationUnitDeclaration cud,
List<ImportReference> cudOriginaImports, List<CompiledClass> compiledClasses) {
builder.setClasses(compiledClasses).setTypes(Collections.<JDeclaredType> emptyList())
.setDependencies(new Dependencies()).setJsniMethods(Collections.<JsniMethod> emptyList())
.setMethodArgs(new MethodArgNamesLookup())
.setProblems(cud.compilationResult().getProblems());
results.add(builder.build());
}
}
/**
* Interface for processing units on the fly during compilation.
*/
public interface UnitProcessor {
void process(CompilationUnitBuilder builder, CompilationUnitDeclaration cud,
List<ImportReference> cudOriginalImports, List<CompiledClass> compiledClasses);
}
/**
* Adapts a {@link CompilationUnit} for a JDT compile.
*/
private static class Adapter implements ICompilationUnit {
private final CompilationUnitBuilder builder;
public Adapter(CompilationUnitBuilder builder) {
this.builder = builder;
}
public CompilationUnitBuilder getBuilder() {
return builder;
}
@Override
public char[] getContents() {
return builder.getSource().toCharArray();
}
@Override
public char[] getFileName() {
return builder.getLocation().toCharArray();
}
@Override
public char[] getMainTypeName() {
return Shared.getShortName(builder.getTypeName()).toCharArray();
}
@Override
public char[][] getPackageName() {
String packageName = Shared.getPackageName(builder.getTypeName());
return CharOperation.splitOn('.', packageName.toCharArray());
}
@Override
public boolean ignoreOptionalProblems() {
return false;
}
@Override
public String toString() {
return builder.toString();
}
}
/**
* ParserImpl intercepts parsing of the source to get rid of methods, fields and classes
* annotated with a *.GwtIncompatible annotation.
*/
private static class ParserImpl extends Parser {
// A place to stash imports before removal. These are needed to correctly check the
// references in Jsni.
// TODO(rluble): find a more modular way for this fix.
public final ListMultimap<CompilationUnitDeclaration, ImportReference> originalImportsByCud =
ArrayListMultimap.create();
public ParserImpl(ProblemReporter problemReporter, boolean optimizeStringLiterals) {
super(problemReporter, optimizeStringLiterals);
}
/**
* Overrides the main parsing entry point to filter out elements annotated with
* {@code GwtIncompatible}.
*/
@Override
public CompilationUnitDeclaration parse(ICompilationUnit sourceUnit,
CompilationResult compilationResult) {
// Never dietParse(), otherwise GwtIncompatible annotations in anonymoous inner classes
// would be ignored.
boolean saveDiet = this.diet;
this.diet = false;
CompilationUnitDeclaration decl = super.parse(sourceUnit, compilationResult);
this.diet = saveDiet;
// Remove @GwtIncompatible classes and members.
// It is safe to remove @GwtIncompatible types, fields and methods on incomplete ASTs due
// to parsing errors.
GwtIncompatiblePreprocessor.preproccess(decl);
if (decl.imports != null) {
originalImportsByCud.putAll(decl, Arrays.asList(decl.imports));
}
if (decl.hasErrors()) {
// The unit has parsing errors; its JDT AST might not be complete. In this case do not
// remove unused imports as it is not safe to do so. UnusedImportsRemover would remove
// imports for types only referred from parts of the AST that was not constructed.
// Later the error reporting logic would complain about missing types for these references
// potentially burying the real error among many spurious errors.
return decl;
}
if (removeUnusedImports) {
// Lastly remove any unused imports
UnusedImportsRemover.exec(decl);
}
return decl;
}
}
/**
* Maximum number of JDT compiler errors or abort requests before it actually returns
* a fatal error to the user.
*/
private static final double ABORT_COUNT_MAX = 100;
private static class CompilerImpl extends Compiler {
private final TreeLogger logger;
private int abortCount = 0;
private final UnitProcessor processor;
private final Map<String, CompiledClass> internalTypes;
public CompilerImpl(
TreeLogger logger,
CompilerOptions compilerOptions,
INameEnvironment nameEnvironment,
UnitProcessor processor,
Map<String, CompiledClass> internalTypes) {
super(nameEnvironment,
DefaultErrorHandlingPolicies.proceedWithAllProblems(),
compilerOptions,
new ICompilerRequestorImpl(),
new DefaultProblemFactory(Locale.getDefault()));
this.logger = logger;
this.processor = processor;
this.internalTypes = internalTypes;
}
/**
* Make the JDT compiler throw the exception so that it can be caught in {@link #doCompile}.
*/
@Override
protected void handleInternalException(AbortCompilation abortException,
CompilationUnitDeclaration unit) {
// Context: The JDT compiler doesn't rethrow AbortCompilation in Compiler.compile().
// Instead it just exits early with a random selection of compilation units never getting
// compiled. This makes sense when an Eclipse user hits cancel, but in GWT, it will result
// in confusing errors later if we don't catch and handle it.
throw abortException;
}
/**
* Overrides the creation of the parser to use one that filters out elements annotated with
* {@code GwtIncompatible}.
*/
@Override
public void initializeParser() {
this.parser = new ParserImpl(this.problemReporter,
this.options.parseLiteralExpressionsAsConstants);
}
@Override
public void process(CompilationUnitDeclaration cud, int i) {
try {
super.process(cud, i);
} catch (AbortCompilation e) {
abortCount++;
String filename = new String(cud.getFileName());
logger.log(TreeLogger.WARN, "JDT aborted: " + filename + ": " + e.problem.getMessage());
if (abortCount >= ABORT_COUNT_MAX) {
logger.log(TreeLogger.ERROR, "JDT threw too many exceptions.");
throw e;
}
return; // continue without it; it might be a server-side class.
} catch (RuntimeException e) {
abortCount++;
String filename = new String(cud.getFileName());
logger.log(TreeLogger.WARN, "JDT threw an exception: " + filename + ": " + e);
if (abortCount >= ABORT_COUNT_MAX) {
logger.log(TreeLogger.ERROR, "JDT threw too many exceptions.");
throw new AbortCompilation(cud.compilationResult, e);
}
return; // continue without it; it might be a server-side class.
}
ClassFile[] classFiles = cud.compilationResult().getClassFiles();
Map<ClassFile, CompiledClass> results = new LinkedHashMap<ClassFile, CompiledClass>();
for (ClassFile classFile : classFiles) {
createCompiledClass(classFile, results);
}
List<CompiledClass> compiledClasses = new ArrayList<CompiledClass>(results.values());
addBinaryTypes(compiledClasses, internalTypes);
ICompilationUnit icu = cud.compilationResult().compilationUnit;
Adapter adapter = (Adapter) icu;
CompilationUnitBuilder builder = adapter.getBuilder();
// TODO(rluble): jsni method parsing should probably be done right after Java parsing, at
// that moment the original list of imports is still present and this hack would not be
// needed.
assert parser instanceof ParserImpl;
// Retrieve the original list of imports and dispose.
List<ImportReference> cudOriginalImports =
((ParserImpl) parser).originalImportsByCud.removeAll(cud);
processor.process(builder, cud, cudOriginalImports, compiledClasses);
}
/**
* Recursively creates enclosing types first.
*/
private void createCompiledClass(ClassFile classFile, Map<ClassFile, CompiledClass> results) {
if (results.containsKey(classFile)) {
// Already created.
return;
}
CompiledClass enclosingClass = null;
if (classFile.enclosingClassFile != null) {
ClassFile enclosingClassFile = classFile.enclosingClassFile;
createCompiledClass(enclosingClassFile, results);
enclosingClass = results.get(enclosingClassFile);
assert enclosingClass != null;
}
String internalName = CharOperation.charToString(classFile.fileName());
String sourceName = JdtUtil.getSourceName(classFile.referenceBinding);
// TODO(cromwellian) implement Retrolambda on output?
CompiledClass result = new CompiledClass(classFile.getBytes(), enclosingClass,
isLocalType(classFile), internalName, sourceName);
results.put(classFile, result);
}
int getAbortCount() {
return abortCount;
}
}
/**
* Hook point to accept results.
*/
private static class ICompilerRequestorImpl implements ICompilerRequestor {
@Override
public void acceptResult(CompilationResult result) {
}
}
/**
* How JDT receives files from the environment.
*/
private static class INameEnvironmentImpl implements INameEnvironment {
/**
* Remembers types that have been found in the classpath or not found at all to avoid
* unnecessary resource scanning.
*/
private final Map<String, NameEnvironmentAnswer> cachedClassPathAnswerByInternalName =
Maps.newHashMap();
private final Set<String> packages;
private final Set<String> notPackages = new HashSet<String>();
private final Map<String, CompiledClass> internalTypes;
public INameEnvironmentImpl(Set<String> packages, Map<String, CompiledClass> internalTypes) {
this.packages = packages;
this.internalTypes = internalTypes;
}
@Override
public void cleanup() {
}
@Override
public NameEnvironmentAnswer findType(char[] type, char[][] pkg) {
return findType(CharOperation.arrayConcat(pkg, type));
}
@Override
public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
char[] internalNameChars = CharOperation.concatWith(compoundTypeName, '/');
String internalName = String.valueOf(internalNameChars);
// If we already know this is a package, take the shortcut.
if (packages.contains(internalName)) {
return null;
}
NameEnvironmentAnswer cachedAnswer = findTypeInCache(internalName);
if (cachedAnswer != null) {
return cachedAnswer;
}
NameEnvironmentAnswer classPathAnswer = findTypeInClassPath(internalName);
if (classPathAnswer != null) {
return classPathAnswer;
}
return null;
}
/*
Generated from:
public class LambdaMetafactory {
public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName,
MethodType invokedType, MethodType samMethodType, MethodHandle implMethod,
MethodType instantiatedMethodType) {
return null;
}
public static CallSite altMetafactory(MethodHandles.Lookup caller, String invokedName,
MethodType invokedType, Object... args) {
return null;
}
}
*/
private byte[] getLambdaMetafactoryBytes() {
return BaseEncoding.base64().decode(
"yv66vgAAADMAFwoAAwARBwASBwATAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJU"
+ "YWJsZQEAC21ldGFmYWN0b3J5BwAVAQAGTG9va3VwAQAMSW5uZXJDbGFzc2VzAQDMKExqYXZhL2xh"
+ "bmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwO0xqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xh"
+ "bmcvaW52b2tlL01ldGhvZFR5cGU7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTtMamF2YS9s"
+ "YW5nL2ludm9rZS9NZXRob2RIYW5kbGU7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTspTGph"
+ "dmEvbGFuZy9pbnZva2UvQ2FsbFNpdGU7AQAOYWx0TWV0YWZhY3RvcnkBAIYoTGphdmEvbGFuZy9p"
+ "bnZva2UvTWV0aG9kSGFuZGxlcyRMb29rdXA7TGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9p"
+ "bnZva2UvTWV0aG9kVHlwZTtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvaW52b2tlL0Nh"
+ "bGxTaXRlOwEAClNvdXJjZUZpbGUBABZMYW1iZGFNZXRhZmFjdG9yeS5qYXZhDAAEAAUBACJqYXZh"
+ "L2xhbmcvaW52b2tlL0xhbWJkYU1ldGFmYWN0b3J5AQAQamF2YS9sYW5nL09iamVjdAcAFgEAJWph"
+ "dmEvbGFuZy9pbnZva2UvTWV0aG9kSGFuZGxlcyRMb29rdXABAB5qYXZhL2xhbmcvaW52b2tlL01l"
+ "dGhvZEhhbmRsZXMAIQACAAMAAAAAAAMAAQAEAAUAAQAGAAAAHQABAAEAAAAFKrcAAbEAAAABAAcA"
+ "AAAGAAEAAAAGAAkACAAMAAEABgAAABoAAQAGAAAAAgGwAAAAAQAHAAAABgABAAAAEACJAA0ADgAB"
+ "AAYAAAAaAAEABAAAAAIBsAAAAAEABwAAAAYAAQAAABUAAgAPAAAAAgAQAAsAAAAKAAEACQAUAAoA"
+ "GQ==");
}
/*
Generated from:
public class SerializedLambda {
public SerializedLambda(Class<?> capturingClass,
String functionalInterfaceClass,
String functionalInterfaceMethodName,
String functionalInterfaceMethodSignature,
int implMethodKind,
String implClass,
String implMethodName,
String implMethodSignature,
String instantiatedMethodType,
Object[] capturedArgs) {
}
public String getCapturingClass() { return null; }
public String getFunctionalInterfaceClass() { return null; }
public String getFunctionalInterfaceMethodName() { return null; }
public String getFunctionalInterfaceMethodSignature() { return null; }
public String getImplClass() { return null; }
public String getImplMethodName() { return null; }
public String getImplMethodSignature() { return null; }
public int getImplMethodKind() { return 0; }
public final String getInstantiatedMethodType() { return null; }
public int getCapturedArgCount() { return 0; }
public Object getCapturedArg(int i) { return null; }
public String toString() { return super.toString(); }
}
*/
private byte[] getSerializedLambdaBytes() {
return BaseEncoding.base64().decode(
"yv66vgAAADMAIQoABAAcCgAEAB0HAB4HAB8BAAY8aW5pdD4BAKYoTGphdmEvbGFuZy9DbGFzcztM"
+ "amF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztJTGph"
+ "dmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJpbmc7TGphdmEv"
+ "bGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvT2JqZWN0OylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJs"
+ "ZQEACVNpZ25hdHVyZQEAqShMamF2YS9sYW5nL0NsYXNzPCo+O0xqYXZhL2xhbmcvU3RyaW5nO0xq"
+ "YXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvU3RyaW5nO0lMamF2YS9sYW5nL1N0cmluZztMamF2"
+ "YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztbTGphdmEv"
+ "bGFuZy9PYmplY3Q7KVYBABFnZXRDYXB0dXJpbmdDbGFzcwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7"
+ "AQAbZ2V0RnVuY3Rpb25hbEludGVyZmFjZUNsYXNzAQAgZ2V0RnVuY3Rpb25hbEludGVyZmFjZU1l"
+ "dGhvZE5hbWUBACVnZXRGdW5jdGlvbmFsSW50ZXJmYWNlTWV0aG9kU2lnbmF0dXJlAQAMZ2V0SW1w"
+ "bENsYXNzAQARZ2V0SW1wbE1ldGhvZE5hbWUBABZnZXRJbXBsTWV0aG9kU2lnbmF0dXJlAQARZ2V0"
+ "SW1wbE1ldGhvZEtpbmQBAAMoKUkBABlnZXRJbnN0YW50aWF0ZWRNZXRob2RUeXBlAQATZ2V0Q2Fw"
+ "dHVyZWRBcmdDb3VudAEADmdldENhcHR1cmVkQXJnAQAVKEkpTGphdmEvbGFuZy9PYmplY3Q7AQAI"
+ "dG9TdHJpbmcBAApTb3VyY2VGaWxlAQAVU2VyaWFsaXplZExhbWJkYS5qYXZhDAAFACAMABkADAEA"
+ "IWphdmEvbGFuZy9pbnZva2UvU2VyaWFsaXplZExhbWJkYQEAEGphdmEvbGFuZy9PYmplY3QBAAMo"
+ "KVYAIQADAAQAAAAAAA0AAQAFAAYAAgAHAAAAIQABAAsAAAAFKrcAAbEAAAABAAgAAAAKAAIAAAAN"
+ "AAQADgAJAAAAAgAKAAEACwAMAAEABwAAABoAAQABAAAAAgGwAAAAAQAIAAAABgABAAAAEAABAA0A"
+ "DAABAAcAAAAaAAEAAQAAAAIBsAAAAAEACAAAAAYAAQAAABEAAQAOAAwAAQAHAAAAGgABAAEAAAAC"
+ "AbAAAAABAAgAAAAGAAEAAAASAAEADwAMAAEABwAAABoAAQABAAAAAgGwAAAAAQAIAAAABgABAAAA"
+ "EwABABAADAABAAcAAAAaAAEAAQAAAAIBsAAAAAEACAAAAAYAAQAAABQAAQARAAwAAQAHAAAAGgAB"
+ "AAEAAAACAbAAAAABAAgAAAAGAAEAAAAVAAEAEgAMAAEABwAAABoAAQABAAAAAgGwAAAAAQAIAAAA"
+ "BgABAAAAFgABABMAFAABAAcAAAAaAAEAAQAAAAIDrAAAAAEACAAAAAYAAQAAABcAEQAVAAwAAQAH"
+ "AAAAGgABAAEAAAACAbAAAAABAAgAAAAGAAEAAAAYAAEAFgAUAAEABwAAABoAAQABAAAAAgOsAAAA"
+ "AQAIAAAABgABAAAAGQABABcAGAABAAcAAAAaAAEAAgAAAAIBsAAAAAEACAAAAAYAAQAAABoAAQAZ"
+ "AAwAAQAHAAAAHQABAAEAAAAFKrcAArAAAAABAAgAAAAGAAEAAAAbAAEAGgAAAAIAGw==");
}
private NameEnvironmentAnswer findTypeInCache(String internalName) {
if (!internalTypes.containsKey(internalName)) {
return null;
}
try {
return internalTypes.get(internalName).getNameEnvironmentAnswer();
} catch (ClassFormatException ex) {
return null;
}
}
private NameEnvironmentAnswer findTypeInClassPath(String internalName) {
// If the class was previously queried here return the cached result.
if (cachedClassPathAnswerByInternalName.containsKey(internalName)) {
// The return might be null if the class was not found at all.
return cachedClassPathAnswerByInternalName.get(internalName);
}
NameEnvironmentAnswer answer = doFindTypeInClassPath(internalName);
// Here we cache the answer even if it is null to denote that it was not found.
cachedClassPathAnswerByInternalName.put(internalName, answer);
return answer;
}
private NameEnvironmentAnswer doFindTypeInClassPath(String internalName) {
URL resource = getClassLoader().getResource(internalName + ".class");
if (resource != null) {
try (InputStream openStream = resource.openStream()) {
ClassFileReader classFileReader =
ClassFileReader.read(openStream, resource.toExternalForm(), true);
// In case insensitive file systems we might have found a resource whose name is different
// in case and should not be returned as an answer.
if (internalName.equals(CharOperation.charToString(classFileReader.getName()))) {
return new NameEnvironmentAnswer(classFileReader, null);
}
} catch (IOException | ClassFormatException e) {
// returns null indicating a failure.
}
}
// LambdaMetafactory and SerializedLambda byte-code side-artifacts of JDT compile and actually
// not referenced by our AST. However, these classes are only available in JDK8+ so
// JdtCompiler fails to validate the classes that are referencing it. We tackle that by
// providing a stub version if it is not found in the class path.
if (internalName.equals("java/lang/invoke/LambdaMetafactory")) {
try {
ClassFileReader cfr = new ClassFileReader(getLambdaMetafactoryBytes(),
"synthetic:java/lang/invoke/LambdaMetafactory".toCharArray(), true);
return new NameEnvironmentAnswer(cfr, null);
} catch (ClassFormatException e) {
e.printStackTrace();
}
} else if (internalName.equals("java/lang/invoke/SerializedLambda")) {
try {
ClassFileReader cfr = new ClassFileReader(getSerializedLambdaBytes(),
"synthetic:java/lang/invoke/SerializedLambda".toCharArray(), true);
return new NameEnvironmentAnswer(cfr, null);
} catch (ClassFormatException e) {
e.printStackTrace();
}
}
return null;
}
@Override
public boolean isPackage(char[][] parentPkg, char[] pkg) {
char[] pathChars = CharOperation.concatWith(parentPkg, pkg, '/');
String packageName = String.valueOf(pathChars);
return isPackage(packageName);
}
private boolean isPackage(String slashedPackageName) {
/*
* TODO(zundel): When cached CompiledClass instances are used, 'packages'
* does not contain all packages in the compile and this test fails the
* test on some packages.
*
* This is supposed to work via the call chain:
*
* CSB.doBuildFrom -> CompileMoreLater.addValidUnit
* -> JdtCompiler.addCompiledUnit
* -> addPackages()
*/
if (packages.contains(slashedPackageName)) {
return true;
}
if (notPackages.contains(slashedPackageName)) {
return false;
}
// Include class loader check for binary-only annotations.
if (caseSensitivePathExists(slashedPackageName)) {
addPackages(packages, slashedPackageName);
return true;
} else {
notPackages.add(slashedPackageName);
return false;
}
}
}
/**
* Compiles the given set of units. The units will be internally modified to
* reflect the results of compilation. If the compiler aborts, logs an error
* and throws UnableToCompleteException.
*/
public static List<CompilationUnit> compile(TreeLogger logger, CompilerContext compilerContext,
Collection<CompilationUnitBuilder> builders) throws UnableToCompleteException {
Event jdtCompilerEvent = SpeedTracerLogger.start(CompilerEventType.JDT_COMPILER);
try {
DefaultUnitProcessor processor = new DefaultUnitProcessor();
JdtCompiler compiler = new JdtCompiler(compilerContext, processor);
compiler.doCompile(logger, builders);
return processor.getResults();
} finally {
jdtCompilerEvent.end();
}
}
public static CompilerOptions getStandardCompilerOptions() {
CompilerOptions options = new CompilerOptions() {
{
warningThreshold.clearAll();
}
};
long jdtSourceLevel = jdtLevelByGwtLevel.get(SourceLevel.DEFAULT_SOURCE_LEVEL);
options.originalSourceLevel = jdtSourceLevel;
options.complianceLevel = jdtSourceLevel;
options.sourceLevel = jdtSourceLevel;
options.targetJDK = jdtSourceLevel;
// Make sure the annotations are stored in and accessible through the bindings.
options.storeAnnotations = true;
// Generate debug info for debugging the output.
options.produceDebugAttributes =
ClassFileConstants.ATTR_VARS | ClassFileConstants.ATTR_LINES
| ClassFileConstants.ATTR_SOURCE;
// Tricks like "boolean stopHere = true;" depend on this setting.
options.preserveAllLocalVariables = true;
// Let the JDT collect compilation unit dependencies
options.produceReferenceInfo = true;
// Turn off all warnings, saves some memory / speed.
options.reportUnusedDeclaredThrownExceptionIncludeDocCommentReference = false;
options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false;
options.inlineJsrBytecode = true;
return options;
}
public CompilerOptions getCompilerOptions() {
CompilerOptions options = getStandardCompilerOptions();
long jdtSourceLevel = jdtLevelByGwtLevel.get(sourceLevel);
options.originalSourceLevel = jdtSourceLevel;
options.complianceLevel = jdtSourceLevel;
options.sourceLevel = jdtSourceLevel;
options.targetJDK = jdtSourceLevel;
return options;
}
private static ReferenceBinding resolveType(LookupEnvironment lookupEnvironment,
String sourceOrBinaryName) {
ReferenceBinding type = null;
int p = sourceOrBinaryName.indexOf('$');
if (p > 0) {
// resolve an outer type before trying to get the cached inner
String cupName = sourceOrBinaryName.substring(0, p);
char[][] chars = CharOperation.splitOn('.', cupName.toCharArray());
ReferenceBinding outerType = lookupEnvironment.getType(chars);
if (outerType != null) {
// outer class was found
resolveRecursive(outerType);
chars = CharOperation.splitOn('.', sourceOrBinaryName.toCharArray());
type = lookupEnvironment.getCachedType(chars);
if (type == null) {
// no inner type; this is a pure failure
return null;
}
}
} else {
// just resolve the type straight out
char[][] chars = CharOperation.splitOn('.', sourceOrBinaryName.toCharArray());
type = lookupEnvironment.getType(chars);
}
if (type != null) {
if (type instanceof UnresolvedReferenceBinding) {
/*
* Since type is an instance of UnresolvedReferenceBinding, we know that
* the return value BinaryTypeBinding.resolveType will be of type
* ReferenceBinding
*/
type = (ReferenceBinding) BinaryTypeBinding.resolveType(type, lookupEnvironment, true);
}
// found it
return type;
}
// Assume that the last '.' should be '$' and try again.
//
p = sourceOrBinaryName.lastIndexOf('.');
if (p >= 0) {
sourceOrBinaryName =
sourceOrBinaryName.substring(0, p) + "$" + sourceOrBinaryName.substring(p + 1);
return resolveType(lookupEnvironment, sourceOrBinaryName);
}
return null;
}
/**
* Returns <code>true</code> if this is a local type, or if this type is
* nested inside of any local type.
*/
private static boolean isLocalType(ClassFile classFile) {
SourceTypeBinding b = classFile.referenceBinding;
while (!b.isStatic()) {
if (b instanceof LocalTypeBinding) {
return true;
}
b = ((NestedTypeBinding) b).enclosingType;
}
return false;
}
/**
* Recursively invoking {@link ReferenceBinding#memberTypes()} causes JDT to
* resolve and cache all nested types at arbitrary depth.
*/
private static void resolveRecursive(ReferenceBinding outerType) {
for (ReferenceBinding memberType : outerType.memberTypes()) {
resolveRecursive(memberType);
}
}
/**
* Maps internal names to compiled classes.
*/
private final Map<String, CompiledClass> internalTypes = new HashMap<String, CompiledClass>();
/**
* Remembers types that where not found during resolution to avoid unnecessary file scanning.
*/
private final Set<String> unresolvableReferences = Sets.newHashSet();
/**
* Only active during a compile.
*/
private transient CompilerImpl compilerImpl;
private final Set<String> packages = new HashSet<String>();
private final UnitProcessor processor;
/**
* Java source level compatibility.
*/
private final SourceLevel sourceLevel;
private CompilerContext compilerContext;
/**
* Controls whether the compiler strips unused imports.
*/
private static boolean removeUnusedImports = true;
/**
* Maps from SourceLevel, the GWT compiler Java source compatibility levels, to JDT
* Java source compatibility levels.
*/
private static final Map<SourceLevel, Long> jdtLevelByGwtLevel =
ImmutableMap.<SourceLevel, Long>of(
SourceLevel.JAVA8, ClassFileConstants.JDK1_8,
SourceLevel.JAVA9, ClassFileConstants.JDK9,
SourceLevel.JAVA10, ClassFileConstants.JDK10,
SourceLevel.JAVA11, ClassFileConstants.JDK11);
public JdtCompiler(CompilerContext compilerContext, UnitProcessor processor) {
this.compilerContext = compilerContext;
this.processor = processor;
this.sourceLevel = compilerContext.getOptions().getSourceLevel();
}
public void addCompiledUnit(CompilationUnit unit) {
addPackages(Shared.getPackageName(unit.getTypeName()).replace('.', '/'));
addBinaryTypes(unit.getCompiledClasses());
}
public ArrayList<String> collectApiRefs(final CompilationUnitDeclaration cud) {
final Set<String> apiRefs = new HashSet<String>();
class DependencyVisitor extends TypeRefVisitor {
public DependencyVisitor() {
super(cud);
}
@Override
public boolean visit(Argument arg, BlockScope scope) {
// Adapted from {@link Argument#traverse}.
// Don't visit annotations.
if (arg.type != null) {
arg.type.traverse(this, scope);
}
return false;
}
@Override
public boolean visit(Argument arg, ClassScope scope) {
// Adapted from {@link Argument#traverse}.
// Don't visit annotations.
if (arg.type != null) {
arg.type.traverse(this, scope);
}
return false;
}
@Override
public boolean visit(Block block, BlockScope scope) {
assert false : "Error in DepedencyVisitor; should never visit a block";
return false;
}
@Override
public boolean visit(Clinit clinit, ClassScope scope) {
return false;
}
@Override
public boolean visit(ConstructorDeclaration ctor, ClassScope scope) {
if (ctor.typeParameters != null) {
int typeParametersLength = ctor.typeParameters.length;
for (int i = 0; i < typeParametersLength; i++) {
ctor.typeParameters[i].traverse(this, ctor.scope);
}
}
traverse(ctor);
return false;
}
@Override
public boolean visit(FieldDeclaration fieldDeclaration, MethodScope scope) {
// Don't visit javadoc.
// Don't visit annotations.
if (fieldDeclaration.type != null) {
fieldDeclaration.type.traverse(this, scope);
}
// Don't visit initialization.
return false;
}
@Override
public boolean visit(Initializer initializer, MethodScope scope) {
return false;
}
@Override
public boolean visit(MethodDeclaration meth, ClassScope scope) {
if (meth.typeParameters != null) {
int typeParametersLength = meth.typeParameters.length;
for (int i = 0; i < typeParametersLength; i++) {
meth.typeParameters[i].traverse(this, meth.scope);
}
}
if (meth.returnType != null) {
meth.returnType.traverse(this, meth.scope);
}
traverse(meth);
return false;
}
@Override
public boolean visit(TypeDeclaration typeDeclaration, ClassScope scope) {
traverse(typeDeclaration);
return false;
}
@Override
public boolean visit(TypeDeclaration typeDeclaration, CompilationUnitScope scope) {
traverse(typeDeclaration);
return false;
}
@Override
protected void onMissingTypeRef(MissingTypeBinding referencedType,
CompilationUnitDeclaration unitOfReferrer, Expression expression) {
addReference(referencedType);
}
@Override
protected void onBinaryTypeRef(BinaryTypeBinding referencedType,
CompilationUnitDeclaration unitOfReferrer, Expression expression) {
if (!String.valueOf(referencedType.getFileName()).endsWith(".java")) {
// ignore binary-only annotations
return;
}
addReference(referencedType);
}
@Override
protected void onTypeRef(SourceTypeBinding referencedType,
CompilationUnitDeclaration unitOfReferrer) {
addReference(referencedType);
}
private void addReference(ReferenceBinding referencedType) {
apiRefs.add(JdtUtil.getSourceName(referencedType));
}
/**
* Adapted from {@link MethodDeclaration#traverse}.
*/
private void traverse(AbstractMethodDeclaration meth) {
// Don't visit javadoc.
// Don't visit annotations.
if (meth.arguments != null) {
int argumentLength = meth.arguments.length;
for (int i = 0; i < argumentLength; i++) {
meth.arguments[i].traverse(this, meth.scope);
}
}
if (meth.thrownExceptions != null) {
int thrownExceptionsLength = meth.thrownExceptions.length;
for (int i = 0; i < thrownExceptionsLength; i++) {
meth.thrownExceptions[i].traverse(this, meth.scope);
}
}
// Don't visit method bodies.
}
/**
* Adapted from {@link TypeDeclaration#traverse}.
*/
private void traverse(TypeDeclaration type) {
// Don't visit javadoc.
// Don't visit annotations.
if (type.superclass != null) {
type.superclass.traverse(this, type.scope);
}
if (type.superInterfaces != null) {
int length = type.superInterfaces.length;
for (int i = 0; i < length; i++) {
type.superInterfaces[i].traverse(this, type.scope);
}
}
if (type.typeParameters != null) {
int length = type.typeParameters.length;
for (int i = 0; i < length; i++) {
type.typeParameters[i].traverse(this, type.scope);
}
}
if (type.memberTypes != null) {
int length = type.memberTypes.length;
for (int i = 0; i < length; i++) {
type.memberTypes[i].traverse(this, type.scope);
}
}
if (type.fields != null) {
int length = type.fields.length;
for (int i = 0; i < length; i++) {
FieldDeclaration field;
if ((field = type.fields[i]).isStatic()) {
field.traverse(this, type.staticInitializerScope);
} else {
field.traverse(this, type.initializerScope);
}
}
}
if (type.methods != null) {
int length = type.methods.length;
for (int i = 0; i < length; i++) {
type.methods[i].traverse(this, type.scope);
}
}
}
}
DependencyVisitor visitor = new DependencyVisitor();
cud.traverse(visitor, cud.scope);
ArrayList<String> result = new ArrayList<String>(apiRefs);
Collections.sort(result);
return result;
}
/**
* Compiles source using the JDT. The {@link UnitProcessor#process} callback method will be called
* once for each compiled file. If the compiler aborts, logs a message and throws
* UnableToCompleteException.
*/
public void doCompile(TreeLogger logger, Collection<CompilationUnitBuilder> builders)
throws UnableToCompleteException {
if (builders.isEmpty()) {
return;
}
List<ICompilationUnit> icus = new ArrayList<ICompilationUnit>();
for (CompilationUnitBuilder builder : builders) {
addPackages(Shared.getPackageName(builder.getTypeName()).replace('.', '/'));
icus.add(new Adapter(builder));
}
compilerImpl =
new CompilerImpl(
logger,
getCompilerOptions(),
new INameEnvironmentImpl(packages, internalTypes),
processor,
internalTypes);
try {
compilerImpl.compile(icus.toArray(new ICompilationUnit[icus.size()]));
} catch (AbortCompilation e) {
final String compilerAborted = String.format("JDT compiler aborted after %d errors",
compilerImpl.getAbortCount());
if (e.problem == null) {
logger.log(TreeLogger.Type.ERROR, compilerAborted + ".");
} else if (e.problem.getOriginatingFileName() == null) {
logger.log(TreeLogger.Type.ERROR, compilerAborted + ": " + e.problem.getMessage());
} else {
String filename = new String(e.problem.getOriginatingFileName());
TreeLogger branch = logger.branch(TreeLogger.Type.ERROR,
"At " + filename + ": " + e.problem.getSourceLineNumber());
branch.log(TreeLogger.Type.ERROR, compilerAborted + ": " + e.problem.getMessage());
}
throw new UnableToCompleteException();
} finally {
compilerImpl = null;
}
}
public ReferenceBinding resolveType(String sourceOrBinaryName) {
if (unresolvableReferences.contains(sourceOrBinaryName)) {
return null;
}
ReferenceBinding typeBinding =
resolveType(compilerImpl.lookupEnvironment, sourceOrBinaryName);
if (typeBinding == null) {
unresolvableReferences.add(sourceOrBinaryName);
}
return typeBinding;
}
/**
* Sets whether the compiler should remove unused imports.
*/
public static void setRemoveUnusedImports(boolean remove) {
removeUnusedImports = remove;
}
private void addBinaryTypes(Collection<CompiledClass> compiledClasses) {
addBinaryTypes(compiledClasses, internalTypes);
}
private static void addBinaryTypes(
Collection<CompiledClass> compiledClasses, Map<String, CompiledClass> internalTypes) {
for (CompiledClass cc : compiledClasses) {
internalTypes.put(cc.getInternalName(), cc);
}
}
private void addPackages(String slashedPackageName) {
addPackages(packages, slashedPackageName);
}
private static void addPackages(Set<String> packages, String slashedPackageName) {
while (packages.add(slashedPackageName)) {
int pos = slashedPackageName.lastIndexOf('/');
if (pos > 0) {
slashedPackageName = slashedPackageName.substring(0, pos);
} else {
packages.add("");
break;
}
}
}
private static boolean caseSensitivePathExists(String resourcePath) {
URL resourceURL = getClassLoader().getResource(resourcePath + '/');
if (resourceURL == null) {
return false;
}
try {
File resourceFile = new File(resourceURL.toURI());
return Arrays.asList(resourceFile.getParentFile().list()).contains(resourceFile.getName());
} catch (URISyntaxException e) {
// The URL can not be converted to a URI.
} catch (IllegalArgumentException e) {
// A file instance can not be constructed from a URI.
}
// Some exception occurred while trying to make sure the name for the resource on disk is
// exactly the same as the one requested including case. If an exception is thrown in the
// process we assume that the URI does not refer to a resource in the filesystem and that the
// resource obtained from the classloader is the one requested.
return true;
}
private static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
}