Here's the complete flow. Yes, it's a hack. A chain of things you're
not supposed to do.
1. disableCheckedExceptions-alpha.jar (hereafter: dCE.jar) is an
annotation processor. This is public API, and is done by adding a SPI
file (META-INF/services/javax.annotation.Processor listing the
classname of your processor. So far we're still in public API
territory.
2. The processor gets initialized by javac. Still okay. But this is
where we walk away from public API, and into the territory of things
which you're not supposed to do:
3. The processor first forces class initialization of
sun.instrument.InstrumentationImpl via Class.forName. If this fails, a
warning message is emitted via the processor API and nothing else
happens (you'd be using a non-sun/sun-derived Java VM. Both the
OpenJDK and Apple VMs DO work with this jar, though). If it works,
this causes (under the hood) the instrumentation system to load, which
includes the loading of
libinstrument.so/jnilib/dll, which is part of
the sun JRE, so this is NOT a dependency on a native library. We need
it to be loaded so we can start calling into it:
4. Using JNA, we act as if we are the JVMTI system and call into the C
code that start injected java agents. Normally you do this by
connecting something like jconsole to a running VM, and loading an
agent that way. You can actually connect to your own VM, but
initializing the management facilities in the middle of a VM run takes
more than 3 seconds on my machine. I assume nobody in his right mind
is willing to tolerate 3 seconds of delay anytime you start javac,
which is why that solution isn't acceptable, and we fake it with a C
call instead. That only adds about 170 milliseconds to the total time
of javac, which seems acceptable.
5. We hand the C call to start an agent our own jar file - the
interface requires a jar file as the agent class name is stored in the
jar manifest. You can't actually know what your jar file is with
public API, so we again hack it: We use Class.getResource() to get a
URL to our own processor class, and we then regexp the jar part out of
it, so we can hand that off to the C call (which is Agent_OnAttach, in
case you were wondering).
6. The Agent_OnAttach call does its thing and our agent is called via
agentmain. We get an Instrumentation object, which is the goal of this
entire exercise. With it, we register a class transformer, and then
use the new (since v1.6) reload class feature to cause
com.sun.tools.javac.comp.Check to be reloaded.
7. As we're a registered class transformer now, we get the chance to
modify the raw byte array of the com.sun.tools.javac.comp.Check class.
We do this using the ASM library (from objectweb - excellent library
to rewrite class files). Specifically, the isUnchecked(ClassSymbol)
method is rewritten from whatever it is to: "return true;".
That's it. The annotation processor's done its job and technically the
agent and the processor can be unloaded at this point in time, though
that's not what happens - all the actual action hooks just do nothing
and return immediately. I don't think there's much value in unloading
at this junction.
I spent the least amount of time on doing that rewrite, the majority
of the work is in letting an annotation processor load itself as an
agent during init process. I just went off what you found out earlier,
Casper. It would indeed be nice if the tool becomes configurable:
- allow catching of unthrown exceptions: Error / Warning / Allow
- allow throwing of undeclared checked exceptions: Error / Warning /
Allow
parameters could be transported to javac via -D switches, though I'd
rather find a better way to configure this stuff.
Eventually this stuff will find its way back into lombok, but until I
figure out how to do this to eclipse, it'll remain a completely
separate side thing.
The code really isn't very big. It's just 2 java files, and some
effort in loading the right SPI file and the right manifest in the
build.xml. Here are links:
http://github.com/rzwitserloot/lombok/blob/172958ac099aa7bb6cef140cc8b6192531e5cb88/src_disableCheckedExceptions/lombok/javac/disableCheckedExceptions/DynamicAgent.java
http://github.com/rzwitserloot/lombok/blob/172958ac099aa7bb6cef140cc8b6192531e5cb88/src_disableCheckedExceptions/lombok/javac/disableCheckedExceptions/CheckForThrownExceptionTransformer.java
if you want to hack away at it, git clone that project, then:
git checkout disableCheckedExceptions