Hi Dan,
<trimming>
On 21/04/2017 4:43 AM, Dan Smith wrote:
Security risk
The JEP text should acknowledge that, while this does allow compilers to grant
finer-grained access to members shared by nestmates, it also pushes compilers
to grant broader access to members that were previously kept private. It's a
trade-off, and presumably a good one because nestmates are completely trusted,
while package-mates might sometimes be suspect.
I'm not following you here. What broader access is being granted to members
that were previously kept private? This effort allows private-only access to
members that were previously package-accessible.
Old approach is to widen access to private members to the package level on a
per-member basis.
New approach is to widen access to private members to the nest level on a
per-class basis.
Nest is narrower than package (good!), but per-class is more permissive than
per-member (bad). Some members that are compiled as totally private in JDK 9 will be
incidentally shared with all nest members in <future JDK version>.
I'm not sure how you are coming at this. For a private member in 9 to be incidentally
shared in <future-JDK> the class of the member has to be added to a nest. With
no context as to how this may happen I can't really comment further. From the Java
language perspective if the class is in a nest then such access to private members
already exists.
Example:
class Outer {
private int x;
private int y;
class Inner { private int z = x; }
}
In JDK 9, this compiles to:
class Outer {
private int x;
private int y;
Outer() {}
static int access$000(Outer arg) { return x; }
}
class Outer$Inner {
private int z;
final Outer this$0;
Outer$Inner(Outer arg) {
this$0 = arg;
z = Outer.access$000(this$0);
}
}
In <future-JDK>, this compiles to:
class Outer [NestMembers:{Outer$Inner}] {
private int x;
private int y;
Outer() {}
}
class Outer$Inner [MemberOfNest:Outer] {
private int z;
final Outer this$0;
Outer$Inner(Outer arg) {
this$0 = arg;
z = this$0.x;
}
}
Notice that in JDK 9, Outer shares access to y with nobody, while in
<future-JDK>, Outer shares access to y with its NestMembers.
Only because in JDK9 javac doesn't generate all potential accessors
(which it could). The current language rules say that y is accessible to
nestmates. As none try to actually access it javac doesn't bother
generating the accessor.
In:
"If getfield or putfield is used to access a protected field referenced in a
superclass ..."
and following, you changed "declared" to read "referenced". Again I do not understand
what this is supposed to mean. It is the declaration site of the field or method that is needed to determine
whether that member is in the same or another package. To me "declared in a superclass ..." was
exactly correct. Your note does not make the change any clearer to me.
Good catch. It's important that the rule be limited to cases that reference a
superclass*; but it's also important that the declaring class be used for the
package check.
(*Here's why: if I reference my subclass, and that resolves to a field declared
in my superclass, the verifier will perform no check, because it may not know
that the referenced class is a subclass.)
Revised:
"If `getfield` or `putfield` is used to access a `protected` field by reference to a
superclass, and that field is declared in a different run-time package than the current
class or interface, then the type of the class instance being accessed must be assignable
to the current class or interface."
I still think "declared in" is the correct terminology. There's a nuance to how
you are expressing this that I'm just not getting.
Example:
public class p1.A {
protected int i;
}
public class p2.B extends p1.A {
int test(p2.C arg) {
aload_1
getfield p2.C.i:I
ireturn
}
}
public class p2.C extends p2.B {
}
When the verifier checks the getfield, it will apply the rules in 4.10.1.8. The check
will trivially pass, because p2.C is not a superclass of the current class (p2.B). There
will be no test that p2.C is "assignable to the current class or interface".
Thus, p2.C need not be loaded during verification.
Strictly reading the old rule, "field declared in a superclass" means that the
verifier would have to load p2.C and resolve (or simulate resolution of) p2.C.i:I in
order to determine whether the field is declared in a superclass of the current class
(p2.B).
I'm not 100% sure of what needs loading when, but don't know why this is
an issue either. The package check is needed to see if the more specific
protected-access check is needed. As p2.C.i actually refers to the 'i'
declared in p1.A, then we should be checking if B and A are in the same
package.
6.5 invokespecial
You note regarding the IllegalAccessError:
"This replaces a VerifyError previously specified by 4.9.2. The check must be
delayed until after resolution in order to determine whether the referenced method is
private."
We know the access flags for the referenced method at verification time -
shouldn't that be the sole basis for the verifier check? Afterall it is
verifying the static properties of the classfile and bytecode. If the actual
resolved method has different access to the referenced method then that may
lead to ICCE (depending on the exact nature of the change - a private method
made public may not be an issue for example).
Example:
invokespecial SomeOtherClass.foo(I)V
Verification relies on the descriptor to tell it that this method expects an
int and returns nothing. Those kinds of checks can be performed locally.
The descriptor doesn't tell you if the method is private. Only way to find out is to
resolve SomeOtherClass, then resolve "foo(I)V".
If we wanted to do the check at verification time, we'd have to load every
class named by an invokespecial instruction.
Now I am confused. I thought the access modifier of the method described by the
Method_Ref was available to us. If it is not then how can all these
verification rules be expressed in terms of non-private when we do not have the
means at verification time to determine if the access is private or not ???
The rule is:
1) if the referenced method name is not <init> (cheap), and
2) if the referenced class name is the name of a superclass or direct
superinterface (cheap—superclass chain is already loaded), and
3) if the loaded referenced class actually is a superclass or direct
superinterface (almost always true and cheap, unless there's a name clash), and
4) if the resolved method is not private (pretty cheap, because referenced
class is already loaded)
*then* check that the stack type is assignable to the current class type.
This is, essentially, the same logic being employed by the old protected check:
if the reference is to a superclass, find the field/method and decide if it's
protected; if the reference is to some other class, don't worry about it.
So here's the problem, in the spec 5.4 states:
"Linking a class or interface involves verifying and preparing that
class or interface, its direct superclass, its direct superinterfaces,
and its element type (if it is an array type), if necessary. Resolution
of symbolic references in the class or interface is an optional part of
linking."
So resolution is _optional_ at link time! But your updated spec requires
resolution to happen before we can complete verification!
When we do the verification of invokespecial in the VM it is before
resolution and we do not know if the target method is private or not.
David
-----
—Dan