forked from OSchip/llvm-project
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:
parent
b05bac940d
commit
29034362ae
|
@ -35,7 +35,7 @@ class OptionalFlag {
|
|||
public:
|
||||
OptionalFlag(const char *Representation)
|
||||
: representation(Representation), flag(false) {}
|
||||
bool isSet() { return flag; }
|
||||
bool isSet() const { return flag; }
|
||||
void set() { flag = true; }
|
||||
void clear() { flag = false; }
|
||||
void setPosition(const char *position) {
|
||||
|
@ -127,7 +127,8 @@ public:
|
|||
dArg,
|
||||
DArg, // Apple extension
|
||||
iArg,
|
||||
IntArgBeg = dArg, IntArgEnd = iArg,
|
||||
IntArgBeg = dArg,
|
||||
IntArgEnd = iArg,
|
||||
|
||||
oArg,
|
||||
OArg, // Apple extension
|
||||
|
@ -135,7 +136,8 @@ public:
|
|||
UArg, // Apple extension
|
||||
xArg,
|
||||
XArg,
|
||||
UIntArgBeg = oArg, UIntArgEnd = XArg,
|
||||
UIntArgBeg = oArg,
|
||||
UIntArgEnd = XArg,
|
||||
|
||||
fArg,
|
||||
FArg,
|
||||
|
@ -145,7 +147,8 @@ public:
|
|||
GArg,
|
||||
aArg,
|
||||
AArg,
|
||||
DoubleArgBeg = fArg, DoubleArgEnd = AArg,
|
||||
DoubleArgBeg = fArg,
|
||||
DoubleArgEnd = AArg,
|
||||
|
||||
sArg,
|
||||
pArg,
|
||||
|
@ -154,13 +157,19 @@ public:
|
|||
CArg,
|
||||
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 **
|
||||
|
||||
ZArg, // MS extension
|
||||
|
||||
// Objective-C specific specifiers.
|
||||
ObjCObjArg, // '@'
|
||||
ObjCBeg = ObjCObjArg, ObjCEnd = ObjCObjArg,
|
||||
ObjCBeg = ObjCObjArg,
|
||||
ObjCEnd = ObjCObjArg,
|
||||
|
||||
// FreeBSD kernel specific specifiers.
|
||||
FreeBSDbArg,
|
||||
|
@ -171,11 +180,13 @@ public:
|
|||
// GlibC specific specifiers.
|
||||
PrintErrno, // 'm'
|
||||
|
||||
PrintfConvBeg = ObjCObjArg, PrintfConvEnd = PrintErrno,
|
||||
PrintfConvBeg = ObjCObjArg,
|
||||
PrintfConvEnd = PrintErrno,
|
||||
|
||||
// ** Scanf-specific **
|
||||
ScanListArg, // '['
|
||||
ScanfConvBeg = ScanListArg, ScanfConvEnd = ScanListArg
|
||||
ScanfConvBeg = ScanListArg,
|
||||
ScanfConvEnd = ScanListArg
|
||||
};
|
||||
|
||||
ConversionSpecifier(bool isPrintf = true)
|
||||
|
@ -437,13 +448,15 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier {
|
|||
OptionalFlag HasAlternativeForm; // '#'
|
||||
OptionalFlag HasLeadingZeroes; // '0'
|
||||
OptionalFlag HasObjCTechnicalTerm; // '[tt]'
|
||||
OptionalFlag IsPrivate; // '{private}'
|
||||
OptionalFlag IsPublic; // '{public}'
|
||||
OptionalAmount Precision;
|
||||
public:
|
||||
PrintfSpecifier() :
|
||||
FormatSpecifier(/* isPrintf = */ true),
|
||||
HasThousandsGrouping("'"), IsLeftJustified("-"), HasPlusPrefix("+"),
|
||||
HasSpacePrefix(" "), HasAlternativeForm("#"), HasLeadingZeroes("0"),
|
||||
HasObjCTechnicalTerm("tt") {}
|
||||
PrintfSpecifier()
|
||||
: FormatSpecifier(/* isPrintf = */ true), HasThousandsGrouping("'"),
|
||||
IsLeftJustified("-"), HasPlusPrefix("+"), HasSpacePrefix(" "),
|
||||
HasAlternativeForm("#"), HasLeadingZeroes("0"),
|
||||
HasObjCTechnicalTerm("tt"), IsPrivate("private"), IsPublic("public") {}
|
||||
|
||||
static PrintfSpecifier Parse(const char *beg, const char *end);
|
||||
|
||||
|
@ -472,6 +485,8 @@ public:
|
|||
void setHasObjCTechnicalTerm(const char *position) {
|
||||
HasObjCTechnicalTerm.setPosition(position);
|
||||
}
|
||||
void setIsPrivate(const char *position) { IsPrivate.setPosition(position); }
|
||||
void setIsPublic(const char *position) { IsPublic.setPosition(position); }
|
||||
void setUsesPositionalArg() { UsesPositionalArg = true; }
|
||||
|
||||
// Methods for querying the format specifier.
|
||||
|
@ -509,6 +524,8 @@ public:
|
|||
const OptionalFlag &hasLeadingZeros() const { return HasLeadingZeroes; }
|
||||
const OptionalFlag &hasSpacePrefix() const { return HasSpacePrefix; }
|
||||
const OptionalFlag &hasObjCTechnicalTerm() const { return HasObjCTechnicalTerm; }
|
||||
const OptionalFlag &isPrivate() const { return IsPrivate; }
|
||||
const OptionalFlag &isPublic() const { return IsPublic; }
|
||||
bool usesPositionalArg() const { return UsesPositionalArg; }
|
||||
|
||||
/// Changes the specifier and length according to a QualType, retaining any
|
||||
|
|
|
@ -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
|
|
@ -1384,6 +1384,10 @@ LANGBUILTIN(to_global, "v*v*", "tn", OCLC20_LANG)
|
|||
LANGBUILTIN(to_local, "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 LIBBUILTIN
|
||||
#undef LANGBUILTIN
|
||||
|
|
|
@ -7413,6 +7413,12 @@ def warn_format_non_standard: Warning<
|
|||
def warn_format_non_standard_conversion_spec: Warning<
|
||||
"using length modifier '%0' with conversion specifier '%1' is not supported by ISO C">,
|
||||
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<
|
||||
"flag '%0' is ignored when flag '%1' is present">,
|
||||
InGroup<Format>;
|
||||
|
@ -7549,6 +7555,15 @@ def warn_cfstring_truncated : Warning<
|
|||
"belong to the input codeset UTF-8">,
|
||||
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.
|
||||
def err_continue_not_in_loop : Error<
|
||||
"'continue' statement not in loop statement">;
|
||||
|
|
|
@ -9679,6 +9679,7 @@ private:
|
|||
VariadicCallType CallType);
|
||||
|
||||
bool CheckObjCString(Expr *Arg);
|
||||
ExprResult CheckOSLogFormatStringArg(Expr *Arg);
|
||||
|
||||
ExprResult CheckBuiltinFunctionCall(FunctionDecl *FDecl,
|
||||
unsigned BuiltinID, CallExpr *TheCall);
|
||||
|
@ -9701,6 +9702,7 @@ private:
|
|||
bool SemaBuiltinVAStartARM(CallExpr *Call);
|
||||
bool SemaBuiltinUnorderedCompare(CallExpr *TheCall);
|
||||
bool SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs);
|
||||
bool SemaBuiltinOSLogFormat(CallExpr *TheCall);
|
||||
|
||||
public:
|
||||
// Used by C++ template instantiation.
|
||||
|
@ -9738,6 +9740,7 @@ public:
|
|||
FST_Kprintf,
|
||||
FST_FreeBSDKPrintf,
|
||||
FST_OSTrace,
|
||||
FST_OSLog,
|
||||
FST_Unknown
|
||||
};
|
||||
static FormatStringType GetFormatStringType(const FormatAttr *Format);
|
||||
|
|
|
@ -16,6 +16,7 @@ add_clang_library(clangAnalysis
|
|||
Dominators.cpp
|
||||
FormatString.cpp
|
||||
LiveVariables.cpp
|
||||
OSLog.cpp
|
||||
ObjCNoReturn.cpp
|
||||
PostOrderCFGView.cpp
|
||||
PrintfFormatString.cpp
|
||||
|
|
|
@ -591,6 +591,8 @@ const char *ConversionSpecifier::toString() const {
|
|||
case cArg: return "c";
|
||||
case sArg: return "s";
|
||||
case pArg: return "p";
|
||||
case PArg:
|
||||
return "P";
|
||||
case nArg: return "n";
|
||||
case PercentArg: return "%";
|
||||
case ScanListArg: return "[";
|
||||
|
@ -866,6 +868,7 @@ bool FormatSpecifier::hasStandardConversionSpecifier(
|
|||
case ConversionSpecifier::ObjCObjArg:
|
||||
case ConversionSpecifier::ScanListArg:
|
||||
case ConversionSpecifier::PercentArg:
|
||||
case ConversionSpecifier::PArg:
|
||||
return true;
|
||||
case ConversionSpecifier::CArg:
|
||||
case ConversionSpecifier::SArg:
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -119,6 +119,39 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H,
|
|||
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).
|
||||
bool hasMore = true;
|
||||
for ( ; I != E; ++I) {
|
||||
|
@ -253,6 +286,10 @@ static PrintfSpecifierResult ParsePrintfSpecifier(FormatStringHandler &H,
|
|||
// POSIX specific.
|
||||
case 'C': k = ConversionSpecifier::CArg; break;
|
||||
case 'S': k = ConversionSpecifier::SArg; break;
|
||||
// Apple extension for os_log
|
||||
case 'P':
|
||||
k = ConversionSpecifier::PArg;
|
||||
break;
|
||||
// Objective-C.
|
||||
case '@': k = ConversionSpecifier::ObjCObjArg; break;
|
||||
// Glibc specific.
|
||||
|
@ -541,6 +578,7 @@ ArgType PrintfSpecifier::getArgType(ASTContext &Ctx,
|
|||
return Ctx.IntTy;
|
||||
return ArgType(Ctx.WideCharTy, "wchar_t");
|
||||
case ConversionSpecifier::pArg:
|
||||
case ConversionSpecifier::PArg:
|
||||
return ArgType::CPointerTy;
|
||||
case ConversionSpecifier::ObjCObjArg:
|
||||
return ArgType::ObjCPointerTy;
|
||||
|
@ -900,7 +938,7 @@ bool PrintfSpecifier::hasValidPrecision() const {
|
|||
if (Precision.getHowSpecified() == OptionalAmount::NotSpecified)
|
||||
return true;
|
||||
|
||||
// Precision is only valid with the diouxXaAeEfFgGs conversions
|
||||
// Precision is only valid with the diouxXaAeEfFgGsP conversions
|
||||
switch (CS.getKind()) {
|
||||
case ConversionSpecifier::dArg:
|
||||
case ConversionSpecifier::DArg:
|
||||
|
@ -922,6 +960,7 @@ bool PrintfSpecifier::hasValidPrecision() const {
|
|||
case ConversionSpecifier::sArg:
|
||||
case ConversionSpecifier::FreeBSDrArg:
|
||||
case ConversionSpecifier::FreeBSDyArg:
|
||||
case ConversionSpecifier::PArg:
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
|
|
@ -11,14 +11,15 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "CodeGenFunction.h"
|
||||
#include "CGCXXABI.h"
|
||||
#include "CGObjCRuntime.h"
|
||||
#include "CGOpenCLRuntime.h"
|
||||
#include "CodeGenFunction.h"
|
||||
#include "CodeGenModule.h"
|
||||
#include "TargetInfo.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/Analysis/Analyses/OSLog.h"
|
||||
#include "clang/Basic/TargetBuiltins.h"
|
||||
#include "clang/Basic/TargetInfo.h"
|
||||
#include "clang/CodeGen/CGFunctionInfo.h"
|
||||
|
@ -564,6 +565,18 @@ Value *CodeGenFunction::EmitMSVCBuiltinExpr(MSVCIntrin BuiltinID,
|
|||
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,
|
||||
unsigned BuiltinID, const CallExpr *E,
|
||||
ReturnValueSlot ReturnValue) {
|
||||
|
@ -2597,6 +2610,76 @@ RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD,
|
|||
// Fall through - it's already mapped to the intrinsic by GCCBuiltin.
|
||||
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 it’s held in the log buffer. Note that if there’s
|
||||
// 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
|
||||
|
|
|
@ -1065,6 +1065,12 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
|
|||
case Builtin::BIget_kernel_preferred_work_group_size_multiple:
|
||||
if (SemaOpenCLBuiltinKernelWorkGroupSize(*this, TheCall))
|
||||
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
|
||||
|
@ -3478,6 +3484,31 @@ bool Sema::CheckObjCString(Expr *Arg) {
|
|||
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'
|
||||
/// for validity. Emit an error and return true on failure; return false
|
||||
/// on success.
|
||||
|
@ -3939,6 +3970,86 @@ bool Sema::SemaBuiltinAssumeAligned(CallExpr *TheCall) {
|
|||
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
|
||||
/// TheCall is a constant expression.
|
||||
bool Sema::SemaBuiltinConstantArg(CallExpr *TheCall, int ArgNum,
|
||||
|
@ -4576,7 +4687,8 @@ Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) {
|
|||
.Case("strfmon", FST_Strfmon)
|
||||
.Cases("kprintf", "cmn_err", "vcmn_err", "zcmn_err", FST_Kprintf)
|
||||
.Case("freebsd_kprintf", FST_FreeBSDKPrintf)
|
||||
.Case("os_trace", FST_OSTrace)
|
||||
.Case("os_trace", FST_OSLog)
|
||||
.Case("os_log", FST_OSLog)
|
||||
.Default(FST_Unknown);
|
||||
}
|
||||
|
||||
|
@ -4687,6 +4799,7 @@ protected:
|
|||
Sema &S;
|
||||
const FormatStringLiteral *FExpr;
|
||||
const Expr *OrigFormatExpr;
|
||||
const Sema::FormatStringType FSType;
|
||||
const unsigned FirstDataArg;
|
||||
const unsigned NumDataArgs;
|
||||
const char *Beg; // Start of format string.
|
||||
|
@ -4703,17 +4816,16 @@ protected:
|
|||
|
||||
public:
|
||||
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,
|
||||
ArrayRef<const Expr *> Args,
|
||||
unsigned formatIdx, bool inFunctionCall,
|
||||
Sema::VariadicCallType callType,
|
||||
ArrayRef<const Expr *> Args, unsigned formatIdx,
|
||||
bool inFunctionCall, Sema::VariadicCallType callType,
|
||||
llvm::SmallBitVector &CheckedVarArgs,
|
||||
UncoveredArgHandler &UncoveredArg)
|
||||
: S(s), FExpr(fexpr), OrigFormatExpr(origFormatExpr),
|
||||
FirstDataArg(firstDataArg), NumDataArgs(numDataArgs),
|
||||
Beg(beg), HasVAListArg(hasVAListArg),
|
||||
Args(Args), FormatIdx(formatIdx),
|
||||
: S(s), FExpr(fexpr), OrigFormatExpr(origFormatExpr), FSType(type),
|
||||
FirstDataArg(firstDataArg), NumDataArgs(numDataArgs), Beg(beg),
|
||||
HasVAListArg(hasVAListArg), Args(Args), FormatIdx(formatIdx),
|
||||
usesPositionalArgs(false), atFirstArg(true),
|
||||
inFunctionCall(inFunctionCall), CallType(callType),
|
||||
CheckedVarArgs(CheckedVarArgs), UncoveredArg(UncoveredArg) {
|
||||
|
@ -5139,24 +5251,28 @@ void CheckFormatHandler::EmitFormatDiagnostic(
|
|||
|
||||
namespace {
|
||||
class CheckPrintfHandler : public CheckFormatHandler {
|
||||
bool ObjCContext;
|
||||
|
||||
public:
|
||||
CheckPrintfHandler(Sema &s, const FormatStringLiteral *fexpr,
|
||||
const Expr *origFormatExpr, unsigned firstDataArg,
|
||||
unsigned numDataArgs, bool isObjC,
|
||||
const char *beg, bool hasVAListArg,
|
||||
ArrayRef<const Expr *> Args,
|
||||
const Expr *origFormatExpr,
|
||||
const Sema::FormatStringType type, unsigned firstDataArg,
|
||||
unsigned numDataArgs, bool isObjC, const char *beg,
|
||||
bool hasVAListArg, ArrayRef<const Expr *> Args,
|
||||
unsigned formatIdx, bool inFunctionCall,
|
||||
Sema::VariadicCallType CallType,
|
||||
llvm::SmallBitVector &CheckedVarArgs,
|
||||
UncoveredArgHandler &UncoveredArg)
|
||||
: CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg,
|
||||
numDataArgs, beg, hasVAListArg, Args,
|
||||
formatIdx, inFunctionCall, CallType, CheckedVarArgs,
|
||||
UncoveredArg),
|
||||
ObjCContext(isObjC)
|
||||
{}
|
||||
: CheckFormatHandler(s, fexpr, origFormatExpr, type, firstDataArg,
|
||||
numDataArgs, beg, hasVAListArg, Args, formatIdx,
|
||||
inFunctionCall, CallType, CheckedVarArgs,
|
||||
UncoveredArg) {}
|
||||
|
||||
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(
|
||||
const analyze_printf::PrintfSpecifier &FS,
|
||||
|
@ -5510,11 +5626,54 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier
|
|||
|
||||
// Check for using an Objective-C specific conversion specifier
|
||||
// in a non-ObjC literal.
|
||||
if (!ObjCContext && CS.isObjCArg()) {
|
||||
if (!allowsObjCArg() && CS.isObjCArg()) {
|
||||
return HandleInvalidPrintfConversionSpecifier(FS, startSpecifier,
|
||||
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
|
||||
if (!FS.hasValidFieldWidth()) {
|
||||
HandleInvalidAmount(FS, FS.getFieldWidth(), /* field width */ 0,
|
||||
|
@ -5527,6 +5686,15 @@ CheckPrintfHandler::HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier
|
|||
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.
|
||||
if (!FS.hasValidThousandsGroupingPrefix())
|
||||
HandleFlag(FS, FS.hasThousandsGrouping(), startSpecifier, specifierLen);
|
||||
|
@ -5676,8 +5844,7 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
|
|||
using namespace analyze_printf;
|
||||
// Now type check the data expression that matches the
|
||||
// format specifier.
|
||||
const analyze_printf::ArgType &AT = FS.getArgType(S.Context,
|
||||
ObjCContext);
|
||||
const analyze_printf::ArgType &AT = FS.getArgType(S.Context, isObjCContext());
|
||||
if (!AT.isValid())
|
||||
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
|
||||
// a cast instead of changing the conversion specifier.
|
||||
QualType IntendedTy = ExprTy;
|
||||
if (ObjCContext &&
|
||||
if (isObjCContext() &&
|
||||
FS.getConversionSpecifier().getKind() == ConversionSpecifier::CArg) {
|
||||
if (ExprTy->isIntegralOrUnscopedEnumerationType() &&
|
||||
!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.
|
||||
PrintfSpecifier fixedFS = FS;
|
||||
bool success = fixedFS.fixType(IntendedTy, S.getLangOpts(),
|
||||
S.Context, ObjCContext);
|
||||
bool success =
|
||||
fixedFS.fixType(IntendedTy, S.getLangOpts(), S.Context, isObjCContext());
|
||||
|
||||
if (success) {
|
||||
// Get the fix string from the fixed format specifier
|
||||
|
@ -5930,18 +6097,17 @@ namespace {
|
|||
class CheckScanfHandler : public CheckFormatHandler {
|
||||
public:
|
||||
CheckScanfHandler(Sema &s, const FormatStringLiteral *fexpr,
|
||||
const Expr *origFormatExpr, unsigned firstDataArg,
|
||||
unsigned numDataArgs, const char *beg, bool hasVAListArg,
|
||||
ArrayRef<const Expr *> Args,
|
||||
unsigned formatIdx, bool inFunctionCall,
|
||||
Sema::VariadicCallType CallType,
|
||||
const Expr *origFormatExpr, Sema::FormatStringType type,
|
||||
unsigned firstDataArg, unsigned numDataArgs,
|
||||
const char *beg, bool hasVAListArg,
|
||||
ArrayRef<const Expr *> Args, unsigned formatIdx,
|
||||
bool inFunctionCall, Sema::VariadicCallType CallType,
|
||||
llvm::SmallBitVector &CheckedVarArgs,
|
||||
UncoveredArgHandler &UncoveredArg)
|
||||
: CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg,
|
||||
numDataArgs, beg, hasVAListArg,
|
||||
Args, formatIdx, inFunctionCall, CallType,
|
||||
CheckedVarArgs, UncoveredArg)
|
||||
{}
|
||||
: CheckFormatHandler(s, fexpr, origFormatExpr, type, firstDataArg,
|
||||
numDataArgs, beg, hasVAListArg, Args, formatIdx,
|
||||
inFunctionCall, CallType, CheckedVarArgs,
|
||||
UncoveredArg) {}
|
||||
|
||||
bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS,
|
||||
const char *startSpecifier,
|
||||
|
@ -6152,13 +6318,13 @@ static void CheckFormatString(Sema &S, const FormatStringLiteral *FExpr,
|
|||
}
|
||||
|
||||
if (Type == Sema::FST_Printf || Type == Sema::FST_NSString ||
|
||||
Type == Sema::FST_FreeBSDKPrintf || Type == Sema::FST_OSTrace) {
|
||||
CheckPrintfHandler H(S, FExpr, OrigFormatExpr, firstDataArg,
|
||||
numDataArgs, (Type == Sema::FST_NSString ||
|
||||
Type == Sema::FST_OSTrace),
|
||||
Str, HasVAListArg, Args, format_idx,
|
||||
inFunctionCall, CallType, CheckedVarArgs,
|
||||
UncoveredArg);
|
||||
Type == Sema::FST_FreeBSDKPrintf || Type == Sema::FST_OSLog ||
|
||||
Type == Sema::FST_OSTrace) {
|
||||
CheckPrintfHandler H(
|
||||
S, FExpr, OrigFormatExpr, Type, firstDataArg, numDataArgs,
|
||||
(Type == Sema::FST_NSString || Type == Sema::FST_OSTrace), Str,
|
||||
HasVAListArg, Args, format_idx, inFunctionCall, CallType,
|
||||
CheckedVarArgs, UncoveredArg);
|
||||
|
||||
if (!analyze_format_string::ParsePrintfString(H, Str, Str + StrLen,
|
||||
S.getLangOpts(),
|
||||
|
@ -6166,10 +6332,9 @@ static void CheckFormatString(Sema &S, const FormatStringLiteral *FExpr,
|
|||
Type == Sema::FST_FreeBSDKPrintf))
|
||||
H.DoneProcessing();
|
||||
} else if (Type == Sema::FST_Scanf) {
|
||||
CheckScanfHandler H(S, FExpr, OrigFormatExpr, firstDataArg, numDataArgs,
|
||||
Str, HasVAListArg, Args, format_idx,
|
||||
inFunctionCall, CallType, CheckedVarArgs,
|
||||
UncoveredArg);
|
||||
CheckScanfHandler H(S, FExpr, OrigFormatExpr, Type, firstDataArg,
|
||||
numDataArgs, Str, HasVAListArg, Args, format_idx,
|
||||
inFunctionCall, CallType, CheckedVarArgs, UncoveredArg);
|
||||
|
||||
if (!analyze_format_string::ParseScanfString(H, Str, Str + StrLen,
|
||||
S.getLangOpts(),
|
||||
|
|
|
@ -2815,6 +2815,7 @@ static FormatAttrKind getFormatAttrKind(StringRef Format) {
|
|||
.Case("kprintf", SupportedFormat) // OpenBSD.
|
||||
.Case("freebsd_kprintf", SupportedFormat) // FreeBSD.
|
||||
.Case("os_trace", SupportedFormat)
|
||||
.Case("os_log", SupportedFormat)
|
||||
|
||||
.Cases("gcc_diag", "gcc_cdiag", "gcc_cxxdiag", "gcc_tdiag", IgnoredFormat)
|
||||
.Default(InvalidFormat);
|
||||
|
|
|
@ -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
|
||||
void test_builtin_longjmp(void **buffer) {
|
||||
// CHECK: [[BITCAST:%.*]] = bitcast
|
||||
|
@ -364,10 +362,147 @@ void test_builtin_longjmp(void **buffer) {
|
|||
__builtin_longjmp(buffer, 1);
|
||||
// CHECK-NEXT: unreachable
|
||||
}
|
||||
#endif
|
||||
|
||||
// CHECK-LABEL: define i64 @test_builtin_readcyclecounter
|
||||
long long test_builtin_readcyclecounter() {
|
||||
// CHECK: call i64 @llvm.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);
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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}}
|
||||
}
|
Loading…
Reference in New Issue