001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache license, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License. You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the license for the specific language governing permissions and
015     * limitations under the license.
016     */
017    package org.apache.logging.log4j.core.impl;
018    
019    import org.apache.logging.log4j.Logger;
020    import org.apache.logging.log4j.core.helpers.Loader;
021    import org.apache.logging.log4j.status.StatusLogger;
022    
023    import java.io.PrintStream;
024    import java.io.PrintWriter;
025    import java.lang.reflect.Method;
026    import java.lang.reflect.Modifier;
027    import java.net.URL;
028    import java.security.CodeSource;
029    import java.util.HashMap;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.Stack;
033    
034    /**
035     * Wraps a Throwable to add packaging information about each stack trace element.
036     */
037    public class ThrowableProxy extends Throwable {
038    
039        private static final long serialVersionUID = -2752771578252251910L;
040    
041        private static Method getCallerClass;
042    
043        private static PrivateSecurityManager securityManager;
044    
045        private static final Logger LOGGER = StatusLogger.getLogger();
046    
047        private static Method getSuppressed;
048        private static Method addSuppressed;
049    
050        private final Throwable throwable;
051        private final ThrowableProxy cause;
052        private int commonElementCount;
053    
054        private final StackTracePackageElement[] callerPackageData;
055    
056    
057        static {
058            setupCallerCheck();
059            versionCheck();
060        }
061    
062        /**
063         * Construct the wrapper for the Throwable that includes packaging data.
064         * @param throwable The Throwable to wrap.
065         */
066        public ThrowableProxy(Throwable throwable) {
067            this.throwable = throwable;
068            Map<String, CacheEntry> map = new HashMap<String, CacheEntry>();
069            Stack<Class<?>> stack = getCurrentStack();
070            callerPackageData = resolvePackageData(stack, map, null, throwable.getStackTrace());
071            this.cause = (throwable.getCause() == null) ? null :
072                new ThrowableProxy(throwable, stack, map, throwable.getCause());
073            setSuppressed(throwable);
074        }
075    
076        /**
077         * Constructs the wrapper for a Throwable that is referenced as the cause by another
078         * Throwable.
079         * @param parent The Throwable referencing this Throwable.
080         * @param stack The Class stack.
081         * @param map The cache containing the packaging data.
082         * @param cause The Throwable to wrap.
083         */
084        private ThrowableProxy(Throwable parent, Stack<Class<?>> stack, Map<String, CacheEntry> map, Throwable cause) {
085            this.throwable = cause;
086            callerPackageData = resolvePackageData(stack, map, parent.getStackTrace(), cause.getStackTrace());
087            this.cause = (throwable.getCause() == null) ? null :
088                new ThrowableProxy(parent, stack, map, throwable.getCause());
089            setSuppressed(throwable);
090        }
091    
092    
093        @Override
094        public void setStackTrace(StackTraceElement[] stackTraceElements) {
095            throw new UnsupportedOperationException("Cannot set the stack trace on a ThrowableProxy");
096        }
097    
098        @Override
099        public String getMessage() {
100            return throwable.getMessage();
101        }
102    
103        @Override
104        public String getLocalizedMessage() {
105            return throwable.getLocalizedMessage();
106        }
107    
108        @Override
109        public Throwable getCause() {
110            return cause;
111        }
112    
113        @Override
114        public Throwable initCause(Throwable throwable) {
115            throw new IllegalStateException("Cannot set the cause on a ThrowableProxy");
116        }
117    
118        @Override
119        public String toString() {
120            return throwable.toString();
121        }
122    
123        @Override
124        public void printStackTrace() {
125            throwable.printStackTrace();
126        }
127    
128        @Override
129        public void printStackTrace(PrintStream printStream) {
130            throwable.printStackTrace(printStream);
131        }
132    
133        @Override
134        public void printStackTrace(PrintWriter printWriter) {
135            throwable.printStackTrace(printWriter);
136        }
137    
138        @Override
139        public Throwable fillInStackTrace() {
140            return this;
141        }
142    
143        @Override
144        public StackTraceElement[] getStackTrace() {
145            return throwable.getStackTrace();
146        }
147    
148        /**
149         * Format the Throwable that is the cause of this Throwable.
150         * @return The formatted Throwable that caused this Throwable.
151         */
152        public String getRootCauseStackTrace() {
153            return getRootCauseStackTrace(null);
154        }
155    
156        /**
157         * Format the Throwable that is the cause of this Throwable.
158         * @param packages The List of packages to be suppressed from the trace.
159         * @return The formatted Throwable that caused this Throwable.
160         */
161        public String getRootCauseStackTrace(List<String> packages) {
162            StringBuilder sb = new StringBuilder();
163            if (cause != null) {
164                formatWrapper(sb, cause);
165                sb.append("Wrapped by: ");
166            }
167            sb.append(throwable.toString());
168            sb.append("\n");
169            formatElements(sb, 0, throwable.getStackTrace(), callerPackageData, packages);
170            return sb.toString();
171        }
172    
173        /**
174         * Formats the specified Throwable.
175         * @param sb StringBuilder to contain the formatted Throwable.
176         * @param cause The Throwable to format.
177         */
178        public void formatWrapper(StringBuilder sb, ThrowableProxy cause) {
179            formatWrapper(sb, cause, null);
180        }
181    
182        /**
183         * Formats the specified Throwable.
184         * @param sb StringBuilder to contain the formatted Throwable.
185         * @param cause The Throwable to format.
186         * @param packages The List of packages to be suppressed from the trace.
187         */
188        public void formatWrapper(StringBuilder sb, ThrowableProxy cause, List<String> packages) {
189            Throwable caused = cause.getCause();
190            if (caused != null) {
191                formatWrapper(sb, cause.cause);
192                sb.append("Wrapped by: ");
193            }
194            sb.append(cause).append("\n");
195            formatElements(sb, cause.commonElementCount, cause.getStackTrace(), cause.callerPackageData, packages);
196        }
197    
198        /**
199         * Format the stack trace including packaging information.
200         * @return The formatted stack trace including packaging information.
201         */
202        public String getExtendedStackTrace() {
203            return getExtendedStackTrace(null);
204        }
205    
206        /**
207         * Format the stack trace including packaging information.
208         * @param packages List of packages to be suppressed from the trace.
209         * @return The formatted stack trace including packaging information.
210         */
211        public String getExtendedStackTrace(List<String> packages) {
212            StringBuilder sb = new StringBuilder(throwable.toString());
213            sb.append("\n");
214            formatElements(sb, 0, throwable.getStackTrace(), callerPackageData, packages);
215            if (cause != null) {
216                formatCause(sb, cause, packages);
217            }
218            return sb.toString();
219        }
220    
221        /**
222         * Format the suppressed Throwables.
223         * @return The formatted suppressed Throwables.
224         */
225        public String getSuppressedStackTrace() {
226            ThrowableProxy[] suppressed = getSuppressedProxies();
227            if (suppressed == null || suppressed.length == 0) {
228                return "";
229            }
230            StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:\n");
231            for (ThrowableProxy proxy : suppressed) {
232                sb.append(proxy.getExtendedStackTrace());
233            }
234            return sb.toString();
235        }
236    
237        private void formatCause(StringBuilder sb, ThrowableProxy cause, List<String> packages) {
238            sb.append("Caused by: ").append(cause).append("\n");
239            formatElements(sb, cause.commonElementCount, cause.getStackTrace(), cause.callerPackageData, packages);
240            if (cause.getCause() != null) {
241                formatCause(sb, cause.cause, packages);
242            }
243        }
244    
245        private void formatElements(StringBuilder sb, int commonCount, StackTraceElement[] causedTrace,
246                                    StackTracePackageElement[] packageData, List<String> packages) {
247            if (packages == null || packages.size() == 0) {
248                for (int i = 0; i < packageData.length; ++i) {
249                    formatEntry(causedTrace[i], packageData[i], sb);
250                }
251            } else {
252                int count = 0;
253                for (int i = 0; i < packageData.length; ++i) {
254                    if (!isSuppressed(causedTrace[i], packages)) {
255                        if (count > 0) {
256                            if (count == 1) {
257                                sb.append("\t....\n");
258                            } else {
259                                sb.append("\t... suppressed ").append(count).append(" lines\n");
260                            }
261                            count = 0;
262                        }
263                        formatEntry(causedTrace[i], packageData[i], sb);
264                    } else {
265                        ++count;
266                    }
267                }
268                if (count > 0) {
269                    if (count == 1) {
270                        sb.append("\t...\n");
271                    } else {
272                        sb.append("\t... suppressed ").append(count).append(" lines\n");
273                    }
274                }
275            }
276            if (commonCount != 0) {
277                sb.append("\t... ").append(commonCount).append(" more").append("\n");
278            }
279        }
280    
281        private void formatEntry(StackTraceElement element, StackTracePackageElement packageData, StringBuilder sb) {
282            sb.append("\tat ");
283            sb.append(element);
284            sb.append(" ");
285            sb.append(packageData);
286            sb.append("\n");
287        }
288    
289        private boolean isSuppressed(StackTraceElement element, List<String> packages) {
290            String className = element.getClassName();
291            for (String pkg : packages) {
292                if (className.startsWith(pkg)) {
293                    return true;
294                }
295            }
296            return false;
297        }
298    
299        /**
300         * Initialize the cache by resolving everything in the current stack trace via Reflection.getCallerClass
301         * or via the SecurityManager if either are available. These are the only Classes that can be trusted
302         * to be accurate.
303         * @return A Deque containing the current stack of Class objects.
304         */
305        private Stack<Class<?>> getCurrentStack() {
306            if (getCallerClass != null) {
307                Stack<Class<?>> classes = new Stack<Class<?>>();
308                int index = 2;
309                Class<?> clazz = getCallerClass(index);
310                while (clazz != null) {
311                    classes.push(clazz);
312                    clazz = getCallerClass(++index);
313                }
314                return classes;
315            } else if (securityManager != null) {
316                Class<?>[] array = securityManager.getClasses();
317                Stack<Class<?>> classes = new Stack<Class<?>>();
318                for (Class<?> clazz : array) {
319                    classes.push(clazz);
320                }
321                return classes;
322            }
323            return new Stack<Class<?>>();
324        }
325    
326        /**
327         * Resolve all the stack entries in this stack trace that are not common with the parent.
328         * @param stack The callers Class stack.
329         * @param map The cache of CacheEntry objects.
330         * @param rootTrace The first stack trace resolve or null.
331         * @param stackTrace The stack trace being resolved.
332         * @return The StackTracePackageElement array.
333         */
334        private StackTracePackageElement[] resolvePackageData(Stack<Class<?>> stack, Map<String, CacheEntry> map,
335                                                              StackTraceElement[] rootTrace,
336                                                              StackTraceElement[] stackTrace) {
337            int stackLength;
338            if (rootTrace != null) {
339                int rootIndex = rootTrace.length - 1;
340                int stackIndex = stackTrace.length - 1;
341                while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
342                    --rootIndex;
343                    --stackIndex;
344                }
345                commonElementCount = stackTrace.length - 1 - stackIndex;
346                stackLength = stackIndex + 1;
347            } else {
348                commonElementCount = 0;
349                stackLength = stackTrace.length;
350            }
351            StackTracePackageElement[] packageArray = new StackTracePackageElement[stackLength];
352            Class<?> clazz = stack.peek();
353            ClassLoader lastLoader = null;
354            for (int i = stackLength - 1; i >= 0; --i) {
355                String className = stackTrace[i].getClassName();
356                // The stack returned from getCurrentStack will be missing entries for  java.lang.reflect.Method.invoke()
357                // and its implementation. The Throwable might also contain stack entries that are no longer
358                // present as those methods have returned.
359                if (className.equals(clazz.getName())) {
360                    CacheEntry entry = resolvePackageElement(clazz, true);
361                    packageArray[i] = entry.element;
362                    lastLoader = entry.loader;
363                    stack.pop();
364                    clazz = stack.peek();
365                } else {
366                    if (map.containsKey(className)) {
367                        CacheEntry entry = map.get(className);
368                        packageArray[i] = entry.element;
369                        if (entry.loader != null) {
370                            lastLoader = entry.loader;
371                        }
372                    } else {
373                        CacheEntry entry = resolvePackageElement(loadClass(lastLoader, className), false);
374                        packageArray[i] = entry.element;
375                        map.put(className, entry);
376                        if (entry.loader != null) {
377                            lastLoader = entry.loader;
378                        }
379                    }
380                }
381            }
382            return packageArray;
383        }
384    
385    
386        /**
387         * Construct the CacheEntry from the Class's information.
388         * @param callerClass The Class.
389         * @param exact True if the class was obtained via Reflection.getCallerClass.
390         * @return The CacheEntry.
391         */
392        private CacheEntry resolvePackageElement(Class<?> callerClass, boolean exact) {
393            String location = "?";
394            String version = "?";
395            ClassLoader lastLoader = null;
396            if (callerClass != null) {
397                try {
398                    CodeSource source = callerClass.getProtectionDomain().getCodeSource();
399                    if (source != null) {
400                        URL locationURL = source.getLocation();
401                        if (locationURL != null) {
402                            String str = locationURL.toString().replace('\\', '/');
403                            int index = str.lastIndexOf("/");
404                            if (index >= 0 && index == str.length() - 1) {
405                                index = str.lastIndexOf("/", index - 1);
406                                location = str.substring(index + 1);
407                            } else {
408                                location = str.substring(index + 1);
409                            }
410                        }
411                    }
412                } catch (Exception ex) {
413                    // Ignore the exception.
414                }
415                Package pkg = callerClass.getPackage();
416                if (pkg != null) {
417                    String ver = pkg.getImplementationVersion();
418                    if (ver != null) {
419                        version = ver;
420                    }
421                }
422                lastLoader = callerClass.getClassLoader();
423            }
424            return new CacheEntry(new StackTracePackageElement(location, version, exact), lastLoader);
425        }
426    
427        /**
428         * Invoke Reflection.getCallerClass via reflection. This is slightly slower than calling the method
429         * directly but removes any dependency on Sun's JDK being present at compile time. The difference
430         * can be measured by running the ReflectionComparison test.
431         * @param index The index into the stack trace.
432         * @return The Class at the specified stack entry.
433         */
434        private Class<?> getCallerClass(int index) {
435            if (getCallerClass != null) {
436                try {
437                    Object[] params = new Object[]{index};
438                    return (Class<?>) getCallerClass.invoke(null, params);
439                } catch (Exception ex) {
440                    // logger.debug("Unable to determine caller class via Sun Reflection", ex);
441                }
442            }
443            return null;
444        }
445    
446        /**
447         * Loads classes not located via Reflection.getCallerClass.
448         * @param lastLoader The ClassLoader that loaded the Class that called this Class.
449         * @param className The name of the Class.
450         * @return The Class object for the Class or null if it could not be located.
451         */
452        private Class<?> loadClass(ClassLoader lastLoader, String className) {
453            Class<?> clazz;
454            if (lastLoader != null) {
455                try {
456                    clazz = lastLoader.loadClass(className);
457                    if (clazz != null) {
458                        return clazz;
459                    }
460                } catch (Exception ex) {
461                    // Ignore exception.
462                }
463            }
464            try {
465                clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
466            } catch (ClassNotFoundException e) {
467                try {
468                    clazz = Class.forName(className);
469                } catch (ClassNotFoundException e1) {
470                    try {
471                        clazz = getClass().getClassLoader().loadClass(className);
472                    } catch (ClassNotFoundException e2) {
473                        return null;
474                    }
475                }
476            }
477            return clazz;
478        }
479    
480        private static void versionCheck() {
481            Method[] methods = Throwable.class.getMethods();
482            for (Method method : methods) {
483                if (method.getName().equals("getSuppressed")) {
484                    getSuppressed = method;
485                } else if (method.getName().equals("addSuppressed")) {
486                    addSuppressed = method;
487                }
488            }
489        }
490    
491        /**
492         * Determine if Reflection.getCallerClass is available.
493         */
494        private static void setupCallerCheck() {
495            try {
496                ClassLoader loader = Loader.getClassLoader();
497                // Use wildcard to avoid compile-time reference.
498                Class<?> clazz = loader.loadClass("sun.reflect.Reflection");
499                Method[] methods = clazz.getMethods();
500                for (Method method : methods) {
501                    int modifier = method.getModifiers();
502                    if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) {
503                        getCallerClass = method;
504                        return;
505                    }
506                }
507            } catch (ClassNotFoundException cnfe) {
508                LOGGER.debug("sun.reflect.Reflection is not installed");
509            }
510    
511            try {
512                PrivateSecurityManager mgr = new PrivateSecurityManager();
513                if (mgr.getClasses() != null) {
514                    securityManager = mgr;
515                } else {
516                    // This shouldn't happen.
517                    LOGGER.error("Unable to obtain call stack from security manager");
518                }
519            } catch (Exception ex) {
520                LOGGER.debug("Unable to install security manager", ex);
521            }
522        }
523    
524        private ThrowableProxy[] getSuppressedProxies() {
525            if (getSuppressed != null) {
526                try {
527                    return (ThrowableProxy[]) getSuppressed.invoke(this, null);
528                } catch (Exception ex) {
529                    return null;
530                }
531            }
532            return null;
533        }
534    
535        private void setSuppressed(Throwable throwable) {
536            if (getSuppressed != null && addSuppressed != null) {
537                try {
538                    Throwable[] array = (Throwable[]) getSuppressed.invoke(throwable, null);
539                    for (Throwable t : array) {
540                        addSuppressed.invoke(this, new ThrowableProxy(t));
541                    }
542                } catch (Exception ex) {
543                    //
544                }
545            }
546        }
547    
548        /**
549         * Cached StackTracePackageElement and the ClassLoader.
550         */
551        private class CacheEntry {
552            private final StackTracePackageElement element;
553            private final ClassLoader loader;
554    
555            public CacheEntry(StackTracePackageElement element, ClassLoader loader) {
556                this.element = element;
557                this.loader = loader;
558            }
559        }
560    
561        /**
562         * Security Manager for accessing the call stack.
563         */
564        private static class PrivateSecurityManager extends SecurityManager {
565            public Class<?>[] getClasses() {
566                return getClassContext();
567            }
568        }
569    }