Skip to content

Unified: Add CFG support for loop guards and defer - #22815

Open
aschackmull wants to merge 3 commits into
github:mainfrom
aschackmull:unified/cfg-updates
Open

aschackmull wants to merge 3 commits into
github:mainfrom
aschackmull:unified/cfg-updates

Conversation

@aschackmull

Copy link
Copy Markdown
Contributor

Loop guard support is added in the shared CFG lib.
Defer statements are handled as try-finally by nesting subsequent statements under the defer and representing them as the try body.

Copilot AI balanced review requested due to automatic review settings October 13, 2026 13:44
@aschackmull
aschackmull requested a review from a team as a code owner October 13, 2026 13:44
@aschackmull aschackmull added the no-change-note-required This PR does not need a change note label Oct 13, 2026
@aschackmull
aschackmull requested a review from a team as a code owner October 13, 2026 13:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The test encodes incorrect fallthrough control flow, and abrupt defer completion remains untested.

2 open findings
What changed in this PR

Adds Unified CFG modeling for Swift loop guards and defer statements.

Changes:

  • Adds guarded for-in control-flow edges.
  • Models defer using nested try/finally scopes.
  • Expands Swift CFG tests and generated expectations.
File Description
shared/​controlflow/​codeql/​controlflow/​ControlFlowGraph.qll Adds loop-guard CFG support.
unified/​ql/​lib/​codeql/​unified/​internal/​ControlFlowGraph.qll Adapts Swift guards and defers.
unified/​ql/​test/​library-tests/​controlflow/​cfg.swift Adds CFG test scenarios.
unified/​ql/​test/​library-tests/​controlflow/​cfg.expected Updates generated CFG expectations.
unified/​ql/​test/​library-tests/​controlflow/​basicblock-slices.expected Updates generated block slices.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

return true
case 42: // $ bbStep='42 : match -> Block(+1)' bbStep='42 : no-match -> SwitchCase(+3)'
print("42!")
fallthrough // $ bbStep=' : successor -> var obj(+5)'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is expected, fallthrough is not supported by the extractor yet.

Comment on lines +176 to +177
class TryStmt extends Stmt {
TryStmt() { this instanceof U::TryExpr or this instanceof U::DeferStmt }

@paldepind paldepind left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a really neat way of handling defer 😊. I've left some small comments, otherwise this looks good to me.

deferScope(defer, n - 1, mid) and
not mid instanceof U::DeferStmt and
block.getStmt(deferIdx + 1 + n) = s
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This disjunct is not covered by the tests. I.e., all the tests still pass when deleting it.

It would get exercised by expanding testDefer to the following (or something similar):

func testDefer(x : inout Int) {
  // Will print 1, 2, 3, 4, 5, 6
  defer {
    print("6")
  }
  print("1")
  print("2")

  defer {
    print("5")
  }

  defer {
    print("3")
     defer {
      print("4")
    }
  }
}

class TryStmt extends U::TryExpr {
AstNode getBody(int index) { index = 0 and result = super.getBody() }
class TryStmt extends Stmt {
TryStmt() { this instanceof U::TryExpr or this instanceof U::DeferStmt }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it would be worth having a comment somewhere with a small example showing how defer is mapped into try-catch?

class Expr = U::Expr;

class BlockStmt = U::Block;
private predicate deferScope(U::DeferStmt defer, int n, Stmt s) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe this qldoc, or something along those lines, would be worth having?

Suggested change
private predicate deferScope(U::DeferStmt defer, int n, Stmt s) {
/**
* Holds if `s` is the `n`th statement in the constructed `try` block of
* `defer`
*/
private predicate deferScope(U::DeferStmt defer, int n, Stmt s) {

@asgerf asgerf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me, though I agree with @paldepind that it would be nice with some qldoc to clarify the interpretation of defer as a try/finally

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-change-note-required This PR does not need a change note Unified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants