In my previous article on this topic, I had given an scenario which justified the use of the decompiler. I have now remembered a much better example.
A customer had problems after upgrading Weblogic 8.1sp2 to 8.1sp3. His JSP - which was rather large - suddenly stoped working with ‘method exceeds 64K’ messages. The JSP itself hasn’t changed between the versions.
The obvious suggestion here is that BEA’s JSP compiler started to generate larger code. We had one issue matching that, but reverting to the old behaviour via flag switch didn’t help.
Unfortunately, due to the complex nature of the JSP, I could not replicate the issue in my lab (too many dependencies and propriatary taglibs). So, I get the customer to send me the class files generated by both versions of JVM (JRockit’s equivalents of SUN’s 1.4.1 and 1.4.2).
The class file sizes are different!
So, given the same input JSP, but different resulting class sizes what would you suspect? I figured that the JSP compiler must have other, not yet identified, changes and get the customer to give me java sources generated by the JSP compilers.
Now, reading the source generated by a JSP compiler in my eyes is just as much of a voodoo and ‘under-the-covers’ work as any other decompilation. But of course, it was our (BEA’s) compiler, so I had to do it anyway. No way to shift the blame to the other vendor here.
So I slog through the source versions trying to identify the differences that are NOT comments. Eventually, I find out the sources are the same!
So, same JSP produces the same Java source, yet the class sizes are different. This is where I had to pull out a decompiler or to be exact the disassembler (javap).
Unfortunately, decompiling both classes produced so many differences on a bytecode level that I had to go back and try to create a replication.
To cut the long story short, it turned out that the difference in file sizes only appeared when there was a try/finally clause. From that point on, the simple test case showed that the content of the finally clause was duplicated into the try class. And - worse yet- with nested try/finally clauses, the outermost finally could get copied up to four times.
As you can probably imagine, BEA’s JSP compiler had a lot of nested try/finally clauses in an attempt to recover from nearly any JSP failure possible. The resulting copies of large finally clauses were more than enough to blow the method limit out of the water.
So, I had a replication and I had an explanation of what happened. But I still did not know why. As the problem was happening in JRockit JVM, I escalated it to the JRockit engineers.
They came back and said that there was no JRockit change that could cause that. But, they spoke to their Sun counterparts and somebody mentioned an internal bug report that may have something to do with it. It took a while to find the internal partner’s account number, not something they just give out to random support personnel.
Eventually, I got to read the bug and, sure enough, Sun changed the behaviour or javac to generate duplicate sections (Bug ID: 4494152). And the reason they did that was so trivial that there must have been other ways to fix it. In fact, the change causes some ugly cascading bugs (1, 2).
In summary, there would have been no way I were able to identify the problem without breaking out the javap. And no way would anybody else be able to identify a non-public Sun change without having a clear cut replication case.
Disassemblers, decompilers, tracers, etc are all effective tools of a developer. Refusing to ever use one due to the vendor’s responsibility concept is not really a supportable stance. Even book publishers think that way (1, 2).
BlogicBlogger Over and Out