Field references in CPS class methods fail in CPS subclasses

This issue is archived. You can view it, but you can't modify it. Learn more

XMLWordPrintable

      Still trying to figure out how to describe this exactly. =)

      There are two cases that are related that I can reproduce. First, in a single Jenkinsfile:

      class C {
        int x = 33
        int getX() {
          2 * this.@x
        }
      }
      
      class D extends C { 
        int getY() { 
          2 * getX() } 
      }
      
      new D().y
      

      This results in groovy.lang.MissingFieldException: No such field: x for class: D, with the following stacktrace:

      	at groovy.lang.MetaClassImpl.getAttribute(MetaClassImpl.java:2823)
      	at groovy.lang.MetaClassImpl.getAttribute(MetaClassImpl.java:3759)
      	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getGroovyObjectField(ScriptBytecodeAdapter.java:356)
      	at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.getAttribute(DefaultInvoker.java:53)
      	at com.cloudbees.groovy.cps.impl.AttributeAccessBlock.rawGet(AttributeAccessBlock.java:20)
      	at C.getX(Script1.groovy:1)
      	at D.getY(Script1.groovy:1)
      	at Script1.run(Script1.groovy:1)
      

      The second I can't reproduce in a single Jenkinsfile - each of the inheriting classes need to be in a shared library, and it needs multiple levels of inheritance.

      class BaseAction {
        String someActionValue = "thevalue"
      }
      
      class ChildAction extends BaseAction {
          def doSomething() {
              return "in ChildAction: " + someActionValue
          }
      }
      
      class GrandChildAction extends ChildAction {
          def doSomething() {
              return "in GrandChildAction: " + someActionValue
          }
      }
      

      With the Jenkinsfile:

      @Library("config@master") _
      import ChildAction
      import GrandChildAction
      println new ChildAction().doSomething()
      println new GrandChildAction().doSomething()
      

      This results in groovy.lang.MissingFieldException: No such field: someActionValue for class: GrandChildAction with the following stacktrace:

      at groovy.lang.MetaClassImpl.getAttribute(MetaClassImpl.java:2846)
      	at groovy.lang.MetaClassImpl.getAttribute(MetaClassImpl.java:3782)
      	at org.codehaus.groovy.runtime.InvokerHelper.getAttribute(InvokerHelper.java:147)
      	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getField(ScriptBytecodeAdapter.java:306)
      	at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.getAttribute(DefaultInvoker.java:48)
      	at com.cloudbees.groovy.cps.impl.AttributeAccessBlock.rawGet(AttributeAccessBlock.java:20)
      	at GrandChildAction.doSomething(/private/var/folders/pr/24nv8g910wg8vr4b4c33q34r0000gn/T/jenkinsTests.tmp/jenkins6831451434103658873test/jobs/p/builds/1/libs/config/src/GrandChildAction.groovy:4)
      	at WorkflowScript.run(WorkflowScript:5)
      

      I left a comment detailing my investigation into this so far over on JENKINS-50736:

      What's actually happening is that we're actually calling (via some layers of abstraction) InvokerHelper.getAttribute(object, name), which ends up getting the MetaBeanProperty for that object/name combo. And the field field of that MetaBeanProperty is null. This is the same behavior you get in vanilla Groovy if you do this.@someField in the subclass. So the problem seems to be that you can't actually call InvokerHelper.getAttribute(object, name) for a field on a superclass, and our hacks to avoid recursion hell with getter methods (which I think was intended primarily to deal with explicit getters, not autogenerated getters, but don't quote me on that) will end up doing that.

      How to fix this? Well, I've got a couple hacks I've tried that seem to "fix" this for some value of "fixed" - most notably changing DefaultInvoker#getAttribute(lhs, name) to catch MissingFieldException when it shows up and try forcing a MetaClass#getAttribute call with InvokerHelper.getMetaClass(lhs.getClass().getSuperclass()), recursing on until we get to Object.class. But that feels really really dirty, so I'm trying to think of something better.

      So at a minimum, we're not properly handling references to fields as attribute expressions (i.e., this.@foo) in a superclass, even if the child class never actually references the field-as-attribute directly, just via a superclass method (note that it doesn't have to be in a getter method like getX to blow up - I used a different method name and it still went kerplooie).

      Needs more thought and explanation, but I wanted to give this a separate JIRA for clarity.

            Assignee:
            Unassigned
            Reporter:
            Andrew Bayer
            Archiver:
            Jenkins Service Account

              Created:
              Updated:
              Archived: