Skip to content
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

[JENKINS-26481] [JENKINS-27421] Use GroovyCategorySupport to invoke CpsDefaultGroovyMethods (w/o DGMPatcher) & IteratorHack #124

Merged
merged 16 commits into from May 30, 2017

Conversation

jglick
Copy link
Member

@jglick jglick commented May 11, 2017

Downstream of cloudbees/groovy-cps#56.

JENKINS-26481: supersedes #14 and most of cloudbees/groovy-cps#51. Seems to demonstrate that we never actually needed all the horrifying stuff in DGMPatcher. And seems to work in any Groovy version we support. (Pending integration of 2.5.0 in Jenkins core, we will however hit GROOVY-6263.)

Also JENKINS-27421: a more maintainable workaround for serialization issues.

Really the technique is quite general: any time you find a (non-static) method somewhere (typically in either the Java Platform or the GDK) which is not amenable to being called from Pipeline script (particularly CPS-transformed parts), we can replace its implementation with something else of our choice.

Tasks, including upstream:

  • using CpsDefaultGroovyMethods
  • adding coverage for most Closure-based methods
  • fixing List.iterator
  • fixing Map.entrySet
  • fixing Set.iterator
  • fixing List.listIterator
  • test coverage for miscellaneous new Closure-based methods
  • GroovyClassLoaderWhitelist to check only Closure-based methods defined outside the supported receivers, as in [JENKINS-34064] Making fix work in Jenkins 2 #14
  • some manual integration checking just because this is all so scary

Subsumes #135.

@reviewbybees if you are feeling adventurous

…sDefaultGroovyMethods without hacks like DGMPatcher.
@@ -146,10 +157,33 @@ public StepExecution getStep() {
this.step = step;
}

/** TODO pending full coverage in {@link CpsDefaultGroovyMethods} */
Copy link
Member Author

Choose a reason for hiding this comment

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

Such as is added in cloudbees/groovy-cps#52, but that mixes in other stuff which we do not need.

outcome = program.run0(o);
outcome = GroovyCategorySupport.use(Arrays.<Class>asList(CpsDefaultGroovyMethods.class, CpsDefaultGroovyMethodsExt.class), new Closure<Outcome>(null) {
@Override public Outcome call() {
return program.run0(o);
Copy link
Member Author

Choose a reason for hiding this comment

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

This is just for experimentation; probably we would rather want to have a change in groovy-cps which

  • fleshes out CpsDefaultGroovyMethods
  • calls GroovyCategorySupport.use from inside Continuable.run0

for (int i = 0; i < args.length; i++) {
if (args[i] instanceof CpsClosure && parameterTypes.length > i && parameterTypes[i] == Closure.class) {
throw new UnsupportedOperationException("Calling " + method + " on a CPS-transformed closure is not yet supported (JENKINS-26481); encapsulate in a @NonCPS method, or use Java-style loops");
if (false) { // TODO be more discriminating
Copy link
Member Author

Choose a reason for hiding this comment

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

Suppressing the main change in #15 for the moment; #14 has something closer to what we need, though ideally we would check the actual method and determine if we have CPS coverage for that method (and overload) now.

Builder b = new Builder(loc("each"));
CpsFunction f = new CpsFunction(Arrays.asList("self", "closure"), b.block(
b.staticCall(-1, CpsDefaultGroovyMethods.class, "each",
b.staticCall(-1, InvokerHelper.class, "asIterator",
Copy link
Member Author

Choose a reason for hiding this comment

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

Just delegates to the Iterator overload.

…work around unserializable iterators, map entries, etc.

@Override public Pickle writeReplace(Object object) {
if (ACCEPTED_TYPES.contains(object.getClass().getName())) {
return new Replacement(object);
Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -219,12 +219,8 @@ public void stop(Throwable cause) throws Exception {
SemaphoreStep.success("new-one/1", null);
SemaphoreStep.success("new-two/1", null);
story.j.waitForCompletion(b);
/* TODO desired behavior:
Copy link
Member Author

Choose a reason for hiding this comment

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

Now we can have the “desired behavior” from #77.

@jglick jglick changed the title [JENKINS-26481] Prototype of using GroovyCategorySupport to invoke CpsDefaultGroovyMethods without hacks like DGMPatcher [JENKINS-26481] [JENKINS-27421] Use GroovyCategorySupport to invoke CpsDefaultGroovyMethods (w/o DGMPatcher) & IteratorHack May 11, 2017
@jglick
Copy link
Member Author

jglick commented May 11, 2017

Hmm, seems load step calls and the like fail with

groovy.lang.MissingMethodException: No signature of method: com.cloudbees.groovy.cps.SandboxCpsTransformer.copy() is applicable for argument types: (java.util.ArrayList) values: [[MethodNode@…[void main([Ljava.lang.String;)], ...]]
Possible solutions: any(), any(groovy.lang.Closure), notify(), wait(), find(), dump()

Looking into that.

@jglick
Copy link
Member Author

jglick commented May 12, 2017

Seems that merely calling GroovyCategorySupport.use—even with no categories—triggers this. So some of the global behavioral switches turned on by the presence of category support seems to be breaking the compiler. I think I have a workaround.

Copy link
Member

@abayer abayer left a comment

Choose a reason for hiding this comment

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

This is some nifty

resumeValue = null;
outcome = program.run0(o);
outcome = GroovyCategorySupport.use(Arrays.<Class>asList(CpsDefaultGroovyMethods.class, CpsDefaultGroovyMethodsExt.class, IteratorHack.class), new Closure<Outcome>(null) {
Copy link
Member

Choose a reason for hiding this comment

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

Given the black magic here, it'd be nice to have some comments explaining what exactly is going on. =)

abayer
abayer previously requested changes May 16, 2017
Copy link
Member

@abayer abayer left a comment

Choose a reason for hiding this comment

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

Overall, this is great, but it needs some more tests for additional methods like Map.each, collect, collectEntries, eachLine... I can get those added today, though. Expect a PR to @jglick's branch.

@jglick
Copy link
Member Author

jglick commented May 16, 2017

Absolutely we would like more test coverage.

As to

Could not find artifact com.cloudbees:groovy-cps:jar:1.13-20170515.220420-1 in repo.jenkins-ci.org (https://repo.jenkins-ci.org/public/)

seems I forgot to deploy to the Jenkins repo. Will fix.

@abayer
Copy link
Member

abayer commented May 16, 2017

@jglick I'm getting test failures locally - CpsFlowExecutionTest#trustedShell_control, CpsScriptTest#evaluateShallSandbox, SnippetizerTest#doDslRef...are you seeing those too or is this possibly a Java 8 issue? (since I'm using Java 8 locally with #126 merged in)

@jglick
Copy link
Member Author

jglick commented May 16, 2017

CpsScriptTest.evaluateShallSandbox indeed fails for me. Will investigate.

@abayer
Copy link
Member

abayer commented May 16, 2017

Had the same failures on Java 7 (and again with the right Java 8 version) so they look legit.

@jglick
Copy link
Member Author

jglick commented May 16, 2017

Yes, somehow these changes seem to be suppressing the sandbox, so will need to figure out why that is.

@abayer
Copy link
Member

abayer commented May 16, 2017

Map.each is failing with

groovy.lang.MissingMethodException: No signature of method: static com.cloudbees.groovy.cps.CpsDefaultGroovyMethods.$callClosureForMapEntry__groovy_lang_Closure__java_util_Map_Entry() is applicable for argument types: (org.jenkinsci.plugins.workflow.cps.CpsClosure2, java.util.AbstractMap$SimpleImmutableEntry) values: [org.jenkinsci.plugins.workflow.cps.CpsClosure2@3f6cd2de, a=1]

And it looks like callClosureForMapEntry doesn't exist in CpsDefaultGroovyMethods for real.

@jglick
Copy link
Member Author

jglick commented May 16, 2017

npm errors are making CI more or less useless in this repository, so I need to take a detour to try using yarn instead.

@jglick
Copy link
Member Author

jglick commented May 16, 2017

Seems that as of the last build, all tests passed, and we just bombed out due to npm (cf. #128).

@jglick jglick dismissed abayer’s stale review May 23, 2017 18:08

I think this was in reference to the upstream PR, and those tests were already added.

@jglick
Copy link
Member Author

jglick commented May 23, 2017

Seems to be causing a failure in LibraryAdderTest.loaderReleased which I will look into.

@jglick
Copy link
Member Author

jglick commented May 23, 2017

Never mind, that seems to be a regression in 2.31; I will look into it, but not a blocker for this.

@ghost
Copy link

ghost commented May 23, 2017

This pull request originates from a CloudBees employee. At CloudBees, we require that all pull requests be reviewed by other CloudBees employees before we seek to have the change accepted. If you want to learn more about our process please see this explanation.

pom.xml Outdated
@@ -62,8 +62,7 @@
</pluginRepository>
</pluginRepositories>
<properties>
<jenkins.version>1.642.3</jenkins.version>
<jenkins-test-harness.version>2.19</jenkins-test-harness.version>
<jenkins.version>2.7.3</jenkins.version>
Copy link
Member Author

Choose a reason for hiding this comment

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

Did not seem practical to support Groovy 1.x, since we have to choose a Groovy release to generate CpsDefaultGroovyMethods from. Too old, and we do not cover enough methods; too new, and it may not link against an older Groovy core.

Copy link
Member

Choose a reason for hiding this comment

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

+1 strongly. Especially given that Blue Ocean and Declarative require 2.7.3, so we've got that as the "default" baseline these days anyway.

pom.xml Outdated
@@ -234,6 +233,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
Copy link
Member Author

Choose a reason for hiding this comment

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

This is #135.

@@ -76,21 +68,4 @@ private boolean permits(Class<?> declaringClass) {
return permits(field.getDeclaringClass()) || delegate.permitsStaticFieldSet(field, value);
}

/**
* Blocks accesses to some {@link DefaultGroovyMethods}, or any other binary method taking a closure, until JENKINS-26481 is fixed.
Copy link
Member Author

Choose a reason for hiding this comment

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

Yes there could be other Closure-taking binary methods which we are not translating, but it is too cumbersome to track them. Better to spend time implementing the more general, and precise, JENKINS-31314.

assertThat("SubversionSCM.workspaceUpdater is mentioned as an attribute of a value of GenericSCMStep.delegate", html, containsString("workspaceUpdater"));
assertThat("CheckoutUpdater is mentioned as an option", html, containsString("CheckoutUpdater"));
assertThat("GitSCM.submoduleCfg is mentioned as an attribute of a value of GenericSCMStep.scm", html, containsString("submoduleCfg"));
assertThat("CleanBeforeCheckout is mentioned as an option", html, containsString("CleanBeforeCheckout"));
Copy link
Member Author

Choose a reason for hiding this comment

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

Just related to jenkins-test-harness bump (no more detached plugins).

}
Set<Map.Entry<K, V>> entries = new LinkedHashSet<>();
for (Map.Entry<K, V> entry : map.entrySet()) {
// TODO return an object holding references to the map and the key and delegating all calls accordingly
Copy link
Member Author

Choose a reason for hiding this comment

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

These impls are not intended to be perfect or complete, just good enough to let typical script usages pass, where previously they would not.

Copy link
Member

Choose a reason for hiding this comment

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

I forget - does this all enable for (a : listOfA) { ... } and the like?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, see IteratorHackTest. Prior to this PR, ArrayList.iterator was safe but nothing else (and in particular I had no idea how to support iterating Maps and the like—the trick did not generalize well). Now most common interface methods in this category should work, and on any implementation class.

pom.xml Outdated
@@ -62,8 +62,7 @@
</pluginRepository>
</pluginRepositories>
<properties>
<jenkins.version>1.642.3</jenkins.version>
<jenkins-test-harness.version>2.19</jenkins-test-harness.version>
<jenkins.version>2.7.3</jenkins.version>
Copy link
Member

Choose a reason for hiding this comment

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

+1 strongly. Especially given that Blue Ocean and Declarative require 2.7.3, so we've got that as the "default" baseline these days anyway.

}
Set<Map.Entry<K, V>> entries = new LinkedHashSet<>();
for (Map.Entry<K, V> entry : map.entrySet()) {
// TODO return an object holding references to the map and the key and delegating all calls accordingly
Copy link
Member

Choose a reason for hiding this comment

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

I forget - does this all enable for (a : listOfA) { ... } and the like?

@jglick
Copy link
Member Author

jglick commented May 24, 2017

@reviewbybees done

@kohsuke are you out there?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants