5 Replies Latest reply on Feb 18, 2016 8:36 AM by Andrew Dinn

    Accessing classes behind field interfaces

    Richard Achmatowicz Novice

      Hello Andrew

       

      If class A has an instance variable b of type class B, I can access the variable b in a rule triggered by a method in A like this:

       

      RULE test
      CLASS A
      METHOD <init>
      AT EXIT
      BIND
        b:B = $0.b ;
         ...
      ENDRULE
      

       

      Now if class A has an instance variable b of type interface B which is initialized with a class C which implements B, I would also like to have access to the implementation class C, possibly like this:

       

      RULE test
      CLASS A
      METHOD <init>
      AT EXIT
      BIND
       c:C = (C)$0.b;
      ...
      ENDRULE
      

       

      However, when I try this, I get the error: script.btm line 9 : invalid expression

      I would have thought that the rule has enough information available to process this cast and provide access to the class C.

       

      Questions:

      - is this sort of casting allowed in bind expressions?

      - if it is not allowed, what are the preferred methods for accessing classes behind interfaces in bind expressions?

       

      Richard

        • 1. Re: Accessing classes behind field interfaces
          Andrew Dinn Master

          Richard Achmatowicz wrote:

           

          Now if class A has an instance variable b of type interface B which is
          initialized with a class C which implements B, I would also like to have
          access to the implementation class C, possibly like this:
          1. RULE test 
          2. CLASS A 
          3. METHOD <init> 
          4. AT EXIT 
          5. BIND 
          6. c:C = (C)$0.b; 
          7. ... 
          8. ENDRULE 

          However, when I try this, I get the error: script.btm line 9 : invalid expression

          I would have thought that the rule has enough information available to process this cast and provide access to the class C.

           

          Questions:

          - is this sort of casting allowed in bind expressions?

          - if it is not allowed, what are the preferred methods for accessing classes behind interfaces in bind expressions?

           

          What you are trying to do is referred to in the programmer's guide as a downcast. This is only pemissible when attempting to initialise a variable at the point of declaration in the BIND clause (i.e. it is not permissible if you try subsequently to assign the BIND variable).

           

          If you have a variable c of type C an initialise it with an expression e

           

            c : C = e
          

           

          then Byteman accepts that initialization so long as the lexically derived type E of e is either a super of C or is an interface implemented (directly or indirecty) by C.

           

          You don't have to insert a cast -- indeed Byteman does not recognise a cast and will regard it as an invalid expression. When Byteman executes the BIND code to initialize c it includes a type check (impemented using the checkcast bytecode when the rule is compiled)  to ensure that the result from evaluating e is actually an instance of C. If the check fails it throws a ClassCastException.

           

          n.b. If you retry this without the cast expression note that you may well need to provide the fully package qualified name of C. If you just write

           

          f: FooBar = fooExpression;
          

           

          where the expression fooExpression has type Foo then Byteman may well be able to infer that fooExpression has type org.my.Foo but it cannot know that Foobar refers to subclass org.my.FooBar of Foo.

           

          Please provide me with more details if you cannot get your example to work.

           

          regards,

           

           

          Andrew Dinn

          • 2. Re: Accessing classes behind field interfaces
            Richard Achmatowicz Novice

            Hi Andrew

             

            Thank you for the clarification. I did write up a small scale example of the problem I was having (it's easier to debug toy examples than sift through the logs of the application server) and the example worked out exactly as you described. With the addition of the package name, that problem has now gone away.

             

            I may have a follow up instance: same situation, but this time an ExecutionException when I try to invoke on the class behind the interface. But before posting, i'll try to do a little more investigating myself.

            • 3. Re: Accessing classes behind field interfaces
              Richard Achmatowicz Novice

              Hi Andrew

              As I said in a previous posting, I made up a toy example of the scenario I was having problems with: accessing the class behind an interface field. I thought the problem went away with making use of the package name, but when I ran the test program again today, it failed with the error I used to see regularly:

               

              Rule.execute called for nested class example_0
              Rule.ensureTypeCheckedCompiled : error type checking rule nested class example
              org.jboss.byteman.rule.exception.TypeException: FieldExpresssion.typeCheck : invalid field reference B .d file /home/nrla/java/byteman/accessNestedInstance/script.btm line 13
                at org.jboss.byteman.rule.expression.FieldExpression.typeCheckAny(FieldExpression.java:203)
                at org.jboss.byteman.rule.expression.FieldExpression.typeCheck(FieldExpression.java:110)
                at org.jboss.byteman.rule.binding.Binding.typeCheck(Binding.java:155)
                at org.jboss.byteman.rule.Event.typeCheck(Event.java:114)
                at org.jboss.byteman.rule.Event.typeCheck(Event.java:106)
                at org.jboss.byteman.rule.Rule.typeCheck(Rule.java:548)
                at org.jboss.byteman.rule.Rule.ensureTypeCheckedCompiled(Rule.java:487)
                at org.jboss.byteman.rule.Rule.execute(Rule.java:705)
                at org.jboss.byteman.rule.Rule.execute(Rule.java:686)
                at Client.displayNest(Client.java)
                at Client.main(Client.java:14)
              

               

              I am expecting to be able to get access to the field d of class C within the Byteman script by following the trail of field assignments starting from A.

              Is this a legitimate use case?

              • 4. Re: Accessing classes behind field interfaces
                Andrew Dinn Master

                Hi Richard,

                 

                The error notified by the type checker occurs when it tries  to type check the assignment of BIND variable d at line 13.This error is actually valid but the reason you cannot see that is because a prior error is not detected and notified.

                 

                The prior error arises because of a problem in your example which Byteman is not using to derail the type check process. You have declared c1 and c2 with type org.byteman.C

                 

                . . .
                # access C instance by accessor via downcast
                  c1:org.byteman.C = b ;
                # access C instance by accessor via downcast
                  c1:org.byteman.C = b ;
                # access C instance by field 
                  c2:org.byteman.C = a.b;
                # access D instance by field
                  d:D = c1.d ;
                . . .
                

                 

                However, your class is located in the default package

                 

                public class C implements B {
                    String id ;
                    D d ;
                    . . .
                

                 

                 

                Byteman should throw an error when it detects that the type instance created to represent declared type org.byteman.C cannot be resolved. However it is conflating this UNKNOWN type instance with the special type instance Type.UNDEFINED and so ends up using the derived type of expressions b and a.b to type the variables c1 and c2.  Later when it tries to type check c1.d the attempt to lookup a field d in interface B fails.

                • 5. Re: Accessing classes behind field interfaces
                  Andrew Dinn Master

                  Hmm, fixing this has been quite 'interesting' :-)

                   

                  The problem arises in the following circumstance. You declare a binding with declaration type org.my.Foo but omit the package qualifier and then initalize it with an expression of type org.my.FooBar which may or may not be related to Foo

                   

                  BIND foo : Foo = returnMeFooBar()
                  . . .
                  

                   

                  The bug is that the type checker fails to throw an exception for Foo being undefined and instead uses type org.my.FooBar for foo.

                   

                  Initially I added a simple fix which said that when the declaration type Foo is undefined you type check the expression and then check whether the type of the expression has Foo as its name when the package is stripped away. So, in the case where returnMeFoo() has return type x.y.Foo this will work

                   

                  BIND foo : org.my.Foo = returnMeFoo()
                  . . .
                  

                   

                  bit in the case above where it returns org.my.FooBar it will fail.

                   

                  That's ok in most cases. But what about when org.my.FooBar is a subclass of Foo or implements Foo as an  interface? In that case it would be sensible to check the class + interface hierarchy above org.my.FooBar to see if any of them can be used to resolve Foo. It turns out that lots of the Byteman unit tests require using the derived type to validate a declared supertype. Typically I was declaring

                   

                  BIND test : Test = $0
                  . . .
                  

                   

                  where $0 is a test class like TestArithmetic that subclasses org.jboss.byteman.tests.Test.  So, I couldn't actually build a patched Byteman without either changing all these tests to use the full name

                   

                  BIND test : org.jboss.byteman.tests.Test = $0
                  . . .
                  

                   

                  or adding the extra functionality. Of course I fixed the type checker rather than change the test scripts.

                   

                  Of course, it doesn't work the other way

                   

                  BIND foobar : FooBar = returnMeFoo()
                  . . .
                  

                   

                  If the initializer returns an instance of org.my.Foo and we don't independently have some way of deriving the package name for FooBar then I can look up the class + interface hierarchy to find a proper name for FooBar but I can't search down the hierarchy (well not using the reflection API at least). So, unfortunately, when you want to downcast you will either have to use a type which has been identified independently as the type of some expression or method parameter or else you will need to use the package qualified name in the declaration e.g.

                   

                  BIND foobar : oeg.my.FooBar = returnMeFoo()
                  . . .
                  

                   

                  I raised JIRA BYTEMAN-315 for this problem and will push a patch asap.