public:
enum Kind { UnknownTy, InvalidTy, SpecificTy, ObjCPointerTy, CPointerTy,
AnyCharTy, CStrTy, WCStrTy, WIntTy };
+
+ enum MatchKind { NoMatch = 0, Match = 1, NoMatchPedantic };
+
private:
const Kind K;
QualType T;
return Res;
}
- bool matchesType(ASTContext &C, QualType argTy) const;
+ MatchKind matchesType(ASTContext &C, QualType argTy) const;
QualType getRepresentativeType(ASTContext &C) const;
def FormatSecurity : DiagGroup<"format-security">;
def FormatNonStandard : DiagGroup<"format-non-iso">;
def FormatY2K : DiagGroup<"format-y2k">;
+def FormatPedantic : DiagGroup<"format-pedantic">;
def Format : DiagGroup<"format",
[FormatExtraArgs, FormatZeroLength, NonNull,
FormatSecurity, FormatY2K, FormatInvalidSpecifier]>,
"format specifies type %0 but the argument has "
"%select{type|underlying type}2 %1">,
InGroup<Format>;
+def warn_format_conversion_argument_type_mismatch_pedantic : Extension<
+ "format specifies type %0 but the argument has "
+ "%select{type|underlying type}2 %1">,
+ InGroup<FormatPedantic>;
def warn_format_argument_needs_cast : Warning<
"%select{values of type|enum values with underlying type}2 '%0' should not "
"be used as format arguments; add an explicit cast to %1 instead">,
// Methods on ArgType.
//===----------------------------------------------------------------------===//
-bool ArgType::matchesType(ASTContext &C, QualType argTy) const {
+clang::analyze_format_string::ArgType::MatchKind
+ArgType::matchesType(ASTContext &C, QualType argTy) const {
if (Ptr) {
// It has to be a pointer.
const PointerType *PT = argTy->getAs<PointerType>();
if (!PT)
- return false;
+ return NoMatch;
// We cannot write through a const qualified pointer.
if (PT->getPointeeType().isConstQualified())
- return false;
+ return NoMatch;
argTy = PT->getPointeeType();
}
llvm_unreachable("ArgType must be valid");
case UnknownTy:
- return true;
-
+ return Match;
+
case AnyCharTy: {
if (const EnumType *ETy = argTy->getAs<EnumType>())
argTy = ETy->getDecl()->getIntegerType();
case BuiltinType::SChar:
case BuiltinType::UChar:
case BuiltinType::Char_U:
- return true;
+ return Match;
}
- return false;
+ return NoMatch;
}
-
+
case SpecificTy: {
if (const EnumType *ETy = argTy->getAs<EnumType>())
argTy = ETy->getDecl()->getIntegerType();
argTy = C.getCanonicalType(argTy).getUnqualifiedType();
if (T == argTy)
- return true;
+ return Match;
// Check for "compatible types".
if (const BuiltinType *BT = argTy->getAs<BuiltinType>())
switch (BT->getKind()) {
case BuiltinType::Char_S:
case BuiltinType::SChar:
case BuiltinType::Char_U:
- case BuiltinType::UChar:
- return T == C.UnsignedCharTy || T == C.SignedCharTy;
+ case BuiltinType::UChar:
+ return T == C.UnsignedCharTy || T == C.SignedCharTy ? Match
+ : NoMatch;
case BuiltinType::Short:
- return T == C.UnsignedShortTy;
+ return T == C.UnsignedShortTy ? Match : NoMatch;
case BuiltinType::UShort:
- return T == C.ShortTy;
+ return T == C.ShortTy ? Match : NoMatch;
case BuiltinType::Int:
- return T == C.UnsignedIntTy;
+ return T == C.UnsignedIntTy ? Match : NoMatch;
case BuiltinType::UInt:
- return T == C.IntTy;
+ return T == C.IntTy ? Match : NoMatch;
case BuiltinType::Long:
- return T == C.UnsignedLongTy;
+ return T == C.UnsignedLongTy ? Match : NoMatch;
case BuiltinType::ULong:
- return T == C.LongTy;
+ return T == C.LongTy ? Match : NoMatch;
case BuiltinType::LongLong:
- return T == C.UnsignedLongLongTy;
+ return T == C.UnsignedLongLongTy ? Match : NoMatch;
case BuiltinType::ULongLong:
- return T == C.LongLongTy;
+ return T == C.LongLongTy ? Match : NoMatch;
}
- return false;
+ return NoMatch;
}
case CStrTy: {
const PointerType *PT = argTy->getAs<PointerType>();
if (!PT)
- return false;
+ return NoMatch;
QualType pointeeTy = PT->getPointeeType();
if (const BuiltinType *BT = pointeeTy->getAs<BuiltinType>())
switch (BT->getKind()) {
case BuiltinType::UChar:
case BuiltinType::Char_S:
case BuiltinType::SChar:
- return true;
+ return Match;
default:
break;
}
- return false;
+ return NoMatch;
}
case WCStrTy: {
const PointerType *PT = argTy->getAs<PointerType>();
if (!PT)
- return false;
+ return NoMatch;
QualType pointeeTy =
C.getCanonicalType(PT->getPointeeType()).getUnqualifiedType();
- return pointeeTy == C.getWideCharType();
+ return pointeeTy == C.getWideCharType() ? Match : NoMatch;
}
-
+
case WIntTy: {
-
+
QualType PromoArg =
argTy->isPromotableIntegerType()
? C.getPromotedIntegerType(argTy) : argTy;
-
+
QualType WInt = C.getCanonicalType(C.getWIntType()).getUnqualifiedType();
PromoArg = C.getCanonicalType(PromoArg).getUnqualifiedType();
-
+
// If the promoted argument is the corresponding signed type of the
// wint_t type, then it should match.
if (PromoArg->hasSignedIntegerRepresentation() &&
C.getCorrespondingUnsignedType(PromoArg) == WInt)
- return true;
+ return Match;
- return WInt == PromoArg;
+ return WInt == PromoArg ? Match : NoMatch;
}
case CPointerTy:
- return argTy->isPointerType() || argTy->isObjCObjectPointerType() ||
- argTy->isBlockPointerType() || argTy->isNullPtrType();
+ if (argTy->isVoidPointerType()) {
+ return Match;
+ } if (argTy->isPointerType() || argTy->isObjCObjectPointerType() ||
+ argTy->isBlockPointerType() || argTy->isNullPtrType()) {
+ return NoMatchPedantic;
+ } else {
+ return NoMatch;
+ }
case ObjCPointerTy: {
if (argTy->getAs<ObjCObjectPointerType>() ||
argTy->getAs<BlockPointerType>())
- return true;
-
+ return Match;
+
// Handle implicit toll-free bridging.
if (const PointerType *PT = argTy->getAs<PointerType>()) {
// Things such as CFTypeRef are really just opaque pointers
// structs can be toll-free bridged, we just accept them all.
QualType pointee = PT->getPointeeType();
if (pointee->getAsStructureType() || pointee->isVoidType())
- return true;
+ return Match;
}
- return false;
+ return NoMatch;
}
}
ExprTy = TET->getUnderlyingExpr()->getType();
}
- if (AT.matchesType(S.Context, ExprTy))
+ analyze_printf::ArgType::MatchKind match = AT.matchesType(S.Context, ExprTy);
+
+ if (match == analyze_printf::ArgType::Match) {
return true;
+ }
// Look through argument promotions for our error message's reported type.
// This includes the integral and floating promotions, but excludes array
// arguments here.
switch (S.isValidVarArgType(ExprTy)) {
case Sema::VAK_Valid:
- case Sema::VAK_ValidInCXX11:
+ case Sema::VAK_ValidInCXX11: {
+ unsigned diag = diag::warn_format_conversion_argument_type_mismatch;
+ if (match == analyze_printf::ArgType::NoMatchPedantic) {
+ diag = diag::warn_format_conversion_argument_type_mismatch_pedantic;
+ }
+
EmitFormatDiagnostic(
- S.PDiag(diag::warn_format_conversion_argument_type_mismatch)
- << AT.getRepresentativeTypeName(S.Context) << ExprTy << IsEnum
- << CSR
- << E->getSourceRange(),
- E->getLocStart(), /*IsStringLocation*/false, CSR);
+ S.PDiag(diag) << AT.getRepresentativeTypeName(S.Context) << ExprTy
+ << IsEnum << CSR << E->getSourceRange(),
+ E->getLocStart(), /*IsStringLocation*/ false, CSR);
break;
-
+ }
case Sema::VAK_Undefined:
case Sema::VAK_MSVCUndefined:
EmitFormatDiagnostic(
FixItHint::CreateRemoval(R));
}
}
-
+
if (!FS.consumesDataArgument()) {
// FIXME: Technically specifying a precision or field width here
// makes no sense. Worth issuing a warning at some point.
return true;
}
-
+
// Consume the argument.
unsigned argIndex = FS.getArgIndex();
if (argIndex < NumDataArgs) {
// function if we encounter some other error.
CoveredArgs.set(argIndex);
}
-
+
// Check the length modifier is valid with the given conversion specifier.
if (!FS.hasValidLengthModifier(S.getASTContext().getTargetInfo()))
HandleInvalidLengthModifier(FS, CS, startSpecifier, specifierLen,
// The remaining checks depend on the data arguments.
if (HasVAListArg)
return true;
-
+
if (!CheckNumArgs(FS, CS, startSpecifier, specifierLen, argIndex))
return false;
-
+
// Check that the argument type matches the format specifier.
const Expr *Ex = getDataArg(argIndex);
if (!Ex)
return true;
const analyze_format_string::ArgType &AT = FS.getArgType(S.Context);
- if (AT.isValid() && !AT.matchesType(S.Context, Ex->getType())) {
+ analyze_format_string::ArgType::MatchKind match =
+ AT.matchesType(S.Context, Ex->getType());
+ if (AT.isValid() && match != analyze_format_string::ArgType::Match) {
ScanfSpecifier fixedFS = FS;
- bool success = fixedFS.fixType(Ex->getType(),
- Ex->IgnoreImpCasts()->getType(),
- S.getLangOpts(), S.Context);
+ bool success =
+ fixedFS.fixType(Ex->getType(), Ex->IgnoreImpCasts()->getType(),
+ S.getLangOpts(), S.Context);
+
+ unsigned diag = diag::warn_format_conversion_argument_type_mismatch;
+ if (match == analyze_format_string::ArgType::NoMatchPedantic) {
+ diag = diag::warn_format_conversion_argument_type_mismatch_pedantic;
+ }
if (success) {
// Get the fix string from the fixed format specifier.
fixedFS.toString(os);
EmitFormatDiagnostic(
- S.PDiag(diag::warn_format_conversion_argument_type_mismatch)
- << AT.getRepresentativeTypeName(S.Context) << Ex->getType() << false
- << Ex->getSourceRange(),
- Ex->getLocStart(),
- /*IsStringLocation*/false,
- getSpecifierRange(startSpecifier, specifierLen),
- FixItHint::CreateReplacement(
+ S.PDiag(diag) << AT.getRepresentativeTypeName(S.Context)
+ << Ex->getType() << false << Ex->getSourceRange(),
+ Ex->getLocStart(),
+ /*IsStringLocation*/ false,
getSpecifierRange(startSpecifier, specifierLen),
- os.str()));
+ FixItHint::CreateReplacement(
+ getSpecifierRange(startSpecifier, specifierLen), os.str()));
} else {
EmitFormatDiagnostic(
- S.PDiag(diag::warn_format_conversion_argument_type_mismatch)
- << AT.getRepresentativeTypeName(S.Context) << Ex->getType() << false
- << Ex->getSourceRange(),
- Ex->getLocStart(),
- /*IsStringLocation*/false,
- getSpecifierRange(startSpecifier, specifierLen));
+ S.PDiag(diag) << AT.getRepresentativeTypeName(S.Context)
+ << Ex->getType() << false << Ex->getSourceRange(),
+ Ex->getLocStart(),
+ /*IsStringLocation*/ false,
+ getSpecifierRange(startSpecifier, specifierLen));
}
}
void f(char **sp, float *fp) {
scanf("%as", sp); // expected-warning{{format specifies type 'float *' but the argument has type 'char **'}}
+ printf("%p", sp); // expected-warning{{format specifies type 'void *' but the argument has type 'char **'}}
+ scanf("%p", sp); // expected-warning{{format specifies type 'void **' but the argument has type 'char **'}}
+
printf("%a", 1.0);
scanf("%afoobar", fp);
printf(nullptr);