📜 ⬆️ ⬇️

Hachim IntegerCache in Java 9

For many, the transition to Java 9 looks like something abstract. Let's translate this into a practical plane with one short, victorious example that Peter Vargas [1] cited in his article.

This is an article in the “wrong translation” genre with ad-libbing, because I am an artist, I see it so =) References to sources - as always, at the bottom of the text.

Five years ago, Peter published a Hungarian blogpost about how to hack IntegerCache in the JDK. This is just a small runtime experiment that has no practical use other than enhancing knowledge, understanding how reflection works, and how the Integer class works.

Look, the generation of real random numbers depends on the entropy of the system [2] . Some argue that this can be done with an honest die roll [3] .
')
image

Others believe that we will come to the rescue by overriding the body of the java.math.Random.nextInt () method.

For those who do not know the ancient bayan [4] . At the hackathon of the Netherlands JPoint in 2013, the build and modification of OpenJDK was discussed. After Roy van Rijn learned how to build it under Windows (how to do it in 2017, I wrote here [5] ), he immediately got down to business and made his first commit.

Instead of changing the core of OpenJDK (which is all in native codes, you need to be a doctor of science), he found that the core libraries are just classes in Java, and they are defenseless against his charisma. If you look in [openjdk] / jdk / src / share / classes , you can find the usual directory-packages like “java. *”, “Javax. *” And even “sun. *”. Therefore, you can get into [openjdk] /jdk/src/share/classes/java/util/Random.java with dirty boots and make an obvious change:

public int nextInt() { return 14; } 

After rebuilding the JDK, all calls to new Random (). NextInt () will indeed return 14.

But this is all complete bullshit. The real boys know that the real way to add entropy is to rewrite java.lang.Integer.IntegerCache at the start of the JVM (and below we will show how).

We remind you that Integer contains a private inner class IntegerCache containing objects of type Integer for the range from -128 to 127. When the code is boxed in Integer, and has a value from this range, runtime uses the cache instead of creating a new Integer. All this for the sake of speed optimization, and implying that in real programs the numbers constantly fit into this range (take at least array indexing).

The side effect of this is the well-known fact that the comparison operator can be used to compare the values ​​of ints, as long as the number is in the specified range. It's funny that such code (being written incorrectly) usually works in all sorts of unit tests (written incorrectly to be consistent), but will fall down in real use as soon as the values ​​go beyond 128. The author of this habroost wonders why this implementation detail was Stretched into the light of day and settled in tests for interviews, spoiling the unruly childish mind to many good people.

Attention, danger. If you plug IntegerCache through reflection, it can lead to magical side effects and will have an effect not only on a specific place, but on the entire contents of this JVM. That is, if the servlet changes some pieces of cache, then all the other servlets in the same Tomkate will be in trouble. Olso, we warned.

Well, let's take the Java 9 beta and try to commit on it the same indecency that rolled in Java 8. Copy the code from the article by Lucas [2] :

 import java.lang.reflect.Field; import java.util.Random; public class Entropy { public static void main(String[] args) throws Exception { //  IntegerCache  reflection Class<?> clazz = Class.forName( "java.lang.Integer$IntegerCache"); Field field = clazz.getDeclaredField("cache"); field.setAccessible(true); Integer[] cache = (Integer[]) field.get(clazz); //  Integer cache for (int i = 0; i < cache.length; i++) { cache[i] = new Integer( new Random().nextInt(cache.length)); } //  ! for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } } } 

As promised, this code accesses IntegerCache using reflection, and fills it with random values. What a marvelous dirty decision!

Now we run the same code under Nine. Bad news for dirty boys, there will be no holiday. When trying to humiliate her, Nine reacts much more seriously:

 Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field static final java.lang.Integer[] java.lang.Integer$IntegerCache.cache accessible: module java.base does not "opens java.lang" to unnamed module @1bc6a36e 

We received an exception that did not exist in the G8. It says that the object is not accessible because the java.base module, which is part of the JDK runtime and automatically imported by any java-program, does not “open” (sic) the module we need for the unnamed module. The error falls on the line where we are trying to make the field accessible.

The object that we could easily reach in the G8 is no longer available, because it is protected by a system of modules. The code can access fields, methods, and so on, using reflection, only if the class is in the same module, or if this module allows reflection access for the whole world, or for a particular module.

This is done in a file called module-info.java, like this:

 module randomModule { exports ru.habrahabr.module.random; opens ru.habrahabr.module.random; } 

The java.base module does not give us access, so we suck a paw. If you want to see a more beautiful error, you can create a module for our code, and see its name in the text of the error.

And can we programmatically open access? There in java.lang.reflect.Module there is some kind of addOpens method, does it go wrong? The bad news is no. It can open a package in module A for module B only if this package is already open for module C, which calls this method. Thus, modules can transfer to each other those rights that already have, but cannot open, closed.

But the same can be considered good news. Java is growing above itself, Nine is not as easy to break as the Eight. At least this little hole was closed. Java is increasingly becoming a professional tool, not a toy. Soon we will be able to rewrite all the serious software, now written by IBM RPG and COBOL.

Oh yeah, it can still be broken like this:

 public class IntegerHack { public static void main(String[] args) throws Exception { //  IntegerCache  reflection Class usf = Class.forName("sun.misc.Unsafe"); Field unsafeField = usf.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); Class<?> clazz = Class.forName("java.lang.Integer$IntegerCache"); Field field = clazz.getDeclaredField("cache"); Integer[] cache = (Integer[])unsafe.getObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field)); //  Integer cache for (int i = 0; i < cache.length; i++) { cache[i] = new Integer( new Random().nextInt(cache.length)); } //  ! for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } } } 

Maybe it is worth banning also Unsafe?

Btw, if you are afraid to write comments here, then you can crawl into my fb , or meet live at some Joker 2017 , or just cross near BC Kronos or Goose in Novosibirsk, have a beer with a smoothie and discuss some other funny game . More game god game!

PS I was asked to insert into the article seals. Therefore, here is a rare photo of a smiling Mark Reinhold:



Sources :

[1] Original article
[2] The person who reanimated the code from the article in Hungarian
[3] The well-known picture about random numbers
[4] How to override nextInt
[5] How to build java under Windows

Source: https://habr.com/ru/post/328120/


All Articles