Skip to content

Commit 33ef239

Browse files
Validate that positional arguments in a format string are self-consistent
1 parent e26f5bd commit 33ef239

File tree

4 files changed

+75
-11
lines changed

4 files changed

+75
-11
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2371,7 +2371,8 @@ class Sema final : public SemaBase {
23712371

23722372
/// Verify that two format strings (as understood by attribute(format) and
23732373
/// attribute(format_matches) are compatible. If they are incompatible,
2374-
/// diagnostics will assume that \c AuthoritativeFormatString is correct and
2374+
/// diagnostics are emitted with the assumption that \c
2375+
/// AuthoritativeFormatString is correct and
23752376
/// \c TestedFormatString is wrong. If \c FunctionCallArg is provided,
23762377
/// diagnostics will point to it and a note will refer to \c
23772378
/// TestedFormatString or \c AuthoritativeFormatString as appropriate.
@@ -2381,6 +2382,12 @@ class Sema final : public SemaBase {
23812382
const StringLiteral *TestedFormatString,
23822383
const Expr *FunctionCallArg = nullptr);
23832384

2385+
/// Verify that one format string (as understood by attribute(format)) is
2386+
/// self-consistent; for instance, that it doesn't have multiple positional
2387+
/// arguments referring to the same argument in incompatible ways. Diagnose
2388+
/// if it isn't.
2389+
bool ValidateFormatString(FormatStringType FST, const StringLiteral *Str);
2390+
23842391
/// \brief Enforce the bounds of a TCB
23852392
/// CheckTCBEnforcement - Enforces that every function in a named TCB only
23862393
/// directly calls other functions in the same TCB as marked by the

clang/lib/Sema/SemaChecking.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8664,6 +8664,39 @@ bool Sema::CheckFormatStringsCompatible(
86648664
return false;
86658665
}
86668666

8667+
bool Sema::ValidateFormatString(FormatStringType Type,
8668+
const StringLiteral *Str) {
8669+
if (Type != Sema::FST_Printf && Type != Sema::FST_NSString &&
8670+
Type != Sema::FST_Kprintf && Type != Sema::FST_FreeBSDKPrintf &&
8671+
Type != Sema::FST_OSLog && Type != Sema::FST_OSTrace &&
8672+
Type != Sema::FST_Syslog)
8673+
return true;
8674+
8675+
FormatStringLiteral RefLit = Str;
8676+
llvm::SmallVector<EquatableFormatArgument, 9> Args;
8677+
bool IsObjC = Type == Sema::FST_NSString || Type == Sema::FST_OSTrace;
8678+
if (!DecomposePrintfHandler::GetSpecifiers(*this, &RefLit, Str, Type, IsObjC,
8679+
true, Args))
8680+
return false;
8681+
8682+
// If positional arguments are used multiple times in the same format string,
8683+
// ensure that they are used in compatible ways.
8684+
bool HadError = false;
8685+
auto Iter = Args.begin();
8686+
auto End = Args.end();
8687+
while (Iter != End) {
8688+
auto PosEnd = std::find_if(Iter + 1, End, [=](const auto &Arg) {
8689+
return Arg.getPosition() != Iter->getPosition();
8690+
});
8691+
for (auto PosIter = Iter + 1; PosIter != PosEnd; ++PosIter) {
8692+
HadError |= !PosIter->VerifyCompatible(*this, *Iter, Str, true);
8693+
}
8694+
Iter = PosEnd;
8695+
}
8696+
8697+
return !HadError;
8698+
}
8699+
86678700
bool Sema::FormatStringHasSArg(const StringLiteral *FExpr) {
86688701
// Str - The format string. NOTE: this is NOT null-terminated!
86698702
StringRef StrRef = FExpr->getString();

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3867,13 +3867,12 @@ static void handleFormatMatchesAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
38673867

38683868
Expr *FormatStrExpr = AL.getArgAsExpr(2)->IgnoreParenImpCasts();
38693869
if (auto *SL = dyn_cast<StringLiteral>(FormatStrExpr)) {
3870-
// Aside from whether we started with a string literal, there is generally
3871-
// no useful checking that we can do here. For instance, "invalid" format
3872-
// specifiers (like %!) are simply ignored by printf and don't have a
3873-
// diagnostic.
3874-
if (auto *NewAttr = S.mergeFormatMatchesAttr(D, AL, Info.Identifier,
3875-
Info.FormatStringIdx, SL))
3876-
D->addAttr(NewAttr);
3870+
Sema::FormatStringType FST =
3871+
S.GetFormatStringType(Info.Identifier->getName());
3872+
if (S.ValidateFormatString(FST, SL))
3873+
if (auto *NewAttr = S.mergeFormatMatchesAttr(D, AL, Info.Identifier,
3874+
Info.FormatStringIdx, SL))
3875+
D->addAttr(NewAttr);
38773876
return;
38783877
}
38793878

clang/test/Sema/format-string-matches.c

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,27 @@
33

44
#include <stdarg.h>
55

6+
__attribute__((format_matches(printf, -1, "%s"))) // expected-error{{'format_matches' attribute parameter 2 is out of bounds}}
7+
int test_out_of_bounds(void);
8+
9+
__attribute__((format_matches(printf, 0, "%s"))) // expected-error{{'format_matches' attribute parameter 2 is out of bounds}}
10+
int test_out_of_bounds(void);
11+
12+
__attribute__((format_matches(printf, 1, "%s"))) // expected-error{{'format_matches' attribute parameter 2 is out of bounds}}
13+
int test_out_of_bounds(void);
14+
15+
__attribute__((format_matches(printf, 1, "%s"))) // expected-error{{format argument not a string type}}
16+
int test_out_of_bounds_int(int x);
17+
18+
// MARK: -
19+
// Calling printf with a format from format_matches(printf) diagnoses with
20+
// that format string
621
__attribute__((format(printf, 1, 2)))
722
int printf(const char *fmt, ...);
823

924
__attribute__((format(printf, 1, 0)))
1025
int vprintf(const char *fmt, va_list);
1126

12-
// MARK: -
13-
// Calling printf with a format from format_matches(printf) diagnoses with
14-
// that format string
1527
__attribute__((format_matches(printf, 1, "%s %1.5s")))
1628
void format_str_str0(const char *fmt) {
1729
printf(fmt, "hello", "world");
@@ -206,3 +218,16 @@ void test_merge_redecl_warn(const char *f);
206218

207219
__attribute__((format_matches(printf, 1, "%s"))) // expected-note{{comparing with this specifier}}
208220
void test_merge_redecl_warn(const char *f);
221+
222+
// MARK: -
223+
// Positional madness
224+
225+
__attribute__((format_matches(printf, 1, "%1$s %1$d"))) // \
226+
expected-warning{{format specifier 's' is incompatible with 'd'}} \
227+
expected-note{{comparing with this specifier}}
228+
void test_positional_incompatible(const char *f);
229+
230+
void call_positional_incompatible(void) {
231+
// expect the attribute was dropped and that there is no diagnostic here
232+
test_positional_incompatible("%d %d %d %d %d");
233+
}

0 commit comments

Comments
 (0)