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

Last change on this file since 13772 was 13647, checked in by Don-vip, 6 years ago

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

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