From 3337804ac0c44388e12d44ddaa3903fec4cc0410 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Sun, 20 Sep 2026 20:17:58 +0100 Subject: [PATCH 01/25] C++: Add forwarding tests. --- .../dataflow/forwarding/test.cpp | 531 ++++++++++++++++++ .../dataflow/forwarding/test.expected | 0 .../dataflow/forwarding/test.ext.yml | 6 + .../library-tests/dataflow/forwarding/test.ql | 31 + 4 files changed, 568 insertions(+) create mode 100644 cpp/ql/test/library-tests/dataflow/forwarding/test.cpp create mode 100644 cpp/ql/test/library-tests/dataflow/forwarding/test.expected create mode 100644 cpp/ql/test/library-tests/dataflow/forwarding/test.ext.yml create mode 100644 cpp/ql/test/library-tests/dataflow/forwarding/test.ql diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp new file mode 100644 index 000000000000..77f7ddaf1d70 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -0,0 +1,531 @@ +template +struct Container { + template + void emplace(Args&&... args); +}; + +struct Element { + int x; + Element(int); // element_int + Element(short); // element_short + Element(unsigned long); // element_ul + Element(const char*, int x); // element_const_char_ptr_int + Element(char*, int x); // element_char_ptr_int +}; + +struct RefElement { + RefElement(int&& x); // element_ref_int_rref + RefElement(int& x); // element_ref_int_lref + RefElement(const int& x); // element_ref_const_int_ref + RefElement(const char* const&, int x); // element_ref_const_char_ptr_const_ref_int + RefElement(const char* const volatile&, int x); // element_ref_const_char_ptr_const_volatile_ref_int + RefElement(const char*&&, int, int); // element_ref_pointer_rref + RefElement(const char*&, int, int, int); // element_ref_pointer_lref +}; + +struct ElementWithDefaultArgument { + int x; + ElementWithDefaultArgument(int x, int = 0); // element_default_int +}; + +struct ElementWithOverloadedArity { + int x; + ElementWithOverloadedArity(int first); // element_overload_arith_1 + ElementWithOverloadedArity(int, int second); // element_overload_arith_2 +}; + +struct ElementFromMutablePointer { + ElementFromMutablePointer(char*, int); // element_from_mutable_ptr +}; + +void test() { + { + Container c; + c.emplace(42); // $ targets=element_int + } + { + Container c; + c.emplace(42); // $ targets=element_default_int + c.emplace(42, 1); // $ targets=element_default_int + } + { + Container c; + c.emplace(42); // $ targets=element_overload_arith_1 + c.emplace(42, 1); // $ targets=element_overload_arith_2 + } + { + Container c; + c.emplace("abc", 42); // $ MISSING: targets=element_const_char_ptr_int + c.emplace((char*)nullptr, 42); // $ targets=element_char_ptr_int SPURIOUS: targets=element_const_char_ptr_int + } + { + Container c; + Container cr; + { + const int x = 42; + c.emplace(x); // $ targets=element_int + cr.emplace(x); // $ targets=element_ref_const_int_ref SPURIOUS: targets=element_ref_int_lref targets=element_ref_int_rref + } + { + int x = 42; + c.emplace(x); // $ targets=element_int + cr.emplace(x); // $ targets=element_ref_int_lref SPURIOUS: targets=element_ref_const_int_ref targets=element_ref_int_rref + } + c.emplace(42); // $ targets=element_int + cr.emplace(42); // $ targets=element_ref_int_rref SPURIOUS: targets=element_ref_const_int_ref targets=element_ref_int_lref + cr.emplace("abc", 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int + const char buffer[] = "abc"; + cr.emplace(buffer, 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int + } + { + Container container; + short shortValue = 42; + unsigned long longValue = 42; + container.emplace(shortValue); // $ targets=element_short + container.emplace(longValue); // $ targets=element_ul + } +} + + +void test_invalid_pointer_conversion() { + Container container; + container.emplace("abc", 42); // no targets +} + +void test_volatile_reference_binding() { + Container container; + const char* volatile pointer = "abc"; + container.emplace(pointer, 42); // $ targets=element_ref_const_char_ptr_const_volatile_ref_int SPURIOUS: targets=element_ref_const_char_ptr_const_ref_int + container.emplace("abc", 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int +} + +struct ImplicitConversion { + ImplicitConversion(int = 0, int = 0); + ImplicitConversion(const char* const&); +}; + +struct ExplicitConversion { + explicit ExplicitConversion(int); +}; + +struct DeletedConversion { + DeletedConversion(int) = delete; +}; + +struct RequiredArgumentConversion { + RequiredArgumentConversion(int, int); +}; + +struct LvalueConversion { + LvalueConversion(int&); +}; + +struct RvalueConversion { + RvalueConversion(int&&); +}; + +struct ChainedConversion { + ChainedConversion(ImplicitConversion); +}; + +struct ElementFromImplicitConversion { + explicit ElementFromImplicitConversion(ImplicitConversion); // element_conversion_value + ElementFromImplicitConversion(const ImplicitConversion&, int); // element_conversion_const_ref + ElementFromImplicitConversion(ImplicitConversion&&, int, int); // element_conversion_rref + ElementFromImplicitConversion(ImplicitConversion&, int, int, int); // element_conversion_lref + ElementFromImplicitConversion(const volatile ImplicitConversion&, int, int, int, int); // element_conversion_const_volatile_ref +}; + +struct ElementFromUnavailableConversion { + ElementFromUnavailableConversion(ExplicitConversion); // element_explicit_conversion + ElementFromUnavailableConversion(DeletedConversion, int); // element_deleted_conversion + ElementFromUnavailableConversion(RequiredArgumentConversion, int, int); // element_required_argument_conversion +}; + +struct ElementFromReferenceConversion { + ElementFromReferenceConversion(LvalueConversion); // element_lvalue_conversion + ElementFromReferenceConversion(RvalueConversion, int); // element_rvalue_conversion +}; + +struct ElementFromChainedConversion { + ElementFromChainedConversion(ChainedConversion); // element_chained_conversion + ElementFromChainedConversion(ImplicitConversion, ImplicitConversion); // element_two_conversions +}; + +void test_implicit_conversion() { + Container container; + container.emplace(42); // $ MISSING: targets=element_conversion_value + container.emplace("abc"); // $ MISSING: targets=element_conversion_value + container.emplace(42, 0); // $ MISSING: targets=element_conversion_const_ref + container.emplace(42, 0, 0); // $ MISSING: targets=element_conversion_rref + container.emplace(42, 0, 0, 0); // no targets + container.emplace(42, 0, 0, 0, 0); // no targets +} + +void test_unavailable_implicit_conversion() { + Container container; + container.emplace(42); // no targets + container.emplace(42, 0); // no targets + container.emplace(42, 0, 0); // no targets +} + +void test_implicit_conversion_input_references() { + Container container; + int value = 42; + const int constValue = 42; + volatile int volatileValue = 42; + container.emplace(value); // $ MISSING: targets=element_lvalue_conversion + container.emplace(42); // no targets + container.emplace(constValue); // no targets + container.emplace(volatileValue); // no targets + container.emplace(42, 0); // $ MISSING: targets=element_rvalue_conversion + container.emplace(value, 0); // no targets + container.emplace(static_cast(constValue), 0); // no targets + container.emplace(static_cast(volatileValue), 0); // no targets +} + +void test_implicit_conversion_limit() { + Container container; + container.emplace(42); // no targets + ImplicitConversion converted(42); + container.emplace(converted); // $ MISSING: targets=element_chained_conversion + container.emplace(42, 42); // $ MISSING: targets=element_two_conversions + + Container references; + references.emplace(converted, 0, 0); // $ SPURIOUS: targets=element_conversion_rref + const ImplicitConversion constConverted(42); + references.emplace(constConverted, 0, 0, 0); // $ SPURIOUS: targets=element_conversion_lref +} + +struct ValueConversionOperator { + operator int(); +}; + +struct LvalueConversionOperator { + operator int&() &; +}; + +struct RvalueConversionOperator { + operator int&&() &&; +}; + +struct ConstConversionOperator { + operator const int&() const &; +}; + +struct VolatileConversionOperator { + operator const volatile int&() const volatile; +}; + +struct ExplicitConversionOperator { + explicit operator int(); +}; + +struct DeletedConversionOperator { + operator int() = delete; +}; + +struct ChainedConversionOperator { + operator ValueConversionOperator&(); +}; + +struct ArrayConversionOperator { + using Buffer = const char[4]; + operator Buffer&() const; +}; + +struct ElementFromConversionOperator { + explicit ElementFromConversionOperator(int); // element_operator_value + ElementFromConversionOperator(int&, int); // element_operator_lref + ElementFromConversionOperator(const int&, int, int); // element_operator_const_ref + ElementFromConversionOperator(int&&, int, int, int); // element_operator_rref + ElementFromConversionOperator(const volatile int&, int, int, int, int); // element_operator_const_volatile_ref +}; + +void test_value_conversion_operator() { + Container container; + ValueConversionOperator value; + const ValueConversionOperator constValue; + volatile ValueConversionOperator volatileValue; + container.emplace(value); // $ MISSING: targets=element_operator_value + container.emplace(ValueConversionOperator()); // $ MISSING: targets=element_operator_value + container.emplace(constValue); // no targets + container.emplace(volatileValue); // no targets + container.emplace(value, 0); // no targets + container.emplace(value, 0, 0); // $ MISSING: targets=element_operator_const_ref + container.emplace(value, 0, 0, 0); // $ MISSING: targets=element_operator_rref + container.emplace(value, 0, 0, 0, 0); // no targets +} + +void test_lvalue_conversion_operator() { + Container container; + LvalueConversionOperator value; + const LvalueConversionOperator constValue; + volatile LvalueConversionOperator volatileValue; + container.emplace(value); // $ MISSING: targets=element_operator_value + container.emplace(value, 0); // $ MISSING: targets=element_operator_lref + container.emplace(value, 0, 0); // $ MISSING: targets=element_operator_const_ref + container.emplace(value, 0, 0, 0); // no targets + container.emplace(value, 0, 0, 0, 0); // $ MISSING: targets=element_operator_const_volatile_ref + container.emplace(value, ValueConversionOperator()); // $ MISSING: targets=element_operator_lref + container.emplace(LvalueConversionOperator()); // no targets + container.emplace(constValue); // no targets + container.emplace(volatileValue); // no targets +} + +void test_rvalue_conversion_operator() { + Container container; + RvalueConversionOperator value; + const RvalueConversionOperator constValue; + container.emplace(value); // no targets + container.emplace(static_cast(constValue)); // no targets + container.emplace(RvalueConversionOperator()); // $ MISSING: targets=element_operator_value + container.emplace(RvalueConversionOperator(), 0); // no targets + container.emplace(RvalueConversionOperator(), 0, 0); // $ MISSING: targets=element_operator_const_ref + container.emplace(RvalueConversionOperator(), 0, 0, 0); // $ MISSING: targets=element_operator_rref + container.emplace(RvalueConversionOperator(), 0, 0, 0, 0); // no targets +} + +void test_conversion_operator_qualification() { + Container container; + const ConstConversionOperator constValue; + volatile ConstConversionOperator volatileValue; + container.emplace(constValue, 0, 0); // $ MISSING: targets=element_operator_const_ref + container.emplace(ConstConversionOperator(), 0, 0); // $ MISSING: targets=element_operator_const_ref + container.emplace(constValue, 0); // no targets + container.emplace(constValue, 0, 0, 0); // no targets + container.emplace(volatileValue); // no targets + + const volatile VolatileConversionOperator constVolatileValue; + container.emplace(constVolatileValue, 0, 0, 0, 0); // $ MISSING: targets=element_operator_const_volatile_ref + container.emplace(constVolatileValue, 0, 0); // no targets + container.emplace(constVolatileValue, 0); // no targets +} + +void test_conversion_operator_limit() { + Container container; + container.emplace(ExplicitConversionOperator()); // no targets + container.emplace(DeletedConversionOperator()); // no targets + container.emplace(ChainedConversionOperator()); // no targets + + Container converted; + converted.emplace(ValueConversionOperator()); // no targets +} + +void test_array_conversion_operator() { + Container container; + container.emplace(ArrayConversionOperator(), 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int + container.emplace(ArrayConversionOperator(), 0, 0); // no targets + container.emplace(ArrayConversionOperator(), 0, 0, 0); // no targets +} + +void test_pointer_value_categories() { + Container container; + const char buffer[] = "abc"; + const char* pointer = buffer; + const char* const constPointer = buffer; + container.emplace(buffer, 0, 0); // $ MISSING: targets=element_ref_pointer_rref + container.emplace(pointer, 0, 0); // $ SPURIOUS: targets=element_ref_pointer_rref + container.emplace(static_cast(pointer), 0, 0); // $ targets=element_ref_pointer_rref + container.emplace(static_cast(constPointer), 0, 0); // $ SPURIOUS: targets=element_ref_pointer_rref + container.emplace(buffer, 0, 0, 0); // no targets + container.emplace(pointer, 0, 0, 0); // $ targets=element_ref_pointer_lref + container.emplace(constPointer, 0, 0, 0); // $ SPURIOUS: targets=element_ref_pointer_lref + container.emplace(static_cast(pointer), 0, 0, 0); // $ SPURIOUS: targets=element_ref_pointer_lref +} + +enum ArithmeticEnum { arithmeticValue = 42 }; +enum class ScopedArithmeticEnum { value = 42 }; + +struct ElementFromArithmetic { + ElementFromArithmetic(long); // element_arithmetic_long + ElementFromArithmetic(double, int); // element_arithmetic_double + ElementFromArithmetic(bool, int, int); // element_arithmetic_bool + ElementFromArithmetic(const long&, int, int, int); // element_arithmetic_const_ref + ElementFromArithmetic(long&&, int, int, int, int); // element_arithmetic_rref +}; + +void test_arithmetic_conversions() { + Container container; + short value = 42; + long longValue = 42; + container.emplace(value); // $ MISSING: targets=element_arithmetic_long + container.emplace(1.5f, 0); // $ MISSING: targets=element_arithmetic_double + container.emplace(arithmeticValue); // $ MISSING: targets=element_arithmetic_long + container.emplace(42, 0, 0); // $ MISSING: targets=element_arithmetic_bool + container.emplace(value, 0, 0, 0); // $ MISSING: targets=element_arithmetic_const_ref + container.emplace(value, 0, 0, 0, 0); // $ MISSING: targets=element_arithmetic_rref + container.emplace(longValue, 0, 0, 0, 0); // $ SPURIOUS: targets=element_arithmetic_rref + container.emplace(ScopedArithmeticEnum::value); // no targets +} + +void test_standard_and_user_defined_conversions() { + short value = 42; + Container before; + before.emplace(value); // $ MISSING: targets=element_conversion_value + Container after; + after.emplace(ValueConversionOperator()); // $ MISSING: targets=element_arithmetic_long + after.emplace(ValueConversionOperator(), 0, 0, 0); // $ MISSING: targets=element_arithmetic_const_ref +} + +struct ElementFromQualifiedPointer { + ElementFromQualifiedPointer(const char*); // element_qualified_pointer + ElementFromQualifiedPointer(const char* const&, int); // element_qualified_pointer_const_ref + ElementFromQualifiedPointer(const char*&&, int, int); // element_qualified_pointer_rref + ElementFromQualifiedPointer(const char*&, int, int, int); // element_qualified_pointer_lref + ElementFromQualifiedPointer(const char**, int, int, int, int); // element_qualified_double_pointer + ElementFromQualifiedPointer(const char* const*, int, int, int, int, int); // element_qualified_const_double_pointer +}; + +void test_pointer_qualification_conversions() { + Container container; + char buffer[] = "abc"; + char* pointer = buffer; + char** doublePointer = &pointer; + const char* constPointer = buffer; + container.emplace(buffer); // $ MISSING: targets=element_qualified_pointer + container.emplace(pointer); // $ targets=element_qualified_pointer + container.emplace(pointer, 0); // $ targets=element_qualified_pointer_const_ref + container.emplace(buffer, 0, 0); // $ MISSING: targets=element_qualified_pointer_rref + container.emplace(static_cast(pointer), 0, 0); // $ targets=element_qualified_pointer_rref + container.emplace(pointer, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_pointer_lref + container.emplace(doublePointer, 0, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_double_pointer + container.emplace(doublePointer, 0, 0, 0, 0, 0); // $ targets=element_qualified_const_double_pointer + Container mutableContainer; + mutableContainer.emplace(constPointer, 42); // $ SPURIOUS: targets=element_from_mutable_ptr +} + +struct ConversionBase { + operator int() const; +}; + +struct ConversionDerived : ConversionBase {}; +struct ConversionFurtherDerived : ConversionDerived {}; + +struct ElementFromBase { + ElementFromBase(ConversionBase); // element_base_value + ElementFromBase(ConversionBase&, int); // element_base_lref + ElementFromBase(const ConversionBase&, int, int); // element_base_const_ref + ElementFromBase(ConversionBase&&, int, int, int); // element_base_rref + ElementFromBase(ConversionBase*, int, int, int, int); // element_base_pointer + ElementFromBase(const ConversionBase*, int, int, int, int, int); // element_const_base_pointer +}; + +struct ElementFromDerived { + ElementFromDerived(ConversionDerived&); // element_derived_lref + ElementFromDerived(ConversionDerived*, int); // element_derived_pointer +}; + +void test_inheritance_conversions() { + ConversionFurtherDerived derived; + const ConversionDerived constDerived{}; + ConversionBase base; + Container container; + container.emplace(derived); // $ MISSING: targets=element_base_value + container.emplace(derived, 0); // $ MISSING: targets=element_base_lref + container.emplace(constDerived, 0, 0); // $ MISSING: targets=element_base_const_ref + container.emplace(ConversionDerived(), 0, 0, 0); // $ MISSING: targets=element_base_rref + container.emplace(derived, 0, 0, 0); // no targets + container.emplace(constDerived, 0); // no targets + container.emplace(&derived, 0, 0, 0, 0); // $ MISSING: targets=element_base_pointer + container.emplace(&constDerived, 0, 0, 0, 0); // no targets + container.emplace(&constDerived, 0, 0, 0, 0, 0); // $ MISSING: targets=element_const_base_pointer + Container downcast; + downcast.emplace(base); // no targets + downcast.emplace(&base, 0); // no targets + Container inherited; + inherited.emplace(derived); // $ MISSING: targets=element_operator_value +} + +struct ElementFromStandardPointer { + ElementFromStandardPointer(void*); // element_void_pointer + ElementFromStandardPointer(const void*, int); // element_const_void_pointer + ElementFromStandardPointer(int*, int, int); // element_int_pointer + ElementFromStandardPointer(bool, int, int, int, int); // element_pointer_bool +}; + +void test_standard_pointer_conversions() { + Container container; + int value = 42; + const int constValue = 42; + container.emplace(&value); // $ MISSING: targets=element_void_pointer + container.emplace(&constValue); // no targets + container.emplace(&constValue, 0); // $ MISSING: targets=element_const_void_pointer + container.emplace(nullptr); // $ MISSING: targets=element_void_pointer + container.emplace(nullptr, 0, 0); // $ MISSING: targets=element_int_pointer + container.emplace(0, 0, 0); // no targets + container.emplace(&value, 0, 0, 0, 0); // $ MISSING: targets=element_pointer_bool + container.emplace(nullptr, 0, 0, 0, 0); // no targets + void* opaque = &value; + container.emplace(opaque, 0, 0); // no targets +} + +using Callback = int (*)(int); +using NothrowCallback = int (*)(int) noexcept; +int callback(int); +int nothrowCallback(int) noexcept; +double differentCallback(int); + +struct ElementFromCallback { + ElementFromCallback(Callback); // element_callback + ElementFromCallback(NothrowCallback, int); // element_nothrow_callback + ElementFromCallback(const Callback&, int, int); // element_callback_const_ref +}; + +void test_function_conversions() { + Container container; + Callback pointer = callback; + NothrowCallback nothrowPointer = nothrowCallback; + container.emplace(callback); // $ MISSING: targets=element_callback + container.emplace(nothrowCallback); // $ MISSING: targets=element_callback + container.emplace(nothrowPointer); // $ targets=element_callback + container.emplace(nothrowCallback, 0); // $ MISSING: targets=element_nothrow_callback + container.emplace(pointer, 0); // $ SPURIOUS: targets=element_nothrow_callback + container.emplace(callback, 0); // no targets + container.emplace(callback, 0, 0); // $ MISSING: targets=element_callback_const_ref + container.emplace(nullptr); // $ MISSING: targets=element_callback + container.emplace(differentCallback); // no targets + Container boolean; + boolean.emplace(callback, 0, 0, 0, 0); // $ MISSING: targets=element_pointer_bool + boolean.emplace(callback); // no targets +} + +void test_standard_conversion_phases() { + Container arithmetic; + long value = 42; + arithmetic.emplace(value, 0, 0, 0, 0); // $ SPURIOUS: targets=element_arithmetic_rref + arithmetic.emplace(&value); // no targets + arithmetic.emplace(nullptr, 0, 0); // no targets + arithmetic.emplace(ValueConversionOperator(), 0, 0, 0, 0); // $ MISSING: targets=element_arithmetic_rref + + Container converted; + char buffer[] = "abc"; + converted.emplace(buffer); // $ MISSING: targets=element_conversion_value + converted.emplace(&value); // no targets + converted.emplace(ScopedArithmeticEnum::value); // no targets +} + +void test_pointer_aliases() { + using Character = const char; + using Pointer = Character*; + using NestedPointer = Pointer*; + Pointer pointer = "abc"; + NestedPointer nested = &pointer; + Pointer const constPointer = pointer; + Pointer volatile volatilePointer = pointer; + Container container; + container.emplace(pointer); // $ targets=element_qualified_pointer + container.emplace(nested, 0, 0, 0, 0); // $ targets=element_qualified_double_pointer + container.emplace(&constPointer, 0, 0, 0, 0, 0); // $ targets=element_qualified_const_double_pointer + container.emplace(&constPointer, 0, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_double_pointer + container.emplace(&volatilePointer, 0, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_double_pointer + container.emplace(&volatilePointer, 0, 0, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_const_double_pointer + + using CallbackAlias = Callback; + CallbackAlias callbackPointer = callback; + Container callbacks; + callbacks.emplace(callbackPointer); // $ targets=element_callback + callbacks.emplace(callbackPointer, 0, 0); // $ targets=element_callback_const_ref + Container pointers; + pointers.emplace(callbackPointer); // no targets +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.expected b/cpp/ql/test/library-tests/dataflow/forwarding/test.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.ext.yml b/cpp/ql/test/library-tests/dataflow/forwarding/test.ext.yml new file mode 100644 index 000000000000..44ba9a609803 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.ext.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/cpp-all + extensible: forwardsModel + data: # namespace, type, subtypes, name, signature, ext, start, constructor, output, provenance + - ["", "Container", True, "emplace", "(Args &&)", "", "0", "T", "Argument[-1].Element", "manual"] diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.ql b/cpp/ql/test/library-tests/dataflow/forwarding/test.ql new file mode 100644 index 000000000000..5340a0cd2929 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.ql @@ -0,0 +1,31 @@ +import cpp +import utils.test.InlineExpectationsTest +import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate +import semmle.code.cpp.ir.IR + +bindingset[s] +string quote(string s) { if s.matches("% %") then result = "\"" + s + "\"" else result = s } + +string getConstructorId(Constructor constructor) { + exists(CppStyleComment comment, string filepath, int startline, int endline | + comment.getLocation().hasLocationInfo(filepath, startline, _, endline, _) and + constructor.getLocation().hasLocationInfo(filepath, startline, _, endline, _) and + result = comment.getContents().suffix(2).trim() + ) +} + +module AsDefinitionTest implements TestSig { + string getARelevantTag() { result = "targets" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(CallInstruction call, Constructor constructor | + forwardingCallTargetsConstructor(call, constructor, _) and + element = call.toString() and + tag = "targets" and + value = quote(getConstructorId(constructor)) and + location = call.getLocation() + ) + } +} + +import MakeTest From 2c5dcfdf78d6a465fa622074ab972621ca4fc16c Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 21 Sep 2026 14:29:57 +0100 Subject: [PATCH 02/25] C++: Move forwarding logic to ExternalFlow.qll --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 64 ++++++++++++++++--- .../ir/dataflow/internal/DataFlowNodes.qll | 3 +- .../ir/dataflow/internal/DataFlowPrivate.qll | 51 ++------------- .../library-tests/dataflow/forwarding/test.ql | 4 +- 4 files changed, 63 insertions(+), 59 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 2b48b36c5022..5609c743abfc 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1142,16 +1142,14 @@ private predicate interpretForwardsModelType( * at calls to `forwarder`. */ private predicate interpretForwardsModel( - Function forwarder, Constructor constructor, int start, string output, string provenance, - string model + Function forwarder, Class c, int start, string output, string provenance, string model ) { - interpretForwardsModelType(forwarder, constructor.getDeclaringType(), start, output, provenance, - model) + interpretForwardsModelType(forwarder, c, start, output, provenance, model) } /** Holds if `forwarder` forwards its arguments starting at `start` to `constructor`. */ -predicate forwards(Function forwarder, Constructor constructor, int start) { - interpretForwardsModel(forwarder, constructor, start, _, _, _) +predicate forwards(Function forwarder, Class c, int start) { + interpretForwardsModel(forwarder, c, start, _, _, _) } private int referenceIndirection(Type unspecified) { @@ -1166,6 +1164,52 @@ private Type stripReference(Type unspecified) { result = unspecified } +module ConstructorForwarding { + /** + * 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 + } + + Cpp::Constructor getForwardingConstructor(Function forwarder, int start) { + exists(int numberOfForwardedArguments | + numberOfForwardedArguments <= result.getNumberOfParameters() + or + result.isVarargs() + | + forwards(forwarder, result.getDeclaringType(), start) and + forwarder.getNumberOfParameters() = start + numberOfForwardedArguments and + forall(int i | i = [0 .. result.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(forwarder.getParameter(start + i).getUnderlyingType()) = + stripReferences(result.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 result.getParameter(i).hasInitializer() + ) + ) + } + + /** Holds if `call` is a call that forwards arguments to a constructor call. */ + predicate isForwarderConstructorArgumentNodeImpl(CallInstruction call) { + exists(getForwardingConstructor(call.getStaticCallTarget(), _)) + } +} + /** * In order to support flow summaries for functions that perform "perfect * forwarding" we interpret a call such as: @@ -1189,12 +1233,14 @@ private Type stripReference(Type unspecified) { 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) + exists(Class c, int start, string constructorOutput | + interpretForwardsModel(forwarder, c, start, constructorOutput, provenance, model) | // Generate the (1) summary - exists(int index, Parameter arg, Parameter p, int indirection | + exists(int index, Parameter arg, Parameter p, int indirection, Cpp::Constructor constructor | arg = forwarder.getParameter(start + index) and + constructor = ConstructorForwarding::getForwardingConstructor(forwarder, start) and + constructor.getDeclaringType() = c and p = constructor.getParameter(index) and indirection = [0 .. SsaImpl::getMaxIndirectionsForPRType(p.getUnspecifiedType())] and input = 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 b493ba001559..9dbf7f8e73c5 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 @@ -3,6 +3,7 @@ private import semmle.code.cpp.ir.ValueNumbering private import semmle.code.cpp.ir.IR private import semmle.code.cpp.models.interfaces.DataFlow private import semmle.code.cpp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +private import semmle.code.cpp.dataflow.ExternalFlow as External private import DataFlowPrivate private import DataFlowUtil private import ModelUtil @@ -192,7 +193,7 @@ private module Cached { TSsaSynthNode(SsaImpl::SynthNode n) or TSsaIteratorNode(IteratorFlow::IteratorFlowNode n) or TForwarderConstructorArgumentNode(CallInstruction call) { - isForwarderConstructorArgumentNodeImpl(call) + External::ConstructorForwarding::isForwarderConstructorArgumentNodeImpl(call) } or TRawIndirectOperand0(Node0Impl node, int indirectionIndex) { SsaImpl::hasRawIndirectOperand(node.asOperand(), indirectionIndex) 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 7faa8bb8681c..29f1a16bf58e 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,52 +593,6 @@ 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 | - 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] | - // 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: * ``` @@ -679,7 +633,10 @@ private class ForwarderConstructorArgumentNode extends ArgumentNode, /** * Gets a constructor which may be targeted by this forwarding call. */ - Cpp::Constructor getAConstructor() { forwardingCallTargetsConstructor(call, result, _) } + Cpp::Constructor getAConstructor() { + result = + External::ConstructorForwarding::getForwardingConstructor(call.getStaticCallTarget(), _) + } override DataFlowCallable getEnclosingCallable() { result.asSourceCallable() = this.getFunction() diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.ql b/cpp/ql/test/library-tests/dataflow/forwarding/test.ql index 5340a0cd2929..e625769b0224 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.ql +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.ql @@ -1,6 +1,6 @@ import cpp import utils.test.InlineExpectationsTest -import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate +import semmle.code.cpp.dataflow.ExternalFlow import semmle.code.cpp.ir.IR bindingset[s] @@ -19,7 +19,7 @@ module AsDefinitionTest implements TestSig { predicate hasActualResult(Location location, string element, string tag, string value) { exists(CallInstruction call, Constructor constructor | - forwardingCallTargetsConstructor(call, constructor, _) and + constructor = ConstructorForwarding::getForwardingConstructor(call.getStaticCallTarget(), _) and element = call.toString() and tag = "targets" and value = quote(getConstructorId(constructor)) and From d8d6a2f7cfd84d0a0570badc50ca899a8cbcb7e2 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 21 Sep 2026 14:36:24 +0100 Subject: [PATCH 03/25] C++: Change 'forall' to 'forex'. There is no need for forwarding if there are no arguments to forward. --- cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 5609c743abfc..ddf4648cbcf5 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1183,7 +1183,7 @@ module ConstructorForwarding { | forwards(forwarder, result.getDeclaringType(), start) and forwarder.getNumberOfParameters() = start + numberOfForwardedArguments and - forall(int i | i = [0 .. result.getNumberOfParameters() - 1] | + forex(int i | i = [0 .. result.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: From 573ca0e93682d1844ab52edec8cb7b54e7d876b5 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 21 Sep 2026 16:00:48 +0100 Subject: [PATCH 04/25] C++: Replace naive reference stripping with a step-relation with conversions. For now we don't implement any converions other than array-to-pointer, but we will add conversions in a later commit. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 92 +++++++++++++++++-- 1 file changed, 83 insertions(+), 9 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index ddf4648cbcf5..cfd707305400 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1165,14 +1165,89 @@ private Type stripReference(Type unspecified) { } module ConstructorForwarding { - /** - * Gets `unspecifiedType`, but with the outermost `ReferenceType` removed, if any. - */ - private Type stripReferences(Type unspecifiedType) { - result = unspecifiedType.(Cpp::ReferenceType).getBaseType().getUnspecifiedType() + private Type getForwardedArgumentType(Function forwarder, int start, int i) { + forwards(forwarder, _, start) and + i = [0 .. forwarder.getNumberOfParameters() - start - 1] and + result = forwarder.getParameter(start + i).getUnderlyingType() + } + + private Type getConstructorParameterType(Cpp::Constructor constructor, int i) { + forwards(_, constructor.getDeclaringType(), _) and + result = constructor.getParameter(i).getUnderlyingType() + } + + private newtype TTypeState = MkTypeState(Type type) { type = type.getUnderlyingType() } + + private class TypeState extends TTypeState { + Type getType() { this = MkTypeState(result) } + + string toString() { result = this.getType().toString() } + + predicate isSource(Type argType) { + argType = getForwardedArgumentType(_, _, _) and + this = MkTypeState(argType) + } + + predicate matchesParameter(Type paramType) { + this.getType().stripTopLevelSpecifiers() = paramType.stripTopLevelSpecifiers() + } + + predicate isSink(Type paramType, Cpp::Constructor constructor) { + paramType = getConstructorParameterType(constructor, _) and + this.matchesParameter(paramType) + } + } + + private Cpp::PointerType pointerType(Type base) { + result.getBaseType() = base.getUnderlyingType() + } + + private predicate arrayToPointerStep(TypeState argState, TypeState paramState) { + exists(Cpp::ArrayType array | + argState = MkTypeState(array) and + paramState = MkTypeState(pointerType(array.getBaseType())) + ) + } + + private predicate step(TypeState argState, TypeState paramState) { + arrayToPointerStep(argState, paramState) + } + + private predicate typeFwd(TypeState state) { + state.isSource(_) or - not unspecifiedType instanceof Cpp::ReferenceType and - result = unspecifiedType + exists(TypeState previous | + typeFwd(previous) and + step(previous, state) + ) + } + + private predicate typeRev(TypeState state, Cpp::Constructor constructor) { + typeFwd(state) and + ( + state.isSink(_, constructor) + or + exists(TypeState next | + typeRev(next, constructor) and + step(state, next) + ) + ) + } + + private predicate prunedStep(TypeState argState, TypeState paramState) { + exists(Cpp::Constructor constructor | + typeRev(argState, constructor) and + typeRev(paramState, constructor) and + step(argState, paramState) + ) + } + + private predicate compatible(Function forwarder, int start, int i, Cpp::Constructor constructor) { + exists(TypeState argState, TypeState paramState | + argState.isSource(getForwardedArgumentType(forwarder, start, i)) and + paramState.isSink(getConstructorParameterType(constructor, i), constructor) and + prunedStep*(argState, paramState) + ) } Cpp::Constructor getForwardingConstructor(Function forwarder, int start) { @@ -1194,8 +1269,7 @@ module ConstructorForwarding { // However, the constructor may not specify all the arguments by // reference. i < numberOfForwardedArguments and - stripReferences(forwarder.getParameter(start + i).getUnderlyingType()) = - stripReferences(result.getParameter(i).getUnspecifiedType()) + compatible(forwarder, start, i, result) 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. From f929a94cf837922a9caa7d5cdc00a86dbfa385ff Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 21 Sep 2026 16:00:55 +0100 Subject: [PATCH 05/25] C++: Accept test changes. --- .../dataflow/forwarding/test.cpp | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index 77f7ddaf1d70..cf12a6ad358a 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -41,38 +41,38 @@ struct ElementFromMutablePointer { void test() { { Container c; - c.emplace(42); // $ targets=element_int + c.emplace(42); // $ MISSING: targets=element_int } { Container c; - c.emplace(42); // $ targets=element_default_int - c.emplace(42, 1); // $ targets=element_default_int + c.emplace(42); // $ MISSING: targets=element_default_int + c.emplace(42, 1); // $ MISSING: targets=element_default_int } { Container c; - c.emplace(42); // $ targets=element_overload_arith_1 - c.emplace(42, 1); // $ targets=element_overload_arith_2 + c.emplace(42); // $ MISSING: targets=element_overload_arith_1 + c.emplace(42, 1); // $ MISSING: targets=element_overload_arith_2 } { Container c; c.emplace("abc", 42); // $ MISSING: targets=element_const_char_ptr_int - c.emplace((char*)nullptr, 42); // $ targets=element_char_ptr_int SPURIOUS: targets=element_const_char_ptr_int + c.emplace((char*)nullptr, 42); // $ MISSING: targets=element_char_ptr_int } { Container c; Container cr; { const int x = 42; - c.emplace(x); // $ targets=element_int - cr.emplace(x); // $ targets=element_ref_const_int_ref SPURIOUS: targets=element_ref_int_lref targets=element_ref_int_rref + c.emplace(x); // $ MISSING: targets=element_int + cr.emplace(x); // $ targets=element_ref_const_int_ref } { int x = 42; - c.emplace(x); // $ targets=element_int - cr.emplace(x); // $ targets=element_ref_int_lref SPURIOUS: targets=element_ref_const_int_ref targets=element_ref_int_rref + c.emplace(x); // $ MISSING: targets=element_int + cr.emplace(x); // $ targets=element_ref_int_lref } - c.emplace(42); // $ targets=element_int - cr.emplace(42); // $ targets=element_ref_int_rref SPURIOUS: targets=element_ref_const_int_ref targets=element_ref_int_lref + c.emplace(42); // $ MISSING: targets=element_int + cr.emplace(42); // $ targets=element_ref_int_rref cr.emplace("abc", 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int const char buffer[] = "abc"; cr.emplace(buffer, 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int @@ -81,8 +81,8 @@ void test() { Container container; short shortValue = 42; unsigned long longValue = 42; - container.emplace(shortValue); // $ targets=element_short - container.emplace(longValue); // $ targets=element_ul + container.emplace(shortValue); // $ MISSING: targets=element_short + container.emplace(longValue); // $ MISSING: targets=element_ul } } @@ -95,7 +95,7 @@ void test_invalid_pointer_conversion() { void test_volatile_reference_binding() { Container container; const char* volatile pointer = "abc"; - container.emplace(pointer, 42); // $ targets=element_ref_const_char_ptr_const_volatile_ref_int SPURIOUS: targets=element_ref_const_char_ptr_const_ref_int + container.emplace(pointer, 42); // $ MISSING: targets=element_ref_const_char_ptr_const_volatile_ref_int container.emplace("abc", 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int } @@ -192,9 +192,9 @@ void test_implicit_conversion_limit() { container.emplace(42, 42); // $ MISSING: targets=element_two_conversions Container references; - references.emplace(converted, 0, 0); // $ SPURIOUS: targets=element_conversion_rref + references.emplace(converted, 0, 0); // no target const ImplicitConversion constConverted(42); - references.emplace(constConverted, 0, 0, 0); // $ SPURIOUS: targets=element_conversion_lref + references.emplace(constConverted, 0, 0, 0); // no target } struct ValueConversionOperator { @@ -325,13 +325,13 @@ void test_pointer_value_categories() { const char* pointer = buffer; const char* const constPointer = buffer; container.emplace(buffer, 0, 0); // $ MISSING: targets=element_ref_pointer_rref - container.emplace(pointer, 0, 0); // $ SPURIOUS: targets=element_ref_pointer_rref - container.emplace(static_cast(pointer), 0, 0); // $ targets=element_ref_pointer_rref - container.emplace(static_cast(constPointer), 0, 0); // $ SPURIOUS: targets=element_ref_pointer_rref + container.emplace(pointer, 0, 0); // no target + container.emplace(static_cast(pointer), 0, 0); // $ MISSING: targets=element_ref_pointer_rref + container.emplace(static_cast(constPointer), 0, 0); // no target container.emplace(buffer, 0, 0, 0); // no targets - container.emplace(pointer, 0, 0, 0); // $ targets=element_ref_pointer_lref - container.emplace(constPointer, 0, 0, 0); // $ SPURIOUS: targets=element_ref_pointer_lref - container.emplace(static_cast(pointer), 0, 0, 0); // $ SPURIOUS: targets=element_ref_pointer_lref + container.emplace(pointer, 0, 0, 0); // $ MISSING: targets=element_ref_pointer_lref + container.emplace(constPointer, 0, 0, 0); // no target + container.emplace(static_cast(pointer), 0, 0, 0); // no target } enum ArithmeticEnum { arithmeticValue = 42 }; @@ -355,7 +355,7 @@ void test_arithmetic_conversions() { container.emplace(42, 0, 0); // $ MISSING: targets=element_arithmetic_bool container.emplace(value, 0, 0, 0); // $ MISSING: targets=element_arithmetic_const_ref container.emplace(value, 0, 0, 0, 0); // $ MISSING: targets=element_arithmetic_rref - container.emplace(longValue, 0, 0, 0, 0); // $ SPURIOUS: targets=element_arithmetic_rref + container.emplace(longValue, 0, 0, 0, 0); // no target container.emplace(ScopedArithmeticEnum::value); // no targets } @@ -384,15 +384,15 @@ void test_pointer_qualification_conversions() { char** doublePointer = &pointer; const char* constPointer = buffer; container.emplace(buffer); // $ MISSING: targets=element_qualified_pointer - container.emplace(pointer); // $ targets=element_qualified_pointer - container.emplace(pointer, 0); // $ targets=element_qualified_pointer_const_ref + container.emplace(pointer); // $ MISSING: targets=element_qualified_pointer + container.emplace(pointer, 0); // $ MISSING: targets=element_qualified_pointer_const_ref container.emplace(buffer, 0, 0); // $ MISSING: targets=element_qualified_pointer_rref - container.emplace(static_cast(pointer), 0, 0); // $ targets=element_qualified_pointer_rref - container.emplace(pointer, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_pointer_lref - container.emplace(doublePointer, 0, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_double_pointer - container.emplace(doublePointer, 0, 0, 0, 0, 0); // $ targets=element_qualified_const_double_pointer + container.emplace(static_cast(pointer), 0, 0); // $ MISSING: targets=element_qualified_pointer_rref + container.emplace(pointer, 0, 0, 0); // no target + container.emplace(doublePointer, 0, 0, 0, 0); // no target + container.emplace(doublePointer, 0, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_const_double_pointer Container mutableContainer; - mutableContainer.emplace(constPointer, 42); // $ SPURIOUS: targets=element_from_mutable_ptr + mutableContainer.emplace(constPointer, 42); // no target } struct ConversionBase { @@ -478,9 +478,9 @@ void test_function_conversions() { NothrowCallback nothrowPointer = nothrowCallback; container.emplace(callback); // $ MISSING: targets=element_callback container.emplace(nothrowCallback); // $ MISSING: targets=element_callback - container.emplace(nothrowPointer); // $ targets=element_callback + container.emplace(nothrowPointer); // $ MISSING: targets=element_callback container.emplace(nothrowCallback, 0); // $ MISSING: targets=element_nothrow_callback - container.emplace(pointer, 0); // $ SPURIOUS: targets=element_nothrow_callback + container.emplace(pointer, 0); // no targets container.emplace(callback, 0); // no targets container.emplace(callback, 0, 0); // $ MISSING: targets=element_callback_const_ref container.emplace(nullptr); // $ MISSING: targets=element_callback @@ -493,7 +493,7 @@ void test_function_conversions() { void test_standard_conversion_phases() { Container arithmetic; long value = 42; - arithmetic.emplace(value, 0, 0, 0, 0); // $ SPURIOUS: targets=element_arithmetic_rref + arithmetic.emplace(value, 0, 0, 0, 0); // no targets arithmetic.emplace(&value); // no targets arithmetic.emplace(nullptr, 0, 0); // no targets arithmetic.emplace(ValueConversionOperator(), 0, 0, 0, 0); // $ MISSING: targets=element_arithmetic_rref @@ -514,18 +514,18 @@ void test_pointer_aliases() { Pointer const constPointer = pointer; Pointer volatile volatilePointer = pointer; Container container; - container.emplace(pointer); // $ targets=element_qualified_pointer - container.emplace(nested, 0, 0, 0, 0); // $ targets=element_qualified_double_pointer - container.emplace(&constPointer, 0, 0, 0, 0, 0); // $ targets=element_qualified_const_double_pointer - container.emplace(&constPointer, 0, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_double_pointer - container.emplace(&volatilePointer, 0, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_double_pointer - container.emplace(&volatilePointer, 0, 0, 0, 0, 0); // $ SPURIOUS: targets=element_qualified_const_double_pointer + container.emplace(pointer); // $ MISSING: targets=element_qualified_pointer + container.emplace(nested, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_double_pointer + container.emplace(&constPointer, 0, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_const_double_pointer + container.emplace(&constPointer, 0, 0, 0, 0); // no targets + container.emplace(&volatilePointer, 0, 0, 0, 0); // no targets + container.emplace(&volatilePointer, 0, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_const_double_pointer using CallbackAlias = Callback; CallbackAlias callbackPointer = callback; Container callbacks; - callbacks.emplace(callbackPointer); // $ targets=element_callback - callbacks.emplace(callbackPointer, 0, 0); // $ targets=element_callback_const_ref + callbacks.emplace(callbackPointer); // $ MISSING: targets=element_callback + callbacks.emplace(callbackPointer, 0, 0); // $ MISSING: targets=element_callback_const_ref Container pointers; pointers.emplace(callbackPointer); // no targets } \ No newline at end of file From d528d3166e8a88b9243e156210cfcef89a9f5f01 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:08:28 +0100 Subject: [PATCH 06/25] C++: Track value categories. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index cfd707305400..4ad7cfca2c84 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1176,16 +1176,51 @@ module ConstructorForwarding { result = constructor.getParameter(i).getUnderlyingType() } - private newtype TTypeState = MkTypeState(Type type) { type = type.getUnderlyingType() } + private newtype ValueCategory = + LValue() or + XValue() or + PRValue() + + private predicate isUnderlyingType(Type type) { type = type.getUnderlyingType() } + + private newtype TTypeState = + MkTypeState(Type type, ValueCategory category) { + not type instanceof Cpp::ReferenceType and + not type instanceof FunctionReferenceType and + isUnderlyingType(type) + } + + private ValueCategory getCategoryForRef(Cpp::ReferenceType reference) { + reference instanceof Cpp::LValueReferenceType and result = LValue() + or + reference instanceof Cpp::RValueReferenceType and result = XValue() + } + + private Type getValueType(Type t, ValueCategory category) { + result = t.(FunctionReferenceType).getBaseType().getUnderlyingType() and + category = LValue() + or + result = t.(Cpp::ReferenceType).getBaseType().getUnderlyingType() and + category = getCategoryForRef(t) + or + not t instanceof FunctionReferenceType and + not t instanceof Cpp::ReferenceType and + result = t and + category = PRValue() + } private class TypeState extends TTypeState { - Type getType() { this = MkTypeState(result) } + Type getType() { this = MkTypeState(result, _) } + + ValueCategory getCategory() { this = MkTypeState(_, result) } string toString() { result = this.getType().toString() } predicate isSource(Type argType) { argType = getForwardedArgumentType(_, _, _) and - this = MkTypeState(argType) + exists(ValueCategory category | + this = MkTypeState(getValueType(argType, category), category) + ) } predicate matchesParameter(Type paramType) { @@ -1204,8 +1239,9 @@ module ConstructorForwarding { private predicate arrayToPointerStep(TypeState argState, TypeState paramState) { exists(Cpp::ArrayType array | - argState = MkTypeState(array) and - paramState = MkTypeState(pointerType(array.getBaseType())) + argState = MkTypeState(array, _) and + paramState = + MkTypeState(pointerType(array.getBaseType()), PRValue()) ) } From 785e41c3c09f87dc1eb720bd7b50c114b8917c61 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 17:35:45 +0100 Subject: [PATCH 07/25] C++: Accept test changes. --- .../dataflow/forwarding/test.cpp | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index cf12a6ad358a..638fa6d76db4 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -41,38 +41,38 @@ struct ElementFromMutablePointer { void test() { { Container c; - c.emplace(42); // $ MISSING: targets=element_int + c.emplace(42); // $ targets=element_int } { Container c; - c.emplace(42); // $ MISSING: targets=element_default_int - c.emplace(42, 1); // $ MISSING: targets=element_default_int + c.emplace(42); // $ targets=element_default_int + c.emplace(42, 1); // $ targets=element_default_int } { Container c; - c.emplace(42); // $ MISSING: targets=element_overload_arith_1 - c.emplace(42, 1); // $ MISSING: targets=element_overload_arith_2 + c.emplace(42); // $ targets=element_overload_arith_1 + c.emplace(42, 1); // $ targets=element_overload_arith_2 } { Container c; - c.emplace("abc", 42); // $ MISSING: targets=element_const_char_ptr_int - c.emplace((char*)nullptr, 42); // $ MISSING: targets=element_char_ptr_int + c.emplace("abc", 42); // $ targets=element_const_char_ptr_int + c.emplace((char*)nullptr, 42); // $ targets=element_char_ptr_int } { Container c; Container cr; { const int x = 42; - c.emplace(x); // $ MISSING: targets=element_int - cr.emplace(x); // $ targets=element_ref_const_int_ref + c.emplace(x); // $ targets=element_int + cr.emplace(x); // $ MISSING: targets=element_ref_const_int_ref } { int x = 42; - c.emplace(x); // $ MISSING: targets=element_int - cr.emplace(x); // $ targets=element_ref_int_lref + c.emplace(x); // $ targets=element_int + cr.emplace(x); // $ MISSING: targets=element_ref_int_lref } - c.emplace(42); // $ MISSING: targets=element_int - cr.emplace(42); // $ targets=element_ref_int_rref + c.emplace(42); // $ targets=element_int + cr.emplace(42); // $ MISSING: targets=element_ref_int_rref cr.emplace("abc", 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int const char buffer[] = "abc"; cr.emplace(buffer, 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int @@ -81,8 +81,8 @@ void test() { Container container; short shortValue = 42; unsigned long longValue = 42; - container.emplace(shortValue); // $ MISSING: targets=element_short - container.emplace(longValue); // $ MISSING: targets=element_ul + container.emplace(shortValue); // $ targets=element_short + container.emplace(longValue); // $ targets=element_ul } } @@ -478,9 +478,9 @@ void test_function_conversions() { NothrowCallback nothrowPointer = nothrowCallback; container.emplace(callback); // $ MISSING: targets=element_callback container.emplace(nothrowCallback); // $ MISSING: targets=element_callback - container.emplace(nothrowPointer); // $ MISSING: targets=element_callback + container.emplace(nothrowPointer); // $ targets=element_callback container.emplace(nothrowCallback, 0); // $ MISSING: targets=element_nothrow_callback - container.emplace(pointer, 0); // no targets + container.emplace(pointer, 0); // $ SPURIOUS: targets=element_nothrow_callback container.emplace(callback, 0); // no targets container.emplace(callback, 0, 0); // $ MISSING: targets=element_callback_const_ref container.emplace(nullptr); // $ MISSING: targets=element_callback @@ -514,9 +514,9 @@ void test_pointer_aliases() { Pointer const constPointer = pointer; Pointer volatile volatilePointer = pointer; Container container; - container.emplace(pointer); // $ MISSING: targets=element_qualified_pointer - container.emplace(nested, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_double_pointer - container.emplace(&constPointer, 0, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_const_double_pointer + container.emplace(pointer); // $ targets=element_qualified_pointer + container.emplace(nested, 0, 0, 0, 0); // $ targets=element_qualified_double_pointer + container.emplace(&constPointer, 0, 0, 0, 0, 0); // $ targets=element_qualified_const_double_pointer container.emplace(&constPointer, 0, 0, 0, 0); // no targets container.emplace(&volatilePointer, 0, 0, 0, 0); // no targets container.emplace(&volatilePointer, 0, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_const_double_pointer @@ -524,7 +524,7 @@ void test_pointer_aliases() { using CallbackAlias = Callback; CallbackAlias callbackPointer = callback; Container callbacks; - callbacks.emplace(callbackPointer); // $ MISSING: targets=element_callback + callbacks.emplace(callbackPointer); // $ targets=element_callback callbacks.emplace(callbackPointer, 0, 0); // $ MISSING: targets=element_callback_const_ref Container pointers; pointers.emplace(callbackPointer); // no targets From fafd35dfa6c2e930d27f7fd30fb315814fa97b54 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:11:07 +0100 Subject: [PATCH 08/25] C++: Handle reference sinks. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 4ad7cfca2c84..5f1f78681458 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1181,6 +1181,26 @@ module ConstructorForwarding { XValue() or PRValue() + bindingset[t1, t2] + pragma[inline_late] + private predicate preservesQualifiers(Type t1, Type t2) { + (t1.isConst() implies t2.isConst()) and + (t1.isVolatile() implies t2.isVolatile()) + } + + private predicate referenceAcceptsCategory(Cpp::ReferenceType t, ValueCategory category) { + if t instanceof Cpp::LValueReferenceType + then + category = LValue() + or + exists(Type base | + base = t.getBaseType() and + base.isConst() and + not base.isVolatile() + ) + else category != LValue() + } + private predicate isUnderlyingType(Type type) { type = type.getUnderlyingType() } private newtype TTypeState = @@ -1209,6 +1229,13 @@ module ConstructorForwarding { category = PRValue() } + bindingset[arg, param] + pragma[inline_late] + private predicate qualificationCompatible(Type arg, Type param) { + arg.stripTopLevelSpecifiers() = param.stripTopLevelSpecifiers() and + preservesQualifiers(arg, param) + } + private class TypeState extends TTypeState { Type getType() { this = MkTypeState(result, _) } @@ -1224,6 +1251,12 @@ module ConstructorForwarding { } predicate matchesParameter(Type paramType) { + exists(Type base | base = paramType.(Cpp::ReferenceType).getBaseType() | + qualificationCompatible(this.getType(), base) and + referenceAcceptsCategory(paramType, this.getCategory()) + ) + or + not paramType instanceof Cpp::ReferenceType and this.getType().stripTopLevelSpecifiers() = paramType.stripTopLevelSpecifiers() } From 0b5711fdbe3bc6da067c96e5c0e18ef844e79146 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 17:38:21 +0100 Subject: [PATCH 09/25] C++: Accept test changes. --- .../dataflow/forwarding/test.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index 638fa6d76db4..95f3ff73575c 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -64,18 +64,18 @@ void test() { { const int x = 42; c.emplace(x); // $ targets=element_int - cr.emplace(x); // $ MISSING: targets=element_ref_const_int_ref + cr.emplace(x); // $ targets=element_ref_const_int_ref } { int x = 42; c.emplace(x); // $ targets=element_int - cr.emplace(x); // $ MISSING: targets=element_ref_int_lref + cr.emplace(x); // $ targets=element_ref_int_lref SPURIOUS: targets=element_ref_const_int_ref } c.emplace(42); // $ targets=element_int - cr.emplace(42); // $ MISSING: targets=element_ref_int_rref - cr.emplace("abc", 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int + cr.emplace(42); // $ targets=element_ref_int_rref SPURIOUS: targets=element_ref_const_int_ref + cr.emplace("abc", 42); // $ targets=element_ref_const_char_ptr_const_ref_int const char buffer[] = "abc"; - cr.emplace(buffer, 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int + cr.emplace(buffer, 42); // $ targets=element_ref_const_char_ptr_const_ref_int } { Container container; @@ -95,8 +95,8 @@ void test_invalid_pointer_conversion() { void test_volatile_reference_binding() { Container container; const char* volatile pointer = "abc"; - container.emplace(pointer, 42); // $ MISSING: targets=element_ref_const_char_ptr_const_volatile_ref_int - container.emplace("abc", 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int + container.emplace(pointer, 42); // $ targets=element_ref_const_char_ptr_const_volatile_ref_int + container.emplace("abc", 42); // $ targets=element_ref_const_char_ptr_const_ref_int } struct ImplicitConversion { @@ -324,12 +324,12 @@ void test_pointer_value_categories() { const char buffer[] = "abc"; const char* pointer = buffer; const char* const constPointer = buffer; - container.emplace(buffer, 0, 0); // $ MISSING: targets=element_ref_pointer_rref + container.emplace(buffer, 0, 0); // $ targets=element_ref_pointer_rref container.emplace(pointer, 0, 0); // no target - container.emplace(static_cast(pointer), 0, 0); // $ MISSING: targets=element_ref_pointer_rref + container.emplace(static_cast(pointer), 0, 0); // $ targets=element_ref_pointer_rref container.emplace(static_cast(constPointer), 0, 0); // no target container.emplace(buffer, 0, 0, 0); // no targets - container.emplace(pointer, 0, 0, 0); // $ MISSING: targets=element_ref_pointer_lref + container.emplace(pointer, 0, 0, 0); // $ targets=element_ref_pointer_lref container.emplace(constPointer, 0, 0, 0); // no target container.emplace(static_cast(pointer), 0, 0, 0); // no target } @@ -525,7 +525,7 @@ void test_pointer_aliases() { CallbackAlias callbackPointer = callback; Container callbacks; callbacks.emplace(callbackPointer); // $ targets=element_callback - callbacks.emplace(callbackPointer, 0, 0); // $ MISSING: targets=element_callback_const_ref + callbacks.emplace(callbackPointer, 0, 0); // $ targets=element_callback_const_ref Container pointers; pointers.emplace(callbackPointer); // no targets } \ No newline at end of file From b884ee1496777d918a7d5b7bc53aec7dcc177314 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:18:33 +0100 Subject: [PATCH 10/25] C++: Handle converting constructrors and operators. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 61 ++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 5f1f78681458..f72084ba047d 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1165,6 +1165,26 @@ private Type stripReference(Type unspecified) { } module ConstructorForwarding { + private import codeql.util.Boolean + + private class ConvertingConstructor extends Constructor { + Type fromType; + + ConvertingConstructor() { + not this.isFromUninstantiatedTemplate(_) and + not this.isExplicit() and + not this.isDeleted() and + not this instanceof CopyConstructor and + not this instanceof MoveConstructor and + fromType = this.getParameter(0).getUnderlyingType() and + forall(int index | index > 0 and exists(this.getParameter(index)) | + this.getParameter(index).hasInitializer() + ) + } + + Type getUnderlyingFromType() { result = fromType } + } + private Type getForwardedArgumentType(Function forwarder, int start, int i) { forwards(forwarder, _, start) and i = [0 .. forwarder.getNumberOfParameters() - start - 1] and @@ -1204,7 +1224,7 @@ module ConstructorForwarding { private predicate isUnderlyingType(Type type) { type = type.getUnderlyingType() } private newtype TTypeState = - MkTypeState(Type type, ValueCategory category) { + MkTypeState(Type type, ValueCategory category, Boolean conversionUsed) { not type instanceof Cpp::ReferenceType and not type instanceof FunctionReferenceType and isUnderlyingType(type) @@ -1237,16 +1257,18 @@ module ConstructorForwarding { } private class TypeState extends TTypeState { - Type getType() { this = MkTypeState(result, _) } + Type getType() { this = MkTypeState(result, _, _) } + + ValueCategory getCategory() { this = MkTypeState(_, result, _) } - ValueCategory getCategory() { this = MkTypeState(_, result) } + predicate hasNotUsedConversion() { this = MkTypeState(_, _, false) } string toString() { result = this.getType().toString() } predicate isSource(Type argType) { argType = getForwardedArgumentType(_, _, _) and exists(ValueCategory category | - this = MkTypeState(getValueType(argType, category), category) + this = MkTypeState(getValueType(argType, category), category, false) ) } @@ -1271,15 +1293,40 @@ module ConstructorForwarding { } private predicate arrayToPointerStep(TypeState argState, TypeState paramState) { - exists(Cpp::ArrayType array | - argState = MkTypeState(array, _) and + exists(Cpp::ArrayType array, boolean conversionUsed | + argState = MkTypeState(array, _, conversionUsed) and + paramState = MkTypeState(pointerType(array.getBaseType()), PRValue(), conversionUsed) + ) + } + + private predicate convertingConstructorStep(TypeState argState, TypeState paramState) { + exists(ConvertingConstructor constructor | + argState.hasNotUsedConversion() and + argState.matchesParameter(constructor.getUnderlyingFromType()) and + paramState = MkTypeState(constructor.getDeclaringType(), PRValue(), true) + ) + } + + private predicate conversionOperatorStep(TypeState argState, TypeState paramState) { + exists(ConversionOperator conversion, ValueCategory category | + not conversion.isFromUninstantiatedTemplate(_) and + not conversion.isExplicit() and + not conversion.isDeleted() and + argState.hasNotUsedConversion() and + conversion.getSourceType() = + argState.getType().stripTopLevelSpecifiers().(Class).getABaseClass*() and paramState = - MkTypeState(pointerType(array.getBaseType()), PRValue()) + MkTypeState(getValueType(conversion.getDestType().getUnderlyingType(), category), category, + true) ) } private predicate step(TypeState argState, TypeState paramState) { arrayToPointerStep(argState, paramState) + or + convertingConstructorStep(argState, paramState) + or + conversionOperatorStep(argState, paramState) } private predicate typeFwd(TypeState state) { From 6f052b154a2a11e46c799529cf159489f2fc62bd Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 17:44:34 +0100 Subject: [PATCH 11/25] C++: Accept test changes. --- .../dataflow/forwarding/test.cpp | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index 95f3ff73575c..1ef7ced4f6a2 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -154,10 +154,10 @@ struct ElementFromChainedConversion { void test_implicit_conversion() { Container container; - container.emplace(42); // $ MISSING: targets=element_conversion_value - container.emplace("abc"); // $ MISSING: targets=element_conversion_value - container.emplace(42, 0); // $ MISSING: targets=element_conversion_const_ref - container.emplace(42, 0, 0); // $ MISSING: targets=element_conversion_rref + container.emplace(42); // $ targets=element_conversion_value + container.emplace("abc"); // $ targets=element_conversion_value + container.emplace(42, 0); // $ targets=element_conversion_const_ref + container.emplace(42, 0, 0); // $ targets=element_conversion_rref container.emplace(42, 0, 0, 0); // no targets container.emplace(42, 0, 0, 0, 0); // no targets } @@ -174,11 +174,11 @@ void test_implicit_conversion_input_references() { int value = 42; const int constValue = 42; volatile int volatileValue = 42; - container.emplace(value); // $ MISSING: targets=element_lvalue_conversion + container.emplace(value); // $ targets=element_lvalue_conversion container.emplace(42); // no targets container.emplace(constValue); // no targets container.emplace(volatileValue); // no targets - container.emplace(42, 0); // $ MISSING: targets=element_rvalue_conversion + container.emplace(42, 0); // $ targets=element_rvalue_conversion container.emplace(value, 0); // no targets container.emplace(static_cast(constValue), 0); // no targets container.emplace(static_cast(volatileValue), 0); // no targets @@ -188,8 +188,8 @@ void test_implicit_conversion_limit() { Container container; container.emplace(42); // no targets ImplicitConversion converted(42); - container.emplace(converted); // $ MISSING: targets=element_chained_conversion - container.emplace(42, 42); // $ MISSING: targets=element_two_conversions + container.emplace(converted); // $ targets=element_chained_conversion + container.emplace(42, 42); // $ targets=element_two_conversions Container references; references.emplace(converted, 0, 0); // no target @@ -247,13 +247,13 @@ void test_value_conversion_operator() { ValueConversionOperator value; const ValueConversionOperator constValue; volatile ValueConversionOperator volatileValue; - container.emplace(value); // $ MISSING: targets=element_operator_value - container.emplace(ValueConversionOperator()); // $ MISSING: targets=element_operator_value - container.emplace(constValue); // no targets - container.emplace(volatileValue); // no targets + container.emplace(value); // $ targets=element_operator_value + container.emplace(ValueConversionOperator()); // $ targets=element_operator_value + container.emplace(constValue); // $ SPURIOUS: targets=element_operator_value + container.emplace(volatileValue); // $ SPURIOUS: targets=element_operator_value container.emplace(value, 0); // no targets - container.emplace(value, 0, 0); // $ MISSING: targets=element_operator_const_ref - container.emplace(value, 0, 0, 0); // $ MISSING: targets=element_operator_rref + container.emplace(value, 0, 0); // $ targets=element_operator_const_ref + container.emplace(value, 0, 0, 0); // $ targets=element_operator_rref container.emplace(value, 0, 0, 0, 0); // no targets } @@ -262,27 +262,27 @@ void test_lvalue_conversion_operator() { LvalueConversionOperator value; const LvalueConversionOperator constValue; volatile LvalueConversionOperator volatileValue; - container.emplace(value); // $ MISSING: targets=element_operator_value - container.emplace(value, 0); // $ MISSING: targets=element_operator_lref - container.emplace(value, 0, 0); // $ MISSING: targets=element_operator_const_ref + container.emplace(value); // $ targets=element_operator_value + container.emplace(value, 0); // $ targets=element_operator_lref + container.emplace(value, 0, 0); // $ targets=element_operator_const_ref container.emplace(value, 0, 0, 0); // no targets - container.emplace(value, 0, 0, 0, 0); // $ MISSING: targets=element_operator_const_volatile_ref - container.emplace(value, ValueConversionOperator()); // $ MISSING: targets=element_operator_lref - container.emplace(LvalueConversionOperator()); // no targets - container.emplace(constValue); // no targets - container.emplace(volatileValue); // no targets + container.emplace(value, 0, 0, 0, 0); // $ targets=element_operator_const_volatile_ref + container.emplace(value, ValueConversionOperator()); // $ targets=element_operator_lref + container.emplace(LvalueConversionOperator()); // $ SPURIOUS: targets=element_operator_value + container.emplace(constValue); // $ SPURIOUS: targets=element_operator_value + container.emplace(volatileValue); // $ SPURIOUS: targets=element_operator_value } void test_rvalue_conversion_operator() { Container container; RvalueConversionOperator value; const RvalueConversionOperator constValue; - container.emplace(value); // no targets - container.emplace(static_cast(constValue)); // no targets - container.emplace(RvalueConversionOperator()); // $ MISSING: targets=element_operator_value + container.emplace(value); // $ SPURIOUS: targets=element_operator_value + container.emplace(static_cast(constValue)); // $ SPURIOUS: targets=element_operator_value + container.emplace(RvalueConversionOperator()); // $ targets=element_operator_value container.emplace(RvalueConversionOperator(), 0); // no targets - container.emplace(RvalueConversionOperator(), 0, 0); // $ MISSING: targets=element_operator_const_ref - container.emplace(RvalueConversionOperator(), 0, 0, 0); // $ MISSING: targets=element_operator_rref + container.emplace(RvalueConversionOperator(), 0, 0); // $ targets=element_operator_const_ref + container.emplace(RvalueConversionOperator(), 0, 0, 0); // $ targets=element_operator_rref container.emplace(RvalueConversionOperator(), 0, 0, 0, 0); // no targets } @@ -290,14 +290,14 @@ void test_conversion_operator_qualification() { Container container; const ConstConversionOperator constValue; volatile ConstConversionOperator volatileValue; - container.emplace(constValue, 0, 0); // $ MISSING: targets=element_operator_const_ref - container.emplace(ConstConversionOperator(), 0, 0); // $ MISSING: targets=element_operator_const_ref + container.emplace(constValue, 0, 0); // $ targets=element_operator_const_ref + container.emplace(ConstConversionOperator(), 0, 0); // $ targets=element_operator_const_ref container.emplace(constValue, 0); // no targets container.emplace(constValue, 0, 0, 0); // no targets - container.emplace(volatileValue); // no targets + container.emplace(volatileValue); // $ targets=element_operator_value const volatile VolatileConversionOperator constVolatileValue; - container.emplace(constVolatileValue, 0, 0, 0, 0); // $ MISSING: targets=element_operator_const_volatile_ref + container.emplace(constVolatileValue, 0, 0, 0, 0); // $ targets=element_operator_const_volatile_ref container.emplace(constVolatileValue, 0, 0); // no targets container.emplace(constVolatileValue, 0); // no targets } @@ -314,8 +314,8 @@ void test_conversion_operator_limit() { void test_array_conversion_operator() { Container container; - container.emplace(ArrayConversionOperator(), 42); // $ MISSING: targets=element_ref_const_char_ptr_const_ref_int - container.emplace(ArrayConversionOperator(), 0, 0); // no targets + container.emplace(ArrayConversionOperator(), 42); // $ targets=element_ref_const_char_ptr_const_ref_int + container.emplace(ArrayConversionOperator(), 0, 0); // $ SPURIOUS: targets=element_ref_pointer_rref container.emplace(ArrayConversionOperator(), 0, 0, 0); // no targets } @@ -434,7 +434,7 @@ void test_inheritance_conversions() { downcast.emplace(base); // no targets downcast.emplace(&base, 0); // no targets Container inherited; - inherited.emplace(derived); // $ MISSING: targets=element_operator_value + inherited.emplace(derived); // $ targets=element_operator_value } struct ElementFromStandardPointer { From 1814eb4c4544fdc8458dcaee34ef1c459de9ce9b Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:21:43 +0100 Subject: [PATCH 12/25] C++: Track conversion phases. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index f72084ba047d..4d8151a1bbaf 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1221,10 +1221,24 @@ module ConstructorForwarding { else category != LValue() } + /** + * Tracks the ordering of standard conversions. All phases allow parameter matching + * and, if none has been used, a user-defined conversion that resets to `Initial()`. + */ + private newtype Phase = + // Allows array/function decay, a value or base-class conversion, or pointer qualification. + Initial() or + // Allows a value or base-class conversion or pointer qualification, but no further decay. + AfterTransformation() or + // Allows only pointer qualification among the remaining conversion steps. + AfterValueConversion() or + // Allows no further conversion steps. + AfterQualification() + private predicate isUnderlyingType(Type type) { type = type.getUnderlyingType() } private newtype TTypeState = - MkTypeState(Type type, ValueCategory category, Boolean conversionUsed) { + MkTypeState(Type type, ValueCategory category, Boolean conversionUsed, Phase phase) { not type instanceof Cpp::ReferenceType and not type instanceof FunctionReferenceType and isUnderlyingType(type) @@ -1257,18 +1271,25 @@ module ConstructorForwarding { } private class TypeState extends TTypeState { - Type getType() { this = MkTypeState(result, _, _) } + Type getType() { this = MkTypeState(result, _, _, _) } - ValueCategory getCategory() { this = MkTypeState(_, result, _) } + ValueCategory getCategory() { this = MkTypeState(_, result, _, _) } - predicate hasNotUsedConversion() { this = MkTypeState(_, _, false) } + Phase getPhase() { this = MkTypeState(_, _, _, result) } + + /** Holds if this state's phase permits a value or base-class conversion. */ + predicate canConvertValue() { + this.getPhase() = Initial() or this.getPhase() = AfterTransformation() + } + + predicate hasNotUsedConversion() { this = MkTypeState(_, _, false, _) } string toString() { result = this.getType().toString() } predicate isSource(Type argType) { argType = getForwardedArgumentType(_, _, _) and exists(ValueCategory category | - this = MkTypeState(getValueType(argType, category), category, false) + this = MkTypeState(getValueType(argType, category), category, false, Initial()) ) } @@ -1294,8 +1315,10 @@ module ConstructorForwarding { private predicate arrayToPointerStep(TypeState argState, TypeState paramState) { exists(Cpp::ArrayType array, boolean conversionUsed | - argState = MkTypeState(array, _, conversionUsed) and - paramState = MkTypeState(pointerType(array.getBaseType()), PRValue(), conversionUsed) + argState = MkTypeState(array, _, conversionUsed, Initial()) and + paramState = + MkTypeState(pointerType(array.getBaseType()), PRValue(), conversionUsed, + AfterTransformation()) ) } @@ -1303,7 +1326,7 @@ module ConstructorForwarding { exists(ConvertingConstructor constructor | argState.hasNotUsedConversion() and argState.matchesParameter(constructor.getUnderlyingFromType()) and - paramState = MkTypeState(constructor.getDeclaringType(), PRValue(), true) + paramState = MkTypeState(constructor.getDeclaringType(), PRValue(), true, Initial()) ) } @@ -1317,7 +1340,7 @@ module ConstructorForwarding { argState.getType().stripTopLevelSpecifiers().(Class).getABaseClass*() and paramState = MkTypeState(getValueType(conversion.getDestType().getUnderlyingType(), category), category, - true) + true, Initial()) ) } From a6287a82176dade834bb53f8db6b13913f8d9fe2 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:24:17 +0100 Subject: [PATCH 13/25] C++: Handle routine-to-function-pointer conversions. --- cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 4d8151a1bbaf..5d65c6624a35 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1322,6 +1322,14 @@ module ConstructorForwarding { ) } + private predicate functionToPointerStep(TypeState argState, TypeState paramState) { + exists(RoutineType routine, FunctionPointerType pointer, boolean conversionUsed | + argState = MkTypeState(routine, _, conversionUsed, Initial()) and + routine = pointer.getBaseType().getUnderlyingType() and + paramState = MkTypeState(pointer, PRValue(), conversionUsed, AfterTransformation()) + ) + } + private predicate convertingConstructorStep(TypeState argState, TypeState paramState) { exists(ConvertingConstructor constructor | argState.hasNotUsedConversion() and @@ -1347,6 +1355,8 @@ module ConstructorForwarding { private predicate step(TypeState argState, TypeState paramState) { arrayToPointerStep(argState, paramState) or + functionToPointerStep(argState, paramState) + or convertingConstructorStep(argState, paramState) or conversionOperatorStep(argState, paramState) From b44e817a8e189a07535c14385d823f495ec9e553 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 17:47:20 +0100 Subject: [PATCH 14/25] C++: Accept test changes. --- cpp/ql/test/library-tests/dataflow/forwarding/test.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index 1ef7ced4f6a2..445163ea17dd 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -476,13 +476,13 @@ void test_function_conversions() { Container container; Callback pointer = callback; NothrowCallback nothrowPointer = nothrowCallback; - container.emplace(callback); // $ MISSING: targets=element_callback - container.emplace(nothrowCallback); // $ MISSING: targets=element_callback + container.emplace(callback); // $ targets=element_callback + container.emplace(nothrowCallback); // $ targets=element_callback container.emplace(nothrowPointer); // $ targets=element_callback - container.emplace(nothrowCallback, 0); // $ MISSING: targets=element_nothrow_callback + container.emplace(nothrowCallback, 0); // $ targets=element_nothrow_callback container.emplace(pointer, 0); // $ SPURIOUS: targets=element_nothrow_callback - container.emplace(callback, 0); // no targets - container.emplace(callback, 0, 0); // $ MISSING: targets=element_callback_const_ref + container.emplace(callback, 0); // $ SPURIOUS: targets=element_nothrow_callback + container.emplace(callback, 0, 0); // $ targets=element_callback_const_ref container.emplace(nullptr); // $ MISSING: targets=element_callback container.emplace(differentCallback); // no targets Container boolean; From a8f6d7763dde4fb9d64718e585ca12911e07c1c9 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:25:52 +0100 Subject: [PATCH 15/25] C++: Handle base-class conversions. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 5d65c6624a35..7f2578dd0b41 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1208,6 +1208,11 @@ module ConstructorForwarding { (t1.isVolatile() implies t2.isVolatile()) } + private predicate baseTypeCompatible(Type arg, Type param) { + param.stripTopLevelSpecifiers() = arg.stripTopLevelSpecifiers().(Class).getABaseClass+() and + preservesQualifiers(arg, param) + } + private predicate referenceAcceptsCategory(Cpp::ReferenceType t, ValueCategory category) { if t instanceof Cpp::LValueReferenceType then @@ -1330,6 +1335,15 @@ module ConstructorForwarding { ) } + private predicate baseClassStep(TypeState argState, TypeState paramState) { + exists(Type arg, Type param, ValueCategory category, boolean conversionUsed | + argState = MkTypeState(arg, category, conversionUsed, _) and + argState.canConvertValue() and + baseTypeCompatible(arg, param) and + paramState = MkTypeState(param, category, conversionUsed, AfterValueConversion()) + ) + } + private predicate convertingConstructorStep(TypeState argState, TypeState paramState) { exists(ConvertingConstructor constructor | argState.hasNotUsedConversion() and @@ -1357,6 +1371,8 @@ module ConstructorForwarding { or functionToPointerStep(argState, paramState) or + baseClassStep(argState, paramState) + or convertingConstructorStep(argState, paramState) or conversionOperatorStep(argState, paramState) From fb59a05702b5c0a24175804f135d11975e129629 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 17:48:34 +0100 Subject: [PATCH 16/25] C++: Accept test changes. --- cpp/ql/test/library-tests/dataflow/forwarding/test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index 445163ea17dd..ed5f3ab30e7c 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -421,10 +421,10 @@ void test_inheritance_conversions() { const ConversionDerived constDerived{}; ConversionBase base; Container container; - container.emplace(derived); // $ MISSING: targets=element_base_value - container.emplace(derived, 0); // $ MISSING: targets=element_base_lref - container.emplace(constDerived, 0, 0); // $ MISSING: targets=element_base_const_ref - container.emplace(ConversionDerived(), 0, 0, 0); // $ MISSING: targets=element_base_rref + container.emplace(derived); // $ targets=element_base_value + container.emplace(derived, 0); // $ targets=element_base_lref + container.emplace(constDerived, 0, 0); // $ targets=element_base_const_ref + container.emplace(ConversionDerived(), 0, 0, 0); // $ targets=element_base_rref container.emplace(derived, 0, 0, 0); // no targets container.emplace(constDerived, 0); // no targets container.emplace(&derived, 0, 0, 0, 0); // $ MISSING: targets=element_base_pointer From ec9d660636e9f750e66a5095d87fae992d778408 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:28:03 +0100 Subject: [PATCH 17/25] C++: Handle conversions in the base-type of a pointer. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 7f2578dd0b41..c61aade58a23 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1335,6 +1335,32 @@ module ConstructorForwarding { ) } + private predicate hasNoTopLevelSpecifiers(Type type) { type = type.stripTopLevelSpecifiers() } + + private newtype PtrKind = + NormalPtrKind() or + FunPtrKind() + + private Type pointerBase(Type pointer, PtrKind k) { + result = pointer.(Cpp::PointerType).getBaseType() and + k = NormalPtrKind() + or + result = pointer.(FunctionPointerType).getBaseType() and + k = FunPtrKind() + } + + private predicate pointerQualificationStep(TypeState argState, TypeState paramState) { + exists(Type argType, Type paramType, boolean conversionUsed, PtrKind k | + argState = MkTypeState(_, _, conversionUsed, _) and + argState.getPhase() != AfterQualification() and + argType = argState.getType().stripTopLevelSpecifiers() and + hasNoTopLevelSpecifiers(paramType) and + argType != paramType and + qualificationCompatible(pointerBase(argType, k), pointerBase(paramType, k)) and + paramState = MkTypeState(paramType, PRValue(), conversionUsed, AfterQualification()) + ) + } + private predicate baseClassStep(TypeState argState, TypeState paramState) { exists(Type arg, Type param, ValueCategory category, boolean conversionUsed | argState = MkTypeState(arg, category, conversionUsed, _) and @@ -1371,6 +1397,8 @@ module ConstructorForwarding { or functionToPointerStep(argState, paramState) or + pointerQualificationStep(argState, paramState) + or baseClassStep(argState, paramState) or convertingConstructorStep(argState, paramState) From 2949b6530d7ecb1bad30691656974761569d8c28 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 17:50:41 +0100 Subject: [PATCH 18/25] C++: Accept test changes. --- .../library-tests/dataflow/forwarding/test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index ed5f3ab30e7c..cbfac4822c7d 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -56,7 +56,7 @@ void test() { { Container c; c.emplace("abc", 42); // $ targets=element_const_char_ptr_int - c.emplace((char*)nullptr, 42); // $ targets=element_char_ptr_int + c.emplace((char*)nullptr, 42); // $ targets=element_char_ptr_int SPURIOUS: targets=element_const_char_ptr_int } { Container c; @@ -383,11 +383,11 @@ void test_pointer_qualification_conversions() { char* pointer = buffer; char** doublePointer = &pointer; const char* constPointer = buffer; - container.emplace(buffer); // $ MISSING: targets=element_qualified_pointer - container.emplace(pointer); // $ MISSING: targets=element_qualified_pointer - container.emplace(pointer, 0); // $ MISSING: targets=element_qualified_pointer_const_ref - container.emplace(buffer, 0, 0); // $ MISSING: targets=element_qualified_pointer_rref - container.emplace(static_cast(pointer), 0, 0); // $ MISSING: targets=element_qualified_pointer_rref + container.emplace(buffer); // $ targets=element_qualified_pointer + container.emplace(pointer); // $ targets=element_qualified_pointer + container.emplace(pointer, 0); // $ targets=element_qualified_pointer_const_ref + container.emplace(buffer, 0, 0); // $ targets=element_qualified_pointer_rref + container.emplace(static_cast(pointer), 0, 0); // $ targets=element_qualified_pointer_rref container.emplace(pointer, 0, 0, 0); // no target container.emplace(doublePointer, 0, 0, 0, 0); // no target container.emplace(doublePointer, 0, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_const_double_pointer @@ -500,7 +500,7 @@ void test_standard_conversion_phases() { Container converted; char buffer[] = "abc"; - converted.emplace(buffer); // $ MISSING: targets=element_conversion_value + converted.emplace(buffer); // $ targets=element_conversion_value converted.emplace(&value); // no targets converted.emplace(ScopedArithmeticEnum::value); // no targets } From 55addf38ac237c6e040f7298e83cf3abdac8d28d Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:29:49 +0100 Subject: [PATCH 19/25] C++: Handle arithmetic conversions. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index c61aade58a23..f90271a40f2d 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1335,8 +1335,28 @@ module ConstructorForwarding { ) } + private predicate arithmeticConversion(Type t) { + t instanceof ArithmeticType + or + t instanceof Enum and not t instanceof ScopedEnum + } + private predicate hasNoTopLevelSpecifiers(Type type) { type = type.stripTopLevelSpecifiers() } + private predicate valueConversionStep(TypeState argState, TypeState paramState) { + exists(Type argType, Type paramType, boolean conversionUsed | + argState = MkTypeState(_, _, conversionUsed, _) and + argState.canConvertValue() and + argType = argState.getType().stripTopLevelSpecifiers() and + hasNoTopLevelSpecifiers(paramType) and + argType != paramType and + paramState = MkTypeState(paramType, PRValue(), conversionUsed, AfterValueConversion()) + | + paramType instanceof ArithmeticType and + arithmeticConversion(argType) + ) + } + private newtype PtrKind = NormalPtrKind() or FunPtrKind() @@ -1397,6 +1417,8 @@ module ConstructorForwarding { or functionToPointerStep(argState, paramState) or + valueConversionStep(argState, paramState) + or pointerQualificationStep(argState, paramState) or baseClassStep(argState, paramState) From 6ca5971977be4b8168607f5987e4720c732f84c7 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 17:55:40 +0100 Subject: [PATCH 20/25] C++: Accept test changes. --- .../dataflow/forwarding/test.cpp | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index cbfac4822c7d..df07ff55d364 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -41,7 +41,7 @@ struct ElementFromMutablePointer { void test() { { Container c; - c.emplace(42); // $ targets=element_int + c.emplace(42); // $ targets=element_int SPURIOUS: targets=element_short targets=element_ul } { Container c; @@ -63,15 +63,15 @@ void test() { Container cr; { const int x = 42; - c.emplace(x); // $ targets=element_int + c.emplace(x); // $ targets=element_int SPURIOUS: targets=element_short targets=element_ul cr.emplace(x); // $ targets=element_ref_const_int_ref } { int x = 42; - c.emplace(x); // $ targets=element_int + c.emplace(x); // $ targets=element_int SPURIOUS: targets=element_short targets=element_ul cr.emplace(x); // $ targets=element_ref_int_lref SPURIOUS: targets=element_ref_const_int_ref } - c.emplace(42); // $ targets=element_int + c.emplace(42); // $ targets=element_int SPURIOUS: targets=element_short targets=element_ul cr.emplace(42); // $ targets=element_ref_int_rref SPURIOUS: targets=element_ref_const_int_ref cr.emplace("abc", 42); // $ targets=element_ref_const_char_ptr_const_ref_int const char buffer[] = "abc"; @@ -81,8 +81,8 @@ void test() { Container container; short shortValue = 42; unsigned long longValue = 42; - container.emplace(shortValue); // $ targets=element_short - container.emplace(longValue); // $ targets=element_ul + container.emplace(shortValue); // $ targets=element_short SPURIOUS: targets=element_int targets=element_ul + container.emplace(longValue); // $ targets=element_ul SPURIOUS: targets=element_int targets=element_short } } @@ -349,12 +349,12 @@ void test_arithmetic_conversions() { Container container; short value = 42; long longValue = 42; - container.emplace(value); // $ MISSING: targets=element_arithmetic_long - container.emplace(1.5f, 0); // $ MISSING: targets=element_arithmetic_double - container.emplace(arithmeticValue); // $ MISSING: targets=element_arithmetic_long - container.emplace(42, 0, 0); // $ MISSING: targets=element_arithmetic_bool - container.emplace(value, 0, 0, 0); // $ MISSING: targets=element_arithmetic_const_ref - container.emplace(value, 0, 0, 0, 0); // $ MISSING: targets=element_arithmetic_rref + container.emplace(value); // $ targets=element_arithmetic_long + container.emplace(1.5f, 0); // $ targets=element_arithmetic_double + container.emplace(arithmeticValue); // $ targets=element_arithmetic_long + container.emplace(42, 0, 0); // $ targets=element_arithmetic_bool + container.emplace(value, 0, 0, 0); // $ targets=element_arithmetic_const_ref + container.emplace(value, 0, 0, 0, 0); // $ targets=element_arithmetic_rref container.emplace(longValue, 0, 0, 0, 0); // no target container.emplace(ScopedArithmeticEnum::value); // no targets } @@ -362,10 +362,10 @@ void test_arithmetic_conversions() { void test_standard_and_user_defined_conversions() { short value = 42; Container before; - before.emplace(value); // $ MISSING: targets=element_conversion_value + before.emplace(value); // $ targets=element_conversion_value Container after; - after.emplace(ValueConversionOperator()); // $ MISSING: targets=element_arithmetic_long - after.emplace(ValueConversionOperator(), 0, 0, 0); // $ MISSING: targets=element_arithmetic_const_ref + after.emplace(ValueConversionOperator()); // $ targets=element_arithmetic_long + after.emplace(ValueConversionOperator(), 0, 0, 0); // $ targets=element_arithmetic_const_ref } struct ElementFromQualifiedPointer { @@ -496,7 +496,7 @@ void test_standard_conversion_phases() { arithmetic.emplace(value, 0, 0, 0, 0); // no targets arithmetic.emplace(&value); // no targets arithmetic.emplace(nullptr, 0, 0); // no targets - arithmetic.emplace(ValueConversionOperator(), 0, 0, 0, 0); // $ MISSING: targets=element_arithmetic_rref + arithmetic.emplace(ValueConversionOperator(), 0, 0, 0, 0); // $ targets=element_arithmetic_rref Container converted; char buffer[] = "abc"; From a82e970510b9f7850b283bf9a7a7100796021da7 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:30:40 +0100 Subject: [PATCH 21/25] C++: Handle pointer conversions. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index f90271a40f2d..8f6f16e82854 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1213,6 +1213,23 @@ module ConstructorForwarding { preservesQualifiers(arg, param) } + private predicate voidType(Type t) { t.stripTopLevelSpecifiers() instanceof VoidType } + + private predicate routineType(Type t) { t.stripTopLevelSpecifiers() instanceof RoutineType } + + private predicate pointerConversion(Cpp::PointerType arg, Cpp::PointerType param) { + exists(Type argBase, Type paramBase | + argBase = arg.getBaseType() and + paramBase = param.getBaseType() + | + baseTypeCompatible(argBase, paramBase) + or + voidType(paramBase) and + not routineType(argBase) and + preservesQualifiers(argBase, paramBase) + ) + } + private predicate referenceAcceptsCategory(Cpp::ReferenceType t, ValueCategory category) { if t instanceof Cpp::LValueReferenceType then @@ -1354,6 +1371,8 @@ module ConstructorForwarding { | paramType instanceof ArithmeticType and arithmeticConversion(argType) + or + pointerConversion(argType, paramType) ) } From 15447bfa921f1a3cb5fd1b15ac3b2a1c6352cf2c Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 18:04:26 +0100 Subject: [PATCH 22/25] C++: Accept test changes. --- cpp/ql/test/library-tests/dataflow/forwarding/test.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index df07ff55d364..b27fe5e0a215 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -427,9 +427,9 @@ void test_inheritance_conversions() { container.emplace(ConversionDerived(), 0, 0, 0); // $ targets=element_base_rref container.emplace(derived, 0, 0, 0); // no targets container.emplace(constDerived, 0); // no targets - container.emplace(&derived, 0, 0, 0, 0); // $ MISSING: targets=element_base_pointer + container.emplace(&derived, 0, 0, 0, 0); // $ targets=element_base_pointer container.emplace(&constDerived, 0, 0, 0, 0); // no targets - container.emplace(&constDerived, 0, 0, 0, 0, 0); // $ MISSING: targets=element_const_base_pointer + container.emplace(&constDerived, 0, 0, 0, 0, 0); // $ targets=element_const_base_pointer Container downcast; downcast.emplace(base); // no targets downcast.emplace(&base, 0); // no targets @@ -448,9 +448,9 @@ void test_standard_pointer_conversions() { Container container; int value = 42; const int constValue = 42; - container.emplace(&value); // $ MISSING: targets=element_void_pointer + container.emplace(&value); // $ targets=element_void_pointer container.emplace(&constValue); // no targets - container.emplace(&constValue, 0); // $ MISSING: targets=element_const_void_pointer + container.emplace(&constValue, 0); // $ targets=element_const_void_pointer container.emplace(nullptr); // $ MISSING: targets=element_void_pointer container.emplace(nullptr, 0, 0); // $ MISSING: targets=element_int_pointer container.emplace(0, 0, 0); // no targets From 70ee2b896e99642efcc6468508039cd9e7da581e Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 16:31:25 +0100 Subject: [PATCH 23/25] C++: Handle null and boolean conversions. --- .../semmle/code/cpp/dataflow/ExternalFlow.qll | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 8f6f16e82854..a2b6c97cef1e 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1230,6 +1230,20 @@ module ConstructorForwarding { ) } + private predicate nullPointerConversion(Type t) { + t instanceof Cpp::PointerType or t instanceof FunctionPointerType + } + + private predicate booleanConversion(Type t) { + t instanceof Cpp::PointerType + or + t instanceof FunctionPointerType + or + t instanceof Cpp::ArrayType + or + t instanceof RoutineType + } + private predicate referenceAcceptsCategory(Cpp::ReferenceType t, ValueCategory category) { if t instanceof Cpp::LValueReferenceType then @@ -1373,6 +1387,12 @@ module ConstructorForwarding { arithmeticConversion(argType) or pointerConversion(argType, paramType) + or + argType instanceof NullPointerType and + nullPointerConversion(paramType) + or + booleanConversion(argType) and + paramType instanceof BoolType ) } From e42def7a4c1417e391f99bf19ece9847280cc31c Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 18:09:40 +0100 Subject: [PATCH 24/25] C++: Accept test changes. --- .../dataflow/external-models/flow.expected | 34 +++++++++++++++++++ .../dataflow/external-models/test.cpp | 4 +-- .../dataflow/forwarding/test.cpp | 10 +++--- 3 files changed, 41 insertions(+), 7 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 21aeeb32e3a1..ee0d3e830d00 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected @@ -682,22 +682,40 @@ edges | test.cpp:362:15:362:23 | call to ymlSource | test.cpp:362:15:362:25 | call to ymlSource | provenance | Src:MaD:48 | | test.cpp:362:15:362:25 | call to ymlSource | test.cpp:363:15:363:15 | *x | provenance | | | test.cpp:363:5:363:5 | forward output argument [s] | test.cpp:365:30:365:30 | *f [s] | provenance | | +| test.cpp:363:5:363:5 | forward output argument [ul] | test.cpp:365:30:365:30 | *f [ul] | provenance | | | test.cpp:363:15:363:15 | *x | test.cpp:341:30:341:32 | arg | provenance | | +| test.cpp:363:15:363:15 | *x | test.cpp:345:38:345:40 | arg | provenance | | | test.cpp:363:15:363:15 | *x | test.cpp:363:5:363:5 | forward output argument [s] | provenance | | +| test.cpp:363:15:363:15 | *x | test.cpp:363:5:363:5 | forward output argument [ul] | provenance | | | test.cpp:365:30:365:30 | *f [s] | test.cpp:365:32:365:34 | call to get [s] | provenance | MaD:88 | +| test.cpp:365:30:365:30 | *f [ul] | test.cpp:365:32:365:34 | call to get [ul] | provenance | MaD:88 | | test.cpp:365:32:365:34 | call to get [s] | test.cpp:365:32:365:34 | call to get [s] | provenance | | | test.cpp:365:32:365:34 | call to get [s] | test.cpp:366:13:366:13 | *c [s] | provenance | | +| test.cpp:365:32:365:34 | call to get [ul] | test.cpp:365:32:365:34 | call to get [ul] | provenance | | +| test.cpp:365:32:365:34 | call to get [ul] | test.cpp:367:13:367:13 | *c [ul] | provenance | | | test.cpp:366:13:366:13 | *c [s] | test.cpp:366:13:366:15 | s | provenance | | | test.cpp:366:13:366:13 | *c [s] | test.cpp:366:15:366:15 | s | provenance | Sink:MaD:3 | | test.cpp:366:13:366:15 | s | test.cpp:366:15:366:15 | s | provenance | Sink:MaD:3 | +| test.cpp:367:13:367:13 | *c [ul] | test.cpp:367:13:367:16 | ul | provenance | | +| test.cpp:367:13:367:13 | *c [ul] | test.cpp:367:15:367:16 | ul | provenance | Sink:MaD:3 | +| test.cpp:367:13:367:16 | ul | test.cpp:367:15:367:16 | ul | provenance | Sink:MaD:3 | | test.cpp:371:24:371:32 | call to ymlSource | test.cpp:371:24:371:34 | call to ymlSource | provenance | Src:MaD:48 | | test.cpp:371:24:371:34 | call to ymlSource | test.cpp:372:15:372:16 | *ul | provenance | | +| test.cpp:372:5:372:5 | forward output argument [s] | test.cpp:374:30:374:30 | *f [s] | provenance | | | test.cpp:372:5:372:5 | forward output argument [ul] | test.cpp:374:30:374:30 | *f [ul] | provenance | | +| test.cpp:372:15:372:16 | *ul | test.cpp:341:30:341:32 | arg | provenance | | | test.cpp:372:15:372:16 | *ul | test.cpp:345:38:345:40 | arg | provenance | | +| test.cpp:372:15:372:16 | *ul | test.cpp:372:5:372:5 | forward output argument [s] | provenance | | | test.cpp:372:15:372:16 | *ul | test.cpp:372:5:372:5 | forward output argument [ul] | provenance | | +| test.cpp:374:30:374:30 | *f [s] | test.cpp:374:32:374:34 | call to get [s] | provenance | MaD:88 | | test.cpp:374:30:374:30 | *f [ul] | test.cpp:374:32:374:34 | call to get [ul] | provenance | MaD:88 | +| test.cpp:374:32:374:34 | call to get [s] | test.cpp:374:32:374:34 | call to get [s] | provenance | | +| test.cpp:374:32:374:34 | call to get [s] | test.cpp:375:13:375:13 | *c [s] | provenance | | | test.cpp:374:32:374:34 | call to get [ul] | test.cpp:374:32:374:34 | call to get [ul] | provenance | | | test.cpp:374:32:374:34 | call to get [ul] | test.cpp:376:13:376:13 | *c [ul] | provenance | | +| test.cpp:375:13:375:13 | *c [s] | test.cpp:375:13:375:15 | s | provenance | | +| test.cpp:375:13:375:13 | *c [s] | test.cpp:375:15:375:15 | s | provenance | Sink:MaD:3 | +| test.cpp:375:13:375:15 | s | test.cpp:375:15:375:15 | s | provenance | Sink:MaD:3 | | test.cpp:376:13:376:13 | *c [ul] | test.cpp:376:13:376:16 | ul | provenance | | | test.cpp:376:13:376:13 | *c [ul] | test.cpp:376:15:376:16 | ul | provenance | Sink:MaD:3 | | test.cpp:376:13:376:16 | ul | test.cpp:376:15:376:16 | ul | provenance | Sink:MaD:3 | @@ -1550,20 +1568,34 @@ nodes | test.cpp:362:15:362:23 | call to ymlSource | semmle.label | call to ymlSource | | test.cpp:362:15:362:25 | call to ymlSource | semmle.label | call to ymlSource | | test.cpp:363:5:363:5 | forward output argument [s] | semmle.label | forward output argument [s] | +| test.cpp:363:5:363:5 | forward output argument [ul] | semmle.label | forward output argument [ul] | | test.cpp:363:15:363:15 | *x | semmle.label | *x | | test.cpp:365:30:365:30 | *f [s] | semmle.label | *f [s] | +| test.cpp:365:30:365:30 | *f [ul] | semmle.label | *f [ul] | | test.cpp:365:32:365:34 | call to get [s] | semmle.label | call to get [s] | | test.cpp:365:32:365:34 | call to get [s] | semmle.label | call to get [s] | +| test.cpp:365:32:365:34 | call to get [ul] | semmle.label | call to get [ul] | +| test.cpp:365:32:365:34 | call to get [ul] | semmle.label | call to get [ul] | | test.cpp:366:13:366:13 | *c [s] | semmle.label | *c [s] | | test.cpp:366:13:366:15 | s | semmle.label | s | | test.cpp:366:15:366:15 | s | semmle.label | s | +| test.cpp:367:13:367:13 | *c [ul] | semmle.label | *c [ul] | +| test.cpp:367:13:367:16 | ul | semmle.label | ul | +| test.cpp:367:15:367:16 | ul | semmle.label | ul | | test.cpp:371:24:371:32 | call to ymlSource | semmle.label | call to ymlSource | | test.cpp:371:24:371:34 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:372:5:372:5 | forward output argument [s] | semmle.label | forward output argument [s] | | test.cpp:372:5:372:5 | forward output argument [ul] | semmle.label | forward output argument [ul] | | test.cpp:372:15:372:16 | *ul | semmle.label | *ul | +| test.cpp:374:30:374:30 | *f [s] | semmle.label | *f [s] | | test.cpp:374:30:374:30 | *f [ul] | semmle.label | *f [ul] | +| test.cpp:374:32:374:34 | call to get [s] | semmle.label | call to get [s] | +| test.cpp:374:32:374:34 | call to get [s] | semmle.label | call to get [s] | | test.cpp:374:32:374:34 | call to get [ul] | semmle.label | call to get [ul] | | test.cpp:374:32:374:34 | call to get [ul] | semmle.label | call to get [ul] | +| test.cpp:375:13:375:13 | *c [s] | semmle.label | *c [s] | +| test.cpp:375:13:375:15 | s | semmle.label | s | +| test.cpp:375:15:375:15 | s | semmle.label | s | | test.cpp:376:13:376:13 | *c [ul] | semmle.label | *c [ul] | | test.cpp:376:13:376:16 | ul | semmle.label | ul | | test.cpp:376:15:376:16 | ul | semmle.label | ul | @@ -1862,6 +1894,8 @@ 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:363:15:363:15 | *x | test.cpp:341:30:341:32 | arg | test.cpp:341:3:341:22 | *this [Return] [s] | test.cpp:363:5:363:5 | forward output argument [s] | +| test.cpp:363:15:363:15 | *x | test.cpp:345:38:345:40 | arg | test.cpp:345:3:345:22 | *this [Return] [ul] | test.cpp:363:5:363:5 | forward output argument [ul] | +| test.cpp:372:15:372:16 | *ul | test.cpp:341:30:341:32 | arg | test.cpp:341:3:341:22 | *this [Return] [s] | test.cpp:372:5:372:5 | forward output argument [s] | | test.cpp:372:15:372:16 | *ul | test.cpp:345:38:345:40 | arg | test.cpp:345:3:345:22 | *this [Return] [ul] | test.cpp:372:5:372:5 | forward output argument [ul] | | test.cpp:443:18:443:18 | *x | test.cpp:435:34:435:38 | first | test.cpp:435:3:435:28 | *this [Return] [x] | test.cpp:443:5:443:5 | emplace output argument [element, x] | | test.cpp:453:21:453:21 | *x | test.cpp:436:39:436:44 | second | test.cpp:436:3:436:28 | *this [Return] [x] | test.cpp:453:5:453:5 | emplace output argument [element, x] | 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 ae4c4f657aba..bfc7c9ed1148 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp @@ -364,7 +364,7 @@ void forward_test() { ConstructableFromInt c = f.get(); ymlSink(c.s); // $ ir - ymlSink(c.ul); // clean + ymlSink(c.ul); // $ SPURIOUS: ir } { Forwarder f; @@ -372,7 +372,7 @@ void forward_test() { f.forward(ul); ConstructableFromInt c = f.get(); - ymlSink(c.s); // clean + ymlSink(c.s); // $ SPURIOUS: ir ymlSink(c.ul); // $ ir } } diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp index b27fe5e0a215..ac225d6c997b 100644 --- a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -451,10 +451,10 @@ void test_standard_pointer_conversions() { container.emplace(&value); // $ targets=element_void_pointer container.emplace(&constValue); // no targets container.emplace(&constValue, 0); // $ targets=element_const_void_pointer - container.emplace(nullptr); // $ MISSING: targets=element_void_pointer - container.emplace(nullptr, 0, 0); // $ MISSING: targets=element_int_pointer + container.emplace(nullptr); // $ targets=element_void_pointer + container.emplace(nullptr, 0, 0); // $ targets=element_int_pointer container.emplace(0, 0, 0); // no targets - container.emplace(&value, 0, 0, 0, 0); // $ MISSING: targets=element_pointer_bool + container.emplace(&value, 0, 0, 0, 0); // $ targets=element_pointer_bool container.emplace(nullptr, 0, 0, 0, 0); // no targets void* opaque = &value; container.emplace(opaque, 0, 0); // no targets @@ -483,10 +483,10 @@ void test_function_conversions() { container.emplace(pointer, 0); // $ SPURIOUS: targets=element_nothrow_callback container.emplace(callback, 0); // $ SPURIOUS: targets=element_nothrow_callback container.emplace(callback, 0, 0); // $ targets=element_callback_const_ref - container.emplace(nullptr); // $ MISSING: targets=element_callback + container.emplace(nullptr); // $ targets=element_callback container.emplace(differentCallback); // no targets Container boolean; - boolean.emplace(callback, 0, 0, 0, 0); // $ MISSING: targets=element_pointer_bool + boolean.emplace(callback, 0, 0, 0, 0); // $ targets=element_pointer_bool boolean.emplace(callback); // no targets } From 0f32801b1f666eb0c824a303c70c2426a5c44322 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 22 Sep 2026 18:45:36 +0100 Subject: [PATCH 25/25] C++: Add QLDoc. --- cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index a2b6c97cef1e..bcf7c24180f9 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1164,6 +1164,10 @@ private Type stripReference(Type unspecified) { result = unspecified } +/** + * Encapsulates predicates used to compute which constructor a perfect + * forwarding function targets. + */ module ConstructorForwarding { private import codeql.util.Boolean @@ -1504,6 +1508,10 @@ module ConstructorForwarding { ) } + /** + * Gets the `Constructor` that should be invoked with arguments `0 .. n` + * when `forwarder` is called with arguments `start + 0 .. start + n`. + */ Cpp::Constructor getForwardingConstructor(Function forwarder, int start) { exists(int numberOfForwardedArguments | numberOfForwardedArguments <= result.getNumberOfParameters()