/* * Copyright 2011 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.TreeLogger.HelpInfo; import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.dev.CompilerContext; import com.google.gwt.dev.javac.CompilationUnitBuilder.GeneratedCompilationUnit; import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.util.Messages; import com.google.gwt.dev.util.Util; import com.google.gwt.thirdparty.guava.common.collect.Lists; import org.eclipse.jdt.core.compiler.CategorizedProblem; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Set; /** * Handles some details of reporting errors in {@link CompilationUnit}s to the * console. */ public class CompilationProblemReporter { /** * Traverses a set of compilation units to record enough information to enable accurate and * detailed compilation error cause traces. */ public static void indexErrors(CompilationErrorsIndex compilationErrorsIndex, List<CompilationUnit> units) { for (CompilationUnit unit : units) { if (unit.isError()) { Dependencies dependencies = unit.getDependencies(); compilationErrorsIndex.add(unit.getTypeName(), unit.getResourceLocation(), dependencies.getApiRefs(), CompilationProblemReporter.formatErrors(unit)); } } } /** * Used as a convenience to catch all exceptions thrown by the compiler. For * instances of {@link InternalCompilerException}, extra diagnostics are * printed. * * @param logger logger used to report errors to the console * @param e the exception to analyze and log * @return Always returns an instance of {@link UnableToCompleteException} so * that the calling method can declare a more narrow 'throws * UnableToCompleteException' */ public static UnableToCompleteException logAndTranslateException(TreeLogger logger, Throwable e) { if (e instanceof UnableToCompleteException) { // just rethrow return (UnableToCompleteException) e; } else if (e instanceof InternalCompilerException) { TreeLogger topBranch = logger.branch(TreeLogger.ERROR, "An internal compiler exception occurred", e); List<NodeInfo> nodeTrace = ((InternalCompilerException) e).getNodeTrace(); for (NodeInfo nodeInfo : nodeTrace) { SourceInfo info = nodeInfo.getSourceInfo(); String msg; if (info != null) { String fileName = info.getFileName(); fileName = fileName.substring(fileName.lastIndexOf('/') + 1); fileName = fileName.substring(fileName.lastIndexOf('\\') + 1); msg = "at " + fileName + "(" + info.getStartLine() + "): "; } else { msg = "<no source info>: "; } String description = nodeInfo.getDescription(); if (description != null) { msg += description; } else { msg += "<no description available>"; } TreeLogger nodeBranch = topBranch.branch(TreeLogger.ERROR, msg, null); String className = nodeInfo.getClassName(); if (className != null) { nodeBranch.log(TreeLogger.INFO, className, null); } } return new UnableToCompleteException(); } else if (e instanceof VirtualMachineError) { // Always rethrow VM errors (an attempt to wrap may fail). throw (VirtualMachineError) e; } else { logger.log(TreeLogger.ERROR, "Unexpected internal compiler error", e); return new UnableToCompleteException(); } } /** * Provides meaningful error messages and hints for types that failed to compile or are otherwise * missing. */ public static int logErrorTrace(TreeLogger logger, Type logLevel, CompilerContext compilerContext, List<CompilationUnit> units, boolean hint) { int errorCount = 0; for (CompilationUnit unit : units) { if (unit.isError()) { logErrorTrace(logger, logLevel, compilerContext, unit.getTypeName(), hint); errorCount++; if (logger.isLoggable(TreeLogger.INFO) && unit instanceof GeneratedCompilationUnit) { CompilationProblemReporter.maybeDumpSource(logger, ((GeneratedCompilationUnit) unit).getSource(), unit.getTypeName()); } } } return errorCount++; } /** * Provides a meaning error message and hints for a type that failed to compile or is otherwise * missing. */ public static void logErrorTrace(TreeLogger logger, Type logLevel, CompilerContext compilerContext, String typeSourceName, boolean hint) { TreeLogger branch = logger.branch(TreeLogger.TRACE, "Tracing compile failure path for type '" + typeSourceName + "'"); if (logErrorChain(branch, logLevel, typeSourceName, compilerContext.getCompilationErrorsIndex())) { return; } if (hint) { logHints(logger, typeSourceName); } } public static int logWarnings(TreeLogger logger, Type logLevelForWarnings, List<CompilationUnit> units) { int warningCount = 0; for (CompilationUnit unit : units) { if (CompilationProblemReporter.logWarnings(logger, logLevelForWarnings, unit)) { warningCount++; } } return warningCount++; } /** * Logs errors to the console. * * @param logger logger for reporting errors to the console * @param unit Compilation unit that may have errors * @param suppressErrors Controls he log level for logging errors. If <code>false</code> is passed, * compilation errors are logged at TreeLogger.ERROR and warnings logged at * TreeLogger.WARN. If <code>true</code> is passed, compilation errors are logged at * TreeLogger.TRACE and TreeLogger.DEBUG. * @return <code>true</code> if an error was logged. */ public static boolean reportErrors(TreeLogger logger, CompilationUnit unit, boolean suppressErrors) { CategorizedProblem[] problems = unit.getProblems(); if (problems == null || problems.length == 0) { return false; } String fileName = unit.getResourceLocation(); boolean isError = unit.isError(); TreeLogger.Type warnLogLevel; TreeLogger.Type errorLogLevel; if (suppressErrors) { errorLogLevel = TreeLogger.TRACE; warnLogLevel = TreeLogger.DEBUG; } else { errorLogLevel = TreeLogger.ERROR; warnLogLevel = TreeLogger.WARN; } TreeLogger branch = null; // Log the errors and GWT warnings. for (CategorizedProblem problem : problems) { TreeLogger.Type logLevel; if (problem.isError()) { // Log errors. logLevel = errorLogLevel; // Only log GWT-specific warnings. } else if (problem.isWarning() && problem instanceof GWTProblem) { logLevel = warnLogLevel; } else { // Ignore all other problems. continue; } HelpInfo helpInfo = null; if (problem instanceof GWTProblem) { GWTProblem gwtProblem = (GWTProblem) problem; helpInfo = gwtProblem.getHelpInfo(); } if (branch == null) { Type branchType = isError ? errorLogLevel : warnLogLevel; String branchString = isError ? "Errors" : "Warnings"; branch = logger.branch(branchType, branchString + " in '" + fileName + "'", null); } branch.log(logLevel, toMessageWithLineNumber(problem), null, helpInfo); } if (branch != null && branch.isLoggable(TreeLogger.INFO)) { if (unit instanceof GeneratedCompilationUnit) { GeneratedCompilationUnit generatedUnit = (GeneratedCompilationUnit) unit; CompilationProblemReporter.maybeDumpSource(branch, generatedUnit.getSource(), unit .getTypeName()); } } return branch != null; } private static void addUnitToVisit(CompilationErrorsIndex compilationErrorsIndex, String typeSourceName, Queue<String> toVisit, Set<String> visited) { if (compilationErrorsIndex.hasCompileErrors(typeSourceName)) { if (!visited.contains(typeSourceName)) { toVisit.add(typeSourceName); visited.add(typeSourceName); } } } /** * Returns readable compilation error messages for a compilation unit. * <p> * Should only be run on CompilationUnits that actually have problems. */ private static List<String> formatErrors(CompilationUnit unit) { CategorizedProblem[] problems = unit.getProblems(); assert problems != null && problems.length > 0; List<String> errorMessages = Lists.newArrayList(); for (CategorizedProblem problem : problems) { if (!problem.isError()) { continue; } errorMessages.add(toMessageWithLineNumber(problem)); } return errorMessages; } private static boolean hasWarnings(CompilationUnit unit) { CategorizedProblem[] problems = unit.getProblems(); if (problems == null || problems.length == 0) { return false; } for (CategorizedProblem problem : problems) { if (problem.isWarning() && problem instanceof GWTProblem) { return true; } } return false; } private static boolean logErrorChain(TreeLogger logger, Type logLevel, String typeSourceName, CompilationErrorsIndex compilationErrorsIndex) { final Set<String> visited = new HashSet<String>(); final Queue<String> toVisit = new LinkedList<String>(); /* * Traverses CompilationUnits enqueued in toVisit(), calling {@link * #addUnitsToVisit(String)} as it encounters dependencies on the node. Each * CompilationUnit is visited only once, and only if it is reachable via the * {@link Dependencies} graph. */ addUnitToVisit(compilationErrorsIndex, typeSourceName, toVisit, visited); while (!toVisit.isEmpty()) { String dependentTypeSourceName = toVisit.remove(); Set<String> compileErrors = compilationErrorsIndex.getCompileErrors(dependentTypeSourceName); TreeLogger branch = logger.branch(logLevel, "Errors in '" + compilationErrorsIndex.getFileName(dependentTypeSourceName) + "'"); for (String compileError : compileErrors) { branch.log(logLevel, compileError); } Set<String> typeReferences = compilationErrorsIndex.getTypeReferences(dependentTypeSourceName); if (typeReferences != null) { for (String typeReference : typeReferences) { addUnitToVisit(compilationErrorsIndex, typeReference, toVisit, visited); } } } logger.log(TreeLogger.DEBUG, "Checked " + visited.size() + " dependencies for errors."); return visited.size() > 1; } private static void logHints(TreeLogger logger, String typeSourceName) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); URL sourceURL = Util.findSourceInClassPath(cl, typeSourceName); if (sourceURL != null) { if (typeSourceName.indexOf(".client.") != -1) { Messages.HINT_CHECK_MODULE_INHERITANCE.log(logger, null); } else { Messages.HINT_CHECK_MODULE_NONCLIENT_SOURCE_DECL.log(logger, null); } } else if (!typeSourceName.equals("java.lang.Object")) { Messages.HINT_CHECK_TYPENAME.log(logger, typeSourceName, null); Messages.HINT_CHECK_CLASSPATH_SOURCE_ENTRIES.log(logger, null); } /* * For missing JRE emulated classes (e.g. Object), or the main GWT libraries, there are special * warnings. */ if (typeSourceName.indexOf("java.lang.") == 0 || typeSourceName.indexOf("com.google.gwt.core.") == 0) { Messages.HINT_CHECK_INHERIT_CORE.log(logger, null); } else if (typeSourceName.indexOf("com.google.gwt.user.") == 0) { Messages.HINT_CHECK_INHERIT_USER.log(logger, null); } } private static boolean logWarnings(TreeLogger logger, TreeLogger.Type logLevel, CompilationUnit unit) { if (!hasWarnings(unit)) { return false; } TreeLogger branch = logger.branch(logLevel, "Warnings in '" + unit.getResourceLocation() + "'", null); for (CategorizedProblem problem : unit.getProblems()) { if (!problem.isWarning() || !(problem instanceof GWTProblem)) { continue; } branch.log(logLevel, toMessageWithLineNumber(problem), null, ((GWTProblem) problem).getHelpInfo()); } if (branch.isLoggable(TreeLogger.INFO) && unit instanceof GeneratedCompilationUnit) { CompilationProblemReporter.maybeDumpSource(branch, ((GeneratedCompilationUnit) unit).getSource(), unit.getTypeName()); } return true; } /** * Give the developer a chance to see the in-memory source that failed. */ private static void maybeDumpSource(TreeLogger logger, String source, String typeName) { File tmpSrc; Throwable caught = null; try { // The tempFile prefix must be at least 3 characters while (typeName.length() < 3) { typeName = "_" + typeName; } tmpSrc = File.createTempFile(typeName, ".java"); Util.writeStringAsFile(tmpSrc, source); String dumpPath = tmpSrc.getAbsolutePath(); if (logger.isLoggable(TreeLogger.INFO)) { logger.log(TreeLogger.INFO, "See snapshot: " + dumpPath, null); } return; } catch (IOException e) { caught = e; } logger.log(TreeLogger.INFO, "Unable to dump source to disk", caught); } private static String toMessageWithLineNumber(CategorizedProblem problem) { int lineNumber = problem.getSourceLineNumber(); return (lineNumber > 0 ? "Line " + lineNumber + ": " : "") + problem.getMessage(); } }