// Copyright 2015 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.syntax; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.Deque; import java.util.LinkedList; /** * EvalException with a stack trace. */ public class EvalExceptionWithStackTrace extends EvalException { private StackTraceElement mostRecentElement; public EvalExceptionWithStackTrace(Exception original, Location callLocation) { super(callLocation, getNonEmptyMessage(original), getCause(original)); } /** * Returns the "real" cause of this exception. * *
If the original exception is an EvalException, its cause is returned.
* Otherwise, the original exception itself is seen as the cause for this exception.
*/
private static Throwable getCause(Exception ex) {
return (ex instanceof EvalException) ? ex.getCause() : ex;
}
/**
* Adds an entry for the given statement to the stack trace.
*/
public void registerStatement(Statement statement) {
Preconditions.checkState(
mostRecentElement == null, "Cannot add a statement to a non-empty stack trace.");
addStackFrame(statement.toString().trim(), statement.getLocation());
}
/**
* Adds an entry for the given function to the stack trace.
*/
public void registerFunction(BaseFunction function, Location location) {
addStackFrame(function.getFullName(), location);
}
/**
* Adds an entry for the given rule to the stack trace.
*/
public void registerRule(Rule rule) {
addStackFrame(
String.format("%s(name = '%s')", rule.getRuleClass(), rule.getName()), rule.getLocation());
}
/**
* Adds a line for the given frame.
*/
private void addStackFrame(String label, Location location) {
mostRecentElement = new StackTraceElement(label, location, mostRecentElement);
}
/**
* Returns the exception message without the stack trace.
*/
public String getOriginalMessage() {
return super.getMessage();
}
@Override
public String getMessage() {
return print();
}
@Override
public String print() {
return print(StackTracePrinter.INSTANCE);
}
/**
* Prints the stack trace iff it contains more than just one built-in function.
*/
public String print(StackTracePrinter printer) {
return canPrintStackTrace()
? printer.print(getOriginalMessage(), mostRecentElement)
: getOriginalMessage();
}
/**
* Returns true when there is at least one non-built-in element.
*/
protected boolean canPrintStackTrace() {
return mostRecentElement != null && mostRecentElement.getCause() != null;
}
/**
* An element in the stack trace which contains the name of the offending function / rule /
* statement and its location.
*/
protected final class StackTraceElement {
private final String label;
private final Location location;
private final StackTraceElement cause;
StackTraceElement(String label, Location location, StackTraceElement cause) {
this.label = label;
this.location = location;
this.cause = cause;
}
String getLabel() {
return label;
}
Location getLocation() {
return location;
}
StackTraceElement getCause() {
return cause;
}
}
/**
* Singleton class that prints stack traces similar to Python.
*/
public enum StackTracePrinter {
INSTANCE;
/**
* Turns the given message and StackTraceElements into a string.
*/
public final String print(String message, StackTraceElement mostRecentElement) {
Deque If the exception itself does not have a message, a new message is constructed from the
* exception's class name.
* For example, an IllegalArgumentException will lead to "Illegal Argument".
* Additionally, the location in the Java code will be added, if applicable,
*/
private static String getNonEmptyMessage(Exception original) {
Preconditions.checkNotNull(original);
String msg = original.getMessage();
if (msg != null && !msg.isEmpty()) {
return msg;
}
char[] name = original.getClass().getSimpleName().replace("Exception", "").toCharArray();
boolean first = true;
StringBuilder builder = new StringBuilder();
for (char current : name) {
if (Character.isUpperCase(current) && !first) {
builder.append(" ");
}
builder.append(current);
first = false;
}
java.lang.StackTraceElement[] trace = original.getStackTrace();
if (trace.length > 0) {
builder.append(String.format(": %s.%s() in %s:%d", getShortClassName(trace[0]),
trace[0].getMethodName(), trace[0].getFileName(), trace[0].getLineNumber()));
}
return builder.toString();
}
private static String getShortClassName(java.lang.StackTraceElement element) {
String name = element.getClassName();
int pos = name.lastIndexOf('.');
return (pos < 0) ? name : name.substring(pos + 1);
}
}