Java Access Control, Stop the Insanity!

crazy pills

Java access modifiers protected, package-private, and 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.

Crazy Talk

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:

Wishful Thinking

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;

The 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.

Back to Reality

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 unsafe proposal:

@Jailbreak Foo foo = new Foo();
foo.privateMethod();
foo.privateField = null;

As with unsafe the @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.