Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
390427a
CpsFlowExecution: parseScript(): log "Method Too Large" situations mo…
jimklimov Jun 6, 2023
c438ebd
Merge branch 'master' into logging-exMTL
jimklimov Dec 11, 2023
93d4621
Merge remote-tracking branch 'upstream/master' into logging-exMTL
jimklimov Mar 5, 2024
b91c7f6
CpsFlowExecution: import groovyjarjarasm.asm.MethodTooLargeException …
jimklimov Mar 5, 2024
28cb0c3
CpsScriptTest: add methodTooLargeExceptionFabricated() and methodTooL…
jimklimov Mar 5, 2024
df2b78f
CpsFlowExecution, CpsScriptTest: handle also MultipleCompilationError…
jimklimov Mar 5, 2024
ffe1f1a
CpsScriptTest.methodTooLargeExceptionRealistic(): reduce iteration co…
jimklimov Mar 5, 2024
fa95de7
CpsFlowExecution.parseScript(): handle MethodTooLargeException withou…
jimklimov Mar 5, 2024
66afa75
CpsFlowExecution.parseScript(): MethodTooLargeException: actionable s…
jimklimov Mar 5, 2024
74a125c
CpsScriptTest.methodTooLargeExceptionRealistic(): update comments and…
jimklimov Mar 5, 2024
5a50a57
CpsScriptTest.methodTooLargeExceptionRealistic(): decouple maxStagesM…
jimklimov Mar 5, 2024
d781a3b
Merge branch 'master' into logging-exMTL
jimklimov Apr 24, 2024
89bbd40
Update plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlo…
jimklimov Jun 11, 2024
38cc476
Merge branch 'master' into logging-exMTL
jimklimov Jun 11, 2024
0fc23a5
Merge branch 'master' into logging-exMTL
jimklimov Jun 24, 2024
92aadc9
CpsFlowExecution: restore the multi-catch for "RuntimeException | Error"
jimklimov Jun 26, 2024
8c36e27
Revert "Update plugin/src/main/java/org/jenkinsci/plugins/workflow/cp…
jimklimov Jun 26, 2024
82470d2
CpsFlowExecution: restore the write into server log, but only at FINE…
jimklimov Jun 26, 2024
db51a8a
CpsFlowExecution: comment that the remaining case is "ecCount == 1 ex…
jimklimov Jun 26, 2024
20213ea
CpsFlowExecution: recognize also CpsCompilationErrorsException for Me…
jimklimov Jun 26, 2024
f23b610
CpsFlowExecution, CpsScriptTest: recognize also Groovy (JSL step or c…
jimklimov Jun 26, 2024
21a3f8d
CpsFlowExecution: introduce METHOD_TOO_LARGE_LOGGER for separated log…
jimklimov Jun 27, 2024
47a5e84
CpsFlowExecution: annotate what we post into METHOD_TOO_LARGE_LOGGER
jimklimov Jun 27, 2024
2adbecf
CpsFlowExecution: refactor whole reportSuspectedMethodTooLarge() magi…
jimklimov Jun 28, 2024
5954624
CpsFlowExecution: parseScript(): IDEA complains for "throw reportSusp…
jimklimov Jun 28, 2024
dac4ca6
CpsScript: getProperty(): involve CpsFlowExecution.reportSuspectedMet…
jimklimov Jun 28, 2024
4126b11
CpsFlowExecution: reportSuspectedMethodTooLarge(): fix indentations a…
jimklimov Jun 28, 2024
b078e7a
CpsFlowExecution: reportSuspectedMethodTooLarge(): when returning sho…
jimklimov Jun 28, 2024
b02afc3
CpsFlowExecution: reportSuspectedMethodTooLarge(): xStr should not in…
jimklimov Jun 28, 2024
5336cbe
CpsFlowExecution: reportSuspectedMethodTooLarge(): at least CpsCompil…
jimklimov Jun 28, 2024
4e19e1f
CpsFlowExecution: reportSuspectedMethodTooLarge(): describe what we s…
jimklimov Jun 28, 2024
93adf33
CpsFlowExecution: reportSuspectedMethodTooLarge(): document MTLE_CLAS…
jimklimov Jun 28, 2024
a512368
CpsFlowExecution: reportSuspectedMethodTooLarge(): overflowedClassNam…
jimklimov Jun 28, 2024
bfa34cb
CpsFlowExecution: reportSuspectedMethodTooLarge(): support also pipel…
jimklimov Jun 28, 2024
2fd7299
CpsFlowExecution: reportSuspectedMethodTooLarge(): wrap original log …
jimklimov Jun 28, 2024
c5e79da
CpsFlowExecution: reportSuspectedMethodTooLarge(): refactor actionabl…
jimklimov Jun 28, 2024
4d39666
CpsFlowExecution: reportSuspectedMethodTooLarge(): refactor overflowe…
jimklimov Jun 28, 2024
4f5ab70
CpsFlowExecution: reportSuspectedMethodTooLarge(): match by *short* o…
jimklimov Jun 28, 2024
9491ce1
CpsFlowExecution: reportSuspectedMethodTooLarge(): wrap original log …
jimklimov Jun 28, 2024
99362bc
CpsFlowExecution: reportSuspectedMethodTooLarge(): comment about "int…
jimklimov Jun 28, 2024
343c5da
CpsFlowExecution: reportSuspectedMethodTooLarge(): do not hijack mtlE…
jimklimov Jun 28, 2024
82edfc1
CpsFlowExecution: reportSuspectedMethodTooLarge(): avoid extra blank …
jimklimov Jun 28, 2024
337cc4e
CpsScriptTest: methodTooLargeExceptionRealistic(): expect updated wor…
jimklimov Jun 28, 2024
1ea2ffe
Merge branch 'master' into logging-exMTL
jimklimov Jan 31, 2025
7fd3dd2
Merge branch 'master' into logging-exMTL
jimklimov Mar 22, 2025
1692243
Merge branch 'master' into logging-exMTL
jimklimov Apr 25, 2025
92b74f1
Merge branch 'master' into logging-exMTL
jimklimov Aug 4, 2025
3cb7ea5
Merge branch 'master' into logging-exMTL
jimklimov Sep 2, 2025
095ff23
Merge remote-tracking branch 'upstream/master' as of 2025-10-04 into …
jimklimov Oct 4, 2025
ede9176
CpsFlowExecution, CpsScriptTest: mvn spotless:apply
jimklimov Oct 4, 2025
2766a10
CpsFlowExecution: rephrase Jenkins Shared Library/JSL (doc book term)…
jimklimov Oct 4, 2025
ef62003
CpsFlowExecution:: simplify end-of-line regex
jimklimov Oct 4, 2025
0612903
Merge branch 'master' into logging-exMTL
jimklimov Oct 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,17 @@
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
import groovyjarjarasm.asm.MethodTooLargeException;
import groovy.lang.GroovyShell;
import hudson.ExtensionList;
import hudson.model.Action;
import hudson.model.Result;
import hudson.util.Iterators;
import jenkins.model.CauseOfInterruption;
import jenkins.model.Jenkins;
import org.codehaus.groovy.control.ErrorCollector;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.control.messages.Message;
import org.jboss.marshalling.Unmarshaller;
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
import org.jenkinsci.plugins.workflow.cps.persistence.PersistIn;
Expand Down Expand Up @@ -633,11 +637,65 @@ private CpsScript parseScript() throws IOException {
trusted = new CpsGroovyShellFactory(this).forTrusted().build();
shell = new CpsGroovyShellFactory(this).withParent(trusted).build();

s = (CpsScript) shell.reparse("WorkflowScript",script);
s = (CpsScript) shell.reparse("WorkflowScript", script);

for (Entry<String, String> e : loadedScripts.entrySet()) {
shell.reparse(e.getKey(), e.getValue());
}
} catch (MethodTooLargeException | MultipleCompilationErrorsException x) {
MethodTooLargeException mtlEx = null;
int ecCount = 0;

closeShells();

if (x instanceof MethodTooLargeException) {
mtlEx = (MethodTooLargeException)x;
ecCount = 1;
} else if (x instanceof MultipleCompilationErrorsException) {
ErrorCollector ec = ((MultipleCompilationErrorsException)x).getErrorCollector();
ecCount = ec.getErrorCount();

for (int i = 0; i < ecCount; i++) {
Exception ex = ec.getException(i);
if (ex == null)
continue;

LOGGER.log(Level.FINE, "Collected Exception #" + i + ": " + ex.toString());
if (ex instanceof MethodTooLargeException) {
mtlEx = (MethodTooLargeException) ex;
break;
}
}
}

if (mtlEx == null) {
// Some other exception type, or collection did not include MTL, rethrow as-is
throw x;
}

String msg = "FAILED to parse WorkflowScript (the pipeline script) due to MethodTooLargeException";
if (ecCount > 1) {
msg += " (and other issues)";
}
// Short message suffices, not much that a pipeline developer
// can do with the stack trace into the guts of groovy
msg += ": " + mtlEx.getMessage();

// Make a note in server log
LOGGER.log(Level.SEVERE, msg);

if (ecCount > 1) {
// Not squashing with explicit MethodTooLargeException
// re-thrown below, in this codepath we have other errors.
throw new RuntimeException(msg, x);
} else {
// Do not confuse pipeline devs by a wall of text in the
// build console, but let the full context be found in
// server log with some dedication.
LOGGER.log(Level.FINE, mtlEx.getMessage());
//throw new RuntimeException(msg, mtlEx);
throw new RuntimeException(msg);
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the concern of "pretty convoluted logic" - this block is largely why I did it so: groovy can return a MultipleCompilationErrorsException with an attached collection of one or more errors (broadly speaking), so we need to count if we only had the MethodTooLargeException one way or another here (and then print the pretty log), or also something else and then better print everything for human devs to sift through it diligently.

} catch (RuntimeException | Error x) {
closeShells();
throw x;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,115 @@ public void evaluateShallSandbox() throws Exception {
r.assertLogContains("Scripts not permitted to use method groovy.lang.Script run java.io.File java.lang.String[]", b);
}

@Test public void methodTooLargeExceptionFabricated() throws Exception {
// Fabricate a MethodTooLargeException which "normally" happens when evaluated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to keep this test case, given the “realistic” test below?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, this fabricated test is about coded reaction to the particular exception, which may be or not be the one thrown in realistic test below (if CPS VM does at all break such way there and with that amount of threads).

// groovy script becomes a Java class too large for Java to handle internally.
// In Jenkins practice this can happen not only due to large singular pipelines
// (one big nudge to offload code into shared libraries), but was also seen due
// to heavy nesting of exception handling and other loops (simple refactoring
// can help).
WorkflowJob p = r.createProject(WorkflowJob.class);
// sandbox == false to allow creation of the exception here:
p.setDefinition(new CpsFlowDefinition(
"import groovyjarjarasm.asm.MethodTooLargeException;\n\n" +
"throw new MethodTooLargeException('className', 'methodName', 'methodDescriptor', 65535);"
, false));
WorkflowRun b = r.buildAndAssertStatus(Result.FAILURE, p);
r.assertLogContains("groovyjarjarasm.asm.MethodTooLargeException: Method too large: className.methodName methodDescriptor", b);
r.assertLogContains("at WorkflowScript.run(WorkflowScript:3)", b);
r.assertLogContains("at ___cps.transform___(Native Method)", b);
}

@Test public void methodTooLargeExceptionRealistic() throws Exception {
// See comments above. Here we try to really induce a "method too large"
// condition by abusing the nesting of exception-handling, too many stages
// or methods, and whatever else we can throw at it.
WorkflowJob p = r.createProject(WorkflowJob.class);
StringBuffer sbMethods = new StringBuffer();
StringBuffer sbStages = new StringBuffer();
int i, max = 255;

for (i = 0; i < 250; i++) {
// Up to 255 stages allowed
sbStages.append("stage('Stage " + i + "') { steps { method" + i + "(); } }\n");
}

for (i = 0; i < max; i++) {
sbMethods.append("def method" + i + "() { echo 'i = " + i + "'; }\n");
}

sbMethods.append("def method() {\n");
for (i = 0; i < max; i++) {
sbMethods.append("try { // " + i + "\n");
}
sbMethods.append(" Integer x = 'zzz'; // incur conversion exception\n");
for (i = 0; i < max; i++) {
sbMethods.append("} catch (Throwable t) { // " + i + "\n method" + i + "(); throw t; }\n");
}
sbMethods.append("}\n");

p.setDefinition(new CpsFlowDefinition(sbMethods.toString() +
"pipeline {\n" +
" agent none;\n" +
" stages {\n" +
" stage ('Test stage') {\n" +
" steps {\n" +
" script {\n" +
" echo 'BEGINNING TEST IN PIPELINE';\n" +
" method();\n" +
" echo 'ENDED TEST IN PIPELINE';\n" +
" }\n" +
" }\n" +
" }\n" +
sbStages.toString() +
" }\n" +
"}\n" +
"echo 'BEGINNING TEST OUT OF PIPELINE';\n" +
"method();\n" +
"echo 'ENDED TEST OUT OF PIPELINE';\n"
, true));

WorkflowRun b = p.scheduleBuild2(0).get();

// DEV-TEST // System.out.println(b.getLog());

r.assertLogContains("MethodTooLargeException", b);

// "Prettier" explanation added by CpsFlowExecution.parseScript():
r.assertLogContains("FAILED to parse WorkflowScript (the pipeline script) due to MethodTooLargeException:", b);

/*
// Report as of release 3880.vb_ef4b_5cfd270 (Feb 2024)
// and same pattern seen since at least Jun 2022 (note
// that numbers after ___cps___ differ from job to job):

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during class generation: Method too large: WorkflowScript.___cps___1 ()Lcom/cloudbees/groovy/cps/impl/CpsFunction;

groovyjarjarasm.asm.MethodTooLargeException: Method too large: WorkflowScript.___cps___1 ()Lcom/cloudbees/groovy/cps/impl/CpsFunction;
at groovyjarjarasm.asm.MethodWriter.computeMethodInfoSize(MethodWriter.java:2087)
at groovyjarjarasm.asm.ClassWriter.toByteArray(ClassWriter.java:447)
at org.codehaus.groovy.control.CompilationUnit$17.call(CompilationUnit.java:850)
at org.codehaus.groovy.control.CompilationUnit.applyToPrimaryClassNodes(CompilationUnit.java:1087)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:624)
at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:602)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:579)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:323)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:293)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox$Scope.parse(GroovySandbox.java:163)
at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.doParse(CpsGroovyShell.java:190)
at org.jenkinsci.plugins.workflow.cps.CpsGroovyShell.reparse(CpsGroovyShell.java:175)
at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.parseScript(CpsFlowExecution.java:637)
at org.jenkinsci.plugins.workflow.cps.CpsFlowExecution.start(CpsFlowExecution.java:583)
at org.jenkinsci.plugins.workflow.job.WorkflowRun.run(WorkflowRun.java:335)
at hudson.model.ResourceController.execute(ResourceController.java:101)
at hudson.model.Executor.run(Executor.java:442)
*/

r.assertLogContains("Method too large: WorkflowScript.___cps___", b);
r.assertLogContains("()Lcom/cloudbees/groovy/cps/impl/CpsFunction;", b);

// Assert separately from (and after) log parsing, to facilitate test maintenance
r.assertBuildStatus(Result.FAILURE, b);
}
}