"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 >