Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package com.google.devtools.build.lib.syntax; |
| 16 | |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 17 | import com.google.common.base.Joiner; |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 18 | import com.google.common.base.Throwables; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 19 | import com.google.devtools.build.lib.events.Location; |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 20 | import com.google.devtools.build.lib.util.LoggingUtil; |
Mark Schaller | 6df8179 | 2015-12-10 18:47:47 +0000 | [diff] [blame] | 21 | import com.google.devtools.build.lib.util.Preconditions; |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 22 | import java.util.logging.Level; |
Laurent Le Brun | 6e5eecb | 2015-09-10 11:37:32 +0000 | [diff] [blame] | 23 | import javax.annotation.Nullable; |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 24 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 25 | /** |
| 26 | * Exceptions thrown during evaluation of BUILD ASTs or Skylark extensions. |
| 27 | * |
| 28 | * <p>This exception must always correspond to a repeatable, permanent error, i.e. evaluating the |
| 29 | * same package again must yield the same exception. Notably, do not use this for reporting I/O |
| 30 | * errors. |
| 31 | * |
| 32 | * <p>This requirement is in place so that we can cache packages where an error is reported by way |
| 33 | * of {@link EvalException}. |
| 34 | */ |
| 35 | public class EvalException extends Exception { |
| 36 | |
Laurent Le Brun | 6e5eecb | 2015-09-10 11:37:32 +0000 | [diff] [blame] | 37 | @Nullable private Location location; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 38 | private final String message; |
| 39 | private final boolean dueToIncompleteAST; |
| 40 | |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 41 | private static final Joiner LINE_JOINER = Joiner.on("\n").skipNulls(); |
| 42 | private static final Joiner FIELD_JOINER = Joiner.on(": ").skipNulls(); |
| 43 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 44 | /** |
| 45 | * @param location the location where evaluation/execution failed. |
| 46 | * @param message the error message. |
| 47 | */ |
| 48 | public EvalException(Location location, String message) { |
| 49 | this.location = location; |
| 50 | this.message = Preconditions.checkNotNull(message); |
| 51 | this.dueToIncompleteAST = false; |
| 52 | } |
| 53 | |
Laurent Le Brun | fa407e5 | 2016-11-04 15:53:08 +0000 | [diff] [blame] | 54 | public EvalException(Location location, String message, String url) { |
| 55 | this.location = location; |
| 56 | this.dueToIncompleteAST = false; |
| 57 | this.message = |
| 58 | Preconditions.checkNotNull(message) |
| 59 | + "\n" |
| 60 | + "Need help? See " |
| 61 | + Preconditions.checkNotNull(url); |
| 62 | } |
| 63 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 64 | /** |
| 65 | * @param location the location where evaluation/execution failed. |
| 66 | * @param message the error message. |
| 67 | * @param dueToIncompleteAST if the error is caused by a previous error, such as parsing. |
| 68 | */ |
| 69 | public EvalException(Location location, String message, boolean dueToIncompleteAST) { |
Michajlo Matijkiw | 32994ca | 2016-08-01 15:43:25 +0000 | [diff] [blame] | 70 | this(location, message, dueToIncompleteAST, true); |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Create an EvalException with the option to not fill in the java stack trace. An optimization |
| 75 | * for ReturnException, and potentially others, which aren't exceptional enough to include a |
| 76 | * stack trace. |
| 77 | * |
| 78 | * @param location the location where evaluation/execution failed. |
| 79 | * @param message the error message. |
| 80 | * @param dueToIncompleteAST if the error is caused by a previous error, such as parsing. |
| 81 | * @param fillInJavaStackTrace whether or not to fill in the java stack trace for this exception |
| 82 | */ |
| 83 | EvalException( |
| 84 | Location location, |
| 85 | String message, |
| 86 | boolean dueToIncompleteAST, |
| 87 | boolean fillInJavaStackTrace) { |
| 88 | super(null, null, /*enableSuppression=*/ true, fillInJavaStackTrace); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 89 | this.location = location; |
| 90 | this.message = Preconditions.checkNotNull(message); |
| 91 | this.dueToIncompleteAST = dueToIncompleteAST; |
| 92 | } |
| 93 | |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 94 | /** |
| 95 | * @param location the location where evaluation/execution failed. |
| 96 | * @param message the error message. |
| 97 | * @param cause a Throwable that caused this exception. |
| 98 | */ |
| 99 | public EvalException(Location location, String message, Throwable cause) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 100 | super(cause); |
| 101 | this.location = location; |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 102 | // This is only used from Skylark, it's useful for debugging. |
| 103 | this.message = FIELD_JOINER.join(message, getCauseMessage(message)); |
| 104 | if (this.message.isEmpty()) { |
| 105 | String details; |
| 106 | if (cause == null) { |
| 107 | details = "Invalid EvalException: no cause given!"; |
| 108 | } else { |
| 109 | details = "Invalid EvalException:\n" + Throwables.getStackTraceAsString(cause); |
Florian Weikert | 77303b4 | 2015-08-27 08:24:24 +0000 | [diff] [blame] | 110 | } |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 111 | LoggingUtil.logToRemote(Level.SEVERE, details, cause); |
| 112 | throw new IllegalArgumentException(details); |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 113 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 114 | this.dueToIncompleteAST = false; |
| 115 | } |
| 116 | |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 117 | public EvalException(Location location, Throwable cause) { |
| 118 | this(location, null, cause); |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Returns the error message with location info if exists. |
| 123 | */ |
| 124 | public String print() { // TODO(bazel-team): do we also need a toString() method? |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 125 | return LINE_JOINER.join("\n", FIELD_JOINER.join(getLocation(), message), |
| 126 | (dueToIncompleteAST ? "due to incomplete AST" : ""), |
| 127 | getCauseMessage(message)); |
Florian Weikert | 3f610e8 | 2015-08-18 14:37:46 +0000 | [diff] [blame] | 128 | } |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 129 | |
| 130 | /** |
| 131 | * @param message the message of this exception, so far. |
| 132 | * @return a message for the cause of the exception, if the main message (passed as argument) |
| 133 | * doesn't already contain this cause; return null if no new information is available. |
| 134 | */ |
| 135 | private String getCauseMessage(String message) { |
Florian Weikert | 3f610e8 | 2015-08-18 14:37:46 +0000 | [diff] [blame] | 136 | Throwable cause = getCause(); |
| 137 | if (cause == null) { |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 138 | return null; |
Florian Weikert | 3f610e8 | 2015-08-18 14:37:46 +0000 | [diff] [blame] | 139 | } |
| 140 | String causeMessage = cause.getMessage(); |
Francois-Rene Rideau | 41d19f0 | 2015-08-27 15:00:12 +0000 | [diff] [blame] | 141 | if (causeMessage == null) { |
| 142 | return null; |
| 143 | } |
| 144 | if (message == null) { |
| 145 | return causeMessage; |
| 146 | } |
| 147 | // Skip the cause if it is redundant with the message so far. |
| 148 | if (message.contains(causeMessage)) { |
| 149 | return null; |
| 150 | } |
| 151 | return causeMessage; |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 152 | } |
| 153 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 154 | /** |
| 155 | * Returns the error message. |
| 156 | */ |
| 157 | @Override |
| 158 | public String getMessage() { |
| 159 | return message; |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * Returns the location of the evaluation error. |
| 164 | */ |
Laurent Le Brun | 6e5eecb | 2015-09-10 11:37:32 +0000 | [diff] [blame] | 165 | @Nullable |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 166 | public Location getLocation() { |
| 167 | return location; |
| 168 | } |
| 169 | |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 170 | /** |
| 171 | * Returns a boolean that tells whether this exception was due to an incomplete AST |
| 172 | */ |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 173 | public boolean isDueToIncompleteAST() { |
| 174 | return dueToIncompleteAST; |
| 175 | } |
| 176 | |
| 177 | /** |
Francois-Rene Rideau | b6038e0 | 2015-06-11 19:51:16 +0000 | [diff] [blame] | 178 | * Ensures that this EvalException has proper location information. |
| 179 | * Does nothing if the exception already had a location, or if no location is provided. |
| 180 | * @return this EvalException, in fluent style. |
| 181 | */ |
| 182 | public EvalException ensureLocation(Location loc) { |
| 183 | if (location == null && loc != null) { |
| 184 | location = loc; |
| 185 | } |
| 186 | return this; |
| 187 | } |
| 188 | |
| 189 | /** |
Florian Weikert | 4b67d4f | 2015-09-14 13:35:34 +0000 | [diff] [blame] | 190 | * Returns whether this exception can be added to a stack trace created by {@link |
| 191 | * EvalExceptionWithStackTrace}. |
| 192 | */ |
| 193 | public boolean canBeAddedToStackTrace() { |
| 194 | return true; |
| 195 | } |
| 196 | |
| 197 | /** |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 198 | * A class to support a special case of EvalException when the cause of the error is an |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 199 | * Exception during a direct Java call. Allow the throwing code to provide context in a message. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 200 | */ |
| 201 | public static final class EvalExceptionWithJavaCause extends EvalException { |
| 202 | |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 203 | /** |
| 204 | * @param location the location where evaluation/execution failed. |
| 205 | * @param message the error message. |
| 206 | * @param cause a Throwable that caused this exception. |
| 207 | */ |
| 208 | public EvalExceptionWithJavaCause(Location location, String message, Throwable cause) { |
| 209 | super(location, message, cause); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 210 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 211 | |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 212 | /** |
| 213 | * @param location the location where evaluation/execution failed. |
| 214 | * @param cause a Throwable that caused this exception. |
| 215 | */ |
| 216 | public EvalExceptionWithJavaCause(Location location, Throwable cause) { |
| 217 | this(location, null, cause); |
| 218 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 219 | } |
| 220 | } |