Classes on the stack trace (was: getElementClass/StackTraceElement, was: @CallerSensitive public API, was: sun.reflect.Reflection.getCallerClass)

David M. Lloyd david.lloyd at redhat.com
Mon Jul 29 13:21:58 UTC 2013


I find it very interesting that reflection is no less than two orders of 
magnitude faster than the security manager solution.  How big was the 
stack in these tests?  It makes me wonder if maybe the implementation of 
the security manager's getContext() method should be reevaluated a bit.

On 07/29/2013 07:53 AM, Nick Williams wrote:
> Just so that everyone understands how important this subject is, this change to getCallerClass(...) is being labeled a "disaster" for logging frameworks everywhere. Here's a benchmark for getting Classes from the following methods:
>
>> 1,000,000 calls of all alternatives were measured as follows :
>> Reflection: 10.195 ms.
>> Current Thread StackTrace: 5886.964 ms.
>> Throwable StackTrace: 4700.073 ms.
>> SecurityManager: 1046.804 ms.
>
> My goal here is to get the entire list engaged in coming up with the right solution. We (the community) can't afford for Java 8 not to have an equivalent replacement for getCallerClass().
>
> Nick
>
> On Jul 27, 2013, at 2:01 PM, Nick Williams wrote:
>
>> All,
>>
>> In the last two months, there have been a number of discussions surrounding stack traces, Classes on the stack trace, and caller classes [1], [2], [3]. These are all related discussions and the solution to them is equally related, so I wanted to consolidate it all into this one discussion where I hope we can finalize on a solution and get it implemented for Java 8.
>>
>> In a nut shell, here are the underlying needs that I have seen expressed through many, many messages:
>>
>> - Some code needs to get the Class of the caller of the current method, skipping any reflection methods.
>> - Some code needs to get the Class of the caller /n/ stack frames before the current method, skipping any reflection methods.
>> - Some code needs to get the current stack trace, populated with Classes, Executables, file names, line numbers, and native flags instead of the String class names and String method names in StackTraceElement. This /should/ include any reflection methods, just like StackTraceElement[]s.
>> - Some code needs to get the stack trace from when a Throwable was created, populated with Classes, Executables, file names, line numbers, and native flags instead of the String class names and String method names in StackTraceElement. This /should/ include any reflection methods, just like StackTraceElement[]s.
>> - There needs to be a reflection way to achieve all of this since some libraries (e.g., Log4j) need to be compiled against Java 6 but run on 7 and 8 (and thus can't use @CallerSensitive).
>>
>> I believe the solutions to these needs are all related. Importantly, I think it is very important that action be taken in Java 8 due to the changes made to sun.reflect.Reflection#getCallerClass(...). While we all understand that relying on private sun.* APIs is not safe, the fact is that many people have relied on sun.reflect.Reflection#getCallerClass(...) due to the fact that there is simply no other way to do this in the standard API. This includes Log4j 2, Logback, SLF4j, and Groovy, some features of which will stop working correctly in Java 7 >= u25.
>>
>> I would point out that this could all easily be solved simply by adding a getElementClass() method to StackTraceElement, but there was strong opposition to this, largely due to serialization issues. Since that is apparently not an option, I propose the following API, based on the various discussions in the last two months, StackTraceElement, and the API that .NET provides to achieve the same needs as listed above:
>>
>> CallerSensitive.java:
>> package java.lang;
>>
>> /** Previously private API, now public */
>> public @interface CallerSensitive {
>>      ...
>> }
>>
>> StackTraceFrame.java:
>> package java.lang;
>>
>> import java.util.Objects.
>>
>> public final class StackTraceFrame {
>>      private final Class<?> declaringClass;
>>      private final Executable executable;
>>      private final String fileName;
>>      private final int lineNumber;
>>
>>      public StackTraceFrame(Class<?> declaringClass, Executable executable, String fileName, int lineNumber) {
>>          this.declaringClass = Objects.requireNonNull(declaringClass, "Declaring class is null");
>>          this.executable = Objects.requireNonNull(executable, "Executable is null");
>>          this.fileName = fileName;
>>          this.lineNumber = lineNumber;
>>      }
>>
>>      public Class<?> getDeclaringClass() {
>>          return this.declaringClass;
>>      }
>>
>>      public Executable getExecutable() {
>>          return this.executable;
>>      }
>>
>>      public String getFileName() {
>>          return this.fileName;
>>      }
>>
>>      public int getLineNumber() {
>>          return this.lineNumber;
>>      }
>>
>>      public boolean isNative() {
>>          return this.lineNumber == -2;
>>      }
>>
>>      public String toString() { /* Same as StackTraceElement */ }
>>      public boolean equals() { /* Ditto */ }
>>      public int hashCode() { /* Ditto */ }
>>
>>      /** Uses @CallerSensitive */
>>      public static native StackTraceFrame getCallerFrame();
>>
>>      /** Works like Java < 7u25 sun.reflect.Reflection#getCallerClass() */
>>      public static native StackTraceFrame getCallerFrame(int skipFrames);
>>
>>      public static native StackTraceFrame[] getCurrentStackTrace();
>> }
>>
>> Throwable.java:
>> package java.lang;
>>
>> ...
>>
>> public class Throwable {
>>      ...
>>      public synchronized Throwable fillInStackTraceFrames() { ... }
>>
>>      private native Throwable fillInStackTraceFrames(int dummy);
>>
>>      public StackTraceFrame[] getStackTraceFrames() {
>>          return this.getOurStackTraceFrames().clone();
>>      }
>>
>>      private synchronized StackTraceFrame[] getOurStackTraceFrames() { ... }
>>      ...
>> }
>>
>> Furthermore, I propose that we restore the behavior of sun.reflect.Reflection#getCallerClass(int) /just for Java 7/ since the proposed above solution cannot be added to Java 7.
>>
>> I would love if we could quickly coalesce around this solution or a derivative thereof so that it can be implemented before Feature Complete. The absence of any replacement or alternative for sun.reflect.Reflection#getCallerClass(int) will be a serious issue in Java 8 that will cause hardships for many projects.
>>
>> [1] http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-June/018049.html
>> [2] http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-June/018349.html, http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-July/019098.html
>> [3] http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-July/018855.html
>


-- 
- DML



More information about the core-libs-dev mailing list