3 Replies Latest reply on Jun 25, 2014 7:09 AM by abroszni

    RULE execution based on triggering method field

    abroszni

      Hi,

      I have a class that I test and I want to instrument with Byteman, in order to do some action depending on some field in my test

      so I have a test like

       

      @BMScript(value = "byteman/stop.btm")

      public class StopTest extends BMNGRunner {

       

        @Test

        public void testStop() throws InterruptedException {

           boolean stopped = false;

       

           MyInstrumentedClass myClass = new MyInstrumentedClass();

       

          for (int i = 0; i < 30; i++) {

            if (i == 5)

              stopped = true;

            if (i == 10)

              stopped = false;

       

            myClass.doOps(i);

          }

        }

      }

       

      with the script being :

       

      RULE stop method

      CLASS MyInstrumentedClass

      METHOD doOps

      AT ENTRY

      BIND stopped:boolean = stopped

      IF stopped

      DO Thread.sleep(10000)

      ENDRULE

       

       

      The rule seems ok, but unfortunately it doesn't get executed, do you have an idea abotu what I'm doing wrong? I looked in the doc but didn't find how to fix this.

      thanks!

       

      Aurelien

        • 1. Re: RULE execution based on triggering method field
          adinn

          Hi Aurelian,

           

          This is not working for several reasons. I think you have somehow acquired a rather confused idea of how Bytreman works. Rather than try to explain everything that is wrong here I will show you several ways to do whhat I think you want to do and recommend you try to look at some other examples of working Byteman rules and then go read the Programmer's Guide very carefully from start to finish. That ought to make things a bit clearer.

           

          Let's look at what your rule say. The CLASS and METHOD clause say that Byteman should inject some code into method MyInstrumentedClass.doOps(). Well, if that is the method you are injecting code into then it's no good showing me the code for method StopTest.testStop(). It is not very easy for me to explain how Byteman is going to rewrite a method if you don't provide the actual code for the method you have asked it to rewrite.


          It seems that what you want  Byteman to do is add a call to Thread.sleep at the start of the bytecode for method doOps(). However, it also looks like are trying to make your rule refer to local variable stopped which is defined in the completely different method testStop(). Now Byteman cannot change the bytecode for one method so that it refers to a local variable in another method. The Java virtual machine does not make it possible for methods to access local variables of their caller methods. So Byteman cannot make that possible either.


          If stopped was a static field defined by class testStop then the injected code could refer to it:

           

          @BMScript(value = "byteman/stop.btm")

          public class StopTest extends BMNGRunner {

            public static boolean stopped = false;

           

            @Test

            public void testStop() throws InterruptedException {

           

               MyInstrumentedClass myClass = new MyInstrumentedClass();

           

              for (int i = 0; i < 30; i++) {

                if (i == 5)

                  stopped = true;

                if (i == 10)

                  stopped = false;

           

                myClass.doOps(i);

              }

            }

          }


          With this definition for your class you could use this rule:


          RULE stop method

          CLASS MyInstrumentedClass

          METHOD doOps

          AT ENTRY

          BIND stopped:boolean = StopTest.stopped

          IF stopped

          DO Thread.sleep(10000)

          ENDRULE


          Or to make it more simple


          RULE stop method

          CLASS MyInstrumentedClass

          METHOD doOps

          AT ENTRY

          IF StopTest.stopped

          DO Thread.sleep(10000)

          ENDRULE


          n.b. it doesn't actually matter that the static field is public or private (if necessary,  Byteman will get round the privacy restriction by injecting bytecode which uses reflection to read the static field).


          Another way to do what you want is to inject code into your test code which saves the value of stopped and then inject code into the target method which uses that saved value. You can use the Byteman flag builtins to do this. Let's assume we are using the original code:


          @BMScript(value = "byteman/stop.btm")

          public class StopTest extends BMNGRunner {

           

            @Test

            public void testStop() throws InterruptedException {

             boolean stopped = false;

               MyInstrumentedClass myClass = new MyInstrumentedClass();

           

              for (int i = 0; i < 30; i++) {

                if (i == 5)

                  stopped = true;

                if (i == 10)

                  stopped = false;

           

                myClass.doOps(i);

              }

            }

          }


          You need two rules injected into the test method to record the value of local variable stopped


          RULE record stopped is set

          CLASS StopTest

          METHOD testStop

          AT CALL doOps

          IF $stopped

          DO flag($myClass)

          ENDRULE


          and

           

          RULE record stopped is clear

          CLASS StopTest

          METHOD testStop

          AT CALL doOps

          IF ! $stopped

          DO clear($myClass)

          ENDRULE


          The location says that these two rules get executed just before the call to doOps() in your test method testStop(). The condition in the first rule tests whether local variable stopped is true (n.b. $varName refers to the value of local variable varName in the method Byteman is injecting code into). Note that the injected bytecode uses the value of stopped at the injection point i.e. just at the point where the call is about to be made. The DO clause for the first rule calls builtin method flag(Object) to set a flag associated with the target object for the call the i.e. object referenced by local variable myClass. The second rule calls builtin clear(Object) to clear the same flag if local variable stopped is false. Both rules get fired just before the call but only one will have a valid condition. So, the flag associated with object myClass will be set or cleared depending upon the value of stopped.


          Now you can refer to the same flag setting in the rule which injects the sleep call


          RULE stop method

          CLASS MyInstrumentedClass

          METHOD doOps

          AT ENTRY

          IF flagged($this)

          DO Thread.sleep(10000)

          ENDRULE


          The condition calls builtin flagged(Object) to test whether the flag associated with the target object for the call to doOps() is set. Note that $this inside the call to doOps() is the same object as $myClass in the calling method so the same flag is tested inside the call as was set outside the call.


          You might like to try running these rules to see what happens and then I suggest you look for some other examples and go read more about Byteman in the Programmer's Guide.


          regards,



          Andrew Dinn


          • 2. Re: Re: RULE execution based on triggering method field
            abroszni

            Hi Andrew,

            thanks a lot for your detailed answer, I am sorry that I wasn't clear enough about my problem,

            Actually I had read the doc in length and had already found the solution you are proposing, using a static variable.

            But it gives me an exception.

             

            Let me explain briefly what I am trying to achieve, I am using an external API, so I don't have access at all to the code, I just want to be able to add a sleep() when calling this API under certain condition.

            This is why I use the boolean 'stopped'. In my test, when my conditions are fulfilled, the call to the API's method should be paused for a while with the sleep() injected in the API's method.

             

            I have opened a jira:

            https://issues.jboss.org/browse/BYTEMAN-265

             

            thanks,

            Aurelien

            • 3. Re: RULE execution based on triggering method field
              abroszni

              For future readers, here is the working solution and explanation from Andrew:

               

               

              Try changing your rule as follows and I believe it will work:

               

              RULE stop method

              CLASS MyInstrumentedClass

              METHOD doOps

              AT ENTRY BIND

              stopped:boolean = com.mycompany.app.StopTest.stopped

              IF stopped

              DO Thread.sleep(10000)

              ENDRULE

               

              Byteman allows you to omit a package qualifier when you mention a class so long as the unqualified class name is known by virtue of appearing in other rule code e.g. as the target class, as an argument type for the target method, as the result type of an expression in the rule body, etc. In this case the type StopTest is not referenced via any of the types mentioned in the rule's event specification (i.e. CLASS/METHOD/LOCATION clauses). Nor does it appear in the condition or action which follow the BIND clause. So, with your original rule Byteman has no way of knowing that there is a class called StopTest in package org.mycompany.app. If you provide the package qualified class name in the static field reference then it should typecheck the reference correctly.