Java access modifiers
private are enforced both by the compiler and the JVM.
Here Scott McKinney explains why this is insane and how access to internals could be made much simpler and type-safe.
You use access modifiers as a means to separate API from implementation (aka encapsulation). The modifiers provide a clean way for you to tell other developers, “Here’s the stuff I intend for you to use, all the rest is internal to my implementation.” In essence access modifiers help you define, identify, and safely use an API.
Fortunately the Java compiler ensures you don’t unintentionally cross the boundaries established by the modifiers. There’s no way you can call a private method illegally without the compiler complaining about it. So why does the JVM also enforce access control when the compiler has already done the work? Is it a form of security? Sadly this is a widely held misconception. Allow me to explain.
The lock on your front door is a form of security, right? The lock’s purpose is to prevent an intruder from entering your home. One thing you wouldn’t do is devise a way for intruders to evade your security. And you certainly would not advertise this information for all to see, right? For example, you wouldn’t post a sign on your front lawn describing the spare bedroom window you leave wide open specifically for intruders, right? Otherwise by definition the lock on your door is not a form of security, but is instead an indication of insanity.
The Java Reflection API is the sign posted on the JVM’s front lawn. It explains precisely how to bypass access control; you can call any method you like using reflection. Therefore under normal circumstances1 JVM access control is not in any logical sense a form of security.
Repeat after me:
As previously stated under normal circumstances there is no reason the JVM should prevent bytecode from accessing a type’s internals since the compiler already makes the necessary guarantees. Furthermore there should be a way to use normal, type-safe syntax to access internals – nothing is accomplished by making it hard and slow and dangerous with reflection.
As a productive alternative the Java language could provide a simpler, type-safe syntax to access otherwise hidden symbols:
unsafe Foo foo = new Foo(); foo.privateMethod(); foo.privateField = null;
unsafe modifier informs the compiler of your intention to type-safely use internal symbols of
Foo. As such
the compiler grants
foo with open access to
Foo. The advantages of this approach are compelling:
All of this could be achieved with or without the cooperation of the JVM. If access control were removed from runtime,
great! Aside from the support for the new
unsafe modifier, nothing else is needed from the compiler and performance is
optimal. Otherwise, the compiler could generate reflection code for usages of
unsafe variables, still a big win.
Of course all of this is make believe,
unsafe will never see the light of day in Redwood City. But should we give up?
Was it over when the Germans bombed Pearl Harbor? Heck no! Where there’s
a will, there’s a way…
Increasingly the Manifold framework picks up where Java leaves off in terms of type system
limitations. In the case of access control Manifold provides a feature aptly named Type-safe Reflection.
It is nearly identical to the
@Jailbreak Foo foo = new Foo(); foo.privateMethod(); foo.privateField = null;
@Jailbreak annotation grants
foo full access to
Foo, all type-safe and compiler friendly.
Additionally IntelliJ IDEA provides comprehensive support for Manifold via plugin. Read more about Type-safe Reflection
and other features at Manifold.io.
1: Standard JVMs and conventional JDKs and application environments are the focus of this article.