Skip to content

Commit 7c3a975

Browse files
authored
Warn user when forgetting def on a variable (jenkinsci#994)
* Exploring a warning when a field is set * Limiting matches to non-null assignments to `CpsScript`s, and suppressing from some tests which could equally well have used lexical variables * Seem to be getting warnings only in appropriate places * Might want a redirect * Better message * Further tweaking message * Forgot to reuse computed `owner` * Kill switch jenkinsci#994 (comment) * Ignore `@Field`s jenkinsci#994 (comment)
1 parent 876ed00 commit 7c3a975

File tree

7 files changed

+37
-6
lines changed

7 files changed

+37
-6
lines changed

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

+24
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.cloudbees.groovy.cps.sandbox.SandboxInvoker;
3131
import edu.umd.cs.findbugs.annotations.CheckForNull;
3232
import edu.umd.cs.findbugs.annotations.NonNull;
33+
import groovy.lang.Closure;
3334
import groovy.lang.GroovyClassLoader;
3435
import hudson.ExtensionList;
3536
import java.util.Set;
@@ -137,6 +138,29 @@ private void maybeRecord(Class<?> clazz, Supplier<String> message) {
137138
Class<?> clazz = classOf(lhs);
138139
maybeRecord(clazz, () -> clazz.getName() + "." + name);
139140
delegate.setProperty(lhs, name, value);
141+
if (SystemProperties.getBoolean(LoggingInvoker.class.getName() + ".fieldSetWarning", true)) {
142+
if (value != null) {
143+
var owner = findOwner(lhs);
144+
if (owner instanceof CpsScript) {
145+
try {
146+
owner.getClass().getDeclaredField(name);
147+
// OK, @Field, ignore
148+
} catch (NoSuchFieldException x) {
149+
var g = CpsThreadGroup.current();
150+
if (g != null) {
151+
var e = g.getExecution();
152+
if (e != null) {
153+
e.getOwner().getListener().getLogger().println(Messages.LoggingInvoker_field_set(owner.getClass().getSimpleName(), name, value.getClass().getSimpleName()));
154+
}
155+
}
156+
}
157+
}
158+
}
159+
}
160+
}
161+
162+
private Object findOwner(Object o) {
163+
return o instanceof Closure<?> c ? findOwner(c.getOwner()) : o;
140164
}
141165

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

plugin/src/main/resources/org/jenkinsci/plugins/workflow/cps/Messages.properties

+5
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@ SnippetizerLink.OnlineDocsLink.displayName=Online Documentation
66
SnippetizerLink.ExamplesLink.displayName=Examples Reference
77
SnippetizerLink.GDSLLink.displayName=IntelliJ IDEA GDSL
88
Pipeline.Syntax=Pipeline Syntax
9+
# TODO perhaps create a https://jenkins.io/redirect/pipeline-field-set/
10+
LoggingInvoker.field_set=\
11+
Did you forget the `def` keyword? \
12+
{0} seems to be setting a field named {1} (to a value of type {2}) \
13+
which could lead to memory leaks or other issues.

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

+1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ public static class Execution extends AbstractStepExecutionImpl {
269269
}
270270
r.assertBuildStatusSuccess(r.waitForCompletion(b));
271271
new DepthFirstScanner().allNodes(b.getExecution()).stream().sorted(Comparator.comparing(n -> Integer.valueOf(n.getId()))).forEach(n -> System.out.println(n.getId() + " " + n.getDisplayName()));
272+
r.assertLogContains(Messages.LoggingInvoker_field_set("WorkflowScript", "g", "CpsClosure2"), b);
272273
});
273274
}
274275

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public class CpsVmExecutorServiceTest {
139139
@Test public void okCatcherMetaClassImpl() throws Exception {
140140
p.setDefinition(new CpsFlowDefinition(
141141
"import org.codehaus.groovy.runtime.InvokerHelper \n" +
142-
"c = { println 'doing a thing' } \n" +
142+
"def c = { println 'doing a thing' } \n" +
143143
"InvokerHelper.getMetaClass(c).invokeMethod(c, 'call', null)", false));
144144
WorkflowRun b = r.buildAndAssertSuccess(p);
145145
r.assertLogNotContains("MetaClassImpl", b);
@@ -150,7 +150,7 @@ public class CpsVmExecutorServiceTest {
150150
@Test public void okCatcherExpandoMetaClass() throws Exception {
151151
p.setDefinition(new CpsFlowDefinition(
152152
"import org.codehaus.groovy.runtime.InvokerHelper \n" +
153-
"c = { println 'doing a thing' } \n" +
153+
"def c = { println 'doing a thing' } \n" +
154154
"c.getMetaClass().someField = 'r' \n" +
155155
"InvokerHelper.getMetaClass(c).invokeMethod(c, 'call', null)", false));
156156
WorkflowRun b = r.buildAndAssertSuccess(p);

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ public void namedSoleParamForStep() throws Exception {
351351
* Tests the ability to execute a user defined closure with one arguments
352352
*/
353353
@Test public void userDefinedClosure1ArgInvocationExecution() throws Exception {
354-
p.setDefinition(new CpsFlowDefinition("my_closure = { String message -> \n" +
354+
p.setDefinition(new CpsFlowDefinition("def my_closure = { String message -> \n" +
355355
" echo message \n" +
356356
"}\n" +
357357
"my_closure(\"my message!\") ", false));
@@ -363,7 +363,7 @@ public void namedSoleParamForStep() throws Exception {
363363
* Tests the ability to execute a user defined closure with 2 arguments
364364
*/
365365
@Test public void userDefinedClosure2ArgInvocationExecution() throws Exception {
366-
p.setDefinition(new CpsFlowDefinition("my_closure = { String message1, String message2 -> \n" +
366+
p.setDefinition(new CpsFlowDefinition("def my_closure = { String message1, String message2 -> \n" +
367367
" echo \"my message is ${message1} and ${message2}\" \n" +
368368
"}\n" +
369369
"my_closure(\"string1\", \"string2\") ", false));
@@ -375,7 +375,7 @@ public void namedSoleParamForStep() throws Exception {
375375
* Tests untyped arguments
376376
*/
377377
@Test public void userDefinedClosureUntypedArgInvocationExecution() throws Exception {
378-
p.setDefinition(new CpsFlowDefinition("my_closure = { a , b -> \n" +
378+
p.setDefinition(new CpsFlowDefinition("def my_closure = { a , b -> \n" +
379379
" echo \"my message is ${a} and ${b}\" \n" +
380380
"}\n" +
381381
"my_closure(\"string1\" ,2)",false));

plugin/src/test/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommandTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public void rebuildNeedScriptApprovalCLIEdition() throws Exception {
5454
WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "SECURITY-3362");
5555
j.jenkins.getWorkspaceFor(p).child("a.groovy").write("echo 'Hello LoadedWorld'", null);
5656
String script =
57+
"def a\n" +
5758
"node() {\n" +
5859
" a = load('a.groovy')\n" +
5960
"}\n";

plugin/src/test/java/org/jenkinsci/plugins/workflow/cps/steps/ParallelStepTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void minimumViableParallelRun() throws Exception {
6666
p = jenkins().createProject(WorkflowJob.class, "demo");
6767
p.setDefinition(new CpsFlowDefinition(join(
6868
"node {",
69-
" x = parallel( a: { echo('echo a'); return 1; }, b: { echo('echo b'); sleep 1; return 2; } )",
69+
" def x = parallel( a: { echo('echo a'); return 1; }, b: { echo('echo b'); sleep 1; return 2; } )",
7070
" assert x.a==1",
7171
" assert x.b==2",
7272
"}"

0 commit comments

Comments
 (0)