blob: 4e55ed50c99b5d3d5b20f906534102a74fc1050b [file] [log] [blame]
/*
* 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();
}
}