[PATCH] 4851444: Exposing sun.reflect.Reflection#getCallerClass as a public API in Java 8

Mandy Chung mandy.chung at oracle.com
Wed Sep 4 01:47:01 UTC 2013


Just realize that I missed a few words in a sentence that could mislead 
what I really mean.... correction below.

On 9/3/2013 5:02 PM, Mandy Chung wrote:
> Nick,
>
> I skimmed through the changes.  Congratulations for your first patch 
> making changes in both hotspot and jdk code.
>
> In my mind, the Log4J use case accessing Class instance to obtain 
> additional information for diagnosability is different than the use 
> case of obtaining the caller's class / loader to lookup resources.  
> While the new APIs might be in the same class, I will discuss them 
> individually and keep us focus one at a time.
>
> Ralph has pointed out [1] that Log4j also needs the ability to find an 
> appropriate ClassLoader which is used for logging separation (thank 
> you Ralph). That will be the caller-sensitivity (i.e. obtain caller's 
> class/loader) discussion.
>
> There are a couple of RFEs:
> https://bugs.openjdk.java.net/browse/JDK-4942697
> https://bugs.openjdk.java.net/browse/JDK-6349551
>
> Performance is critical for Log4j to traverse the stack trace and 
> obtain Class information.  I like your patch to speed up the 
> generation of StackTraceElement[] (and StackTraceFrame[] - essentially 
> same code with different type). java.util.logging.LogRecord has 
> workaround the performance overhead and go to a specific frame 
> directly and avoid the cost of generating the entire array. 
> JDK-6349551 requests to lazily instantiate the StackTraceElement 
> object unless that frame is requested.  Does Log4J always walk all 
> frames and log all information?  Do you just log only some max number 
> of frames rather than the entire stack trace?
>
> Class<?> getDeclaringClass() method is the key method you need to 
> enhance the diagnosability.  This method opens up a new way to access 
> a Class instance that untrusted code wouldn't be able in the past.  A 
> permission check is needed as Alan points out early.  Performance as 
> well as logging framework can't access all class loaders are two 
> factors to be considered when defining the permission check.
>
> I saw your other comment about permission check (cut-n-paste below).  
> It seems to me that you don't agree a permission check is needed for 
> the getDeclaringClass() method (regardless of which class it belongs 
> to) and if that's the case, no point to continue.
>
> I want to make it very clear that I have agreed to take this on and 
> provide a replacement of sun.reflect.Reflection.getCallerClass(int) in 
> JDK 8 to address the use cases.  It will take time for the API and 
> security discussion and please be prepared for that (also I am working 
> on other things at the same time).
>

What I really meant is:

I want to make it very clear that I have agreed to take this to come to 
a conclusion and ensure to provide a replacement of 
sun.reflect.Reflection.getCallerClass(int) in JDK 8 to address the use 
cases.  It will take time for the API and security discussion and please 
be prepared for that (also I am working on other things at the same time).

Mandy

> The second comment on your patch is that there are lot of duplicated 
> code in hotspot in order to support two similar but different types 
> (StackTraceFrame and StackTraceElement). Serialization is the reason 
> leading you to have a new StackTraceFrame class.  Maybe some 
> refactoring can help but this is the cost of having the VM directly 
> creating the instance.  One other option, as suggested in the previous 
> thread, is to keep the declaring class in the StackTraceElement as a 
> transient field.  If we add the getDeclaringClass method in the 
> StackTraceElement class, it would need to specify to be optional that 
> it only returns the Class instance when it's available.
>
> There are currently three different ways to get a stack trace:
> 1. Throwable.getStackTrace()
> 2. Thread.getStackTrace() and Thread.getAllStackTraces()
> 3. java.lang.management.ThreadMXBean.getThreadInfo(long id, int 
> maxDepth).getStackTrace() and multiple thread IDs version
>   (a) local (b) remote
>
> Since it's a new StackTraceFrame class, you have to provide a new 
> method replacing #1 and #2.  I don't see any need to provide a new API 
> equivalent to Thread.getAllStackTraces() and java.lang.management.
>
> The proposal I originally have last week was to keep declaring class 
> as transient and add a method in StackTraceElement with a permission 
> check at every call. Tom raises a good question about the cost of 
> permission check.  Would that be a concern to Log4j? Is log4j on 
> bootclasspath or extension directory?  I assume not. So for log4j to 
> work with security manager installed, it would have torequire the 
> application to grant certain permissions - can you confirm?  For 
> example it calls sun.reflect.Reflection.getCallerClass method that 
> will require RuntimePermission("accessClassInPackage.sun.reflect") 
> permission. Calling Class.getProtectionDomain and 
> Class.getClassLoader() requires 
> RuntimePermission("getProtectionDomain") and
> RuntimePermission("getClassLoader") respectively.  That gives me an 
> impression that permission check on individual stack frame might be a 
> non-issue?
>
> Mandy
> [1] 
> http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-September/020525.html
>
> On 9/3/13 5:24 AM, Nick Williams wrote:
>> On Sep 3, 2013, at 4:07 AM, Alan Bateman wrote:
>>>> I'm not voicing any objection to any kind of security/permissions 
>>>> checks in these methods. Before I can pass judgement one way or 
>>>> another, I'd want to know 1) specifically what type of permissions 
>>>> check you are looking for, and 2) what you're looking to achieve 
>>>> with said permissions check.
>>> I would say this is TBD and start by asking the question as to 
>>> whether there are concerns about leaking reference to Class objects 
>>> that untrusted code wouldn't normally be able to get a reference to. 
>>> Tom brings up the cost of the permission check and also whether any 
>>> API should be tied to class loader. There are clearly discussion 
>>> points here that could potentially influence the API.
>> As I have said before, there are MANY ways to get a Class object that 
>> aren't security checked. It's when you try to USE that class object 
>> to impersonate it or invoke methods that security checks begin to 
>> happen. As they should!
>>
>> Nick
>
>
>
> On 9/1/13 1:16 AM, Nick Williams wrote:
>> I have completed and am proposing a patch for replacing 
>> sun.reflect.Reflection#getCallerClass(...) with a public API in Java 
>> 8. I saw no point in duplicating an issue, even though it's over 10 
>> years old, so I'm indicating that this is a patch for 4851444 
>> (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4851444).
>>
>> I welcome and solicit support/+1s and a sponsor. Someone about a 
>> month ago had mentioned that they would be willing to be a sponsor if 
>> the patch looked good, but I can't remember who it was and I can't 
>> find the email. I want to say it was someone with RedHat, but my 
>> memory could be faulty, so please don't hold it against me if I'm wrong.
>>
>> *Summary of Changes*
>> Added the new class java.lang.StackTraceFrame. It's a virtual mirror 
>> of StackTraceElement, except that it contains a Class<?> 
>> declaringClass property instead of a String className property. Since 
>> the list members expressed reluctance to add methods to Thread and 
>> Throwable, StackTraceFrame also contains several static methods for 
>> retrieving Classes and StackTraceFrames from the stack. These methods 
>> are as follows:
>>
>> Class<?> getCallerClass(): Retrieves the class of the caller of the 
>> method calling getCallerClass(). This is identical to the new 
>> Reflection#getCallerClass() added in Java 7u25/8.
>>
>> Class<?> getCallerClass(int): Retrieves the class of the caller n 
>> frames down the stack. This is identical to the old 
>> Reflection#getCallerClass(int) that was deprecated in Java 7u25 and 
>> removed in Java 8.
>>
>> StackTraceFrame getCallerFrame(): Retrieves the StackTraceFrame of 
>> the line of code that called the method calling getCallerClass(). 
>> This is similar to the new Reflection#getCallerClass() added in Java 
>> 7u25/8, except that it returns a StackTraceFrame.
>>
>> StackTraceFrame getCallerFrame(int): Retrieves the StackTraceFrame of 
>> the caller n frames down the stack. This is similar to the old 
>> Reflection#getCallerClass(int), except that it returns a 
>> StackTraceFrame.
>>
>> StackTraceFrame[] getCurrentStackTrace(): Functionally equivalent to 
>> Thread#getStackTrace(), except that it returns an array of 
>> StackTraceFrames.
>>
>> StackTraceFrame[] getStackTrace(Throwable throwable): Functionally 
>> equivalent to Throwable#getStackTrace(), except that it returns an 
>> array of StackTraceFrames. It uses the same save point (backtrace) 
>> created when the Throwable is created that Throwable#getStackTrace() 
>> uses when it's first called. It caches the array of StackTraceFrames 
>> in the Throwable just like the array of StackTraceElements are 
>> cached, so that multiple calls for the same Throwable are fast.
>>
>> As a result of this change, sun.reflect.CallerSensitive has been 
>> moved to java.lang.CallerSensitive.
>>
>> I spent considerable time reviewing, revising, considering, and 
>> testing these changes. I included several unit tests that establish 
>> the proper behavior. I also spent considerable time benchmarking the 
>> changes. While benchmarking, I noticed some things. First, 
>> getCallerClass() and getCallerClass(int) are as fast as their 
>> counterparts in sun.reflect.Reflection, and they easily inline when 
>> appropriate. Second, getCallerFrame() and getCallerFrame(int) are 
>> /almost/ as fast as the Class versions, but there is some additional 
>> overhead for the construction of the StackTraceFrame. This is 
>> minuscule (1,000,000 invocations consume around 500 ms total on my 
>> machine). At this point, all of the benchmarking was as expected.
>>
>> However, I then encountered a surprise. The getCurrentStackTrace() 
>> and getStackTrace(Throwable) methods executed (again, 1,000,000 
>> times) in about 70% of the time that Thread#getStackTrace() and 
>> Throwable#getStackTrace() did, respectively. Theoretically, they 
>> should have executed in the same amount of time, not faster. After 
>> extensive analysis, I discovered (what I considered to be) a serious 
>> flaw in how the stack trace is filled in within Throwable (which also 
>> affects how Thread#getStackTrace() works).
>>
>> Instead of simply iterating over the entire save point and filling in 
>> the Throwable stack trace in native code (which is what I did when I 
>> implemented the StackTraceFrame methods), the Java code in Throwable 
>> first called a native method to figure out how deep the stack was, 
>> then called another native method once for every frame in the stack 
>> to retrieve each element individually. This native method that is 
>> called repeatedly iterates over the entire backtrace once for each 
>> call, stopping only when it finds the matching element (so it's O(1) 
>> for the first call, O(2) for the second call, O(3) for the third 
>> call, and so on). While my StackTraceFrame methods were iterating 
>> over the backtrace exactly 1 time (O(n)), the Throwable methods were 
>> iterating over the backtrace 1+(n/2) times (worse than O(nlogn) but 
>> not as bad as O(n^2)). This problem would not have been extremely 
>> apparent over small stack traces (the 30% improvement was a stack 
>> trace of 6 elements), but over a large (200+ elements) stack traces 
>> the performance difference would be huge and noticeable. Seeing a 
>> room for improvement, I refactored the code that fills in the stack 
>> trace for Throwable, improving its performance accordingly to match 
>> the performance of the StackTraceFrame methods.
>>
>> I'm very pleased with how this turned out, and both the unit tests 
>> and my extensive functional testing show that the new class and its 
>> methods are working great. I just need someone willing to review and 
>> sponsor my patch!
>>
>> *The Code Changes*
>> I couldn't get WebRev to run without all kinds of errors. So I ran 
>> `hg diff -g` on every repository in the forest with changes. Here are 
>> the four patch files for the four repositories that had changes (no 
>> other repositories had changes):
>>
>> https://java.nicholaswilliams.net/Java8GetCallerClass/jdk8.patch
>> https://java.nicholaswilliams.net/Java8GetCallerClass/jdk8_jdk.patch
>> https://java.nicholaswilliams.net/Java8GetCallerClass/jdk8_hotspot.patch
>> https://java.nicholaswilliams.net/Java8GetCallerClass/jdk8_nashorn.patch
>>
>> I believe I have followed all of the procedures as closely as 
>> possible. I await feedback and hope for some support on this, so that 
>> we can get a public replacement for this method in Java 8. Let me 
>> know if you have any questions.
>>
>> Thanks!
>>
>> Nick
>
>




More information about the core-libs-dev mailing list