/* * 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.InternalCompilerException; 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.JarURLConnection; 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.Enumeration; 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; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Manages the process of compiling {@link CompilationUnit}s. */ public class JdtCompiler { /** * Provides hooks for changing the behavior of the JdtCompiler when unknown * types are encountered during compilation. Currently used for allowing * external tools to provide source lazily when undefined references appear. */ public interface AdditionalTypeProviderDelegate { /** * Checks for additional packages which may contain additional compilation * units. * * @param slashedPackageName the '/' separated name of the package to find * @return <code>true</code> if such a package exists */ boolean doFindAdditionalPackage(String slashedPackageName); /** * Finds a new compilation unit on-the-fly for the requested type, if there * is an alternate mechanism for doing so. * * @param binaryName the binary name of the requested type * @return a unit answering the name, or <code>null</code> if no such unit * can be created */ GeneratedUnit doFindAdditionalType(String binaryName); } /** * 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()); } } /** * Static cache of all the JRE package names. */ public static class JreIndex { private static Set<String> packages = readPackages(); public static boolean contains(String name) { return packages.contains(name); } private static void addPackageRecursively(Set<String> packages, String pkg) { if (!packages.add(pkg)) { return; } int i = pkg.lastIndexOf('/'); if (i != -1) { addPackageRecursively(packages, pkg.substring(0, i)); } } private static Set<String> readPackages() { HashSet<String> pkgs = new HashSet<String>(); String klass = "java/lang/Object.class"; URL url = ClassLoader.getSystemClassLoader().getResource(klass); try { JarURLConnection connection = (JarURLConnection) url.openConnection(); JarFile f = connection.getJarFile(); Enumeration<JarEntry> entries = f.entries(); while (entries.hasMoreElements()) { JarEntry e = entries.nextElement(); String name = e.getName(); if (name.endsWith(".class")) { String pkg = Shared.getSlashedPackageFrom(name); addPackageRecursively(pkgs, pkg); } } return pkgs; } catch (IOException e) { throw new InternalCompilerException("Unable to find JRE", e); } } } /** * 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 class CompilerImpl extends Compiler { private TreeLogger logger; private int abortCount = 0; public CompilerImpl(TreeLogger logger, CompilerOptions compilerOptions) { super(new INameEnvironmentImpl(), DefaultErrorHandlingPolicies.proceedWithAllProblems(), compilerOptions, new ICompilerRequestorImpl(), new DefaultProblemFactory( Locale.getDefault())); this.logger = logger; } /** * 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); 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 class INameEnvironmentImpl implements INameEnvironment { @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 (JreIndex.contains(internalName) || packages.contains(internalName)) { return null; } NameEnvironmentAnswer cachedAnswer = findTypeInCache(internalName); if (cachedAnswer != null) { return cachedAnswer; } NameEnvironmentAnswer additionalProviderAnswer = findTypeInAdditionalProvider(internalName); if (additionalProviderAnswer != null) { return additionalProviderAnswer; } 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 findTypeInAdditionalProvider(String internalName) { if (additionalTypeProviderDelegate == null) { return null; } GeneratedUnit unit = additionalTypeProviderDelegate.doFindAdditionalType(internalName); if (unit == null) { return null; } return new NameEnvironmentAnswer(new Adapter(CompilationUnitBuilder.create(unit)), 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) { // Test the JRE explicitly, because the classloader trick doesn't work. if (JreIndex.contains(slashedPackageName)) { return true; } /* * 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; } if ((additionalTypeProviderDelegate != null && additionalTypeProviderDelegate .doFindAdditionalPackage(slashedPackageName))) { addPackages(slashedPackageName); return true; } // Include class loader check for binary-only annotations. if (caseSensitivePathExists(slashedPackageName)) { addPackages(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); } } private AdditionalTypeProviderDelegate additionalTypeProviderDelegate; /** * Maps internal names to compiled classes. */ private final Map<String, CompiledClass> internalTypes = new HashMap<String, CompiledClass>(); /** * 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(); /** * 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> notPackages = new HashSet<String>(); 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); 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()); 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; } public void setAdditionalTypeProviderDelegate(AdditionalTypeProviderDelegate newDelegate) { additionalTypeProviderDelegate = newDelegate; } /** * Sets whether the compiler should remove unused imports. */ public static void setRemoveUnusedImports(boolean remove) { removeUnusedImports = remove; } private void addBinaryTypes(Collection<CompiledClass> compiledClasses) { for (CompiledClass cc : compiledClasses) { internalTypes.put(cc.getInternalName(), cc); } } private void addPackages(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(); } }