Skip to content

Commit 41d6af3

Browse files
Add test for warn_format_cmp_modifierfor_mismatch
1 parent ac5e728 commit 41d6af3

File tree

2 files changed

+57
-40
lines changed

2 files changed

+57
-40
lines changed

clang/lib/Sema/SemaChecking.cpp

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7407,25 +7407,7 @@ bool DecomposePrintfHandler::GetSpecifiers(
74077407
[](const EquatableFormatArgument &A, const EquatableFormatArgument &B) {
74087408
return A.getPosition() < B.getPosition();
74097409
});
7410-
7411-
// If there are duplicate positional arguments, ensure that they are all
7412-
// mutually compatible.
7413-
bool HadError = false;
7414-
auto ArgIter = Args.begin();
7415-
auto ArgEnd = Args.end();
7416-
while (ArgIter != ArgEnd) {
7417-
auto ArgNext = ArgIter + 1;
7418-
auto PositionalEnd = std::find_if(ArgNext, ArgEnd, [=](const auto &Arg) {
7419-
return Arg.getPosition() != ArgIter->getPosition();
7420-
});
7421-
while (ArgNext != PositionalEnd) {
7422-
HadError |=
7423-
!ArgIter->VerifyCompatible(S, *ArgNext, FmtExpr, InFunctionCall);
7424-
++ArgNext;
7425-
}
7426-
ArgIter = ArgNext;
7427-
}
7428-
return !HadError;
7410+
return true;
74297411
}
74307412

74317413
bool DecomposePrintfHandler::HandlePrintfSpecifier(
@@ -8514,36 +8496,58 @@ static bool CompareFormatSpecifiers(Sema &S, const StringLiteral *Ref,
85148496
ArrayRef<EquatableFormatArgument> FmtArgs,
85158497
const Expr *FmtExpr, bool InFunctionCall) {
85168498
bool HadError = false;
8517-
unsigned CommonArgs;
8518-
if (FmtArgs.size() > RefArgs.size()) {
8519-
CommonArgs = RefArgs.size();
8520-
// "data argument not used by format string"
8499+
auto FmtIter = FmtArgs.begin(), FmtEnd = FmtArgs.end();
8500+
auto RefIter = RefArgs.begin(), RefEnd = RefArgs.end();
8501+
while (FmtIter < FmtEnd && RefIter < RefEnd) {
8502+
// In positional-style format strings, the same specifier can appear
8503+
// multiple times. In the Ref format string, we have already verified that
8504+
// each repetition of the same positional specifier are mutually compatible,
8505+
// so we only need to test each repetition in the Fmt string with the first
8506+
// corresponding Ref specifier.
8507+
auto FmtIterEnd = std::find_if(FmtIter + 1, FmtEnd, [=](const auto &Arg) {
8508+
return Arg.getPosition() != FmtIter->getPosition();
8509+
});
8510+
auto RefIterEnd = std::find_if(RefIter + 1, RefEnd, [=](const auto &Arg) {
8511+
return Arg.getPosition() != RefIter->getPosition();
8512+
});
8513+
8514+
// Clang does not diagnose missing format specifiers in positional-style
8515+
// strings (TODO: which it probably should do, as it is UB to skip over a
8516+
// format argument). Skip specifiers if needed.
8517+
if (FmtIter->getPosition() < RefIter->getPosition()) {
8518+
FmtIter = FmtIterEnd;
8519+
continue;
8520+
}
8521+
if (RefIter->getPosition() < FmtIter->getPosition()) {
8522+
RefIter = RefIterEnd;
8523+
continue;
8524+
}
8525+
8526+
while (FmtIter != FmtIterEnd) {
8527+
HadError |=
8528+
!FmtIter->VerifyCompatible(S, *RefIter, FmtExpr, InFunctionCall);
8529+
++FmtIter;
8530+
}
8531+
RefIter = RefIterEnd;
8532+
}
8533+
8534+
if (FmtIter < FmtEnd) {
85218535
CheckFormatHandler::EmitFormatDiagnostic(
85228536
S, InFunctionCall, FmtExpr,
85238537
S.PDiag(diag::warn_format_cmp_specifier_arity) << 1,
8524-
FmtExpr->getBeginLoc(), false,
8525-
FmtArgs[RefArgs.size()].getSourceRange());
8538+
FmtExpr->getBeginLoc(), false, FmtIter->getSourceRange());
85268539
S.Diag(Ref->getBeginLoc(), diag::note_format_cmp_with) << 1;
85278540
HadError = true;
8528-
} else if (RefArgs.size() > FmtArgs.size()) {
8529-
CommonArgs = FmtArgs.size();
8530-
// "fewer specifiers in format string than expected"
8541+
} else if (RefIter < RefEnd) {
85318542
CheckFormatHandler::EmitFormatDiagnostic(
85328543
S, InFunctionCall, FmtExpr,
85338544
S.PDiag(diag::warn_format_cmp_specifier_arity) << 0,
85348545
FmtExpr->getBeginLoc(), false, Fmt->getSourceRange());
85358546
S.Diag(Ref->getBeginLoc(), diag::note_format_cmp_with)
8536-
<< 1 << RefArgs[FmtArgs.size()].getSourceRange();
8547+
<< 1 << RefIter->getSourceRange();
85378548
HadError = true;
8538-
} else {
8539-
CommonArgs = FmtArgs.size();
85408549
}
8541-
8542-
for (size_t I = 0; I < CommonArgs; ++I) {
8543-
HadError |=
8544-
!FmtArgs[I].VerifyCompatible(S, RefArgs[I], FmtExpr, InFunctionCall);
8545-
}
8546-
return HadError;
8550+
return !HadError;
85478551
}
85488552

85498553
static void CheckFormatString(

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void cvt_at(const char *c) __attribute__((format_matches(NSString, 1, "%@")));
6060
void cvt_c(const char *c) __attribute__((format_matches(printf, 1, "%c"))); // expected-note{{comparing with this specifier}}
6161
void cvt_u(const char *c) __attribute__((format_matches(printf, 1, "%u"))); // expected-note 2{{comparing with this specifier}}
6262
void cvt_hhi(const char *c) __attribute__((format_matches(printf, 1, "%hhi"))); // expected-note 3{{comparing with this specifier}}
63-
void cvt_i(const char *c) __attribute__((format_matches(printf, 1, "%i"))); // expected-note 4{{comparing with this specifier}}
63+
void cvt_i(const char *c) __attribute__((format_matches(printf, 1, "%i"))); // expected-note 5{{comparing with this specifier}}
6464
void cvt_p(const char *c) __attribute__((format_matches(printf, 1, "%p")));
6565
void cvt_s(const char *c) __attribute__((format_matches(printf, 1, "%s"))); // expected-note{{comparing with this specifier}}
6666
void cvt_n(const char *c) __attribute__((format_matches(printf, 1, "%n"))); // expected-note{{comparing with this specifier}}
@@ -223,11 +223,24 @@ void test_merge_redecl_warn(const char *f);
223223
// Positional madness
224224

225225
__attribute__((format_matches(printf, 1, "%1$s %1$d"))) // \
226-
expected-warning{{format specifier 's' is incompatible with 'd'}} \
226+
expected-warning{{format specifier 'd' is incompatible with 's'}} \
227227
expected-note{{comparing with this specifier}}
228228
void test_positional_incompatible(const char *f);
229229

230230
void call_positional_incompatible(void) {
231231
// expect the attribute was dropped and that there is no diagnostic here
232232
test_positional_incompatible("%d %d %d %d %d");
233-
}
233+
}
234+
235+
void test_many_i(void) {
236+
cvt_i("%1$d %1$i");
237+
cvt_i("%1$d %1$s"); // expected-warning{{format specifier 's' is incompatible with 'i'}}
238+
}
239+
240+
__attribute__((format_matches(printf, 1, "%*d %*d"))) // expected-note{{comparing with this specifier}}
241+
void accept_modifiers(const char *f);
242+
243+
void test_modifiers(void) {
244+
accept_modifiers("%2$*1$d %4$*3$d");
245+
accept_modifiers("%2$*3$d %4$*3$d"); // expected-warning{{format argument modifies specifier at position 2, but it should modify specifier at position 4}}
246+
}

0 commit comments

Comments
 (0)