"For caller-sensitive methods, the approach taken with new 
Reflection.getCallerClass() is the right one, I think. There's no need to 
support a fragile API when caller-sensitivity is concerned, so the lack of 
"int" parameter, combined with annotation for marking such methods is correct 
approach, I think."

Not when the code running on Java 8 was compiled on Java 6 or 7 and thus can't 
be annotated @CallerSensitive. In these cases, use of any new public API must 
use reflection (sudo code: If Java 8, use new public caller-sensitive API), so 
it needs code that it can pass a number into.

Nick

On Jul 30, 2013, at 7:17 AM, Peter Levart wrote:

> 
> On 07/27/2013 09: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.
> 
> Hi,
> 
> The needs described above may seem related, but from what I see in this 
> commit:
> 
>     http://hg.openjdk.java.net/jdk8/tl/jdk/rev/da6addef956e
> 
> my observations are as following (please comment if I missed or misunderstood 
> anything):
> 
> sun.reflect.Reflection.getCallerClass(int) is/was used internally in JDK more 
> or less for different purposes than outside the JDK. Inside it was used 
> basically for implementing security-sensitive checks like optimizations for 
> public methods which can avoid calling SecurityManager API wen called from 
> withing JDK classes. It was used for security-unrelated purposes too, like 
> for example Class.forName(String) or ResourceBundle.getBundle(String). All 
> internal JDK uses do share one common thing though: it is very important that 
> the right direct caller of a caller-sensitive method is established, since 
> any failure to do so can have devastating effect on security or correctness.
> 
> The API taking an "int" to count the frames between the top of the call-stack 
> to the indirect caller was convenient, but too fragile to support such 
> important use cases. Every time some code was refactored, there was danger 
> that some call-frame was inadvertently inserted or removed. So I think it was 
> decided to "cripple" the API to only support obtaining the immediate caller 
> of the method making call to the Reflection.getCallerClass() and all uses 
> modified accordingly to make the internal JDK code more robust to 
> refactorings.
> 
> And there's also MethodHandles which are early-bound. Meaning that the caller 
> is established and bound when the MethodHandle instance is looked-up. The 
> "lookupClass" which is associated with the Lookup object and used in 
> permission checks when obtaining MHs is also used as the bound caller class 
> when the MH is invoked. Now there's a method:
> 
> java.lang.invoke.MethodHandles.Lookup {
>     public java.lang.invoke.MethodHandles.Lookup in(java.lang.Class<?> 
> requestedLookupClass)
> 
> that returns a Lookup object which reports a different "lookupClass" and has 
> capabilities to lookup MHs which are combined from capabilities of the 
> original Lookup object and new lookupClass (tipicaly less, never more). Most 
> of such Lookup objects are prevented from looking-up MHs for caller-sensitive 
> methods, since they could be used to "pose" as a caller that is not the one 
> having obtained the MH and therefore gain access to restricted resource, for 
> example:
> 
> MethodHandle mh = MethodHandles.lookup().in(Object.class).lookupXXX(....)
> 
> ...such mh could be used to pose as being called from withing Object if 
> allowed to be obtained for caller-sensitive methods. So here comes 
> @CallerSensitive annotation to mark such methods and prevent such lookups 
> (before that - in JDK7, all internal caller-sensitive methods were 
> hand-maintained in a list).
> 
> So this is, what I think, the background and rationale for changing the API.
> 
> For outside JDK use, I think there are two main needs, which are actually 
> distinct:
> 
> a) the caller-sensitive methods
> b) anything else that is not caller-sensitive, but wants to fiddle with the 
> call-stack
> 
> For caller-sensitive methods, the approach taken with new 
> Reflection.getCallerClass() is the right one, I think. There's no need to 
> support a fragile API when caller-sensitivity is concerned, so the lack of 
> "int" parameter, combined with annotation for marking such methods is correct 
> approach, I think. The refactorings to support this change in JDK show that 
> this API is adequate. The "surface" public API methods must capture the 
> caller class and pass it down the internal API where it can be used.
> 
> So what would give Groovy or other language runtimes headaches when all there 
> was was a parameter-less getCallerClass() API? Aren't the intermediate frames 
> inserted by those runtimes controlled by the runtimes? Couldn't the "surface" 
> runtime-inserted methods capture the caller and pass it down? I guess the 
> problem is supporting calling the caller-sensitive methods like 
> Class.forName(String) and such which don't have the overloaded variant taking 
> caller Class or ClassLoader as an argument...
> 
> John Rose suggested to "capture" the caller in the "surface" method and bind 
> it with a MethodHandle and then pass such MH down the runtime API and finally 
> call that method via MH. For that to work, two problems would have to be 
> resolved first:
> 
> 1) the runtime-inserted "surface" method would have to be annotated with 
> @CallerSensitive so that illegal "posers" could be prevented
> 2) this "surface" method would have to be given permission to "pose as" the 
> caller class when looking-up the MethodHandle of the target caller-sensitive 
> method.
> 
> The 1st part is not a problem I think, but the 2nd part is a problem. What 
> makes the runtime-inserted "surface" method so special that it can be allowed 
> to "pose" as its caller?
> 
> Now that is the question for mlvm-dev mailing list: Isn't preventing almost 
> all Lookup objects obtained by Lookup.in(RequestedLookupClass.class) from 
> obtaining MHs of @CallerSensitive methods too restrictive? 
> 
> Currently classes are only allowed to "pose as" it's nest-mate classes - the 
> classes that share the same outermost enclosing class, since the check to 
> look-up @CallerSensitive methods is based on the ability to look-up PRIVATE 
> members. So the ability to "pose as" another class when binding caller 
> already exists even for @CallerSensitive methods, just the restriction is too 
> conservative, isn't it?
> 
> Perhaps a class that is visible from the calling class could be allowed to 
> look-up MHs of @CallerSensitive methods and "pose" as the calling class, 
> bearing all other security checks for combined abilities have passed. For 
> example, why wouldn't class A be allowed to "pose as" class B if they are 
> loaded by the same ClassLoader or if class B is loaded by a ClassLoader that 
> directly or indirectly delegates to the ClassLoader of class A?
> 
> These are my thoughts about caller-sensitivity and why I think it requires 
> special restricted API. Anything else that needs to examine the whole 
> call-stack is a separate need that is not infected by the strict constraints 
> of caller-sensitivity and for that purpose an API like the one presented 
> below (StackTraceFrame) is a good starting-point, maybe it just doesn't need 
> the static getCallerFrame() method which suggests that it's use is for 
> implementing caller-sensitive methods.
> 
> Regards, Peter
> 
>> 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
> 

Reply via email to