source: josm/trunk/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java@ 17380

Last change on this file since 17380 was 17380, checked in by stoecker, 3 years ago

missing counter increase, fix #CID1437607

  • Property svn:eol-style set to native
File size: 12.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools.bugreport;
3
4import java.io.PrintWriter;
5import java.io.Serializable;
6import java.lang.reflect.InvocationTargetException;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.ConcurrentModificationException;
12import java.util.HashMap;
13import java.util.IdentityHashMap;
14import java.util.Iterator;
15import java.util.LinkedList;
16import java.util.Map;
17import java.util.Map.Entry;
18import java.util.NoSuchElementException;
19import java.util.Set;
20import java.util.function.Supplier;
21
22import org.openstreetmap.josm.tools.Logging;
23import org.openstreetmap.josm.tools.StreamUtils;
24
25/**
26 * This is a special exception that cannot be directly thrown.
27 * <p>
28 * It is used to capture more information about an exception that was already thrown.
29 *
30 * @author Michael Zangl
31 * @see BugReport
32 * @since 10285
33 */
34@SuppressWarnings("OverrideThrowableToString")
35public class ReportedException extends RuntimeException {
36 /**
37 * How many entries of a collection to include in the bug report.
38 */
39 private static final int MAX_COLLECTION_ENTRIES = 30;
40
41 private static final long serialVersionUID = 737333873766201033L;
42
43 /**
44 * We capture all stack traces on exception creation. This allows us to trace synchronization problems better.
45 * We cannot be really sure what happened but we at least see which threads
46 */
47 private final transient Map<Thread, StackTraceElement[]> allStackTraces = new HashMap<>();
48 private final LinkedList<Section> sections = new LinkedList<>();
49 private final transient Thread caughtOnThread;
50 private String methodWarningFrom;
51
52 /**
53 * Constructs a new {@code ReportedException}.
54 * @param exception the cause (which is saved for later retrieval by the {@link #getCause()} method)
55 * @since 14380
56 */
57 public ReportedException(Throwable exception) {
58 this(exception, Thread.currentThread());
59 }
60
61 /**
62 * Constructs a new {@code ReportedException}.
63 * @param exception the cause (which is saved for later retrieval by the {@link #getCause()} method)
64 * @param caughtOnThread thread where the exception was caught
65 * @since 14380
66 */
67 public ReportedException(Throwable exception, Thread caughtOnThread) {
68 super(exception);
69
70 try {
71 allStackTraces.putAll(Thread.getAllStackTraces());
72 } catch (SecurityException e) {
73 Logging.log(Logging.LEVEL_ERROR, "Unable to get thread stack traces", e);
74 }
75 this.caughtOnThread = caughtOnThread;
76 }
77
78 /**
79 * Displays a warning for this exception. The program can then continue normally. Does not block.
80 */
81 public void warn() {
82 methodWarningFrom = BugReport.getCallingMethod(2);
83 try {
84 BugReportQueue.getInstance().submit(this);
85 } catch (RuntimeException e) { // NOPMD
86 Logging.error(e);
87 }
88 }
89
90 /**
91 * Starts a new debug data section. This normally does not need to be called manually.
92 *
93 * @param sectionName
94 * The section name.
95 */
96 public void startSection(String sectionName) {
97 sections.add(new Section(sectionName));
98 }
99
100 /**
101 * Prints the captured data of this report to a {@link PrintWriter}.
102 *
103 * @param out
104 * The writer to print to.
105 */
106 public void printReportDataTo(PrintWriter out) {
107 out.println("=== REPORTED CRASH DATA ===");
108 for (Section s : sections) {
109 s.printSection(out);
110 out.println();
111 }
112
113 if (methodWarningFrom != null) {
114 out.println("Warning issued by: " + methodWarningFrom);
115 out.println();
116 }
117 }
118
119 /**
120 * Prints the stack trace of this report to a {@link PrintWriter}.
121 *
122 * @param out
123 * The writer to print to.
124 */
125 public void printReportStackTo(PrintWriter out) {
126 out.println("=== STACK TRACE ===");
127 out.println(niceThreadName(caughtOnThread));
128 getCause().printStackTrace(out);
129 out.println();
130 }
131
132 /**
133 * Prints the stack traces for other threads of this report to a {@link PrintWriter}.
134 *
135 * @param out
136 * The writer to print to.
137 */
138 public void printReportThreadsTo(PrintWriter out) {
139 out.println("=== RUNNING THREADS ===");
140 for (Entry<Thread, StackTraceElement[]> thread : allStackTraces.entrySet()) {
141 out.println(niceThreadName(thread.getKey()));
142 if (caughtOnThread.equals(thread.getKey())) {
143 out.println("Stacktrace see above.");
144 } else {
145 for (StackTraceElement e : thread.getValue()) {
146 out.println(e);
147 }
148 }
149 out.println();
150 }
151 }
152
153 private static String niceThreadName(Thread thread) {
154 StringBuilder name = new StringBuilder("Thread: ").append(thread.getName()).append(" (").append(thread.getId()).append(')');
155 ThreadGroup threadGroup = thread.getThreadGroup();
156 if (threadGroup != null) {
157 name.append(" of ").append(threadGroup.getName());
158 }
159 return name.toString();
160 }
161
162 /**
163 * Checks if this exception is considered the same as an other exception. This is the case if both have the same cause and message.
164 *
165 * @param e
166 * The exception to check against.
167 * @return <code>true</code> if they are considered the same.
168 */
169 public boolean isSame(ReportedException e) {
170 if (!getMessage().equals(e.getMessage())) {
171 return false;
172 }
173
174 return hasSameStackTrace(new CauseTraceIterator(), e.getCause());
175 }
176
177 private static boolean hasSameStackTrace(CauseTraceIterator causeTraceIterator, Throwable e2) {
178 if (!causeTraceIterator.hasNext()) {
179 // all done.
180 return true;
181 }
182 Throwable e1 = causeTraceIterator.next();
183 StackTraceElement[] t1 = e1.getStackTrace();
184 StackTraceElement[] t2 = e2.getStackTrace();
185
186 if (!Arrays.equals(t1, t2)) {
187 return false;
188 }
189
190 Throwable c1 = e1.getCause();
191 Throwable c2 = e2.getCause();
192 if ((c1 == null) != (c2 == null)) {
193 return false;
194 } else if (c1 != null) {
195 return hasSameStackTrace(causeTraceIterator, c2);
196 } else {
197 return true;
198 }
199 }
200
201 /**
202 * Adds some debug values to this exception. The value is converted to a string. Errors during conversion are handled.
203 *
204 * @param key
205 * The key to add this for. Does not need to be unique but it would be nice.
206 * @param value
207 * The value.
208 * @return This exception for easy chaining.
209 */
210 public ReportedException put(String key, Object value) {
211 return put(key, () -> value);
212 }
213
214 /**
215 * Adds some debug values to this exception. This method automatically catches errors that occur during the production of the value.
216 *
217 * @param key
218 * The key to add this for. Does not need to be unique but it would be nice.
219 * @param valueSupplier
220 * A supplier that is called once to get the value.
221 * @return This exception for easy chaining.
222 * @since 10586
223 */
224 public ReportedException put(String key, Supplier<Object> valueSupplier) {
225 String string;
226 try {
227 Object value = valueSupplier.get();
228 if (value == null) {
229 string = "null";
230 } else if (value instanceof Collection) {
231 string = makeCollectionNice((Collection<?>) value);
232 } else if (value.getClass().isArray()) {
233 string = makeCollectionNice(Arrays.asList(value));
234 } else {
235 string = value.toString();
236 }
237 } catch (RuntimeException t) { // NOPMD
238 Logging.warn(t);
239 string = "<Error calling toString()>";
240 }
241 sections.getLast().put(key, string);
242 return this;
243 }
244
245 private static String makeCollectionNice(Collection<?> value) {
246 int lines = 0;
247 StringBuilder str = new StringBuilder(32);
248 for (Object e : value) {
249 str.append("\n - ");
250 if (lines <= MAX_COLLECTION_ENTRIES) {
251 ++lines;
252 str.append(e);
253 } else {
254 str.append("\n ... (")
255 .append(value.size())
256 .append(" entries)");
257 break;
258 }
259 }
260 return str.toString();
261 }
262
263 @Override
264 public String toString() {
265 return "ReportedException [thread=" + caughtOnThread + ", exception=" + getCause()
266 + ", methodWarningFrom=" + methodWarningFrom + ']';
267 }
268
269 /**
270 * Check if this exception may be caused by a threading issue.
271 * @return <code>true</code> if it is.
272 * @since 10585
273 */
274 public boolean mayHaveConcurrentSource() {
275 return StreamUtils.toStream(CauseTraceIterator::new)
276 .anyMatch(t -> t instanceof ConcurrentModificationException || t instanceof InvocationTargetException);
277 }
278
279 /**
280 * Check if this is caused by an out of memory situation
281 * @return <code>true</code> if it is.
282 * @since 10819
283 */
284 public boolean isOutOfMemory() {
285 return StreamUtils.toStream(CauseTraceIterator::new).anyMatch(t -> t instanceof OutOfMemoryError);
286 }
287
288 /**
289 * Iterates over the causes for this exception. Ignores cycles and aborts iteration then.
290 * @author Michal Zangl
291 * @since 10585
292 */
293 private final class CauseTraceIterator implements Iterator<Throwable> {
294 private Throwable current = getCause();
295 private final Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
296
297 @Override
298 public boolean hasNext() {
299 return current != null;
300 }
301
302 @Override
303 public Throwable next() {
304 if (!hasNext()) {
305 throw new NoSuchElementException();
306 }
307 Throwable toReturn = current;
308 advance();
309 return toReturn;
310 }
311
312 private void advance() {
313 dejaVu.add(current);
314 current = current.getCause();
315 if (current != null && dejaVu.contains(current)) {
316 current = null;
317 }
318 }
319 }
320
321 private static class SectionEntry implements Serializable {
322
323 private static final long serialVersionUID = 1L;
324
325 private final String key;
326 private final String value;
327
328 SectionEntry(String key, String value) {
329 this.key = key;
330 this.value = value;
331 }
332
333 /**
334 * Prints this entry to the output stream in a line.
335 * @param out The stream to print to.
336 */
337 public void print(PrintWriter out) {
338 out.print(" - ");
339 out.print(key);
340 out.print(": ");
341 out.println(value);
342 }
343 }
344
345 private static class Section implements Serializable {
346
347 private static final long serialVersionUID = 1L;
348
349 private final String sectionName;
350 private final ArrayList<SectionEntry> entries = new ArrayList<>();
351
352 Section(String sectionName) {
353 this.sectionName = sectionName;
354 }
355
356 /**
357 * Add a key/value entry to this section.
358 * @param key The key. Need not be unique.
359 * @param value The value.
360 */
361 public void put(String key, String value) {
362 entries.add(new SectionEntry(key, value));
363 }
364
365 /**
366 * Prints this section to the output stream.
367 * @param out The stream to print to.
368 */
369 public void printSection(PrintWriter out) {
370 out.println(sectionName + ':');
371 if (entries.isEmpty()) {
372 out.println("No data collected.");
373 } else {
374 for (SectionEntry e : entries) {
375 e.print(out);
376 }
377 }
378 }
379 }
380}
Note: See TracBrowser for help on using the repository browser.