jenkinsci / jenkinsci/workflow-cps-plugin

[JENKINS-50954] Field references in CPS class methods fail in CPS subclasses

Open
#1,444 0 comments 0 reactions 0 assignees View on GitHub
component:workflow-cps-plugin imported-jira-issue priority:major resolution:unresolved
Dominant language
Java
Stars
186
Forks
213
Avg merge
12h 12m
Merged PRs (30d)
8

Description

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.

---
Originally reported by abayer, imported from: Field references in CPS class methods fail in CPS subclasses


  • status: Open
  • priority: Major
  • component(s): workflow-cps-plugin
  • resolution: Unresolved
  • votes: 0
  • watchers: 1
  • imported: 2025-12-07

Raw content of original issue

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.

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the Jenkinsfile and shared-library inheritance examples, then inspect DefaultInvoker#getAttribute and the referenced MetaClass and InvokerHelper calls. Done means superclass field references work in both CPS subclass scenarios without MissingFieldException, with regression coverage for the reported cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
groovy, java
Domain
backend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.