$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
- @LaChilangringa thank you, he will be called Walter and might like trains or frogs. You were at the rally? What did your sign say? in reply to LaChilangringa 2010-11-06
- It says I'm not eligible to get a payout in the Buzz settlement. I'll have to settle for juggling with the Buzz developers. :) 2010-11-03
- It's Movember and you can sponsor my mustache. http://goo.gl/Z1O4 I miss the beard; It's very drafty on my face today. 2010-11-02
- Can 4 guys make themselves look enough like Mount Rushmore to fool Google Goggles image search? Love the demo slam. http://demoslam.com 2010-10-20
- Saw Dalai Lama on Thurs, running last 6mi of SF women's marathon with Peggy today. Too many crazy crowds this week! 2010-10-17
- Attn: people of the future. We wanted to avoid all that litter! It was our 2nd priority, right after annoying noises. http://bit.ly/cJzkGT 2010-10-09
- Headed to Hardly Strictly bluegrass in GG park. Elvis Costello free! 2010-10-03
- I vote that @TCooganPlants is having a rough week and deserves nachos. Who's with me? 2010-09-29
- More updates...
Powered by Twitter Tools