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