Skip to content

Conversation

aschackmull
Copy link
Contributor

@aschackmull aschackmull commented Aug 27, 2025

This adds a shared implementation of the SuccessorType class that's used as edge labels in our CFGs. I've also updated all languages and all shared libraries to use this new shared code.

Commit-by-commit review is encouraged.

A few notes:

  • SuccessorType is now a shared non-parameterized implementation. This means that it's not using language-specific details like exception type or goto-labels.
  • Splitting in C# on finally blocks use SuccessorType, so the change has a slight semantic impact here. Initially I tried a version that bunched all of the jump successors together, but that gave a single new FP in C# nullness, so I choose to have a more detailed distinction between different jump successors.
  • I've kept the successorTypeIsCondition predicate in a few places in order to not disrupt basic block boundaries too much. Streamlining this across languages is left as a potential follow-up consideration.

@@ -882,7 +881,7 @@
/** Gets the type of the exception being thrown. */
ExceptionClass getExceptionClass() { result = ec }

override ExceptionSuccessor getAMatchingSuccessorType() { result.getExceptionClass() = ec }
override ExceptionSuccessor getAMatchingSuccessorType() { any() }

Check warning

Code scanning / CodeQL

Override with unmentioned parameter Warning

Override predicate doesn't mention
result
. Maybe mention it in a 'exists(result)'?
@@ -431,7 +430,7 @@
this = TNestedCompletion(_, TRaiseCompletion(), _)
}

override RaiseSuccessor getAMatchingSuccessorType() { any() }
override ExceptionSuccessor getAMatchingSuccessorType() { any() }

Check warning

Code scanning / CodeQL

Override with unmentioned parameter Warning

Override predicate doesn't mention
result
. Maybe mention it in a 'exists(result)'?
@github-actions github-actions bot added Rust Pull requests that update Rust code Java Swift Actions Analysis of GitHub Actions labels Aug 28, 2025
@@ -468,7 +467,7 @@

FallthroughCompletion() { this = TFallthroughCompletion(dest) }

override FallthroughSuccessor getAMatchingSuccessorType() { any() }
override DirectSuccessor getAMatchingSuccessorType() { any() }

Check warning

Code scanning / CodeQL

Override with unmentioned parameter Warning

Override predicate doesn't mention
result
. Maybe mention it in a 'exists(result)'?
@@ -859,7 +858,7 @@
/** Gets the label of the `goto` completion. */
string getLabel() { result = label }

override GotoSuccessor getAMatchingSuccessorType() { result.getLabel() = label }
override GotoSuccessor getAMatchingSuccessorType() { any() }

Check warning

Code scanning / CodeQL

Override with unmentioned parameter Warning

Override predicate doesn't mention
result
. Maybe mention it in a 'exists(result)'?
@@ -377,7 +376,7 @@
this = TNestedCompletion(_, TNextCompletion(), _)
}

override NextSuccessor getAMatchingSuccessorType() { any() }
override ContinueSuccessor getAMatchingSuccessorType() { any() }

Check warning

Code scanning / CodeQL

Override with unmentioned parameter Warning

Override predicate doesn't mention
result
. Maybe mention it in a 'exists(result)'?
@aschackmull aschackmull marked this pull request as ready for review August 29, 2025 10:59
@aschackmull aschackmull requested review from a team as code owners August 29, 2025 10:59
@Copilot Copilot AI review requested due to automatic review settings August 29, 2025 10:59
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds a shared implementation of the SuccessorType class used as edge labels in control flow graphs (CFGs). The implementation centralizes successor type definitions in the shared/controlflow module and updates all languages to use this shared code instead of maintaining language-specific implementations.

Key changes include:

  • Adding a comprehensive shared SuccessorType implementation with a well-defined hierarchy
  • Removing language-specific successor type implementations
  • Updating all languages to import and use the shared successor types

Reviewed Changes

Copilot reviewed 64 out of 68 changed files in this pull request and generated no comments.

Show a summary per file
File Description
shared/controlflow/codeql/controlflow/SuccessorType.qll New shared successor type implementation with comprehensive hierarchy
shared/controlflow/codeql/controlflow/Guards.qll Updated to import shared successor types
shared/controlflow/codeql/controlflow/Cfg.qll Updated to use shared successor types and removed language-specific definitions
shared/controlflow/codeql/controlflow/BasicBlock.qll Updated to import shared successor types
swift/ql/lib/codeql/swift/security/PathInjectionExtensions.qll Updated to use shared BooleanSuccessor type
swift/ql/lib/codeql/swift/controlflow/* Removed Swift-specific successor types and updated to use shared implementation
rust/ql/lib/codeql/rust/controlflow/* Removed Rust-specific successor types and updated to use shared implementation
ruby/ql/lib/codeql/ruby/controlflow/* Removed Ruby-specific successor types and updated to use shared implementation
java/ql/lib/semmle/code/java/controlflow/SuccessorType.qll Removed Java-specific successor type implementation
csharp/ql/test/library-tests/goto/Goto1.expected Updated test expectations for goto successor label changes
ruby/ql/test/library-tests/controlflow/graph/*.expected Updated test expectations for successor type label changes
Comments suppressed due to low confidence (1)

swift/ql/lib/codeql/swift/controlflow/internal/Completion.qll:1

  • The change from MatchSuccessor to MatchingSuccessor suggests a naming inconsistency between the old Swift implementation and the new shared implementation. Ensure all references are updated consistently.
/**

@aschackmull aschackmull added the no-change-note-required This PR does not need a change note label Sep 1, 2025
private import codeql.util.Boolean

/*
* SuccessorType
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add this as ```text QL doc to the class.

*/
class SuccessorType extends TSuccessorType {
/** Gets a textual representation of this successor type. */
abstract string toString();
Copy link
Contributor

Choose a reason for hiding this comment

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

We should not be exposing abstract classes or predicates. Instead, apply the usual private Impl + final external alias trick.

*
* has a control flow graph containing Boolean successors:
*
* ```
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add a text suffix to prevent VS code from attempting to syntax highlighting this. Same for other examples.

}

/** A Java `yield` control flow successor. */
class JavaYieldSuccessor extends JumpSuccessor, TJavaYieldSuccessor {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be weird for Java to reuse Break instead for this (it looks similar to how one can break with a value in Ruby)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No that's probably sensible. I'll delete JavaYieldSuccessor.

@@ -123,16 +122,19 @@ module EnsureSplitting {
* the `ensure` block must end with a `return` as well (provided that
* the `ensure` block executes normally).
*/
class EnsureSplitType extends SuccessorType {
class EnsureSplitType instanceof SuccessorType {
Copy link
Contributor

Choose a reason for hiding this comment

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

Revert this when SuccessorType is no longer abstract.

EnsureSplitType() { not this instanceof ConditionalSuccessor }

/** Gets a textual representation of this successor type. */
string toString() { result = super.toString() }
Copy link
Contributor

Choose a reason for hiding this comment

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

revert

| CompileTimeOperators.cs:37:13:37:40 | [finally: goto(End)] call to method WriteLine | CompileTimeOperators.cs:28:10:28:10 | M |
| CompileTimeOperators.cs:37:13:37:41 | [finally: goto(End)] ...; | CompileTimeOperators.cs:28:10:28:10 | M |
| CompileTimeOperators.cs:37:31:37:39 | [finally: goto(End)] "Finally" | CompileTimeOperators.cs:28:10:28:10 | M |
| CompileTimeOperators.cs:36:9:38:9 | [finally: goto] {...} | CompileTimeOperators.cs:28:10:28:10 | M |
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, when we no longer store the target of the goto, how can we make the correct jump after finally?

Comment on lines -2192 to +2168
| Finally.cs:114:19:114:35 | [finally: exception(Exception)] ... == ... | Finally.cs:114:17:114:36 | [false, finally: exception(Exception)] !... | true |
| Finally.cs:114:19:114:35 | [finally: exception(Exception)] ... == ... | Finally.cs:114:17:114:36 | [true, finally: exception(Exception)] !... | false |
| Finally.cs:114:19:114:35 | [finally: exception(NullReferenceException)] ... == ... | Finally.cs:114:17:114:36 | [false, finally: exception(NullReferenceException)] !... | true |
| Finally.cs:114:19:114:35 | [finally: exception(NullReferenceException)] ... == ... | Finally.cs:114:17:114:36 | [true, finally: exception(NullReferenceException)] !... | false |
| Finally.cs:114:19:114:35 | [finally: exception(OutOfMemoryException)] ... == ... | Finally.cs:114:17:114:36 | [false, finally: exception(OutOfMemoryException)] !... | true |
| Finally.cs:114:19:114:35 | [finally: exception(OutOfMemoryException)] ... == ... | Finally.cs:114:17:114:36 | [true, finally: exception(OutOfMemoryException)] !... | false |
| Finally.cs:114:19:114:35 | [finally: exception] ... == ... | Finally.cs:114:17:114:36 | [false, finally: exception] !... | true |
Copy link
Contributor

Choose a reason for hiding this comment

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

Same: When we don't record the exception type, we may not be able to get control flow right when a finally is inside a try.

@@ -339,3 +339,9 @@ class RetrySuccessor extends JumpSuccessor, TRetrySuccessor {
class JavaYieldSuccessor extends JumpSuccessor, TJavaYieldSuccessor {
override string toString() { result = "yield" }
}

/** Holds if `t` is an abnormal exit type out of a CFG scope. */
predicate isAbnormalExitType(SuccessorType t) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This file is reexported elsewhere, so not nice to have a top-level predicate reexported.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about I'll make it a member on AbruptSuccessor instead, then?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Actions Analysis of GitHub Actions C# C++ Java JS no-change-note-required This PR does not need a change note Python Ruby Rust Pull requests that update Rust code Swift
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants