From 1b41baea51da92c47257830b70b1c0a21d0aef11 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 7 Sep 2026 17:43:17 +0100 Subject: [PATCH 01/13] C++: Add tests with missing flow. 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. --- cpp/ql/lib/ext/empty.model.yml | 4 + .../internal/ExternalFlowExtensions.qll | 8 ++ .../dataflow/external-models/flow.ext.yml | 10 +++ .../dataflow/external-models/sinks.expected | 6 ++ .../dataflow/external-models/sources.expected | 4 + .../dataflow/external-models/test.cpp | 78 +++++++++++++++++++ 6 files changed, 110 insertions(+) diff --git a/cpp/ql/lib/ext/empty.model.yml b/cpp/ql/lib/ext/empty.model.yml index e5202b5ad73c..9c2921cfef1b 100644 --- a/cpp/ql/lib/ext/empty.model.yml +++ b/cpp/ql/lib/ext/empty.model.yml @@ -21,3 +21,7 @@ extensions: pack: codeql/cpp-all extensible: summaryModel data: [] + - addsTo: + pack: codeql/cpp-all + extensible: forwardsModel + data: [] \ No newline at end of file diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/ExternalFlowExtensions.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/ExternalFlowExtensions.qll index 22c74c2aa714..e05c1bb2b810 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/ExternalFlowExtensions.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/ExternalFlowExtensions.qll @@ -51,6 +51,14 @@ extensible predicate neutralModel( string namespace, string type, string name, string signature, string kind, string provenance ); +/** + * Holds if a constructor forwarding model exists for the given parameters. + */ +extensible predicate forwardsModel( + string namespace, string type, boolean subtypes, string name, string signature, string ext, + string start, string constructor, string output, string provenance, QlBuiltins::ExtensionId madId +); + module Extensions implements SharedMaD::ExtensionsSig { import ExternalFlowExtensions diff --git a/cpp/ql/test/library-tests/dataflow/external-models/flow.ext.yml b/cpp/ql/test/library-tests/dataflow/external-models/flow.ext.yml index 0db87b5da615..735bcaa320ed 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/flow.ext.yml +++ b/cpp/ql/test/library-tests/dataflow/external-models/flow.ext.yml @@ -42,3 +42,13 @@ extensions: - ["", "ReverseFlow", True, "get_ptr", "", "", "ReturnValue[*]", "Argument[-1].Field[ReverseFlow::value]", "value", "manual"] - ["", "MyString", True, "operator[]", "", "", "ReturnValue[*]", "Argument[-1]", "taint", "manual"] - ["", "MyString", True, "operator[]", "", "", "Argument[-1]", "ReturnValue[*]", "taint", "manual"] + - ["", "Forwarder", True, "get", "", "", "Argument[-1]", "ReturnValue", "value", "manual"] + - ["", "Container", True, "get", "", "", "Argument[-1].Element", "ReturnValue[*]", "value", "manual"] + - ["", "Element", True, "Element", "", "", "Argument[0]", "Argument[-1].Field[Element::x]", "value", "manual"] + - ["", "ElementWithDefaultArgument", True, "ElementWithDefaultArgument", "", "", "Argument[0]", "Argument[-1].Field[ElementWithDefaultArgument::x]", "value", "manual"] + - addsTo: + pack: codeql/cpp-all + extensible: forwardsModel + data: # namespace, type, subtypes, name, signature, ext, start, constructor, output, provenance + - ["", "Forwarder", True, "forward", "(Args &&)", "", "0", "T", "Argument[-1]", "manual"] + - ["", "Container", True, "emplace", "(int,Args &&)", "", "1", "T", "Argument[-1].Element", "manual"] diff --git a/cpp/ql/test/library-tests/dataflow/external-models/sinks.expected b/cpp/ql/test/library-tests/dataflow/external-models/sinks.expected index 5851e825013d..0701b0f7eeb2 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/sinks.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/sinks.expected @@ -43,3 +43,9 @@ | test.cpp:331:10:331:19 | * ... | test-sink | | test.cpp:333:15:333:20 | source | test-sink | | test.cpp:334:10:334:16 | * ... | test-sink | +| test.cpp:365:15:365:15 | s | test-sink | +| test.cpp:366:15:366:16 | ul | test-sink | +| test.cpp:374:15:374:15 | s | test-sink | +| test.cpp:375:15:375:16 | ul | test-sink | +| test.cpp:398:13:398:13 | x | test-sink | +| test.cpp:412:13:412:13 | x | test-sink | diff --git a/cpp/ql/test/library-tests/dataflow/external-models/sources.expected b/cpp/ql/test/library-tests/dataflow/external-models/sources.expected index b30f1e88b99a..7f71ec61b6e4 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/sources.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/sources.expected @@ -19,6 +19,10 @@ | test.cpp:222:10:222:18 | call to ymlSource | local | | test.cpp:297:33:297:41 | call to ymlSource | local | | test.cpp:317:51:317:59 | call to ymlSource | local | +| test.cpp:361:15:361:23 | call to ymlSource | local | +| test.cpp:370:24:370:32 | call to ymlSource | local | +| test.cpp:394:11:394:19 | call to ymlSource | local | +| test.cpp:408:11:408:19 | call to ymlSource | local | | windows.cpp:22:15:22:29 | call to GetCommandLineA | local | | windows.cpp:34:17:34:38 | call to GetEnvironmentStringsA | local | | windows.cpp:39:36:39:38 | GetEnvironmentVariableA output argument | local | diff --git a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp index 739c36bc67d3..7865c177e4e6 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp @@ -332,4 +332,82 @@ void test_parameter(SourceWrapper* p, SourceWrapper s, int* source) { ymlSink((int)source); // clean ymlSink(*source); // $ ir +} + + +struct ConstructableFromInt { + short s; + unsigned long ul; + ConstructableFromInt(short arg) { + this->s = arg; + } + + ConstructableFromInt(unsigned long arg) { + this->ul = arg; + } +}; + +template +struct Forwarder { + template + void forward(Args&&... args); + + T get(); +}; + +void forward_test() { + { + Forwarder f; + short x = ymlSource(); + f.forward(x); + + ConstructableFromInt c = f.get(); + ymlSink(c.s); // $ MISSING: ir + ymlSink(c.ul); // clean + } + { + Forwarder f; + unsigned long ul = ymlSource(); + f.forward(ul); + + ConstructableFromInt c = f.get(); + ymlSink(c.s); // clean + ymlSink(c.ul); // $ MISSING: ir + } +} + +template +struct Container { + template + void emplace(int pos, Args&&... args); + + T& get(); +}; + +struct Element { + int x; + Element(int); +}; + +void forward_test_model() { + Container c; + int x = ymlSource(); + c.emplace(0, x); + + Element e = c.get(); + ymlSink(e.x); // $ MISSING: ir +} + +struct ElementWithDefaultArgument { + int x; + ElementWithDefaultArgument(int x, int = 0); +}; + +void forward_test_model_with_default_argument() { + Container c; + int x = ymlSource(); + c.emplace(0, x); + + ElementWithDefaultArgument e = c.get(); + ymlSink(e.x); // $ MISSING: ir } \ No newline at end of file From 0169e1852daeb86d0cc933f7d62cd8a6d2240c3a Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 8 Sep 2026 12:52:49 +0100 Subject: [PATCH 02/13] C++: Add a missing utility predicate on Call instructions. --- .../implementation/aliased_ssa/Instruction.qll | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index b7dcd4d8f754..4d26df9726b3 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -1706,6 +1706,13 @@ class CallInstruction extends Instruction { result.getIndex() = index } + /** + * Gets a positional argument operand, if any. + */ + final PositionalArgumentOperand getAPositionalArgumentOperand() { + result = this.getPositionalArgumentOperand(_) + } + /** * Gets the argument at the specified index. */ @@ -1714,6 +1721,11 @@ class CallInstruction extends Instruction { result = this.getPositionalArgumentOperand(index).getDef() } + /** + * Gets a positional argument, if any. + */ + final Instruction getAPositionalArgument() { result = this.getPositionalArgument(_) } + /** * Gets the argument operand at the specified index, or `this` if `index` is `-1`. */ @@ -1735,6 +1747,11 @@ class CallInstruction extends Instruction { */ final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } + /** + * Gets the number of positional arguments of the call. + */ + final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgument()) } + /** * Holds if the result is a side effect for the argument at the specified index, or `this` if * `index` is `-1`. From 3777020fe9ec32e1263abffd192241bc190d0016 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 8 Sep 2026 12:53:05 +0100 Subject: [PATCH 03/13] C++: Sync identical files. --- .../cpp/ir/implementation/raw/Instruction.qll | 17 +++++++++++++++++ .../unaliased_ssa/Instruction.qll | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll index b7dcd4d8f754..4d26df9726b3 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -1706,6 +1706,13 @@ class CallInstruction extends Instruction { result.getIndex() = index } + /** + * Gets a positional argument operand, if any. + */ + final PositionalArgumentOperand getAPositionalArgumentOperand() { + result = this.getPositionalArgumentOperand(_) + } + /** * Gets the argument at the specified index. */ @@ -1714,6 +1721,11 @@ class CallInstruction extends Instruction { result = this.getPositionalArgumentOperand(index).getDef() } + /** + * Gets a positional argument, if any. + */ + final Instruction getAPositionalArgument() { result = this.getPositionalArgument(_) } + /** * Gets the argument operand at the specified index, or `this` if `index` is `-1`. */ @@ -1735,6 +1747,11 @@ class CallInstruction extends Instruction { */ final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } + /** + * Gets the number of positional arguments of the call. + */ + final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgument()) } + /** * Holds if the result is a side effect for the argument at the specified index, or `this` if * `index` is `-1`. diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index b7dcd4d8f754..4d26df9726b3 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1706,6 +1706,13 @@ class CallInstruction extends Instruction { result.getIndex() = index } + /** + * Gets a positional argument operand, if any. + */ + final PositionalArgumentOperand getAPositionalArgumentOperand() { + result = this.getPositionalArgumentOperand(_) + } + /** * Gets the argument at the specified index. */ @@ -1714,6 +1721,11 @@ class CallInstruction extends Instruction { result = this.getPositionalArgumentOperand(index).getDef() } + /** + * Gets a positional argument, if any. + */ + final Instruction getAPositionalArgument() { result = this.getPositionalArgument(_) } + /** * Gets the argument operand at the specified index, or `this` if `index` is `-1`. */ @@ -1735,6 +1747,11 @@ class CallInstruction extends Instruction { */ final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } + /** + * Gets the number of positional arguments of the call. + */ + final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgument()) } + /** * Holds if the result is a side effect for the argument at the specified index, or `this` if * `index` is `-1`. From 77bb230e198b369cb413fe044b1a2eaddca20b24 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 8 Sep 2026 23:41:32 +0100 Subject: [PATCH 04/13] C++: Small refactor. --- .../code/cpp/ir/dataflow/internal/DataFlowNodes.qll | 11 ++++------- .../cpp/ir/dataflow/internal/DataFlowPrivate.qll | 6 +++--- .../code/cpp/ir/dataflow/internal/SsaImplCommon.qll | 13 ++++++++++++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll index 541b6d13b149..39bc10415161 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll @@ -144,7 +144,7 @@ private module Cached { TNonUnionContent(CanonicalField f, int indirectionIndex) { // the indirection index for field content starts at 1 (because `TNonUnionContent` is thought of as // the address of the field, `FieldAddress` in the IR). - indirectionIndex = [1 .. max(SsaImpl::getMaxIndirectionsForType(f.getAnUnspecifiedType()))] and + indirectionIndex = [1 .. max(SsaImpl::getMaxIndirectionsForGLType(f.getAnUnspecifiedType()))] and // Reads and writes of union fields are tracked using `UnionContent`. not f.getDeclaringType() instanceof Union } or @@ -156,7 +156,7 @@ private module Cached { // field can be read by any read of the union's fields. Again, the indirection index // is 1-based (because 0 is considered the address). indirectionIndex = - [1 .. max(SsaImpl::getMaxIndirectionsForType(getAFieldWithSize(u, bytes) + [1 .. max(SsaImpl::getMaxIndirectionsForGLType(getAFieldWithSize(u, bytes) .getAnUnspecifiedType()) )] ) @@ -184,7 +184,7 @@ private module Cached { TNode0(Node0Impl node) { DataFlowImplCommon::forceCachingInSameStage() } or TGlobalLikeVariableNode(GlobalLikeVariable var, int indirectionIndex) { indirectionIndex = - [getMinIndirectionsForType(var.getUnspecifiedType()) .. SsaImpl::getMaxIndirectionsForType(var.getUnspecifiedType())] + [getMinIndirectionsForType(var.getUnspecifiedType()) .. SsaImpl::getMaxIndirectionsForGLType(var.getUnspecifiedType())] } or TPostUpdateNodeImpl(Operand operand, int indirectionIndex) { isPostUpdateNodeImpl(operand, indirectionIndex) @@ -209,10 +209,7 @@ private module Cached { TBodyLessParameterNodeImpl(Parameter p, int indirectionIndex) { // Rule out parameters of catch blocks. not exists(p.getCatchBlock()) and - // We subtract one because `getMaxIndirectionsForType` returns the maximum - // indirection for a glvalue of a given type, and this doesn't apply to - // parameters. - indirectionIndex = [0 .. SsaImpl::getMaxIndirectionsForType(p.getUnspecifiedType()) - 1] and + indirectionIndex = [0 .. SsaImpl::getMaxIndirectionsForPRType(p.getUnspecifiedType())] and not any(InitializeParameterInstruction init).getParameter() = p } or TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index 551035c5589e..8f66739aaa45 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -737,7 +737,7 @@ newtype TPosition = // the function. exists(Cpp::Function f, Cpp::Parameter p | p = f.getParameter(argumentIndex) and - indirectionIndex = [1 .. Ssa::getMaxIndirectionsForType(p.getUnspecifiedType()) - 1] + indirectionIndex = [1 .. Ssa::getMaxIndirectionsForPRType(p.getUnspecifiedType())] ) } or TFlowSummaryPosition(ReturnKind rk) { FlowSummaryImpl::Private::relevantFlowSummaryPosition(rk) } @@ -755,7 +755,7 @@ private newtype TReturnKind = [0 .. max(Cpp::Function f | not exists(f.getBlock()) | - Ssa::getMaxIndirectionsForType(f.getUnspecifiedType()) - 1 // -1 because a returned value is a prvalue not a glvalue + Ssa::getMaxIndirectionsForPRType(f.getUnspecifiedType()) )] } or TIndirectReturnKind(int argumentIndex, int indirectionIndex) { @@ -770,7 +770,7 @@ private newtype TReturnKind = [0 .. max(Cpp::Function f | not exists(f.getBlock()) | - Ssa::getMaxIndirectionsForType(f.getParameter(argumentIndex).getUnspecifiedType()) - 1 // -1 because an argument is a prvalue not a glvalue + Ssa::getMaxIndirectionsForPRType(f.getParameter(argumentIndex).getUnspecifiedType()) )] } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll index 31931189003c..5a56042612a4 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll @@ -40,10 +40,21 @@ CppType getLanguageType(Operand operand) { result = getResultLanguageType(operan * - If `type = MyStruct`, the result is 1 * - If `type = char*`, the result is 2 */ -int getMaxIndirectionsForType(Type type) { +int getMaxIndirectionsForGLType(Type type) { result = countIndirectionsForCppType(getTypeForGLValue(type)) } +/** + * Gets the maximum number of indirections a prvalue of type `type` can have. + * For example: + * - If `type = int`, the result is 0 + * - If `type = MyStruct`, the result is 0 + * - If `type = char*`, the result is 1 + */ +int getMaxIndirectionsForPRType(Type type) { + result = countIndirectionsForCppType(getTypeForPRValue(type)) +} + private class PointerOrArrayOrReferenceType extends Cpp::DerivedType { PointerOrArrayOrReferenceType() { this instanceof Cpp::PointerType From 04c2ac48e29877eb1b9e7df55cea10acff33c972 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 7 Sep 2026 17:51:57 +0100 Subject: [PATCH 05/13] C++: We will need the template resolution for something other than the 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. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 4f84b30d557e..40cb03daf262 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -596,6 +596,14 @@ private string getAtIndex(string s, int i) { not (s = "" and i = 0) } +/** Gets the number of comma-separated arguments in `s`. */ +bindingset[s] +private int getNumberOfArguments(string s) { + s = "" and result = 0 + or + s != "" and result = count(s.indexOf(",")) + 1 +} + /** * Normalizes `partiallyNormalizedSignature` by replacing the `remaining` * number of template arguments in `partiallyNormalizedSignature` with their @@ -605,7 +613,7 @@ private string getSignatureWithoutClassTemplateNames( string partiallyNormalizedSignature, string typeArgs, string nameArgs, int remaining ) { elementSpecWithArguments0(_, _, _, partiallyNormalizedSignature, typeArgs, nameArgs) and - remaining = count(partiallyNormalizedSignature.indexOf(",")) + 1 and + remaining = getNumberOfArguments(typeArgs) and result = partiallyNormalizedSignature or exists(string mid | @@ -619,7 +627,7 @@ private string getSignatureWithoutClassTemplateNames( ) or // Make sure `remaining` is properly bound - remaining = [0 .. count(partiallyNormalizedSignature.indexOf(",")) + 1] and + remaining = [0 .. getNumberOfArguments(typeArgs)] and not exists(getAtIndex(typeArgs, remaining)) and result = mid ) @@ -636,7 +644,7 @@ pragma[nomagic] private string getSignatureWithoutFunctionTemplateNames( string partiallyNormalizedSignature, string typeArgs, string nameArgs, int remaining ) { - remaining = count(partiallyNormalizedSignature.indexOf(",")) + 1 and + remaining = getNumberOfArguments(nameArgs) and result = getSignatureWithoutClassTemplateNames(partiallyNormalizedSignature, typeArgs, nameArgs, 0) or @@ -651,7 +659,7 @@ private string getSignatureWithoutFunctionTemplateNames( ) or // Make sure `remaining` is properly bound - remaining = [0 .. count(partiallyNormalizedSignature.indexOf(",")) + 1] and + remaining = [0 .. getNumberOfArguments(nameArgs)] and not exists(getAtIndex(nameArgs, remaining)) and result = mid ) From 48de4cbede00181ed1741b6dd46b28e5a1ff0766 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 7 Sep 2026 17:58:27 +0100 Subject: [PATCH 06/13] C++: Add MaD support for models that specify argument forwarding. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 122 ++++++++++++++- .../cpp/dataflow/internal/FlowSummaryImpl.qll | 3 + .../ir/dataflow/internal/DataFlowNodes.qll | 3 + .../ir/dataflow/internal/DataFlowPrivate.qll | 145 ++++++++++++++++-- 4 files changed, 258 insertions(+), 15 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 40cb03daf262..c9dedb4da82d 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -15,6 +15,8 @@ * `namespace; type; subtypes; name; signature; ext; output; kind; provenance` * - BarrierGuards: * `namespace; type; subtypes; name; signature; ext; input; acceptingValue; kind; provenance` + * - Forwards: + * `namespace; type; subtypes; name; signature; ext; start; constructor; output; provenance` * * The interpretation of a row is similar to API-graphs with a left-to-right * reading. @@ -115,6 +117,7 @@ private import new.DataFlow private import semmle.code.cpp.controlflow.IRGuards private import semmle.code.cpp.ir.dataflow.internal.DataFlowNodes as Nodes private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate as Private +private import semmle.code.cpp.ir.dataflow.internal.SsaImpl as SsaImpl private import semmle.code.cpp.ir.dataflow.internal.DataFlowUtil private import internal.FlowSummaryImpl private import internal.FlowSummaryImpl::Public @@ -160,6 +163,20 @@ predicate summaryModel( ) } +/** + * Holds if a forward model exists for the given parameters. + */ +predicate forwardsModel( + string namespace, string type, boolean subtypes, string name, string signature, string ext, + string start, string constructor, string output, string provenance, string model +) { + exists(QlBuiltins::ExtensionId madId | + Extensions::forwardsModel(namespace, type, subtypes, name, signature, ext, start, constructor, + output, provenance, madId) and + model = "MaD:" + madId.toString() + ) +} + /** Provides a query predicate to check the data for validation errors. */ module ModelValidation { private string getInvalidModelInput() { @@ -186,6 +203,8 @@ module ModelValidation { sourceModel(_, _, _, _, _, _, output, _, _, _) and pred = "source" or summaryModel(_, _, _, _, _, _, _, output, _, _, _) and pred = "summary" + or + forwardsModel(_, _, _, _, _, _, _, _, output, _, _) and pred = "forwards" | invalidSpecComponent(output, part) and not part = "" and @@ -259,7 +278,8 @@ private predicate elementSpec( sinkModel(namespace, type, subtypes, name, signature, ext, _, _, _, _) or barrierModel(namespace, type, subtypes, name, signature, ext, _, _, _, _) or barrierGuardModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _) or - summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _) + summaryModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _) or + forwardsModel(namespace, type, subtypes, name, signature, ext, _, _, _, _, _) } /** @@ -1054,6 +1074,103 @@ private module Cached { import Cached +/** Gets the constructor type selected by `constructorType` in a forwarding model. */ +bindingset[forwarder, type, name, constructorType] +private Type getForwardedConstructorType( + Function forwarder, string type, string name, string constructorType +) { + exists(string typeArguments, int index | + parseAngles(type, _, typeArguments, "") and + constructorType = getAtIndex(typeArguments, index) and + result = forwarder.getDeclaringType().getTemplateArgument(index) + ) + or + exists(string nameArguments, int index | + parseAngles(name, _, nameArguments, "") and + constructorType = getAtIndex(nameArguments, index) and + result = forwarder.getTemplateArgument(index) + ) +} + +/** Interprets a forwarding model, retaining its output and provenance. */ +private predicate interpretForwardsModel( + Function forwarder, Constructor constructor, int start, string output, string provenance, + string model +) { + exists( + string namespace, string type, boolean subtypes, string name, string signature, string ext, + string startString, string constructorType + | + forwardsModel(namespace, type, subtypes, name, signature, ext, startString, constructorType, + output, provenance, model) and + forwarder = interpretElement(namespace, type, subtypes, name, signature, ext) and + start = startString.toInt() + | + // Either the row specifies forwarding to a type given by the type or + // function template, in which case we need to resolve that from the type + // or function name. + constructor.getDeclaringType() = + getForwardedConstructorType(forwarder, type, name, constructorType).getUnspecifiedType() + or + // Or the row specifies forwarding to a specific type. + classHasQualifiedName(constructor.getDeclaringType(), namespace, constructorType) + ) +} + +/** Holds if `forwarder` forwards its arguments starting at `start` to `constructor`. */ +predicate forwards(Function forwarder, Constructor constructor, int start) { + interpretForwardsModel(forwarder, constructor, start, _, _, _) +} + +private int referenceIndirection(Type unspecified) { + if unspecified instanceof ReferenceType then result = 1 else result = 0 +} + +/** + * In order to support flow summaries for functions that perform "perfect + * forwarding" we interpret a call such as: + * ```cpp + * struct Foo { Foo(int) }; + * std::vector v; + * v.emplace_back(42); + * ``` + * as: + * ```cpp + * v.emplace_back(42, &Foo); + * ``` + * and add two summaries: + * (1) One flow from `42` to the first argument of a call to `Foo` + * (2) One flow from the return value of `Foo` to the `this` argument of the call + * to `emplace_back` (with a sequence of output `Content`s). + * + * These two summaries are automatically generated when a forwarding model + * for `emplace_back` exists. + */ +private predicate interpretForwardingSummary( + Function forwarder, string input, string output, string provenance, string model +) { + exists(Constructor constructor, int start, string constructorOutput | + interpretForwardsModel(forwarder, constructor, start, constructorOutput, provenance, model) + | + // Generate the (1) summary + exists(int index, Parameter arg, Parameter p, int indirection | + arg = forwarder.getParameter(start + index) and + p = constructor.getParameter(index) and + indirection = [0 .. SsaImpl::getMaxIndirectionsForPRType(p.getUnspecifiedType())] and + input = + "Argument[" + repeatStars(indirection + referenceIndirection(arg.getUnspecifiedType())) + + (start + index) + "]" and + output = + "Argument[forward].Parameter[" + + repeatStars(indirection + referenceIndirection(p.getUnspecifiedType())) + index + "]" + ) + or + // Generate the (2) summary + input = "Argument[forward].Parameter[-1]" and + output = constructorOutput + ) +} + /** * Holds if `node` is specified as a source with the given kind in a MaD flow * model. @@ -1082,6 +1199,9 @@ private predicate interpretSummary( model) and f = interpretElement(namespace, type, subtypes, name, signature, ext) ) + or + interpretForwardingSummary(f, input, output, provenance, model) and + kind = "value" } // adapter class for converting Mad summaries to `SummarizedCallable`s diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowSummaryImpl.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowSummaryImpl.qll index 176b95933db8..6c613308d5f4 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowSummaryImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/FlowSummaryImpl.qll @@ -111,6 +111,9 @@ module Input implements InputSig { pos = -1 and result = TIndirectionPosition(pos, indirection + 1) ) ) + or + argString = "forward" and + result = TForwardPosition() } bindingset[token] diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll index 39bc10415161..b493ba001559 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll @@ -191,6 +191,9 @@ private module Cached { } or TSsaSynthNode(SsaImpl::SynthNode n) or TSsaIteratorNode(IteratorFlow::IteratorFlowNode n) or + TForwarderConstructorArgumentNode(CallInstruction call) { + isForwarderConstructorArgumentNodeImpl(call) + } or TRawIndirectOperand0(Node0Impl node, int indirectionIndex) { SsaImpl::hasRawIndirectOperand(node.asOperand(), indirectionIndex) } or diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index 8f66739aaa45..a60d3ca04cce 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -593,6 +593,101 @@ private class SideEffectArgumentNode extends ArgumentNode, SideEffectOperandNode } } +/** + * Gets `unspecifiedType`, but with the outermost `ReferenceType` removed, if any. + */ +private Type stripReferences(Type unspecifiedType) { + result = unspecifiedType.(Cpp::ReferenceType).getBaseType().getUnspecifiedType() + or + not unspecifiedType instanceof Cpp::ReferenceType and + result = unspecifiedType +} + +predicate forwardingCallTargetsConstructor( + CallInstruction call, Cpp::Constructor constructor, int start +) { + exists(int numberOfForwardedArguments | + External::forwards(call.getStaticCallTarget(), constructor, start) and + call.getNumberOfPositionalArguments() = start + numberOfForwardedArguments and + forall(int i | i = [0 .. constructor.getNumberOfParameters() - 1] | + // If we are still processing the forwarded arguments then we need to + // check that the argument types match the parameter types. + // Functions that perform perfect forwarding are always written as: + // ``` + // template void emplace(Args&&... args) { ... } + // ``` + // and so all the arguments will be reference typed (lvalue or rvalued). + // However, the constructor may not specify all the arguments by + // reference. + i < numberOfForwardedArguments and + stripReferences(call.getPositionalArgument(start + i).getResultType()) = + stripReferences(constructor.getParameter(i).getUnspecifiedType()) + or + // If the constructor has a default argument and we have processed all + // the forwarded arguments then we don't need to check the types. + i >= numberOfForwardedArguments and constructor.getParameter(i).hasInitializer() + ) + ) +} + +/** Holds if `call` is a call that forwards arguments to a constructor call. */ +predicate isForwarderConstructorArgumentNodeImpl(CallInstruction call) { + forwardingCallTargetsConstructor(call, _, _) +} + +/** + * In order to implement a MaD summary for a flow such as: + * ``` + * struct Foo { + * int x; + * Foo(int x) { // (2) + * this->x = x; + * } + * } + * + * std::vector v; + * int x = source(); + * v.emplace_back(x); // (1) + * sink(v.back()); + * ``` + * we model it as if the code was: + * ``` + * v.__emplace_back(x, &Foo) + * ``` + * (nevermind that this is not real C++ since you cannot take the address of a + * constructor.) + * where `__emplace_back` invokes `Foo` with the `x` argument and returns the + * result. + * + * This class serves as the argument node for `&Foo`. + */ +private class ForwarderConstructorArgumentNode extends ArgumentNode, + TForwarderConstructorArgumentNode +{ + private CallInstruction call; + + ForwarderConstructorArgumentNode() { this = TForwarderConstructorArgumentNode(call) } + + override predicate sourceArgumentOf(CallInstruction c, ArgumentPosition pos) { + c = call and pos = TForwardPosition() + } + + /** + * Gets a constructor which may be targeted by this forwarding call. + */ + Cpp::Constructor getAConstructor() { forwardingCallTargetsConstructor(call, result, _) } + + override DataFlowCallable getEnclosingCallable() { + result.asSourceCallable() = this.getFunction() + } + + override Declaration getFunction() { result = call.getEnclosingFunction() } + + override Location getLocationImpl() { result = call.getLocation() } + + override string toStringImpl() { result = "forwarder for " + call.toString() } +} + /** * An argument node that is part of a summary. These only occur when the * summary contains a synthesized call. @@ -672,6 +767,12 @@ abstract class Position extends TPosition { this.getArgumentIndex() = -1 and result = call.getQualifier() } + + /** + * Holds if this position is the synthetic argument for an address of a + * constructor used for functions which perform "perfect forwarding". + */ + predicate isForward() { none() } } class DirectPosition extends Position, TDirectPosition { @@ -721,6 +822,16 @@ class FlowSummaryPosition extends Position, TFlowSummaryPosition { final override int getIndirectionIndex() { result = rk.getIndirectionIndex() } } +class ForwardPosition extends Position, TForwardPosition { + final override predicate isForward() { any() } + + override int getArgumentIndex() { none() } + + final override int getIndirectionIndex() { result = 0 } + + override string toString() { result = "forward" } +} + newtype TPosition = TDirectPosition(int argumentIndex) { exists(any(CallInstruction c).getArgument(argumentIndex)) @@ -740,6 +851,7 @@ newtype TPosition = indirectionIndex = [1 .. Ssa::getMaxIndirectionsForPRType(p.getUnspecifiedType())] ) } or + TForwardPosition() or TFlowSummaryPosition(ReturnKind rk) { FlowSummaryImpl::Private::relevantFlowSummaryPosition(rk) } private newtype TReturnKind = @@ -1258,6 +1370,19 @@ private predicate summarizedCallableIsManual(SummarizedCallable sc) { sc.asSummarizedCallable().hasManualModel() } +private DataFlowCallable getTarget(Declaration target) { + // Don't use the source callable if there is a manual model for the target. + not exists(SummarizedCallable sc | + sc.asSummarizedCallable() = target and + summarizedCallableIsManual(sc) + ) and + result.asSourceCallable() = target + or + // When there is no function body, or when we have a manual model, dispatch to the summary. + (not target.hasDefinition() or summarizedCallableIsManual(result)) and + result.asSummarizedCallable() = target +} + /** * A function call relevant for data flow. This includes calls from source * code and calls inside library callables with a flow summary. @@ -1293,20 +1418,7 @@ class DataFlowCall extends TDataFlowCall { * whether is it manual or generated. */ final DataFlowCallable getStaticCallTarget() { - exists(Declaration target | target = this.getStaticCallSourceTarget() | - // Don't use the source callable if there is a manual model for the - // target - not exists(SummarizedCallable sc | - sc.asSummarizedCallable() = target and - summarizedCallableIsManual(sc) - ) and - result.asSourceCallable() = target - or - // When there is no function body, or when we have a manual model then - // we dispatch to the summary. - (not target.hasDefinition() or summarizedCallableIsManual(result)) and - result.asSummarizedCallable() = target - ) + result = getTarget(this.getStaticCallSourceTarget()) } /** @@ -1493,6 +1605,8 @@ predicate nodeIsHidden(Node n) { n instanceof SsaSynthNode or n.(FlowSummaryNode).getSummaryNode().isHidden() + or + n instanceof ForwarderConstructorArgumentNode } predicate neverSkipInPathGraph(Node n) { @@ -1574,6 +1688,9 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) kind.isFunctionPointer() and creation.asInstruction().(FunctionAddressInstruction).getFunctionSymbol() = c.asSourceCallable() or + kind.isFunctionPointer() and + c = getTarget(creation.(ForwarderConstructorArgumentNode).getAConstructor()) + or kind.isFunctor() and exists(OperatorCall operator | operator = c.asSourceCallable() | isFunctorCreationWithoutConstructor(creation, operator) From 4f2fc96bedff0ec6117b848eff60454e6525b794 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 7 Sep 2026 20:14:15 +0100 Subject: [PATCH 07/13] C++: Accept test changes. --- .../dataflow/external-models/flow.expected | 188 +++++++++++++----- .../dataflow/external-models/test.cpp | 8 +- 2 files changed, 147 insertions(+), 49 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected index 65817b549a90..d66dc6d385f6 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected @@ -85,24 +85,26 @@ models | 84 | Summary: ; ; false; ymlStepGenerated; ; ; Argument[0]; ReturnValue; taint; df-generated | | 85 | Summary: ; ; false; ymlStepManual; ; ; Argument[0]; ReturnValue; taint; manual | | 86 | Summary: ; ; false; ymlStepManual_with_body; ; ; Argument[0]; ReturnValue; taint; manual | -| 87 | Summary: ; MyString; true; operator[]; ; ; Argument[-1]; ReturnValue[*]; taint; manual | -| 88 | Summary: ; MyString; true; operator[]; ; ; ReturnValue[*]; Argument[-1]; taint; manual | -| 89 | Summary: ; ReverseFlow; true; get_ptr; ; ; ReturnValue[*]; Argument[-1].Field[ReverseFlow::value]; value; manual | -| 90 | Summary: ; TemplateClass1; true; templateFunction2; (U,V); ; Argument[1]; ReturnValue; value; manual | -| 91 | Summary: ; TemplateClass1; false; templateFunction; (T,U); ; Argument[0]; ReturnValue; value; manual | -| 92 | Summary: ; TemplateClass2; true; function; (U,T); ; Argument[1]; ReturnValue; value; manual | -| 93 | Summary: Azure::Core::IO; BodyStream; true; Read; ; ; Argument[-1]; Argument[*0]; taint; manual | -| 94 | Summary: Azure::Core::IO; BodyStream; true; ReadToCount; ; ; Argument[-1]; Argument[*0]; taint; manual | -| 95 | Summary: Azure::Core::IO; BodyStream; true; ReadToEnd; ; ; Argument[-1]; ReturnValue.Element; taint; manual | -| 96 | Summary: Azure; Nullable; true; Value; ; ; Argument[-1]; ReturnValue[*]; taint; manual | -| 97 | Summary: BloombergLP::bdlbb; Blob; true; buffer; ; ; Argument[-1]; ReturnValue[*]; taint; manual | -| 98 | Summary: BloombergLP::bdlbb; BlobBuffer; true; buffer; ; ; Argument[-1]; ReturnValue[*]; taint; manual | -| 99 | Summary: BloombergLP::bdlbb; BlobBuffer; true; data; ; ; Argument[-1]; ReturnValue[*]; taint; manual | -| 100 | Summary: BloombergLP::bdlbb; BlobUtil; true; copy; (Blob *,int,const Blob &,int,int); ; Argument[*2]; Argument[*0]; taint; manual | -| 101 | Summary: BloombergLP::bdlbb; BlobUtil; true; copy; (Blob *,int,const char *,int); ; Argument[*2]; Argument[*0]; taint; manual | -| 102 | Summary: BloombergLP::bdlbb; BlobUtil; true; copy; (char *,const Blob &,int,int); ; Argument[*1]; Argument[*0]; taint; manual | -| 103 | Summary: BloombergLP::bdlbb; BlobUtil; true; getContiguousRangeOrCopy; ; ; Argument[*1]; ReturnValue[*]; taint; manual | -| 104 | Summary: boost::asio; ; false; buffer; ; ; Argument[*0]; ReturnValue; taint; manual | +| 87 | Summary: ; Container; true; get; ; ; Argument[-1].Element; ReturnValue[*]; value; manual | +| 88 | Summary: ; Forwarder; true; get; ; ; Argument[-1]; ReturnValue; value; manual | +| 89 | Summary: ; MyString; true; operator[]; ; ; Argument[-1]; ReturnValue[*]; taint; manual | +| 90 | Summary: ; MyString; true; operator[]; ; ; ReturnValue[*]; Argument[-1]; taint; manual | +| 91 | Summary: ; ReverseFlow; true; get_ptr; ; ; ReturnValue[*]; Argument[-1].Field[ReverseFlow::value]; value; manual | +| 92 | Summary: ; TemplateClass1; true; templateFunction2; (U,V); ; Argument[1]; ReturnValue; value; manual | +| 93 | Summary: ; TemplateClass1; false; templateFunction; (T,U); ; Argument[0]; ReturnValue; value; manual | +| 94 | Summary: ; TemplateClass2; true; function; (U,T); ; Argument[1]; ReturnValue; value; manual | +| 95 | Summary: Azure::Core::IO; BodyStream; true; Read; ; ; Argument[-1]; Argument[*0]; taint; manual | +| 96 | Summary: Azure::Core::IO; BodyStream; true; ReadToCount; ; ; Argument[-1]; Argument[*0]; taint; manual | +| 97 | Summary: Azure::Core::IO; BodyStream; true; ReadToEnd; ; ; Argument[-1]; ReturnValue.Element; taint; manual | +| 98 | Summary: Azure; Nullable; true; Value; ; ; Argument[-1]; ReturnValue[*]; taint; manual | +| 99 | Summary: BloombergLP::bdlbb; Blob; true; buffer; ; ; Argument[-1]; ReturnValue[*]; taint; manual | +| 100 | Summary: BloombergLP::bdlbb; BlobBuffer; true; buffer; ; ; Argument[-1]; ReturnValue[*]; taint; manual | +| 101 | Summary: BloombergLP::bdlbb; BlobBuffer; true; data; ; ; Argument[-1]; ReturnValue[*]; taint; manual | +| 102 | Summary: BloombergLP::bdlbb; BlobUtil; true; copy; (Blob *,int,const Blob &,int,int); ; Argument[*2]; Argument[*0]; taint; manual | +| 103 | Summary: BloombergLP::bdlbb; BlobUtil; true; copy; (Blob *,int,const char *,int); ; Argument[*2]; Argument[*0]; taint; manual | +| 104 | Summary: BloombergLP::bdlbb; BlobUtil; true; copy; (char *,const Blob &,int,int); ; Argument[*1]; Argument[*0]; taint; manual | +| 105 | Summary: BloombergLP::bdlbb; BlobUtil; true; getContiguousRangeOrCopy; ; ; Argument[*1]; ReturnValue[*]; taint; manual | +| 106 | Summary: boost::asio; ; false; buffer; ; ; Argument[*0]; ReturnValue; taint; manual | edges | asio_streams.cpp:87:34:87:44 | read_until output argument | asio_streams.cpp:91:7:91:17 | recv_buffer | provenance | Src:MaD:56 | | asio_streams.cpp:87:34:87:44 | read_until output argument | asio_streams.cpp:93:29:93:39 | recv_buffer | provenance | Src:MaD:56 Sink:MaD:4 | @@ -111,16 +113,16 @@ edges | asio_streams.cpp:100:44:100:62 | call to buffer | asio_streams.cpp:100:44:100:62 | call to buffer | provenance | | | asio_streams.cpp:100:44:100:62 | call to buffer | asio_streams.cpp:101:7:101:17 | send_buffer | provenance | | | asio_streams.cpp:100:44:100:62 | call to buffer | asio_streams.cpp:103:29:103:39 | send_buffer | provenance | Sink:MaD:4 | -| asio_streams.cpp:100:64:100:71 | *send_str | asio_streams.cpp:100:44:100:62 | call to buffer | provenance | MaD:104 | +| asio_streams.cpp:100:64:100:71 | *send_str | asio_streams.cpp:100:44:100:62 | call to buffer | provenance | MaD:106 | | azure.cpp:253:48:253:60 | *call to GetBodyStream | azure.cpp:257:5:257:8 | *resp | provenance | | | azure.cpp:253:48:253:60 | *call to GetBodyStream | azure.cpp:262:5:262:8 | *resp | provenance | | | azure.cpp:253:48:253:60 | *call to GetBodyStream | azure.cpp:266:38:266:41 | *resp | provenance | | | azure.cpp:253:48:253:60 | call to GetBodyStream | azure.cpp:253:48:253:60 | *call to GetBodyStream | provenance | Src:MaD:53 | -| azure.cpp:257:5:257:8 | *resp | azure.cpp:257:16:257:21 | Read output argument | provenance | MaD:93 | +| azure.cpp:257:5:257:8 | *resp | azure.cpp:257:16:257:21 | Read output argument | provenance | MaD:95 | | azure.cpp:257:16:257:21 | Read output argument | azure.cpp:258:10:258:16 | * ... | provenance | | -| azure.cpp:262:5:262:8 | *resp | azure.cpp:262:23:262:28 | ReadToCount output argument | provenance | MaD:94 | +| azure.cpp:262:5:262:8 | *resp | azure.cpp:262:23:262:28 | ReadToCount output argument | provenance | MaD:96 | | azure.cpp:262:23:262:28 | ReadToCount output argument | azure.cpp:263:10:263:16 | * ... | provenance | | -| azure.cpp:266:38:266:41 | *resp | azure.cpp:266:44:266:52 | call to ReadToEnd [element] | provenance | MaD:95 | +| azure.cpp:266:38:266:41 | *resp | azure.cpp:266:44:266:52 | call to ReadToEnd [element] | provenance | MaD:97 | | azure.cpp:266:44:266:52 | call to ReadToEnd [element] | azure.cpp:266:44:266:52 | call to ReadToEnd [element] | provenance | | | azure.cpp:266:44:266:52 | call to ReadToEnd [element] | azure.cpp:267:10:267:12 | vec [element] | provenance | | | azure.cpp:267:10:267:12 | vec [element] | azure.cpp:267:10:267:12 | vec | provenance | | @@ -136,10 +138,10 @@ edges | azure.cpp:278:10:278:13 | body | azure.cpp:278:10:278:13 | body | provenance | | | azure.cpp:281:68:281:84 | *call to ExtractBodyStream | azure.cpp:282:21:282:23 | *call to get | provenance | | | azure.cpp:281:68:281:84 | call to ExtractBodyStream | azure.cpp:281:68:281:84 | *call to ExtractBodyStream | provenance | Src:MaD:50 | -| azure.cpp:282:21:282:23 | *call to get | azure.cpp:282:28:282:36 | call to ReadToEnd [element] | provenance | MaD:95 | +| azure.cpp:282:21:282:23 | *call to get | azure.cpp:282:28:282:36 | call to ReadToEnd [element] | provenance | MaD:97 | | azure.cpp:282:28:282:36 | call to ReadToEnd [element] | azure.cpp:282:10:282:38 | call to ReadToEnd | provenance | | | azure.cpp:282:28:282:36 | call to ReadToEnd [element] | azure.cpp:282:28:282:36 | call to ReadToEnd [element] | provenance | | -| azure.cpp:289:24:289:56 | call to GetHeader | azure.cpp:289:63:289:65 | call to Value | provenance | MaD:96 | +| azure.cpp:289:24:289:56 | call to GetHeader | azure.cpp:289:63:289:65 | call to Value | provenance | MaD:98 | | azure.cpp:289:32:289:40 | call to GetHeader | azure.cpp:289:24:289:56 | call to GetHeader | provenance | | | azure.cpp:289:32:289:40 | call to GetHeader | azure.cpp:289:32:289:40 | call to GetHeader | provenance | Src:MaD:54 | | azure.cpp:289:63:289:65 | call to Value | azure.cpp:289:63:289:65 | call to Value | provenance | | @@ -153,36 +155,36 @@ edges | azure.cpp:295:10:295:20 | contentType | azure.cpp:295:10:295:20 | contentType | provenance | | | bdlbb.cpp:54:16:54:23 | call to source | bdlbb.cpp:56:49:56:52 | *call to data | provenance | TaintFunction | | bdlbb.cpp:56:37:56:41 | copy output argument | bdlbb.cpp:58:42:58:45 | *blob | provenance | | -| bdlbb.cpp:56:49:56:52 | *call to data | bdlbb.cpp:56:37:56:41 | copy output argument | provenance | MaD:101 | +| bdlbb.cpp:56:49:56:52 | *call to data | bdlbb.cpp:56:37:56:41 | copy output argument | provenance | MaD:103 | | bdlbb.cpp:58:37:58:39 | copy output argument | bdlbb.cpp:59:7:59:10 | * ... | provenance | | -| bdlbb.cpp:58:42:58:45 | *blob | bdlbb.cpp:58:37:58:39 | copy output argument | provenance | MaD:102 | +| bdlbb.cpp:58:42:58:45 | *blob | bdlbb.cpp:58:37:58:39 | copy output argument | provenance | MaD:104 | | bdlbb.cpp:63:16:63:23 | call to source | bdlbb.cpp:65:49:65:52 | *call to data | provenance | TaintFunction | | bdlbb.cpp:65:37:65:41 | copy output argument | bdlbb.cpp:66:18:66:21 | *blob | provenance | | -| bdlbb.cpp:65:49:65:52 | *call to data | bdlbb.cpp:65:37:65:41 | copy output argument | provenance | MaD:101 | -| bdlbb.cpp:66:18:66:21 | *blob | bdlbb.cpp:66:29:66:32 | *call to buffer | provenance | MaD:97 | +| bdlbb.cpp:65:49:65:52 | *call to data | bdlbb.cpp:65:37:65:41 | copy output argument | provenance | MaD:103 | +| bdlbb.cpp:66:18:66:21 | *blob | bdlbb.cpp:66:29:66:32 | *call to buffer | provenance | MaD:99 | | bdlbb.cpp:66:18:66:38 | *call to data | bdlbb.cpp:66:18:66:38 | *call to data | provenance | | | bdlbb.cpp:66:18:66:38 | *call to data | bdlbb.cpp:67:7:67:8 | * ... | provenance | | -| bdlbb.cpp:66:29:66:32 | *call to buffer | bdlbb.cpp:66:18:66:38 | *call to data | provenance | MaD:99 | +| bdlbb.cpp:66:29:66:32 | *call to buffer | bdlbb.cpp:66:18:66:38 | *call to data | provenance | MaD:101 | | bdlbb.cpp:72:16:72:23 | call to source | bdlbb.cpp:74:49:74:52 | *call to data | provenance | TaintFunction | | bdlbb.cpp:74:37:74:41 | copy output argument | bdlbb.cpp:75:18:75:21 | *blob | provenance | | -| bdlbb.cpp:74:49:74:52 | *call to data | bdlbb.cpp:74:37:74:41 | copy output argument | provenance | MaD:101 | -| bdlbb.cpp:75:18:75:21 | *blob | bdlbb.cpp:75:29:75:32 | *call to buffer | provenance | MaD:97 | +| bdlbb.cpp:74:49:74:52 | *call to data | bdlbb.cpp:74:37:74:41 | copy output argument | provenance | MaD:103 | +| bdlbb.cpp:75:18:75:21 | *blob | bdlbb.cpp:75:29:75:32 | *call to buffer | provenance | MaD:99 | | bdlbb.cpp:75:18:75:46 | call to get | bdlbb.cpp:76:7:76:8 | * ... | provenance | | -| bdlbb.cpp:75:29:75:32 | *call to buffer | bdlbb.cpp:75:39:75:41 | *call to buffer | provenance | MaD:98 | +| bdlbb.cpp:75:29:75:32 | *call to buffer | bdlbb.cpp:75:39:75:41 | *call to buffer | provenance | MaD:100 | | bdlbb.cpp:75:39:75:41 | *call to buffer | bdlbb.cpp:75:18:75:46 | call to get | provenance | DataFlowFunction | | bdlbb.cpp:80:16:80:23 | call to source | bdlbb.cpp:82:49:82:52 | *call to data | provenance | TaintFunction | | bdlbb.cpp:82:37:82:41 | copy output argument | bdlbb.cpp:84:72:84:75 | *blob | provenance | | -| bdlbb.cpp:82:49:82:52 | *call to data | bdlbb.cpp:82:37:82:41 | copy output argument | provenance | MaD:101 | +| bdlbb.cpp:82:49:82:52 | *call to data | bdlbb.cpp:82:37:82:41 | copy output argument | provenance | MaD:103 | | bdlbb.cpp:84:12:84:65 | *call to getContiguousRangeOrCopy | bdlbb.cpp:84:12:84:65 | *call to getContiguousRangeOrCopy | provenance | | | bdlbb.cpp:84:12:84:65 | *call to getContiguousRangeOrCopy | bdlbb.cpp:85:7:85:8 | * ... | provenance | | -| bdlbb.cpp:84:72:84:75 | *blob | bdlbb.cpp:84:12:84:65 | *call to getContiguousRangeOrCopy | provenance | MaD:103 | +| bdlbb.cpp:84:72:84:75 | *blob | bdlbb.cpp:84:12:84:65 | *call to getContiguousRangeOrCopy | provenance | MaD:105 | | bdlbb.cpp:90:16:90:23 | call to source | bdlbb.cpp:92:48:92:51 | *call to data | provenance | TaintFunction | | bdlbb.cpp:92:37:92:40 | copy output argument | bdlbb.cpp:94:46:94:48 | *src | provenance | | -| bdlbb.cpp:92:48:92:51 | *call to data | bdlbb.cpp:92:37:92:40 | copy output argument | provenance | MaD:101 | +| bdlbb.cpp:92:48:92:51 | *call to data | bdlbb.cpp:92:37:92:40 | copy output argument | provenance | MaD:103 | | bdlbb.cpp:94:37:94:40 | copy output argument | bdlbb.cpp:96:42:96:44 | *dst | provenance | | -| bdlbb.cpp:94:46:94:48 | *src | bdlbb.cpp:94:37:94:40 | copy output argument | provenance | MaD:100 | +| bdlbb.cpp:94:46:94:48 | *src | bdlbb.cpp:94:37:94:40 | copy output argument | provenance | MaD:102 | | bdlbb.cpp:96:37:96:39 | copy output argument | bdlbb.cpp:97:7:97:10 | * ... | provenance | | -| bdlbb.cpp:96:42:96:44 | *dst | bdlbb.cpp:96:37:96:39 | copy output argument | provenance | MaD:102 | +| bdlbb.cpp:96:42:96:44 | *dst | bdlbb.cpp:96:37:96:39 | copy output argument | provenance | MaD:104 | | test.cpp:7:47:7:52 | value2 | test.cpp:7:64:7:69 | value2 | provenance | | | test.cpp:7:64:7:69 | value2 | test.cpp:7:5:7:30 | *ymlStepGenerated_with_body | provenance | | | test.cpp:10:10:10:18 | call to ymlSource | test.cpp:10:10:10:18 | call to ymlSource | provenance | Src:MaD:48 | @@ -234,27 +236,27 @@ edges | test.cpp:133:10:133:18 | call to ymlSource | test.cpp:134:45:134:45 | x | provenance | | | test.cpp:134:13:134:43 | call to templateFunction | test.cpp:134:13:134:43 | call to templateFunction | provenance | | | test.cpp:134:13:134:43 | call to templateFunction | test.cpp:135:10:135:10 | y | provenance | Sink:MaD:3 | -| test.cpp:134:45:134:45 | x | test.cpp:134:13:134:43 | call to templateFunction | provenance | MaD:91 | +| test.cpp:134:45:134:45 | x | test.cpp:134:13:134:43 | call to templateFunction | provenance | MaD:93 | | test.cpp:146:10:146:18 | call to ymlSource | test.cpp:146:10:146:18 | call to ymlSource | provenance | Src:MaD:48 | | test.cpp:146:10:146:18 | call to ymlSource | test.cpp:148:26:148:26 | x | provenance | | | test.cpp:148:10:148:27 | call to function | test.cpp:148:10:148:27 | call to function | provenance | | | test.cpp:148:10:148:27 | call to function | test.cpp:149:10:149:10 | z | provenance | Sink:MaD:3 | -| test.cpp:148:26:148:26 | x | test.cpp:148:10:148:27 | call to function | provenance | MaD:92 | +| test.cpp:148:26:148:26 | x | test.cpp:148:10:148:27 | call to function | provenance | MaD:94 | | test.cpp:155:10:155:18 | call to ymlSource | test.cpp:155:10:155:18 | call to ymlSource | provenance | Src:MaD:48 | | test.cpp:155:10:155:18 | call to ymlSource | test.cpp:157:26:157:26 | x | provenance | | | test.cpp:157:13:157:20 | call to function | test.cpp:157:13:157:20 | call to function | provenance | | | test.cpp:157:13:157:20 | call to function | test.cpp:158:10:158:10 | z | provenance | Sink:MaD:3 | -| test.cpp:157:26:157:26 | x | test.cpp:157:13:157:20 | call to function | provenance | MaD:92 | +| test.cpp:157:26:157:26 | x | test.cpp:157:13:157:20 | call to function | provenance | MaD:94 | | test.cpp:164:34:164:34 | x | test.cpp:165:69:165:69 | x | provenance | | | test.cpp:165:12:165:64 | call to templateFunction2 | test.cpp:164:7:164:7 | *templateFunction3 | provenance | | | test.cpp:165:12:165:64 | call to templateFunction2 | test.cpp:165:12:165:64 | call to templateFunction2 | provenance | | -| test.cpp:165:69:165:69 | x | test.cpp:165:12:165:64 | call to templateFunction2 | provenance | MaD:90 | +| test.cpp:165:69:165:69 | x | test.cpp:165:12:165:64 | call to templateFunction2 | provenance | MaD:92 | | test.cpp:170:10:170:18 | call to ymlSource | test.cpp:170:10:170:18 | call to ymlSource | provenance | Src:MaD:48 | | test.cpp:170:10:170:18 | call to ymlSource | test.cpp:172:51:172:51 | x | provenance | | | test.cpp:172:13:172:44 | call to templateFunction3 | test.cpp:172:13:172:44 | call to templateFunction3 | provenance | | | test.cpp:172:13:172:44 | call to templateFunction3 | test.cpp:173:10:173:10 | y | provenance | Sink:MaD:3 | | test.cpp:172:51:172:51 | x | test.cpp:164:34:164:34 | x | provenance | | -| test.cpp:172:51:172:51 | x | test.cpp:172:13:172:44 | call to templateFunction3 | provenance | MaD:90 | +| test.cpp:172:51:172:51 | x | test.cpp:172:13:172:44 | call to templateFunction3 | provenance | MaD:92 | | test.cpp:186:2:186:2 | *s [post update] [myField] | test.cpp:187:33:187:34 | *& ... [myField] | provenance | | | test.cpp:186:2:186:24 | ... = ... | test.cpp:186:2:186:2 | *s [post update] [myField] | provenance | | | test.cpp:186:14:186:22 | call to ymlSource | test.cpp:186:2:186:24 | ... = ... | provenance | Src:MaD:48 | @@ -268,15 +270,15 @@ edges | test.cpp:200:10:200:33 | call to read_field_from_struct_2 | test.cpp:201:10:201:10 | x | provenance | Sink:MaD:3 | | test.cpp:200:35:200:36 | *& ... [myField] | test.cpp:200:10:200:33 | call to read_field_from_struct_2 | provenance | MaD:83 | | test.cpp:216:3:216:4 | get_ptr output argument [value] | test.cpp:217:11:217:12 | *rf [value] | provenance | | -| test.cpp:216:3:216:28 | ... = ... | test.cpp:216:3:216:4 | get_ptr output argument [value] | provenance | MaD:89 | +| test.cpp:216:3:216:28 | ... = ... | test.cpp:216:3:216:4 | get_ptr output argument [value] | provenance | MaD:91 | | test.cpp:216:18:216:26 | call to ymlSource | test.cpp:216:3:216:28 | ... = ... | provenance | Src:MaD:48 | | test.cpp:217:11:217:12 | *rf [value] | test.cpp:217:14:217:18 | value | provenance | | | test.cpp:217:14:217:18 | value | test.cpp:217:14:217:18 | value | provenance | | | test.cpp:217:14:217:18 | value | test.cpp:218:11:218:11 | x | provenance | Sink:MaD:3 | | test.cpp:222:3:222:3 | operator[] output argument | test.cpp:223:12:223:12 | *s | provenance | | -| test.cpp:222:3:222:20 | ... = ... | test.cpp:222:3:222:3 | operator[] output argument | provenance | MaD:88 | +| test.cpp:222:3:222:20 | ... = ... | test.cpp:222:3:222:3 | operator[] output argument | provenance | MaD:90 | | test.cpp:222:10:222:18 | call to ymlSource | test.cpp:222:3:222:20 | ... = ... | provenance | Src:MaD:48 | -| test.cpp:223:12:223:12 | *s | test.cpp:223:13:223:15 | call to operator[] | provenance | MaD:87 | +| test.cpp:223:12:223:12 | *s | test.cpp:223:13:223:15 | call to operator[] | provenance | MaD:89 | | test.cpp:223:13:223:15 | call to operator[] | test.cpp:223:13:223:15 | call to operator[] | provenance | | | test.cpp:223:13:223:15 | call to operator[] | test.cpp:224:11:224:11 | c | provenance | Sink:MaD:3 | | test.cpp:242:29:242:29 | *s [value] | test.cpp:243:10:243:10 | *s [value] | provenance | | @@ -351,6 +353,52 @@ edges | test.cpp:329:12:329:16 | value | test.cpp:329:12:329:16 | value | provenance | Sink:MaD:3 | | test.cpp:331:10:331:19 | * ... | test.cpp:331:10:331:19 | * ... | provenance | Sink:MaD:3 | | test.cpp:331:11:331:11 | *s [*pointer] | test.cpp:331:10:331:19 | * ... | provenance | | +| test.cpp:341:30:341:32 | arg | test.cpp:342:5:342:17 | ... = ... | provenance | | +| test.cpp:342:5:342:8 | *this [post update] [s] | test.cpp:341:3:341:22 | *this [Return] [s] | provenance | | +| test.cpp:342:5:342:17 | ... = ... | test.cpp:342:5:342:8 | *this [post update] [s] | provenance | | +| test.cpp:345:38:345:40 | arg | test.cpp:346:5:346:18 | ... = ... | provenance | | +| test.cpp:346:5:346:8 | *this [post update] [ul] | test.cpp:345:3:345:22 | *this [Return] [ul] | provenance | | +| test.cpp:346:5:346:18 | ... = ... | test.cpp:346:5:346:8 | *this [post update] [ul] | provenance | | +| test.cpp:361:15:361:23 | call to ymlSource | test.cpp:361:15:361:25 | call to ymlSource | provenance | Src:MaD:48 | +| test.cpp:361:15:361:25 | call to ymlSource | test.cpp:362:15:362:15 | *x | provenance | | +| test.cpp:362:5:362:5 | forward output argument [s] | test.cpp:364:30:364:30 | *f [s] | provenance | | +| test.cpp:362:15:362:15 | *x | test.cpp:341:30:341:32 | arg | provenance | | +| test.cpp:362:15:362:15 | *x | test.cpp:362:5:362:5 | forward output argument [s] | provenance | | +| test.cpp:364:30:364:30 | *f [s] | test.cpp:364:32:364:34 | call to get [s] | provenance | MaD:88 | +| test.cpp:364:32:364:34 | call to get [s] | test.cpp:364:32:364:34 | call to get [s] | provenance | | +| test.cpp:364:32:364:34 | call to get [s] | test.cpp:365:13:365:13 | *c [s] | provenance | | +| test.cpp:365:13:365:13 | *c [s] | test.cpp:365:13:365:15 | s | provenance | | +| test.cpp:365:13:365:13 | *c [s] | test.cpp:365:15:365:15 | s | provenance | Sink:MaD:3 | +| test.cpp:365:13:365:15 | s | test.cpp:365:15:365:15 | s | provenance | Sink:MaD:3 | +| test.cpp:370:24:370:32 | call to ymlSource | test.cpp:370:24:370:34 | call to ymlSource | provenance | Src:MaD:48 | +| test.cpp:370:24:370:34 | call to ymlSource | test.cpp:371:15:371:16 | *ul | provenance | | +| test.cpp:371:5:371:5 | forward output argument [ul] | test.cpp:373:30:373:30 | *f [ul] | provenance | | +| test.cpp:371:15:371:16 | *ul | test.cpp:345:38:345:40 | arg | provenance | | +| test.cpp:371:15:371:16 | *ul | test.cpp:371:5:371:5 | forward output argument [ul] | provenance | | +| test.cpp:373:30:373:30 | *f [ul] | test.cpp:373:32:373:34 | call to get [ul] | provenance | MaD:88 | +| test.cpp:373:32:373:34 | call to get [ul] | test.cpp:373:32:373:34 | call to get [ul] | provenance | | +| test.cpp:373:32:373:34 | call to get [ul] | test.cpp:375:13:375:13 | *c [ul] | provenance | | +| test.cpp:375:13:375:13 | *c [ul] | test.cpp:375:13:375:16 | ul | provenance | | +| test.cpp:375:13:375:13 | *c [ul] | test.cpp:375:15:375:16 | ul | provenance | Sink:MaD:3 | +| test.cpp:375:13:375:16 | ul | test.cpp:375:15:375:16 | ul | provenance | Sink:MaD:3 | +| test.cpp:394:11:394:19 | call to ymlSource | test.cpp:394:11:394:19 | call to ymlSource | provenance | Src:MaD:48 | +| test.cpp:394:11:394:19 | call to ymlSource | test.cpp:395:16:395:16 | *x | provenance | | +| test.cpp:395:3:395:3 | emplace output argument [element, x] | test.cpp:397:15:397:15 | *c [element, x] | provenance | | +| test.cpp:395:16:395:16 | *x | test.cpp:395:3:395:3 | emplace output argument [element, x] | provenance | | +| test.cpp:397:15:397:15 | *c [element, x] | test.cpp:397:20:397:22 | call to get [x] | provenance | MaD:87 | +| test.cpp:397:20:397:22 | call to get [x] | test.cpp:397:20:397:22 | call to get [x] | provenance | | +| test.cpp:397:20:397:22 | call to get [x] | test.cpp:398:11:398:11 | *e [x] | provenance | | +| test.cpp:398:11:398:11 | *e [x] | test.cpp:398:13:398:13 | x | provenance | | +| test.cpp:398:13:398:13 | x | test.cpp:398:13:398:13 | x | provenance | Sink:MaD:3 | +| test.cpp:408:11:408:19 | call to ymlSource | test.cpp:408:11:408:19 | call to ymlSource | provenance | Src:MaD:48 | +| test.cpp:408:11:408:19 | call to ymlSource | test.cpp:409:16:409:16 | *x | provenance | | +| test.cpp:409:3:409:3 | emplace output argument [element, x] | test.cpp:411:34:411:34 | *c [element, x] | provenance | | +| test.cpp:409:16:409:16 | *x | test.cpp:409:3:409:3 | emplace output argument [element, x] | provenance | | +| test.cpp:411:34:411:34 | *c [element, x] | test.cpp:411:39:411:41 | call to get [x] | provenance | MaD:87 | +| test.cpp:411:39:411:41 | call to get [x] | test.cpp:411:39:411:41 | call to get [x] | provenance | | +| test.cpp:411:39:411:41 | call to get [x] | test.cpp:412:11:412:11 | *e [x] | provenance | | +| test.cpp:412:11:412:11 | *e [x] | test.cpp:412:13:412:13 | x | provenance | | +| test.cpp:412:13:412:13 | x | test.cpp:412:13:412:13 | x | provenance | Sink:MaD:3 | | windows.cpp:22:15:22:29 | *call to GetCommandLineA | windows.cpp:24:8:24:11 | * ... | provenance | | | windows.cpp:22:15:22:29 | *call to GetCommandLineA | windows.cpp:27:36:27:38 | *cmd | provenance | | | windows.cpp:22:15:22:29 | call to GetCommandLineA | windows.cpp:22:15:22:29 | *call to GetCommandLineA | provenance | Src:MaD:5 | @@ -811,6 +859,54 @@ nodes | test.cpp:331:10:331:19 | * ... | semmle.label | * ... | | test.cpp:331:11:331:11 | *s [*pointer] | semmle.label | *s [*pointer] | | test.cpp:334:10:334:16 | * ... | semmle.label | * ... | +| test.cpp:341:3:341:22 | *this [Return] [s] | semmle.label | *this [Return] [s] | +| test.cpp:341:30:341:32 | arg | semmle.label | arg | +| test.cpp:342:5:342:8 | *this [post update] [s] | semmle.label | *this [post update] [s] | +| test.cpp:342:5:342:17 | ... = ... | semmle.label | ... = ... | +| test.cpp:345:3:345:22 | *this [Return] [ul] | semmle.label | *this [Return] [ul] | +| test.cpp:345:38:345:40 | arg | semmle.label | arg | +| test.cpp:346:5:346:8 | *this [post update] [ul] | semmle.label | *this [post update] [ul] | +| test.cpp:346:5:346:18 | ... = ... | semmle.label | ... = ... | +| test.cpp:361:15:361:23 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:361:15:361:25 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:362:5:362:5 | forward output argument [s] | semmle.label | forward output argument [s] | +| test.cpp:362:15:362:15 | *x | semmle.label | *x | +| test.cpp:364:30:364:30 | *f [s] | semmle.label | *f [s] | +| test.cpp:364:32:364:34 | call to get [s] | semmle.label | call to get [s] | +| test.cpp:364:32:364:34 | call to get [s] | semmle.label | call to get [s] | +| test.cpp:365:13:365:13 | *c [s] | semmle.label | *c [s] | +| test.cpp:365:13:365:15 | s | semmle.label | s | +| test.cpp:365:15:365:15 | s | semmle.label | s | +| test.cpp:370:24:370:32 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:370:24:370:34 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:371:5:371:5 | forward output argument [ul] | semmle.label | forward output argument [ul] | +| test.cpp:371:15:371:16 | *ul | semmle.label | *ul | +| test.cpp:373:30:373:30 | *f [ul] | semmle.label | *f [ul] | +| test.cpp:373:32:373:34 | call to get [ul] | semmle.label | call to get [ul] | +| test.cpp:373:32:373:34 | call to get [ul] | semmle.label | call to get [ul] | +| test.cpp:375:13:375:13 | *c [ul] | semmle.label | *c [ul] | +| test.cpp:375:13:375:16 | ul | semmle.label | ul | +| test.cpp:375:15:375:16 | ul | semmle.label | ul | +| test.cpp:394:11:394:19 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:394:11:394:19 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:395:3:395:3 | emplace output argument [element, x] | semmle.label | emplace output argument [element, x] | +| test.cpp:395:16:395:16 | *x | semmle.label | *x | +| test.cpp:397:15:397:15 | *c [element, x] | semmle.label | *c [element, x] | +| test.cpp:397:20:397:22 | call to get [x] | semmle.label | call to get [x] | +| test.cpp:397:20:397:22 | call to get [x] | semmle.label | call to get [x] | +| test.cpp:398:11:398:11 | *e [x] | semmle.label | *e [x] | +| test.cpp:398:13:398:13 | x | semmle.label | x | +| test.cpp:398:13:398:13 | x | semmle.label | x | +| test.cpp:408:11:408:19 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:408:11:408:19 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:409:3:409:3 | emplace output argument [element, x] | semmle.label | emplace output argument [element, x] | +| test.cpp:409:16:409:16 | *x | semmle.label | *x | +| test.cpp:411:34:411:34 | *c [element, x] | semmle.label | *c [element, x] | +| test.cpp:411:39:411:41 | call to get [x] | semmle.label | call to get [x] | +| test.cpp:411:39:411:41 | call to get [x] | semmle.label | call to get [x] | +| test.cpp:412:11:412:11 | *e [x] | semmle.label | *e [x] | +| test.cpp:412:13:412:13 | x | semmle.label | x | +| test.cpp:412:13:412:13 | x | semmle.label | x | | windows.cpp:22:15:22:29 | *call to GetCommandLineA | semmle.label | *call to GetCommandLineA | | windows.cpp:22:15:22:29 | call to GetCommandLineA | semmle.label | call to GetCommandLineA | | windows.cpp:24:8:24:11 | * ... | semmle.label | * ... | @@ -1032,4 +1128,6 @@ nodes subpaths | test.cpp:32:41:32:41 | x | test.cpp:7:47:7:52 | value2 | test.cpp:7:5:7:30 | *ymlStepGenerated_with_body | test.cpp:32:11:32:36 | call to ymlStepGenerated_with_body | | test.cpp:172:51:172:51 | x | test.cpp:164:34:164:34 | x | test.cpp:164:7:164:7 | *templateFunction3 | test.cpp:172:13:172:44 | call to templateFunction3 | +| test.cpp:362:15:362:15 | *x | test.cpp:341:30:341:32 | arg | test.cpp:341:3:341:22 | *this [Return] [s] | test.cpp:362:5:362:5 | forward output argument [s] | +| test.cpp:371:15:371:16 | *ul | test.cpp:345:38:345:40 | arg | test.cpp:345:3:345:22 | *this [Return] [ul] | test.cpp:371:5:371:5 | forward output argument [ul] | testFailures diff --git a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp index 7865c177e4e6..4d6345710ce7 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp @@ -362,7 +362,7 @@ void forward_test() { f.forward(x); ConstructableFromInt c = f.get(); - ymlSink(c.s); // $ MISSING: ir + ymlSink(c.s); // $ ir ymlSink(c.ul); // clean } { @@ -372,7 +372,7 @@ void forward_test() { ConstructableFromInt c = f.get(); ymlSink(c.s); // clean - ymlSink(c.ul); // $ MISSING: ir + ymlSink(c.ul); // $ ir } } @@ -395,7 +395,7 @@ void forward_test_model() { c.emplace(0, x); Element e = c.get(); - ymlSink(e.x); // $ MISSING: ir + ymlSink(e.x); // $ ir } struct ElementWithDefaultArgument { @@ -409,5 +409,5 @@ void forward_test_model_with_default_argument() { c.emplace(0, x); ElementWithDefaultArgument e = c.get(); - ymlSink(e.x); // $ MISSING: ir + ymlSink(e.x); // $ ir } \ No newline at end of file From db2a462b933264a8a09e71abba7ccf14e9917d28 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 9 Sep 2026 11:26:30 +0100 Subject: [PATCH 08/13] Apply batched suggestions from code review C++: Accept Copilot suggestion Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../code/cpp/ir/implementation/aliased_ssa/Instruction.qll | 2 +- .../lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll | 2 +- .../code/cpp/ir/implementation/unaliased_ssa/Instruction.qll | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index 4d26df9726b3..40aec2243ff4 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -1750,7 +1750,7 @@ class CallInstruction extends Instruction { /** * Gets the number of positional arguments of the call. */ - final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgument()) } + final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgumentOperand()) } /** * Holds if the result is a side effect for the argument at the specified index, or `this` if diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll index 4d26df9726b3..40aec2243ff4 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -1750,7 +1750,7 @@ class CallInstruction extends Instruction { /** * Gets the number of positional arguments of the call. */ - final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgument()) } + final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgumentOperand()) } /** * Holds if the result is a side effect for the argument at the specified index, or `this` if diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index 4d26df9726b3..40aec2243ff4 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1750,7 +1750,7 @@ class CallInstruction extends Instruction { /** * Gets the number of positional arguments of the call. */ - final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgument()) } + final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgumentOperand()) } /** * Holds if the result is a side effect for the argument at the specified index, or `this` if From 908e32b00aba755cbc385695d0bbee33b663b0b4 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 9 Sep 2026 11:38:34 +0100 Subject: [PATCH 09/13] C++: Respond to Copilot comments. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index c9dedb4da82d..224094543dce 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1075,20 +1075,25 @@ private module Cached { import Cached /** Gets the constructor type selected by `constructorType` in a forwarding model. */ -bindingset[forwarder, type, name, constructorType] private Type getForwardedConstructorType( - Function forwarder, string type, string name, string constructorType + Function forwarder, string namespace, string type, boolean subtypes, string name, + string signature, string ext, string constructorType ) { - exists(string typeArguments, int index | - parseAngles(type, _, typeArguments, "") and - constructorType = getAtIndex(typeArguments, index) and - result = forwarder.getDeclaringType().getTemplateArgument(index) - ) - or - exists(string nameArguments, int index | - parseAngles(name, _, nameArguments, "") and - constructorType = getAtIndex(nameArguments, index) and - result = forwarder.getTemplateArgument(index) + exists(int index | + forwardsModel(namespace, type, subtypes, name, signature, ext, _, constructorType, _, _, _) and + forwarder = interpretElement(namespace, type, subtypes, name, signature, ext) + | + exists(string typeArguments | + parseAngles(type, _, typeArguments, "") and + constructorType = getAtIndex(typeArguments, index) and + result = forwarder.getDeclaringType().getTemplateArgument(index) + ) + or + exists(string nameArguments | + parseAngles(name, _, nameArguments, "") and + constructorType = getAtIndex(nameArguments, index) and + result = forwarder.getTemplateArgument(index) + ) ) } @@ -1110,9 +1115,14 @@ private predicate interpretForwardsModel( // function template, in which case we need to resolve that from the type // or function name. constructor.getDeclaringType() = - getForwardedConstructorType(forwarder, type, name, constructorType).getUnspecifiedType() + getForwardedConstructorType(forwarder, namespace, type, subtypes, name, signature, ext, + constructorType).getUnspecifiedType() or // Or the row specifies forwarding to a specific type. + not exists( + getForwardedConstructorType(forwarder, namespace, type, subtypes, name, signature, ext, + constructorType) + ) and classHasQualifiedName(constructor.getDeclaringType(), namespace, constructorType) ) } From 10981ae7d9c9024ed7df1a844ea257e4ccae0af2 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 9 Sep 2026 11:39:43 +0100 Subject: [PATCH 10/13] C++: Autoformat after Copilot suggestions. --- .../code/cpp/ir/implementation/aliased_ssa/Instruction.qll | 4 +++- .../lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll | 4 +++- .../code/cpp/ir/implementation/unaliased_ssa/Instruction.qll | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index 40aec2243ff4..5205e8d3f0ba 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -1750,7 +1750,9 @@ class CallInstruction extends Instruction { /** * Gets the number of positional arguments of the call. */ - final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgumentOperand()) } + final int getNumberOfPositionalArguments() { + result = count(this.getAPositionalArgumentOperand()) + } /** * Holds if the result is a side effect for the argument at the specified index, or `this` if diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll index 40aec2243ff4..5205e8d3f0ba 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -1750,7 +1750,9 @@ class CallInstruction extends Instruction { /** * Gets the number of positional arguments of the call. */ - final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgumentOperand()) } + final int getNumberOfPositionalArguments() { + result = count(this.getAPositionalArgumentOperand()) + } /** * Holds if the result is a side effect for the argument at the specified index, or `this` if diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index 40aec2243ff4..5205e8d3f0ba 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1750,7 +1750,9 @@ class CallInstruction extends Instruction { /** * Gets the number of positional arguments of the call. */ - final int getNumberOfPositionalArguments() { result = count(this.getAPositionalArgumentOperand()) } + final int getNumberOfPositionalArguments() { + result = count(this.getAPositionalArgumentOperand()) + } /** * Holds if the result is a side effect for the argument at the specified index, or `this` if From 1511488cbe43602a32e25d9599a3cbf4eb02e183 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 9 Sep 2026 11:40:57 +0100 Subject: [PATCH 11/13] C++: Fix spelling. --- .../semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index a60d3ca04cce..ca9170371dfa 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -654,7 +654,7 @@ predicate isForwarderConstructorArgumentNodeImpl(CallInstruction call) { * ``` * v.__emplace_back(x, &Foo) * ``` - * (nevermind that this is not real C++ since you cannot take the address of a + * (never mind that this is not real C++ since you cannot take the address of a * constructor.) * where `__emplace_back` invokes `Foo` with the `x` argument and returns the * result. From cca9f3fce1eaccd064ff69eb651741f6eee3da41 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 9 Sep 2026 11:59:17 +0100 Subject: [PATCH 12/13] C++: Add false positive from lack of overload handling. --- .../dataflow/external-models/flow.expected | 59 +++++++++++++++++++ .../dataflow/external-models/sinks.expected | 3 + .../dataflow/external-models/sources.expected | 1 + .../dataflow/external-models/test.cpp | 25 ++++++++ 4 files changed, 88 insertions(+) diff --git a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected index d66dc6d385f6..e88ac350f6c6 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected @@ -399,6 +399,34 @@ edges | test.cpp:411:39:411:41 | call to get [x] | test.cpp:412:11:412:11 | *e [x] | provenance | | | test.cpp:412:11:412:11 | *e [x] | test.cpp:412:13:412:13 | x | provenance | | | test.cpp:412:13:412:13 | x | test.cpp:412:13:412:13 | x | provenance | Sink:MaD:3 | +| test.cpp:417:3:417:28 | *ElementWithOverloadedArity [post update] [x] | test.cpp:417:3:417:28 | *this [Return] [x] | provenance | | +| test.cpp:417:34:417:38 | first | test.cpp:417:45:417:49 | first | provenance | | +| test.cpp:417:45:417:49 | first | test.cpp:417:3:417:28 | *ElementWithOverloadedArity [post update] [x] | provenance | | +| test.cpp:418:3:418:28 | *ElementWithOverloadedArity [post update] [x] | test.cpp:418:3:418:28 | *this [Return] [x] | provenance | | +| test.cpp:418:39:418:44 | second | test.cpp:418:51:418:56 | second | provenance | | +| test.cpp:418:51:418:56 | second | test.cpp:418:3:418:28 | *ElementWithOverloadedArity [post update] [x] | provenance | | +| test.cpp:422:11:422:19 | call to ymlSource | test.cpp:422:11:422:19 | call to ymlSource | provenance | Src:MaD:48 | +| test.cpp:422:11:422:19 | call to ymlSource | test.cpp:425:18:425:18 | *x | provenance | | +| test.cpp:422:11:422:19 | call to ymlSource | test.cpp:430:18:430:18 | *x | provenance | | +| test.cpp:422:11:422:19 | call to ymlSource | test.cpp:435:21:435:21 | *x | provenance | | +| test.cpp:425:5:425:5 | emplace output argument [element, x] | test.cpp:426:13:426:13 | *c [element, x] | provenance | | +| test.cpp:425:18:425:18 | *x | test.cpp:417:34:417:38 | first | provenance | | +| test.cpp:425:18:425:18 | *x | test.cpp:425:5:425:5 | emplace output argument [element, x] | provenance | | +| test.cpp:426:13:426:13 | *c [element, x] | test.cpp:426:18:426:20 | *call to get [x] | provenance | MaD:87 | +| test.cpp:426:18:426:20 | *call to get [x] | test.cpp:426:21:426:21 | x | provenance | | +| test.cpp:426:21:426:21 | x | test.cpp:426:21:426:21 | x | provenance | Sink:MaD:3 | +| test.cpp:430:5:430:5 | emplace output argument [element, x] | test.cpp:431:13:431:13 | *c [element, x] | provenance | | +| test.cpp:430:18:430:18 | *x | test.cpp:417:34:417:38 | first | provenance | | +| test.cpp:430:18:430:18 | *x | test.cpp:430:5:430:5 | emplace output argument [element, x] | provenance | | +| test.cpp:431:13:431:13 | *c [element, x] | test.cpp:431:18:431:20 | *call to get [x] | provenance | MaD:87 | +| test.cpp:431:18:431:20 | *call to get [x] | test.cpp:431:21:431:21 | x | provenance | | +| test.cpp:431:21:431:21 | x | test.cpp:431:21:431:21 | x | provenance | Sink:MaD:3 | +| test.cpp:435:5:435:5 | emplace output argument [element, x] | test.cpp:436:13:436:13 | *c [element, x] | provenance | | +| test.cpp:435:21:435:21 | *x | test.cpp:418:39:418:44 | second | provenance | | +| test.cpp:435:21:435:21 | *x | test.cpp:435:5:435:5 | emplace output argument [element, x] | provenance | | +| test.cpp:436:13:436:13 | *c [element, x] | test.cpp:436:18:436:20 | *call to get [x] | provenance | MaD:87 | +| test.cpp:436:18:436:20 | *call to get [x] | test.cpp:436:21:436:21 | x | provenance | | +| test.cpp:436:21:436:21 | x | test.cpp:436:21:436:21 | x | provenance | Sink:MaD:3 | | windows.cpp:22:15:22:29 | *call to GetCommandLineA | windows.cpp:24:8:24:11 | * ... | provenance | | | windows.cpp:22:15:22:29 | *call to GetCommandLineA | windows.cpp:27:36:27:38 | *cmd | provenance | | | windows.cpp:22:15:22:29 | call to GetCommandLineA | windows.cpp:22:15:22:29 | *call to GetCommandLineA | provenance | Src:MaD:5 | @@ -907,6 +935,34 @@ nodes | test.cpp:412:11:412:11 | *e [x] | semmle.label | *e [x] | | test.cpp:412:13:412:13 | x | semmle.label | x | | test.cpp:412:13:412:13 | x | semmle.label | x | +| test.cpp:417:3:417:28 | *ElementWithOverloadedArity [post update] [x] | semmle.label | *ElementWithOverloadedArity [post update] [x] | +| test.cpp:417:3:417:28 | *this [Return] [x] | semmle.label | *this [Return] [x] | +| test.cpp:417:34:417:38 | first | semmle.label | first | +| test.cpp:417:45:417:49 | first | semmle.label | first | +| test.cpp:418:3:418:28 | *ElementWithOverloadedArity [post update] [x] | semmle.label | *ElementWithOverloadedArity [post update] [x] | +| test.cpp:418:3:418:28 | *this [Return] [x] | semmle.label | *this [Return] [x] | +| test.cpp:418:39:418:44 | second | semmle.label | second | +| test.cpp:418:51:418:56 | second | semmle.label | second | +| test.cpp:422:11:422:19 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:422:11:422:19 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:425:5:425:5 | emplace output argument [element, x] | semmle.label | emplace output argument [element, x] | +| test.cpp:425:18:425:18 | *x | semmle.label | *x | +| test.cpp:426:13:426:13 | *c [element, x] | semmle.label | *c [element, x] | +| test.cpp:426:18:426:20 | *call to get [x] | semmle.label | *call to get [x] | +| test.cpp:426:21:426:21 | x | semmle.label | x | +| test.cpp:426:21:426:21 | x | semmle.label | x | +| test.cpp:430:5:430:5 | emplace output argument [element, x] | semmle.label | emplace output argument [element, x] | +| test.cpp:430:18:430:18 | *x | semmle.label | *x | +| test.cpp:431:13:431:13 | *c [element, x] | semmle.label | *c [element, x] | +| test.cpp:431:18:431:20 | *call to get [x] | semmle.label | *call to get [x] | +| test.cpp:431:21:431:21 | x | semmle.label | x | +| test.cpp:431:21:431:21 | x | semmle.label | x | +| test.cpp:435:5:435:5 | emplace output argument [element, x] | semmle.label | emplace output argument [element, x] | +| test.cpp:435:21:435:21 | *x | semmle.label | *x | +| test.cpp:436:13:436:13 | *c [element, x] | semmle.label | *c [element, x] | +| test.cpp:436:18:436:20 | *call to get [x] | semmle.label | *call to get [x] | +| test.cpp:436:21:436:21 | x | semmle.label | x | +| test.cpp:436:21:436:21 | x | semmle.label | x | | windows.cpp:22:15:22:29 | *call to GetCommandLineA | semmle.label | *call to GetCommandLineA | | windows.cpp:22:15:22:29 | call to GetCommandLineA | semmle.label | call to GetCommandLineA | | windows.cpp:24:8:24:11 | * ... | semmle.label | * ... | @@ -1130,4 +1186,7 @@ subpaths | test.cpp:172:51:172:51 | x | test.cpp:164:34:164:34 | x | test.cpp:164:7:164:7 | *templateFunction3 | test.cpp:172:13:172:44 | call to templateFunction3 | | test.cpp:362:15:362:15 | *x | test.cpp:341:30:341:32 | arg | test.cpp:341:3:341:22 | *this [Return] [s] | test.cpp:362:5:362:5 | forward output argument [s] | | test.cpp:371:15:371:16 | *ul | test.cpp:345:38:345:40 | arg | test.cpp:345:3:345:22 | *this [Return] [ul] | test.cpp:371:5:371:5 | forward output argument [ul] | +| test.cpp:425:18:425:18 | *x | test.cpp:417:34:417:38 | first | test.cpp:417:3:417:28 | *this [Return] [x] | test.cpp:425:5:425:5 | emplace output argument [element, x] | +| test.cpp:430:18:430:18 | *x | test.cpp:417:34:417:38 | first | test.cpp:417:3:417:28 | *this [Return] [x] | test.cpp:430:5:430:5 | emplace output argument [element, x] | +| test.cpp:435:21:435:21 | *x | test.cpp:418:39:418:44 | second | test.cpp:418:3:418:28 | *this [Return] [x] | test.cpp:435:5:435:5 | emplace output argument [element, x] | testFailures diff --git a/cpp/ql/test/library-tests/dataflow/external-models/sinks.expected b/cpp/ql/test/library-tests/dataflow/external-models/sinks.expected index 0701b0f7eeb2..6db6de3fc398 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/sinks.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/sinks.expected @@ -49,3 +49,6 @@ | test.cpp:375:15:375:16 | ul | test-sink | | test.cpp:398:13:398:13 | x | test-sink | | test.cpp:412:13:412:13 | x | test-sink | +| test.cpp:426:21:426:21 | x | test-sink | +| test.cpp:431:21:431:21 | x | test-sink | +| test.cpp:436:21:436:21 | x | test-sink | diff --git a/cpp/ql/test/library-tests/dataflow/external-models/sources.expected b/cpp/ql/test/library-tests/dataflow/external-models/sources.expected index 7f71ec61b6e4..bf4dd9810df4 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/sources.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/sources.expected @@ -23,6 +23,7 @@ | test.cpp:370:24:370:32 | call to ymlSource | local | | test.cpp:394:11:394:19 | call to ymlSource | local | | test.cpp:408:11:408:19 | call to ymlSource | local | +| test.cpp:422:11:422:19 | call to ymlSource | local | | windows.cpp:22:15:22:29 | call to GetCommandLineA | local | | windows.cpp:34:17:34:38 | call to GetEnvironmentStringsA | local | | windows.cpp:39:36:39:38 | GetEnvironmentVariableA output argument | local | diff --git a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp index 4d6345710ce7..94b961a853eb 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp @@ -410,4 +410,29 @@ void forward_test_model_with_default_argument() { ElementWithDefaultArgument e = c.get(); ymlSink(e.x); // $ ir +} + +struct ElementWithOverloadedArity { + int x; + ElementWithOverloadedArity(int first) : x(first) {} + ElementWithOverloadedArity(int, int second) : x(second) {} +}; + +void forward_test_constructor_arity() { + int x = ymlSource(); + { + Container c; + c.emplace(0, x); + ymlSink(c.get().x); // $ ir + } + { + Container c; + c.emplace(0, x, 0); + ymlSink(c.get().x); // $ SPURIOUS: ir + } + { + Container c; + c.emplace(0, 0, x); + ymlSink(c.get().x); // $ ir + } } \ No newline at end of file From 246c4864fe1a55cd2b186d8d6592e5383fd7a0a2 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 9 Sep 2026 12:08:34 +0100 Subject: [PATCH 13/13] C++: Fix FP by ensuring that the targeted constructor has at least as many parameters as the number of forwarded arguments. --- .../cpp/ir/dataflow/internal/DataFlowPrivate.qll | 4 ++++ .../dataflow/external-models/flow.expected | 14 -------------- .../dataflow/external-models/test.cpp | 2 +- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index ca9170371dfa..7faa8bb8681c 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -607,6 +607,10 @@ predicate forwardingCallTargetsConstructor( CallInstruction call, Cpp::Constructor constructor, int start ) { exists(int numberOfForwardedArguments | + numberOfForwardedArguments <= constructor.getNumberOfParameters() + or + constructor.isVarargs() + | External::forwards(call.getStaticCallTarget(), constructor, start) and call.getNumberOfPositionalArguments() = start + numberOfForwardedArguments and forall(int i | i = [0 .. constructor.getNumberOfParameters() - 1] | diff --git a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected index e88ac350f6c6..60638e39b726 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected @@ -407,7 +407,6 @@ edges | test.cpp:418:51:418:56 | second | test.cpp:418:3:418:28 | *ElementWithOverloadedArity [post update] [x] | provenance | | | test.cpp:422:11:422:19 | call to ymlSource | test.cpp:422:11:422:19 | call to ymlSource | provenance | Src:MaD:48 | | test.cpp:422:11:422:19 | call to ymlSource | test.cpp:425:18:425:18 | *x | provenance | | -| test.cpp:422:11:422:19 | call to ymlSource | test.cpp:430:18:430:18 | *x | provenance | | | test.cpp:422:11:422:19 | call to ymlSource | test.cpp:435:21:435:21 | *x | provenance | | | test.cpp:425:5:425:5 | emplace output argument [element, x] | test.cpp:426:13:426:13 | *c [element, x] | provenance | | | test.cpp:425:18:425:18 | *x | test.cpp:417:34:417:38 | first | provenance | | @@ -415,12 +414,6 @@ edges | test.cpp:426:13:426:13 | *c [element, x] | test.cpp:426:18:426:20 | *call to get [x] | provenance | MaD:87 | | test.cpp:426:18:426:20 | *call to get [x] | test.cpp:426:21:426:21 | x | provenance | | | test.cpp:426:21:426:21 | x | test.cpp:426:21:426:21 | x | provenance | Sink:MaD:3 | -| test.cpp:430:5:430:5 | emplace output argument [element, x] | test.cpp:431:13:431:13 | *c [element, x] | provenance | | -| test.cpp:430:18:430:18 | *x | test.cpp:417:34:417:38 | first | provenance | | -| test.cpp:430:18:430:18 | *x | test.cpp:430:5:430:5 | emplace output argument [element, x] | provenance | | -| test.cpp:431:13:431:13 | *c [element, x] | test.cpp:431:18:431:20 | *call to get [x] | provenance | MaD:87 | -| test.cpp:431:18:431:20 | *call to get [x] | test.cpp:431:21:431:21 | x | provenance | | -| test.cpp:431:21:431:21 | x | test.cpp:431:21:431:21 | x | provenance | Sink:MaD:3 | | test.cpp:435:5:435:5 | emplace output argument [element, x] | test.cpp:436:13:436:13 | *c [element, x] | provenance | | | test.cpp:435:21:435:21 | *x | test.cpp:418:39:418:44 | second | provenance | | | test.cpp:435:21:435:21 | *x | test.cpp:435:5:435:5 | emplace output argument [element, x] | provenance | | @@ -951,12 +944,6 @@ nodes | test.cpp:426:18:426:20 | *call to get [x] | semmle.label | *call to get [x] | | test.cpp:426:21:426:21 | x | semmle.label | x | | test.cpp:426:21:426:21 | x | semmle.label | x | -| test.cpp:430:5:430:5 | emplace output argument [element, x] | semmle.label | emplace output argument [element, x] | -| test.cpp:430:18:430:18 | *x | semmle.label | *x | -| test.cpp:431:13:431:13 | *c [element, x] | semmle.label | *c [element, x] | -| test.cpp:431:18:431:20 | *call to get [x] | semmle.label | *call to get [x] | -| test.cpp:431:21:431:21 | x | semmle.label | x | -| test.cpp:431:21:431:21 | x | semmle.label | x | | test.cpp:435:5:435:5 | emplace output argument [element, x] | semmle.label | emplace output argument [element, x] | | test.cpp:435:21:435:21 | *x | semmle.label | *x | | test.cpp:436:13:436:13 | *c [element, x] | semmle.label | *c [element, x] | @@ -1187,6 +1174,5 @@ subpaths | test.cpp:362:15:362:15 | *x | test.cpp:341:30:341:32 | arg | test.cpp:341:3:341:22 | *this [Return] [s] | test.cpp:362:5:362:5 | forward output argument [s] | | test.cpp:371:15:371:16 | *ul | test.cpp:345:38:345:40 | arg | test.cpp:345:3:345:22 | *this [Return] [ul] | test.cpp:371:5:371:5 | forward output argument [ul] | | test.cpp:425:18:425:18 | *x | test.cpp:417:34:417:38 | first | test.cpp:417:3:417:28 | *this [Return] [x] | test.cpp:425:5:425:5 | emplace output argument [element, x] | -| test.cpp:430:18:430:18 | *x | test.cpp:417:34:417:38 | first | test.cpp:417:3:417:28 | *this [Return] [x] | test.cpp:430:5:430:5 | emplace output argument [element, x] | | test.cpp:435:21:435:21 | *x | test.cpp:418:39:418:44 | second | test.cpp:418:3:418:28 | *this [Return] [x] | test.cpp:435:5:435:5 | emplace output argument [element, x] | testFailures diff --git a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp index 94b961a853eb..4bcfeb64473a 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp @@ -428,7 +428,7 @@ void forward_test_constructor_arity() { { Container c; c.emplace(0, x, 0); - ymlSink(c.get().x); // $ SPURIOUS: ir + ymlSink(c.get().x); // clean } { Container c;