Skip to content

Ruby: Replace CFG with shared implementation - #22158

Merged
aschackmull merged 12 commits into
github:mainfrom
aschackmull:ruby/cfg-swap
Aug 25, 2026
Merged

Ruby: Replace CFG with shared implementation#22158
aschackmull merged 12 commits into
github:mainfrom
aschackmull:ruby/cfg-swap

Conversation

@aschackmull

@aschackmullaschackmull commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

This replaces the Ruby control flow graph with an instantiation of the shared library.

Comment threadruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph2.qll Fixed
@aschackmull
aschackmullforce-pushed the ruby/cfg-swap branch 3 times, most recently from 597c62a to e8c7a76CompareJuly 10, 2026 13:02
@aschackmull
aschackmullforce-pushed the ruby/cfg-swap branch 3 times, most recently from 10337a2 to 8b0d578CompareJuly 30, 2026 12:03
Comment threadruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll Fixed
Comment threadruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qll Fixed
@aschackmull
aschackmullforce-pushed the ruby/cfg-swap branch 4 times, most recently from c74162e to 8dcd6e6CompareAugust 11, 2026 12:10
Comment on lines +16 to +18
guardNode.isAfterValue(guard.getAstNode(), s) and
s.getValue() = branch and
guardNode.getBasicBlock().dominates(bb)
@aschackmull
aschackmull marked this pull request as ready for review August 11, 2026 12:50
@aschackmull
aschackmull requested a review from a team as a code ownerAugust 11, 2026 12:50
CopilotAI balanced review requested due to automatic review settings August 11, 2026 12:50
@aschackmullaschackmull added the no-change-note-required This PR does not need a change note label Aug 11, 2026

CopilotAI 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.

Pull request overview

Replaces Ruby’s custom control-flow graph implementation with the shared control-flow library and updates dependent analyses and test baselines.

Changes:

  • Instantiates the shared CFG framework for Ruby and removes bespoke CFG, completion, splitting, and basic-block implementations.
  • Migrates SSA, data-flow, security, framework, and IDE consumers to shared CFG APIs.
  • Refreshes affected expected test results and consistency checks.
Show a summary per file
FileDescription
ruby/ql/test/library-tests/variables/ssa.expectedUpdates SSA results for the shared CFG.
ruby/ql/test/library-tests/frameworks/active_record/ActiveRecord.expectedUpdates Active Record data-flow results.
ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expectedUpdates type-tracking results.
ruby/ql/test/library-tests/dataflow/local/Nodes.expectedUpdates local data-flow nodes.
ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.qlUses shared basic-block types.
ruby/ql/test/library-tests/controlflow/graph/Nodes.expectedUpdates CFG node results.
ruby/ql/test/library-tests/controlflow/graph/Cfg.qlUses shared CFG test output.
ruby/ql/test/library-tests/controlflow/graph/BasicBlocks.qlAdapts basic-block tests.
ruby/ql/test/library-tests/ast/ValueText.expectedUpdates CFG-backed value results.
ruby/ql/src/queries/variables/DeadStoreOfLocal.qlUses enclosing callable scope.
ruby/ql/src/queries/performance/DatabaseQueryInLoop.qlUpdates loop scope lookup.
ruby/ql/src/experimental/performance/UseDetect.qlUses direct control-flow-node lookup.
ruby/ql/lib/ide-contextual-queries/printCfg.qlMigrates CFG visualization APIs.
ruby/ql/lib/codeql/ruby/security/ConditionalBypassCustomizations.qllAdapts edge-dominance checks.
ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qllUpdates callable scope lookup.
ruby/ql/lib/codeql/ruby/dataflow/SSA.qllSimplifies phi-node rendering.
ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImpl.qllMigrates SSA construction to shared CFG blocks.
ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qllMigrates internal data-flow CFG usage.
ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qllUpdates call scope and file lookup.
ruby/ql/lib/codeql/ruby/controlflow/internal/Splitting.qllRemoves bespoke CFG splitting.
ruby/ql/lib/codeql/ruby/controlflow/internal/NonReturning.qllMaps non-returning calls to shared successor types.
ruby/ql/lib/codeql/ruby/controlflow/internal/Guards.qllAdapts guard dominance logic.
ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImpl.qllRemoves the previous CFG implementation.
ruby/ql/lib/codeql/ruby/controlflow/internal/Completion.qllRemoves custom completion modeling.
ruby/ql/lib/codeql/ruby/controlflow/ControlFlowGraph.qllInstantiates and configures the shared CFG.
ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qllMigrates Ruby CFG node wrappers.
ruby/ql/lib/codeql/ruby/controlflow/BasicBlocks.qllRemoves custom basic-block wrappers.
ruby/ql/lib/codeql/ruby/CFG.qllDrops the removed basic-block import.
ruby/ql/lib/codeql/ruby/ast/Statement.qllExposes shared control-flow-node mappings.
ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qllExcludes synthesized assignment operands from CFG construction.
ruby/ql/consistency-queries/DataFlowConsistency.qlRemoves split-specific exclusions.
ruby/ql/consistency-queries/CfgConsistency.qlUses shared CFG consistency checks.

Review details

Tip

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

  • Files reviewed: 34/38 changed files
  • Comments generated: 0
  • Review effort level: Balanced

@aschackmull
aschackmullforce-pushed the ruby/cfg-swap branch 2 times, most recently from 99eb18e to 9859c59CompareAugust 20, 2026 11:29
@aschackmullaschackmull removed the no-change-note-required This PR does not need a change note label Aug 24, 2026

@hvitvedhvitved 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 great, thanks for doing this. I have a few minor comments.

ControlFlowNode getControlFlowNode() { result.injects(this) }

/** Gets a control-flow node for this statement, if any. */
CfgNodes::AstCfgNode getAControlFlowNode() { result.getAstNode() = this }

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 we should deprecate this predicate.

}

private R::Ast::AstNode adjustedGetChild(R::Ast::AstNode parent, int index) {
exists(R::Ast::WhenClause when | parent = when |

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 have a mild preference for parent = any(R::Ast::WhenClause when | ... (same in other cases below).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I tried to see how it looks and it generally becomes longer and more indented, so I'll leave it as-is.

or
findpattern.getElement(index) = result
or
index = 1 + max(int i | exists(findpattern.getElement(i))) and

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.

Use count instead as in the ArrayPattern case (not sure if there can be 0 elements)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Yep - that's simpler. I don't think there can be 0 elements, though.

Comment on lines +202 to +203
result = c.(R::Ast::Toplevel).getABeginBlock() or
result = c.(R::Ast::Toplevel).getAStmt() or

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.

Perhaps result = callableGetBodyPart(c, _, _) instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've factored out the common part.

Comment on lines +550 to +552
n1.isAfterTrue(ce.getCondition()) and not exists(ce.getThen()) and n2.isAfter(ce)
or
n1.isAfterFalse(ce.getCondition()) and not exists(ce.getElse()) and n2.isAfter(ce)

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.

Should this instead be done in the shared library?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, done.

@aschackmull
aschackmull requested a review from a team as a code ownerAugust 25, 2026 11:24
@aschackmull

aschackmull commented Aug 25, 2026

Copy link
Copy Markdown
ContributorAuthor

@hvitvedhvitved 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.

🎉

@aschackmull
aschackmull merged commit daa37c0 into github:mainAug 25, 2026
113 checks passed
@aschackmull
aschackmull deleted the ruby/cfg-swap branch August 25, 2026 12:18
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aschackmull@hvitved@github-advanced-security