[clang] c710118 - [clang] Implement __attribute__((format_matches)) (#116708)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 24 18:59:02 PST 2025
Author: apple-fcloutier
Date: 2025-02-24T18:58:59-08:00
New Revision: c7101188fb3f17176e9152b1d733da6d7199d317
URL: https://github.com/llvm/llvm-project/commit/c7101188fb3f17176e9152b1d733da6d7199d317
DIFF: https://github.com/llvm/llvm-project/commit/c7101188fb3f17176e9152b1d733da6d7199d317.diff
LOG: [clang] Implement __attribute__((format_matches)) (#116708)
This implements ``__attribute__((format_matches))``, as described in the
RFC:
https://discourse.llvm.org/t/rfc-format-attribute-attribute-format-like/83076
The ``format`` attribute only allows the compiler to check that a format
string matches its arguments. If the format string is passed
independently of its arguments, there is no way to have the compiler
check it. ``format_matches(flavor, fmtidx, example)`` allows the
compiler to check format strings against the ``example`` format string
instead of against format arguments. See the changes to AttrDocs.td in
this diff for more information.
Implementation-wise, this change subclasses CheckPrintfHandler and
CheckScanfHandler to allow them to collect specifiers into arrays, and
implements comparing that two specifiers are equivalent.
`checkFormatStringExpr` gets a new `ReferenceFormatString` argument that
is piped down when calling a function with the `format_matches`
attribute (and is `nullptr` otherwise); this is the string that the
actual format string is compared against.
Although this change does not enable -Wformat-nonliteral by default,
IMO, all the pieces are now in place such that it could be.
Added:
clang/test/Sema/format-string-matches.c
Modified:
clang/docs/ReleaseNotes.rst
clang/include/clang/AST/FormatString.h
clang/include/clang/Basic/Attr.td
clang/include/clang/Basic/AttrDocs.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Sema.h
clang/lib/AST/AttrImpl.cpp
clang/lib/AST/FormatString.cpp
clang/lib/Sema/SemaChecking.cpp
clang/lib/Sema/SemaDecl.cpp
clang/lib/Sema/SemaDeclAttr.cpp
clang/lib/Sema/SemaObjC.cpp
clang/test/Sema/format-strings.c
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 699cbb17edca7..657340c170503 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -132,6 +132,50 @@ Attribute Changes in Clang
This forces the global to be considered small or large in regards to the
x86-64 code model, regardless of the code model specified for the compilation.
+- There is a new ``format_matches`` attribute to complement the existing
+ ``format`` attribute. ``format_matches`` allows the compiler to verify that
+ a format string argument is equivalent to a reference format string: it is
+ useful when a function accepts a format string without its accompanying
+ arguments to format. For instance:
+
+ .. code-block:: c
+
+ static int status_code;
+ static const char *status_string;
+
+ void print_status(const char *fmt) {
+ fprintf(stderr, fmt, status_code, status_string);
+ // ^ warning: format string is not a string literal [-Wformat-nonliteral]
+ }
+
+ void stuff(void) {
+ print_status("%s (%#08x)\n");
+ // order of %s and %x is swapped but there is no diagnostic
+ }
+
+ Before the introducion of ``format_matches``, this code cannot be verified
+ at compile-time. ``format_matches`` plugs that hole:
+
+ .. code-block:: c
+
+ __attribute__((format_matches(printf, 1, "%x %s")))
+ void print_status(const char *fmt) {
+ fprintf(stderr, fmt, status_code, status_string);
+ // ^ `fmt` verified as if it was "%x %s" here; no longer triggers
+ // -Wformat-nonliteral, would warn if arguments did not match "%x %s"
+ }
+
+ void stuff(void) {
+ print_status("%s (%#08x)\n");
+ // warning: format specifier 's' is incompatible with 'x'
+ // warning: format specifier 'x' is incompatible with 's'
+ }
+
+ Like with ``format``, the first argument is the format string flavor and the
+ second argument is the index of the format string parameter.
+ ``format_matches`` accepts an example valid format string as its third
+ argument. For more information, see the Clang attributes documentation.
+
Improvements to Clang's diagnostics
-----------------------------------
diff --git a/clang/include/clang/AST/FormatString.h b/clang/include/clang/AST/FormatString.h
index a074dd23e2ad4..3560766433fe2 100644
--- a/clang/include/clang/AST/FormatString.h
+++ b/clang/include/clang/AST/FormatString.h
@@ -292,7 +292,7 @@ class ArgType {
};
private:
- const Kind K;
+ Kind K;
QualType T;
const char *Name = nullptr;
bool Ptr = false;
@@ -338,6 +338,7 @@ class ArgType {
}
MatchKind matchesType(ASTContext &C, QualType argTy) const;
+ MatchKind matchesArgType(ASTContext &C, const ArgType &other) const;
QualType getRepresentativeType(ASTContext &C) const;
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 65c91ccd75ecc..8bbd096bb1f72 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1834,6 +1834,16 @@ def Format : InheritableAttr {
let Documentation = [FormatDocs];
}
+def FormatMatches : InheritableAttr {
+ let Spellings = [GCC<"format_matches">];
+ let Args = [IdentifierArgument<"Type">, IntArgument<"FormatIdx">, ExprArgument<"ExpectedFormat">];
+ let AdditionalMembers = [{
+ StringLiteral *getFormatString() const;
+ }];
+ let Subjects = SubjectList<[ObjCMethod, Block, HasFunctionProto]>;
+ let Documentation = [FormatMatchesDocs];
+}
+
def FormatArg : InheritableAttr {
let Spellings = [GCC<"format_arg">];
let Args = [ParamIdxArgument<"FormatIdx">];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index f5362b4d59142..79c615be754c4 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3886,6 +3886,132 @@ behavior of the program is undefined.
}];
}
+def FormatMatchesDocs : Documentation {
+ let Category = DocCatFunction;
+ let Content = [{
+
+The ``format`` attribute is the basis for the enforcement of diagnostics in the
+``-Wformat`` family, but it only handles the case where the format string is
+passed along with the arguments it is going to format. It cannot handle the case
+where the format string and the format arguments are passed separately from each
+other. For instance:
+
+.. code-block:: c
+
+ static const char *first_name;
+ static double todays_temperature;
+ static int wind_speed;
+
+ void say_hi(const char *fmt) {
+ printf(fmt, first_name, todays_temperature);
+ // ^ warning: format string is not a string literal
+ printf(fmt, first_name, wind_speed);
+ // ^ warning: format string is not a string literal
+ }
+
+ int main() {
+ say_hi("hello %s, it is %g degrees outside");
+ say_hi("hello %s, it is %d degrees outside!");
+ // ^ no diagnostic, but %d cannot format doubles
+ }
+
+In this example, ``fmt`` is expected to format a ``const char *`` and a
+``double``, but these values are not passed to ``say_hi``. Without the
+``format`` attribute (which cannot apply in this case), the -Wformat-nonliteral
+diagnostic unnecessarily triggers in the body of ``say_hi``, and incorrect
+``say_hi`` call sites do not trigger a diagnostic.
+
+To complement the ``format`` attribute, Clang also defines the
+``format_matches`` attribute. Its syntax is similar to the ``format``
+attribute's, but instead of taking the index of the first formatted value
+argument, it takes a C string literal with the expected specifiers:
+
+.. code-block:: c
+
+ static const char *first_name;
+ static double todays_temperature;
+ static int wind_speed;
+
+ __attribute__((__format_matches__(printf, 1, "%s %g")))
+ void say_hi(const char *fmt) {
+ printf(fmt, first_name, todays_temperature); // no dignostic
+ printf(fmt, first_name, wind_speed); // warning: format specifies type 'int' but the argument has type 'double'
+ }
+
+ int main() {
+ say_hi("hello %s, it is %g degrees outside");
+ say_hi("it is %g degrees outside, have a good day %s!");
+ // warning: format specifies 'double' where 'const char *' is required
+ // warning: format specifies 'const char *' where 'double' is required
+ }
+
+The third argument to ``format_matches`` is expected to evaluate to a **C string
+literal** even when the format string would normally be a
diff erent type for the
+given flavor, like a ``CFStringRef`` or a ``NSString *``.
+
+The only requirement on the format string literal is that it has specifiers
+that are compatible with the arguments that will be used. It can contain
+arbitrary non-format characters. For instance, for the purposes of compile-time
+validation, ``"%s scored %g%% on her test"`` and ``"%s%g"`` are interchangeable
+as the format string argument. As a means of self-documentation, users may
+prefer the former when it provides a useful example of an expected format
+string.
+
+In the implementation of a function with the ``format_matches`` attribute,
+format verification works as if the format string was identical to the one
+specified in the attribute.
+
+.. code-block:: c
+
+ __attribute__((__format_matches__(printf, 1, "%s %g")))
+ void say_hi(const char *fmt) {
+ printf(fmt, "person", 546);
+ // ^ warning: format specifies type 'double' but the
+ // argument has type 'int'
+ // note: format string is defined here:
+ // __attribute__((__format_matches__(printf, 1, "%s %g")))
+ // ^~
+ }
+
+
+At the call sites of functions with the ``format_matches`` attribute, format
+verification instead compares the two format strings to evaluate their
+equivalence. Each format flavor defines equivalence between format specifiers.
+Generally speaking, two specifiers are equivalent if they format the same type.
+For instance, in the ``printf`` flavor, ``%2i`` and ``%-0.5d`` are compatible.
+When ``-Wformat-signedness`` is disabled, ``%d`` and ``%u`` are compatible. For
+a negative example, ``%ld`` is incompatible with ``%d``.
+
+Do note the following un-obvious cases:
+
+* Passing ``NULL`` as the format string does not trigger format diagnostics.
+* When the format string is not NULL, it cannot _miss_ specifiers, even in
+ trailing positions. For instance, ``%d`` is not accepted when the required
+ format is ``%d %d %d``.
+* While checks for the ``format`` attribute tolerate sone size mismatches
+ that standard argument promotion renders immaterial (such as formatting an
+ ``int`` with ``%hhd``, which specifies a ``char``-sized integer), checks for
+ ``format_matches`` require specified argument sizes to match exactly.
+* Format strings expecting a variable modifier (such as ``%*s``) are
+ incompatible with format strings that would itemize the variable modifiers
+ (such as ``%i %s``), even if the two specify ABI-compatible argument lists.
+* All pointer specifiers, modifiers aside, are mutually incompatible. For
+ instance, ``%s`` is not compatible with ``%p``, and ``%p`` is not compatible
+ with ``%n``, and ``%hhn`` is incompatible with ``%s``, even if the pointers
+ are ABI-compatible or identical on the selected platform. However, ``%0.5s``
+ is compatible with ``%s``, since the
diff erence only exists in modifier flags.
+ This is not overridable with ``-Wformat-pedantic`` or its inverse, which
+ control similar behavior in ``-Wformat``.
+
+At this time, clang implements ``format_matches`` only for format types in the
+``printf`` family. This includes variants such as Apple's NSString format and
+the FreeBSD ``kprintf``, but excludes ``scanf``. Using a known but unsupported
+format silently fails in order to be compatible with other implementations that
+would support these formats.
+
+ }];
+}
+
def FlagEnumDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f4f1bc67724a1..51301d95e55b9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7764,6 +7764,7 @@ def warn_format_nonliteral_noargs : Warning<
def warn_format_nonliteral : Warning<
"format string is not a string literal">,
InGroup<FormatNonLiteral>, DefaultIgnore;
+def err_format_nonliteral : Error<"format string is not a string literal">;
def err_unexpected_interface : Error<
"unexpected interface name %0: expected expression">;
@@ -10066,6 +10067,8 @@ def note_previous_declaration_as : Note<
def warn_printf_insufficient_data_args : Warning<
"more '%%' conversions than data arguments">, InGroup<FormatInsufficientArgs>;
+def warn_format_cmp_specifier_arity : Warning<
+ "%select{fewer|more}0 specifiers in format string than expected">, InGroup<FormatInsufficientArgs>;
def warn_printf_data_arg_not_used : Warning<
"data argument not used by format string">, InGroup<FormatExtraArgs>;
def warn_format_invalid_conversion : Warning<
@@ -10183,6 +10186,27 @@ def note_format_fix_specifier : Note<"did you mean to use '%0'?">;
def note_printf_c_str: Note<"did you mean to call the %0 method?">;
def note_format_security_fixit: Note<
"treat the string as an argument to avoid this">;
+def warn_format_string_type_incompatible : Warning<
+ "passing '%0' format string where '%1' format string is expected">,
+ InGroup<Format>;
+def warn_format_cmp_role_mismatch : Warning<
+ "format argument is %select{a value|an indirect field width|an indirect "
+ "precision|an auxiliary value}0, but it should be %select{a value|an indirect "
+ "field width|an indirect precision|an auxiliary value}1">, InGroup<Format>;
+def warn_format_cmp_modifierfor_mismatch : Warning<
+ "format argument modifies specifier at position %0, but it should modify "
+ "specifier at position %1">, InGroup<Format>;
+def warn_format_cmp_sensitivity_mismatch : Warning<
+ "argument sensitivity is %select{unspecified|private|public|sensitive}0, but "
+ "it should be %select{unspecified|private|public|sensitive}1">, InGroup<Format>;
+def warn_format_cmp_specifier_mismatch : Warning<
+ "format specifier '%0' is incompatible with '%1'">, InGroup<Format>;
+def warn_format_cmp_specifier_sign_mismatch : Warning<
+ "signedness of format specifier '%0' is incompatible with '%1'">, InGroup<Format>;
+def warn_format_cmp_specifier_mismatch_pedantic : Extension<
+ warn_format_cmp_specifier_sign_mismatch.Summary>, InGroup<FormatPedantic>;
+def note_format_cmp_with : Note<
+ "comparing with this %select{specifier|format string}0">;
def warn_null_arg : Warning<
"null passed to a callee that requires a non-null argument">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 093e9a06b00ce..476abe86cb2d2 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2177,6 +2177,7 @@ class Sema final : public SemaBase {
FAPK_Fixed, // values to format are fixed (no C-style variadic arguments)
FAPK_Variadic, // values to format are passed as variadic arguments
FAPK_VAList, // values to format are passed in a va_list
+ FAPK_Elsewhere, // values to format are not passed to this function
};
// Used to grab the relevant information from a FormatAttr and a
@@ -2187,12 +2188,15 @@ class Sema final : public SemaBase {
FormatArgumentPassingKind ArgPassingKind;
};
- /// Given a FunctionDecl's FormatAttr, attempts to populate the
- /// FomatStringInfo parameter with the FormatAttr's correct format_idx and
- /// firstDataArg. Returns true when the format fits the function and the
- /// FormatStringInfo has been populated.
- static bool getFormatStringInfo(const FormatAttr *Format, bool IsCXXMember,
- bool IsVariadic, FormatStringInfo *FSI);
+ /// Given a function and its FormatAttr or FormatMatchesAttr info, attempts to
+ /// populate the FomatStringInfo parameter with the attribute's correct
+ /// format_idx and firstDataArg. Returns true when the format fits the
+ /// function and the FormatStringInfo has been populated.
+ static bool getFormatStringInfo(const Decl *Function, unsigned FormatIdx,
+ unsigned FirstArg, FormatStringInfo *FSI);
+ static bool getFormatStringInfo(unsigned FormatIdx, unsigned FirstArg,
+ bool IsCXXMember, bool IsVariadic,
+ FormatStringInfo *FSI);
// Used by C++ template instantiation.
ExprResult BuiltinShuffleVector(CallExpr *TheCall);
@@ -2215,7 +2219,10 @@ class Sema final : public SemaBase {
FST_Syslog,
FST_Unknown
};
+ static StringRef GetFormatStringTypeName(FormatStringType FST);
+ static FormatStringType GetFormatStringType(StringRef FormatFlavor);
static FormatStringType GetFormatStringType(const FormatAttr *Format);
+ static FormatStringType GetFormatStringType(const FormatMatchesAttr *Format);
bool FormatStringHasSArg(const StringLiteral *FExpr);
@@ -2362,6 +2369,25 @@ class Sema final : public SemaBase {
bool IsMemberFunction, SourceLocation Loc, SourceRange Range,
VariadicCallType CallType);
+ /// Verify that two format strings (as understood by attribute(format) and
+ /// attribute(format_matches) are compatible. If they are incompatible,
+ /// diagnostics are emitted with the assumption that \c
+ /// AuthoritativeFormatString is correct and
+ /// \c TestedFormatString is wrong. If \c FunctionCallArg is provided,
+ /// diagnostics will point to it and a note will refer to \c
+ /// TestedFormatString or \c AuthoritativeFormatString as appropriate.
+ bool
+ CheckFormatStringsCompatible(FormatStringType FST,
+ const StringLiteral *AuthoritativeFormatString,
+ const StringLiteral *TestedFormatString,
+ const Expr *FunctionCallArg = nullptr);
+
+ /// Verify that one format string (as understood by attribute(format)) is
+ /// self-consistent; for instance, that it doesn't have multiple positional
+ /// arguments referring to the same argument in incompatible ways. Diagnose
+ /// if it isn't.
+ bool ValidateFormatString(FormatStringType FST, const StringLiteral *Str);
+
/// \brief Enforce the bounds of a TCB
/// CheckTCBEnforcement - Enforces that every function in a named TCB only
/// directly calls other functions in the same TCB as marked by the
@@ -2577,11 +2603,17 @@ class Sema final : public SemaBase {
VariadicCallType CallType, SourceLocation Loc,
SourceRange Range,
llvm::SmallBitVector &CheckedVarArgs);
+ bool CheckFormatString(const FormatMatchesAttr *Format,
+ ArrayRef<const Expr *> Args, bool IsCXXMember,
+ VariadicCallType CallType, SourceLocation Loc,
+ SourceRange Range,
+ llvm::SmallBitVector &CheckedVarArgs);
bool CheckFormatArguments(ArrayRef<const Expr *> Args,
- FormatArgumentPassingKind FAPK, unsigned format_idx,
- unsigned firstDataArg, FormatStringType Type,
- VariadicCallType CallType, SourceLocation Loc,
- SourceRange range,
+ FormatArgumentPassingKind FAPK,
+ const StringLiteral *ReferenceFormatString,
+ unsigned format_idx, unsigned firstDataArg,
+ FormatStringType Type, VariadicCallType CallType,
+ SourceLocation Loc, SourceRange range,
llvm::SmallBitVector &CheckedVarArgs);
void CheckInfNaNFunction(const CallExpr *Call, const FunctionDecl *FDecl);
@@ -4576,6 +4608,11 @@ class Sema final : public SemaBase {
FormatAttr *mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI,
IdentifierInfo *Format, int FormatIdx,
int FirstArg);
+ FormatMatchesAttr *mergeFormatMatchesAttr(Decl *D,
+ const AttributeCommonInfo &CI,
+ IdentifierInfo *Format,
+ int FormatIdx,
+ StringLiteral *FormatStr);
/// AddAlignedAttr - Adds an aligned attribute to a particular declaration.
void AddAlignedAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E,
diff --git a/clang/lib/AST/AttrImpl.cpp b/clang/lib/AST/AttrImpl.cpp
index f198a9acf8481..fefb8f55a9ee2 100644
--- a/clang/lib/AST/AttrImpl.cpp
+++ b/clang/lib/AST/AttrImpl.cpp
@@ -270,4 +270,8 @@ unsigned AlignedAttr::getAlignment(ASTContext &Ctx) const {
return Ctx.getTargetDefaultAlignForAttributeAligned();
}
+StringLiteral *FormatMatchesAttr::getFormatString() const {
+ return cast<StringLiteral>(getExpectedFormat());
+}
+
#include "clang/AST/AttrImpl.inc"
diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp
index e892c1592df98..5d3b56fc4e713 100644
--- a/clang/lib/AST/FormatString.cpp
+++ b/clang/lib/AST/FormatString.cpp
@@ -595,6 +595,97 @@ ArgType::matchesType(ASTContext &C, QualType argTy) const {
llvm_unreachable("Invalid ArgType Kind!");
}
+static analyze_format_string::ArgType::MatchKind
+integerTypeMatch(ASTContext &C, QualType A, QualType B, bool CheckSign) {
+ using MK = analyze_format_string::ArgType::MatchKind;
+
+ uint64_t IntSize = C.getTypeSize(C.IntTy);
+ uint64_t ASize = C.getTypeSize(A);
+ uint64_t BSize = C.getTypeSize(B);
+ if (std::max(ASize, IntSize) != std::max(BSize, IntSize))
+ return MK::NoMatch;
+ if (CheckSign && A->isSignedIntegerType() != B->isSignedIntegerType())
+ return MK::NoMatchSignedness;
+ if (ASize != BSize)
+ return MK::MatchPromotion;
+ return MK::Match;
+}
+
+analyze_format_string::ArgType::MatchKind
+ArgType::matchesArgType(ASTContext &C, const ArgType &Other) const {
+ using AK = analyze_format_string::ArgType::Kind;
+
+ // Per matchesType.
+ if (K == AK::InvalidTy || Other.K == AK::InvalidTy)
+ return NoMatch;
+ if (K == AK::UnknownTy || Other.K == AK::UnknownTy)
+ return Match;
+
+ // Handle whether either (or both, or neither) sides has Ptr set,
+ // in addition to whether either (or both, or neither) sides is a SpecificTy
+ // that is a pointer.
+ ArgType Left = *this;
+ bool LeftWasPointer = false;
+ ArgType Right = Other;
+ bool RightWasPointer = false;
+ if (Left.Ptr) {
+ Left.Ptr = false;
+ LeftWasPointer = true;
+ } else if (Left.K == AK::SpecificTy && Left.T->isPointerType()) {
+ Left.T = Left.T->getPointeeType();
+ LeftWasPointer = true;
+ }
+ if (Right.Ptr) {
+ Right.Ptr = false;
+ RightWasPointer = true;
+ } else if (Right.K == AK::SpecificTy && Right.T->isPointerType()) {
+ Right.T = Right.T->getPointeeType();
+ RightWasPointer = true;
+ }
+
+ if (LeftWasPointer != RightWasPointer)
+ return NoMatch;
+
+ // Ensure that if at least one side is a SpecificTy, then Left is a
+ // SpecificTy.
+ if (Right.K == AK::SpecificTy)
+ std::swap(Left, Right);
+
+ if (Left.K == AK::SpecificTy) {
+ if (Right.K == AK::SpecificTy) {
+ auto Canon1 = C.getCanonicalType(Left.T);
+ auto Canon2 = C.getCanonicalType(Right.T);
+ if (Canon1 == Canon2)
+ return Match;
+
+ auto *BT1 = QualType(Canon1)->getAs<BuiltinType>();
+ auto *BT2 = QualType(Canon2)->getAs<BuiltinType>();
+ if (BT1 == nullptr || BT2 == nullptr)
+ return NoMatch;
+ if (BT1 == BT2)
+ return Match;
+
+ if (!LeftWasPointer && BT1->isInteger() && BT2->isInteger())
+ return integerTypeMatch(C, Canon1, Canon2, true);
+ return NoMatch;
+ } else if (Right.K == AK::AnyCharTy) {
+ if (!LeftWasPointer && Left.T->isIntegerType())
+ return integerTypeMatch(C, Left.T, C.CharTy, false);
+ return NoMatch;
+ } else if (Right.K == AK::WIntTy) {
+ if (!LeftWasPointer && Left.T->isIntegerType())
+ return integerTypeMatch(C, Left.T, C.WIntTy, true);
+ return NoMatch;
+ }
+ // It's hypothetically possible to create an AK::SpecificTy ArgType
+ // that matches another kind of ArgType, but in practice Clang doesn't
+ // do that, so ignore that case.
+ return NoMatch;
+ }
+
+ return Left.K == Right.K ? Match : NoMatch;
+}
+
ArgType ArgType::makeVectorType(ASTContext &C, unsigned NumElts) const {
// Check for valid vector element types.
if (T.isNull())
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 74f425d32648f..81209f2242f59 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -3021,17 +3021,33 @@ bool Sema::ValueIsRunOfOnes(CallExpr *TheCall, unsigned ArgNum) {
<< ArgNum << Arg->getSourceRange();
}
-bool Sema::getFormatStringInfo(const FormatAttr *Format, bool IsCXXMember,
- bool IsVariadic, FormatStringInfo *FSI) {
- if (Format->getFirstArg() == 0)
+bool Sema::getFormatStringInfo(const Decl *D, unsigned FormatIdx,
+ unsigned FirstArg, FormatStringInfo *FSI) {
+ bool IsCXXMember = false;
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(D))
+ IsCXXMember = MD->isInstance();
+ bool IsVariadic = false;
+ if (const FunctionType *FnTy = D->getFunctionType())
+ IsVariadic = cast<FunctionProtoType>(FnTy)->isVariadic();
+ else if (const auto *BD = dyn_cast<BlockDecl>(D))
+ IsVariadic = BD->isVariadic();
+ else if (const auto *OMD = dyn_cast<ObjCMethodDecl>(D))
+ IsVariadic = OMD->isVariadic();
+
+ return getFormatStringInfo(FormatIdx, FirstArg, IsCXXMember, IsVariadic, FSI);
+}
+
+bool Sema::getFormatStringInfo(unsigned FormatIdx, unsigned FirstArg,
+ bool IsCXXMember, bool IsVariadic,
+ FormatStringInfo *FSI) {
+ if (FirstArg == 0)
FSI->ArgPassingKind = FAPK_VAList;
else if (IsVariadic)
FSI->ArgPassingKind = FAPK_Variadic;
else
FSI->ArgPassingKind = FAPK_Fixed;
- FSI->FormatIdx = Format->getFormatIdx() - 1;
- FSI->FirstDataArg =
- FSI->ArgPassingKind == FAPK_VAList ? 0 : Format->getFirstArg() - 1;
+ FSI->FormatIdx = FormatIdx - 1;
+ FSI->FirstDataArg = FSI->ArgPassingKind == FAPK_VAList ? 0 : FirstArg - 1;
// The way the format attribute works in GCC, the implicit this argument
// of member functions is counted. However, it doesn't appear in our own
@@ -3285,10 +3301,15 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
// Printf and scanf checking.
llvm::SmallBitVector CheckedVarArgs;
if (FDecl) {
- for (const auto *I : FDecl->specific_attrs<FormatAttr>()) {
+ for (const auto *I : FDecl->specific_attrs<FormatMatchesAttr>()) {
// Only create vector if there are format attributes.
CheckedVarArgs.resize(Args.size());
+ CheckFormatString(I, Args, IsMemberFunction, CallType, Loc, Range,
+ CheckedVarArgs);
+ }
+ for (const auto *I : FDecl->specific_attrs<FormatAttr>()) {
+ CheckedVarArgs.resize(Args.size());
CheckFormatArguments(I, Args, IsMemberFunction, CallType, Loc, Range,
CheckedVarArgs);
}
@@ -5460,7 +5481,7 @@ bool Sema::BuiltinOSLogFormat(CallExpr *TheCall) {
llvm::SmallBitVector CheckedVarArgs(NumArgs, false);
ArrayRef<const Expr *> Args(TheCall->getArgs(), TheCall->getNumArgs());
bool Success = CheckFormatArguments(
- Args, FAPK_Variadic, FormatIdx, FirstDataArg, FST_OSLog,
+ Args, FAPK_Variadic, nullptr, FormatIdx, FirstDataArg, FST_OSLog,
VariadicFunction, TheCall->getBeginLoc(), SourceRange(),
CheckedVarArgs);
if (!Success)
@@ -5856,13 +5877,13 @@ class FormatStringLiteral {
const StringLiteral *FExpr;
int64_t Offset;
- public:
+public:
FormatStringLiteral(const StringLiteral *fexpr, int64_t Offset = 0)
: FExpr(fexpr), Offset(Offset) {}
- StringRef getString() const {
- return FExpr->getString().drop_front(Offset);
- }
+ const StringLiteral *getFormatString() const { return FExpr; }
+
+ StringRef getString() const { return FExpr->getString().drop_front(Offset); }
unsigned getByteLength() const {
return FExpr->getByteLength() - getCharByteWidth() * Offset;
@@ -5900,7 +5921,8 @@ class FormatStringLiteral {
} // namespace
static void CheckFormatString(
- Sema &S, const FormatStringLiteral *FExpr, const Expr *OrigFormatExpr,
+ Sema &S, const FormatStringLiteral *FExpr,
+ const StringLiteral *ReferenceFormatString, const Expr *OrigFormatExpr,
ArrayRef<const Expr *> Args, Sema::FormatArgumentPassingKind APK,
unsigned format_idx, unsigned firstDataArg, Sema::FormatStringType Type,
bool inFunctionCall, Sema::VariadicCallType CallType,
@@ -5914,14 +5936,13 @@ static const Expr *maybeConstEvalStringLiteral(ASTContext &Context,
// If this function returns false on the arguments to a function expecting a
// format string, we will usually need to emit a warning.
// True string literals are then checked by CheckFormatString.
-static StringLiteralCheckType
-checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
- Sema::FormatArgumentPassingKind APK, unsigned format_idx,
- unsigned firstDataArg, Sema::FormatStringType Type,
- Sema::VariadicCallType CallType, bool InFunctionCall,
- llvm::SmallBitVector &CheckedVarArgs,
- UncoveredArgHandler &UncoveredArg, llvm::APSInt Offset,
- bool IgnoreStringsWithoutSpecifiers = false) {
+static StringLiteralCheckType checkFormatStringExpr(
+ Sema &S, const StringLiteral *ReferenceFormatString, const Expr *E,
+ ArrayRef<const Expr *> Args, Sema::FormatArgumentPassingKind APK,
+ unsigned format_idx, unsigned firstDataArg, Sema::FormatStringType Type,
+ Sema::VariadicCallType CallType, bool InFunctionCall,
+ llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg,
+ llvm::APSInt Offset, bool IgnoreStringsWithoutSpecifiers = false) {
if (S.isConstantEvaluatedContext())
return SLCT_NotALiteral;
tryAgain:
@@ -5943,10 +5964,10 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
case Stmt::InitListExprClass:
// Handle expressions like {"foobar"}.
if (const clang::Expr *SLE = maybeConstEvalStringLiteral(S.Context, E)) {
- return checkFormatStringExpr(S, SLE, Args, APK, format_idx, firstDataArg,
- Type, CallType, /*InFunctionCall*/ false,
- CheckedVarArgs, UncoveredArg, Offset,
- IgnoreStringsWithoutSpecifiers);
+ return checkFormatStringExpr(
+ S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg,
+ Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs,
+ UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
}
return SLCT_NotALiteral;
case Stmt::BinaryConditionalOperatorClass:
@@ -5978,19 +5999,19 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
if (!CheckLeft)
Left = SLCT_UncheckedLiteral;
else {
- Left = checkFormatStringExpr(S, C->getTrueExpr(), Args, APK, format_idx,
- firstDataArg, Type, CallType, InFunctionCall,
- CheckedVarArgs, UncoveredArg, Offset,
- IgnoreStringsWithoutSpecifiers);
+ Left = checkFormatStringExpr(
+ S, ReferenceFormatString, C->getTrueExpr(), Args, APK, format_idx,
+ firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
+ UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
if (Left == SLCT_NotALiteral || !CheckRight) {
return Left;
}
}
StringLiteralCheckType Right = checkFormatStringExpr(
- S, C->getFalseExpr(), Args, APK, format_idx, firstDataArg, Type,
- CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, Offset,
- IgnoreStringsWithoutSpecifiers);
+ S, ReferenceFormatString, C->getFalseExpr(), Args, APK, format_idx,
+ firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
+ UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
return (CheckLeft && Left < Right) ? Left : Right;
}
@@ -6040,7 +6061,8 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
Init = InitList->getInit(0)->IgnoreParenImpCasts();
}
return checkFormatStringExpr(
- S, Init, Args, APK, format_idx, firstDataArg, Type, CallType,
+ S, ReferenceFormatString, Init, Args, APK, format_idx,
+ firstDataArg, Type, CallType,
/*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg, Offset);
}
}
@@ -6061,7 +6083,17 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
// ...
// }
//
- // Another interaction that we need to support is calling a variadic
+ // Another interaction that we need to support is using a format string
+ // specified by the format_matches attribute:
+ //
+ // __attribute__((format_matches(printf, 1, "%s %d")))
+ // void logmessage(char const *fmt, const char *a, int b) {
+ // printf(fmt, a, b); /* do not emit a warning about "fmt" */
+ // printf(fmt, 123.4); /* emit warnings that "%s %d" is incompatible */
+ // ...
+ // }
+ //
+ // Yet another interaction that we need to support is calling a variadic
// format function from a format function that has fixed arguments. For
// instance:
//
@@ -6084,39 +6116,63 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
//
if (const auto *PV = dyn_cast<ParmVarDecl>(VD)) {
if (const auto *D = dyn_cast<Decl>(PV->getDeclContext())) {
- for (const auto *PVFormat : D->specific_attrs<FormatAttr>()) {
- bool IsCXXMember = false;
- if (const auto *MD = dyn_cast<CXXMethodDecl>(D))
- IsCXXMember = MD->isInstance();
-
- bool IsVariadic = false;
- if (const FunctionType *FnTy = D->getFunctionType())
- IsVariadic = cast<FunctionProtoType>(FnTy)->isVariadic();
- else if (const auto *BD = dyn_cast<BlockDecl>(D))
- IsVariadic = BD->isVariadic();
- else if (const auto *OMD = dyn_cast<ObjCMethodDecl>(D))
- IsVariadic = OMD->isVariadic();
+ for (const auto *PVFormatMatches :
+ D->specific_attrs<FormatMatchesAttr>()) {
+ Sema::FormatStringInfo CalleeFSI;
+ if (!Sema::getFormatStringInfo(D, PVFormatMatches->getFormatIdx(),
+ 0, &CalleeFSI))
+ continue;
+ if (PV->getFunctionScopeIndex() == CalleeFSI.FormatIdx) {
+ // If using the wrong type of format string, emit a diagnostic
+ // here and stop checking to avoid irrelevant diagnostics.
+ if (Type != S.GetFormatStringType(PVFormatMatches)) {
+ S.Diag(Args[format_idx]->getBeginLoc(),
+ diag::warn_format_string_type_incompatible)
+ << PVFormatMatches->getType()->getName()
+ << S.GetFormatStringTypeName(Type);
+ if (!InFunctionCall) {
+ S.Diag(PVFormatMatches->getFormatString()->getBeginLoc(),
+ diag::note_format_string_defined);
+ }
+ return SLCT_UncheckedLiteral;
+ }
+ return checkFormatStringExpr(
+ S, ReferenceFormatString, PVFormatMatches->getFormatString(),
+ Args, APK, format_idx, firstDataArg, Type, CallType,
+ /*InFunctionCall*/ false, CheckedVarArgs, UncoveredArg,
+ Offset, IgnoreStringsWithoutSpecifiers);
+ }
+ }
+ for (const auto *PVFormat : D->specific_attrs<FormatAttr>()) {
Sema::FormatStringInfo CallerFSI;
- if (Sema::getFormatStringInfo(PVFormat, IsCXXMember, IsVariadic,
- &CallerFSI)) {
- // We also check if the formats are compatible.
- // We can't pass a 'scanf' string to a 'printf' function.
- if (PV->getFunctionScopeIndex() == CallerFSI.FormatIdx &&
- Type == S.GetFormatStringType(PVFormat)) {
- // Lastly, check that argument passing kinds transition in a
- // way that makes sense:
- // from a caller with FAPK_VAList, allow FAPK_VAList
- // from a caller with FAPK_Fixed, allow FAPK_Fixed
- // from a caller with FAPK_Fixed, allow FAPK_Variadic
- // from a caller with FAPK_Variadic, allow FAPK_VAList
- switch (combineFAPK(CallerFSI.ArgPassingKind, APK)) {
- case combineFAPK(Sema::FAPK_VAList, Sema::FAPK_VAList):
- case combineFAPK(Sema::FAPK_Fixed, Sema::FAPK_Fixed):
- case combineFAPK(Sema::FAPK_Fixed, Sema::FAPK_Variadic):
- case combineFAPK(Sema::FAPK_Variadic, Sema::FAPK_VAList):
- return SLCT_UncheckedLiteral;
- }
+ if (!Sema::getFormatStringInfo(D, PVFormat->getFormatIdx(),
+ PVFormat->getFirstArg(), &CallerFSI))
+ continue;
+ // We also check if the formats are compatible.
+ // We can't pass a 'scanf' string to a 'printf' function.
+ if (Type != S.GetFormatStringType(PVFormat)) {
+ S.Diag(Args[format_idx]->getBeginLoc(),
+ diag::warn_format_string_type_incompatible)
+ << PVFormat->getType()->getName()
+ << S.GetFormatStringTypeName(Type);
+ if (!InFunctionCall) {
+ S.Diag(E->getBeginLoc(), diag::note_format_string_defined);
+ }
+ return SLCT_UncheckedLiteral;
+ } else if (PV->getFunctionScopeIndex() == CallerFSI.FormatIdx) {
+ // Lastly, check that argument passing kinds transition in a
+ // way that makes sense:
+ // from a caller with FAPK_VAList, allow FAPK_VAList
+ // from a caller with FAPK_Fixed, allow FAPK_Fixed
+ // from a caller with FAPK_Fixed, allow FAPK_Variadic
+ // from a caller with FAPK_Variadic, allow FAPK_VAList
+ switch (combineFAPK(CallerFSI.ArgPassingKind, APK)) {
+ case combineFAPK(Sema::FAPK_VAList, Sema::FAPK_VAList):
+ case combineFAPK(Sema::FAPK_Fixed, Sema::FAPK_Fixed):
+ case combineFAPK(Sema::FAPK_Fixed, Sema::FAPK_Variadic):
+ case combineFAPK(Sema::FAPK_Variadic, Sema::FAPK_VAList):
+ return SLCT_UncheckedLiteral;
}
}
}
@@ -6136,9 +6192,9 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
for (const auto *FA : ND->specific_attrs<FormatArgAttr>()) {
const Expr *Arg = CE->getArg(FA->getFormatIdx().getASTIndex());
StringLiteralCheckType Result = checkFormatStringExpr(
- S, Arg, Args, APK, format_idx, firstDataArg, Type, CallType,
- InFunctionCall, CheckedVarArgs, UncoveredArg, Offset,
- IgnoreStringsWithoutSpecifiers);
+ S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg,
+ Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg,
+ Offset, IgnoreStringsWithoutSpecifiers);
if (IsFirst) {
CommonResult = Result;
IsFirst = false;
@@ -6153,17 +6209,17 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
BuiltinID == Builtin::BI__builtin___NSStringMakeConstantString) {
const Expr *Arg = CE->getArg(0);
return checkFormatStringExpr(
- S, Arg, Args, APK, format_idx, firstDataArg, Type, CallType,
- InFunctionCall, CheckedVarArgs, UncoveredArg, Offset,
- IgnoreStringsWithoutSpecifiers);
+ S, ReferenceFormatString, Arg, Args, APK, format_idx,
+ firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs,
+ UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
}
}
}
if (const Expr *SLE = maybeConstEvalStringLiteral(S.Context, E))
- return checkFormatStringExpr(S, SLE, Args, APK, format_idx, firstDataArg,
- Type, CallType, /*InFunctionCall*/ false,
- CheckedVarArgs, UncoveredArg, Offset,
- IgnoreStringsWithoutSpecifiers);
+ return checkFormatStringExpr(
+ S, ReferenceFormatString, SLE, Args, APK, format_idx, firstDataArg,
+ Type, CallType, /*InFunctionCall*/ false, CheckedVarArgs,
+ UncoveredArg, Offset, IgnoreStringsWithoutSpecifiers);
return SLCT_NotALiteral;
}
case Stmt::ObjCMessageExprClass: {
@@ -6187,9 +6243,9 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
const Expr *Arg = ME->getArg(FA->getFormatIdx().getASTIndex());
return checkFormatStringExpr(
- S, Arg, Args, APK, format_idx, firstDataArg, Type, CallType,
- InFunctionCall, CheckedVarArgs, UncoveredArg, Offset,
- IgnoreStringsWithoutSpecifiers);
+ S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg,
+ Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg,
+ Offset, IgnoreStringsWithoutSpecifiers);
}
}
@@ -6211,8 +6267,9 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
return SLCT_NotALiteral;
}
FormatStringLiteral FStr(StrE, Offset.sextOrTrunc(64).getSExtValue());
- CheckFormatString(S, &FStr, E, Args, APK, format_idx, firstDataArg, Type,
- InFunctionCall, CallType, CheckedVarArgs, UncoveredArg,
+ CheckFormatString(S, &FStr, ReferenceFormatString, E, Args, APK,
+ format_idx, firstDataArg, Type, InFunctionCall,
+ CallType, CheckedVarArgs, UncoveredArg,
IgnoreStringsWithoutSpecifiers);
return SLCT_CheckedLiteral;
}
@@ -6289,8 +6346,31 @@ static const Expr *maybeConstEvalStringLiteral(ASTContext &Context,
return nullptr;
}
-Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) {
- return llvm::StringSwitch<FormatStringType>(Format->getType()->getName())
+StringRef Sema::GetFormatStringTypeName(Sema::FormatStringType FST) {
+ switch (FST) {
+ case FST_Scanf:
+ return "scanf";
+ case FST_Printf:
+ return "printf";
+ case FST_NSString:
+ return "NSString";
+ case FST_Strftime:
+ return "strftime";
+ case FST_Strfmon:
+ return "strfmon";
+ case FST_Kprintf:
+ return "kprintf";
+ case FST_FreeBSDKPrintf:
+ return "freebsd_kprintf";
+ case FST_OSLog:
+ return "os_log";
+ default:
+ return "<unknown>";
+ }
+}
+
+Sema::FormatStringType Sema::GetFormatStringType(StringRef Flavor) {
+ return llvm::StringSwitch<FormatStringType>(Flavor)
.Case("scanf", FST_Scanf)
.Cases("printf", "printf0", "syslog", FST_Printf)
.Cases("NSString", "CFString", FST_NSString)
@@ -6303,22 +6383,49 @@ Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) {
.Default(FST_Unknown);
}
+Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) {
+ return GetFormatStringType(Format->getType()->getName());
+}
+
+Sema::FormatStringType
+Sema::GetFormatStringType(const FormatMatchesAttr *Format) {
+ return GetFormatStringType(Format->getType()->getName());
+}
+
bool Sema::CheckFormatArguments(const FormatAttr *Format,
ArrayRef<const Expr *> Args, bool IsCXXMember,
VariadicCallType CallType, SourceLocation Loc,
SourceRange Range,
llvm::SmallBitVector &CheckedVarArgs) {
FormatStringInfo FSI;
- if (getFormatStringInfo(Format, IsCXXMember, CallType != VariadicDoesNotApply,
- &FSI))
- return CheckFormatArguments(Args, FSI.ArgPassingKind, FSI.FormatIdx,
+ if (getFormatStringInfo(Format->getFormatIdx(), Format->getFirstArg(),
+ IsCXXMember, CallType != VariadicDoesNotApply, &FSI))
+ return CheckFormatArguments(
+ Args, FSI.ArgPassingKind, nullptr, FSI.FormatIdx, FSI.FirstDataArg,
+ GetFormatStringType(Format), CallType, Loc, Range, CheckedVarArgs);
+ return false;
+}
+
+bool Sema::CheckFormatString(const FormatMatchesAttr *Format,
+ ArrayRef<const Expr *> Args, bool IsCXXMember,
+ VariadicCallType CallType, SourceLocation Loc,
+ SourceRange Range,
+ llvm::SmallBitVector &CheckedVarArgs) {
+ FormatStringInfo FSI;
+ if (getFormatStringInfo(Format->getFormatIdx(), 0, IsCXXMember, false,
+ &FSI)) {
+ FSI.ArgPassingKind = Sema::FAPK_Elsewhere;
+ return CheckFormatArguments(Args, FSI.ArgPassingKind,
+ Format->getFormatString(), FSI.FormatIdx,
FSI.FirstDataArg, GetFormatStringType(Format),
CallType, Loc, Range, CheckedVarArgs);
+ }
return false;
}
bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
Sema::FormatArgumentPassingKind APK,
+ const StringLiteral *ReferenceFormatString,
unsigned format_idx, unsigned firstDataArg,
FormatStringType Type,
VariadicCallType CallType, SourceLocation Loc,
@@ -6346,8 +6453,8 @@ bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
// the same format string checking logic for both ObjC and C strings.
UncoveredArgHandler UncoveredArg;
StringLiteralCheckType CT = checkFormatStringExpr(
- *this, OrigFormatExpr, Args, APK, format_idx, firstDataArg, Type,
- CallType,
+ *this, ReferenceFormatString, OrigFormatExpr, Args, APK, format_idx,
+ firstDataArg, Type, CallType,
/*IsFunctionCall*/ true, CheckedVarArgs, UncoveredArg,
/*no string offset*/ llvm::APSInt(64, false) = 0);
@@ -6443,6 +6550,11 @@ class CheckFormatHandler : public analyze_format_string::FormatStringHandler {
CoveredArgs.reset();
}
+ bool HasFormatArguments() const {
+ return ArgPassingKind == Sema::FAPK_Fixed ||
+ ArgPassingKind == Sema::FAPK_Variadic;
+ }
+
void DoneProcessing();
void HandleIncompleteSpecifier(const char *startSpecifier,
@@ -6678,7 +6790,7 @@ const Expr *CheckFormatHandler::getDataArg(unsigned i) const {
void CheckFormatHandler::DoneProcessing() {
// Does the number of data arguments exceed the number of
// format conversions in the format string?
- if (ArgPassingKind != Sema::FAPK_VAList) {
+ if (HasFormatArguments()) {
// Find any arguments that weren't covered.
CoveredArgs.flip();
signed notCoveredArg = CoveredArgs.find_first();
@@ -6787,7 +6899,7 @@ CheckFormatHandler::CheckNumArgs(
const analyze_format_string::ConversionSpecifier &CS,
const char *startSpecifier, unsigned specifierLen, unsigned argIndex) {
- if (argIndex >= NumDataArgs) {
+ if (HasFormatArguments() && argIndex >= NumDataArgs) {
PartialDiagnostic PDiag = FS.usesPositionalArg()
? (S.PDiag(diag::warn_printf_positional_arg_exceeds_data_args)
<< (argIndex+1) << NumDataArgs)
@@ -6928,18 +7040,112 @@ class CheckPrintfHandler : public CheckFormatHandler {
void HandleInvalidObjCModifierFlag(const char *startFlag,
unsigned flagLen) override;
- void HandleObjCFlagsWithNonObjCConversion(const char *flagsStart,
- const char *flagsEnd,
- const char *conversionPosition)
- override;
+ void
+ HandleObjCFlagsWithNonObjCConversion(const char *flagsStart,
+ const char *flagsEnd,
+ const char *conversionPosition) override;
+};
+
+/// Keeps around the information needed to verify that two specifiers are
+/// compatible.
+class EquatableFormatArgument {
+public:
+ enum SpecifierSensitivity : unsigned {
+ SS_None,
+ SS_Private,
+ SS_Public,
+ SS_Sensitive
+ };
+
+ enum FormatArgumentRole : unsigned {
+ FAR_Data,
+ FAR_FieldWidth,
+ FAR_Precision,
+ FAR_Auxiliary, // FreeBSD kernel %b and %D
+ };
+
+private:
+ analyze_format_string::ArgType ArgType;
+ analyze_format_string::LengthModifier::Kind LengthMod;
+ StringRef SpecifierLetter;
+ CharSourceRange Range;
+ SourceLocation ElementLoc;
+ FormatArgumentRole Role : 2;
+ SpecifierSensitivity Sensitivity : 2; // only set for FAR_Data
+ unsigned Position : 14;
+ unsigned ModifierFor : 14; // not set for FAR_Data
+
+ void EmitDiagnostic(Sema &S, PartialDiagnostic PDiag, const Expr *FmtExpr,
+ bool InFunctionCall) const;
+
+public:
+ EquatableFormatArgument(CharSourceRange Range, SourceLocation ElementLoc,
+ analyze_format_string::LengthModifier::Kind LengthMod,
+ StringRef SpecifierLetter,
+ analyze_format_string::ArgType ArgType,
+ FormatArgumentRole Role,
+ SpecifierSensitivity Sensitivity, unsigned Position,
+ unsigned ModifierFor)
+ : ArgType(ArgType), LengthMod(LengthMod),
+ SpecifierLetter(SpecifierLetter), Range(Range), ElementLoc(ElementLoc),
+ Role(Role), Sensitivity(Sensitivity), Position(Position),
+ ModifierFor(ModifierFor) {}
+
+ unsigned getPosition() const { return Position; }
+ SourceLocation getSourceLocation() const { return ElementLoc; }
+ CharSourceRange getSourceRange() const { return Range; }
+ analyze_format_string::LengthModifier getLengthModifier() const {
+ return analyze_format_string::LengthModifier(nullptr, LengthMod);
+ }
+ void setModifierFor(unsigned V) { ModifierFor = V; }
+
+ std::string buildFormatSpecifier() const {
+ std::string result;
+ llvm::raw_string_ostream(result)
+ << getLengthModifier().toString() << SpecifierLetter;
+ return result;
+ }
+
+ bool VerifyCompatible(Sema &S, const EquatableFormatArgument &Other,
+ const Expr *FmtExpr, bool InFunctionCall) const;
+};
+
+/// Turns format strings into lists of EquatableSpecifier objects.
+class DecomposePrintfHandler : public CheckPrintfHandler {
+ llvm::SmallVectorImpl<EquatableFormatArgument> &Specs;
+ bool HadError;
+
+ DecomposePrintfHandler(
+ Sema &s, const FormatStringLiteral *fexpr, const Expr *origFormatExpr,
+ const Sema::FormatStringType type, unsigned firstDataArg,
+ unsigned numDataArgs, bool isObjC, const char *beg,
+ Sema::FormatArgumentPassingKind APK, ArrayRef<const Expr *> Args,
+ unsigned formatIdx, bool inFunctionCall, Sema::VariadicCallType CallType,
+ llvm::SmallBitVector &CheckedVarArgs, UncoveredArgHandler &UncoveredArg,
+ llvm::SmallVectorImpl<EquatableFormatArgument> &Specs)
+ : CheckPrintfHandler(s, fexpr, origFormatExpr, type, firstDataArg,
+ numDataArgs, isObjC, beg, APK, Args, formatIdx,
+ inFunctionCall, CallType, CheckedVarArgs,
+ UncoveredArg),
+ Specs(Specs), HadError(false) {}
+
+public:
+ static bool
+ GetSpecifiers(Sema &S, const FormatStringLiteral *FSL, const Expr *FmtExpr,
+ Sema::FormatStringType type, bool IsObjC, bool InFunctionCall,
+ llvm::SmallVectorImpl<EquatableFormatArgument> &Args);
+
+ virtual bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS,
+ const char *startSpecifier,
+ unsigned specifierLen,
+ const TargetInfo &Target) override;
};
} // namespace
bool CheckPrintfHandler::HandleInvalidPrintfConversionSpecifier(
- const analyze_printf::PrintfSpecifier &FS,
- const char *startSpecifier,
- unsigned specifierLen) {
+ const analyze_printf::PrintfSpecifier &FS, const char *startSpecifier,
+ unsigned specifierLen) {
const analyze_printf::PrintfConversionSpecifier &CS =
FS.getConversionSpecifier();
@@ -6957,7 +7163,7 @@ bool CheckPrintfHandler::HandleAmount(
const analyze_format_string::OptionalAmount &Amt, unsigned k,
const char *startSpecifier, unsigned specifierLen) {
if (Amt.hasDataArgument()) {
- if (ArgPassingKind != Sema::FAPK_VAList) {
+ if (HasFormatArguments()) {
unsigned argIndex = Amt.getArgIndex();
if (argIndex >= NumDataArgs) {
EmitFormatDiagnostic(S.PDiag(diag::warn_printf_asterisk_missing_arg)
@@ -7082,8 +7288,216 @@ void CheckPrintfHandler::HandleObjCFlagsWithNonObjCConversion(
auto diag = diag::warn_printf_ObjCflags_without_ObjCConversion;
EmitFormatDiagnostic(S.PDiag(diag) << StringRef(conversionPosition, 1),
getLocationOfByte(conversionPosition),
- /*IsStringLocation*/true,
- Range, FixItHint::CreateRemoval(Range));
+ /*IsStringLocation*/ true, Range,
+ FixItHint::CreateRemoval(Range));
+}
+
+void EquatableFormatArgument::EmitDiagnostic(Sema &S, PartialDiagnostic PDiag,
+ const Expr *FmtExpr,
+ bool InFunctionCall) const {
+ CheckFormatHandler::EmitFormatDiagnostic(S, InFunctionCall, FmtExpr, PDiag,
+ ElementLoc, true, Range);
+}
+
+bool EquatableFormatArgument::VerifyCompatible(
+ Sema &S, const EquatableFormatArgument &Other, const Expr *FmtExpr,
+ bool InFunctionCall) const {
+ using MK = analyze_format_string::ArgType::MatchKind;
+ if (Role != Other.Role) {
+ // diagnose and stop
+ EmitDiagnostic(
+ S, S.PDiag(diag::warn_format_cmp_role_mismatch) << Role << Other.Role,
+ FmtExpr, InFunctionCall);
+ S.Diag(Other.ElementLoc, diag::note_format_cmp_with) << 0 << Other.Range;
+ return false;
+ }
+
+ if (Role != FAR_Data) {
+ if (ModifierFor != Other.ModifierFor) {
+ // diagnose and stop
+ EmitDiagnostic(S,
+ S.PDiag(diag::warn_format_cmp_modifierfor_mismatch)
+ << (ModifierFor + 1) << (Other.ModifierFor + 1),
+ FmtExpr, InFunctionCall);
+ S.Diag(Other.ElementLoc, diag::note_format_cmp_with) << 0 << Other.Range;
+ return false;
+ }
+ return true;
+ }
+
+ bool HadError = false;
+ if (Sensitivity != Other.Sensitivity) {
+ // diagnose and continue
+ EmitDiagnostic(S,
+ S.PDiag(diag::warn_format_cmp_sensitivity_mismatch)
+ << Sensitivity << Other.Sensitivity,
+ FmtExpr, InFunctionCall);
+ HadError = S.Diag(Other.ElementLoc, diag::note_format_cmp_with)
+ << 0 << Other.Range;
+ }
+
+ switch (ArgType.matchesArgType(S.Context, Other.ArgType)) {
+ case MK::Match:
+ break;
+
+ case MK::MatchPromotion:
+ // Per consensus reached at https://discourse.llvm.org/t/-/83076/12,
+ // MatchPromotion is treated as a failure by format_matches.
+ case MK::NoMatch:
+ case MK::NoMatchTypeConfusion:
+ case MK::NoMatchPromotionTypeConfusion:
+ EmitDiagnostic(S,
+ S.PDiag(diag::warn_format_cmp_specifier_mismatch)
+ << buildFormatSpecifier()
+ << Other.buildFormatSpecifier(),
+ FmtExpr, InFunctionCall);
+ HadError = S.Diag(Other.ElementLoc, diag::note_format_cmp_with)
+ << 0 << Other.Range;
+ break;
+
+ case MK::NoMatchPedantic:
+ EmitDiagnostic(S,
+ S.PDiag(diag::warn_format_cmp_specifier_mismatch_pedantic)
+ << buildFormatSpecifier()
+ << Other.buildFormatSpecifier(),
+ FmtExpr, InFunctionCall);
+ HadError = S.Diag(Other.ElementLoc, diag::note_format_cmp_with)
+ << 0 << Other.Range;
+ break;
+
+ case MK::NoMatchSignedness:
+ if (!S.getDiagnostics().isIgnored(
+ diag::warn_format_conversion_argument_type_mismatch_signedness,
+ ElementLoc)) {
+ EmitDiagnostic(S,
+ S.PDiag(diag::warn_format_cmp_specifier_sign_mismatch)
+ << buildFormatSpecifier()
+ << Other.buildFormatSpecifier(),
+ FmtExpr, InFunctionCall);
+ HadError = S.Diag(Other.ElementLoc, diag::note_format_cmp_with)
+ << 0 << Other.Range;
+ }
+ break;
+ }
+ return !HadError;
+}
+
+bool DecomposePrintfHandler::GetSpecifiers(
+ Sema &S, const FormatStringLiteral *FSL, const Expr *FmtExpr,
+ Sema::FormatStringType Type, bool IsObjC, bool InFunctionCall,
+ llvm::SmallVectorImpl<EquatableFormatArgument> &Args) {
+ StringRef Data = FSL->getString();
+ const char *Str = Data.data();
+ llvm::SmallBitVector BV;
+ UncoveredArgHandler UA;
+ DecomposePrintfHandler H(S, FSL, FSL->getFormatString(), Type, 0, 0, IsObjC,
+ Str, Sema::FAPK_Elsewhere, {FSL->getFormatString()},
+ 0, InFunctionCall, Sema::VariadicDoesNotApply, BV,
+ UA, Args);
+
+ if (!analyze_format_string::ParsePrintfString(
+ H, Str, Str + Data.size(), S.getLangOpts(), S.Context.getTargetInfo(),
+ Type == Sema::FST_FreeBSDKPrintf))
+ H.DoneProcessing();
+ if (H.HadError)
+ return false;
+
+ std::sort(
+ Args.begin(), Args.end(),
+ [](const EquatableFormatArgument &A, const EquatableFormatArgument &B) {
+ return A.getPosition() < B.getPosition();
+ });
+ return true;
+}
+
+bool DecomposePrintfHandler::HandlePrintfSpecifier(
+ const analyze_printf::PrintfSpecifier &FS, const char *startSpecifier,
+ unsigned specifierLen, const TargetInfo &Target) {
+ if (!CheckPrintfHandler::HandlePrintfSpecifier(FS, startSpecifier,
+ specifierLen, Target)) {
+ HadError = true;
+ return false;
+ }
+
+ // Do not add any specifiers to the list for %%. This is possibly incorrect
+ // if using a precision/width with a data argument, but that combination is
+ // meaningless and we wouldn't know which format to attach the
+ // precision/width to.
+ const auto &CS = FS.getConversionSpecifier();
+ if (CS.getKind() == analyze_format_string::ConversionSpecifier::PercentArg)
+ return true;
+
+ // have to patch these to have the right ModifierFor if they are used
+ const unsigned Unset = ~0;
+ unsigned FieldWidthIndex = Unset;
+ unsigned PrecisionIndex = Unset;
+
+ // field width?
+ const auto &FieldWidth = FS.getFieldWidth();
+ if (!FieldWidth.isInvalid() && FieldWidth.hasDataArgument()) {
+ FieldWidthIndex = Specs.size();
+ Specs.emplace_back(getSpecifierRange(startSpecifier, specifierLen),
+ getLocationOfByte(FieldWidth.getStart()),
+ analyze_format_string::LengthModifier::None, "*",
+ FieldWidth.getArgType(S.Context),
+ EquatableFormatArgument::FAR_FieldWidth,
+ EquatableFormatArgument::SS_None,
+ FieldWidth.usesPositionalArg()
+ ? FieldWidth.getPositionalArgIndex() - 1
+ : FieldWidthIndex,
+ 0);
+ }
+ // precision?
+ const auto &Precision = FS.getPrecision();
+ if (!Precision.isInvalid() && Precision.hasDataArgument()) {
+ PrecisionIndex = Specs.size();
+ Specs.emplace_back(
+ getSpecifierRange(startSpecifier, specifierLen),
+ getLocationOfByte(Precision.getStart()),
+ analyze_format_string::LengthModifier::None, ".*",
+ Precision.getArgType(S.Context), EquatableFormatArgument::FAR_Precision,
+ EquatableFormatArgument::SS_None,
+ Precision.usesPositionalArg() ? Precision.getPositionalArgIndex() - 1
+ : PrecisionIndex,
+ 0);
+ }
+
+ // this specifier
+ unsigned SpecIndex =
+ FS.usesPositionalArg() ? FS.getPositionalArgIndex() - 1 : Specs.size();
+ if (FieldWidthIndex != Unset)
+ Specs[FieldWidthIndex].setModifierFor(SpecIndex);
+ if (PrecisionIndex != Unset)
+ Specs[PrecisionIndex].setModifierFor(SpecIndex);
+
+ EquatableFormatArgument::SpecifierSensitivity Sensitivity;
+ if (FS.isPrivate())
+ Sensitivity = EquatableFormatArgument::SS_Private;
+ else if (FS.isPublic())
+ Sensitivity = EquatableFormatArgument::SS_Public;
+ else if (FS.isSensitive())
+ Sensitivity = EquatableFormatArgument::SS_Sensitive;
+ else
+ Sensitivity = EquatableFormatArgument::SS_None;
+
+ Specs.emplace_back(
+ getSpecifierRange(startSpecifier, specifierLen),
+ getLocationOfByte(CS.getStart()), FS.getLengthModifier().getKind(),
+ CS.getCharacters(), FS.getArgType(S.Context, isObjCContext()),
+ EquatableFormatArgument::FAR_Data, Sensitivity, SpecIndex, 0);
+
+ // auxiliary argument?
+ if (CS.getKind() == analyze_format_string::ConversionSpecifier::FreeBSDbArg ||
+ CS.getKind() == analyze_format_string::ConversionSpecifier::FreeBSDDArg) {
+ Specs.emplace_back(getSpecifierRange(startSpecifier, specifierLen),
+ getLocationOfByte(CS.getStart()),
+ analyze_format_string::LengthModifier::None,
+ CS.getCharacters(),
+ analyze_format_string::ArgType::CStrTy,
+ EquatableFormatArgument::FAR_Auxiliary, Sensitivity,
+ SpecIndex + 1, SpecIndex);
+ }
+ return true;
}
// Determines if the specified is a C++ class or struct containing
@@ -7212,34 +7626,36 @@ bool CheckPrintfHandler::HandlePrintfSpecifier(
if (!CheckNumArgs(FS, CS, startSpecifier, specifierLen, argIndex + 1))
return false;
- // Claim the second argument.
- CoveredArgs.set(argIndex + 1);
-
- // Type check the first argument (int for %b, pointer for %D)
- const Expr *Ex = getDataArg(argIndex);
- const analyze_printf::ArgType &AT =
- (CS.getKind() == ConversionSpecifier::FreeBSDbArg) ?
- ArgType(S.Context.IntTy) : ArgType::CPointerTy;
- if (AT.isValid() && !AT.matchesType(S.Context, Ex->getType()))
- EmitFormatDiagnostic(
- S.PDiag(diag::warn_format_conversion_argument_type_mismatch)
- << AT.getRepresentativeTypeName(S.Context) << Ex->getType()
- << false << Ex->getSourceRange(),
- Ex->getBeginLoc(), /*IsStringLocation*/ false,
- getSpecifierRange(startSpecifier, specifierLen));
-
- // Type check the second argument (char * for both %b and %D)
- Ex = getDataArg(argIndex + 1);
- const analyze_printf::ArgType &AT2 = ArgType::CStrTy;
- if (AT2.isValid() && !AT2.matchesType(S.Context, Ex->getType()))
- EmitFormatDiagnostic(
- S.PDiag(diag::warn_format_conversion_argument_type_mismatch)
- << AT2.getRepresentativeTypeName(S.Context) << Ex->getType()
- << false << Ex->getSourceRange(),
- Ex->getBeginLoc(), /*IsStringLocation*/ false,
- getSpecifierRange(startSpecifier, specifierLen));
-
- return true;
+ if (HasFormatArguments()) {
+ // Claim the second argument.
+ CoveredArgs.set(argIndex + 1);
+
+ // Type check the first argument (int for %b, pointer for %D)
+ const Expr *Ex = getDataArg(argIndex);
+ const analyze_printf::ArgType &AT =
+ (CS.getKind() == ConversionSpecifier::FreeBSDbArg)
+ ? ArgType(S.Context.IntTy)
+ : ArgType::CPointerTy;
+ if (AT.isValid() && !AT.matchesType(S.Context, Ex->getType()))
+ EmitFormatDiagnostic(
+ S.PDiag(diag::warn_format_conversion_argument_type_mismatch)
+ << AT.getRepresentativeTypeName(S.Context) << Ex->getType()
+ << false << Ex->getSourceRange(),
+ Ex->getBeginLoc(), /*IsStringLocation*/ false,
+ getSpecifierRange(startSpecifier, specifierLen));
+
+ // Type check the second argument (char * for both %b and %D)
+ Ex = getDataArg(argIndex + 1);
+ const analyze_printf::ArgType &AT2 = ArgType::CStrTy;
+ if (AT2.isValid() && !AT2.matchesType(S.Context, Ex->getType()))
+ EmitFormatDiagnostic(
+ S.PDiag(diag::warn_format_conversion_argument_type_mismatch)
+ << AT2.getRepresentativeTypeName(S.Context) << Ex->getType()
+ << false << Ex->getSourceRange(),
+ Ex->getBeginLoc(), /*IsStringLocation*/ false,
+ getSpecifierRange(startSpecifier, specifierLen));
+ }
+ return true;
}
// Check for using an Objective-C specific conversion specifier
@@ -7359,7 +7775,7 @@ bool CheckPrintfHandler::HandlePrintfSpecifier(
HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen);
// The remaining checks depend on the data arguments.
- if (ArgPassingKind == Sema::FAPK_VAList)
+ if (!HasFormatArguments())
return true;
if (!CheckNumArgs(FS, CS, startSpecifier, specifierLen, argIndex))
@@ -8016,7 +8432,7 @@ bool CheckScanfHandler::HandleScanfSpecifier(
HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen);
// The remaining checks depend on the data arguments.
- if (ArgPassingKind == Sema::FAPK_VAList)
+ if (!HasFormatArguments())
return true;
if (!CheckNumArgs(FS, CS, startSpecifier, specifierLen, argIndex))
@@ -8074,8 +8490,67 @@ bool CheckScanfHandler::HandleScanfSpecifier(
return true;
}
+static bool CompareFormatSpecifiers(Sema &S, const StringLiteral *Ref,
+ ArrayRef<EquatableFormatArgument> RefArgs,
+ const StringLiteral *Fmt,
+ ArrayRef<EquatableFormatArgument> FmtArgs,
+ const Expr *FmtExpr, bool InFunctionCall) {
+ bool HadError = false;
+ auto FmtIter = FmtArgs.begin(), FmtEnd = FmtArgs.end();
+ auto RefIter = RefArgs.begin(), RefEnd = RefArgs.end();
+ while (FmtIter < FmtEnd && RefIter < RefEnd) {
+ // In positional-style format strings, the same specifier can appear
+ // multiple times (like %2$i %2$d). Specifiers in both RefArgs and FmtArgs
+ // are sorted by getPosition(), and we process each range of equal
+ // getPosition() values as one group.
+ // RefArgs are taken from a string literal that was given to
+ // attribute(format_matches), and if we got this far, we have already
+ // verified that if it has positional specifiers that appear in multiple
+ // locations, then they are all mutually compatible. What's left for us to
+ // do is verify that all specifiers with the same position in FmtArgs are
+ // compatible with the RefArgs specifiers. We check each specifier from
+ // FmtArgs against the first member of the RefArgs group.
+ for (; FmtIter < FmtEnd; ++FmtIter) {
+ // Clang does not diagnose missing format specifiers in positional-style
+ // strings (TODO: which it probably should do, as it is UB to skip over a
+ // format argument). Skip specifiers if needed.
+ if (FmtIter->getPosition() < RefIter->getPosition())
+ continue;
+
+ // Delimits a new getPosition() value.
+ if (FmtIter->getPosition() > RefIter->getPosition())
+ break;
+
+ HadError |=
+ !FmtIter->VerifyCompatible(S, *RefIter, FmtExpr, InFunctionCall);
+ }
+
+ // Jump RefIter to the start of the next group.
+ RefIter = std::find_if(RefIter + 1, RefEnd, [=](const auto &Arg) {
+ return Arg.getPosition() != RefIter->getPosition();
+ });
+ }
+
+ if (FmtIter < FmtEnd) {
+ CheckFormatHandler::EmitFormatDiagnostic(
+ S, InFunctionCall, FmtExpr,
+ S.PDiag(diag::warn_format_cmp_specifier_arity) << 1,
+ FmtExpr->getBeginLoc(), false, FmtIter->getSourceRange());
+ HadError = S.Diag(Ref->getBeginLoc(), diag::note_format_cmp_with) << 1;
+ } else if (RefIter < RefEnd) {
+ CheckFormatHandler::EmitFormatDiagnostic(
+ S, InFunctionCall, FmtExpr,
+ S.PDiag(diag::warn_format_cmp_specifier_arity) << 0,
+ FmtExpr->getBeginLoc(), false, Fmt->getSourceRange());
+ HadError = S.Diag(Ref->getBeginLoc(), diag::note_format_cmp_with)
+ << 1 << RefIter->getSourceRange();
+ }
+ return !HadError;
+}
+
static void CheckFormatString(
- Sema &S, const FormatStringLiteral *FExpr, const Expr *OrigFormatExpr,
+ Sema &S, const FormatStringLiteral *FExpr,
+ const StringLiteral *ReferenceFormatString, const Expr *OrigFormatExpr,
ArrayRef<const Expr *> Args, Sema::FormatArgumentPassingKind APK,
unsigned format_idx, unsigned firstDataArg, Sema::FormatStringType Type,
bool inFunctionCall, Sema::VariadicCallType CallType,
@@ -8130,16 +8605,22 @@ static void CheckFormatString(
Type == Sema::FST_Kprintf || Type == Sema::FST_FreeBSDKPrintf ||
Type == Sema::FST_OSLog || Type == Sema::FST_OSTrace ||
Type == Sema::FST_Syslog) {
- CheckPrintfHandler H(
- S, FExpr, OrigFormatExpr, Type, firstDataArg, numDataArgs,
- (Type == Sema::FST_NSString || Type == Sema::FST_OSTrace), Str, APK,
- Args, format_idx, inFunctionCall, CallType, CheckedVarArgs,
- UncoveredArg);
-
- if (!analyze_format_string::ParsePrintfString(
- H, Str, Str + StrLen, S.getLangOpts(), S.Context.getTargetInfo(),
- Type == Sema::FST_Kprintf || Type == Sema::FST_FreeBSDKPrintf))
- H.DoneProcessing();
+ bool IsObjC = Type == Sema::FST_NSString || Type == Sema::FST_OSTrace;
+ if (ReferenceFormatString == nullptr) {
+ CheckPrintfHandler H(S, FExpr, OrigFormatExpr, Type, firstDataArg,
+ numDataArgs, IsObjC, Str, APK, Args, format_idx,
+ inFunctionCall, CallType, CheckedVarArgs,
+ UncoveredArg);
+
+ if (!analyze_format_string::ParsePrintfString(
+ H, Str, Str + StrLen, S.getLangOpts(), S.Context.getTargetInfo(),
+ Type == Sema::FST_Kprintf || Type == Sema::FST_FreeBSDKPrintf))
+ H.DoneProcessing();
+ } else {
+ S.CheckFormatStringsCompatible(
+ Type, ReferenceFormatString, FExpr->getFormatString(),
+ inFunctionCall ? nullptr : Args[format_idx]);
+ }
} else if (Type == Sema::FST_Scanf) {
CheckScanfHandler H(S, FExpr, OrigFormatExpr, Type, firstDataArg,
numDataArgs, Str, APK, Args, format_idx, inFunctionCall,
@@ -8151,6 +8632,74 @@ static void CheckFormatString(
} // TODO: handle other formats
}
+bool Sema::CheckFormatStringsCompatible(
+ FormatStringType Type, const StringLiteral *AuthoritativeFormatString,
+ const StringLiteral *TestedFormatString, const Expr *FunctionCallArg) {
+ if (Type != Sema::FST_Printf && Type != Sema::FST_NSString &&
+ Type != Sema::FST_Kprintf && Type != Sema::FST_FreeBSDKPrintf &&
+ Type != Sema::FST_OSLog && Type != Sema::FST_OSTrace &&
+ Type != Sema::FST_Syslog)
+ return true;
+
+ bool IsObjC = Type == Sema::FST_NSString || Type == Sema::FST_OSTrace;
+ llvm::SmallVector<EquatableFormatArgument, 9> RefArgs, FmtArgs;
+ FormatStringLiteral RefLit = AuthoritativeFormatString;
+ FormatStringLiteral TestLit = TestedFormatString;
+ const Expr *Arg;
+ bool DiagAtStringLiteral;
+ if (FunctionCallArg) {
+ Arg = FunctionCallArg;
+ DiagAtStringLiteral = false;
+ } else {
+ Arg = TestedFormatString;
+ DiagAtStringLiteral = true;
+ }
+ if (DecomposePrintfHandler::GetSpecifiers(*this, &RefLit,
+ AuthoritativeFormatString, Type,
+ IsObjC, true, RefArgs) &&
+ DecomposePrintfHandler::GetSpecifiers(*this, &TestLit, Arg, Type, IsObjC,
+ DiagAtStringLiteral, FmtArgs)) {
+ return CompareFormatSpecifiers(*this, AuthoritativeFormatString, RefArgs,
+ TestedFormatString, FmtArgs, Arg,
+ DiagAtStringLiteral);
+ }
+ return false;
+}
+
+bool Sema::ValidateFormatString(FormatStringType Type,
+ const StringLiteral *Str) {
+ if (Type != Sema::FST_Printf && Type != Sema::FST_NSString &&
+ Type != Sema::FST_Kprintf && Type != Sema::FST_FreeBSDKPrintf &&
+ Type != Sema::FST_OSLog && Type != Sema::FST_OSTrace &&
+ Type != Sema::FST_Syslog)
+ return true;
+
+ FormatStringLiteral RefLit = Str;
+ llvm::SmallVector<EquatableFormatArgument, 9> Args;
+ bool IsObjC = Type == Sema::FST_NSString || Type == Sema::FST_OSTrace;
+ if (!DecomposePrintfHandler::GetSpecifiers(*this, &RefLit, Str, Type, IsObjC,
+ true, Args))
+ return false;
+
+ // Group arguments by getPosition() value, and check that each member of the
+ // group is compatible with the first member. This verifies that when
+ // positional arguments are used multiple times (such as %2$i %2$d), all uses
+ // are mutually compatible. As an optimization, don't test the first member
+ // against itself.
+ bool HadError = false;
+ auto Iter = Args.begin();
+ auto End = Args.end();
+ while (Iter != End) {
+ const auto &FirstInGroup = *Iter;
+ for (++Iter;
+ Iter != End && Iter->getPosition() == FirstInGroup.getPosition();
+ ++Iter) {
+ HadError |= !Iter->VerifyCompatible(*this, FirstInGroup, Str, true);
+ }
+ }
+ return !HadError;
+}
+
bool Sema::FormatStringHasSArg(const StringLiteral *FExpr) {
// Str - The format string. NOTE: this is NOT null-terminated!
StringRef StrRef = FExpr->getString();
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 4b5351410f459..285bd27a35a76 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2818,6 +2818,9 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D,
else if (const auto *FA = dyn_cast<FormatAttr>(Attr))
NewAttr = S.mergeFormatAttr(D, *FA, FA->getType(), FA->getFormatIdx(),
FA->getFirstArg());
+ else if (const auto *FMA = dyn_cast<FormatMatchesAttr>(Attr))
+ NewAttr = S.mergeFormatMatchesAttr(
+ D, *FMA, FMA->getType(), FMA->getFormatIdx(), FMA->getFormatString());
else if (const auto *SA = dyn_cast<SectionAttr>(Attr))
NewAttr = S.mergeSectionAttr(D, *SA, SA->getName());
else if (const auto *CSA = dyn_cast<CodeSegAttr>(Attr))
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 620290af9509f..fdf3b9c636127 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3701,61 +3701,95 @@ FormatAttr *Sema::mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI,
return ::new (Context) FormatAttr(Context, CI, Format, FormatIdx, FirstArg);
}
+FormatMatchesAttr *Sema::mergeFormatMatchesAttr(Decl *D,
+ const AttributeCommonInfo &CI,
+ IdentifierInfo *Format,
+ int FormatIdx,
+ StringLiteral *FormatStr) {
+ // Check whether we already have an equivalent FormatMatches attribute.
+ for (auto *F : D->specific_attrs<FormatMatchesAttr>()) {
+ if (F->getType() == Format && F->getFormatIdx() == FormatIdx) {
+ if (!CheckFormatStringsCompatible(GetFormatStringType(Format->getName()),
+ F->getFormatString(), FormatStr))
+ return nullptr;
+
+ // If we don't have a valid location for this attribute, adopt the
+ // location.
+ if (F->getLocation().isInvalid())
+ F->setRange(CI.getRange());
+ return nullptr;
+ }
+ }
+
+ return ::new (Context)
+ FormatMatchesAttr(Context, CI, Format, FormatIdx, FormatStr);
+}
+
+struct FormatAttrCommon {
+ FormatAttrKind Kind;
+ IdentifierInfo *Identifier;
+ unsigned NumArgs;
+ unsigned FormatStringIdx;
+};
+
/// Handle __attribute__((format(type,idx,firstarg))) attributes based on
/// http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
-static void handleFormatAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+static bool handleFormatAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
+ FormatAttrCommon *Info) {
+ // Checks the first two arguments of the attribute; this is shared between
+ // Format and FormatMatches attributes.
+
if (!AL.isArgIdent(0)) {
S.Diag(AL.getLoc(), diag::err_attribute_argument_n_type)
<< AL << 1 << AANT_ArgumentIdentifier;
- return;
+ return false;
}
// In C++ the implicit 'this' function parameter also counts, and they are
// counted from one.
bool HasImplicitThisParam = isInstanceMethod(D);
- unsigned NumArgs = getFunctionOrMethodNumParams(D) + HasImplicitThisParam;
+ Info->NumArgs = getFunctionOrMethodNumParams(D) + HasImplicitThisParam;
- IdentifierInfo *II = AL.getArgAsIdent(0)->Ident;
- StringRef Format = II->getName();
+ Info->Identifier = AL.getArgAsIdent(0)->Ident;
+ StringRef Format = Info->Identifier->getName();
if (normalizeName(Format)) {
// If we've modified the string name, we need a new identifier for it.
- II = &S.Context.Idents.get(Format);
+ Info->Identifier = &S.Context.Idents.get(Format);
}
// Check for supported formats.
- FormatAttrKind Kind = getFormatAttrKind(Format);
+ Info->Kind = getFormatAttrKind(Format);
- if (Kind == IgnoredFormat)
- return;
+ if (Info->Kind == IgnoredFormat)
+ return false;
- if (Kind == InvalidFormat) {
+ if (Info->Kind == InvalidFormat) {
S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported)
- << AL << II->getName();
- return;
+ << AL << Info->Identifier->getName();
+ return false;
}
// checks for the 2nd argument
Expr *IdxExpr = AL.getArgAsExpr(1);
- uint32_t Idx;
- if (!S.checkUInt32Argument(AL, IdxExpr, Idx, 2))
- return;
+ if (!S.checkUInt32Argument(AL, IdxExpr, Info->FormatStringIdx, 2))
+ return false;
- if (Idx < 1 || Idx > NumArgs) {
+ if (Info->FormatStringIdx < 1 || Info->FormatStringIdx > Info->NumArgs) {
S.Diag(AL.getLoc(), diag::err_attribute_argument_out_of_bounds)
<< AL << 2 << IdxExpr->getSourceRange();
- return;
+ return false;
}
// FIXME: Do we need to bounds check?
- unsigned ArgIdx = Idx - 1;
+ unsigned ArgIdx = Info->FormatStringIdx - 1;
if (HasImplicitThisParam) {
if (ArgIdx == 0) {
S.Diag(AL.getLoc(),
diag::err_format_attribute_implicit_this_format_string)
- << IdxExpr->getSourceRange();
- return;
+ << IdxExpr->getSourceRange();
+ return false;
}
ArgIdx--;
}
@@ -3767,10 +3801,19 @@ static void handleFormatAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
(!Ty->isPointerType() ||
!Ty->castAs<PointerType>()->getPointeeType()->isCharType())) {
S.Diag(AL.getLoc(), diag::err_format_attribute_not)
- << IdxExpr->getSourceRange() << getFunctionOrMethodParamRange(D, ArgIdx);
- return;
+ << IdxExpr->getSourceRange()
+ << getFunctionOrMethodParamRange(D, ArgIdx);
+ return false;
}
+ return true;
+}
+
+static void handleFormatAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ FormatAttrCommon Info;
+ if (!handleFormatAttrCommon(S, D, AL, &Info))
+ return;
+
// check the 3rd argument
Expr *FirstArgExpr = AL.getArgAsExpr(2);
uint32_t FirstArg;
@@ -3779,7 +3822,7 @@ static void handleFormatAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// FirstArg == 0 is is always valid.
if (FirstArg != 0) {
- if (Kind == StrftimeFormat) {
+ if (Info.Kind == StrftimeFormat) {
// If the kind is strftime, FirstArg must be 0 because strftime does not
// use any variadic arguments.
S.Diag(AL.getLoc(), diag::err_format_strftime_third_parameter)
@@ -3790,17 +3833,17 @@ static void handleFormatAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// Else, if the function is variadic, then FirstArg must be 0 or the
// "position" of the ... parameter. It's unusual to use 0 with variadic
// functions, so the fixit proposes the latter.
- if (FirstArg != NumArgs + 1) {
+ if (FirstArg != Info.NumArgs + 1) {
S.Diag(AL.getLoc(), diag::err_attribute_argument_out_of_bounds)
<< AL << 3 << FirstArgExpr->getSourceRange()
<< FixItHint::CreateReplacement(FirstArgExpr->getSourceRange(),
- std::to_string(NumArgs + 1));
+ std::to_string(Info.NumArgs + 1));
return;
}
} else {
// Inescapable GCC compatibility diagnostic.
S.Diag(D->getLocation(), diag::warn_gcc_requires_variadic_function) << AL;
- if (FirstArg <= Idx) {
+ if (FirstArg <= Info.FormatStringIdx) {
// Else, the function is not variadic, and FirstArg must be 0 or any
// parameter after the format parameter. We don't offer a fixit because
// there are too many possible good values.
@@ -3811,11 +3854,33 @@ static void handleFormatAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
}
}
- FormatAttr *NewAttr = S.mergeFormatAttr(D, AL, II, Idx, FirstArg);
+ FormatAttr *NewAttr =
+ S.mergeFormatAttr(D, AL, Info.Identifier, Info.FormatStringIdx, FirstArg);
if (NewAttr)
D->addAttr(NewAttr);
}
+static void handleFormatMatchesAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ FormatAttrCommon Info;
+ if (!handleFormatAttrCommon(S, D, AL, &Info))
+ return;
+
+ Expr *FormatStrExpr = AL.getArgAsExpr(2)->IgnoreParenImpCasts();
+ if (auto *SL = dyn_cast<StringLiteral>(FormatStrExpr)) {
+ Sema::FormatStringType FST =
+ S.GetFormatStringType(Info.Identifier->getName());
+ if (S.ValidateFormatString(FST, SL))
+ if (auto *NewAttr = S.mergeFormatMatchesAttr(D, AL, Info.Identifier,
+ Info.FormatStringIdx, SL))
+ D->addAttr(NewAttr);
+ return;
+ }
+
+ S.Diag(AL.getLoc(), diag::err_format_nonliteral)
+ << FormatStrExpr->getSourceRange();
+ return;
+}
+
/// Handle __attribute__((callback(CalleeIdx, PayloadIdx0, ...))) attributes.
static void handleCallbackAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// The index that identifies the callback callee is mandatory.
@@ -6850,6 +6915,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_Format:
handleFormatAttr(S, D, AL);
break;
+ case ParsedAttr::AT_FormatMatches:
+ handleFormatMatchesAttr(S, D, AL);
+ break;
case ParsedAttr::AT_FormatArg:
handleFormatArgAttr(S, D, AL);
break;
diff --git a/clang/lib/Sema/SemaObjC.cpp b/clang/lib/Sema/SemaObjC.cpp
index 57eda18c2d8e1..073d9791d037b 100644
--- a/clang/lib/Sema/SemaObjC.cpp
+++ b/clang/lib/Sema/SemaObjC.cpp
@@ -2259,7 +2259,8 @@ void SemaObjC::handleExternallyRetainedAttr(Decl *D, const ParsedAttr &AL) {
bool SemaObjC::GetFormatNSStringIdx(const FormatAttr *Format, unsigned &Idx) {
Sema::FormatStringInfo FSI;
if ((SemaRef.GetFormatStringType(Format) == Sema::FST_NSString) &&
- SemaRef.getFormatStringInfo(Format, false, true, &FSI)) {
+ SemaRef.getFormatStringInfo(Format->getFormatIdx(), Format->getFirstArg(),
+ false, true, &FSI)) {
Idx = FSI.FormatIdx;
return true;
}
diff --git a/clang/test/Sema/format-string-matches.c b/clang/test/Sema/format-string-matches.c
new file mode 100644
index 0000000000000..9a6e591659589
--- /dev/null
+++ b/clang/test/Sema/format-string-matches.c
@@ -0,0 +1,273 @@
+// RUN: %clang_cc1 -verify -fblocks -fsyntax-only -Wformat-nonliteral -Wformat-signedness -isystem %S/Inputs %s
+// RUN: %clang_cc1 -verify -fblocks -fsyntax-only -Wformat-nonliteral -Wformat-signedness -isystem %S/Inputs -fno-signed-char %s
+
+#include <stdarg.h>
+
+__attribute__((format_matches(printf, -1, "%s"))) // expected-error{{'format_matches' attribute parameter 2 is out of bounds}}
+int test_out_of_bounds(void);
+
+__attribute__((format_matches(printf, 0, "%s"))) // expected-error{{'format_matches' attribute parameter 2 is out of bounds}}
+int test_out_of_bounds(void);
+
+__attribute__((format_matches(printf, 1, "%s"))) // expected-error{{'format_matches' attribute parameter 2 is out of bounds}}
+int test_out_of_bounds(void);
+
+__attribute__((format_matches(printf, 1, "%s"))) // expected-error{{format argument not a string type}}
+int test_out_of_bounds_int(int x);
+
+// MARK: -
+// Calling printf with a format from format_matches(printf) diagnoses with
+// that format string
+__attribute__((format(printf, 1, 2)))
+int printf(const char *fmt, ...);
+
+__attribute__((format(printf, 1, 0)))
+int vprintf(const char *fmt, va_list);
+
+__attribute__((format_matches(printf, 1, "%s %1.5s")))
+void format_str_str0(const char *fmt) {
+ printf(fmt, "hello", "world");
+}
+
+__attribute__((format_matches(printf, 1, "%s" "%1.5s")))
+void format_str_str1(const char *fmt) {
+ printf(fmt, "hello", "world");
+}
+
+__attribute__((format_matches(printf, 1, ("%s" "%1.5s") + 5))) // expected-error{{format string is not a string literal}}
+void format_str_str2(const char *fmt);
+
+__attribute__((format_matches(printf, 1, "%s %g"))) // expected-note{{format string is defined here}}
+void format_str_double_warn(const char *fmt) {
+ printf(fmt, "hello", "world"); // expected-warning{{format specifies type 'double' but the argument has type 'char *'}}
+}
+
+__attribute__((format_matches(printf, 1, "%s %g")))
+void vformat(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ vprintf(fmt, ap); // XXX: ideally this would be a diagnostic
+ va_end(ap);
+}
+
+// MARK: -
+// Calling a function with format_matches diagnoses for incompatible formats.
+
+void cvt_percent(const char *c) __attribute__((format_matches(printf, 1, "%%"))); // expected-note 2{{comparing with this format string}}
+void cvt_at(const char *c) __attribute__((format_matches(NSString, 1, "%@"))); // \
+ expected-note{{comparing with this specifier}} \
+ expected-note 3{{comparing with this format string}}
+void cvt_c(const char *c) __attribute__((format_matches(printf, 1, "%c"))); // expected-note{{comparing with this specifier}}
+void cvt_u(const char *c) __attribute__((format_matches(printf, 1, "%u"))); // expected-note 2{{comparing with this specifier}}
+void cvt_hhi(const char *c) __attribute__((format_matches(printf, 1, "%hhi"))); // expected-note 3{{comparing with this specifier}}
+void cvt_i(const char *c) __attribute__((format_matches(printf, 1, "%i"))); // expected-note 5{{comparing with this specifier}}
+void cvt_p(const char *c) __attribute__((format_matches(printf, 1, "%p")));
+void cvt_s(const char *c) __attribute__((format_matches(printf, 1, "%s"))); // expected-note{{comparing with this specifier}}
+void cvt_n(const char *c) __attribute__((format_matches(printf, 1, "%n"))); // expected-note{{comparing with this specifier}}
+
+void test_compatibility(void) {
+ cvt_c("%i");
+ const char *const fmt_i = "%i";
+ cvt_c(fmt_i);
+
+ cvt_i("%c");
+ cvt_c("%u"); // expected-warning{{signedness of format specifier 'u' is incompatible with 'c'}}
+ cvt_u("%c"); // expected-warning{{signedness of format specifier 'c' is incompatible with 'u'}}
+
+ const char *const fmt_c = "%c"; // expected-note{{format string is defined here}}
+ cvt_u(fmt_c); // expected-warning{{signedness of format specifier 'c' is incompatible with 'u'}}
+
+ cvt_i("%hi"); // expected-warning{{format specifier 'hi' is incompatible with 'i'}}
+ cvt_i("%hhi"); // expected-warning{{format specifier 'hhi' is incompatible with 'i'}}
+ cvt_i("%lli"); // expected-warning{{format specifier 'lli' is incompatible with 'i'}}
+ cvt_i("%p"); // expected-warning{{format specifier 'p' is incompatible with 'i'}}
+ cvt_hhi("%hhi");
+ cvt_hhi("%hi"); // expected-warning{{format specifier 'hi' is incompatible with 'hhi'}}
+ cvt_hhi("%i"); // expected-warning{{format specifier 'i' is incompatible with 'hhi'}}
+ cvt_hhi("%li"); // expected-warning{{format specifier 'li' is incompatible with 'hhi'}}
+ cvt_n("%s"); // expected-warning{{format specifier 's' is incompatible with 'n'}}
+ cvt_s("%hhn"); // expected-warning{{format specifier 'hhn' is incompatible with 's'}}
+
+ cvt_p("%@"); // expected-warning{{invalid conversion specifier '@'}}
+ cvt_at("%p"); // expected-warning{{format specifier 'p' is incompatible with '@'}}
+
+ cvt_percent("hello");
+ cvt_percent("%c"); // expected-warning{{more specifiers in format string than expected}}
+
+ const char *const too_many = "%c"; // expected-note{{format string is defined here}}
+ cvt_percent(too_many); // expected-warning{{more specifiers in format string than expected}}
+}
+
+void test_too_few_args(void) {
+ cvt_at("a"); // expected-warning{{fewer specifiers in format string than expected}}
+ cvt_at("%@ %@"); // expected-warning{{more specifiers in format string than expected}}
+
+ const char *const too_few = "a"; // expected-note{{format string is defined here}}
+ cvt_at(too_few); // expected-warning{{fewer specifiers in format string than expected}}
+}
+
+void cvt_several(const char *c) __attribute__((format_matches(printf, 1, "%f %i %s"))); // expected-note{{comparing with this specifier}}
+
+void test_moving_args_around(void) {
+ cvt_several("%1g %-d %1.5s");
+
+ cvt_several("%3$s %1$g %2$i");
+
+ cvt_several("%f %*s"); // expected-warning{{format argument is an indirect field width, but it should be a value}}
+}
+
+void cvt_freebsd_D(const char *c) __attribute__((format_matches(freebsd_kprintf, 1, "%D"))); // expected-note{{comparing with this specifier}}
+
+void test_freebsd_specifiers(void) {
+ cvt_freebsd_D("%D");
+ cvt_freebsd_D("%b");
+ cvt_freebsd_D("%s %i"); // expected-warning{{format argument is a value, but it should be an auxiliary value}}
+}
+
+// passing the wrong kind of string literal
+void takes_printf_string(const char *fmt) __attribute__((format_matches(printf, 1, "%s")));
+__attribute__((format_matches(freebsd_kprintf, 1, "%s"))) // expected-note{{format string is defined here}}
+void takes_freebsd_kprintf_string(const char *fmt) {
+ takes_printf_string(fmt); // expected-warning{{passing 'freebsd_kprintf' format string where 'printf' format string is expected}}
+
+ const char *const fmt2 = fmt;
+ takes_printf_string(fmt2); // expected-warning{{passing 'freebsd_kprintf' format string where 'printf' format string is expected}}
+}
+
+__attribute__((format_matches(printf, 1, "%s"))) // expected-note{{comparing with this specifier}}
+__attribute__((format_matches(os_log, 2, "%i"))) // expected-note{{comparing with this specifier}}
+void test_recv_multiple_format_strings(const char *fmt1, const char *fmt2);
+
+__attribute__((format_matches(printf, 1, "%s")))
+__attribute__((format_matches(os_log, 2, "%i")))
+void test_multiple_format_strings(const char *fmt1, const char *fmt2) {
+ test_recv_multiple_format_strings("%s", "%i");
+ test_recv_multiple_format_strings("%s", "%s"); // expected-warning{{format specifier 's' is incompatible with 'i'}}
+ test_recv_multiple_format_strings("%i", "%i"); // expected-warning{{format specifier 'i' is incompatible with 's'}}
+
+ test_recv_multiple_format_strings(fmt1, fmt2);
+ test_recv_multiple_format_strings("%.5s", fmt2);
+ test_recv_multiple_format_strings(fmt1, "%04d");
+
+ test_recv_multiple_format_strings("%s", fmt1); // expected-warning{{passing 'printf' format string where 'os_log' format string is expected}}
+ test_recv_multiple_format_strings(fmt2, "%d"); // expected-warning{{passing 'os_log' format string where 'printf' format string is expected}}
+
+ test_recv_multiple_format_strings(fmt2, fmt1); // \
+ expected-warning{{passing 'printf' format string where 'os_log' format string is expected}} \
+ expected-warning{{passing 'os_log' format string where 'printf' format string is expected}}
+}
+
+__attribute__((format_matches(os_log, 1, "%{public}s"))) // expected-note 4{{comparing with this specifier}}
+void call_oslog_public(const char *fmt);
+
+__attribute__((format_matches(os_log, 1, "%{sensitive}s"))) // expected-note 2{{comparing with this specifier}}
+void call_oslog_sensitive(const char *fmt);
+
+__attribute__((format_matches(os_log, 1, "%{private}s"))) // expected-note 2{{comparing with this specifier}}
+void call_oslog_private(const char *fmt);
+
+void test_oslog(void) {
+ call_oslog_public("%{public}s");
+ call_oslog_public("%{private}s"); // expected-warning{{argument sensitivity is private, but it should be public}}
+ call_oslog_public("%{sensitive}s"); // expected-warning{{argument sensitivity is sensitive, but it should be public}}
+
+ call_oslog_sensitive("%{public}s"); // expected-warning{{argument sensitivity is public, but it should be sensitive}}
+ call_oslog_sensitive("%{private}s"); // expected-warning{{argument sensitivity is private, but it should be sensitive}}
+ call_oslog_sensitive("%{sensitive}s");
+
+ call_oslog_private("%{public}s"); // expected-warning{{argument sensitivity is public, but it should be private}}
+ call_oslog_private("%{private}s");
+ call_oslog_private("%{sensitive}s"); // expected-warning{{argument sensitivity is sensitive, but it should be private}}
+
+ // expected-warning at +2{{argument sensitivity is private, but it should be public}}
+ // expected-warning at +1{{format specifier 'i' is incompatible with 's'}}
+ call_oslog_public("%{private}i");
+}
+
+// MARK: -
+void accept_value(const char *f) __attribute__((format_matches(freebsd_kprintf, 1, "%s%i%i"))); // \
+ expected-note 3{{comparing with this specifier}} \
+ expected-note 3{{format string is defined here}}
+void accept_indirect_field_width(const char *f) __attribute__((format_matches(freebsd_kprintf, 1, "%s%*i"))); // \
+ expected-note 3{{comparing with this specifier}} \
+ expected-note 3{{format string is defined here}}
+void accept_indirect_field_precision(const char *f) __attribute__((format_matches(freebsd_kprintf, 1, "%s%.*i"))); // \
+ expected-note 3{{comparing with this specifier}} \
+ expected-note 3{{format string is defined here}}
+void accept_aux_value(const char *f) __attribute__((format_matches(freebsd_kprintf, 1, "%D%i"))); // \
+ expected-note 3{{comparing with this specifier}} \
+ expected-note 3{{format string is defined here}}
+
+void accept_value(const char *f) {
+ accept_indirect_field_width(f); // expected-warning{{format argument is a value, but it should be an indirect field width}}
+ accept_indirect_field_precision(f); // expected-warning{{format argument is a value, but it should be an indirect precision}}
+ accept_aux_value(f); // expected-warning{{format argument is a value, but it should be an auxiliary value}}
+}
+
+void accept_indirect_field_width(const char *f) {
+ accept_value(f); // expected-warning{{format argument is an indirect field width, but it should be a value}}
+ accept_indirect_field_precision(f); // expected-warning{{format argument is an indirect field width, but it should be an indirect precision}}
+ accept_aux_value(f); // expected-warning{{format argument is an indirect field width, but it should be an auxiliary value}}
+}
+
+void accept_indirect_field_precision(const char *f) {
+ accept_value(f); // expected-warning{{format argument is an indirect precision, but it should be a value}}
+ accept_indirect_field_width(f); // expected-warning{{format argument is an indirect precision, but it should be an indirect field width}}
+ accept_aux_value(f); // expected-warning{{format argument is an indirect precision, but it should be an auxiliary value}}
+}
+
+void accept_aux_value(const char *f) {
+ accept_value(f); // expected-warning{{format argument is an auxiliary value, but it should be a value}}
+ accept_indirect_field_width(f); // expected-warning{{format argument is an auxiliary value, but it should be an indirect field width}}
+ accept_indirect_field_precision(f); // expected-warning{{format argument is an auxiliary value, but it should be an indirect precision}}
+}
+
+// MARK: - Merging format attributes
+__attribute__((format_matches(printf, 1, "%i")))
+__attribute__((format_matches(printf, 1, "%d")))
+void test_merge_self(const char *f);
+
+__attribute__((format_matches(printf, 1, "%i"))) // expected-note{{comparing with this specifier}}
+__attribute__((format_matches(printf, 1, "%s"))) // expected-warning{{format specifier 's' is incompatible with 'i'}}
+void test_merge_self_warn(const char *f);
+
+__attribute__((format_matches(printf, 1, "%i")))
+void test_merge_redecl(const char *f);
+
+__attribute__((format_matches(printf, 1, "%d")))
+void test_merge_redecl(const char *f);
+
+// XXX: ideally the warning and note would be swapped, but this is entirely
+// reliant on which decl clang considers to be the "true one", and it might
+// upset something else more important if we tried to change it.
+__attribute__((format_matches(printf, 1, "%i"))) // expected-warning{{format specifier 'i' is incompatible with 's'}}
+void test_merge_redecl_warn(const char *f);
+
+__attribute__((format_matches(printf, 1, "%s"))) // expected-note{{comparing with this specifier}}
+void test_merge_redecl_warn(const char *f);
+
+// MARK: -
+// Positional madness
+
+__attribute__((format_matches(printf, 1, "%1$s %1$d"))) // \
+ expected-warning{{format specifier 'd' is incompatible with 's'}} \
+ expected-note{{comparing with this specifier}}
+void test_positional_incompatible(const char *f);
+
+void call_positional_incompatible(void) {
+ // expect the attribute was dropped and that there is no diagnostic here
+ test_positional_incompatible("%d %d %d %d %d");
+}
+
+void test_many_i(void) {
+ cvt_i("%1$d %1$i");
+ cvt_i("%1$d %1$s"); // expected-warning{{format specifier 's' is incompatible with 'i'}}
+}
+
+__attribute__((format_matches(printf, 1, "%*d %*d"))) // expected-note{{comparing with this specifier}}
+void accept_modifiers(const char *f);
+
+void test_modifiers(void) {
+ accept_modifiers("%2$*1$d %4$*3$d");
+ 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}}
+}
diff --git a/clang/test/Sema/format-strings.c b/clang/test/Sema/format-strings.c
index 04bad1f13c8cd..efd88d5c18e66 100644
--- a/clang/test/Sema/format-strings.c
+++ b/clang/test/Sema/format-strings.c
@@ -94,7 +94,7 @@ void check_string_literal2( FILE* fp, const char* s, char *buf, ... ) {
// expected-note at -1{{treat the string as an argument to avoid this}}
__builtin___vsnprintf_chk(buf,2,0,-1,s,ap); // no-warning
- vscanf(s, ap); // expected-warning {{format string is not a string literal}}
+ vscanf(s, ap); // expected-warning {{passing 'printf' format string where 'scanf' format string is expected}}
}
void check_conditional_literal(const char* s, int i) {
More information about the cfe-commits
mailing list