aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/EvalException.java
blob: aec9f2b26674ef7ed4759989ae78933e3fe37fc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// Copyright 2014 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.common.base.Throwables;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.util.LoggingUtil;

import java.util.logging.Level;
import javax.annotation.Nullable;

/**
 * Exceptions thrown during evaluation of BUILD ASTs or Skylark extensions.
 *
 * <p>This exception must always correspond to a repeatable, permanent error, i.e. evaluating the
 * same package again must yield the same exception. Notably, do not use this for reporting I/O
 * errors.
 *
 * <p>This requirement is in place so that we can cache packages where an error is reported by way
 * of {@link EvalException}.
 */
public class EvalException extends Exception {

  @Nullable private Location location;
  private final String message;
  private final boolean dueToIncompleteAST;

  private static final Joiner LINE_JOINER = Joiner.on("\n").skipNulls();
  private static final Joiner FIELD_JOINER = Joiner.on(": ").skipNulls();

  /**
   * @param location the location where evaluation/execution failed.
   * @param message the error message.
   */
  public EvalException(Location location, String message) {
    this.location = location;
    this.message = Preconditions.checkNotNull(message);
    this.dueToIncompleteAST = false;
  }

  /**
   * @param location the location where evaluation/execution failed.
   * @param message the error message.
   * @param dueToIncompleteAST if the error is caused by a previous error, such as parsing.
   */
  public EvalException(Location location, String message, boolean dueToIncompleteAST) {
    this.location = location;
    this.message = Preconditions.checkNotNull(message);
    this.dueToIncompleteAST = dueToIncompleteAST;
  }

  /**
   * @param location the location where evaluation/execution failed.
   * @param message the error message.
   * @param cause a Throwable that caused this exception.
   */
  public EvalException(Location location, String message, Throwable cause) {
    super(cause);
    this.location = location;
    // This is only used from Skylark, it's useful for debugging.
    this.message = FIELD_JOINER.join(message, getCauseMessage(message));
    if (this.message.isEmpty()) {
      String details;
      if (cause == null) {
        details = "Invalid EvalException: no cause given!";
      } else {
        details = "Invalid EvalException:\n" + Throwables.getStackTraceAsString(cause);
      }
      LoggingUtil.logToRemote(Level.SEVERE, details, cause);
      throw new IllegalArgumentException(details);
    }
    this.dueToIncompleteAST = false;
  }

  public EvalException(Location location, Throwable cause) {
    this(location, null, cause);
  }

  /**
   * Returns the error message with location info if exists.
   */
  public String print() { // TODO(bazel-team): do we also need a toString() method?
    return LINE_JOINER.join("\n", FIELD_JOINER.join(getLocation(), message),
        (dueToIncompleteAST ? "due to incomplete AST" : ""),
        getCauseMessage(message));
  }

  /**
   * @param message the message of this exception, so far.
   * @return a message for the cause of the exception, if the main message (passed as argument)
   * doesn't already contain this cause; return null if no new information is available.
   */
  private String getCauseMessage(String message) {
    Throwable cause = getCause();
    if (cause == null) {
      return null;
    }
    String causeMessage = cause.getMessage();
    if (causeMessage == null) {
      return null;
    }
    if (message == null) {
      return causeMessage;
    }
    // Skip the cause if it is redundant with the message so far.
    if (message.contains(causeMessage)) {
      return null;
    }
    return causeMessage;
  }

  /**
   * Returns the error message.
   */
  @Override
  public String getMessage() {
    return message;
  }

  /**
   * Returns the location of the evaluation error.
   */
  @Nullable
  public Location getLocation() {
    return location;
  }

  /**
   * Returns a boolean that tells whether this exception was due to an incomplete AST
   */
  public boolean isDueToIncompleteAST() {
    return dueToIncompleteAST;
  }

  /**
   * Ensures that this EvalException has proper location information.
   * Does nothing if the exception already had a location, or if no location is provided.
   * @return this EvalException, in fluent style.
   */
  public EvalException ensureLocation(Location loc) {
    if (location == null && loc != null) {
      location = loc;
    }
    return this;
  }

  /**
   * Returns whether this exception can be added to a stack trace created by {@link
   * EvalExceptionWithStackTrace}.
   */
  public boolean canBeAddedToStackTrace() {
    return true;
  }

  /**
   * A class to support a special case of EvalException when the cause of the error is an
   * Exception during a direct Java call. Allow the throwing code to provide context in a message.
   */
  public static final class EvalExceptionWithJavaCause extends EvalException {

    /**
     * @param location the location where evaluation/execution failed.
     * @param message the error message.
     * @param cause a Throwable that caused this exception.
     */
    public EvalExceptionWithJavaCause(Location location, String message, Throwable cause) {
      super(location, message, cause);
    }

    /**
     * @param location the location where evaluation/execution failed.
     * @param cause a Throwable that caused this exception.
     */
    public EvalExceptionWithJavaCause(Location location, Throwable cause) {
      this(location, null, cause);
    }
  }
}