Skip to content

Commit f547819

Browse files
authored
Merge pull request #1061 from dwnusbaum/failureadapter-npe
Prevent `NullPointerException` in `CpsBodyExecution.FailureAdapter`
2 parents 23daee5 + 7b2c472 commit f547819

File tree

4 files changed

+46
-4
lines changed

4 files changed

+46
-4
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,14 @@ private Throwable handleError(CpsBodySubContext sc, Throwable t) {
375375
localThread = thread;
376376
}
377377
try {
378-
var handler = localThread.getContextVariables().get(FailureHandler.class, localThread::getExecution, sc::getNode);
379-
if (handler != null) {
380-
return handler.handle(sc, t);
378+
var contextVars = localThread.getContextVariables();
379+
// CpsFlowExecution.start sets the context variables for the initial CpsThread to null, so context
380+
// variables for all subsequent threads remain null until a block step adds a variable to its body.
381+
if (contextVars != null) {
382+
var handler = contextVars.get(FailureHandler.class, localThread::getExecution, sc::getNode);
383+
if (handler != null) {
384+
return handler.handle(sc, t);
385+
}
381386
}
382387
} catch (Throwable t2) {
383388
t.addSuppressed(t2);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class CpsBodySubContext extends DefaultStepContext {
3535

3636
@NonNull
3737
@Override
38-
protected FlowNode getNode() throws IOException {
38+
protected FlowNode getNode() {
3939
return node;
4040
}
4141

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ <T> T getContextVariable(Class<T> key, ContextVariableSet.ThrowingSupplier<FlowE
144144
}
145145
}
146146

147+
@CheckForNull
147148
public ContextVariableSet getContextVariables() {
148149
return contextVariables;
149150
}
@@ -236,6 +237,7 @@ boolean isAlive() {
236237
* something is unexpectedly holding a reference directly to it (see JENKINS-63164 for an example scenario).
237238
*/
238239
void cleanUp() {
240+
LOGGER.finest(() -> "Cleaning up " + this);
239241
program = null;
240242
resumeValue = null;
241243
step = null;

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
2727
import org.jenkinsci.plugins.workflow.steps.BodyExecution;
2828
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
29+
import org.jenkinsci.plugins.workflow.steps.Step;
30+
import org.jenkinsci.plugins.workflow.steps.StepContext;
31+
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
2932
import org.jenkinsci.plugins.workflow.steps.StepExecution;
33+
import org.jenkinsci.plugins.workflow.steps.StepExecutions;
3034
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
3135
import org.junit.ClassRule;
3236
import org.junit.Rule;
@@ -273,4 +277,35 @@ public static class Execution extends AbstractStepExecutionImpl {
273277
});
274278
}
275279

280+
@Test public void failureWithNoContextVars() throws Throwable {
281+
rr.then(r -> {
282+
WorkflowJob p = r.createProject(WorkflowJob.class, "p");
283+
p.setDefinition(new CpsFlowDefinition("noOpBlock { throw new Exception('oops') }", true));
284+
var b = r.buildAndAssertStatus(Result.FAILURE, p);
285+
r.assertLogNotContains("java.lang.NullPointerException: Cannot invoke \"org.jenkinsci.plugins.workflow.cps.ContextVariableSet.get", b);
286+
});
287+
}
288+
289+
public static class NoOpBlockStep extends Step {
290+
@DataBoundConstructor
291+
public NoOpBlockStep() {}
292+
@Override
293+
public StepExecution start(StepContext context) throws Exception {
294+
return StepExecutions.block(context, (ctx, inv) -> {});
295+
}
296+
@TestExtension("failureWithNoContextVars") public static class DescriptorImpl extends StepDescriptor {
297+
@Override public String getFunctionName() {
298+
return "noOpBlock";
299+
}
300+
@Override
301+
public boolean takesImplicitBlockArgument() {
302+
return true;
303+
}
304+
@Override
305+
public Set<? extends Class<?>> getRequiredContext() {
306+
return Set.of();
307+
}
308+
}
309+
}
310+
276311
}

0 commit comments

Comments
 (0)