1 2 3 Previous Next 40 Replies Latest reply on Oct 14, 2013 10:38 PM by ron_sigal Go to original post
      • 30. Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Skipping code with Byteman?
        ron_sigal

        Re: comment #27

         

        I made the same mistake I made in comment #26.  I was throwing the exception in the wrong class and method.

        • 31. Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Skipping code with Byteman?
          adinn

          Hi Ron,

           

          That was just a dumb Byteman programming mistake.

           

          Hmm, not quite so dumb as you think :-)

           

          Your initial rule was mistaken because you tried to use RETURN expression in an AT CALL rule to modify the value returned by the call which defines the trigger location. This is a misnomer because RETURN forces an immediate return form the trigger method. What you really wanted was to change the value returned by the called method. You don't have to modify the called method to do that. Here is what your rule could have looked like:

           

          @BMRule(name="constraintTypeUtil11_getConstraintType_unexpectedPathNode",
                  targetClass="org.jboss.resteasy.plugins.validation.ConstraintTypeUtil11",
                  targetMethod="getConstraintType",
                  targetLocation = "AFTER INVOKE getKind",
                  action = "traceln(\"call returned \" + $!); " +
                           "$! = javax.validation.ElementKind.METHOD; " +
                           "traceln(\"substituted \" + $!);"
                  )
          . . .
          

           

          The special variable $! is only valid when your location is AFTER INVOKE or AT RETURN. In the first case it refers to the return value of the invoked method -- i.e. the one specified by the AFTER CALL location. In the second case it refers to the return value of the trigger method (i.e. the value on top of the java stack at the point where return is about to execute. The type of !$ is defined by the relevant method's return type and on entry to the rule it is bound to the value which is about to be returned. If you reassign $! as above then the you value is returned instead.

           

          One more point about your alternative rule

           

          @BMRule(name="constraintTypeUtil11_getConstraintType_unexpectedPathNode",
                  targetClass="org.hibernate.validator.internal.engine.path.NodeImpl",
                  targetMethod="getKind",
                  targetLocation="AT ENTRY",
                  action="trace(\"returning METHOD\"); RETURN javax.validation.ElementKind.METHOD;")
          

           

          This rule will be triggered by every call to getKind irrespective of who the caller is. Now this may not be an issue for your test because nothing  calls it other than the code in the path you are testing. However, it is probably more robust to add a condition which ensures that the rule only fires when triggered under the desired caller

           

          @BMRule(name="constraintTypeUtil11_getConstraintType_unexpectedPathNode",
                  targetClass="org.hibernate.validator.internal.engine.path.NodeImpl",
                  targetMethod="getKind",
                  targetLocation="AT ENTRY",
                  condition="callerMatches(\"ConstraintTypeUtil11.getConstraintType", true)",
                  action="trace(\"returning METHOD\"); RETURN javax.validation.ElementKind.METHOD;")
          
          • 32. Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Skipping code with Byteman?
            adinn

            Hi Ron,

             

            I think you sorted this out but I''m not sure if you fully understood why what works does work. So, I'll provide some background here.

             

            There are two things you need to be aware of when using a THROW expression.

             

            1) The exception gets thrown from the trigger method to its caller and the trigger method is given no opportunity to catch it. A common mistake is to think that it gets thrown from the trigger point. Let's assume we have a method and a rule defined as follows

             

            class Foo {
              . . .
              int foo(int[] array, int idx) {
                try {
                  int val = array[idx];
                  bar(val);
                } catch Exception (e) {
                  System.out.println("caught it " + e);
               }
              }
              . . .
            

             

            RULE throw an exception
            CLASS Foo
            METHOD foo
            AT INVOKE bar
            IF $idx == 0
            DO THROW new RuntimeException("byteman woz ere");
            ENDRULE
            

             

            What happens when a method m calls myFoo.foo(myIntArray, 0)? The rule is triggered and a RuntimeException is thrown out of the call to foo. So, either m handles it or it gets passed to m's caller and so on. The exception flow bypasses the catch clause which wraps teh array access and the call to bar. Why? well that relates to the second point.

             

            2) A THROW clause is only valid if the type of the thrown exception is compatible with the signature of the trigger method. In the example above the THROW clause throws a RuntimeException. Now any method can legitimately throw a RuntimeException so anny caller of Foo.foo could legitimately expect to see the behaviour introduced by the Byteman rule. Let's vary things slightly

             

            class Foo {
              . . .
              int foo(int[] array, int idx) throws CheckedArrayAccessException {
                int val = checkedArrayAccess(array, idx);
                bar(val);
              }
              . . .
            

             

             

            RULE throw an exception
            CLASS Foo
            METHOD foo
            AT INVOKE bar
            IF $idx == 0
            DO THROW new CheckedArrayAccessException("byteman woz ere", $array, $idx);
            ENDRULE
            
            

             

            So, the assumption here is that method checkedArrayAccess will throw a CheckedArrayAccessException if we get an unacceptable index. Our rule is going to force an exception even if the index is the perfectly acceptable value 0. Now, the method declaration specifies CheckedArrayAccessException as a legitimate type for foo to throw. So, the rule will type check ok and eexecuting the rule will not break the method's contract with its caller. If we change the rule as follows

            RULE throw an exception
            CLASS Foo
            METHOD foo
            AT INVOKE bar
            IF $idx == 0
            DO THROW new EmptyArrayException("byteman woz ere", $array);
            ENDRULE
            

             

            then the rule will fail with a type check error including the message message "ThrowExpression.typeCheck : exception type not declared by trigger method EmptyArrayException file . . ."

             

            So, why bypass the internal plumbing? Why not make the THROW expression throw the expression from the trigger point?

             

            Well, basically, it's not possible for the Byteman rule injection code to scope and type check the implications of inserting a throw operation at an arbitrary point inside a method body. Whereas, throwing the exception from the trigger method to its caller has a clean, clear semantics and can be made type-safe by checking the exception type against the method's exception signature.

            • 33. Re: Skipping code with Byteman?
              ron_sigal

              re: "Your initial rule was mistaken because you tried to use RETURN expression in an AT CALL rule to modify the value returned by the call which defines the trigger location. This is a misnomer because RETURN forces an immediate return form the trigger method. What you really wanted was to change the value returned by the called method. You don't have to modify the called method to do that."

               

              So, there seem to be two ways to fix the problem.

               

              1. My fix was to modify the returned value in the called method.

               

              2. Your fix is to modify, in the target method, the value returned by the called method.

               

              I didn't pay a lot of attention to the special variables like $!.  That's very good to know.  Thank you.

              • 34. Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Skipping code with Byteman?
                ron_sigal

                Re: "I''m not sure if you fully understood why what works does work"

                 

                I certainly didn't.  ;-)  Thank you for the explanation.

                 

                In this case as well, I've changed the rule to apply to the called method:

                   @BMRule(name="validatorContextResolver_getContext_unableToLoadValidationSupport",

                         targetClass="javax.validation.Validation$GenericBootstrapImpl",

                         targetMethod="configure",

                         action="THROW new RuntimeException(\"bm\");")

                and it works.

                 

                Note that when I used an interface:

                         targetClass="^javax.validation.bootstrap.GenericBootstrap",

                which is implemented by "javax.validation.Validation$GenericBootstrapImpl, nothing happened ...

                • 35. Re: Skipping code with Byteman?
                  ron_sigal

                  Re: "Hmm, not quite so dumb as you think :-)"

                   

                  Reminds me of one of my favorite lines in cinema.  In Bruce Beresford's 1976 move "Don's Party", a woman says, "My husband thinks his p*nis is too small".

                   

                  He stands up, aggrieved and defensive, and says, "It's not too small.  I just think it is".

                   

                  [Wouldn't let me save the reply when I spelled out p*nis.  Omg.]

                  • 36. Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Re: Skipping code with Byteman?
                    adinn

                    Hi Ron,

                    Note that when I used an interface:

                             targetClass="^javax.validation.bootstrap.GenericBootstrap",

                    which is implemented by "javax.validation.Validation$GenericBootstrapImpl, nothing happened ...

                    Hmm, there's an interesting mistake behind this which, at the very least, says that the BMUnit documentation is not up to scratch.

                     

                    You want a rule which applies to an interface and which injects not just through the interface into implemenations but also into subclasses of those implementing classes. So, your BMRule annotation needs to specify that as follows:

                    targetClass="javax.validation.bootstrap.GenericBootstrap",

                    isInterface=true,

                    isOverriding=true,

                    . . .

                     

                    In a script you would write

                    RULE my overriding interface rule

                    INTERFACE ^javax.validation.bootstrap.GenericBootstrap

                    . . .

                    Don't tell anyone but you could probably get away with writing your BMRule annotation like this:

                    targetClass="^javax.validation.bootstrap.GenericBootstrap",

                    isInterface=true

                    . . .

                    All BMUnit does is glue together the supplied text with the required rule keywords :-)

                    • 37. Re: Skipping code with Byteman?
                      ron_sigal

                      Re: "You want a rule which applies to an interface and which injects not just through the interface into implemenations but also into subclasses of those implementing classes. So, your BMRule annotation needs to specify that as follows:

                      targetClass="javax.validation.bootstrap.GenericBootstrap",

                      isInterface=true,

                      isOverriding=true,

                      . . .

                       

                      It works!!  Thank you.

                      • 38. Re: Skipping code with Byteman?
                        ron_sigal

                        There is no single correct answer, since I asked, and Andrew answered, so many questions.  Thank you, Andrew!!  You've earned a JBoss mug.

                         

                        mug.jpg

                         

                        One last question.  Andrew, did you see the license plate in my picture?  I put it up just for you.

                        • 39. Re: Skipping code with Byteman?
                          adinn

                          Hi Ron,


                          One last question.  Andrew, did you see the license plate in my picture?  I put it up just for you.


                          Hmm, I believe the right thing to say here is that I can neither confirm nor deny . . . ;-)

                           

                          n.b. as Thomas Pynchon once said: those who know, know!

                          1 of 1 people found this helpful
                          • 40. Re: Skipping code with Byteman?
                            ron_sigal

                            Re:  Hmm, I believe the right thing to say here is that I can neither confirm nor deny . . . ;-)

                             

                            Lolll.  That's a true fact.

                            1 2 3 Previous Next