Add support for __builtin_os_log_format[_buffer_size]

These new builtins support a mechanism for logging OS events, using a
printf-like format string to specify the layout of data in a buffer.
The _buffer_size version of the builtin can be used to determine the size
of the buffer to allocate to hold the data, and then __builtin_os_log_format
can write data into that buffer. This implements format checking to report
mismatches between the format string and the data arguments. Most of this
code was written by Chris Willmore.

Differential Revision: https://reviews.llvm.org/D25888

llvm-svn: 284990
This commit is contained in:
Mehdi Amini 2016-10-24 16:56:23 +00:00
parent b05bac940d
commit 29034362ae
15 changed files with 992 additions and 93 deletions

View File

@ -35,7 +35,7 @@ class OptionalFlag {
public: public:
OptionalFlag(const char *Representation) OptionalFlag(const char *Representation)
: representation(Representation), flag(false) {} : representation(Representation), flag(false) {}
bool isSet() { return flag; } bool isSet() const { return flag; }
void set() { flag = true; } void set() { flag = true; }
void clear() { flag = false; } void clear() { flag = false; }
void setPosition(const char *position) { void setPosition(const char *position) {
@ -122,12 +122,13 @@ class ConversionSpecifier {
public: public:
enum Kind { enum Kind {
InvalidSpecifier = 0, InvalidSpecifier = 0,
// C99 conversion specifiers. // C99 conversion specifiers.
cArg, cArg,
dArg, dArg,
DArg, // Apple extension DArg, // Apple extension
iArg, iArg,
IntArgBeg = dArg, IntArgEnd = iArg, IntArgBeg = dArg,
IntArgEnd = iArg,
oArg, oArg,
OArg, // Apple extension OArg, // Apple extension
@ -135,7 +136,8 @@ public:
UArg, // Apple extension UArg, // Apple extension
xArg, xArg,
XArg, XArg,
UIntArgBeg = oArg, UIntArgEnd = XArg, UIntArgBeg = oArg,
UIntArgEnd = XArg,
fArg, fArg,
FArg, FArg,
@ -145,7 +147,8 @@ public:
GArg, GArg,
aArg, aArg,
AArg, AArg,
DoubleArgBeg = fArg, DoubleArgEnd = AArg, DoubleArgBeg = fArg,
DoubleArgEnd = AArg,
sArg, sArg,
pArg, pArg,
@ -154,13 +157,19 @@ public:
CArg, CArg,
SArg, SArg,
// Apple extension: P specifies to os_log that the data being pointed to is
// to be copied by os_log. The precision indicates the number of bytes to
// copy.
PArg,
// ** Printf-specific ** // ** Printf-specific **
ZArg, // MS extension ZArg, // MS extension
// Objective-C specific specifiers. // Objective-C specific specifiers.
ObjCObjArg, // '@' ObjCObjArg, // '@'
ObjCBeg = ObjCObjArg, ObjCEnd = ObjCObjArg, ObjCBeg = ObjCObjArg,
ObjCEnd = ObjCObjArg,
// FreeBSD kernel specific specifiers. // FreeBSD kernel specific specifiers.
FreeBSDbArg, FreeBSDbArg,
@ -169,13 +178,15 @@ public:
FreeBSDyArg, FreeBSDyArg,
// GlibC specific specifiers. // GlibC specific specifiers.
PrintErrno, // 'm' PrintErrno, // 'm'
PrintfConvBeg = ObjCObjArg, PrintfConvEnd = PrintErrno, PrintfConvBeg = ObjCObjArg,
PrintfConvEnd = PrintErrno,
// ** Scanf-specific ** // ** Scanf-specific **
ScanListArg, // '[' ScanListArg, // '['
ScanfConvBeg = ScanListArg, ScanfConvEnd = ScanListArg ScanfConvBeg = ScanListArg,
ScanfConvEnd = ScanListArg
}; };
ConversionSpecifier(bool isPrintf = true) ConversionSpecifier(bool isPrintf = true)
@ -437,13 +448,15 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier {
OptionalFlag HasAlternativeForm; // '#' OptionalFlag HasAlternativeForm; // '#'
OptionalFlag HasLeadingZeroes; // '0' OptionalFlag HasLeadingZeroes; // '0'
OptionalFlag HasObjCTechnicalTerm; // '[tt]' OptionalFlag HasObjCTechnicalTerm; // '[tt]'
OptionalFlag IsPrivate; // '{private}'
OptionalFlag IsPublic; // '{public}'
OptionalAmount Precision; OptionalAmount Precision;
public: public:
PrintfSpecifier() : PrintfSpecifier()
FormatSpecifier(/* isPrintf = */ true), : FormatSpecifier(/* isPrintf = */ true), HasThousandsGrouping("'"),
HasThousandsGrouping("'"), IsLeftJustified("-"), HasPlusPrefix("+"), IsLeftJustified("-"), HasPlusPrefix("+"), HasSpacePrefix(" "),
HasSpacePrefix(" "), HasAlternativeForm("#"), HasLeadingZeroes("0"), HasAlternativeForm("#"), HasLeadingZeroes("0"),
HasObjCTechnicalTerm("tt") {} HasObjCTechnicalTerm("tt"), IsPrivate("private"), IsPublic("public") {}
static PrintfSpecifier Parse(const char *beg, const char *end); static PrintfSpecifier Parse(const char *beg, const char *end);
@ -472,6 +485,8 @@ public:
void setHasObjCTechnicalTerm(const char *position) { void setHasObjCTechnicalTerm(const char *position) {
HasObjCTechnicalTerm.setPosition(position); HasObjCTechnicalTerm.setPosition(position);
} }
void setIsPrivate(const char *position) { IsPrivate.setPosition(position); }
void setIsPublic(const char *position) { IsPublic.setPosition(position); }
void setUsesPositionalArg() { UsesPositionalArg = true; } void setUsesPositionalArg() { UsesPositionalArg = true; }
// Methods for querying the format specifier. // Methods for querying the format specifier.
@ -509,6 +524,8 @@ public:
const OptionalFlag &hasLeadingZeros() const { return HasLeadingZeroes; } const OptionalFlag &hasLeadingZeros() const { return HasLeadingZeroes; }
const OptionalFlag &hasSpacePrefix() const { return HasSpacePrefix; } const OptionalFlag &hasSpacePrefix() const { return HasSpacePrefix; }
const OptionalFlag &hasObjCTechnicalTerm() const { return HasObjCTechnicalTerm; } const OptionalFlag &hasObjCTechnicalTerm() const { return HasObjCTechnicalTerm; }
const OptionalFlag &isPrivate() const { return IsPrivate; }
const OptionalFlag &isPublic() const { return IsPublic; }
bool usesPositionalArg() const { return UsesPositionalArg; } bool usesPositionalArg() const { return UsesPositionalArg; }
/// Changes the specifier and length according to a QualType, retaining any /// Changes the specifier and length according to a QualType, retaining any

View File

@ -0,0 +1,155 @@
//= OSLog.h - Analysis of calls to os_log builtins --*- C++ -*-===============//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines APIs for determining the layout of the data buffer for
// os_log() and os_trace().
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_OSLOG_H
#define LLVM_CLANG_ANALYSIS_ANALYSES_OSLOG_H
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
namespace clang {
namespace analyze_os_log {
/// An OSLogBufferItem represents a single item in the data written by a call
/// to os_log() or os_trace().
class OSLogBufferItem {
public:
enum Kind {
// The item is a scalar (int, float, raw pointer, etc.). No further copying
// is required. This is the only kind allowed by os_trace().
ScalarKind = 0,
// The item is a count, which describes the length of the following item to
// be copied. A count may only be followed by an item of kind StringKind,
// WideStringKind, or PointerKind.
CountKind,
// The item is a pointer to a C string. If preceded by a count 'n',
// os_log() will copy at most 'n' bytes from the pointer.
StringKind,
// The item is a pointer to a block of raw data. This item must be preceded
// by a count 'n'. os_log() will copy exactly 'n' bytes from the pointer.
PointerKind,
// The item is a pointer to an Objective-C object. os_log() may retain the
// object for later processing.
ObjCObjKind,
// The item is a pointer to wide-char string.
WideStringKind,
// The item is corresponding to the '%m' format specifier, no value is
// populated in the buffer and the runtime is loading the errno value.
ErrnoKind
};
enum {
// The item is marked "private" in the format string.
IsPrivate = 0x1,
// The item is marked "public" in the format string.
IsPublic = 0x2
};
private:
Kind TheKind = ScalarKind;
const Expr *TheExpr = nullptr;
CharUnits ConstValue;
CharUnits Size; // size of the data, not including the header bytes
unsigned Flags = 0;
public:
OSLogBufferItem(Kind kind, const Expr *expr, CharUnits size, unsigned flags)
: TheKind(kind), TheExpr(expr), Size(size), Flags(flags) {}
OSLogBufferItem(ASTContext &Ctx, CharUnits value, unsigned flags)
: TheKind(CountKind), ConstValue(value),
Size(Ctx.getTypeSizeInChars(Ctx.IntTy)), Flags(flags) {}
unsigned char getDescriptorByte() const {
unsigned char result = 0;
if (getIsPrivate())
result |= IsPrivate;
if (getIsPublic())
result |= IsPublic;
result |= ((unsigned)getKind()) << 4;
return result;
}
unsigned char getSizeByte() const { return size().getQuantity(); }
Kind getKind() const { return TheKind; }
bool getIsPrivate() const { return (Flags & IsPrivate) != 0; }
bool getIsPublic() const { return (Flags & IsPublic) != 0; }
const Expr *getExpr() const { return TheExpr; }
CharUnits getConstValue() const { return ConstValue; }
CharUnits size() const { return Size; }
};
class OSLogBufferLayout {
public:
SmallVector<OSLogBufferItem, 4> Items;
enum Flags { HasPrivateItems = 1, HasNonScalarItems = 1 << 1 };
CharUnits size() const {
CharUnits result;
result += CharUnits::fromQuantity(2); // summary byte, num-args byte
for (auto &item : Items) {
// descriptor byte, size byte
result += item.size() + CharUnits::fromQuantity(2);
}
return result;
}
bool hasPrivateItems() const {
return llvm::any_of(
Items, [](const OSLogBufferItem &Item) { return Item.getIsPrivate(); });
}
bool hasPublicItems() const {
return llvm::any_of(
Items, [](const OSLogBufferItem &Item) { return Item.getIsPublic(); });
}
bool hasNonScalar() const {
return llvm::any_of(Items, [](const OSLogBufferItem &Item) {
return Item.getKind() != OSLogBufferItem::ScalarKind;
});
}
unsigned char getSummaryByte() const {
unsigned char result = 0;
if (hasPrivateItems())
result |= HasPrivateItems;
if (hasNonScalar())
result |= HasNonScalarItems;
return result;
}
unsigned char getNumArgsByte() const { return Items.size(); }
};
// Given a call 'E' to one of the builtins __builtin_os_log_format() or
// __builtin_os_log_format_buffer_size(), compute the layout of the buffer that
// the call will write into and store it in 'layout'. Returns 'false' if there
// was some error encountered while computing the layout, and 'true' otherwise.
bool computeOSLogBufferLayout(clang::ASTContext &Ctx, const clang::CallExpr *E,
OSLogBufferLayout &layout);
} // namespace analyze_os_log
} // namespace clang
#endif

View File

@ -1384,6 +1384,10 @@ LANGBUILTIN(to_global, "v*v*", "tn", OCLC20_LANG)
LANGBUILTIN(to_local, "v*v*", "tn", OCLC20_LANG) LANGBUILTIN(to_local, "v*v*", "tn", OCLC20_LANG)
LANGBUILTIN(to_private, "v*v*", "tn", OCLC20_LANG) LANGBUILTIN(to_private, "v*v*", "tn", OCLC20_LANG)
// Builtins for os_log/os_trace
BUILTIN(__builtin_os_log_format_buffer_size, "zcC*.", "p:0:nut")
BUILTIN(__builtin_os_log_format, "v*v*cC*.", "p:0:nt")
#undef BUILTIN #undef BUILTIN
#undef LIBBUILTIN #undef LIBBUILTIN
#undef LANGBUILTIN #undef LANGBUILTIN

View File

@ -7413,6 +7413,12 @@ def warn_format_non_standard: Warning<
def warn_format_non_standard_conversion_spec: Warning< def warn_format_non_standard_conversion_spec: Warning<
"using length modifier '%0' with conversion specifier '%1' is not supported by ISO C">, "using length modifier '%0' with conversion specifier '%1' is not supported by ISO C">,
InGroup<FormatNonStandard>, DefaultIgnore; InGroup<FormatNonStandard>, DefaultIgnore;
def warn_format_invalid_annotation : Warning<
"using '%0' format specifier annotation outside of os_log()/os_trace()">,
InGroup<Format>;
def warn_format_P_no_precision : Warning<
"using '%%P' format specifier without precision">,
InGroup<Format>;
def warn_printf_ignored_flag: Warning< def warn_printf_ignored_flag: Warning<
"flag '%0' is ignored when flag '%1' is present">, "flag '%0' is ignored when flag '%1' is present">,
InGroup<Format>; InGroup<Format>;
@ -7549,6 +7555,15 @@ def warn_cfstring_truncated : Warning<
"belong to the input codeset UTF-8">, "belong to the input codeset UTF-8">,
InGroup<DiagGroup<"CFString-literal">>; InGroup<DiagGroup<"CFString-literal">>;
// os_log checking
// TODO: separate diagnostic for os_trace()
def err_os_log_format_not_string_constant : Error<
"os_log() format argument is not a string constant">;
def err_os_log_argument_too_big : Error<
"os_log() argument %d is too big (%d bytes, max %d)">;
def warn_os_log_format_narg : Error<
"os_log() '%%n' format specifier is not allowed">, DefaultError;
// Statements. // Statements.
def err_continue_not_in_loop : Error< def err_continue_not_in_loop : Error<
"'continue' statement not in loop statement">; "'continue' statement not in loop statement">;

View File

@ -9679,6 +9679,7 @@ private:
VariadicCallType CallType); VariadicCallType CallType);
bool CheckObjCString(Expr *Arg); bool CheckObjCString(Expr *Arg);
ExprResult CheckOSLogFormatStringArg(Expr *Arg);
ExprResult CheckBuiltinFunctionCall(FunctionDecl *FDecl, ExprResult CheckBuiltinFunctionCall(FunctionDecl *FDecl,
unsigned BuiltinID, CallExpr *TheCall); unsigned BuiltinID, CallExpr *TheCall);
@ -9701,6 +9702,7 @@ private:
bool SemaBuiltinVAStartARM(CallExpr *Call); bool SemaBuiltinVAStartARM(CallExpr *Call);
bool SemaBuiltinUnorderedCompare(CallExpr *TheCall); bool SemaBuiltinUnorderedCompare(CallExpr *TheCall);
bool SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs); bool SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs);
bool SemaBuiltinOSLogFormat(CallExpr *TheCall);
public: public:
// Used by C++ template instantiation. // Used by C++ template instantiation.
@ -9738,6 +9740,7 @@ public:
FST_Kprintf, FST_Kprintf,
FST_FreeBSDKPrintf, FST_FreeBSDKPrintf,
FST_OSTrace, FST_OSTrace,
FST_OSLog,
FST_Unknown FST_Unknown
}; };
static FormatStringType GetFormatStringType(const FormatAttr *Format); static FormatStringType GetFormatStringType(const FormatAttr *Format);

View File

@ -16,6 +16,7 @@ add_clang_library(clangAnalysis
Dominators.cpp Dominators.cpp
FormatString.cpp FormatString.cpp
LiveVariables.cpp LiveVariables.cpp
OSLog.cpp
ObjCNoReturn.cpp ObjCNoReturn.cpp
PostOrderCFGView.cpp PostOrderCFGView.cpp
PrintfFormatString.cpp PrintfFormatString.cpp

View File

@ -591,6 +591,8 @@ const char *ConversionSpecifier::toString() const {
case cArg: return "c"; case cArg: return "c";
case sArg: return "s"; case sArg: return "s";
case pArg: return "p"; case pArg: return "p";
case PArg:
return "P";
case nArg: return "n"; case nArg: return "n";
case PercentArg: return "%"; case PercentArg: return "%";
case ScanListArg: return "["; case ScanListArg: return "[";
@ -866,6 +868,7 @@ bool FormatSpecifier::hasStandardConversionSpecifier(
case ConversionSpecifier::ObjCObjArg: case ConversionSpecifier::ObjCObjArg:
case ConversionSpecifier::ScanListArg: case ConversionSpecifier::ScanListArg:
case ConversionSpecifier::PercentArg: case ConversionSpecifier::PercentArg:
case ConversionSpecifier::PArg:
return true; return true;
case ConversionSpecifier::CArg: case ConversionSpecifier::CArg:
case ConversionSpecifier::SArg: case ConversionSpecifier::SArg:

View File

@ -0,0 +1,177 @@
// TODO: header template
#include "clang/Analysis/Analyses/OSLog.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/Analysis/Analyses/FormatString.h"
#include "clang/Basic/Builtins.h"
#include "llvm/ADT/SmallBitVector.h"
using namespace clang;
using llvm::APInt;
using clang::analyze_os_log::OSLogBufferItem;
using clang::analyze_os_log::OSLogBufferLayout;
class OSLogFormatStringHandler
: public analyze_format_string::FormatStringHandler {
private:
struct ArgData {
const Expr *E = nullptr;
Optional<OSLogBufferItem::Kind> Kind;
Optional<unsigned> Size;
unsigned char Flags = 0;
};
SmallVector<ArgData, 4> ArgsData;
ArrayRef<const Expr *> Args;
OSLogBufferItem::Kind
getKind(analyze_format_string::ConversionSpecifier::Kind K) {
switch (K) {
case clang::analyze_format_string::ConversionSpecifier::sArg: // "%s"
return OSLogBufferItem::StringKind;
case clang::analyze_format_string::ConversionSpecifier::SArg: // "%S"
return OSLogBufferItem::WideStringKind;
case clang::analyze_format_string::ConversionSpecifier::PArg: { // "%P"
return OSLogBufferItem::PointerKind;
case clang::analyze_format_string::ConversionSpecifier::ObjCObjArg: // "%@"
return OSLogBufferItem::ObjCObjKind;
case clang::analyze_format_string::ConversionSpecifier::PrintErrno: // "%m"
return OSLogBufferItem::ErrnoKind;
default:
return OSLogBufferItem::ScalarKind;
}
}
}
public:
OSLogFormatStringHandler(ArrayRef<const Expr *> Args) : Args(Args) {
ArgsData.reserve(Args.size());
}
virtual bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS,
const char *StartSpecifier,
unsigned SpecifierLen) {
if (!FS.consumesDataArgument() &&
FS.getConversionSpecifier().getKind() !=
clang::analyze_format_string::ConversionSpecifier::PrintErrno)
return false;
ArgsData.emplace_back();
unsigned ArgIndex = FS.getArgIndex();
if (ArgIndex < Args.size())
ArgsData.back().E = Args[ArgIndex];
// First get the Kind
ArgsData.back().Kind = getKind(FS.getConversionSpecifier().getKind());
if (ArgsData.back().Kind != OSLogBufferItem::ErrnoKind &&
!ArgsData.back().E) {
// missing argument
ArgsData.pop_back();
return false;
}
switch (FS.getConversionSpecifier().getKind()) {
case clang::analyze_format_string::ConversionSpecifier::sArg: // "%s"
case clang::analyze_format_string::ConversionSpecifier::SArg: { // "%S"
auto &precision = FS.getPrecision();
switch (precision.getHowSpecified()) {
case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%s"
break;
case clang::analyze_format_string::OptionalAmount::Constant: // "%.16s"
ArgsData.back().Size = precision.getConstantAmount();
break;
case clang::analyze_format_string::OptionalAmount::Arg: // "%.*s"
ArgsData.back().Kind = OSLogBufferItem::CountKind;
break;
case clang::analyze_format_string::OptionalAmount::Invalid:
return false;
}
break;
}
case clang::analyze_format_string::ConversionSpecifier::PArg: { // "%P"
auto &precision = FS.getPrecision();
switch (precision.getHowSpecified()) {
case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%P"
return false; // length must be supplied with pointer format specifier
case clang::analyze_format_string::OptionalAmount::Constant: // "%.16P"
ArgsData.back().Size = precision.getConstantAmount();
break;
case clang::analyze_format_string::OptionalAmount::Arg: // "%.*P"
ArgsData.back().Kind = OSLogBufferItem::CountKind;
break;
case clang::analyze_format_string::OptionalAmount::Invalid:
return false;
}
break;
}
default:
break;
}
if (FS.isPrivate()) {
ArgsData.back().Flags |= OSLogBufferItem::IsPrivate;
}
if (FS.isPublic()) {
ArgsData.back().Flags |= OSLogBufferItem::IsPublic;
}
return true;
}
void computeLayout(ASTContext &Ctx, OSLogBufferLayout &Layout) const {
Layout.Items.clear();
for (auto &Data : ArgsData) {
if (Data.Size)
Layout.Items.emplace_back(Ctx, CharUnits::fromQuantity(*Data.Size),
Data.Flags);
if (Data.Kind) {
CharUnits Size;
if (*Data.Kind == OSLogBufferItem::ErrnoKind)
Size = CharUnits::Zero();
else
Size = Ctx.getTypeSizeInChars(Data.E->getType());
Layout.Items.emplace_back(*Data.Kind, Data.E, Size, Data.Flags);
} else {
auto Size = Ctx.getTypeSizeInChars(Data.E->getType());
Layout.Items.emplace_back(OSLogBufferItem::ScalarKind, Data.E, Size,
Data.Flags);
}
}
}
};
bool clang::analyze_os_log::computeOSLogBufferLayout(
ASTContext &Ctx, const CallExpr *E, OSLogBufferLayout &Layout) {
ArrayRef<const Expr *> Args(E->getArgs(), E->getArgs() + E->getNumArgs());
const Expr *StringArg;
ArrayRef<const Expr *> VarArgs;
switch (E->getBuiltinCallee()) {
case Builtin::BI__builtin_os_log_format_buffer_size:
assert(E->getNumArgs() >= 1 &&
"__builtin_os_log_format_buffer_size takes at least 1 argument");
StringArg = E->getArg(0);
VarArgs = Args.slice(1);
break;
case Builtin::BI__builtin_os_log_format:
assert(E->getNumArgs() >= 2 &&
"__builtin_os_log_format takes at least 2 arguments");
StringArg = E->getArg(1);
VarArgs = Args.slice(2);
break;
default:
llvm_unreachable("non-os_log builtin passed to computeOSLogBufferLayout");
}
const StringLiteral *Lit = cast<StringLiteral>(StringArg->IgnoreParenCasts());
assert(Lit && (Lit->isAscii() || Lit->isUTF8()));
StringRef Data = Lit->getString();
OSLogFormatStringHandler H(VarArgs);
ParsePrintfString(H, Data.begin(), Data.end(), Ctx.getLangOpts(),
Ctx.getTargetInfo(), /*isFreeBSDKPrintf*/ false);
H.computeLayout(Ctx, Layout);
return true;
}

View File

@ -119,6 +119,39 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H,
return true; return true;
} }
const char *OSLogVisibilityFlagsStart = nullptr,
*OSLogVisibilityFlagsEnd = nullptr;
if (*I == '{') {
OSLogVisibilityFlagsStart = I++;
// Find the end of the modifier.
while (I != E && *I != '}') {
I++;
}
if (I == E) {
if (Warn)
H.HandleIncompleteSpecifier(Start, E - Start);
return true;
}
assert(*I == '}');
OSLogVisibilityFlagsEnd = I++;
// Just see if 'private' or 'public' is the first word. os_log itself will
// do any further parsing.
const char *P = OSLogVisibilityFlagsStart + 1;
while (P < OSLogVisibilityFlagsEnd && isspace(*P))
P++;
const char *WordStart = P;
while (P < OSLogVisibilityFlagsEnd && (isalnum(*P) || *P == '_'))
P++;
const char *WordEnd = P;
StringRef Word(WordStart, WordEnd - WordStart);
if (Word == "private") {
FS.setIsPrivate(WordStart);
} else if (Word == "public") {
FS.setIsPublic(WordStart);
}
}
// Look for flags (if any). // Look for flags (if any).
bool hasMore = true; bool hasMore = true;
for ( ; I != E; ++I) { for ( ; I != E; ++I) {
@ -253,6 +286,10 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H,
// POSIX specific. // POSIX specific.
case 'C': k = ConversionSpecifier::CArg; break; case 'C': k = ConversionSpecifier::CArg; break;
case 'S': k = ConversionSpecifier::SArg; break; case 'S': k = ConversionSpecifier::SArg; break;
// Apple extension for os_log
case 'P':
k = ConversionSpecifier::PArg;
break;
// Objective-C. // Objective-C.
case '@': k = ConversionSpecifier::ObjCObjArg; break; case '@': k = ConversionSpecifier::ObjCObjArg; break;
// Glibc specific. // Glibc specific.
@ -301,7 +338,7 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H,
conversionPosition); conversionPosition);
return true; return true;
} }
PrintfConversionSpecifier CS(conversionPosition, k); PrintfConversionSpecifier CS(conversionPosition, k);
FS.setConversionSpecifier(CS); FS.setConversionSpecifier(CS);
if (CS.consumesDataArgument() && !FS.usesPositionalArg()) if (CS.consumesDataArgument() && !FS.usesPositionalArg())
@ -541,6 +578,7 @@ ArgType PrintfSpecifier::getArgType(ASTContext &Ctx,
return Ctx.IntTy; return Ctx.IntTy;
return ArgType(Ctx.WideCharTy, "wchar_t"); return ArgType(Ctx.WideCharTy, "wchar_t");
case ConversionSpecifier::pArg: case ConversionSpecifier::pArg:
case ConversionSpecifier::PArg:
return ArgType::CPointerTy; return ArgType::CPointerTy;
case ConversionSpecifier::ObjCObjArg: case ConversionSpecifier::ObjCObjArg:
return ArgType::ObjCPointerTy; return ArgType::ObjCPointerTy;
@ -900,7 +938,7 @@ bool PrintfSpecifier::hasValidPrecision() const {
if (Precision.getHowSpecified() == OptionalAmount::NotSpecified) if (Precision.getHowSpecified() == OptionalAmount::NotSpecified)
return true; return true;
// Precision is only valid with the diouxXaAeEfFgGs conversions // Precision is only valid with the diouxXaAeEfFgGsP conversions
switch (CS.getKind()) { switch (CS.getKind()) {
case ConversionSpecifier::dArg: case ConversionSpecifier::dArg:
case ConversionSpecifier::DArg: case ConversionSpecifier::DArg:
@ -922,6 +960,7 @@ bool PrintfSpecifier::hasValidPrecision() const {
case ConversionSpecifier::sArg: case ConversionSpecifier::sArg:
case ConversionSpecifier::FreeBSDrArg: case ConversionSpecifier::FreeBSDrArg:
case ConversionSpecifier::FreeBSDyArg: case ConversionSpecifier::FreeBSDyArg:
case ConversionSpecifier::PArg:
return true; return true;
default: default:

View File

@ -11,14 +11,15 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include "CodeGenFunction.h"
#include "CGCXXABI.h" #include "CGCXXABI.h"
#include "CGObjCRuntime.h" #include "CGObjCRuntime.h"
#include "CGOpenCLRuntime.h" #include "CGOpenCLRuntime.h"
#include "CodeGenFunction.h"
#include "CodeGenModule.h" #include "CodeGenModule.h"
#include "TargetInfo.h" #include "TargetInfo.h"
#include "clang/AST/ASTContext.h" #include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h" #include "clang/AST/Decl.h"
#include "clang/Analysis/Analyses/OSLog.h"
#include "clang/Basic/TargetBuiltins.h" #include "clang/Basic/TargetBuiltins.h"
#include "clang/Basic/TargetInfo.h" #include "clang/Basic/TargetInfo.h"
#include "clang/CodeGen/CGFunctionInfo.h" #include "clang/CodeGen/CGFunctionInfo.h"
@ -564,6 +565,18 @@ Value *CodeGenFunction::EmitMSVCBuiltinExpr(MSVCIntrin BuiltinID,
llvm_unreachable("Incorrect MSVC intrinsic!"); llvm_unreachable("Incorrect MSVC intrinsic!");
} }
namespace {
// ARC cleanup for __builtin_os_log_format
struct CallObjCArcUse final : EHScopeStack::Cleanup {
CallObjCArcUse(llvm::Value *object) : object(object) {}
llvm::Value *object;
void Emit(CodeGenFunction &CGF, Flags flags) override {
CGF.EmitARCIntrinsicUse(object);
}
};
}
RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD, RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD,
unsigned BuiltinID, const CallExpr *E, unsigned BuiltinID, const CallExpr *E,
ReturnValueSlot ReturnValue) { ReturnValueSlot ReturnValue) {
@ -2597,6 +2610,76 @@ RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD,
// Fall through - it's already mapped to the intrinsic by GCCBuiltin. // Fall through - it's already mapped to the intrinsic by GCCBuiltin.
break; break;
} }
case Builtin::BI__builtin_os_log_format: {
assert(E->getNumArgs() >= 2 &&
"__builtin_os_log_format takes at least 2 arguments");
analyze_os_log::OSLogBufferLayout Layout;
analyze_os_log::computeOSLogBufferLayout(CGM.getContext(), E, Layout);
Address BufAddr = EmitPointerWithAlignment(E->getArg(0));
// Ignore argument 1, the format string. It is not currently used.
CharUnits Offset;
Builder.CreateStore(
Builder.getInt8(Layout.getSummaryByte()),
Builder.CreateConstByteGEP(BufAddr, Offset++, "summary"));
Builder.CreateStore(
Builder.getInt8(Layout.getNumArgsByte()),
Builder.CreateConstByteGEP(BufAddr, Offset++, "numArgs"));
llvm::SmallVector<llvm::Value *, 4> RetainableOperands;
for (const auto &Item : Layout.Items) {
Builder.CreateStore(
Builder.getInt8(Item.getDescriptorByte()),
Builder.CreateConstByteGEP(BufAddr, Offset++, "argDescriptor"));
Builder.CreateStore(
Builder.getInt8(Item.getSizeByte()),
Builder.CreateConstByteGEP(BufAddr, Offset++, "argSize"));
Address Addr = Builder.CreateConstByteGEP(BufAddr, Offset);
if (const Expr *TheExpr = Item.getExpr()) {
Addr = Builder.CreateElementBitCast(
Addr, ConvertTypeForMem(TheExpr->getType()));
// Check if this is a retainable type.
if (TheExpr->getType()->isObjCRetainableType()) {
assert(getEvaluationKind(TheExpr->getType()) == TEK_Scalar &&
"Only scalar can be a ObjC retainable type");
llvm::Value *SV = EmitScalarExpr(TheExpr, /*Ignore*/ false);
RValue RV = RValue::get(SV);
LValue LV = MakeAddrLValue(Addr, TheExpr->getType());
EmitStoreThroughLValue(RV, LV);
// Check if the object is constant, if not, save it in
// RetainableOperands.
if (!isa<Constant>(SV))
RetainableOperands.push_back(SV);
} else {
EmitAnyExprToMem(TheExpr, Addr, Qualifiers(), /*isInit*/ true);
}
} else {
Addr = Builder.CreateElementBitCast(Addr, Int32Ty);
Builder.CreateStore(
Builder.getInt32(Item.getConstValue().getQuantity()), Addr);
}
Offset += Item.size();
}
// Push a clang.arc.use cleanup for each object in RetainableOperands. The
// cleanup will cause the use to appear after the final log call, keeping
// the object valid while its held in the log buffer. Note that if theres
// a release cleanup on the object, it will already be active; since
// cleanups are emitted in reverse order, the use will occur before the
// object is released.
if (!RetainableOperands.empty() && getLangOpts().ObjCAutoRefCount &&
CGM.getCodeGenOpts().OptimizationLevel != 0)
for (llvm::Value *object : RetainableOperands)
pushFullExprCleanup<CallObjCArcUse>(getARCCleanupKind(), object);
return RValue::get(BufAddr.getPointer());
}
case Builtin::BI__builtin_os_log_format_buffer_size: {
analyze_os_log::OSLogBufferLayout Layout;
analyze_os_log::computeOSLogBufferLayout(CGM.getContext(), E, Layout);
return RValue::get(ConstantInt::get(ConvertType(E->getType()),
Layout.size().getQuantity()));
}
} }
// If this is an alias for a lib function (e.g. __builtin_sin), emit // If this is an alias for a lib function (e.g. __builtin_sin), emit

View File

@ -1065,6 +1065,12 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
case Builtin::BIget_kernel_preferred_work_group_size_multiple: case Builtin::BIget_kernel_preferred_work_group_size_multiple:
if (SemaOpenCLBuiltinKernelWorkGroupSize(*this, TheCall)) if (SemaOpenCLBuiltinKernelWorkGroupSize(*this, TheCall))
return ExprError(); return ExprError();
case Builtin::BI__builtin_os_log_format:
case Builtin::BI__builtin_os_log_format_buffer_size:
if (SemaBuiltinOSLogFormat(TheCall)) {
return ExprError();
}
break;
} }
// Since the target specific builtins for each arch overlap, only check those // Since the target specific builtins for each arch overlap, only check those
@ -3478,6 +3484,31 @@ bool Sema::CheckObjCString(Expr *Arg) {
return false; return false;
} }
/// CheckObjCString - Checks that the format string argument to the os_log()
/// and os_trace() functions is correct, and converts it to const char *.
ExprResult Sema::CheckOSLogFormatStringArg(Expr *Arg) {
Arg = Arg->IgnoreParenCasts();
auto *Literal = dyn_cast<StringLiteral>(Arg);
if (!Literal) {
if (auto *ObjcLiteral = dyn_cast<ObjCStringLiteral>(Arg)) {
Literal = ObjcLiteral->getString();
}
}
if (!Literal || (!Literal->isAscii() && !Literal->isUTF8())) {
return ExprError(
Diag(Arg->getLocStart(), diag::err_os_log_format_not_string_constant)
<< Arg->getSourceRange());
}
ExprResult Result(Literal);
QualType ResultTy = Context.getPointerType(Context.CharTy.withConst());
InitializedEntity Entity =
InitializedEntity::InitializeParameter(Context, ResultTy, false);
Result = PerformCopyInitialization(Entity, SourceLocation(), Result);
return Result;
}
/// Check the arguments to '__builtin_va_start' or '__builtin_ms_va_start' /// Check the arguments to '__builtin_va_start' or '__builtin_ms_va_start'
/// for validity. Emit an error and return true on failure; return false /// for validity. Emit an error and return true on failure; return false
/// on success. /// on success.
@ -3939,6 +3970,86 @@ bool Sema::SemaBuiltinAssumeAligned(CallExpr *TheCall) {
return false; return false;
} }
bool Sema::SemaBuiltinOSLogFormat(CallExpr *TheCall) {
unsigned BuiltinID =
cast<FunctionDecl>(TheCall->getCalleeDecl())->getBuiltinID();
bool IsSizeCall = BuiltinID == Builtin::BI__builtin_os_log_format_buffer_size;
unsigned NumArgs = TheCall->getNumArgs();
unsigned NumRequiredArgs = IsSizeCall ? 1 : 2;
if (NumArgs < NumRequiredArgs) {
return Diag(TheCall->getLocEnd(), diag::err_typecheck_call_too_few_args)
<< 0 /* function call */ << NumRequiredArgs << NumArgs
<< TheCall->getSourceRange();
}
if (NumArgs >= NumRequiredArgs + 0x100) {
return Diag(TheCall->getLocEnd(),
diag::err_typecheck_call_too_many_args_at_most)
<< 0 /* function call */ << (NumRequiredArgs + 0xff) << NumArgs
<< TheCall->getSourceRange();
}
unsigned i = 0;
// For formatting call, check buffer arg.
if (!IsSizeCall) {
ExprResult Arg(TheCall->getArg(i));
InitializedEntity Entity = InitializedEntity::InitializeParameter(
Context, Context.VoidPtrTy, false);
Arg = PerformCopyInitialization(Entity, SourceLocation(), Arg);
if (Arg.isInvalid())
return true;
TheCall->setArg(i, Arg.get());
i++;
}
// Check string literal arg.
unsigned FormatIdx = i;
{
ExprResult Arg = CheckOSLogFormatStringArg(TheCall->getArg(i));
if (Arg.isInvalid())
return true;
TheCall->setArg(i, Arg.get());
i++;
}
// Make sure variadic args are scalar.
unsigned FirstDataArg = i;
while (i < NumArgs) {
ExprResult Arg = DefaultVariadicArgumentPromotion(
TheCall->getArg(i), VariadicFunction, nullptr);
if (Arg.isInvalid())
return true;
CharUnits ArgSize = Context.getTypeSizeInChars(Arg.get()->getType());
if (ArgSize.getQuantity() >= 0x100) {
return Diag(Arg.get()->getLocEnd(), diag::err_os_log_argument_too_big)
<< i << (int)ArgSize.getQuantity() << 0xff
<< TheCall->getSourceRange();
}
TheCall->setArg(i, Arg.get());
i++;
}
// Check formatting specifiers. NOTE: We're only doing this for the non-size
// call to avoid duplicate diagnostics.
if (!IsSizeCall) {
llvm::SmallBitVector CheckedVarArgs(NumArgs, false);
ArrayRef<const Expr *> Args(TheCall->getArgs(), TheCall->getNumArgs());
bool Success = CheckFormatArguments(
Args, /*HasVAListArg*/ false, FormatIdx, FirstDataArg, FST_OSLog,
VariadicFunction, TheCall->getLocStart(), SourceRange(),
CheckedVarArgs);
if (!Success)
return true;
}
if (IsSizeCall) {
TheCall->setType(Context.getSizeType());
} else {
TheCall->setType(Context.VoidPtrTy);
}
return false;
}
/// SemaBuiltinConstantArg - Handle a check if argument ArgNum of CallExpr /// SemaBuiltinConstantArg - Handle a check if argument ArgNum of CallExpr
/// TheCall is a constant expression. /// TheCall is a constant expression.
bool Sema::SemaBuiltinConstantArg(CallExpr *TheCall, int ArgNum, bool Sema::SemaBuiltinConstantArg(CallExpr *TheCall, int ArgNum,
@ -4569,15 +4680,16 @@ checkFormatStringExpr(Sema &S, const Expr *E, ArrayRef<const Expr *> Args,
Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) { Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) {
return llvm::StringSwitch<FormatStringType>(Format->getType()->getName()) return llvm::StringSwitch<FormatStringType>(Format->getType()->getName())
.Case("scanf", FST_Scanf) .Case("scanf", FST_Scanf)
.Cases("printf", "printf0", FST_Printf) .Cases("printf", "printf0", FST_Printf)
.Cases("NSString", "CFString", FST_NSString) .Cases("NSString", "CFString", FST_NSString)
.Case("strftime", FST_Strftime) .Case("strftime", FST_Strftime)
.Case("strfmon", FST_Strfmon) .Case("strfmon", FST_Strfmon)
.Cases("kprintf", "cmn_err", "vcmn_err", "zcmn_err", FST_Kprintf) .Cases("kprintf", "cmn_err", "vcmn_err", "zcmn_err", FST_Kprintf)
.Case("freebsd_kprintf", FST_FreeBSDKPrintf) .Case("freebsd_kprintf", FST_FreeBSDKPrintf)
.Case("os_trace", FST_OSTrace) .Case("os_trace", FST_OSLog)
.Default(FST_Unknown); .Case("os_log", FST_OSLog)
.Default(FST_Unknown);
} }
/// CheckFormatArguments - Check calls to printf and scanf (and similar /// CheckFormatArguments - Check calls to printf and scanf (and similar
@ -4687,6 +4799,7 @@ protected:
Sema &S; Sema &S;
const FormatStringLiteral *FExpr; const FormatStringLiteral *FExpr;
const Expr *OrigFormatExpr; const Expr *OrigFormatExpr;
const Sema::FormatStringType FSType;
const unsigned FirstDataArg; const unsigned FirstDataArg;
const unsigned NumDataArgs; const unsigned NumDataArgs;
const char *Beg; // Start of format string. const char *Beg; // Start of format string.
@ -4703,20 +4816,19 @@ protected:
public: public:
CheckFormatHandler(Sema &s, const FormatStringLiteral *fexpr, CheckFormatHandler(Sema &s, const FormatStringLiteral *fexpr,
const Expr *origFormatExpr, unsigned firstDataArg, const Expr *origFormatExpr,
const Sema::FormatStringType type, unsigned firstDataArg,
unsigned numDataArgs, const char *beg, bool hasVAListArg, unsigned numDataArgs, const char *beg, bool hasVAListArg,
ArrayRef<const Expr *> Args, ArrayRef<const Expr *> Args, unsigned formatIdx,
unsigned formatIdx, bool inFunctionCall, bool inFunctionCall, Sema::VariadicCallType callType,
Sema::VariadicCallType callType,
llvm::SmallBitVector &CheckedVarArgs, llvm::SmallBitVector &CheckedVarArgs,
UncoveredArgHandler &UncoveredArg) UncoveredArgHandler &UncoveredArg)
: S(s), FExpr(fexpr), OrigFormatExpr(origFormatExpr), : S(s), FExpr(fexpr), OrigFormatExpr(origFormatExpr), FSType(type),
FirstDataArg(firstDataArg), NumDataArgs(numDataArgs), FirstDataArg(firstDataArg), NumDataArgs(numDataArgs), Beg(beg),
Beg(beg), HasVAListArg(hasVAListArg), HasVAListArg(hasVAListArg), Args(Args), FormatIdx(formatIdx),
Args(Args), FormatIdx(formatIdx), usesPositionalArgs(false), atFirstArg(true),
usesPositionalArgs(false), atFirstArg(true), inFunctionCall(inFunctionCall), CallType(callType),
inFunctionCall(inFunctionCall), CallType(callType), CheckedVarArgs(CheckedVarArgs), UncoveredArg(UncoveredArg) {
CheckedVarArgs(CheckedVarArgs), UncoveredArg(UncoveredArg) {
CoveredArgs.resize(numDataArgs); CoveredArgs.resize(numDataArgs);
CoveredArgs.reset(); CoveredArgs.reset();
} }
@ -5139,24 +5251,28 @@ void CheckFormatHandler::EmitFormatDiagnostic(
namespace { namespace {
class CheckPrintfHandler : public CheckFormatHandler { class CheckPrintfHandler : public CheckFormatHandler {
bool ObjCContext;
public: public:
CheckPrintfHandler(Sema &s, const FormatStringLiteral *fexpr, CheckPrintfHandler(Sema &s, const FormatStringLiteral *fexpr,
const Expr *origFormatExpr, unsigned firstDataArg, const Expr *origFormatExpr,
unsigned numDataArgs, bool isObjC, const Sema::FormatStringType type, unsigned firstDataArg,
const char *beg, bool hasVAListArg, unsigned numDataArgs, bool isObjC, const char *beg,
ArrayRef<const Expr *> Args, bool hasVAListArg, ArrayRef<const Expr *> Args,
unsigned formatIdx, bool inFunctionCall, unsigned formatIdx, bool inFunctionCall,
Sema::VariadicCallType CallType, Sema::VariadicCallType CallType,
llvm::SmallBitVector &CheckedVarArgs, llvm::SmallBitVector &CheckedVarArgs,
UncoveredArgHandler &UncoveredArg) UncoveredArgHandler &UncoveredArg)
: CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg, : CheckFormatHandler(s, fexpr, origFormatExpr, type, firstDataArg,
numDataArgs, beg, hasVAListArg, Args, numDataArgs, beg, hasVAListArg, Args, formatIdx,
formatIdx, inFunctionCall, CallType, CheckedVarArgs, inFunctionCall, CallType, CheckedVarArgs,
UncoveredArg), UncoveredArg) {}
ObjCContext(isObjC)
{} bool isObjCContext() const { return FSType == Sema::FST_NSString; }
/// Returns true if '%@' specifiers are allowed in the format string.
bool allowsObjCArg() const {
return FSType == Sema::FST_NSString || FSType == Sema::FST_OSLog ||
FSType == Sema::FST_OSTrace;
}
bool HandleInvalidPrintfConversionSpecifier( bool HandleInvalidPrintfConversionSpecifier(
const analyze_printf::PrintfSpecifier &FS, const analyze_printf::PrintfSpecifier &FS,
@ -5510,11 +5626,54 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier
// Check for using an Objective-C specific conversion specifier // Check for using an Objective-C specific conversion specifier
// in a non-ObjC literal. // in a non-ObjC literal.
if (!ObjCContext && CS.isObjCArg()) { if (!allowsObjCArg() && CS.isObjCArg()) {
return HandleInvalidPrintfConversionSpecifier(FS, startSpecifier, return HandleInvalidPrintfConversionSpecifier(FS, startSpecifier,
specifierLen); specifierLen);
} }
// %P can only be used with os_log.
if (FSType != Sema::FST_OSLog && CS.getKind() == ConversionSpecifier::PArg) {
return HandleInvalidPrintfConversionSpecifier(FS, startSpecifier,
specifierLen);
}
// %n is not allowed with os_log.
if (FSType == Sema::FST_OSLog && CS.getKind() == ConversionSpecifier::nArg) {
EmitFormatDiagnostic(S.PDiag(diag::warn_os_log_format_narg),
getLocationOfByte(CS.getStart()),
/*IsStringLocation*/ false,
getSpecifierRange(startSpecifier, specifierLen));
return true;
}
// Only scalars are allowed for os_trace.
if (FSType == Sema::FST_OSTrace &&
(CS.getKind() == ConversionSpecifier::PArg ||
CS.getKind() == ConversionSpecifier::sArg ||
CS.getKind() == ConversionSpecifier::ObjCObjArg)) {
return HandleInvalidPrintfConversionSpecifier(FS, startSpecifier,
specifierLen);
}
// 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)
<< "public",
getLocationOfByte(FS.isPublic().getPosition()),
/*IsStringLocation*/ false,
getSpecifierRange(startSpecifier, specifierLen));
}
if (FS.isPrivate().isSet()) {
EmitFormatDiagnostic(S.PDiag(diag::warn_format_invalid_annotation)
<< "private",
getLocationOfByte(FS.isPrivate().getPosition()),
/*IsStringLocation*/ false,
getSpecifierRange(startSpecifier, specifierLen));
}
}
// Check for invalid use of field width // Check for invalid use of field width
if (!FS.hasValidFieldWidth()) { if (!FS.hasValidFieldWidth()) {
HandleInvalidAmount(FS, FS.getFieldWidth(), /* field width */ 0, HandleInvalidAmount(FS, FS.getFieldWidth(), /* field width */ 0,
@ -5527,6 +5686,15 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier
startSpecifier, specifierLen); startSpecifier, specifierLen);
} }
// 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),
getLocationOfByte(startSpecifier),
/*IsStringLocation*/ false,
getSpecifierRange(startSpecifier, specifierLen));
}
// Check each flag does not conflict with any other component. // Check each flag does not conflict with any other component.
if (!FS.hasValidThousandsGroupingPrefix()) if (!FS.hasValidThousandsGroupingPrefix())
HandleFlag(FS, FS.hasThousandsGrouping(), startSpecifier, specifierLen); HandleFlag(FS, FS.hasThousandsGrouping(), startSpecifier, specifierLen);
@ -5676,8 +5844,7 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
using namespace analyze_printf; using namespace analyze_printf;
// Now type check the data expression that matches the // Now type check the data expression that matches the
// format specifier. // format specifier.
const analyze_printf::ArgType &AT = FS.getArgType(S.Context, const analyze_printf::ArgType &AT = FS.getArgType(S.Context, isObjCContext());
ObjCContext);
if (!AT.isValid()) if (!AT.isValid())
return true; return true;
@ -5732,7 +5899,7 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
// If the argument is an integer of some kind, believe the %C and suggest // If the argument is an integer of some kind, believe the %C and suggest
// a cast instead of changing the conversion specifier. // a cast instead of changing the conversion specifier.
QualType IntendedTy = ExprTy; QualType IntendedTy = ExprTy;
if (ObjCContext && if (isObjCContext() &&
FS.getConversionSpecifier().getKind() == ConversionSpecifier::CArg) { FS.getConversionSpecifier().getKind() == ConversionSpecifier::CArg) {
if (ExprTy->isIntegralOrUnscopedEnumerationType() && if (ExprTy->isIntegralOrUnscopedEnumerationType() &&
!ExprTy->isCharType()) { !ExprTy->isCharType()) {
@ -5773,8 +5940,8 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
// We may be able to offer a FixItHint if it is a supported type. // We may be able to offer a FixItHint if it is a supported type.
PrintfSpecifier fixedFS = FS; PrintfSpecifier fixedFS = FS;
bool success = fixedFS.fixType(IntendedTy, S.getLangOpts(), bool success =
S.Context, ObjCContext); fixedFS.fixType(IntendedTy, S.getLangOpts(), S.Context, isObjCContext());
if (success) { if (success) {
// Get the fix string from the fixed format specifier // Get the fix string from the fixed format specifier
@ -5930,19 +6097,18 @@ namespace {
class CheckScanfHandler : public CheckFormatHandler { class CheckScanfHandler : public CheckFormatHandler {
public: public:
CheckScanfHandler(Sema &s, const FormatStringLiteral *fexpr, CheckScanfHandler(Sema &s, const FormatStringLiteral *fexpr,
const Expr *origFormatExpr, unsigned firstDataArg, const Expr *origFormatExpr, Sema::FormatStringType type,
unsigned numDataArgs, const char *beg, bool hasVAListArg, unsigned firstDataArg, unsigned numDataArgs,
ArrayRef<const Expr *> Args, const char *beg, bool hasVAListArg,
unsigned formatIdx, bool inFunctionCall, ArrayRef<const Expr *> Args, unsigned formatIdx,
Sema::VariadicCallType CallType, bool inFunctionCall, Sema::VariadicCallType CallType,
llvm::SmallBitVector &CheckedVarArgs, llvm::SmallBitVector &CheckedVarArgs,
UncoveredArgHandler &UncoveredArg) UncoveredArgHandler &UncoveredArg)
: CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg, : CheckFormatHandler(s, fexpr, origFormatExpr, type, firstDataArg,
numDataArgs, beg, hasVAListArg, numDataArgs, beg, hasVAListArg, Args, formatIdx,
Args, formatIdx, inFunctionCall, CallType, inFunctionCall, CallType, CheckedVarArgs,
CheckedVarArgs, UncoveredArg) UncoveredArg) {}
{}
bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS, bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS,
const char *startSpecifier, const char *startSpecifier,
unsigned specifierLen) override; unsigned specifierLen) override;
@ -6152,13 +6318,13 @@ static void CheckFormatString(Sema &S, const FormatStringLiteral *FExpr,
} }
if (Type == Sema::FST_Printf || Type == Sema::FST_NSString || if (Type == Sema::FST_Printf || Type == Sema::FST_NSString ||
Type == Sema::FST_FreeBSDKPrintf || Type == Sema::FST_OSTrace) { Type == Sema::FST_FreeBSDKPrintf || Type == Sema::FST_OSLog ||
CheckPrintfHandler H(S, FExpr, OrigFormatExpr, firstDataArg, Type == Sema::FST_OSTrace) {
numDataArgs, (Type == Sema::FST_NSString || CheckPrintfHandler H(
Type == Sema::FST_OSTrace), S, FExpr, OrigFormatExpr, Type, firstDataArg, numDataArgs,
Str, HasVAListArg, Args, format_idx, (Type == Sema::FST_NSString || Type == Sema::FST_OSTrace), Str,
inFunctionCall, CallType, CheckedVarArgs, HasVAListArg, Args, format_idx, inFunctionCall, CallType,
UncoveredArg); CheckedVarArgs, UncoveredArg);
if (!analyze_format_string::ParsePrintfString(H, Str, Str + StrLen, if (!analyze_format_string::ParsePrintfString(H, Str, Str + StrLen,
S.getLangOpts(), S.getLangOpts(),
@ -6166,10 +6332,9 @@ static void CheckFormatString(Sema &S, const FormatStringLiteral *FExpr,
Type == Sema::FST_FreeBSDKPrintf)) Type == Sema::FST_FreeBSDKPrintf))
H.DoneProcessing(); H.DoneProcessing();
} else if (Type == Sema::FST_Scanf) { } else if (Type == Sema::FST_Scanf) {
CheckScanfHandler H(S, FExpr, OrigFormatExpr, firstDataArg, numDataArgs, CheckScanfHandler H(S, FExpr, OrigFormatExpr, Type, firstDataArg,
Str, HasVAListArg, Args, format_idx, numDataArgs, Str, HasVAListArg, Args, format_idx,
inFunctionCall, CallType, CheckedVarArgs, inFunctionCall, CallType, CheckedVarArgs, UncoveredArg);
UncoveredArg);
if (!analyze_format_string::ParseScanfString(H, Str, Str + StrLen, if (!analyze_format_string::ParseScanfString(H, Str, Str + StrLen,
S.getLangOpts(), S.getLangOpts(),

View File

@ -2804,20 +2804,21 @@ enum FormatAttrKind {
/// types. /// types.
static FormatAttrKind getFormatAttrKind(StringRef Format) { static FormatAttrKind getFormatAttrKind(StringRef Format) {
return llvm::StringSwitch<FormatAttrKind>(Format) return llvm::StringSwitch<FormatAttrKind>(Format)
// Check for formats that get handled specially. // Check for formats that get handled specially.
.Case("NSString", NSStringFormat) .Case("NSString", NSStringFormat)
.Case("CFString", CFStringFormat) .Case("CFString", CFStringFormat)
.Case("strftime", StrftimeFormat) .Case("strftime", StrftimeFormat)
// Otherwise, check for supported formats. // Otherwise, check for supported formats.
.Cases("scanf", "printf", "printf0", "strfmon", SupportedFormat) .Cases("scanf", "printf", "printf0", "strfmon", SupportedFormat)
.Cases("cmn_err", "vcmn_err", "zcmn_err", SupportedFormat) .Cases("cmn_err", "vcmn_err", "zcmn_err", SupportedFormat)
.Case("kprintf", SupportedFormat) // OpenBSD. .Case("kprintf", SupportedFormat) // OpenBSD.
.Case("freebsd_kprintf", SupportedFormat) // FreeBSD. .Case("freebsd_kprintf", SupportedFormat) // FreeBSD.
.Case("os_trace", SupportedFormat) .Case("os_trace", SupportedFormat)
.Case("os_log", SupportedFormat)
.Cases("gcc_diag", "gcc_cdiag", "gcc_cxxdiag", "gcc_tdiag", IgnoredFormat) .Cases("gcc_diag", "gcc_cdiag", "gcc_cxxdiag", "gcc_tdiag", IgnoredFormat)
.Default(InvalidFormat); .Default(InvalidFormat);
} }
/// Handle __attribute__((init_priority(priority))) attributes based on /// Handle __attribute__((init_priority(priority))) attributes based on

View File

@ -355,8 +355,6 @@ void test_float_builtin_ops(float F, double D, long double LD) {
} }
// __builtin_longjmp isn't supported on all platforms, so only test it on X86.
#ifdef __x86_64__
// CHECK-LABEL: define void @test_builtin_longjmp // CHECK-LABEL: define void @test_builtin_longjmp
void test_builtin_longjmp(void **buffer) { void test_builtin_longjmp(void **buffer) {
// CHECK: [[BITCAST:%.*]] = bitcast // CHECK: [[BITCAST:%.*]] = bitcast
@ -364,10 +362,147 @@ void test_builtin_longjmp(void **buffer) {
__builtin_longjmp(buffer, 1); __builtin_longjmp(buffer, 1);
// CHECK-NEXT: unreachable // CHECK-NEXT: unreachable
} }
#endif
// CHECK-LABEL: define i64 @test_builtin_readcyclecounter // CHECK-LABEL: define i64 @test_builtin_readcyclecounter
long long test_builtin_readcyclecounter() { long long test_builtin_readcyclecounter() {
// CHECK: call i64 @llvm.readcyclecounter() // CHECK: call i64 @llvm.readcyclecounter()
return __builtin_readcyclecounter(); return __builtin_readcyclecounter();
} }
// CHECK-LABEL: define void @test_builtin_os_log
// CHECK: (i8* [[BUF:%.*]], i32 [[I:%.*]], i8* [[DATA:%.*]])
void test_builtin_os_log(void *buf, int i, const char *data) {
volatile int len;
// CHECK: store i8* [[BUF]], i8** [[BUF_ADDR:%.*]], align 8
// CHECK: store i32 [[I]], i32* [[I_ADDR:%.*]], align 4
// CHECK: store i8* [[DATA]], i8** [[DATA_ADDR:%.*]], align 8
// CHECK: store volatile i32 34
len = __builtin_os_log_format_buffer_size("%d %{public}s %{private}.16P", i, data, data);
// CHECK: [[BUF2:%.*]] = load i8*, i8** [[BUF_ADDR]]
// CHECK: [[SUMMARY:%.*]] = getelementptr i8, i8* [[BUF2]], i64 0
// CHECK: store i8 3, i8* [[SUMMARY]]
// CHECK: [[NUM_ARGS:%.*]] = getelementptr i8, i8* [[BUF2]], i64 1
// CHECK: store i8 4, i8* [[NUM_ARGS]]
//
// CHECK: [[ARG1_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 2
// CHECK: store i8 0, i8* [[ARG1_DESC]]
// CHECK: [[ARG1_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 3
// CHECK: store i8 4, i8* [[ARG1_SIZE]]
// CHECK: [[ARG1:%.*]] = getelementptr i8, i8* [[BUF2]], i64 4
// CHECK: [[ARG1_INT:%.*]] = bitcast i8* [[ARG1]] to i32*
// CHECK: [[I2:%.*]] = load i32, i32* [[I_ADDR]]
// CHECK: store i32 [[I2]], i32* [[ARG1_INT]]
// CHECK: [[ARG2_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 8
// CHECK: store i8 34, i8* [[ARG2_DESC]]
// CHECK: [[ARG2_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 9
// CHECK: store i8 8, i8* [[ARG2_SIZE]]
// CHECK: [[ARG2:%.*]] = getelementptr i8, i8* [[BUF2]], i64 10
// CHECK: [[ARG2_PTR:%.*]] = bitcast i8* [[ARG2]] to i8**
// CHECK: [[DATA2:%.*]] = load i8*, i8** [[DATA_ADDR]]
// CHECK: store i8* [[DATA2]], i8** [[ARG2_PTR]]
// CHECK: [[ARG3_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 18
// CHECK: store i8 17, i8* [[ARG3_DESC]]
// CHECK: [[ARG3_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 19
// CHECK: store i8 4, i8* [[ARG3_SIZE]]
// CHECK: [[ARG3:%.*]] = getelementptr i8, i8* [[BUF2]], i64 20
// CHECK: [[ARG3_INT:%.*]] = bitcast i8* [[ARG3]] to i32*
// CHECK: store i32 16, i32* [[ARG3_INT]]
// CHECK: [[ARG4_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 24
// CHECK: store i8 49, i8* [[ARG4_DESC]]
// CHECK: [[ARG4_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 25
// CHECK: store i8 8, i8* [[ARG4_SIZE]]
// CHECK: [[ARG4:%.*]] = getelementptr i8, i8* [[BUF2]], i64 26
// CHECK: [[ARG4_PTR:%.*]] = bitcast i8* [[ARG4]] to i8**
// CHECK: [[DATA3:%.*]] = load i8*, i8** [[DATA_ADDR]]
// CHECK: store i8* [[DATA3]], i8** [[ARG4_PTR]]
__builtin_os_log_format(buf, "%d %{public}s %{private}.16P", i, data, data);
}
// CHECK-LABEL: define void @test_builtin_os_log_errno
// CHECK: (i8* [[BUF:%.*]], i8* [[DATA:%.*]])
void test_builtin_os_log_errno(void *buf, const char *data) {
volatile int len;
// CHECK: store i8* [[BUF]], i8** [[BUF_ADDR:%.*]], align 8
// CHECK: store i8* [[DATA]], i8** [[DATA_ADDR:%.*]], align 8
// CHECK: store volatile i32 2
len = __builtin_os_log_format_buffer_size("%S");
// CHECK: [[BUF2:%.*]] = load i8*, i8** [[BUF_ADDR]]
// CHECK: [[SUMMARY:%.*]] = getelementptr i8, i8* [[BUF2]], i64 0
// CHECK: store i8 2, i8* [[SUMMARY]]
// CHECK: [[NUM_ARGS:%.*]] = getelementptr i8, i8* [[BUF2]], i64 1
// CHECK: store i8 1, i8* [[NUM_ARGS]]
// CHECK: [[ARG1_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 2
// CHECK: store i8 96, i8* [[ARG1_DESC]]
// CHECK: [[ARG1_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 3
// CHECK: store i8 0, i8* [[ARG1_SIZE]]
// CHECK: [[ARG1:%.*]] = getelementptr i8, i8* [[BUF2]], i64 4
// CHECK: [[ARG1_INT:%.*]] = bitcast i8* [[ARG1]] to i32*
// CHECK: store i32 0, i32* [[ARG1_INT]]
__builtin_os_log_format(buf, "%m");
}
// CHECK-LABEL: define void @test_builtin_os_log_wide
// CHECK: (i8* [[BUF:%.*]], i8* [[DATA:%.*]], i32* [[STR:%.*]])
typedef int wchar_t;
void test_builtin_os_log_wide(void *buf, const char *data, wchar_t *str) {
volatile int len;
// CHECK: store i8* [[BUF]], i8** [[BUF_ADDR:%.*]], align 8
// CHECK: store i8* [[DATA]], i8** [[DATA_ADDR:%.*]], align 8
// CHECK: store i32* [[STR]], i32** [[STR_ADDR:%.*]],
// CHECK: store volatile i32 12
len = __builtin_os_log_format_buffer_size("%S", str);
// CHECK: [[BUF2:%.*]] = load i8*, i8** [[BUF_ADDR]]
// CHECK: [[SUMMARY:%.*]] = getelementptr i8, i8* [[BUF2]], i64 0
// CHECK: store i8 2, i8* [[SUMMARY]]
// CHECK: [[NUM_ARGS:%.*]] = getelementptr i8, i8* [[BUF2]], i64 1
// CHECK: store i8 1, i8* [[NUM_ARGS]]
// CHECK: [[ARG1_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 2
// CHECK: store i8 80, i8* [[ARG1_DESC]]
// CHECK: [[ARG1_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 3
// CHECK: store i8 8, i8* [[ARG1_SIZE]]
// CHECK: [[ARG1:%.*]] = getelementptr i8, i8* [[BUF2]], i64 4
// CHECK: [[ARG1_PTR:%.*]] = bitcast i8* [[ARG1]] to i32**
// CHECK: [[STR2:%.*]] = load i32*, i32** [[STR_ADDR]]
// CHECK: store i32* [[STR2]], i32** [[ARG1_PTR]]
__builtin_os_log_format(buf, "%S", str);
}
// CHECK-LABEL: define void @test_builtin_os_log_percent
// Check that the %% which does not consume any argument is correctly handled
void test_builtin_os_log_percent(void *buf, const char *data) {
volatile int len;
// CHECK: store i8* [[BUF]], i8** [[BUF_ADDR:%.*]], align 8
// CHECK: store i8* [[DATA]], i8** [[DATA_ADDR:%.*]], align 8
// CHECK: store volatile i32 12
len = __builtin_os_log_format_buffer_size("%s %%", data);
// CHECK: [[BUF2:%.*]] = load i8*, i8** [[BUF_ADDR]]
// CHECK: [[SUMMARY:%.*]] = getelementptr i8, i8* [[BUF2]], i64 0
// CHECK: store i8 2, i8* [[SUMMARY]]
// CHECK: [[NUM_ARGS:%.*]] = getelementptr i8, i8* [[BUF2]], i64 1
// CHECK: store i8 1, i8* [[NUM_ARGS]]
//
// CHECK: [[ARG1_DESC:%.*]] = getelementptr i8, i8* [[BUF2]], i64 2
// CHECK: store i8 32, i8* [[ARG1_DESC]]
// CHECK: [[ARG1_SIZE:%.*]] = getelementptr i8, i8* [[BUF2]], i64 3
// CHECK: store i8 8, i8* [[ARG1_SIZE]]
// CHECK: [[ARG1:%.*]] = getelementptr i8, i8* [[BUF2]], i64 4
// CHECK: [[ARG1_PTR:%.*]] = bitcast i8* [[ARG1]] to i8**
// CHECK: [[DATA2:%.*]] = load i8*, i8** [[DATA_ADDR]]
// CHECK: store i8* [[DATA2]], i8** [[ARG1_PTR]]
__builtin_os_log_format(buf, "%s %%", data);
}

View File

@ -0,0 +1,39 @@
// RUN: %clang_cc1 %s -emit-llvm -o - -triple x86_64-darwin-apple -fobjc-arc -O2 | FileCheck %s
// Make sure we emit clang.arc.use before calling objc_release as part of the
// cleanup. This way we make sure the object will not be released until the
// end of the full expression.
// rdar://problem/24528966
@class NSString;
extern __attribute__((visibility("default"))) NSString *GenString();
// Behavior of __builtin_os_log differs between platforms, so only test on X86
#ifdef __x86_64__
// CHECK-LABEL: define i8* @test_builtin_os_log
void *test_builtin_os_log(void *buf) {
return __builtin_os_log_format(buf, "capabilities: %@", GenString());
// CHECK: store i8 2, i8*
// CHECK: [[NUM_ARGS:%.*]] = getelementptr i8, i8* {{.*}}, i64 1
// CHECK: store i8 1, i8* [[NUM_ARGS]]
//
// CHECK: [[ARG1_DESC:%.*]] = getelementptr i8, i8* {{.*}}, i64 2
// CHECK: store i8 64, i8* [[ARG1_DESC]]
// CHECK: [[ARG1_SIZE:%.*]] = getelementptr i8, i8* {{.*}}, i64 3
// CHECK: store i8 8, i8* [[ARG1_SIZE]]
// CHECK: [[ARG1:%.*]] = getelementptr i8, i8* {{.*}}, i64 4
// CHECK: [[ARG1_CAST:%.*]] = bitcast i8* [[ARG1]] to
// CHECK: [[STRING:%.*]] = {{.*}} call {{.*}} @GenString()
// CHECK: [[STRING_CAST:%.*]] = bitcast {{.*}} [[STRING]] to
// CHECK: call {{.*}} @objc_retainAutoreleasedReturnValue(i8* [[STRING_CAST]])
// CHECK: store {{.*}} [[STRING]], {{.*}} [[ARG1_CAST]]
// CHECK: call void (...) @clang.arc.use({{.*}} [[STRING]])
// CHECK: call void @objc_release(i8* [[STRING_CAST]])
// CHECK: ret i8*
}
#endif

View File

@ -0,0 +1,62 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
#include <stdarg.h>
#include <stddef.h>
#define __need_wint_t
#include <stddef.h> // For wint_t and wchar_t
int printf(const char *restrict, ...);
@interface NSString
@end
void test_os_log_format(const char *pc, int i, void *p, void *buf) {
__builtin_os_log_format(buf, "");
__builtin_os_log_format(buf, "%d"); // expected-warning {{more '%' conversions than data arguments}}
__builtin_os_log_format(buf, "%d", i);
__builtin_os_log_format(buf, "%P", p); // expected-warning {{using '%P' format specifier without precision}}
__builtin_os_log_format(buf, "%.10P", p);
__builtin_os_log_format(buf, "%.*P", p); // expected-warning {{field precision should have type 'int', but argument has type 'void *'}}
__builtin_os_log_format(buf, "%.*P", i, p);
__builtin_os_log_format(buf, "%.*P", i, i); // expected-warning {{format specifies type 'void *' but the argument has type 'int'}}
__builtin_os_log_format(buf, "%n"); // expected-error {{os_log() '%n' format specifier is not allowed}}
__builtin_os_log_format(buf, pc); // expected-error {{os_log() format argument is not a string constant}}
printf("%{private}s", pc); // expected-warning {{using 'private' format specifier annotation outside of os_log()/os_trace()}}
__builtin_os_log_format(buf, "%{private}s", pc);
// <rdar://problem/23835805>
__builtin_os_log_format_buffer_size("no-args");
__builtin_os_log_format(buf, "%s", "hi");
// <rdar://problem/24828090>
wchar_t wc = 'a';
__builtin_os_log_format(buf, "%C", wc);
printf("%C", wc);
wchar_t wcs[] = {'a', 0};
__builtin_os_log_format(buf, "%S", wcs);
printf("%S", wcs);
}
// Test os_log_format primitive with ObjC string literal format argument.
void test_objc(const char *pc, int i, void *p, void *buf, NSString *nss) {
__builtin_os_log_format(buf, @"");
__builtin_os_log_format(buf, @"%d"); // expected-warning {{more '%' conversions than data arguments}}
__builtin_os_log_format(buf, @"%d", i);
__builtin_os_log_format(buf, @"%P", p); // expected-warning {{using '%P' format specifier without precision}}
__builtin_os_log_format(buf, @"%.10P", p);
__builtin_os_log_format(buf, @"%.*P", p); // expected-warning {{field precision should have type 'int', but argument has type 'void *'}}
__builtin_os_log_format(buf, @"%.*P", i, p);
__builtin_os_log_format(buf, @"%.*P", i, i); // expected-warning {{format specifies type 'void *' but the argument has type 'int'}}
__builtin_os_log_format(buf, @"%{private}s", pc);
__builtin_os_log_format(buf, @"%@", nss);
}
// Test the os_log format attribute.
void MyOSLog(const char *format, ...) __attribute__((format(os_log, 1, 2)));
void test_attribute(void *p) {
MyOSLog("%s\n", "Hello");
MyOSLog("%d"); // expected-warning {{more '%' conversions than data arguments}}
MyOSLog("%P", p); // expected-warning {{using '%P' format specifier without precision}}
}