$SwitchMap$
I recently became the maintainer of the Testability Explorer project. It’s a library that inspects Java bytecode, assessing how difficult the code will be to unit test. Check out the author’s blog: Misko Hevery. For more info about the project, see http://testability-explorer.googlecode.com.
Testability explorer looks for a few things in your code. One of them is some mutable object that’s globally accessible. For example, the Evil Singleton makes it hard to unit test anything that uses it, but a public static final String is fine, since String is immutable.
So I was investigating a strange issue where a large mutable global cost was assigned to some classes, due to an anonymous inner class. It seemed that there was some globally mutable state hidden in SomethingOrOther$1, whenever SomethingOrOther has a switch statement with a variable of type Enum as the argument. That doesn’t make your code hard to test, so what’s going on?
Try it at home! Compile this with javac:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class HasStaticCost { public int compare() { Fruit fruit = Fruit.APPLE; switch(fruit) { case APPLE: return 1; case ORANGE: return -1; default: return 0; } } public enum Fruit { APPLE, ORANGE } } |
And run javac HasStaticCost. The outputs are:
HasStaticCost$1.classHasStaticCost$Fruit.classHasStaticCost.class
Three classes? What’s in that first one?? We’ll have to look at the bytecode:
class com.google.test.metric.collection.HasStaticCost$1 extends java.lang.Object { static final int[] $SwitchMap$com$google$test$metric$collection$HasStaticCost$Fruit; static {}; Code: 0: invokestatic #1; //Method com/google/test/metric/collection/HasStaticCost$Fruit.values:()[Lcom/google/test/metric/collection/HasStaticCost$Fruit; 3: arraylength 4: newarray int 6: putstatic #2; //Field $SwitchMap$com$google$test$metric$collection$HasStaticCost$Fruit:[I 9: getstatic #2; //Field $SwitchMap$com$google$test$metric$collection$HasStaticCost$Fruit:[I 12: getstatic #3; //Field com/google/test/metric/collection/HasStaticCost$Fruit.APPLE:Lcom/google/test/metric/collection/HasStaticCost$Fruit; 15: invokevirtual #4; //Method com/google/test/metric/collection/HasStaticCost$Fruit.ordinal:()I 18: iconst_1 19: iastore 20: goto 24 23: astore_0 // same for Fruit.ORANGE 39: return }
This is pretty crazy. The ordinal values of the Fruit enum are stored into a static array, named $SwitchMap$<enumname-encoded-with-dollars>, and stored in this synthesized inner class, and accessible to other classes in the same package. And that synthesized class isn’t stored with the enum, instead, each class that switches on the enum gets blessed with some ugly global state. Why? There was a release of the JVM in Java 1.5, along with the new language features, so you’d think the switch statement would understand an enum type. But this bytecode looks like a complete hack to make the switch statement think it’s operating on an int instead. Here’s how the switch is implemented:
0: getstatic #2; //Field com/google/test/metric/collection/HasStaticCost$Fruit.APPLE:Lcom/google/test/metric/collection/HasStaticCost$Fruit; 3: astore_1 4: getstatic #3; //Field com/google/test/metric/collection/HasStaticCost$1.$SwitchMap$com$google$test$metric$collection$HasStaticCost$Fruit:[I 7: aload_1 8: invokevirtual #4; //Method com/google/test/metric/collection/HasStaticCost$Fruit.ordinal:()I 11: iaload 12: lookupswitch{ //2 1: 40; 2: 42; default: 44 }
At instruction 12, I would think the value 1 would naturally refer to the first ordinal value of the enum without needing to load the constant from the static array as it does.
It turns out this was just bad decision making by the Java 1.5 committee. They intended to implement Java 1.5 language features in a way that would execute on the 1.4 JVM, I bet because they knew that BigCorp wasn’t going to upgrade their production environments and the language features would sit on the shelf for a few years unless they could be deployed with little risk. So we got stuck with big things like generic type erasure, and small things like this hack for the switch statement… but hey, at least adoption would be easy.
I fixed testability explorer by whitelisting field names matching /\$SwitchMap\$.*/. Gross. Although that’s straightened out, it still leaves that bad taste in my mouth. Because it turned out that there was some feature added later on in Java 1.5 that did require a change to the VM, and so all these hacks are in there for no good reason. And, of course, BigCorp stayed on 1.4 for years.
No comments yet.
Leave a comment
About Me
Tweets
- I played the ice hockey for the second time in about 8 years. I was about as good as ever, I guess. Which was fairly bad. 16 hrs ago
- I finally jailbroke an iPhone. Now I feel like I have decent geek cred again. 2 days ago
- Lost a bolt on my lower control arm. Found out about it when the wheel came partly off. http://twitgoo.com/fw9e0 3 days ago
- Wow we have the craziest channel 1.6 on broadcast TV where I live, that runs this show: http://intensit.tv/ 5 days ago
- Dorfmeister is playing Zurich the day after I leave. Worst! 6 days ago
- 70 fresh, organic oranges from our tree were sitting on the table this morning. So, marmalade had to be canned. It's tasty! 6 days ago
- Moles, cousins, and unattended baggage #10kpyramid 1 week ago
- @mdauber You live in Sunnyvale too? And NBC is ruining your olympics also? We should get together. 1 week ago
- More updates...
Powered by Twitter Tools