namespace clang {
+class TargetInfo;
+
//===----------------------------------------------------------------------===//
/// Common components of both fprintf and fscanf format strings.
namespace analyze_format_string {
bool usesPositionalArg() const { return UsesPositionalArg; }
- bool hasValidLengthModifier() const;
+ bool hasValidLengthModifier(const TargetInfo &Target) const;
bool hasStandardLengthModifier() const;
+ llvm::Optional<LengthModifier> getCorrectedLengthModifier() const;
+
bool hasStandardConversionSpecifier(const LangOptions &LangOpt) const;
bool hasStandardLengthConversionCombination() const;
"no closing ']' for '%%[' in scanf format string">,
InGroup<Format>;
def note_format_string_defined : Note<"format string is defined here">;
+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 warn_null_arg : Warning<
#include "FormatStringParsing.h"
#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/TargetInfo.h"
using clang::analyze_format_string::ArgType;
using clang::analyze_format_string::FormatStringHandler;
}
}
-bool FormatSpecifier::hasValidLengthModifier() const {
+bool FormatSpecifier::hasValidLengthModifier(const TargetInfo &Target) const {
switch (LM.getKind()) {
case LengthModifier::None:
return true;
case ConversionSpecifier::gArg:
case ConversionSpecifier::GArg:
return true;
- // GNU extension.
+ // GNU libc extension.
case ConversionSpecifier::dArg:
case ConversionSpecifier::iArg:
case ConversionSpecifier::oArg:
case ConversionSpecifier::uArg:
case ConversionSpecifier::xArg:
case ConversionSpecifier::XArg:
- return true;
+ return !Target.getTriple().isOSDarwin() &&
+ !Target.getTriple().isOSWindows();
default:
return false;
}
return true;
}
+llvm::Optional<LengthModifier>
+FormatSpecifier::getCorrectedLengthModifier() const {
+ if (LM.getKind() == LengthModifier::AsLongDouble) {
+ switch (CS.getKind()) {
+ case ConversionSpecifier::dArg:
+ case ConversionSpecifier::iArg:
+ case ConversionSpecifier::oArg:
+ case ConversionSpecifier::uArg:
+ case ConversionSpecifier::xArg:
+ case ConversionSpecifier::XArg: {
+ LengthModifier FixedLM(LM);
+ FixedLM.setKind(LengthModifier::AsLongLong);
+ return FixedLM;
+ }
+ default:
+ break;
+ }
+ }
+
+ return llvm::Optional<LengthModifier>();
+}
+
bool FormatSpecifier::namedTypeToLengthModifier(QualType QT,
LengthModifier &LM) {
assert(isa<TypedefType>(QT) && "Expected a TypedefType");
namedTypeToLengthModifier(QT, LM);
// If fixing the length modifier was enough, we are done.
- const analyze_printf::ArgType &ATR = getArgType(Ctx, IsObjCLiteral);
- if (hasValidLengthModifier() && ATR.isValid() && ATR.matchesType(Ctx, QT))
- return true;
+ if (hasValidLengthModifier(Ctx.getTargetInfo())) {
+ const analyze_printf::ArgType &ATR = getArgType(Ctx, IsObjCLiteral);
+ if (ATR.isValid() && ATR.matchesType(Ctx, QT))
+ return true;
+ }
// Set conversion specifier and disable any flags which do not apply to it.
// Let typedefs to char fall through to int, as %c is silly for uint8_t.
namedTypeToLengthModifier(PT, LM);
// If fixing the length modifier was enough, we are done.
- const analyze_scanf::ArgType &AT = getArgType(Ctx);
- if (hasValidLengthModifier() && AT.isValid() && AT.matchesType(Ctx, QT))
- return true;
+ if (hasValidLengthModifier(Ctx.getTargetInfo())) {
+ const analyze_scanf::ArgType &AT = getArgType(Ctx);
+ if (AT.isValid() && AT.matchesType(Ctx, QT))
+ return true;
+ }
// Figure out the conversion specifier.
if (PT->isRealFloatingType())
void HandleIncompleteSpecifier(const char *startSpecifier,
unsigned specifierLen);
+ void HandleInvalidLengthModifier(
+ const analyze_format_string::FormatSpecifier &FS,
+ const analyze_format_string::ConversionSpecifier &CS,
+ const char *startSpecifier, unsigned specifierLen);
+
void HandleNonStandardLengthModifier(
const analyze_format_string::LengthModifier &LM,
const char *startSpecifier, unsigned specifierLen);
getSpecifierRange(startSpecifier, specifierLen));
}
+void CheckFormatHandler::HandleInvalidLengthModifier(
+ const analyze_format_string::FormatSpecifier &FS,
+ const analyze_format_string::ConversionSpecifier &CS,
+ const char *startSpecifier, unsigned specifierLen) {
+ using namespace analyze_format_string;
+
+ const LengthModifier &LM = FS.getLengthModifier();
+ CharSourceRange LMRange = getSpecifierRange(LM.getStart(), LM.getLength());
+
+ // See if we know how to fix this length modifier.
+ llvm::Optional<LengthModifier> FixedLM = FS.getCorrectedLengthModifier();
+ if (FixedLM) {
+ EmitFormatDiagnostic(S.PDiag(diag::warn_format_nonsensical_length)
+ << LM.toString() << CS.toString(),
+ getLocationOfByte(LM.getStart()),
+ /*IsStringLocation*/true,
+ getSpecifierRange(startSpecifier, specifierLen));
+
+ S.Diag(getLocationOfByte(LM.getStart()), diag::note_format_fix_specifier)
+ << FixedLM->toString()
+ << FixItHint::CreateReplacement(LMRange, FixedLM->toString());
+
+ } else {
+ EmitFormatDiagnostic(S.PDiag(diag::warn_format_nonsensical_length)
+ << LM.toString() << CS.toString(),
+ getLocationOfByte(LM.getStart()),
+ /*IsStringLocation*/true,
+ getSpecifierRange(startSpecifier, specifierLen),
+ FixItHint::CreateRemoval(LMRange));
+ }
+}
+
void CheckFormatHandler::HandleNonStandardLengthModifier(
const analyze_format_string::LengthModifier &LM,
const char *startSpecifier, unsigned specifierLen) {
// Check the length modifier is valid with the given conversion specifier.
const LengthModifier &LM = FS.getLengthModifier();
- if (!FS.hasValidLengthModifier())
- EmitFormatDiagnostic(S.PDiag(diag::warn_format_nonsensical_length)
- << LM.toString() << CS.toString(),
- getLocationOfByte(LM.getStart()),
- /*IsStringLocation*/true,
- getSpecifierRange(startSpecifier, specifierLen),
- FixItHint::CreateRemoval(
- getSpecifierRange(LM.getStart(),
- LM.getLength())));
- if (!FS.hasStandardLengthModifier())
+ if (!FS.hasValidLengthModifier(S.getASTContext().getTargetInfo()))
+ HandleInvalidLengthModifier(FS, CS, startSpecifier, specifierLen);
+ else if (!FS.hasStandardLengthModifier())
HandleNonStandardLengthModifier(LM, startSpecifier, specifierLen);
- if (!FS.hasStandardConversionSpecifier(S.getLangOpts()))
- HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen);
- if (!FS.hasStandardLengthConversionCombination())
+ else if (!FS.hasStandardLengthConversionCombination())
HandleNonStandardConversionSpecification(LM, CS, startSpecifier,
specifierLen);
+ if (!FS.hasStandardConversionSpecifier(S.getLangOpts()))
+ HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen);
+
// The remaining checks depend on the data arguments.
if (HasVAListArg)
return true;
// Check the length modifier is valid with the given conversion specifier.
const LengthModifier &LM = FS.getLengthModifier();
- if (!FS.hasValidLengthModifier()) {
- const CharSourceRange &R = getSpecifierRange(LM.getStart(), LM.getLength());
- EmitFormatDiagnostic(S.PDiag(diag::warn_format_nonsensical_length)
- << LM.toString() << CS.toString()
- << getSpecifierRange(startSpecifier, specifierLen),
- getLocationOfByte(LM.getStart()),
- /*IsStringLocation*/true, R,
- FixItHint::CreateRemoval(R));
- }
-
- if (!FS.hasStandardLengthModifier())
+ if (!FS.hasValidLengthModifier(S.getASTContext().getTargetInfo()))
+ HandleInvalidLengthModifier(FS, CS, startSpecifier, specifierLen);
+ else if (!FS.hasStandardLengthModifier())
HandleNonStandardLengthModifier(LM, startSpecifier, specifierLen);
- if (!FS.hasStandardConversionSpecifier(S.getLangOpts()))
- HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen);
- if (!FS.hasStandardLengthConversionCombination())
+ else if (!FS.hasStandardLengthConversionCombination())
HandleNonStandardConversionSpecification(LM, CS, startSpecifier,
specifierLen);
+ if (!FS.hasStandardConversionSpecifier(S.getLangOpts()))
+ HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen);
+
// The remaining checks depend on the data arguments.
if (HasVAListArg)
return true;
--- /dev/null
+// RUN: %clang_cc1 -fsyntax-only -verify -triple i386-apple-darwin9 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple thumbv6-apple-ios4.0 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64-mingw32 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple i686-pc-win32 %s
+
+// RUN: %clang_cc1 -fsyntax-only -verify -triple i686-linux-gnu -DALLOWED %s
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64-unknown-freebsd -DALLOWED %s
+
+int printf(const char *restrict, ...);
+int scanf(const char * restrict, ...) ;
+
+void test() {
+ long notLongEnough = 1;
+ long long quiteLong = 2;
+
+ printf("%Ld", notLongEnough); // expected-warning {{format specifies type 'long long' but the argument has type 'long'}}
+ printf("%Ld", quiteLong);
+
+#ifndef ALLOWED
+ // expected-warning@-4 {{length modifier 'L' results in undefined behavior or no effect with 'd' conversion specifier}}
+ // expected-note@-5 {{did you mean to use 'll'?}}
+
+ // expected-warning@-6 {{length modifier 'L' results in undefined behavior or no effect with 'd' conversion specifier}}
+ // expected-note@-7 {{did you mean to use 'll'?}}
+#endif
+}
+
+void testAlwaysInvalid() {
+ // We should not suggest 'll' here!
+ printf("%Lc", 'a'); // expected-warning {{length modifier 'L' results in undefined behavior or no effect with 'c' conversion specifier}}
+ printf("%Ls", "a"); // expected-warning {{length modifier 'L' results in undefined behavior or no effect with 's' conversion specifier}}
+}
+
+#ifdef ALLOWED
+// PR 9466: clang: doesn't know about %Lu, %Ld, and %Lx
+void printf_longlong(long long x, unsigned long long y) {
+ printf("%Ld", y); // no-warning
+ printf("%Lu", y); // no-warning
+ printf("%Lx", y); // no-warning
+ printf("%Ld", x); // no-warning
+ printf("%Lu", x); // no-warning
+ printf("%Lx", x); // no-warning
+ printf("%Ls", "hello"); // expected-warning {{length modifier 'L' results in undefined behavior or no effect with 's' conversion specifier}}
+}
+
+void scanf_longlong(long long *x, unsigned long long *y) {
+ scanf("%Ld", y); // no-warning
+ scanf("%Lu", y); // no-warning
+ scanf("%Lx", y); // no-warning
+ scanf("%Ld", x); // no-warning
+ scanf("%Lu", x); // no-warning
+ scanf("%Lx", x); // no-warning
+ scanf("%Ls", "hello"); // expected-warning {{length modifier 'L' results in undefined behavior or no effect with 's' conversion specifier}}
+}
+#endif
-// RUN: %clang_cc1 -fsyntax-only -verify -std=c99 -pedantic %s
+// RUN: %clang_cc1 -triple i686-linux-gnu -fsyntax-only -verify -std=c99 -pedantic %s
int printf(const char *restrict, ...);
int scanf(const char * restrict, ...);
-// RUN: %clang_cc1 -fsyntax-only -verify -triple i386-apple-darwin9 -Wformat-nonliteral %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wformat-nonliteral %s
// Test that -Wformat=0 works:
// RUN: %clang_cc1 -fsyntax-only -Werror -Wformat=0 %s
scanf("%m[abc]", fp); // expected-warning{{format specifies type 'char **' but the argument has type 'float *'}}
}
-void test_longlong(long long *x, unsigned long long *y) {
- scanf("%Ld", y); // no-warning
- scanf("%Lu", y); // no-warning
- scanf("%Lx", y); // no-warning
- scanf("%Ld", x); // no-warning
- scanf("%Lu", x); // no-warning
- scanf("%Lx", x); // no-warning
- scanf("%Ls", "hello"); // expected-warning {{length modifier 'L' results in undefined behavior or no effect with 's' conversion specifier}}
-}
-
void test_quad(int *x, long long *llx) {
scanf("%qd", x); // expected-warning{{format specifies type 'long long *' but the argument has type 'int *'}}
scanf("%qd", llx); // no-warning
0.0); // expected-warning{{format specifies}}
}
-// PR 9466: clang: doesn't know about %Lu, %Ld, and %Lx
-void printf_longlong(long long x, unsigned long long y) {
- printf("%Ld", y); // no-warning
- printf("%Lu", y); // no-warning
- printf("%Lx", y); // no-warning
- printf("%Ld", x); // no-warning
- printf("%Lu", x); // no-warning
- printf("%Lx", x); // no-warning
- printf("%Ls", "hello"); // expected-warning {{length modifier 'L' results in undefined behavior or no effect with 's' conversion specifier}}
-}
-
void __attribute__((format(strfmon,1,2))) monformat(const char *fmt, ...);
void __attribute__((format(strftime,1,0))) dateformat(const char *fmt);