jenkinsci / jenkinsci/workflow-cps-plugin
[JENKINS-49826] Incorrect and confusing compilation of sets with custom types
- Dominant language
- Java
- Stars
- 186
- Forks
- 213
- Avg merge
- 12h 12m
- Merged PRs (30d)
- 8
Description
First, create a pipeline job with this pipeline that contains a class definition:
class Data implements Serializable {
final String string
Data(final String string) {
this.string = Objects.requireNonNull(string)
}boolean equals(final other) {
if (this.is(other)) {
return true
}
if (getClass() != other.class) {
return false
}final Data that = (Data) other
return string == that.string
}int hashCode() {
return string.hashCode()
}
}final d1 = new Data('a')
final d2 = new Data('b')
final d3 = new Data('c')final s1 = 'a'
final s2 = 'b'
final s3 = 'c'final s0 = [s1, s2, s3]
echo "s1: ${s0.getClass()}"
final ss1 = [s1, s2, s3].toSet()
echo "ss1: ${ss1.getClass()}"
final Set<String> ss2 = [s1, s2, s3]
echo "ss2: ${ss2.getClass()}"
final Set<String> ss3 = [s1, s2, s3].toSet()
echo "ss3: ${ss3.getClass()}"
final ss4 = [s1, s2, s3] as Set<String>
echo "ss4: ${ss4.getClass()}"
final Set<String> ss5 = [s1, s2, s3] as Set<String>
echo "ss5: ${ss5.getClass()}"// Is there some issue with hashCodes?
// Maps seem to also be affected?
//final m1 = [:]
//m1[d1] = s1
//echo "Map: ${m1.getClass()}"
//echo "${m1.size()}"
//echo "Map keys: ${m1.keySet().getClass()}"
//echo "Map first key: ${m1.keySet().toList()[0].getClass()}"
final d0 = [d1, d2, d3]
echo "d0: ${s0.getClass()}"
final ds1 = [d1, d2, d3].toSet()
echo "ds1: ${ds1.getClass()}"// Fails with:
// com.cloudbees.groovy.cps.impl.CpsCallableInvocation
//final Set ds2 = [d1, d2, d3]
//echo "ds2: ${ds2.getClass()}"
// Fails with:
// hudson.remoting.ProxyException: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '97' with class 'java.lang.Integer' to class 'java.util.Set'
//final Set ds3 = [d1, d2, d3].toSet()
//echo "ds3: ${ds3.getClass()}"
// Fails with:
// hudson.remoting.ProxyException: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '97' with class 'java.lang.Integer' to class 'java.util.Set'
//final ds4 = [d1, d2, d3] as Set
//echo "ds4: ${ds4.getClass()}"
// Fails with:
// hudson.remoting.ProxyException: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '97' with class 'java.lang.Integer' to class 'java.util.Set\'
//final Set ds5 = [d1, d2, d3] as Set
//echo "ds5: ${ds5.getClass()}"
Run once, and approve the hashCode() signature since it is not in default whitelist right now:
Uncomment any of the lines under the cases above, build again, and see failure and confusing types. For example, uncommenting the ds3 example above results in this:
```
[Pipeline] echo
s1: class java.util.ArrayList
[Pipeline] echo
ss1: class java.util.HashSet
[Pipeline] echo
ss2: class java.util.LinkedHashSet
[Pipeline] echo
ss3: class java.util.HashSet
[Pipeline] echo
ss4: class java.util.LinkedHashSet
[Pipeline] echo
ss5: class java.util.LinkedHashSet
[Pipeline] echo
d0: class java.util.ArrayList
[Pipeline] echo
ds1: class java.lang.Integer
[Pipeline] End of Pipeline
hudson.remoting.ProxyException: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '97' with class 'java.lang.Integer' to class 'java.util.Set'
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnSAM(DefaultTypeTransformation.java:405)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnNumber(DefaultTypeTransformation.java:319)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnCollection(DefaultTypeTransformation.java:267)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:219)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.java:603)
at Unknown.Unknown(Unknown)
at ___cps.transform___(Native Method)
at com.cloudbees.groovy.cps.impl.LocalVariableBlock$LocalVariable.set(LocalVariableBlock.java:45)
at com.cloudbees.groovy.cps.impl.AssignmentBlock$ContinuationImpl.assignAndDone(AssignmentBlock.java:70)
at sun.reflect.GeneratedMethodAccessor127.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:60)
at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:109)
at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:77)
at sun.reflect.GeneratedMethodAccessor109.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
at com.cloudbees.groovy.cps.Next.step(Next.java:83)
at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:174)
at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:163)
at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:122)
at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:261)
at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:163)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:19)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:35)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:32)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:32)
at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:174)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:331)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$200(CpsThreadGroup.java:82)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:243)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:231)
at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:64)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
```
---
Originally reported by
mkobit, imported from: Incorrect and confusing compilation of sets with custom types
Raw content of original issue
First, create a pipeline job with this pipeline that contains a class definition:
class Data implements Serializable {
final String string
Data(final String string) {
this.string = Objects.requireNonNull(string)
}boolean equals(final other) {
if (this.is(other)) {
return true
}
if (getClass() != other.class) {
return false
}final Data that = (Data) other
return string == that.string
}int hashCode() {
return string.hashCode()
}
}final d1 = new Data('a')
final d2 = new Data('b')
final d3 = new Data('c')final s1 = 'a'
final s2 = 'b'
final s3 = 'c'final s0 = [s1, s2, s3]
echo "s1: ${s0.getClass()}"
final ss1 = [s1, s2, s3].toSet()
echo "ss1: ${ss1.getClass()}"
final Set<String> ss2 = [s1, s2, s3]
echo "ss2: ${ss2.getClass()}"
final Set<String> ss3 = [s1, s2, s3].toSet()
echo "ss3: ${ss3.getClass()}"
final ss4 = [s1, s2, s3] as Set<String>
echo "ss4: ${ss4.getClass()}"
final Set<String> ss5 = [s1, s2, s3] as Set<String>
echo "ss5: ${ss5.getClass()}"// Is there some issue with hashCodes?
// Maps seem to also be affected?
//final m1 = [:]
//m1[d1] = s1
//echo "Map: ${m1.getClass()}"
//echo "${m1.size()}"
//echo "Map keys: ${m1.keySet().getClass()}"
//echo "Map first key: ${m1.keySet().toList()[0].getClass()}"
final d0 = [d1, d2, d3]
echo "d0: ${s0.getClass()}"
final ds1 = [d1, d2, d3].toSet()
echo "ds1: ${ds1.getClass()}"// Fails with:
// com.cloudbees.groovy.cps.impl.CpsCallableInvocation
//final Set<Data> ds2 = [d1, d2, d3]
//echo "ds2: ${ds2.getClass()}"
// Fails with:
// hudson.remoting.ProxyException: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '97' with class 'java.lang.Integer' to class 'java.util.Set'
//final Set<Data> ds3 = [d1, d2, d3].toSet()
//echo "ds3: ${ds3.getClass()}"
// Fails with:
// hudson.remoting.ProxyException: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '97' with class 'java.lang.Integer' to class 'java.util.Set'
//final ds4 = [d1, d2, d3] as Set<Data>
//echo "ds4: ${ds4.getClass()}"
// Fails with:
// hudson.remoting.ProxyException: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '97' with class 'java.lang.Integer' to class 'java.util.Set\'
//final Set<Data> ds5 = [d1, d2, d3] as Set<Data>
//echo "ds5: ${ds5.getClass()}"
Run once, and approve the hashCode() signature since it is not in default whitelist right now:
Uncomment any of the lines under the cases above, build again, and see failure and confusing types. For example, uncommenting the ds3 example above results in this:
[Pipeline] echo
s1: class java.util.ArrayList
[Pipeline] echo
ss1: class java.util.HashSet
[Pipeline] echo
ss2: class java.util.LinkedHashSet
[Pipeline] echo
ss3: class java.util.HashSet
[Pipeline] echo
ss4: class java.util.LinkedHashSet
[Pipeline] echo
ss5: class java.util.LinkedHashSet
[Pipeline] echo
d0: class java.util.ArrayList
[Pipeline] echo
ds1: class java.lang.Integer
[Pipeline] End of Pipeline
hudson.remoting.ProxyException: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '97' with class 'java.lang.Integer' to class 'java.util.Set'
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnSAM(DefaultTypeTransformation.java:405)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnNumber(DefaultTypeTransformation.java:319)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnCollection(DefaultTypeTransformation.java:267)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:219)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.java:603)
at Unknown.Unknown(Unknown)
at ___cps.transform___(Native Method)
at com.cloudbees.groovy.cps.impl.LocalVariableBlock$LocalVariable.set(LocalVariableBlock.java:45)
at com.cloudbees.groovy.cps.impl.AssignmentBlock$ContinuationImpl.assignAndDone(AssignmentBlock.java:70)
at sun.reflect.GeneratedMethodAccessor127.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:60)
at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:109)
at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixName(FunctionCallBlock.java:77)
at sun.reflect.GeneratedMethodAccessor109.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
at com.cloudbees.groovy.cps.Next.step(Next.java:83)
at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:174)
at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:163)
at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:122)
at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:261)
at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:163)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:19)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:35)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable$1.call(SandboxContinuable.java:32)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox.runInSandbox(GroovySandbox.java:108)
at org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:32)
at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:174)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:331)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$200(CpsThreadGroup.java:82)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:243)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:231)
at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:64)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112)
at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
environment
```
Jenkins version: 2.89.4
Pipeline (workflow-aggregator) => 2.5
Pipeline: API (workflow-api) => 2.26
Pipeline: Basic Steps (workflow-basic-steps) => 2.6
Pipeline: Groovy (workflow-cps) => 2.45
Pipeline: Job (workflow-job) => 2.17
Pipeline: Multibranch (workflow-multibranch) => 2.17
Pipeline: Nodes and Processes (workflow-durable-task-step) => 2.19
Pipeline: SCM Step (workflow-scm-step) => 2.6
Pipeline: Shared Groovy Libraries (workflow-cps-global-lib) => 2.9
Pipeline: Step API (workflow-step-api) => 2.14
Pipeline: Supporting APIs (workflow-support) => 2.18
```
Contributor guide
Research direction
Start with the supplied pipeline reproducer and the workflow-cps-plugin CPS stack trace, especially the failures around typed Set assignments and custom Data values. Determine the intended behavior for the shown set expressions and verify that they no longer produce incorrect types or GroovyCastException failures.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- groovy, java
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100