From b2bd281ef1753c9566386c2fcef6f441294449ea Mon Sep 17 00:00:00 2001 From: Mandeep Singh Grang Date: Thu, 2 Sep 2021 15:39:15 -0700 Subject: [PATCH] Support variadic function calls in checked scope (#1174) * Support variadic function calls in checked scope We add support for calling variadic functions in checked scope. These are functions like printf, scanf, etc that take a format string and have a variable number of arguments. We implement checking of arguments to these functions. Following is a list of some important checks that we implement in checked scope for these functions: - check that the argument corresponding to the %s format specifier is a null-terminated array. - all warnings emitted by the -Wformat family of flags have been converted to errors in checked scope. * Allow only certain printf/scanf like functions in checked scope --- .../clang/Basic/DiagnosticSemaKinds.td | 78 ++++- clang/lib/Sema/SemaChecking.cpp | 312 ++++++++++++++---- clang/lib/Sema/SemaExpr.cpp | 26 +- .../checked-scope/variadic-functions.c | 235 +++++++++++++ 4 files changed, 582 insertions(+), 69 deletions(-) create mode 100644 clang/test/CheckedC/checked-scope/variadic-functions.c diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ccc694fbeaec..c25a7cf55c85 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11511,7 +11511,7 @@ def err_bounds_type_annotation_lost_checking : Error< "variable arguments function cannot be made in a checked scope">; def err_checked_scope_no_variadic_func_for_expression : Error< - "cannot use a variable arguments function in a checked scope or function">; + "cannot use this variable arguments function in a checked scope or function">; def err_checked_scope_no_assume_bounds_casting : Error< "_Assume_bounds_cast not allowed in a checked scope or function">; @@ -11524,6 +11524,12 @@ def err_bounds_type_annotation_lost_checking : Error< "%select{'_Unchecked'|'_Checked _Bounds_only|'_Checked'}0 " "can only appear on functions">; + def err_checked_scope_invalid_format_specifier_argument : Error< + "in a checked scope %0 format specifier requires %1 argument">; + + def err_checked_scope_scanf_width : Error< + "in a checked scope width is not allowed with format specifier in scanf">; + def err_pragma_pop_checked_scope_mismatch : Error< "#pragma CHECKED_SCOPE pop with no matching #pragma CHECKED_SCOPE push">; @@ -11709,5 +11715,75 @@ def err_bounds_type_annotation_lost_checking : Error< def err_expanding_cycle : Error< "expanding cycle in struct definition">; + // -Wformat warnings issued as errors in checked scope. + def err_format_nonliteral_noargs : Error< + "format string is not a string literal (potentially insecure)">; + def err_format_nonliteral : Error< + "format string is not a string literal">; + def err_printf_insufficient_data_args : Error< + "more '%%' conversions than data arguments">; + def err_printf_data_arg_not_used : Error< + "data argument not used by format string">; + def err_format_invalid_conversion : Error< + "invalid conversion specifier '%0'">; + def err_printf_incomplete_specifier : Error< + "incomplete format specifier">; + def err_missing_format_string : Error< + "format string missing">; + def err_scanf_nonzero_width : Error< + "zero field width in scanf format string is unused">; + def err_format_conversion_argument_type_mismatch : Error< + "format specifies type %0 but the argument has " + "%select{type|underlying type}2 %1">; + def err_format_conversion_argument_type_mismatch_pedantic : Error< + err_format_conversion_argument_type_mismatch.Text>; + def err_format_conversion_argument_type_mismatch_confusion : Error< + err_format_conversion_argument_type_mismatch.Text>; + def err_format_argument_needs_cast : Error< + "%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">; + def err_format_argument_needs_cast_pedantic : Error< + err_format_argument_needs_cast.Text>; + def err_printf_positional_arg_exceeds_data_args : Error < + "data argument position '%0' exceeds the number of data arguments (%1)">; + def err_format_invalid_positional_specifier : Error< + "invalid position specified for %select{field width|field precision}0">; + def err_format_mix_positional_nonpositional_args : Error< + "cannot mix positional and non-positional arguments in format string">; + def err_empty_format_string : Error< + "format string is empty">; + def err_format_string_is_wide_literal : Error< + "format string should not be a wide string">; + def err_printf_format_string_contains_null_char : Error< + "format string contains '\\0' within the string body">; + def err_printf_format_string_not_null_terminated : Error< + "format string is not null-terminated">; + def err_printf_asterisk_missing_arg : Error< + "'%select{*|.*}0' specified field %select{width|precision}0 is missing a matching 'int' argument">; + def err_printf_asterisk_wrong_type : Error< + "field %select{width|precision}0 should have type %1, but argument has type %2">; + def err_printf_nonsensical_optional_amount: Error< + "%select{field width|precision}0 used with '%1' conversion specifier, resulting in undefined behavior">; + def err_printf_nonsensical_flag: Error< + "flag '%0' results in undefined behavior with '%1' conversion specifier">; + def err_format_nonsensical_length: Error< + "length modifier '%0' results in undefined behavior or no effect with '%1' conversion specifier">; + def err_format_non_standard_positional_arg: Error< + "positional arguments are not supported by ISO C">; + def err_format_non_standard: Error< + "'%0' %select{length modifier|conversion specifier}1 is not supported by ISO C">; + def err_format_non_standard_conversion_spec: Error< + "using length modifier '%0' with conversion specifier '%1' is not supported by ISO C">; + def err_format_invalid_annotation : Error< + "using '%0' format specifier annotation outside of os_log()/os_trace()">; + def err_format_P_no_precision : Error< + "using '%%P' format specifier without precision">; + def err_printf_ignored_flag: Error< + "flag '%0' is ignored when flag '%1' is present">; + def err_scanf_scanlist_incomplete : Error< + "no closing ']' for '%%[' in scanf format string">; + def err_format_bool_as_character : Error< + "using '%0' format specifier, but argument has boolean value">; + } // end of Checked C Category } // end of sema component. diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index bb476366ff52..1bc22b8e6451 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -7429,7 +7429,10 @@ bool Sema::CheckFormatArguments(ArrayRef Args, llvm::SmallBitVector &CheckedVarArgs) { // CHECK: printf/scanf-like function is called with no format string. if (format_idx >= Args.size()) { - Diag(Loc, diag::warn_missing_format_string) << Range; + (IsCheckedScope() ? + Diag(Loc, diag::err_missing_format_string) : + Diag(Loc, diag::warn_missing_format_string)) + << Range; return false; } @@ -7482,8 +7485,10 @@ bool Sema::CheckFormatArguments(ArrayRef Args, // If there are no arguments specified, warn with -Wformat-security, otherwise // warn only with -Wformat-nonliteral. if (Args.size() == firstDataArg) { - Diag(FormatLoc, diag::warn_format_nonliteral_noargs) - << OrigFormatExpr->getSourceRange(); + (IsCheckedScope() ? + Diag(FormatLoc, diag::err_format_nonliteral_noargs) : + Diag(FormatLoc, diag::warn_format_nonliteral_noargs)) + << OrigFormatExpr->getSourceRange(); switch (Type) { default: break; @@ -7499,8 +7504,10 @@ bool Sema::CheckFormatArguments(ArrayRef Args, break; } } else { - Diag(FormatLoc, diag::warn_format_nonliteral) - << OrigFormatExpr->getSourceRange(); + (IsCheckedScope() ? + Diag(FormatLoc, diag::err_format_nonliteral) : + Diag(FormatLoc, diag::warn_format_nonliteral)) + << OrigFormatExpr->getSourceRange(); } return false; } @@ -7574,6 +7581,11 @@ class CheckFormatHandler : public analyze_format_string::FormatStringHandler { void HandleNullChar(const char *nullCharacter) override; + void CheckVarargsInCheckedScope( + const analyze_format_string::ConversionSpecifier &CS, + const char *StartSpecifier, unsigned SpecifierLen, const Expr *E, + SmallString<128> FSString); + template static void EmitFormatDiagnostic(Sema &S, bool inFunctionCall, const Expr *ArgumentExpr, @@ -7633,7 +7645,9 @@ SourceLocation CheckFormatHandler::getLocationOfByte(const char *x) { void CheckFormatHandler::HandleIncompleteSpecifier(const char *startSpecifier, unsigned specifierLen){ - EmitFormatDiagnostic(S.PDiag(diag::warn_printf_incomplete_specifier), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_printf_incomplete_specifier) : + S.PDiag(diag::warn_printf_incomplete_specifier)), getLocationOfByte(startSpecifier), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen)); @@ -7662,7 +7676,8 @@ void CheckFormatHandler::HandleInvalidLengthModifier( } else { FixItHint Hint; - if (DiagID == diag::warn_format_nonsensical_length) + if (DiagID == diag::warn_format_nonsensical_length || + DiagID == diag::err_format_nonsensical_length) Hint = FixItHint::CreateRemoval(LMRange); EmitFormatDiagnostic(S.PDiag(DiagID) << LM.toString() << CS.toString(), @@ -7684,8 +7699,10 @@ void CheckFormatHandler::HandleNonStandardLengthModifier( // See if we know how to fix this length modifier. Optional FixedLM = FS.getCorrectedLengthModifier(); if (FixedLM) { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_non_standard) - << LM.toString() << 0, + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_non_standard) : + S.PDiag(diag::warn_format_non_standard)) + << LM.toString() << 0, getLocationOfByte(LM.getStart()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen)); @@ -7695,8 +7712,10 @@ void CheckFormatHandler::HandleNonStandardLengthModifier( << FixItHint::CreateReplacement(LMRange, FixedLM->toString()); } else { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_non_standard) - << LM.toString() << 0, + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_non_standard) : + S.PDiag(diag::warn_format_non_standard)) + << LM.toString() << 0, getLocationOfByte(LM.getStart()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen)); @@ -7711,8 +7730,10 @@ void CheckFormatHandler::HandleNonStandardConversionSpecifier( // See if we know how to fix this conversion specifier. Optional FixedCS = CS.getStandardSpecifier(); if (FixedCS) { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_non_standard) - << CS.toString() << /*conversion specifier*/1, + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_non_standard) : + S.PDiag(diag::warn_format_non_standard)) + << CS.toString() << /*conversion specifier*/1, getLocationOfByte(CS.getStart()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen)); @@ -7722,8 +7743,10 @@ void CheckFormatHandler::HandleNonStandardConversionSpecifier( << FixedCS->toString() << FixItHint::CreateReplacement(CSRange, FixedCS->toString()); } else { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_non_standard) - << CS.toString() << /*conversion specifier*/1, + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_non_standard) : + S.PDiag(diag::warn_format_non_standard)) + << CS.toString() << /*conversion specifier*/1, getLocationOfByte(CS.getStart()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen)); @@ -7732,7 +7755,9 @@ void CheckFormatHandler::HandleNonStandardConversionSpecifier( void CheckFormatHandler::HandlePosition(const char *startPos, unsigned posLen) { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_non_standard_positional_arg), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_non_standard_positional_arg) : + S.PDiag(diag::warn_format_non_standard_positional_arg)), getLocationOfByte(startPos), /*IsStringLocation*/true, getSpecifierRange(startPos, posLen)); @@ -7741,8 +7766,10 @@ void CheckFormatHandler::HandlePosition(const char *startPos, void CheckFormatHandler::HandleInvalidPosition(const char *startPos, unsigned posLen, analyze_format_string::PositionContext p) { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_invalid_positional_specifier) - << (unsigned) p, + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_invalid_positional_specifier) : + S.PDiag(diag::warn_format_invalid_positional_specifier)) + << (unsigned) p, getLocationOfByte(startPos), /*IsStringLocation*/true, getSpecifierRange(startPos, posLen)); } @@ -7759,7 +7786,9 @@ void CheckFormatHandler::HandleNullChar(const char *nullCharacter) { if (!isa(OrigFormatExpr)) { // The presence of a null character is likely an error. EmitFormatDiagnostic( - S.PDiag(diag::warn_printf_format_string_contains_null_char), + (S.IsCheckedScope() ? + S.PDiag(diag::err_printf_format_string_contains_null_char) : + S.PDiag(diag::warn_printf_format_string_contains_null_char)), getLocationOfByte(nullCharacter), /*IsStringLocation*/true, getFormatStringRange()); } @@ -7800,7 +7829,9 @@ void UncoveredArgHandler::Diagnose(Sema &S, bool IsFunctionCall, if (S.getSourceManager().isInSystemMacro(Loc)) return; - PartialDiagnostic PDiag = S.PDiag(diag::warn_printf_data_arg_not_used); + PartialDiagnostic PDiag = S.IsCheckedScope() ? + S.PDiag(diag::err_printf_data_arg_not_used) : + S.PDiag(diag::warn_printf_data_arg_not_used); for (auto E : DiagnosticExprs) PDiag << E->getSourceRange(); @@ -7863,7 +7894,10 @@ CheckFormatHandler::HandleInvalidConversionSpecifier(unsigned argIndex, } EmitFormatDiagnostic( - S.PDiag(diag::warn_format_invalid_conversion) << Specifier, Loc, + (S.IsCheckedScope() ? + S.PDiag(diag::err_format_invalid_conversion) : + S.PDiag(diag::warn_format_invalid_conversion)) + << Specifier, Loc, /*IsStringLocation*/ true, getSpecifierRange(startSpec, specifierLen)); return keepGoing; @@ -7874,7 +7908,9 @@ CheckFormatHandler::HandlePositionalNonpositionalArgs(SourceLocation Loc, const char *startSpec, unsigned specifierLen) { EmitFormatDiagnostic( - S.PDiag(diag::warn_format_mix_positional_nonpositional_args), + (S.IsCheckedScope() ? + S.PDiag(diag::err_format_mix_positional_nonpositional_args) : + S.PDiag(diag::warn_format_mix_positional_nonpositional_args)), Loc, /*isStringLoc*/true, getSpecifierRange(startSpec, specifierLen)); } @@ -7886,9 +7922,14 @@ CheckFormatHandler::CheckNumArgs( if (argIndex >= NumDataArgs) { PartialDiagnostic PDiag = FS.usesPositionalArg() - ? (S.PDiag(diag::warn_printf_positional_arg_exceeds_data_args) - << (argIndex+1) << NumDataArgs) - : S.PDiag(diag::warn_printf_insufficient_data_args); + ? (S.IsCheckedScope() ? + S.PDiag(diag::err_printf_positional_arg_exceeds_data_args) + << (argIndex+1) << NumDataArgs : + S.PDiag(diag::warn_printf_positional_arg_exceeds_data_args) + << (argIndex+1) << NumDataArgs) + : (S.IsCheckedScope() ? + S.PDiag(diag::err_printf_insufficient_data_args) : + S.PDiag(diag::warn_printf_insufficient_data_args)); EmitFormatDiagnostic( PDiag, getLocationOfByte(CS.getStart()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen)); @@ -7901,6 +7942,50 @@ CheckFormatHandler::CheckNumArgs( return true; } +void CheckFormatHandler::CheckVarargsInCheckedScope( + const analyze_format_string::ConversionSpecifier &CS, + const char *StartSpecifier, unsigned SpecifierLen, const Expr *E, + SmallString<128> FSString) { + + // Check arguments to variadic functions like printf/scanf, etc in checked + // scope. This function is called per argument. E is current argument that + // needs checking. + + using ConversionSpecifier = analyze_format_string::ConversionSpecifier; + + // Do not proceed with the checking if we are not in a checked scope. + if (!S.IsCheckedScope()) + return; + + QualType ArgTy = E->getType(); + bool EmitVariadicFuncDiag = false; + std::string ExpectedTyMsg; + + switch (CS.getKind()) { + default: + break; + + // Check if the argument corresponding to the %s format specifier is either + // _Nt_array_ptr or _Nt_checked. + case ConversionSpecifier::sArg: + if (!ArgTy->isCheckedPointerNtArrayType() && + !ArgTy->isNtCheckedArrayType()) { + EmitVariadicFuncDiag = true; + ExpectedTyMsg = "null-terminated"; + } + break; + } + + if (EmitVariadicFuncDiag) { + EmitFormatDiagnostic( + S.PDiag(diag::err_checked_scope_invalid_format_specifier_argument) + << FSString << ExpectedTyMsg, + E->getExprLoc(), /*IsStringLocation*/false, + getSpecifierRange(StartSpecifier, SpecifierLen)); + } +} + + template void CheckFormatHandler::EmitFormatDiagnostic(PartialDiagnostic PDiag, SourceLocation Loc, @@ -8058,8 +8143,10 @@ bool CheckPrintfHandler::HandleAmount( if (!HasVAListArg) { unsigned argIndex = Amt.getArgIndex(); if (argIndex >= NumDataArgs) { - EmitFormatDiagnostic(S.PDiag(diag::warn_printf_asterisk_missing_arg) - << k, + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_printf_asterisk_missing_arg) : + S.PDiag(diag::warn_printf_asterisk_missing_arg)) + << k, getLocationOfByte(Amt.getStart()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen)); @@ -8083,9 +8170,11 @@ bool CheckPrintfHandler::HandleAmount( assert(AT.isValid()); if (!AT.matchesType(S.Context, T)) { - EmitFormatDiagnostic(S.PDiag(diag::warn_printf_asterisk_wrong_type) - << k << AT.getRepresentativeTypeName(S.Context) - << T << Arg->getSourceRange(), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_printf_asterisk_wrong_type) : + S.PDiag(diag::warn_printf_asterisk_wrong_type)) + << k << AT.getRepresentativeTypeName(S.Context) + << T << Arg->getSourceRange(), getLocationOfByte(Amt.getStart()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen)); @@ -8113,8 +8202,10 @@ void CheckPrintfHandler::HandleInvalidAmount( Amt.getConstantLength())) : FixItHint(); - EmitFormatDiagnostic(S.PDiag(diag::warn_printf_nonsensical_optional_amount) - << type << CS.toString(), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_printf_nonsensical_optional_amount) : + S.PDiag(diag::warn_printf_nonsensical_optional_amount)) + << type << CS.toString(), getLocationOfByte(Amt.getStart()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen), @@ -8128,8 +8219,10 @@ void CheckPrintfHandler::HandleFlag(const analyze_printf::PrintfSpecifier &FS, // Warn about pointless flag with a fixit removal. const analyze_printf::PrintfConversionSpecifier &CS = FS.getConversionSpecifier(); - EmitFormatDiagnostic(S.PDiag(diag::warn_printf_nonsensical_flag) - << flag.toString() << CS.toString(), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_printf_nonsensical_flag) : + S.PDiag(diag::warn_printf_nonsensical_flag)) + << flag.toString() << CS.toString(), getLocationOfByte(flag.getPosition()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen), @@ -8144,8 +8237,10 @@ void CheckPrintfHandler::HandleIgnoredFlag( const char *startSpecifier, unsigned specifierLen) { // Warn about ignored flag with a fixit removal. - EmitFormatDiagnostic(S.PDiag(diag::warn_printf_ignored_flag) - << ignoredFlag.toString() << flag.toString(), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_printf_ignored_flag) : + S.PDiag(diag::warn_printf_ignored_flag)) + << ignoredFlag.toString() << flag.toString(), getLocationOfByte(ignoredFlag.getPosition()), /*IsStringLocation*/true, getSpecifierRange(startSpecifier, specifierLen), @@ -8322,7 +8417,9 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier 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) + (S.IsCheckedScope() ? + S.PDiag(diag::err_format_conversion_argument_type_mismatch) : + S.PDiag(diag::warn_format_conversion_argument_type_mismatch)) << AT.getRepresentativeTypeName(S.Context) << Ex->getType() << false << Ex->getSourceRange(), Ex->getBeginLoc(), /*IsStringLocation*/ false, @@ -8333,7 +8430,9 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier 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) + (S.IsCheckedScope() ? + S.PDiag(diag::err_format_conversion_argument_type_mismatch) : + S.PDiag(diag::warn_format_conversion_argument_type_mismatch)) << AT2.getRepresentativeTypeName(S.Context) << Ex->getType() << false << Ex->getSourceRange(), Ex->getBeginLoc(), /*IsStringLocation*/ false, @@ -8377,14 +8476,18 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier // Check for use of public/private annotation outside of os_log(). if (FSType != Sema::FST_OSLog) { if (FS.isPublic().isSet()) { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_invalid_annotation) + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_invalid_annotation) : + S.PDiag(diag::warn_format_invalid_annotation)) << "public", getLocationOfByte(FS.isPublic().getPosition()), /*IsStringLocation*/ false, getSpecifierRange(startSpecifier, specifierLen)); } if (FS.isPrivate().isSet()) { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_invalid_annotation) + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_invalid_annotation) : + S.PDiag(diag::warn_format_invalid_annotation)) << "private", getLocationOfByte(FS.isPrivate().getPosition()), /*IsStringLocation*/ false, @@ -8407,7 +8510,9 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier // Precision is mandatory for %P specifier. if (CS.getKind() == ConversionSpecifier::PArg && FS.getPrecision().getHowSpecified() == OptionalAmount::NotSpecified) { - EmitFormatDiagnostic(S.PDiag(diag::warn_format_P_no_precision), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_P_no_precision) : + S.PDiag(diag::warn_format_P_no_precision)), getLocationOfByte(startSpecifier), /*IsStringLocation*/ false, getSpecifierRange(startSpecifier, specifierLen)); @@ -8439,12 +8544,16 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier if (!FS.hasValidLengthModifier(S.getASTContext().getTargetInfo(), S.getLangOpts())) HandleInvalidLengthModifier(FS, CS, startSpecifier, specifierLen, - diag::warn_format_nonsensical_length); + (S.IsCheckedScope() ? + diag::err_format_nonsensical_length : + diag::warn_format_nonsensical_length)); else if (!FS.hasStandardLengthModifier()) HandleNonStandardLengthModifier(FS, startSpecifier, specifierLen); else if (!FS.hasStandardLengthConversionCombination()) HandleInvalidLengthModifier(FS, CS, startSpecifier, specifierLen, - diag::warn_format_non_standard_conversion_spec); + (S.IsCheckedScope() ? + diag::err_format_non_standard_conversion_spec : + diag::warn_format_non_standard_conversion_spec)); if (!FS.hasStandardConversionSpecifier(S.getLangOpts())) HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen); @@ -8460,7 +8569,15 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier if (!Arg) return true; - return checkFormatExpr(FS, startSpecifier, specifierLen, Arg); + if (!checkFormatExpr(FS, startSpecifier, specifierLen, Arg)) + return false; + + SmallString<128> FSString; + llvm::raw_svector_ostream os(FSString); + FS.toString(os); + + CheckVarargsInCheckedScope(CS, startSpecifier, specifierLen, Arg, FSString); + return true; } static bool requiresParensToAddCast(const Expr *E) { @@ -8608,7 +8725,9 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS, SmallString<4> FSString; llvm::raw_svector_ostream os(FSString); FS.toString(os); - EmitFormatDiagnostic(S.PDiag(diag::warn_format_bool_as_character) + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_format_bool_as_character) : + S.PDiag(diag::warn_format_bool_as_character)) << FSString, E->getExprLoc(), false, CSR); return true; @@ -8726,13 +8845,22 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS, switch (Match) { case ArgType::Match: llvm_unreachable("expected non-matching"); case ArgType::NoMatchPedantic: - Diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; + if (S.IsCheckedScope()) + Diag = diag::err_format_conversion_argument_type_mismatch_pedantic; + else + Diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; break; case ArgType::NoMatchTypeConfusion: - Diag = diag::warn_format_conversion_argument_type_mismatch_confusion; + if (S.IsCheckedScope()) + Diag = diag::err_format_conversion_argument_type_mismatch_confusion; + else + Diag = diag::warn_format_conversion_argument_type_mismatch_confusion; break; case ArgType::NoMatch: - Diag = diag::warn_format_conversion_argument_type_mismatch; + if (S.IsCheckedScope()) + Diag = diag::err_format_conversion_argument_type_mismatch; + else + Diag = diag::warn_format_conversion_argument_type_mismatch; break; } @@ -8791,9 +8919,16 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS, Name = TypedefTy->getDecl()->getName(); else Name = CastTyName; - unsigned Diag = Match == ArgType::NoMatchPedantic + unsigned Diag; + if (S.IsCheckedScope()) { + Diag = Match == ArgType::NoMatchPedantic + ? diag::err_format_argument_needs_cast_pedantic + : diag::err_format_argument_needs_cast; + } else { + Diag = Match == ArgType::NoMatchPedantic ? diag::warn_format_argument_needs_cast_pedantic : diag::warn_format_argument_needs_cast; + } EmitFormatDiagnostic(S.PDiag(Diag) << Name << IntendedTy << IsEnum << E->getSourceRange(), E->getBeginLoc(), /*IsStringLocation=*/false, @@ -8803,9 +8938,11 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS, // specifier, but we've decided that the specifier is probably correct // and we should cast instead. Just use the normal warning message. EmitFormatDiagnostic( - S.PDiag(diag::warn_format_conversion_argument_type_mismatch) - << AT.getRepresentativeTypeName(S.Context) << ExprTy << IsEnum - << E->getSourceRange(), + (S.IsCheckedScope() ? + S.PDiag(diag::err_format_conversion_argument_type_mismatch) : + S.PDiag(diag::warn_format_conversion_argument_type_mismatch)) + << AT.getRepresentativeTypeName(S.Context) << ExprTy << IsEnum + << E->getSourceRange(), E->getBeginLoc(), /*IsStringLocation*/ false, SpecRange, Hints); } } @@ -8822,13 +8959,22 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS, switch (Match) { case ArgType::Match: llvm_unreachable("expected non-matching"); case ArgType::NoMatchPedantic: - Diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; + if (S.IsCheckedScope()) + Diag = diag::err_format_conversion_argument_type_mismatch_pedantic; + else + Diag = diag::warn_format_conversion_argument_type_mismatch_pedantic; break; case ArgType::NoMatchTypeConfusion: - Diag = diag::warn_format_conversion_argument_type_mismatch_confusion; + if (S.IsCheckedScope()) + Diag = diag::err_format_conversion_argument_type_mismatch_confusion; + else + Diag = diag::warn_format_conversion_argument_type_mismatch_confusion; break; case ArgType::NoMatch: - Diag = diag::warn_format_conversion_argument_type_mismatch; + if (S.IsCheckedScope()) + Diag = diag::err_format_conversion_argument_type_mismatch; + else + Diag = diag::warn_format_conversion_argument_type_mismatch; break; } @@ -8909,7 +9055,9 @@ class CheckScanfHandler : public CheckFormatHandler { void CheckScanfHandler::HandleIncompleteScanList(const char *start, const char *end) { - EmitFormatDiagnostic(S.PDiag(diag::warn_scanf_scanlist_incomplete), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_scanf_scanlist_incomplete) : + S.PDiag(diag::warn_scanf_scanlist_incomplete)), getLocationOfByte(end), /*IsStringLocation*/true, getSpecifierRange(start, end - start)); } @@ -8950,13 +9098,24 @@ bool CheckScanfHandler::HandleScanfSpecifier( } } - // Check if the field with is non-zero. + // Check if the field width is non-zero. const OptionalAmount &Amt = FS.getFieldWidth(); + if (S.IsCheckedScope() && + Amt.getHowSpecified() != OptionalAmount::NotSpecified) { + const CharSourceRange &R = getSpecifierRange(startSpecifier, specifierLen); + EmitFormatDiagnostic(S.PDiag(diag::err_checked_scope_scanf_width), + getLocationOfByte(Amt.getStart()), + /*IsStringLocation*/true, R, + FixItHint::CreateRemoval(R)); + } + if (Amt.getHowSpecified() == OptionalAmount::Constant) { if (Amt.getConstantAmount() == 0) { const CharSourceRange &R = getSpecifierRange(Amt.getStart(), Amt.getConstantLength()); - EmitFormatDiagnostic(S.PDiag(diag::warn_scanf_nonzero_width), + EmitFormatDiagnostic((S.IsCheckedScope() ? + S.PDiag(diag::err_scanf_nonzero_width) : + S.PDiag(diag::warn_scanf_nonzero_width)), getLocationOfByte(Amt.getStart()), /*IsStringLocation*/true, R, FixItHint::CreateRemoval(R)); @@ -8982,12 +9141,16 @@ bool CheckScanfHandler::HandleScanfSpecifier( if (!FS.hasValidLengthModifier(S.getASTContext().getTargetInfo(), S.getLangOpts())) HandleInvalidLengthModifier(FS, CS, startSpecifier, specifierLen, - diag::warn_format_nonsensical_length); + (S.IsCheckedScope() ? + diag::err_format_nonsensical_length : + diag::warn_format_nonsensical_length)); else if (!FS.hasStandardLengthModifier()) HandleNonStandardLengthModifier(FS, startSpecifier, specifierLen); else if (!FS.hasStandardLengthConversionCombination()) HandleInvalidLengthModifier(FS, CS, startSpecifier, specifierLen, - diag::warn_format_non_standard_conversion_spec); + (S.IsCheckedScope() ? + diag::err_format_non_standard_conversion_spec : + diag::warn_format_non_standard_conversion_spec)); if (!FS.hasStandardConversionSpecifier(S.getLangOpts())) HandleNonStandardConversionSpecifier(CS, startSpecifier, specifierLen); @@ -9010,6 +9173,11 @@ bool CheckScanfHandler::HandleScanfSpecifier( return true; } + SmallString<128> FSString; + llvm::raw_svector_ostream os(FSString); + FS.toString(os); + CheckVarargsInCheckedScope(CS, startSpecifier, specifierLen, Ex, FSString); + analyze_format_string::ArgType::MatchKind Match = AT.matchesType(S.Context, Ex->getType()); bool Pedantic = Match == analyze_format_string::ArgType::NoMatchPedantic; @@ -9020,9 +9188,16 @@ bool CheckScanfHandler::HandleScanfSpecifier( bool Success = fixedFS.fixType(Ex->getType(), Ex->IgnoreImpCasts()->getType(), S.getLangOpts(), S.Context); - unsigned Diag = + unsigned Diag; + if (S.IsCheckedScope()) { + Diag = + Pedantic ? diag::err_format_conversion_argument_type_mismatch_pedantic + : diag::err_format_conversion_argument_type_mismatch; + } else { + Diag = Pedantic ? diag::warn_format_conversion_argument_type_mismatch_pedantic : diag::warn_format_conversion_argument_type_mismatch; + } if (Success) { // Get the fix string from the fixed format specifier. @@ -9046,7 +9221,6 @@ bool CheckScanfHandler::HandleScanfSpecifier( /*IsStringLocation*/ false, getSpecifierRange(startSpecifier, specifierLen)); } - return true; } @@ -9065,7 +9239,10 @@ static void CheckFormatString(Sema &S, const FormatStringLiteral *FExpr, if (!FExpr->isAscii() && !FExpr->isUTF8()) { CheckFormatHandler::EmitFormatDiagnostic( S, inFunctionCall, Args[format_idx], - S.PDiag(diag::warn_format_string_is_wide_literal), FExpr->getBeginLoc(), + (S.IsCheckedScope() ? + S.PDiag(diag::err_format_string_is_wide_literal) : + S.PDiag(diag::warn_format_string_is_wide_literal)), + FExpr->getBeginLoc(), /*IsStringLocation*/ true, OrigFormatExpr->getSourceRange()); return; } @@ -9092,7 +9269,9 @@ static void CheckFormatString(Sema &S, const FormatStringLiteral *FExpr, StrRef.substr(0, TypeSize).find('\0') == StringRef::npos) { CheckFormatHandler::EmitFormatDiagnostic( S, inFunctionCall, Args[format_idx], - S.PDiag(diag::warn_printf_format_string_not_null_terminated), + (S.IsCheckedScope() ? + S.PDiag(diag::err_printf_format_string_not_null_terminated) : + S.PDiag(diag::warn_printf_format_string_not_null_terminated)), FExpr->getBeginLoc(), /*IsStringLocation=*/true, OrigFormatExpr->getSourceRange()); return; @@ -9102,7 +9281,10 @@ static void CheckFormatString(Sema &S, const FormatStringLiteral *FExpr, if (StrLen == 0 && numDataArgs > 0) { CheckFormatHandler::EmitFormatDiagnostic( S, inFunctionCall, Args[format_idx], - S.PDiag(diag::warn_empty_format_string), FExpr->getBeginLoc(), + (S.IsCheckedScope() ? + S.PDiag(diag::err_empty_format_string) : + S.PDiag(diag::warn_empty_format_string)), + FExpr->getBeginLoc(), /*IsStringLocation*/ true, OrigFormatExpr->getSourceRange()); return; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index aa2be795adb0..902e80bd2426 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -196,6 +196,15 @@ void Sema::MaybeSuggestAddingStaticToDecl(const FunctionDecl *Cur) { } } +static bool IsVariadicAllowedInCheckedScope(StringRef FuncName) { + return llvm::StringSwitch(FuncName) + .Cases("printf", "fprintf", "sprintf", "snprintf", + "vprintf", "vfprintf", "vsprintf", true) + .Cases("scanf", "fscanf", "sscanf", + "vscanf", "vfscanf", "vsscanf", true) + .Default(false); +} + /// Determine whether the use of this declaration is valid, and /// emit any corresponding diagnostics. /// @@ -359,10 +368,21 @@ bool Sema::DiagnoseUseOfDecl(NamedDecl *D, ArrayRef Locs, ValueDecl *VD = cast(D); if (!VD->isInvalidDecl() && !DiagnoseCheckedDecl(VD, Loc)) return true; + if (FunctionDecl *FD = dyn_cast(D)) { - if (FD->getType()->hasVariadicType()) { - Diag(Loc, diag::err_checked_scope_no_variadic_func_for_expression); - return true; + // In checked scope, we only allow functions calls to the following + // variadic functions: + // 1. C library functions like printf/scanf, etc. + // 2. Functions that are marked as __attribute__((format(func))), where + // func is a C library function like printf/scanf, etc. + if (FD->getType()->hasVariadicType() && + !IsVariadicAllowedInCheckedScope(FD->getName())) { + const auto *FA = FD->getAttr(); + if (!FA || + !IsVariadicAllowedInCheckedScope(FA->getType()->getName())) { + Diag(Loc, diag::err_checked_scope_no_variadic_func_for_expression); + return true; + } } } } diff --git a/clang/test/CheckedC/checked-scope/variadic-functions.c b/clang/test/CheckedC/checked-scope/variadic-functions.c new file mode 100644 index 000000000000..89028cab8274 --- /dev/null +++ b/clang/test/CheckedC/checked-scope/variadic-functions.c @@ -0,0 +1,235 @@ +// Test calls to variadic functions in checked scopes. + +// RUN: %clang_cc1 -fcheckedc-extension -verify \ +// RUN: -verify-ignore-unexpected=note %s + +typedef __builtin_va_list va_list; +typedef __WCHAR_TYPE__ wchar_t; +typedef __SIZE_TYPE__ size_t; +typedef _Ptr FILE; + +int printf(const char *format : itype(_Nt_array_ptr), ...); +int MyPrintf(const char *format : itype(_Nt_array_ptr), ...) + __attribute__((format(printf, 1, 2))); + +int scanf(const char *format : itype(_Nt_array_ptr), ...); +int MyScanf(const char *format : itype(_Nt_array_ptr), ...) + __attribute__((format(scanf, 1, 2))); + +int fprintf(FILE *stream : itype(_Ptr), + const char *format : itype(_Nt_array_ptr), ...); +int sprintf(char *s, + const char *format : itype(_Nt_array_ptr), ...); + +int snprintf(char *s : itype(_Nt_array_ptr) count(n-1), + size_t n _Where n > 0, + const char *format : itype(_Nt_array_ptr), ...); + +int fscanf(FILE *stream : itype(_Ptr), + const char *format : itype(_Nt_array_ptr), ...); +int sscanf(const char *s : itype(_Nt_array_ptr), + const char *format : itype(_Nt_array_ptr), ...); + +int vprintf(const char *format : itype(_Nt_array_ptr), + va_list arg); +int vfprintf(FILE *stream : itype(_Ptr), + const char *format : itype(_Nt_array_ptr), + va_list arg); +int vsprintf(char *s, + const char *format : itype(_Nt_array_ptr), + va_list arg); + +int vscanf(const char *format : itype(_Nt_array_ptr), + va_list arg); +int vfscanf(FILE *stream : itype(_Ptr), + const char *format : itype(_Nt_array_ptr), + va_list arg); +int vsscanf(const char *s : itype(_Nt_array_ptr), + const char *format : itype(_Nt_array_ptr), + va_list arg); + +void f1(_Nt_array_ptr chk, char *unchk1, _Array_ptr arr) { + int a = 1, b = 2; + char *unchk2; + + printf("%d %s %d %s", 1, unchk1, 2, unchk2); + MyPrintf("%d %s %d %s", 1, unchk1, 2, unchk2); + scanf("%d %s %d %s", &a, unchk1, &b, unchk2); + MyScanf("%d %s %d %s", &a, unchk1, &b, unchk2); + +_Checked { + printf("%d %s %d %s", 1, unchk1, 2, unchk2); // expected-error {{local variable used in a checked scope must have a checked type}} expected-error {{parameter used in a checked scope must have a checked type or a bounds-safe interface}} + MyPrintf("%d %s %d %s", 1, unchk1, 2, unchk2); // expected-error {{local variable used in a checked scope must have a checked type}} expected-error {{parameter used in a checked scope must have a checked type or a bounds-safe interface}} + scanf("%d %s %d %s", &a, unchk1, &b, unchk2); // expected-error {{local variable used in a checked scope must have a checked type}} expected-error {{parameter used in a checked scope must have a checked type or a bounds-safe interface}} + MyScanf("%d %s %d %s", &a, unchk1, &b, unchk2); // expected-error {{local variable used in a checked scope must have a checked type}} expected-error {{parameter used in a checked scope must have a checked type or a bounds-safe interface}} + + printf("%s", arr); // expected-error {{in a checked scope %s format specifier requires null-terminated argument}} + MyPrintf("%s", arr); // expected-error {{in a checked scope %s format specifier requires null-terminated argument}} + scanf("%s", arr); // expected-error {{in a checked scope %s format specifier requires null-terminated argument}} + MyScanf("%s", arr); // expected-error {{in a checked scope %s format specifier requires null-terminated argument}} +} +} + +void f2 (_Nt_array_ptr p, _Array_ptr arr, _Ptr fp) { + char *s; + +_Checked { + fprintf(fp, "%s", arr); // expected-error {{in a checked scope %s format specifier requires null-terminated argument}} + sprintf(s, "%s", arr); // expected-error {{local variable used in a checked scope must have a checked type}} + snprintf(p, 1, "%s", arr); // expected-error {{in a checked scope %s format specifier requires null-terminated argument}} + + fscanf(fp, "%s", arr); // expected-error {{in a checked scope %s format specifier requires null-terminated argument}} + sscanf(p, "%s", arr); // expected-error {{in a checked scope %s format specifier requires null-terminated argument}} +} +} + +void f3(_Nt_array_ptr p, _Array_ptr arr, _Ptr fp) { + va_list args; + char *s; + +_Checked { + vprintf(p, args); // expected-error {{local variable used in a checked scope must have a checked type}} + vfprintf(fp, "%s", args); // expected-error {{local variable used in a checked scope must have a checked type}} + vsprintf(s, "%s", args); // expected-error {{local variable used in a checked scope must have a checked type}} expected-error {{local variable used in a checked scope must have a checked type}} + + vscanf(p, args); // expected-error {{local variable used in a checked scope must have a checked type}} + vfscanf(fp, "%s", args); // expected-error {{local variable used in a checked scope must have a checked type}} + vsscanf(s, "%s", args); // expected-error {{local variable used in a checked scope must have a checked type}} expected-error {{local variable used in a checked scope must have a checked type}} +} +} + +void f4 (_Nt_array_ptr p, _Nt_array_ptr w, _Ptr ptr, _Ptr voidPtr) { +_Checked { + printf(p); // expected-error {{format string is not a string literal (potentially insecure)}} + MyPrintf(p); // expected-error {{format string is not a string literal (potentially insecure)}} + scanf(p); // expected-error {{format string is not a string literal (potentially insecure)}} + MyScanf(p); // expected-error {{format string is not a string literal (potentially insecure)}} + + printf(p, ""); // expected-error {{format string is not a string literal}} + MyPrintf(p, ""); // expected-error {{format string is not a string literal}} + scanf(p, ""); // expected-error {{format string is not a string literal}} + MyScanf(p, ""); // expected-error {{format string is not a string literal}} + + printf("%d"); // expected-error {{more '%' conversions than data arguments}} + MyPrintf("%d"); // expected-error {{more '%' conversions than data arguments}} + scanf("%d"); // expected-error {{more '%' conversions than data arguments}} + MyScanf("%d"); // expected-error {{more '%' conversions than data arguments}} + + printf("%d", 1, 2); // expected-error {{data argument not used by format string}} + MyPrintf("%d", 1, 2); // expected-error {{data argument not used by format string}} + scanf("%d", 1, 2); // expected-error {{format specifies type 'int *' but the argument has type 'int'}} expected-error {{data argument not used by format string}} + MyScanf("%d", 1, 2); // expected-error {{format specifies type 'int *' but the argument has type 'int'}} expected-error {{data argument not used by format string}} + + printf("%Z", p); // expected-error {{invalid conversion specifier 'Z'}} + MyPrintf("%Z", p); // expected-error {{invalid conversion specifier 'Z'}} + scanf("%Z", p); // expected-error {{invalid conversion specifier 'Z'}} + MyScanf("%Z", p); // expected-error {{invalid conversion specifier 'Z'}} + + printf("\%", p); // expected-error {{incomplete format specifier}} + MyPrintf("\%", p); // expected-error {{incomplete format specifier}} + scanf("\%", p); // expected-error {{incomplete format specifier}} + MyScanf("\%", p); // expected-error {{incomplete format specifier}} + + printf("", p); // expected-error {{format string is empty}} + MyPrintf("", p); // expected-error {{format string is empty}} + scanf("", p); // expected-error {{format string is empty}} + MyScanf("", p); // expected-error {{format string is empty}} + + printf("%10s", p); + MyPrintf("%10s", p); + scanf("%10s", p); // expected-error {{in a checked scope width is not allowed with format specifier in scanf}} + MyScanf("%10s", p); // expected-error {{in a checked scope width is not allowed with format specifier in scanf}} + + printf("%d", 1.0); // expected-error {{format specifies type 'int' but the argument has type 'double'}} + MyPrintf("%d", 1.0); // expected-error {{format specifies type 'int' but the argument has type 'double'}} + scanf("%d", 1.0); // expected-error {{format specifies type 'int *' but the argument has type 'double'}} + MyScanf("%d", 1.0); // expected-error {{format specifies type 'int *' but the argument has type 'double'}} + + printf("%1$d", 1); // expected-error {{positional arguments are not supported by ISO C}} + MyPrintf("%1$d", 1); // expected-error {{positional arguments are not supported by ISO C}} + scanf("%1$d", 1); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{format specifies type 'int *' but the argument has type 'int'}} + MyScanf("%1$d", 1); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{format specifies type 'int *' but the argument has type 'int'}} + + printf("%2$d", 1); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{data argument position '2' exceeds the number of data arguments (1)}} + MyPrintf("%2$d", 1); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{data argument position '2' exceeds the number of data arguments (1)}} + scanf("%2$d", 1); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{data argument position '2' exceeds the number of data arguments (1)}} + MyScanf("%2$d", 1); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{data argument position '2' exceeds the number of data arguments (1)}} + + printf("%1$d %d", 4, 4); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{cannot mix positional and non-positional arguments in format string}} + MyPrintf("%1$d %d", 4, 4); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{cannot mix positional and non-positional arguments in format string}} + scanf("%1$d %d", 4, 4); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{cannot mix positional and non-positional arguments in format string}} expected-error {{format specifies type 'int *' but the argument has type 'int'}} + MyScanf("%1$d %d", 4, 4); // expected-error {{positional arguments are not supported by ISO C}} expected-error {{cannot mix positional and non-positional arguments in format string}} expected-error {{format specifies type 'int *' but the argument has type 'int'}} + + printf("\0%d", 1); // expected-error {{format string contains '\0' within the string body}} + MyPrintf("\0%d", 1); // expected-error {{format string contains '\0' within the string body}} + scanf("\0%d", 1); // expected-error {{format string contains '\0' within the string body}} + MyScanf("\0%d", 1); // expected-error {{format string contains '\0' within the string body}} + + printf("%Lc", 'a'); // expected-error {{length modifier 'L' results in undefined behavior or no effect with 'c' conversion specifier}} + MyPrintf("%Lc", 'a'); // expected-error {{length modifier 'L' results in undefined behavior or no effect with 'c' conversion specifier}} + scanf("%Lc", 'a'); // expected-error {{length modifier 'L' results in undefined behavior or no effect with 'c' conversion specifier}} + MyScanf("%Lc", 'a'); // expected-error {{length modifier 'L' results in undefined behavior or no effect with 'c' conversion specifier}} + + printf("%Li", (long long) 42); // expected-error{{using length modifier 'L' with conversion specifier 'i' is not supported by ISO C}} + MyPrintf("%Li", (long long) 42); // expected-error{{using length modifier 'L' with conversion specifier 'i' is not supported by ISO C}} + scanf("%Li", (long long) 42); // expected-error{{using length modifier 'L' with conversion specifier 'i' is not supported by ISO C}} expected-error {{format specifies type 'long long *' but the argument has type 'long long'}} + MyScanf("%Li", (long long) 42); // expected-error{{using length modifier 'L' with conversion specifier 'i' is not supported by ISO C}} expected-error {{format specifies type 'long long *' but the argument has type 'long long'}} + + printf("%P", p); // expected-error {{invalid conversion specifier 'P'}} + MyPrintf("%P", p); // expected-error {{invalid conversion specifier 'P'}} + scanf("%P", p); // expected-error {{invalid conversion specifier 'P'}} + MyScanf("%P", p); // expected-error {{invalid conversion specifier 'P'}} + + printf("%S", w); // expected-error {{'S' conversion specifier is not supported by ISO C}} + MyPrintf("%S", w); // expected-error {{'S' conversion specifier is not supported by ISO C}} + scanf("%S", w); // expected-error {{'S' conversion specifier is not supported by ISO C}} + MyScanf("%S", w); // expected-error {{'S' conversion specifier is not supported by ISO C}} + + printf("%*d"); // expected-error {{'*' specified field width is missing a matching 'int' argument}} + MyPrintf("%*d"); // expected-error {{'*' specified field width is missing a matching 'int' argument}} + scanf("%*d"); // Note: This is safe because * indicates the data is to be read from the stream but ignored (i.e. it is not stored in the location pointed by an argument). + MyScanf("%*d"); // Note: This is safe because * indicates the data is to be read from the stream but ignored (i.e. it is not stored in the location pointed by an argument). + + printf("%.*d"); // expected-error {{'.*' specified field precision is missing a matching 'int' argument}} + MyPrintf("%.*d"); // expected-error {{'.*' specified field precision is missing a matching 'int' argument}} + scanf("%.*d"); // expected-error {{invalid conversion specifier '.'}} + MyScanf("%.*d"); // expected-error {{invalid conversion specifier '.'}} + + printf("%.3p", voidPtr); // expected-error {{precision used with 'p' conversion specifier, resulting in undefined behavior}} + scanf("%.3p", voidPtr); // expected-error {{invalid conversion specifier '.'}} + MyScanf("%.3p", voidPtr); // expected-error {{invalid conversion specifier '.'}} + + printf("%+p", voidPtr); // expected-error {{flag '+' results in undefined behavior with 'p' conversion specifier}} + MyPrintf("%+p", voidPtr); // expected-error {{flag '+' results in undefined behavior with 'p' conversion specifier}} + scanf("%+p", voidPtr); // expected-error {{invalid conversion specifier '+'}} + MyScanf("%+p", voidPtr); // expected-error {{invalid conversion specifier '+'}} + + printf("%{private}s", p); // expected-error {{using 'private' format specifier annotation outside of os_log()/os_trace()}} + MyPrintf("%{private}s", p); // expected-error {{using 'private' format specifier annotation outside of os_log()/os_trace()}} + scanf("%{private}s", p); // expected-error {{invalid conversion specifier '{'}} + MyScanf("%{private}s", p); // expected-error {{invalid conversion specifier '{'}} + + printf("% +f", 1.23); // expected-error {{flag ' ' is ignored when flag '+' is present}} + MyPrintf("% +f", 1.23); // expected-error {{flag ' ' is ignored when flag '+' is present}} + scanf("% +f", 1.23); // expected-error {{invalid conversion specifier ' '}} + MyScanf("% +f", 1.23); // expected-error {{invalid conversion specifier ' '}} + + printf("%c", (_Bool)1); // expected-error {{using '%c' format specifier, but argument has boolean value}} + MyPrintf("%c", (_Bool)1); // expected-error {{using '%c' format specifier, but argument has boolean value}} + scanf("%c", (_Bool)1); // expected-error {{format specifies type 'char *' but the argument has type 'int'}} + MyScanf("%c", (_Bool)1); // expected-error {{format specifies type 'char *' but the argument has type 'int'}} + + printf("%]", p); // expected-error {{invalid conversion specifier ']'}} + MyPrintf("%]", p); // expected-error {{invalid conversion specifier ']'}} + scanf("%]", p); // expected-error {{invalid conversion specifier ']'}} + MyScanf("%]", p); // expected-error {{invalid conversion specifier ']'}} +} +} + +int MyPrintf_NoFormatAttr(const char *format : itype(_Nt_array_ptr), ...); + +void f5(_Nt_array_ptr p) { +_Checked { + MyPrintf_NoFormatAttr("%s", "abc"); // expected-error {{cannot use this variable arguments function in a checked scope or function}} +} +}