Skip to content

Commit 0f52791

Browse files
committed
Fix ClassCastException when SCMSource is not GitHubSCMSource
The getKey() method in IssueCommentTrigger, LabelAddedTrigger, and PullRequestReviewTrigger performs an unsafe cast of the result of SCMSource.SourceByItem.findSource() to GitHubSCMSource. This throws a ClassCastException when the source is a NullSCMSource (or any other non-GitHub SCM source). This happens during the stop()/start() cycle triggered by MultiBranchProject.processHeadUpdate() when a push event causes BranchProjectFactory.decorate() to call stopTriggers(). The exception in stop() prevents the trigger from being re-registered in start(), silently removing the job from the DescriptorImpl.jobs map. Subsequent webhook events for that PR are then silently ignored because the key lookup returns an empty set. The fix adds instanceof checks before casting in getKey(), returning null when the source is not a GitHubSCMSource, and guards callers in start() and stop() against null keys. A warning is logged to help diagnose similar issues in the future.
1 parent ff6ad3d commit 0f52791

3 files changed

Lines changed: 72 additions & 36 deletions

File tree

src/main/java/org/jenkinsci/plugins/pipeline/github/trigger/IssueCommentTrigger.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.jenkinsci.plugins.pipeline.github.trigger;
22

33
import edu.umd.cs.findbugs.annotations.NonNull;
4-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
54
import hudson.Extension;
65
import hudson.model.Item;
76
import hudson.triggers.Trigger;
@@ -46,29 +45,42 @@ public void start(final WorkflowJob project, final boolean newInstance) {
4645
super.start(project, newInstance);
4746
// we only care about pull requests
4847
if (SCMHead.HeadByItem.findHead(project) instanceof PullRequestSCMHead) {
49-
DescriptorImpl.jobs
50-
.computeIfAbsent(getKey(project), key -> new HashSet<>())
51-
.add(project);
48+
final String key = getKey(project);
49+
if (key != null) {
50+
DescriptorImpl.jobs
51+
.computeIfAbsent(key, k -> new HashSet<>())
52+
.add(project);
53+
}
5254
}
5355
}
5456

5557
@Override
5658
public void stop() {
5759
if (SCMHead.HeadByItem.findHead(job) instanceof PullRequestSCMHead) {
58-
DescriptorImpl.jobs.getOrDefault(getKey(job), Collections.emptySet())
59-
.remove(job);
60+
final String key = getKey(job);
61+
if (key != null) {
62+
DescriptorImpl.jobs.getOrDefault(key, Collections.emptySet())
63+
.remove(job);
64+
}
6065
}
6166
}
6267

63-
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
6468
private String getKey(final WorkflowJob project) {
65-
final GitHubSCMSource scmSource = (GitHubSCMSource) SCMSource.SourceByItem.findSource(project);
66-
final PullRequestSCMHead scmHead = (PullRequestSCMHead) SCMHead.HeadByItem.findHead(project);
69+
final SCMSource scmSource = SCMSource.SourceByItem.findSource(project);
70+
if (!(scmSource instanceof GitHubSCMSource)) {
71+
LOG.warn("Job: {} has a non-GitHub SCM source: {}, skipping trigger registration",
72+
project.getFullName(), scmSource != null ? scmSource.getClass().getName() : "null");
73+
return null;
74+
}
75+
final SCMHead scmHead = SCMHead.HeadByItem.findHead(project);
76+
if (!(scmHead instanceof PullRequestSCMHead)) {
77+
return null;
78+
}
6779

6880
return String.format("%s/%s/%d",
69-
scmSource.getRepoOwner(),
70-
scmSource.getRepository(),
71-
scmHead.getNumber()).toLowerCase();
81+
((GitHubSCMSource) scmSource).getRepoOwner(),
82+
((GitHubSCMSource) scmSource).getRepository(),
83+
((PullRequestSCMHead) scmHead).getNumber()).toLowerCase();
7284
}
7385

7486
public String getCommentPattern() {

src/main/java/org/jenkinsci/plugins/pipeline/github/trigger/LabelAddedTrigger.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.jenkinsci.plugins.pipeline.github.trigger;
22

33
import edu.umd.cs.findbugs.annotations.NonNull;
4-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
54
import hudson.Extension;
65
import hudson.model.Item;
76
import hudson.triggers.Trigger;
@@ -46,29 +45,42 @@ public void start(final WorkflowJob project, final boolean newInstance) {
4645
super.start(project, newInstance);
4746
// we only care about pull requests
4847
if (SCMHead.HeadByItem.findHead(project) instanceof PullRequestSCMHead) {
49-
DescriptorImpl.jobs
50-
.computeIfAbsent(getKey(project), key -> new HashSet<>())
51-
.add(project);
48+
final String key = getKey(project);
49+
if (key != null) {
50+
DescriptorImpl.jobs
51+
.computeIfAbsent(key, k -> new HashSet<>())
52+
.add(project);
53+
}
5254
}
5355
}
5456

5557
@Override
5658
public void stop() {
5759
if (SCMHead.HeadByItem.findHead(job) instanceof PullRequestSCMHead) {
58-
DescriptorImpl.jobs.getOrDefault(getKey(job), Collections.emptySet())
59-
.remove(job);
60+
final String key = getKey(job);
61+
if (key != null) {
62+
DescriptorImpl.jobs.getOrDefault(key, Collections.emptySet())
63+
.remove(job);
64+
}
6065
}
6166
}
6267

63-
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
6468
private String getKey(final WorkflowJob project) {
65-
final GitHubSCMSource scmSource = (GitHubSCMSource) SCMSource.SourceByItem.findSource(project);
66-
final PullRequestSCMHead scmHead = (PullRequestSCMHead) SCMHead.HeadByItem.findHead(project);
69+
final SCMSource scmSource = SCMSource.SourceByItem.findSource(project);
70+
if (!(scmSource instanceof GitHubSCMSource)) {
71+
LOG.warn("Job: {} has a non-GitHub SCM source: {}, skipping trigger registration",
72+
project.getFullName(), scmSource != null ? scmSource.getClass().getName() : "null");
73+
return null;
74+
}
75+
final SCMHead scmHead = SCMHead.HeadByItem.findHead(project);
76+
if (!(scmHead instanceof PullRequestSCMHead)) {
77+
return null;
78+
}
6779

6880
return String.format("%s/%s/%d",
69-
scmSource.getRepoOwner(),
70-
scmSource.getRepository(),
71-
scmHead.getNumber()).toLowerCase();
81+
((GitHubSCMSource) scmSource).getRepoOwner(),
82+
((GitHubSCMSource) scmSource).getRepository(),
83+
((PullRequestSCMHead) scmHead).getNumber()).toLowerCase();
7284
}
7385

7486
public String getLabelTrigger() {

src/main/java/org/jenkinsci/plugins/pipeline/github/trigger/PullRequestReviewTrigger.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.jenkinsci.plugins.pipeline.github.trigger;
22

33
import edu.umd.cs.findbugs.annotations.NonNull;
4-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
54
import hudson.Extension;
65
import hudson.model.Item;
76
import hudson.triggers.Trigger;
@@ -56,29 +55,42 @@ public void start(final WorkflowJob project, final boolean newInstance) {
5655
super.start(project, newInstance);
5756
// we only care about pull requests
5857
if (SCMHead.HeadByItem.findHead(project) instanceof PullRequestSCMHead) {
59-
DescriptorImpl.jobs
60-
.computeIfAbsent(getKey(project), key -> new HashSet<>())
61-
.add(project);
58+
final String key = getKey(project);
59+
if (key != null) {
60+
DescriptorImpl.jobs
61+
.computeIfAbsent(key, k -> new HashSet<>())
62+
.add(project);
63+
}
6264
}
6365
}
6466

6567
@Override
6668
public void stop() {
6769
if (SCMHead.HeadByItem.findHead(job) instanceof PullRequestSCMHead) {
68-
DescriptorImpl.jobs.getOrDefault(getKey(job), Collections.emptySet())
69-
.remove(job);
70+
final String key = getKey(job);
71+
if (key != null) {
72+
DescriptorImpl.jobs.getOrDefault(key, Collections.emptySet())
73+
.remove(job);
74+
}
7075
}
7176
}
7277

73-
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
7478
private String getKey(final WorkflowJob project) {
75-
final GitHubSCMSource scmSource = (GitHubSCMSource) SCMSource.SourceByItem.findSource(project);
76-
final PullRequestSCMHead scmHead = (PullRequestSCMHead) SCMHead.HeadByItem.findHead(project);
79+
final SCMSource scmSource = SCMSource.SourceByItem.findSource(project);
80+
if (!(scmSource instanceof GitHubSCMSource)) {
81+
LOG.warn("Job: {} has a non-GitHub SCM source: {}, skipping trigger registration",
82+
project.getFullName(), scmSource != null ? scmSource.getClass().getName() : "null");
83+
return null;
84+
}
85+
final SCMHead scmHead = SCMHead.HeadByItem.findHead(project);
86+
if (!(scmHead instanceof PullRequestSCMHead)) {
87+
return null;
88+
}
7789

7890
return String.format("%s/%s/%d",
79-
scmSource.getRepoOwner(),
80-
scmSource.getRepository(),
81-
scmHead.getNumber()).toLowerCase();
91+
((GitHubSCMSource) scmSource).getRepoOwner(),
92+
((GitHubSCMSource) scmSource).getRepository(),
93+
((PullRequestSCMHead) scmHead).getNumber()).toLowerCase();
8294
}
8395

8496
boolean matches(final String reviewState) {

0 commit comments

Comments
 (0)