Skip to content

Commit 19f11da

Browse files
authored
Merge pull request #929 from jglick/LoggingInvoker
False positives from `LoggingInvoker.isInternal`
2 parents 7935cbe + 7947494 commit 19f11da

File tree

6 files changed

+119
-29
lines changed

6 files changed

+119
-29
lines changed

plugin/pom.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
<dependency>
5151
<groupId>io.jenkins.tools.bom</groupId>
5252
<artifactId>bom-2.426.x</artifactId>
53-
<version>2982.vdce2153031a_0</version>
53+
<version>3023.v02a_987a_b_3ff9</version>
5454
<scope>import</scope>
5555
<type>pom</type>
5656
</dependency>
@@ -80,7 +80,6 @@
8080
<dependency>
8181
<groupId>org.jenkins-ci.plugins</groupId>
8282
<artifactId>script-security</artifactId>
83-
<version>1336.vf33a_a_9863911</version>
8483
</dependency>
8584
<dependency>
8685
<groupId>org.jenkins-ci.plugins</groupId>

plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowExecution.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ void recordInternalCall(@NonNull String call) {
483483
}
484484

485485
@NonNull Set<String> getInternalCalls() {
486-
return new TreeSet<>(internalCalls);
486+
return internalCalls;
487487
}
488488

489489
/**
@@ -2105,6 +2105,8 @@ public void autopersist(@NonNull FlowNode n) throws IOException {
21052105
container.add(new Content("nodes/master/pipeline-internal-calls.txt") {
21062106
@Override public void writeTo(OutputStream outputStream) throws IOException {
21072107
PrintWriter pw = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
2108+
pw.println("Internal Jenkins API calls from the last build of any job (plus one example of such a build):");
2109+
Map<String, String> internalCallsToExample = new TreeMap<>();
21082110
for (Job<?, ?> job : Jenkins.get().getAllItems(Job.class)) {
21092111
// TODO as above
21102112
if (job instanceof Queue.FlyweightTask) {
@@ -2114,19 +2116,17 @@ public void autopersist(@NonNull FlowNode n) throws IOException {
21142116
if (owner != null) {
21152117
FlowExecution exec = owner.getOrNull();
21162118
if (exec instanceof CpsFlowExecution) {
2117-
Set<String> calls = ((CpsFlowExecution) exec).getInternalCalls();
2118-
if (!calls.isEmpty()) {
2119-
pw.println("Internal calls for " + run + ":");
2120-
for (String call : calls) {
2121-
pw.println(" " + call);
2122-
}
2123-
pw.println();
2119+
for (var call : ((CpsFlowExecution) exec).getInternalCalls()) {
2120+
internalCallsToExample.putIfAbsent(call, run.toString());
21242121
}
21252122
}
21262123
}
21272124
}
21282125
}
21292126
}
2127+
for (var entry : internalCallsToExample.entrySet()) {
2128+
pw.println(entry.getKey() + " (" + entry.getValue() + ")");
2129+
}
21302130
pw.flush();
21312131
}
21322132
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2024 CloudBees, Inc.
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.jenkinsci.plugins.workflow.cps;
26+
27+
import hudson.ExtensionPoint;
28+
import org.kohsuke.accmod.Restricted;
29+
import org.kohsuke.accmod.restrictions.Beta;
30+
31+
/**
32+
* Allows selected classes to be ignored for purposes of {@link LoggingInvoker#isInternal}.
33+
*/
34+
@Restricted(value = Beta.class)
35+
public interface IgnoredInternalClasses extends ExtensionPoint {
36+
37+
boolean ignore(Class<?> clazz);
38+
39+
}

plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/LoggingInvoker.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
import edu.umd.cs.findbugs.annotations.CheckForNull;
3232
import edu.umd.cs.findbugs.annotations.NonNull;
3333
import groovy.lang.GroovyClassLoader;
34+
import hudson.ExtensionList;
35+
import java.util.Set;
3436
import java.util.function.Supplier;
3537
import jenkins.util.SystemProperties;
38+
import org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper;
3639

3740
/**
3841
* Captures CPS-transformed events.
@@ -64,8 +67,17 @@ private void record(String call) {
6467
execution.recordInternalCall(call);
6568
}
6669

70+
private static final Set<Class<?>> IGNORED = Set.of(
71+
Safepoint.class,
72+
CpsClosure2.class,
73+
EnvActionImpl.class,
74+
RunWrapper.class);
75+
6776
private static boolean isInternal(Class<?> clazz) {
68-
if (clazz == Safepoint.class || clazz == CpsClosure2.class) {
77+
if (IGNORED.contains(clazz)) {
78+
return false;
79+
}
80+
if (ExtensionList.lookup(IgnoredInternalClasses.class).stream().anyMatch(iic -> iic.ignore(clazz))) {
6981
return false;
7082
}
7183
if (clazz.getClassLoader() instanceof GroovyClassLoader) { // similar to GroovyClassLoaderWhitelist
@@ -75,7 +87,7 @@ private static boolean isInternal(Class<?> clazz) {
7587
// (simply checking whether the class loader can “see”, say, jenkins/model/Jenkins.class
7688
// would falsely mark third-party libs bundled in Jenkins plugins)
7789
String name = clazz.getName();
78-
if (name.startsWith("com.cloudbees.groovy.cps.")) {
90+
if (name.startsWith("com.cloudbees.groovy.cps.") || name.startsWith("org.jenkinsci.plugins.workflow.cps.persistence.IteratorHack$")) {
7991
// Likely synthetic call, as to CpsDefaultGroovyMethods.
8092
return false;
8193
}

plugin/src/test/java/org/jenkinsci/plugins/workflow/cps/CpsFlowExecutionTest.java

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424

2525
package org.jenkinsci.plugins.workflow.cps;
2626

27-
import static org.hamcrest.MatcherAssert.assertThat;
28-
import static org.hamcrest.Matchers.containsString;
27+
import com.cloudbees.jenkins.support.api.Container;
28+
import com.cloudbees.jenkins.support.api.Content;
2929
import static org.hamcrest.Matchers.equalTo;
3030
import static org.hamcrest.Matchers.hasItem;
3131
import static org.hamcrest.Matchers.instanceOf;
@@ -49,6 +49,7 @@
4949
import hudson.model.Item;
5050
import hudson.model.Result;
5151
import hudson.model.TaskListener;
52+
import java.io.ByteArrayOutputStream;
5253
import java.io.File;
5354
import java.io.IOException;
5455
import java.io.Serializable;
@@ -72,10 +73,14 @@
7273
import java.util.stream.Collectors;
7374
import jenkins.model.Jenkins;
7475
import org.apache.commons.io.FileUtils;
75-
import org.hamcrest.Matchers;
76+
import static org.hamcrest.MatcherAssert.assertThat;
77+
import static org.hamcrest.Matchers.arrayContaining;
7678
import static org.hamcrest.Matchers.contains;
79+
import static org.hamcrest.Matchers.containsInAnyOrder;
7780
import static org.hamcrest.Matchers.containsInRelativeOrder;
81+
import static org.hamcrest.Matchers.containsString;
7882
import static org.hamcrest.Matchers.empty;
83+
import static org.hamcrest.Matchers.not;
7984
import org.htmlunit.ElementNotFoundException;
8085
import org.htmlunit.FailingHttpStatusCodeException;
8186
import org.htmlunit.HttpMethod;
@@ -493,7 +498,7 @@ private static final class Execution extends StepExecution {
493498
Thread.sleep(100); // apparently a race condition between CpsVmExecutorService.tearDown and WorkflowRun.finish
494499
}
495500
// TODO https://github.com/jenkinsci/workflow-cps-plugin/pull/570#issuecomment-1192679404 message can be duplicated
496-
assertThat(logger.getRecords(), Matchers.not(Matchers.empty()));
501+
assertThat(logger.getRecords(), not(empty()));
497502
assertEquals(CpsFlowExecution.TimingKind.values().length, ((CpsFlowExecution) b.getExecution()).liveTimings.keySet().size());
498503
});
499504
}
@@ -511,16 +516,43 @@ private static final class Execution extends StepExecution {
511516
assertThat(new XmlFile(new File(b.getRootDir(), "build.xml")).asString(), containsString(
512517
"<string>org.jenkinsci.plugins.workflow.job.WorkflowRun.description</string>"));
513518
CpsFlowExecution exec = (CpsFlowExecution) b.getExecution();
514-
assertThat(exec.getInternalCalls(), contains(
515-
"org.jenkinsci.plugins.workflow.job.WorkflowRun.description",
516-
"org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper.rawBuild"));
519+
assertThat(exec.getInternalCalls(), containsInAnyOrder(
520+
"org.jenkinsci.plugins.workflow.job.WorkflowRun.description"));
517521
SemaphoreStep.success("wait/1", null);
518522
r.assertBuildStatusSuccess(r.waitForCompletion(b));
519-
assertThat(exec.getInternalCalls(), contains(
523+
assertThat(exec.getInternalCalls(), containsInAnyOrder(
520524
"hudson.model.Hudson.systemMessage",
521525
"jenkins.model.Jenkins.instance",
522-
"org.jenkinsci.plugins.workflow.job.WorkflowRun.description",
523-
"org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper.rawBuild"));
526+
"org.jenkinsci.plugins.workflow.job.WorkflowRun.description"));
527+
});
528+
}
529+
530+
@Test public void internalCallsUniquified() throws Throwable {
531+
sessions.then(r -> {
532+
var p1 = r.jenkins.createProject(WorkflowJob.class, "project-1");
533+
p1.setDefinition(new CpsFlowDefinition("currentBuild.rawBuild.description = 'XXX'; Jenkins.instance.systemMessage = 'XXX'", false));
534+
r.buildAndAssertSuccess(p1);
535+
var p2 = r.jenkins.createProject(WorkflowJob.class, "project-2");
536+
p2.setDefinition(new CpsFlowDefinition("Jenkins.instance.systemMessage = Jenkins.VERSION", false));
537+
r.buildAndAssertSuccess(p2);
538+
var p3 = r.jenkins.createProject(WorkflowJob.class, "project-3");
539+
p3.setDefinition(new CpsFlowDefinition("echo 'clean'", false));
540+
r.buildAndAssertSuccess(p3);
541+
var baos = new ByteArrayOutputStream();
542+
new CpsFlowExecution.PipelineInternalCalls().addContents(new Container() {
543+
@Override public void add(Content content) {
544+
try {
545+
content.writeTo(baos);
546+
} catch (IOException x) {
547+
assert false : x;
548+
}
549+
}
550+
});
551+
assertThat(baos.toString().replaceFirst(".+\r?\n", "").split("\r?\n"), arrayContaining(
552+
"hudson.model.Hudson.systemMessage (project-1 #1)",
553+
"jenkins.model.Jenkins.VERSION (project-2 #1)",
554+
"jenkins.model.Jenkins.instance (project-1 #1)",
555+
"org.jenkinsci.plugins.workflow.job.WorkflowRun.description (project-1 #1)"));
524556
});
525557
}
526558

plugin/src/test/java/org/jenkinsci/plugins/workflow/cps/LoggingInvokerTest.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,25 @@
2424

2525
package org.jenkinsci.plugins.workflow.cps;
2626

27-
import java.util.Arrays;
28-
import java.util.TreeSet;
2927
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
3028
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
31-
import org.junit.Test;
32-
import static org.junit.Assert.*;
3329
import org.junit.ClassRule;
30+
import org.junit.Test;
31+
import org.jvnet.hudson.test.BuildWatcher;
3432
import org.jvnet.hudson.test.JenkinsRule;
33+
import static org.hamcrest.MatcherAssert.assertThat;
34+
import static org.hamcrest.Matchers.containsInAnyOrder;
3535

3636
public class LoggingInvokerTest {
3737

3838
@ClassRule public static JenkinsRule r = new JenkinsRule();
39+
@ClassRule public static final BuildWatcher bw = new BuildWatcher();
3940

4041
@Test public void smokes() throws Exception {
4142
assertInternalCalls("currentBuild.rawBuild.description = 'XXX'; Jenkins.instance.systemMessage = 'XXX'", false,
4243
"hudson.model.Hudson.systemMessage",
4344
"jenkins.model.Jenkins.instance",
44-
"org.jenkinsci.plugins.workflow.job.WorkflowRun.description",
45-
"org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper.rawBuild");
45+
"org.jenkinsci.plugins.workflow.job.WorkflowRun.description");
4646
}
4747

4848
@Test public void groovyCalls() throws Exception {
@@ -60,12 +60,20 @@ public class LoggingInvokerTest {
6060
assertInternalCalls("class X extends hudson.lifecycle.Lifecycle {void onReady() {super.onReady()}}; new X().getHudsonWar(); new X().onReady()", false);
6161
}
6262

63+
@Test public void envAction() throws Exception {
64+
assertInternalCalls("env.XXX = 'yyy'; echo XXX", true);
65+
}
66+
67+
@Test public void runWrapper() throws Exception {
68+
assertInternalCalls("echo currentBuild.displayName", true);
69+
}
70+
6371
private void assertInternalCalls(String script, boolean sandbox, String... calls) throws Exception {
6472
WorkflowJob p = r.createProject(WorkflowJob.class);
6573
p.setDefinition(new CpsFlowDefinition(script, sandbox));
6674
WorkflowRun b = r.buildAndAssertSuccess(p);
6775
CpsFlowExecution exec = (CpsFlowExecution) b.getExecution();
68-
assertEquals(new TreeSet<>(Arrays.asList(calls)).toString(), exec.getInternalCalls().toString());
76+
assertThat(exec.getInternalCalls(), containsInAnyOrder(calls));
6977
}
7078

7179
}

0 commit comments

Comments
 (0)