Skip to content

Commit 31f68d2

Browse files
authored
Merge pull request #17095 from MathiasVP/mad-free-functions
C++: Proper MaD support for free functions
2 parents 0d46953 + 6d8a83f commit 31f68d2

File tree

7 files changed

+192
-53
lines changed

7 files changed

+192
-53
lines changed

cpp/ql/lib/ext/std.format.model.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/cpp-all
4+
extensible: summaryModel
5+
data: # namespace, type, subtypes, name, signature, ext, input, output, kind, provenance
6+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
7+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[*1]", "ReturnValue", "taint", "manual"]
8+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[*2]", "ReturnValue", "taint", "manual"]
9+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[*3]", "ReturnValue", "taint", "manual"]
10+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[*4]", "ReturnValue", "taint", "manual"]
11+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[*5]", "ReturnValue", "taint", "manual"]
12+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[*6]", "ReturnValue", "taint", "manual"]
13+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[*7]", "ReturnValue", "taint", "manual"]
14+
- ["std", "", False, "format<Args>", "(format_string,Args &&)", "", "Argument[*8]", "ReturnValue", "taint", "manual"]

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

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -435,12 +435,17 @@ private predicate elementSpec(
435435
}
436436

437437
/** Gets the fully templated version of `f`. */
438-
private Function getFullyTemplatedMemberFunction(Function f) {
438+
private Function getFullyTemplatedFunction(Function f) {
439439
not f.isFromUninstantiatedTemplate(_) and
440-
exists(Class c, Class templateClass, int i |
441-
c.isConstructedFrom(templateClass) and
442-
f = c.getAMember(i) and
443-
result = templateClass.getCanonicalMember(i)
440+
(
441+
exists(Class c, Class templateClass, int i |
442+
c.isConstructedFrom(templateClass) and
443+
f = c.getAMember(i) and
444+
result = templateClass.getCanonicalMember(i)
445+
)
446+
or
447+
not exists(f.getDeclaringType()) and
448+
f.isConstructedFrom(result)
444449
)
445450
}
446451

@@ -464,14 +469,14 @@ string getParameterTypeWithoutTemplateArguments(Function f, int n) {
464469
*/
465470
private string getTypeNameWithoutFunctionTemplates(Function f, int n, int remaining) {
466471
exists(Function templateFunction |
467-
templateFunction = getFullyTemplatedMemberFunction(f) and
472+
templateFunction = getFullyTemplatedFunction(f) and
468473
remaining = templateFunction.getNumberOfTemplateArguments() and
469474
result = getParameterTypeWithoutTemplateArguments(templateFunction, n)
470475
)
471476
or
472477
exists(string mid, TemplateParameter tp, Function templateFunction |
473478
mid = getTypeNameWithoutFunctionTemplates(f, n, remaining + 1) and
474-
templateFunction = getFullyTemplatedMemberFunction(f) and
479+
templateFunction = getFullyTemplatedFunction(f) and
475480
tp = templateFunction.getTemplateArgument(remaining) and
476481
result = mid.replaceAll(tp.getName(), "func:" + remaining.toString())
477482
)
@@ -482,12 +487,18 @@ private string getTypeNameWithoutFunctionTemplates(Function f, int n, int remain
482487
* with `class:N` (where `N` is the index of the template).
483488
*/
484489
private string getTypeNameWithoutClassTemplates(Function f, int n, int remaining) {
490+
// If there is a declaring type then we start by expanding the function templates
485491
exists(Class template |
486492
f.getDeclaringType().isConstructedFrom(template) and
487493
remaining = template.getNumberOfTemplateArguments() and
488494
result = getTypeNameWithoutFunctionTemplates(f, n, 0)
489495
)
490496
or
497+
// If there is no declaring type we're done after expanding the function templates
498+
not exists(f.getDeclaringType()) and
499+
remaining = 0 and
500+
result = getTypeNameWithoutFunctionTemplates(f, n, 0)
501+
or
491502
exists(string mid, TemplateParameter tp, Class template |
492503
mid = getTypeNameWithoutClassTemplates(f, n, remaining + 1) and
493504
f.getDeclaringType().isConstructedFrom(template) and
@@ -570,38 +581,6 @@ private string getSignatureWithoutFunctionTemplateNames(
570581
)
571582
}
572583

573-
private string paramsStringPart(Function c, int i) {
574-
not c.isFromUninstantiatedTemplate(_) and
575-
(
576-
i = -1 and result = "(" and exists(c)
577-
or
578-
exists(int n, string p | getParameterTypeName(c, n) = p |
579-
i = 2 * n and result = p
580-
or
581-
i = 2 * n - 1 and result = "," and n != 0
582-
)
583-
or
584-
i = 2 * c.getNumberOfParameters() and result = ")"
585-
)
586-
}
587-
588-
/**
589-
* Gets a parenthesized string containing all parameter types of this callable, separated by a comma.
590-
*
591-
* Returns the empty string if the callable has no parameters.
592-
* Parameter types are represented by their type erasure.
593-
*/
594-
cached
595-
private string paramsString(Function c) {
596-
result = concat(int i | | paramsStringPart(c, i) order by i)
597-
}
598-
599-
bindingset[func]
600-
private predicate matchesSignature(Function func, string signature) {
601-
signature = "" or
602-
paramsString(func) = signature
603-
}
604-
605584
/**
606585
* Holds if `elementSpec(_, type, _, name, signature, _)` holds and
607586
* - `typeArgs` represents the named template parameters supplied to `type`, and
@@ -750,17 +729,17 @@ private predicate elementSpecWithArguments0(
750729

751730
/**
752731
* Holds if `elementSpec(namespace, type, subtypes, name, signature, _)` and
753-
* `method`'s signature matches `signature`.
732+
* `func`'s signature matches `signature`.
754733
*
755734
* `signature` may contain template parameter names that are bound by `type` and `name`.
756735
*/
757736
pragma[nomagic]
758737
private predicate elementSpecMatchesSignature(
759-
Function method, string namespace, string type, boolean subtypes, string name, string signature
738+
Function func, string namespace, string type, boolean subtypes, string name, string signature
760739
) {
761740
elementSpec(namespace, pragma[only_bind_into](type), subtypes, pragma[only_bind_into](name),
762741
pragma[only_bind_into](signature), _) and
763-
signatureMatches(method, signature, type, name, 0)
742+
signatureMatches(func, signature, type, name, 0)
764743
}
765744

766745
/**
@@ -776,13 +755,22 @@ private predicate hasClassAndName(Class classWithMethod, Function method, string
776755
)
777756
}
778757

758+
bindingset[name]
759+
pragma[inline_late]
760+
private predicate funcHasQualifiedName(Function func, string namespace, string name) {
761+
exists(string nameWithoutArgs |
762+
parseAngles(name, nameWithoutArgs, _, "") and
763+
func.hasQualifiedName(namespace, nameWithoutArgs)
764+
)
765+
}
766+
779767
/**
780768
* Holds if `namedClass` is in namespace `namespace` and has
781769
* name `type` (excluding any template parameters).
782770
*/
783771
bindingset[type, namespace]
784772
pragma[inline_late]
785-
private predicate hasQualifiedName(Class namedClass, string namespace, string type) {
773+
private predicate classHasQualifiedName(Class namedClass, string namespace, string type) {
786774
exists(string typeWithoutArgs |
787775
parseAngles(type, typeWithoutArgs, _, "") and
788776
namedClass.hasQualifiedName(namespace, typeWithoutArgs)
@@ -804,15 +792,16 @@ private Element interpretElement0(
804792
string namespace, string type, boolean subtypes, string name, string signature
805793
) {
806794
(
807-
elementSpec(namespace, type, subtypes, name, signature, _) and
808795
// Non-member functions
809-
exists(Function func |
810-
func.hasQualifiedName(namespace, name) and
811-
type = "" and
812-
matchesSignature(func, signature) and
813-
subtypes = false and
814-
not exists(func.getDeclaringType()) and
815-
result = func
796+
elementSpec(namespace, type, subtypes, name, signature, _) and
797+
subtypes = false and
798+
type = "" and
799+
(
800+
elementSpecMatchesSignature(result, namespace, type, subtypes, name, signature)
801+
or
802+
signature = "" and
803+
elementSpec(namespace, type, subtypes, name, "", _) and
804+
funcHasQualifiedName(result, namespace, name)
816805
)
817806
or
818807
// Member functions
@@ -825,7 +814,7 @@ private Element interpretElement0(
825814
elementSpec(namespace, type, subtypes, name, "", _) and
826815
hasClassAndName(classWithMethod, result, name)
827816
) and
828-
hasQualifiedName(namedClass, namespace, type) and
817+
classHasQualifiedName(namedClass, namespace, type) and
829818
(
830819
// member declared in the named type or a subtype of it
831820
subtypes = true and

cpp/ql/test/library-tests/dataflow/external-models/validatemodels.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
| Dubious signature "(const_iterator,size_type,const T &)" in summary model. |
1717
| Dubious signature "(deque &&)" in summary model. |
1818
| Dubious signature "(deque &&,const Allocator &)" in summary model. |
19+
| Dubious signature "(format_string,Args &&)" in summary model. |
1920
| Dubious signature "(forward_list &&)" in summary model. |
2021
| Dubious signature "(forward_list &&,const Allocator &)" in summary model. |
2122
| Dubious signature "(list &&)" in summary model. |

cpp/ql/test/library-tests/dataflow/taint-tests/format.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
#include "stl.h"
22
typedef unsigned long size_t;
33
typedef struct {} FILE;
44

@@ -157,3 +157,11 @@ void test2()
157157
sink(s[strlen(s) - 1]); // $ ast,ir
158158
sink(ws + (wcslen(ws) / 2)); // $ ast,ir
159159
}
160+
161+
void test_format() {
162+
auto s = std::format("{}", string::source());
163+
sink(s); // $ ir MISSING: ast
164+
165+
auto s2 = std::format(string::source());
166+
sink(s2); // $ ir MISSING: ast
167+
}

cpp/ql/test/library-tests/dataflow/taint-tests/localTaint.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,10 @@ WARNING: module 'TaintTracking' has been deprecated and may be removed in future
447447
| format.cpp:158:13:158:18 | call to wcslen | format.cpp:158:13:158:26 | ... / ... | TAINT |
448448
| format.cpp:158:13:158:26 | ... / ... | format.cpp:158:7:158:27 | ... + ... | TAINT |
449449
| format.cpp:158:26:158:26 | 2 | format.cpp:158:13:158:26 | ... / ... | TAINT |
450+
| format.cpp:162:12:162:22 | call to format | format.cpp:163:8:163:8 | s | |
451+
| format.cpp:162:24:162:27 | {} | format.cpp:162:24:162:27 | call to basic_format_string | TAINT |
452+
| format.cpp:165:13:165:23 | call to format | format.cpp:166:8:166:9 | s2 | |
453+
| format.cpp:165:25:165:38 | call to source | format.cpp:165:25:165:40 | call to basic_format_string | TAINT |
450454
| map.cpp:21:28:21:28 | call to pair | map.cpp:23:2:23:2 | a | |
451455
| map.cpp:21:28:21:28 | call to pair | map.cpp:24:7:24:7 | a | |
452456
| map.cpp:21:28:21:28 | call to pair | map.cpp:25:7:25:7 | a | |

cpp/ql/test/library-tests/dataflow/taint-tests/stl.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,3 +642,38 @@ namespace std {
642642
pair<const_iterator, const_iterator> equal_range(const key_type& k) const;
643643
};
644644
}
645+
646+
// --- string view ---
647+
648+
namespace std {
649+
template<class CharT, class Traits = char_traits<CharT>>
650+
class basic_string_view {
651+
public:
652+
using size_type = size_t;
653+
654+
basic_string_view() noexcept;
655+
basic_string_view(const basic_string_view&) noexcept;
656+
basic_string_view(const CharT*, size_type);
657+
basic_string_view(const CharT*);
658+
template<class It, class End> basic_string_view(It, End);
659+
template<class R> explicit basic_string_view(R&&);
660+
basic_string_view& operator=(const basic_string_view&) noexcept;
661+
};
662+
663+
using string_view = basic_string_view<char>;
664+
}
665+
666+
// --- format ---
667+
namespace std {
668+
template<class CharT /* class... Args */>
669+
struct basic_format_string {
670+
public:
671+
template<class T> basic_format_string(const T&);
672+
673+
basic_string_view<CharT> get() const noexcept;
674+
};
675+
676+
using format_string = basic_format_string<char>; // simplified from `char, std::type_identity_t<Args>...`
677+
678+
template<class... Args> string format( format_string fmt, Args&&... args );
679+
}

0 commit comments

Comments
 (0)