Skip to content

Commit afbde6a

Browse files
authored
Merge pull request #997 from dwnusbaum/JENKINS-75361
[JENKINS-75361] Consider `Closure.resolveStrategy` to reduce false positives
2 parents d58c465 + 21c02bc commit afbde6a

File tree

2 files changed

+45
-6
lines changed

2 files changed

+45
-6
lines changed

plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/LoggingInvoker.java

+26-6
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,17 @@ private void maybeRecord(Class<?> clazz, Supplier<String> message) {
142142
delegate.setProperty(lhs, name, value);
143143
if (SystemProperties.getBoolean(LoggingInvoker.class.getName() + ".fieldSetWarning", true)) {
144144
if (value != null && !CLOSURE_METAPROPS.contains(name)) {
145-
var owner = findOwner(lhs);
146-
if (owner instanceof CpsScript) {
145+
var receiver = findReceiver(lhs);
146+
if (receiver instanceof CpsScript) {
147147
try {
148-
owner.getClass().getDeclaredField(name);
148+
receiver.getClass().getDeclaredField(name);
149149
// OK, @Field, ignore
150150
} catch (NoSuchFieldException x) {
151151
var g = CpsThreadGroup.current();
152152
if (g != null) {
153153
var e = g.getExecution();
154154
if (e != null) {
155-
e.getOwner().getListener().getLogger().println(Messages.LoggingInvoker_field_set(owner.getClass().getSimpleName(), name, value.getClass().getSimpleName()));
155+
e.getOwner().getListener().getLogger().println(Messages.LoggingInvoker_field_set(receiver.getClass().getSimpleName(), name, value.getClass().getSimpleName()));
156156
}
157157
}
158158
}
@@ -161,8 +161,28 @@ private void maybeRecord(Class<?> clazz, Supplier<String> message) {
161161
}
162162
}
163163

164-
private Object findOwner(Object o) {
165-
return o instanceof Closure<?> c ? findOwner(c.getOwner()) : o;
164+
private Object findReceiver(Object o) {
165+
if (o instanceof Closure<?> c) {
166+
// c.f. https://github.com/apache/groovy/blob/41b990d0a20e442f29247f0e04cbed900f3dcad4/src/main/groovy/lang/Closure.java#L344
167+
return switch (c.getResolveStrategy()) {
168+
case Closure.DELEGATE_ONLY -> findReceiver(c.getDelegate());
169+
case Closure.OWNER_ONLY -> findReceiver(c.getOwner());
170+
case Closure.TO_SELF -> c;
171+
case Closure.DELEGATE_FIRST -> {
172+
var delegate = c.getDelegate();
173+
if (delegate == null) {
174+
yield findReceiver(c.getOwner());
175+
}
176+
// TODO: Groovy will actually try the delegate first and then the owner if needed, but it is
177+
// difficult for us to know what will happen in advance.
178+
yield findReceiver(delegate);
179+
}
180+
default ->
181+
// TODO: Groovy will actually try the owner first and then the delegate if needed.
182+
findReceiver(c.getOwner());
183+
};
184+
}
185+
return o;
166186
}
167187

168188
@Override public Object getAttribute(Object lhs, String name) throws Throwable {

plugin/src/test/java/org/jenkinsci/plugins/workflow/cps/LoggingInvokerTest.java

+19
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,23 @@ private void assertInternalCalls(String script, boolean sandbox, String... calls
8282
r.assertLogNotContains("`def`", r.buildAndAssertSuccess(p));
8383
}
8484

85+
@Test public void closureToMapDslPattern() throws Exception {
86+
var p = r.createProject(WorkflowJob.class);
87+
p.setDefinition(new CpsFlowDefinition(
88+
"""
89+
def closureToMap(body) {
90+
def map = [:]
91+
body.resolveStrategy = Closure.DELEGATE_FIRST
92+
body.delegate = map
93+
body()
94+
map
95+
}
96+
closureToMap {
97+
key1 = 'value1'
98+
key2 = 'value2'
99+
}
100+
""", true));
101+
r.assertLogNotContains("`def`", r.buildAndAssertSuccess(p));
102+
}
103+
85104
}

0 commit comments

Comments
 (0)