Skip to content

C++: Implement MaD support for flow through perfect-forwarding functions - #22525

Draft
MathiasVP wants to merge 10 commits into
github:mainfrom
MathiasVP:flow-through-forwards-using-callbacks-2
Draft

C++: Implement MaD support for flow through perfect-forwarding functions#22525
MathiasVP wants to merge 10 commits into
github:mainfrom
MathiasVP:flow-through-forwards-using-callbacks-2

Conversation

@MathiasVP

Copy link
Copy Markdown
Contributor

This PR implements the necessary library changes to support MaD summaries for functions such as vector::emplace_back or make_unique. These functions receive a list of arguments and then forwards them to a constructor call.

@hvitved had a super cool implementation idea. Given:

struct Foo {
  Foo(int);
};

std::vector<Foo> v;

we model a call such as v.emplace_back(42) as:

v.emplace_back(42, &Foo)

and give emplace_back two summaries:

  1. Argument[0] -> Argument[1].Parameter[0]
  2. Argument[1].Parameter[this] -> Argument[this].Element

The first summary states that 42 goes into the 0'th parameter of the Foo constructor, and the second summary states that the this parameter of the constructed object goes into the this argument of v with an Element content.

(A few lines I told in the above paragraph:

  • You cannot take the address of a constructor in C++. But that doesn't mean we cannot use it as an implementation detail!
  • We don't actually use the last argument as the argument position of the synthetic function pointer for the constructor. This PR adds a new argument position which we name forward in MaD. I'm happy to change this name to something else if anyone has any strong opinions about this.)

In order to know which constructor to forward to we need to know what type is being constructed. For example:

std::vector<Foo> v;
v.emplace_back(42); // calls `Foo(42)`
v.emplace(v.begin(), 42); // calls `Foo(42)`
std::make_unique<Foo> p(42); // calls `Foo(42)`

To know which type is constructed we add a new extensible called forwardsModel with rows very much like what we have for MaD summaries. For example, I've added this as row as a test:

["", "Container<T>", True, "emplace<Args>", "(Args &&)", "", "0", "T", "manual"]

this says that a call to Container<T>::emplace(args0, ..., argsN) forwards its arguments to a call to T(args0, ..., argsN). The 0 specifies an offset so we can support cases like v.emplace(v.begin(), 42) where we need to ignore the first argument.

This PR doesn't actually add any non-test MaD summaries. I'll delay that to a future PR.

In the upcoming commits we will add a new extensional predicate which
allows us to model that a function forwards it arguments to the
constructor of a given type. This initial commit adds the test YAML
models for this new extensional predicate.
signature to implement the forwardsModel. So instead of recursing on the
number of elements in the signature we will recurse on the number of
elements in the type (or name) columns. For well-formed models this will
be equivalent.
Copilot AI balanced review requested due to automatic review settings September 8, 2026 15:58
@MathiasVP
MathiasVP requested a review from a team as a code owner September 8, 2026 15:58
@MathiasVP MathiasVP added the no-change-note-required This PR does not need a change note label Sep 8, 2026

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.

Copilot review overview

🟡 Changes recommended

Constructor matching mishandles reference overloads and default parameters, while forwarding model validation and key test branches are incomplete.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 2 Medium severity · 1 Low severity

New issues introduced by this change (3)
Severity Finding
Medium severity cpp/​ql/​lib/​semmle/​code/​cpp/​dataflow/​ExternalFlow.qllforwardsModel is included in element interpretation here but is omitted from…
Medium severity cpp/​ql/​lib/​semmle/​code/​cpp/​ir/​dataflow/​internal/​DataFlowPrivate.qll — Requiring the number of forwarded arguments to equal the constructor's full parameter count drops…
Low severity cpp/​ql/​test/​library-tests/​dataflow/​external-models/​flow.ext.yml — Both forwarding fixtures use start = 0, leaving the new offset behavior entirely untested even…
What changed in this PR

Adds C++ MaD support for modeling perfect-forwarding APIs through synthetic constructor arguments.

Changes:

  • Introduces forwardsModel and Argument[forward].
  • Resolves forwarded constructor targets and connects their data flow.
  • Adds external-model fixtures and expected results.
File Description
cpp/​ql/​test/​library-tests/​dataflow/​external-models/​test.cpp Adds forwarding test fixtures.
cpp/​ql/​test/​library-tests/​dataflow/​external-models/​sources.expected Updates expected sources.
cpp/​ql/​test/​library-tests/​dataflow/​external-models/​sinks.expected Updates expected sinks.
cpp/​ql/​test/​library-tests/​dataflow/​external-models/​flow.ext.yml Adds forwarding models.
cpp/​ql/​test/​library-tests/​dataflow/​external-models/​flow.expected Updates expected flow graph.
cpp/​ql/​lib/​semmle/​code/​cpp/​ir/​implementation/​unaliased_ssa/​Instruction.qll Adds positional-argument helpers.
cpp/​ql/​lib/​semmle/​code/​cpp/​ir/​implementation/​raw/​Instruction.qll Adds positional-argument helpers.
cpp/​ql/​lib/​semmle/​code/​cpp/​ir/​implementation/​aliased_ssa/​Instruction.qll Adds positional-argument helpers.
cpp/​ql/​lib/​semmle/​code/​cpp/​ir/​dataflow/​internal/​DataFlowPrivate.qll Implements synthetic constructor flow.
cpp/​ql/​lib/​semmle/​code/​cpp/​ir/​dataflow/​internal/​DataFlowNodes.qll Registers forwarding nodes.
cpp/​ql/​lib/​semmle/​code/​cpp/​dataflow/​internal/​FlowSummaryImpl.qll Parses forward positions.
cpp/​ql/​lib/​semmle/​code/​cpp/​dataflow/​internal/​ExternalFlowExtensions.qll Declares forwardsModel.
cpp/​ql/​lib/​semmle/​code/​cpp/​dataflow/​ExternalFlow.qll Interprets forwarding models and constructor types.
cpp/​ql/​lib/​ext/​empty.model.yml Registers an empty forwarding extension.
Suppressed comments (2)

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll:616

  • This equality check does not preserve C++ overload resolution. stripReferences also deeply removes cv-qualifiers, so an lvalue Foo argument makes both Foo(const Foo&) and Foo(Foo&&) look like Foo; both constructor bodies/summaries can then contribute flow even though only the lvalue overload is callable. Conversely, valid implicit conversions match no constructor. Please resolve viability with reference binding/value-category and conversion rules rather than comparing stripped types.
      typeCall = stripReferences(call.getPositionalArgument(start + i).getResultType()) and
      typeConstructor = stripReferences(constructor.getParameter(i).getUnspecifiedType())
    |
      typeCall = typeConstructor

cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll:1088

  • This function-template branch is not exercised by the added tests: both fixtures obtain the constructor type from a class template argument. Add a free-function forwarding case such as the make_unique<T, Args> shape described by the PR so regressions in forwarder.getTemplateArgument(index) are covered.
  exists(string nameArguments, int index |
    parseAngles(name, _, nameArguments, "") and
    constructorType = getAtIndex(nameArguments, index) and
    result = forwarder.getTemplateArgument(index)

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

Comment thread cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll
Comment thread cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll Outdated
Comment thread cpp/ql/test/library-tests/dataflow/external-models/flow.ext.yml Outdated
@github-actions github-actions Bot added the C++ label Sep 8, 2026
@MathiasVP
MathiasVP force-pushed the flow-through-forwards-using-callbacks-2 branch from cb6ca91 to afdaabf Compare September 8, 2026 18:43
@MathiasVP
MathiasVP marked this pull request as draft September 8, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C++ no-change-note-required This PR does not need a change note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants