Take this lightly informed suggestion with a grain of salt, but why not, for 
purposes of performance and security,
change the logging-specific getCallerClass methods so that their "class" 
references are instead wrapped in some sort of proxy object that only forwards 
certain operations quickly without a security check?  For example, equals, 
hashcode, and toString are probably not security-sensitive.

i.e.
class SafeClass {
 private final Class clazz;
 public SafeClass(Class clazz) { this.clazz = class; }
 public String toString() { return clazz.toString(); }
 public int hashCode() { return clazz.hashCode(); }
 public boolean equals(Object o) { return clazz.equals(o); }
 public Class maybeWeLetYouLookAtTheRealClass() { ... a bunch of security 
checks ... }
}

If necessary, do the same for classloaders.
And them, no security checks needed, as long as the "safe" methods are enough 
to get the job done.

David

On 2013-09-09, at 10:54 AM, Mandy Chung <[email protected]> wrote:

> Nick,
> 
> Do you have any information to some of the questions I asked below that can 
> help the API discussion?
> 
> We need to decide on the permission check and also the hotspot change has to 
> be integrated and the jdk change will need to wait until it shows in a 
> promoted build.  Schedule-wise, to make JDK 8, we should finalize the API 
> this week so that you can update the patch soon.
> 
> Mandy
> 
> 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).
>> 
>> 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
>> 
>> 
> 

Reply via email to