1 2 3 Previous Next 40 Replies Latest reply on Oct 14, 2013 10:38 PM by Ron Sigal

    Skipping code with Byteman?

    Ron Sigal Master

      I want to post to the forum a conversation I had with Andrew, in the hope it might be useful to someone.

       

      ------------------------------------------------------------------------------------------------------------------------------------

       

      Hi Ron,

       

      On 22/09/13 01:59, Ron Sigal wrote:

       

      > I have a question about byteman.  I'm starting to work on

      > internationalizing Resteasy (the deal I made with Bill was that I would

      > do the boring stuff he didn't want to do  ), and I'm debating whether

      > to go to the trouble of testing each log statement in place.  If I do

      > that, I'm thinking that Byteman would be useful. Now, I've just started

      > looking at the first place I would want to use Byteman, and I don't see

      > how to do what I want to do.

       

      Hmm, this will probably require a little bit of Byteman trickery to make

      it work.

       

      > The code is

      >

       

                      try
                      {
                         Context context = new InitialContext();
                         validatorFactory = tmpValidatorFactory = ValidatorFactory.class.cast(context.lookup("java:comp/ValidatorFactory"));
                         LogMessages.LOGGER.supportingCDI(validatorFactory);
                      }
                      catch (NamingException e)
                      {
                         LogMessages.LOGGER.unableToSupportCDI();
                         HibernateValidatorConfiguration config = Validation.byProvider(HibernateValidator.class).configure();
                         validatorFactory = tmpValidatorFactory = config.buildValidatorFactory();
                      }

      >

      > Running out of the box and outside of AS, the call to context.lookup()

      > will throw the NamingException, and I'd like to skip over that call to

      > get to LogMessages.LOGGER.supportingCDI(validatorFactory).  That's what

      > I don't see how to do.  Is there a way to skip code?

       

      Well no, Byteman doesn't give you that directly. For the most part it

      works by injecting *extra* code into the existing code. However, you can

      change control flow or spoof return values using a THROW or RETURN

      action and in this case the latter option ought to allow you to engineer

      the situation you want.

       

      You need to short circuit the context lookup so it returns a dummy value

      before it tries to check its argument. IT can return any object but

      since the caller is expecting it to return a class let's just use the

      InitialContext object's class.

       

      RULE context lookup force early return

      CLASS InitialContext

      METHOD lookup

      AT ENTRY

      IF callerEquals("xxx", "yyy", true)

      DO RETURN $0.getClass();

      ENDRULE

       

      $0 refers to the InitialContext instance which is executing the lookup

      method. The IF condition uses Byteman built-in  'callerEquals' to

      identify the caller of InitialContext.lookup (with this argument pattern

      by method name and non-package qualified class name but other formats

      allow you to be more or less specific). The builtin call needs to have

      the name and class of the calling method inserted in place of "xxx" and

      "yyy" (i.e. the one you have excerpted this snippet of code from).

       

      The rule condition makes sure that the call is only bypassed when the

      lookup method is called from this point in the codebase. It's probably

      overkill for your test since this is likely the only call to

      InitialContext.lookup but in general you need some sort of guard liek

      this when trying to short circuit code during testing.

       

      The returned value (InitialContext.class) will now be passed to the cast

      call and it will return null which will then be passed to the Logger call.

       

      > I just think it would be awesome to not have to change the code to

      > support testing.

       

      Yes, that's the primary goal of Byteman -- to test as much 'real' code

      as possible. Nothing worse than finding out that your tests pass but the

      real code still fails because it's not the same as the test.

        • 1. Re: Skipping code with Byteman?
          Ron Sigal Master

          My first problem.

           

          I've created a simple rule as a first step to using Andrew's suggestion:

           

             RULE Context.lookup()

             CLASS ^javax.naming.Context

             METHOD lookup

             AT ENTRY

             IF TRUE

             DO

             traceln("bm: returning null ValidatorFactory");

             ENDRULE

           

          and I'm getting

           

             stdOut: ValidatorContextResolver.getValidatorFactory() caught org.jboss.byteman.rule.exception.EarlyReturnException: test

             errorOut: Exception in thread "main" java.lang.NoClassDefFoundError: org/jboss/byteman/rule/exception/EarlyReturnException

             errorOut:     at javax.naming.InitialContext.lookup(InitialContext.java)

             errorOut:     at org.jboss.resteasy.plugins.validation.ValidatorContextResolver.getValidatorFactory(ValidatorContextResolver.java:61)

             errorOut:     at org.jboss.resteasy.plugins.validation.ValidatorContextResolver.getContext(ValidatorContextResolver.java:103)

             errorOut:     at org.jboss.resteasy.plugins.validation.ValidatorContextResolver.getContext(ValidatorContextResolver.java:31)

             errorOut:     at org.jboss.resteasy.core.ResourceMethodInvoker.<init>(ResourceMethodInvoker.java:115)

             errorOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.processMethod(ResourceMethodRegistry.java:280)

             errorOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.register(ResourceMethodRegistry.java:251)

             errorOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:221)

             errorOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:193)

             errorOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:179)

             errorOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:156)

             errorOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addPerRequestResource(ResourceMethodRegistry.java:75)

             errorOut:     at org.jboss.resteasy.test.nextgen.validation.TestServerI18N.before(TestServerI18N.java:74)

             errorOut:     at org.jboss.resteasy.test.nextgen.validation.TestServerI18N.main(TestServerI18N.java:51)

           

          Now, the situation is a bit complicated.  I have a junit test which is running a servlet engine in a java.lang.Process, which I did to make it easy to capture stdout and stderr.  I know that byteman.jar is on the classpath in this Process, because I added

           

             try

             {

                throw new EarlyReturnException("test");

             }

             catch (Exception e)

             {

                System.out.println("ValidatorContextResolver.getValidatorFactory() caught " + e);

             }

           

          right before the intercepted call to Context.lookup(), and I see

           

          stdOut: ValidatorContextResolver.getValidatorFactory() caught org.jboss.byteman.rule.exception.EarlyReturnException: test

           

          Any suggestions?

           

          Thanks,

          Ron

          • 2. Re: Skipping code with Byteman?
            Andrew Dinn Master

            Hmm, that's interesting!

             

            Are you running this with BMUnit? under maven?

             

            Weirdly, it looks like the test which failed is not catching an EarlyReturnException and letting it pass out of method InitialContext.lookup().

             

            Does your test code include a reference to any of the Byteman classes which are in the Byteman core jar? (i.e. in byteman.jar, not  byteman-bmunit.jar etc). In particular, does your code reference EarlyReturnException?

             

            I ask because this looks like it might be a class loader issue. The BMUnit code looks for the byteman jar in the System classpath and bumps it up into the bootstrap classpath before auto-loading the Byteman agent. This is to ensure that any code Byteman injects into bootstrap classes can refer to EarlyReturnException, etc. Unfortunately, it also means that should your test (or indeed app) code refer to Byteman core classes and resolve those references before BMUnit runs then some Byteman core classes might exist in both the system loader and the bootstrap loader. If that happens then the injected handler code will catch instances of the sys loader's EarlyReturnException but will not catch instances of the boot loader's EarlyReturnException.

             

            That's the only way I can account for an EarlyReturnException exception flowing through the injected catch handler and out of the trigger method. Of course, I guess it is possible that the injection code has an error and the protection region against the EarlyReturnException is not set up properly. One way we can check that is to dump the transformed bytecode for he trigger method.

             

            Can you set the following system property in your test JVM (either in the maven config or using -D on the command line)

             

              org.jboss.byteman.dump.generated.classes

             

            and then rerun the test. The property does not need a value (use =true if you want one but =false will also enabel dumping). When the test runs you should see a message about dumping the transformed bytecode for class IniitalContext and you should find a file IniitalContext.class in the relevant package dir tree below the working directory of your test run. If you decompile it using javap -c -verbose you should be able to see teh code Byteman has injected. If you post the result I'll be happy to check whether it has been correctly transformed.

            • 3. Re: Re: Skipping code with Byteman?
              Ron Sigal Master

              Hey Andrew,

               

              I'm using maven to run the junit test, and the junit test calls Runtime.getRuntime().exec("java -cp ... -javaagent:target/lib/byteman.jar=script:src/test/resources/ -Dorg.jboss.byteman.dump.generated.classes=true ...") to run the server.

               

              Before I added

               

                try

                {

                    throw new EarlyReturnException("test");

                }

                catch (Exception e)

                {

                    System.out.println("ValidatorContextResolver.getValidatorFactory() caught " + e);

                }

               

              to the server code, there was no reference to byteman in the test code nor in the the vanilla Resteasy code.

               

              Note that when I run the test, I get

               

                stdOut: org.jboss.byteman.agent.Transformer : Saving transformed bytes to ./javax/naming/InitialContext.class

               

              which means that the transformation is done in the Runtime (I'm capturing standout from the Runtime and printing it).  Could that be relevant?

               

              I'm attaching the decompiled InitialContext class.

               

              Thanks, Andrew.

               

              -Ron

              • 4. Re: Skipping code with Byteman?
                Andrew Dinn Master

                Hmm, the transformed code is correct. Here's the start of the lookup method:

                 

                  public java.lang.Object lookup(java.lang.String) throws javax.naming.NamingException;
                    flags: ACC_PUBLIC
                    Code:
                      stack=3, locals=2, args_size=2
                        0: ldc          #107                // String Context.lookup() 2_2
                        2: aload_0     
                        3: aconst_null 
                        4: invokestatic  #113                // Method org/jboss/byteman/rule/Rule.execute:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V
                        7: aload_0     
                        8: aload_1     
                        9: invokevirtual #121                // Method getURLOrDefaultInitCtx:(Ljava/lang/String;)Ljavax/naming/Context;

                           . . .

                 

                The entries in the exception table protect the injected code at bytecodes 0 to 7 (inclusive start, exclusive end):

                 

                      Exception table:
                         from    to  target type
                             0     7    19   Class org/jboss/byteman/rule/exception/EarlyReturnException
                             0     7    23   Class org/jboss/byteman/rule/exception/ThrowException
                             0     7    27   Class org/jboss/byteman/rule/exception/ExecuteException

                 

                The 3 possible Byteman exceptions are vectored out to after the end of the original method code starting at 19:

                 

                        18: areturn      
                        19: invokevirtual #125                // Method org/jboss/byteman/rule/exception/EarlyReturnException.getReturnValue:()Ljava/lang/Object;
                        22: areturn      
                        23: invokevirtual #129                // Method org/jboss/byteman/rule/exception/ThrowException.getThrowable:()Ljava/lang/Throwable;
                        26: athrow       
                        27: athrow       

                 

                So, if an EarlyReturnException was created inside the Byteman rule engine and thrown out of the call to Rule.execute then it I think it ought to be vectored to 19 where the returned value gets pulled out of the exception (call to getReturnValue at 19) and returned (areturn at 22). If your test code sees an EarlyReturnException then I think that could only happen if the thrown type does not match the type named in the exception table.

                 

                So, this suggests to me that something in your test setup is causing the EarlyReturnException to be visible in multiple classloader scopes. Could you post your pom? It may be something to do with how it is configured.

                • 5. Re: Re: Skipping code with Byteman?
                  Ron Sigal Master

                  Here's the pom and the parent pom.  Thanks, Andrew.

                  • 6. Re: Re: Skipping code with Byteman?
                    Ron Sigal Master

                    Now I have what is probably a dumb question.  I can't see anything in the Byteman Programmer's Guide about setting variables in rule actions.  However, I find that this works

                     

                       action="traceln(\"bm local: i = \" + $i); $i = 717; traceln(\"i = \" + $i);")

                     

                    where i is an int, but this

                     

                       action="traceln(\"bm local: line = \" + $line); $line = \"abc\"; traceln(\"line = \" + $line);")

                     

                    results in

                     

                    java.lang.VerifyError: (class: org/jboss/resteasy/api/validation/ResteasyViolationException, method: convertFromString signature: (Ljava/lang/String;)V) Incompatible object argument for function call

                        at org.jboss.resteasy.plugins.validation.GeneralValidatorImpl.validateReturnValue(GeneralValidatorImpl.java:179)

                        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:177)

                        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:280)

                        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:234)

                        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:221)

                        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:356)

                        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179)

                        at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220)

                        at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)

                        at org.jboss.resteasy.plugins.server.tjws.TJWSServletDispatcher.service(TJWSServletDispatcher.java:40)

                        at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)

                        at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)

                        at Acme.Serve.Serve$ServeConnection.runServlet(Serve.java:2326)

                        at Acme.Serve.Serve$ServeConnection.parseRequest(Serve.java:2280)

                        at Acme.Serve.Serve$ServeConnection.run(Serve.java:2052)

                        at Acme.Utils$ThreadPool$PooledThread.run(Utils.java:1402)

                        at java.lang.Thread.run(Thread.java:722)

                    ...

                    Exception in thread "Thread-0" java.lang.InternalError

                        at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)

                        at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)

                        at org.jboss.byteman.agent.Retransformer.removeScripts(Retransformer.java:292)

                        at org.jboss.byteman.agent.TransformListener.handleScripts(TransformListener.java:335)

                        at org.jboss.byteman.agent.TransformListener.deleteScripts(TransformListener.java:290)

                        at org.jboss.byteman.agent.TransformListener.handleConnection(TransformListener.java:215)

                        at org.jboss.byteman.agent.TransformListener.run(TransformListener.java:146)

                     

                    where line is a String.

                     

                    I think I'm on the wrong track somehow.

                    • 7. Re: Re: Re: Skipping code with Byteman?
                      Andrew Dinn Master

                      Hi Ron,

                       

                      I see two unexpected things in your pom.

                       

                      Firstly, you have commented out the test scope for the byteman core jar in your top-level dependencies declaration.

                       

                      <dependencies>
                        . . .
                        <dependency>
                          <groupId>org.jboss.byteman</groupId>
                          <artifactId>byteman</artifactId>
                          <!--scope>test</scope-->
                          <version>${byteman.version}</version>
                        </dependency>
                        <dependency>
                          <groupId>org.jboss.byteman</groupId>
                          <artifactId>byteman-submit</artifactId>
                           <scope>test</scope>
                           <version>${byteman.version}</version>
                        </dependency>
                        . . .
                      

                       

                      Secondly, the maven dependency plugin is configured to install the byteman core jar locally in target/lib

                       

                        <build>
                          <plugins>
                              <plugin>
                                  <artifactId>maven-dependency-plugin</artifactId>
                                  <executions>
                                      <execution>
                                          <id>unpack</id>
                                          <phase>process-test-classes</phase>
                                          <goals>
                                              <goal>copy</goal>
                                          </goals>
                                          <configuration>
                                              <artifactItems>
                                                  <artifactItem>
                                                      <groupId>org.jboss.byteman</groupId>
                                                      <artifactId>byteman</artifactId>
                                                      <version>${byteman.version}</version>
                                                      <overWrite>false</overWrite>
                                                      <outputDirectory>target/lib</outputDirectory>
                                                      <destFileName>byteman.jar</destFileName>
                                                  </artifactItem>
                                              </artifactItems>
                                          </configuration>
                                      </execution>
                                  </executions>
                              </plugin>
                        . . .
                      

                       

                       

                      You should only need to declare the byteman jars as test dependencies in the main dependency list and all of them ought to be declared in test scope, including the byteman core jar.

                       

                      You should not have to download a local copy of the byteman jar using the maven dependency plugin.

                       

                      Can you explain why this plugin configuration is included in the pom?

                      • 8. Re: Re: Re: Skipping code with Byteman?
                        Andrew Dinn Master

                        Now I have what is probably a dumb question.

                         

                        Questions are never dumb so long as you learn from the answer.

                         

                         

                        I can't see anything in the Byteman Programmer's Guide about setting variables in rule actions.


                        It is described in subsection "Rule Expressions" of main section "The Byteman Rule Language".

                         

                        However, I find that this works

                         

                           action="traceln(\"bm local: i = \" + $i); $i = 717; traceln(\"i = \" + $i);")

                         

                        where i is an int, but this

                         

                           action="traceln(\"bm local: line = \" + $line); $line = \"abc\"; traceln(\"line = \" + $line);")

                         

                        results in

                         

                        java.lang.VerifyError: (class: org/jboss/resteasy/api/validation/ResteasyViolationException, method: convertFromString signature: (Ljava/lang/String;)V) Incompatible object argument for function call

                          . . .

                         

                        I think I probably need to see the source code of the target method as well as the full rule text to understand this problem. However, the VerifyError suggests that the something is going wrong at the injection point. Clearly, the rule has been injected into the code otherwise there would be no VerifyError. Am I right to assume that local variable line is a String instance?

                         

                        There may be an error in the byetcode which passes the String into the rule execution engine or (more likely) in the bytecode which retrieves the updated value and assigns it to the trigger method local variable on return from the execution engine. The Java7 verifier is way fussier than the old Java6 verifier and I may still have some issues to iron out in the way the injected code is generated.

                         

                        One thing which would help is to obtain a dump of the transformed trigger method to see what the byetcode looks like. So, if you could run again with -Dorg.jboss.byteman.dump.generated.classes and post a dump of the bytecode for class ResteasyViolationException I might be able to work out why the Java runtime thinks the transformed bytecode is wrong.

                        • 9. Re: Skipping code with Byteman?
                          Ron Sigal Master

                          Hey Andrew,

                           

                          1. I have no recollection of commenting out the test scope, and I have no idea why I did it.  So, I'll undo it.

                           

                          2. I did have a reason for copying byteman.jar to the target directory.  In the junit tests, I'm running the server like so:

                           

                             public void before(String language, String country, String variant, String script) throws Exception

                             {

                                String command = "java -cp \".:" +  System.getProperty("java.class.path") + "\" "

                                      + (script == null ? "" : "-javaagent:target/lib/byteman.jar" + "=script:src/test/resources/" + script + " ")

                          //            + " -Dorg.jboss.byteman.dump.generated.classes=true "

                                      + TestServerI18N.class.getName() + " "

                                      + language + " "

                                      + country + " "

                                      + variant;

                                System.out.println("command: " + command);

                                writer = new CharArrayWriter(4096);

                                serverExecutor = new TestExecutor(command, writer);

                                System.out.println("starting server");

                                serverExecutor.start();

                                System.out.println("waiting on server");

                                serverExecutor.waitUntilReady();

                                System.out.println("server is ready");

                             }

                           

                          which I do so that I can capture and scan the logging output.  There might be a more elegant way of doing it, but it works.

                           

                          -Ron

                          • 10. Re: Skipping code with Byteman?
                            Ron Sigal Master

                            Re: "Questions are never dumb so long as you learn from the answer."

                             

                            No dumb questions, only dumb questioners.  ;-)

                             

                            Re: "It is described in subsection "Rule Expressions" of main section "The Byteman Rule Language"."

                             

                            So, do I need to bind a variable in the rule first?

                             

                            Re: "I think I probably need to see the source code of the target method as well as the full rule text to understand this problem."

                             

                            I've starting using @BMRule for some tests, and the rule is

                             

                            @BMRule(name="ResteasyViolationException.convertFromString_badEnum",

                                   targetClass = "ResteasyViolationException",

                                   targetMethod = "convertFromString",

                                   targetLocation = "AFTER WRITE $line",

                                   action="traceln(\"bm local: line = \" + $line); $line=\"abc\";traceln(\"line = \" + $line);")

                             

                            and the beginning of ResteasyViolationException.convertFromString() is

                             

                              protected void convertFromString(String stringRep)

                               {

                                  System.out.println("entering convertFromString()");

                                  int i = 17;

                                  InputStream is = new ByteArrayInputStream(stringRep.getBytes());

                                  BufferedReader br = new BufferedReader(new InputStreamReader(is));

                                  String line;

                                  try

                                  {

                                     int index = 0;

                                     line = br.readLine();

                                     System.out.println("line: " + line);

                                     while (line != null )

                             

                            But now I'm just getting

                             

                            Exception in thread "Thread-0" java.lang.InternalError

                                at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)

                                at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)

                                at org.jboss.byteman.agent.Retransformer.removeScripts(Retransformer.java:292)

                                at org.jboss.byteman.agent.TransformListener.handleScripts(TransformListener.java:335)

                                at org.jboss.byteman.agent.TransformListener.deleteScripts(TransformListener.java:290)

                                at org.jboss.byteman.agent.TransformListener.handleConnection(TransformListener.java:215)

                                at org.jboss.byteman.agent.TransformListener.run(TransformListener.java:146)

                             

                            I've changed things so many times that I'm not sure if I've recreated the rule I was using before, though I think I did.

                             

                            re: "One thing which would help is to obtain a dump of the transformed trigger method to see what the byetcode looks like."

                             

                            I'm having trouble getting the transformed class.  Before I was putting "-Dorg.jboss.byteman.dump.generated.classes=true" in the command line (see Comment #9), but for the tests that use @BMLocal I've got

                             

                            <plugin>

                              <groupId>org.apache.maven.plugins</groupId>

                              <artifactId>maven-surefire-plugin</artifactId>

                              <version>2.4.3</version>

                              <configuration>

                              <systemProperties>

                                <property>

                                  <name>org.jboss.byteman.dump.generated.classes</name>

                                  <value>true</value>

                                </property>

                              </systemProperties>

                              ...

                             

                            but it doesn't seem to be working.

                            • 11. Re: Re: Skipping code with Byteman?
                              Andrew Dinn Master

                              hi Ron,

                               

                              I think I need to review your test setup here because it does not look like the normal BMUnit (or JUnit) setup. That may account for why you are hitting problems.

                               

                              You say . . .

                              In the junit tests, I'm running the server like so:

                               

                                 public void before(String language, String country, String variant, String script) throws Exception

                                 {

                                    String command = "java -cp \".:" +  System.getProperty("java.class.path") + "\" "

                                          + (script == null ? "" : "-javaagent:target/lib/byteman.jar" + "=script:src/test/resources/" + script + " ")

                              //            + " -Dorg.jboss.byteman.dump.generated.classes=true "

                                          + TestServerI18N.class.getName() + " "

                                          + language + " "

                                          + country + " "

                                          + variant;

                                    System.out.println("command: " + command);

                                    writer = new CharArrayWriter(4096);

                                    serverExecutor = new TestExecutor(command, writer);

                                    System.out.println("starting server");

                                    serverExecutor.start();

                                    System.out.println("waiting on server");

                                    serverExecutor.waitUntilReady();

                                    System.out.println("server is ready");

                                 }

                               

                              which I do so that I can capture and scan the logging output. . . .

                               

                              and also

                               

                              I've starting using @BMRule for some tests. . .

                               

                              So which of the following assumptions are correct?

                               

                              • Your test setup has two JVMs, JVM A which runs the JUnit (including BMUnit) tests and JVM B which is running a test server.
                              • You are installing a Byteman agent into JVM B from the command line and passing it a script which modifies server code
                              • Some of your tests running in JVM A use BMRule annotations to load rules which modify server code in JVM B
                              • You do not want to load rules into JVM A to modify test code.

                               

                              The reason for checking all this is that the above setup is not the normal model for how to use BMUnit (although you can still use BMUnit like this if you configure things right).

                               

                              Normally:

                               

                              • your test code and the app code it tests both run in a single JVM, JVM A
                              • BMUnit automagically loads the Byteman agent into JVM A when it finds the first BMUnit tests
                              • BMUnit ensures the core (agent) jar is installed in the bootstrap classpath of JVM A
                              • BMUNit ensures the agent in JVM A is listening on a specific socket for rule load/unload requests
                              • BMUNit automagically talks to JVM A via that socket when it loads and unloads the rules identified in BMRule or BMScript annotations

                               

                              The important point here is that BMUnit selects certain options for how the agent is loaded. If you want to run the agent in a 3rd-party JVM then you will probably need to add more options to the -javaagent command line argument. You may also need to specify some BMUnit-specific system properties in your pom to ensure that BMUnit does nto try to automagically load the agent into its own JVM and that it talks to the server JVM. This  may well account for the problems you are seeing. So, if you could either confirm or reject the asumptions above that will help pin down what you need to do to configure the setup you want.

                               

                              Regarding the local variable issue

                               

                              So, do I need to bind a variable in the rule first?

                               

                              There are two different things at play here. The BIND clause introduces a rule variable i.e. it introduces a simple unadorned rule-local identifier which can be used to reference a value whose lifetime is the a specific triggering of the rule. It is just a convenience to allow you to hang on to a value computed when the rule is run and reference or update it it in the condition or action. e.g.

                               

                              RULE foo
                              CLASS Foo
                              METHOD foo
                              AT ENTRY
                              BIND myCount = $0.count;
                              IF myCount > 0
                              DO traceln("doubling count " + myCount);
                                 myCount = 2 * myCount;
                                 $0.count = myCount;
                              ENDRULE
                              

                               

                              A local or parameter variable is different to a rule variable. These terms refer to a variable defined in the trigger method for the rule and they are identified using the $ prefix. Parameter varoables may be referred to by number or by name. So, for example, $0 above is an indexed parameter variable reference and it refers to the target instance for method Foo.foo. You can also refer to this value by name using $this. Similarly, arguments to the call to foo which triggered the rule can be referred to using index notation as $1, $2 etc. However, if you compile with -g then you can also refer to parameters or method local variables by name e.g. given this definition for Foo.foo

                               

                              class Foo {
                                . . .
                                void foo(String name, String[] details) {
                                  for (int i = 0; i < details.length; i++) {
                                    this.appendDetails(name, details[i])
                                }
                              }
                              

                               

                              we can read and update the local variables name, details and i as follows

                               

                              RULE foo
                              CLASS Foo
                              METHOD foo
                              AFTER CALL appendDetails
                              BIND cutoff = $details.length / 2;
                              IF $name.equals("Andrew") && $i == cutoff
                              DO $i = $i + cutoff + 1
                              ENDRULE
                              

                               

                              So, you don't need to declare name, details or i. Byteman checks that the target method Foo.foo contains parameters or local variables with those names (and ensures their types are compatible with the code in the rule). It sets up bindings for $i, $name etc when the rule starts executing and after the rule finishes it ensures that updates to these bindings are propagated back into the trigger method. So, for example, the rule above will skip half of the iterations when len is greater than 2.

                              • 12. Re: Skipping code with Byteman?
                                Ron Sigal Master

                                Hi Andrew,

                                 

                                I'm sorry for creating so much confusion.  In fact, I have tests running (or not running) in two different situations.

                                 

                                1. My first set of tests, in which I'm checking the output of I18N log messages on the server, uses two JVMs, JVM A which runs the JUnit (but NOT including BMUnit annotations) tests and JVM B which is running a test server.  In this case, I am installing a Byteman agent into JVM B from the command line (passed to a java.lang.Runtime) and passing it a script which modifies server code.  The problem with EarlyReturnException occurred in this setup.

                                 

                                2. When I started testing some I18N exceptions, I realized that I could make some calls directly without even starting a server.  In this case, I'm running JUnit and BMUnit in a single JVM.  The problem with VerifyError occurred in this setup.

                                 

                                Thanks for the explanation about rule variables and local variables.  It confirms what I thought, but it's much clearer now.

                                 

                                -Ron

                                • 13. Re: Re: Skipping code with Byteman?
                                  Ron Sigal Master

                                  Well, I've made some progress.  I realized that I was able to inject Byteman code into Resteasy classes, and I was getting the EarlyReturnException from javax.naming.InitialContext.  When I told Byteman to modify JVM classes, the problem went away:

                                   

                                    String command = "java -cp \".:" +  System.getProperty("java.class.path") + "\" "
                                              + (script == null ? "" : "-javaagent:target/lib/byteman.jar" + "=script:src/test/resources/" + script + ",boot:target/lib/byteman.jar")
                                              + " -Dorg.jboss.byteman.dump.generated.classes=true "
                                               + "-Dorg.jboss.byteman.transform.all "
                                              + TestServerI18N.class.getName() + " "
                                              + language + " "
                                              + country + " "
                                              + variant;

                                   

                                  Now I'm on to my next problem.  I can return null from InitialContext.lookup():

                                   

                                  RULE Context.lookup()

                                  CLASS ^javax.naming.InitialContext

                                  METHOD lookup

                                  AT ENTRY

                                  IF TRUE

                                  DO

                                  traceln("bm: entering Context.lookup()");

                                  return null;

                                  ENDRULE

                                   

                                  but I really want to return (or set after the return) an actual ValidatorFactory.  When I add the rule

                                   

                                  RULE after write validatorFactory 1

                                  CLASS ValidatorContextResolver

                                  METHOD getValidatorFactory

                                  AFTER WRITE validatorFactory 1

                                  BIND

                                  config:HibernateValidatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();

                                  tmp:ValidatorFactory = config.buildValidatorFactory();

                                  IF TRUE

                                  DO

                                  traceln("bm: wrote validatorFactory: no exception");

                                  #validatorFactory = tmp

                                  traceln("bm: set default ValidatorFactory");

                                  ENDRULE

                                   

                                  where the two bindings are taken from

                                   

                                                HibernateValidatorConfiguration config = Validation.byProvider(HibernateValidator.class).configure();
                                                validatorFactory = tmpValidatorFactory = config.buildValidatorFactory();

                                   

                                  in ValidatorContextResolver, I get

                                   

                                  stdOut: bm: calling Context.lookup()

                                  stdOut: bm: entering Context.lookup()

                                  stdOut: bm: called Context.lookup()

                                  stdOut: Rule.ensureTypeCheckedCompiled : error type checking rule after write validatorFactory 1

                                  stdOut: org.jboss.byteman.rule.exception.TypeException: FieldExpression.typeCheck : invalid path Validation to static method byProvider file src/test/resources/validation.btm line 32

                                  stdOut:     at org.jboss.byteman.rule.expression.MethodExpression.typeCheck(MethodExpression.java:121)

                                  stdOut:     at org.jboss.byteman.rule.expression.MethodExpression.typeCheck(MethodExpression.java:168)

                                  stdOut:     at org.jboss.byteman.rule.binding.Binding.typeCheck(Binding.java:140)

                                  stdOut:     at org.jboss.byteman.rule.Event.typeCheck(Event.java:114)

                                  stdOut:     at org.jboss.byteman.rule.Event.typeCheck(Event.java:106)

                                  stdOut:     at org.jboss.byteman.rule.Rule.typeCheck(Rule.java:521)

                                  stdOut:     at org.jboss.byteman.rule.Rule.ensureTypeCheckedCompiled(Rule.java:449)

                                  stdOut:     at org.jboss.byteman.rule.Rule.execute(Rule.java:672)

                                  stdOut:     at org.jboss.byteman.rule.Rule.execute(Rule.java:653)

                                  stdOut:     at org.jboss.resteasy.plugins.validation.ValidatorContextResolver.getValidatorFactory(ValidatorContextResolver.java:63)

                                  stdOut:     at org.jboss.resteasy.plugins.validation.ValidatorContextResolver.getContext(ValidatorContextResolver.java:105)

                                  stdOut:     at org.jboss.resteasy.plugins.validation.ValidatorContextResolver.getContext(ValidatorContextResolver.java:32)

                                  stdOut:     at org.jboss.resteasy.core.ResourceMethodInvoker.<init>(ResourceMethodInvoker.java:115)

                                  stdOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.processMethod(ResourceMethodRegistry.java:280)

                                  stdOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.register(ResourceMethodRegistry.java:251)

                                  stdOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:221)

                                  stdOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:193)

                                  stdOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:179)

                                  stdOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:156)

                                  stdOut:     at org.jboss.resteasy.core.ResourceMethodRegistry.addPerRequestResource(ResourceMethodRegistry.java:75)

                                  stdOut:     at org.jboss.resteasy.test.nextgen.validation.TestServerI18N.before(TestServerI18N.java:74)

                                  stdOut:     at org.jboss.resteasy.test.nextgen.validation.TestServerI18N.main(TestServerI18N.java:51)

                                   

                                  where line 32 is

                                   

                                    config:HibernateValidatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();

                                   

                                  Curiously, when I uncomment

                                   

                                    #validatorFactory = tmp

                                   

                                  the exception goes away but the rule doesn't seem to execute.  I.e., I don't get the trace output "bm: set default ValidatorFactory".

                                  • 14. Re: Re: Re: Skipping code with Byteman?
                                    Andrew Dinn Master

                                    Hi Ron,

                                     

                                    Thanks for clarifying the setup.

                                    When I told Byteman to modify JVM classes, the problem went away:

                                    . . .

                                    Ok, that's good. I was about to suggest this once you had clarified what was happening. When BMUnit automagically loads the agent it supplies the boot: option and sets property org.jboss.byteman.transform.all. When you do the agent load on the Java command line you need to do the former if you want to avoid seeing Byteman classes in different scopes.

                                     

                                    Now I'm on to my next problem.  I can return null from InitialContext.lookup():

                                    . . .

                                    but I really want to return (or set after the return) an actual ValidatorFactory.

                                    . . .

                                     

                                    RULE after write validatorFactory 1
                                    CLASS ValidatorContextResolver
                                    METHOD getValidatorFactory
                                    AFTER WRITE validatorFactory 1
                                    BIND
                                    config:HibernateValidatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
                                    tmp:ValidatorFactory = config.buildValidatorFactory();
                                    IF TRUE
                                    DO
                                    traceln("bm: wrote validatorFactory: no exception");
                                    #validatorFactory = tmp
                                    traceln("bm: set default ValidatorFactory");
                                    ENDRULE
                                    

                                     

                                    This looks to me like it is a naming issue. The exception shows that the problem occurs when Byteman tries to type check the method expression Validation.byProvider(). The error message 'invalid path' means that the path expression preceding the call to byProvider() cannot be typed. n.b. by path expression Byteman means something of the form x.y.z. preceding the methodname() part -- a path may be a variable or field name possibly with a trailing field reference chain or alternatively it may be a static field name possibly with a  package and/or type prefix and possibly with a trailing field reference chain). In this case the path is meant to be resolved as a Type name locating a static method, Validator. Now type names are resolved relative to the classloader of the class which owns the target method, i.e. ValidatorContextResolver, and I would guess that Validation is indeed in scope in that classloader. However, you have referred to it without mentioning the package name. Much as Byteman tries to infer the package for a type automatically it cannot conjure them up without a hint. IN this case there has been no prior mention of ValidatorContextResolver so it doesn't know where to look to find this type. So, if you specify the full package name for Validation I suspect you rule will then work.

                                     

                                    Note that you probably won't have to specify the package for HibernateValidatorConfiguration. Byteman will check the return type of the method and use it to validate the declared type name provided on the left hand side of the binding. You don't have to declare a type for config if the return type is HibernateValidatorConfiguration. The declaration is only there to allow Byteman to check your intentions. Of course, if HibernateValidatorConfiguration is a supertype of the return type then Byteman may not be happy unless you provide the package qualified name.

                                     

                                    Just while we are here I'll mention that you can also get away with using a subtype of the return type when declaring a bound variable -- that's called a downcast and the only place where an assignment is allowed to employ a downcast is in BIND declarations (allowing it for all assignments is likely to let too many mistakes pass unnoticed). Downcasting is provided specifically so you can do things like pull an Object out of, say, an ArrayList<XXX> and bind it as an XXX (yes, Byteman doesn't know about generics -- indeed probably cannot really know about them).

                                     

                                    Finally, why do you only see an error when the assignment is present? That looks like a bug in the type checker. The declaration for config contains an undefined type whether or not the assignment is present. So, you ought to see an error in either case. I'll try to reproduce this and see if I can provide a fix.

                                    1 2 3 Previous Next