-
1. Re: Throwing an exception in a try/catch block
adinn May 9, 2017 4:53 AM (in response to rmichoud)Hi Roger,
That is indeed a deliberate limitation. I'll explain why before providing a rule which does precisely what you want.
Let's start with your example. If Byteman simply injected "throw new IOException" into the try block just before the new operation then that would not change the method contract for your original code. The exception would be caught. So, the method would not generate an unexpected exception (I am assuming there is no outer try catch).
Now imagine instead that you asked for the exception to be injected into the catch block. If Byteman simply did what you asked then this would cause an IOException to be thrown from the method that the code belongs to. That would be a very bad idea as it changes the method's contract with its callers (of course, if the method declared IOException as a checked exception then it would be legitimate to inject a throw into the catch block). Luckily, the JVM's byecode verifier checks code after agents apply transforms and rejects any transform that would produce a breaking change.
So, I could implement a throw rule by injecting a throw directly into the bytecode at the injection point. Byteman could check the try-catch plumbing at every requested each injection point to see whether an injected throw made sense. If a throw is valid because it will be caught or because it will escape but still match the method's exception signature the Byteman could happily inject the rule. If not it could notify an error. However, I decided that this was not how I wanted throw to work.
The problem with this model is that your injected code is hostage to the try-catch structure of the method. Consider the case where a method catches an InterruptedException and then rethrows it
... connection = establishConnection(); try { pollConnection(connection) } catch (InterruptedException e) { resetConnection(connection) throw e } ....
In this case if Byteman injected "throw new InterruptedException" at the call to poll it would not bypass the handling code that resets the connection. That might actually be critical to establishing your test scenario and there is no way to work round it.
So, Byteman implements throw as a short-circuit of the method from the injection point. If you inject a THROW rule at the call (AT INVOKE pollConnection) then when (IF) it executes the throw control exits the method immediately at the top level, bypassing any intervening catch blocks. n.b. Byteman adopts the same model when you inject a RETURN rule. This behaviour means that it is easier to write correct rules. If you want to THROW an exception (or RETURN, with or without a value) then you simply need to ensure that the exception type (or return type) conforms to the method's exception (or return) declaration. You don't need to be sure that the injection point will be inside a compatible try block.
So, in that case how do you achieve the result you want, where an exception is generated inside the try block and caught by the catch block. The answer is to inject it into the called method that generates the exception. With your code you could use a rule that looked like this
RULE throw IOException so myMethod can catch it CLASS PrintWriter METHOD <init> AT ENTRY IF callerEquals("MyClass.myMethod", true) DO THROW new IOException("Byteman foils the evil villains once again!"); ENDRULE
The rule is injected into the constructor for PrintWriter. So, when your code creates a new PrintWriter it throws an exception. Notice that the condition includes a test which executes built-in method callerEquals to ensure that the calling method name and class match the caller you are interested in (the second argument true indicates that the supplied name "MyClass.myMethod" specifies both the class name and the method name).
-
2. Re: Throwing an exception in a try/catch block
rmichoud May 9, 2017 2:33 PM (in response to adinn)Hi Andrew,
This is awesome, exactly what I wanted. I thought about triggering the exception from the called functions (here the constructor) but I didn't know that you could do a filter on the caller and I wanted the exception to be injected only in that function. Great!
Keep up the good work!
Roger