-
Notifications
You must be signed in to change notification settings - Fork 198
CpsFlowExecution: parseScript(): log "Method Too Large" situations more readably #817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
390427a
c438ebd
93d4621
b91c7f6
28cb0c3
df2b78f
ffe1f1a
fa95de7
66afa75
74a125c
5a50a57
d781a3b
89bbd40
38cc476
0fc23a5
92aadc9
8c36e27
82470d2
db51a8a
20213ea
f23b610
21a3f8d
47a5e84
2adbecf
5954624
dac4ca6
4126b11
b078e7a
b02afc3
5336cbe
4e19e1f
93adf33
a512368
bfa34cb
2fd7299
c5e79da
4d39666
4f5ab70
9491ce1
99362bc
343c5da
82edfc1
337cc4e
1ea2ffe
7fd3dd2
1692243
92b74f1
3cb7ea5
095ff23
ede9176
2766a10
ef62003
0612903
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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); | ||
|
|
||
jimklimov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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()); | ||
jimklimov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| //throw new RuntimeException(msg, mtlEx); | ||
| throw new RuntimeException(msg); | ||
| } | ||
|
||
| } catch (RuntimeException | Error x) { | ||
| closeShells(); | ||
| throw x; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
||
| // 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); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.