Skip to content

Commit ca8a1e7

Browse files
committed
Prevent infinite loop when self-referencing Cascade parameter
1 parent 534deb9 commit ca8a1e7

4 files changed

Lines changed: 170 additions & 1 deletion

File tree

‎CHANGES.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Bump source-map-js from 1.2.1 to 1.2.2
1616
- Bump webpack from 5.109.2 to 5.111.1
1717
- Bump webpack-cli from 7.2.2 to 7.2.3
18+
- Prevent infinite loop when a Cascade Reference Parameter references itself #1017 (thanks @B-Sp67)
1819

1920
## Version 2.8.10 (2026/08/25)
2021

‎src/main/js/UnoChoice.es6‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ var UnoChoice = UnoChoice || (jQuery3 => {
371371
if (this.referencesMe(other)) {
372372
console.log(`[${this.paramName}] - CascadeParameter#update - Updating ${other.getParameterName()} from ${this.getParameterName()}`);
373373
window.requestIdleCallback(async () => {
374-
await other.update(false);
374+
await other.update(other.getParameterName() === this.getParameterName());
375375
})
376376
}
377377
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2014-2026 Ioannis Moutsatsos, Bruno P. Kinoshita
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package org.biouno.unochoice.github_1017;
26+
27+
import hudson.model.FreeStyleProject;
28+
import hudson.model.ParametersDefinitionProperty;
29+
import org.biouno.unochoice.BaseUiTest;
30+
import org.biouno.unochoice.CascadeChoiceParameter;
31+
import org.biouno.unochoice.model.GroovyScript;
32+
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript;
33+
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
34+
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage;
35+
import org.junit.jupiter.api.Test;
36+
import org.jvnet.hudson.test.Issue;
37+
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
38+
import org.openqa.selenium.By;
39+
import org.openqa.selenium.support.ui.WebDriverWait;
40+
41+
import java.time.Duration;
42+
import java.util.UUID;
43+
44+
import static org.junit.jupiter.api.Assertions.assertEquals;
45+
import static org.junit.jupiter.api.Assertions.assertTrue;
46+
47+
/**
48+
* Test that a reference parameter, when referencing itself, does not
49+
* result in an infinite loop.
50+
*/
51+
@Issue("GH-1017")
52+
@WithJenkins
53+
public class TestReferenceParameterSelfReference extends BaseUiTest {
54+
55+
private static final String PARAMETER_NAME = "SKIP";
56+
57+
private static final String SCRIPT = """
58+
Map<String, String> aRtn = [
59+
'DRYRUN': 'Perform dryrun, i.e. no commits, packaging, deployment and JIRA update',
60+
'MAIL': 'Skip "Sending notification mails"' + (SKIP.toString().find(/^(.*,)*MAIL(,.+)*$/) ? ':selected' : ''),
61+
'DEPLOY':'Skip "Deploy build package"',
62+
'PACKAGE':'Skip "Build update package"',
63+
'DEPLOYPKG':'Skip "Deploy update package"',
64+
'JIRA':'Skip "Update JIRA"',
65+
'CLEAN':'Skip "Cleanup"' + (SKIP.toString().find(/^(.*,)*CLEAN(,.+)*$/) ? ':selected' : ''),
66+
'GET':'Skip "Get sources"' + (SKIP.toString().find(/^(.*,)*GET(,.+)*$/) ? ':selected' : ''),
67+
'GENDIFF':'Skip "Evaluate differences"' + (SKIP.toString().find(/^(.*,)*GENDIFF(,.+)*$/) ? ':selected' : ''),
68+
'ADJPROJ':'Skip "Adjust profilefiles"' + (SKIP.toString().find(/^(.*,)*ADJPROJ(,.+)*$/) ? ':selected' : ''),
69+
'STARTLABEL':'Skip "Prepare startlabel"' + (SKIP.toString().find(/^(.*,)*STARTLABEL(,.+)*$/) ? ':selected' : ''),
70+
'COMPILE':'Skip "First compile"' + (SKIP.toString().find(/^(.*,)*COMPILE(,.+)*$/) ? ':selected' : ''),
71+
'STORE':'Skip "Storing"' + (SKIP.toString().find(/^(.*,)*STORE(,.+)*$/) ? ':selected' : ''),
72+
'ENDLABEL':'Skip "Prepare endlabel"' + (SKIP.toString().find(/^(.*,)*ENDLABEL(,.+)*$/) ? ':selected' : ''),
73+
'APPDIFF':'Skip "Apply differences"' + (SKIP.toString().find(/^(.*,)*APPDIFF(,.+)*$/) ? ':selected' : ''),
74+
'ADJPROJ2':'Skip "Adjust profilefiles 2nd time"' + (SKIP.toString().find(/^(.*,)*ADJPROJ2(,.+)*$/) ? ':selected' : ''),
75+
'COMPILE2':'Skip "Second compile"' + (SKIP.toString().find(/^(.*,)*COMPILE2(,.+)*$/) ? ':selected' : ''),
76+
'DELTA':'Skip "Generate delta"' + (SKIP.toString().find(/^(.*,)*DELTA(,.+)*$/) ? ':selected' : ''),
77+
'OBFUSCATE':'Skip "Obfuscate assemblies"' + (SKIP.toString().find(/^(.*,)*OBFUSCATE(,.+)*$/) ? ':selected' : ''),
78+
'SIGN':'Skip "Sign binaries"' + (SKIP.toString().find(/^(.*,)*SIGN(,.+)*$/) ? ':selected' : ''),
79+
'ARCHIVE':'Skip "Archiving"' + (SKIP.toString().find(/^(.*,)*ARCHIVE(,.+)*$/) ? ':selected' : '')
80+
]
81+
82+
if (SKIP.toString().find(/^(.*,)*DRYRUN(,.+)*$/)) {
83+
aRtn['DRYRUN'] += (aRtn['DRYRUN'].find(/^.*:selected.*$/) ? '' : ':selected')
84+
}
85+
if (aRtn['DRYRUN'].find(/^.*:selected.*$/)) { // DRYRUN selected, disable DEPLOYMENT and JIRA
86+
aRtn.remove('DEPLOY')
87+
aRtn.remove('PACKAGE')
88+
aRtn.remove('DEPLOYPKG')
89+
aRtn.remove('JIRA')
90+
}
91+
92+
return aRtn
93+
""";
94+
95+
@Test
96+
void testSelfReferencingParameterRendersWithoutInfiniteLoop() throws Exception {
97+
98+
ScriptApproval.get().preapprove(SCRIPT, GroovyLanguage.get());
99+
100+
GroovyScript groovyScript = new GroovyScript(
101+
new SecureGroovyScript(SCRIPT, false, null),
102+
new SecureGroovyScript("return ''", false, null)
103+
);
104+
105+
// Recursion here, as the PARAMETER_NAME is used for name and for reference!
106+
CascadeChoiceParameter parameter =
107+
new CascadeChoiceParameter(
108+
PARAMETER_NAME,
109+
"Self-referencing parameter",
110+
UUID.randomUUID().toString(),
111+
groovyScript,
112+
"PT_CHECKBOX",
113+
PARAMETER_NAME,
114+
false,
115+
0
116+
);
117+
118+
FreeStyleProject project =
119+
j.createFreeStyleProject("self-reference");
120+
121+
project.addProperty(
122+
new ParametersDefinitionProperty(parameter)
123+
);
124+
125+
driver.manage().timeouts()
126+
.pageLoadTimeout(Duration.ofSeconds(20));
127+
128+
driver.get(
129+
j.getURL().toExternalForm()
130+
+ project.getUrl()
131+
+ "build?delay=0sec"
132+
);
133+
134+
waitForCheckboxes(PARAMETER_NAME, 21);
135+
assertEquals(21, findCheckboxes(PARAMETER_NAME).size(), "The self-referencing checkbox parameter should render its choices");
136+
}
137+
138+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2014-2026 Ioannis Moutsatsos, Bruno P. Kinoshita
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
/**
26+
* Tests for parameter recursion. See GitHub issue 1017.
27+
*
28+
* @since 2.8.11.
29+
*/
30+
package org.biouno.unochoice.github_1017;

0 commit comments

Comments
 (0)