Merge remote-tracking branch 'origin/swift-3.0-branch' into stable
* origin/swift-3.0-branch:
Clean up some whitespace and comments for the new logging builtins patch.
Add support for __builtin_os_log_format[_buffer_size]
diff --git a/include/clang/Analysis/Analyses/FormatString.h b/include/clang/Analysis/Analyses/FormatString.h
index a593e98..ada3fb0 100644
--- a/include/clang/Analysis/Analyses/FormatString.h
+++ b/include/clang/Analysis/Analyses/FormatString.h
@@ -35,7 +35,7 @@
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) {
@@ -154,6 +154,11 @@
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
@@ -433,13 +438,15 @@
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") {}
+ HasObjCTechnicalTerm("tt"), IsPrivate("private"), IsPublic("public") {}
static PrintfSpecifier Parse(const char *beg, const char *end);
@@ -468,6 +475,12 @@
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.
@@ -505,6 +518,8 @@
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
diff --git a/include/clang/Analysis/Analyses/OSLog.h b/include/clang/Analysis/Analyses/OSLog.h
new file mode 100644
index 0000000..084b079
--- /dev/null
+++ b/include/clang/Analysis/Analyses/OSLog.h
@@ -0,0 +1,147 @@
+//= 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 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
+ };
+
+ 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 |= 0x01;
+ if (getIsPublic()) result |= 0x02;
+ result |= ((unsigned)getKind()) << 4;
+ return result;
+ }
+
+ unsigned char getSizeByte() const {
+ return getSize().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 getSize() const { return Size; }
+};
+
+class OSLogBufferLayout {
+public:
+ SmallVector<OSLogBufferItem, 4> Items;
+
+ CharUnits getSize() const {
+ CharUnits result;
+ result += CharUnits::fromQuantity(2); // summary byte, num-args byte
+ for (auto &item : Items) {
+ // descriptor byte, size byte
+ result += item.getSize() + CharUnits::fromQuantity(2);
+ }
+ return result;
+ }
+
+ bool getHasPrivateItems() const {
+ return std::any_of(Items.begin(), Items.end(),
+ [](const OSLogBufferItem &item) { return item.getIsPrivate(); });
+ }
+
+ bool getHasPublicItems() const {
+ return std::any_of(Items.begin(), Items.end(),
+ [](const OSLogBufferItem &item) { return item.getIsPublic(); });
+ }
+
+ bool getHasNonScalar() const {
+ return std::any_of(Items.begin(), Items.end(),
+ [](const OSLogBufferItem &item) {
+ return item.getKind() != OSLogBufferItem::ScalarKind;
+ });
+ }
+
+ unsigned char getSummaryByte() const {
+ unsigned char result = 0;
+ if (getHasPrivateItems()) result |= 0x01;
+ if (getHasNonScalar()) result |= 0x02;
+ 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
diff --git a/include/clang/Basic/Builtins.def b/include/clang/Basic/Builtins.def
index 4f474eb..1da3f02 100644
--- a/include/clang/Basic/Builtins.def
+++ b/include/clang/Basic/Builtins.def
@@ -1252,6 +1252,10 @@
BUILTIN(__builtin_nontemporal_store, "v.", "t")
BUILTIN(__builtin_nontemporal_load, "v.", "t")
+// 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
diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td
index 33a473c..2129bcc 100644
--- a/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/include/clang/Basic/DiagnosticSemaKinds.td
@@ -7156,6 +7156,12 @@
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>;
@@ -7292,6 +7298,13 @@
"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)">;
+
// Statements.
def err_continue_not_in_loop : Error<
"'continue' statement not in loop statement">;
diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h
index 5c868f0..32f249e 100644
--- a/include/clang/Sema/Sema.h
+++ b/include/clang/Sema/Sema.h
@@ -9095,6 +9095,7 @@
VariadicCallType CallType);
bool CheckObjCString(Expr *Arg);
+ ExprResult CheckOSLogFormatStringArg(Expr *Arg);
ExprResult CheckBuiltinFunctionCall(FunctionDecl *FDecl,
unsigned BuiltinID, CallExpr *TheCall);
@@ -9116,6 +9117,7 @@
bool SemaBuiltinVAStartARM(CallExpr *Call);
bool SemaBuiltinUnorderedCompare(CallExpr *TheCall);
bool SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs);
+ bool SemaBuiltinOSLogFormat(CallExpr *TheCall);
public:
// Used by C++ template instantiation.
@@ -9151,6 +9153,7 @@
FST_Kprintf,
FST_FreeBSDKPrintf,
FST_OSTrace,
+ FST_OSLog,
FST_Unknown
};
static FormatStringType GetFormatStringType(const FormatAttr *Format);
diff --git a/lib/Analysis/CMakeLists.txt b/lib/Analysis/CMakeLists.txt
index 1df093d..a3990d6 100644
--- a/lib/Analysis/CMakeLists.txt
+++ b/lib/Analysis/CMakeLists.txt
@@ -15,6 +15,7 @@
Dominators.cpp
FormatString.cpp
LiveVariables.cpp
+ OSLog.cpp
ObjCNoReturn.cpp
PostOrderCFGView.cpp
PrintfFormatString.cpp
diff --git a/lib/Analysis/FormatString.cpp b/lib/Analysis/FormatString.cpp
index a8a911a..b1c868a 100644
--- a/lib/Analysis/FormatString.cpp
+++ b/lib/Analysis/FormatString.cpp
@@ -571,6 +571,7 @@
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 "[";
@@ -846,6 +847,7 @@
case ConversionSpecifier::ObjCObjArg:
case ConversionSpecifier::ScanListArg:
case ConversionSpecifier::PercentArg:
+ case ConversionSpecifier::PArg:
return true;
case ConversionSpecifier::CArg:
case ConversionSpecifier::SArg:
diff --git a/lib/Analysis/OSLog.cpp b/lib/Analysis/OSLog.cpp
new file mode 100644
index 0000000..b6e9c49
--- /dev/null
+++ b/lib/Analysis/OSLog.cpp
@@ -0,0 +1,167 @@
+//===--- OSLog.cpp - 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().
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Analyses/OSLog.h"
+#include "clang/Analysis/Analyses/FormatString.h"
+#include "clang/Basic/Builtins.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/ExprObjC.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:
+ ArrayRef<const Expr *> Args;
+ SmallVector<Optional<OSLogBufferItem::Kind>, 4> ArgKind;
+ SmallVector<Optional<unsigned>, 4> ArgSize;
+ SmallVector<unsigned char, 4> ArgFlags;
+
+public:
+ OSLogFormatStringHandler(ArrayRef<const Expr *> args)
+ : FormatStringHandler(), Args(args), ArgKind(args.size(), None),
+ ArgSize(args.size(), None), ArgFlags(args.size(), 0)
+ {}
+
+ virtual bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS,
+ const char *startSpecifier,
+ unsigned specifierLen) {
+
+ // Cases to handle:
+ // * "%f", "%d"... scalar (assumed for anything that doesn't fit the below
+ // cases)
+ // * "%s" pointer to null-terminated string
+ // * "%.*s" strlen (arg), pointer to string
+ // * "%.16s" strlen (non-arg), pointer to string
+ // * "%.*P" len (arg), pointer to data
+ // * "%.16P" len (non-arg), pointer to data
+ // * "%@" pointer to objc object
+
+ unsigned argIndex = FS.getArgIndex();
+ if (argIndex >= Args.size()) {
+ return false;
+ }
+ switch (FS.getConversionSpecifier().getKind()) {
+ case clang::analyze_format_string::ConversionSpecifier::sArg: { // "%s"
+ ArgKind[argIndex] = OSLogBufferItem::StringKind;
+ auto &precision = FS.getPrecision();
+ switch (precision.getHowSpecified()) {
+ case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%s"
+ break;
+ case clang::analyze_format_string::OptionalAmount::Constant: // "%.16s"
+ ArgSize[argIndex] = precision.getConstantAmount();
+ break;
+ case clang::analyze_format_string::OptionalAmount::Arg: // "%.*s"
+ ArgKind[precision.getArgIndex()] = OSLogBufferItem::CountKind;
+ break;
+ case clang::analyze_format_string::OptionalAmount::Invalid:
+ return false;
+ }
+ break;
+ }
+ case clang::analyze_format_string::ConversionSpecifier::PArg: { // "%P"
+ ArgKind[argIndex] = OSLogBufferItem::PointerKind;
+ 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"
+ ArgSize[argIndex] = precision.getConstantAmount();
+ break;
+ case clang::analyze_format_string::OptionalAmount::Arg: // "%.*P"
+ ArgKind[precision.getArgIndex()] = OSLogBufferItem::CountKind;
+ break;
+ case clang::analyze_format_string::OptionalAmount::Invalid:
+ return false;
+ }
+ break;
+ }
+ case clang::analyze_format_string::ConversionSpecifier::ObjCObjArg: // "%@"
+ ArgKind[argIndex] = OSLogBufferItem::ObjCObjKind;
+ break;
+ default:
+ ArgKind[argIndex] = OSLogBufferItem::ScalarKind;
+ break;
+ }
+
+ if (FS.isPrivate()) {
+ ArgFlags[argIndex] |= OSLogBufferItem::IsPrivate;
+ }
+ if (FS.isPublic()) {
+ ArgFlags[argIndex] |= OSLogBufferItem::IsPublic;
+ }
+ return true;
+ }
+
+ void computeLayout(ASTContext &Ctx, OSLogBufferLayout &layout) const {
+ layout.Items.clear();
+ for (unsigned i = 0; i < Args.size(); i++) {
+ const Expr *arg = Args[i];
+ if (ArgSize[i]) {
+ layout.Items.emplace_back(Ctx, CharUnits::fromQuantity(*ArgSize[i]),
+ ArgFlags[i]);
+ }
+ CharUnits size = Ctx.getTypeSizeInChars(arg->getType());
+ if (ArgKind[i]) {
+ layout.Items.emplace_back(*ArgKind[i], arg, size, ArgFlags[i]);
+ } else {
+ layout.Items.emplace_back(OSLogBufferItem::ScalarKind, arg, size,
+ ArgFlags[i]);
+ }
+ }
+ }
+};
+
+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;
+}
diff --git a/lib/Analysis/PrintfFormatString.cpp b/lib/Analysis/PrintfFormatString.cpp
index fb5df61..cb39c05 100644
--- a/lib/Analysis/PrintfFormatString.cpp
+++ b/lib/Analysis/PrintfFormatString.cpp
@@ -119,6 +119,35 @@
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 +282,8 @@
// 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.
@@ -301,7 +332,7 @@
conversionPosition);
return true;
}
-
+
PrintfConversionSpecifier CS(conversionPosition, k);
FS.setConversionSpecifier(CS);
if (CS.consumesDataArgument() && !FS.usesPositionalArg())
@@ -541,6 +572,7 @@
return Ctx.IntTy;
return ArgType(Ctx.WideCharTy, "wchar_t");
case ConversionSpecifier::pArg:
+ case ConversionSpecifier::PArg:
return ArgType::CPointerTy;
case ConversionSpecifier::ObjCObjArg:
return ArgType::ObjCPointerTy;
@@ -896,7 +928,7 @@
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:
@@ -918,6 +950,7 @@
case ConversionSpecifier::sArg:
case ConversionSpecifier::FreeBSDrArg:
case ConversionSpecifier::FreeBSDyArg:
+ case ConversionSpecifier::PArg:
return true;
default:
diff --git a/lib/CodeGen/CGBuiltin.cpp b/lib/CodeGen/CGBuiltin.cpp
index 55f919e..bf9f2b4 100644
--- a/lib/CodeGen/CGBuiltin.cpp
+++ b/lib/CodeGen/CGBuiltin.cpp
@@ -18,6 +18,7 @@
#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"
@@ -407,6 +408,17 @@
return Builder.CreateCall(F, {EmitScalarExpr(E), CI});
}
+namespace {
+ 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) {
@@ -1961,6 +1973,77 @@
return RValue::get(llvm::ConstantExpr::getBitCast(GV, CGM.Int8PtrTy));
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 *expr = item.getExpr()) {
+ addr = Builder.CreateElementBitCast(addr,
+ ConvertTypeForMem(expr->getType()));
+ // Check if this is a retainable type.
+ if (expr->getType()->isObjCRetainableType()) {
+ assert(getEvaluationKind(expr->getType()) == TEK_Scalar &&
+ "Only scalar can be a ObjC retainable type");
+ llvm::Value *SV = EmitScalarExpr(expr, /*Ignore*/ false);
+ RValue RV = RValue::get(SV);
+ LValue LV = MakeAddrLValue(addr, expr->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(expr, addr, Qualifiers(), /*isInit*/true);
+ }
+ } else {
+ addr = Builder.CreateElementBitCast(addr, Int32Ty);
+ Builder.CreateStore(
+ Builder.getInt32(item.getConstValue().getQuantity()), addr);
+ }
+ offset += item.getSize();
+ }
+
+ // 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.getSize().getQuantity()));
+ }
}
// If this is an alias for a lib function (e.g. __builtin_sin), emit
diff --git a/lib/Sema/SemaChecking.cpp b/lib/Sema/SemaChecking.cpp
index bbd7779..90ae3ce 100644
--- a/lib/Sema/SemaChecking.cpp
+++ b/lib/Sema/SemaChecking.cpp
@@ -566,6 +566,12 @@
TheCall->setType(Context.VoidPtrTy);
break;
+ 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
@@ -2396,6 +2402,31 @@
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();
+ StringLiteral *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.
@@ -2842,6 +2873,86 @@
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,
@@ -3216,7 +3327,8 @@
.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);
}
@@ -3316,6 +3428,7 @@
Sema &S;
const StringLiteral *FExpr;
const Expr *OrigFormatExpr;
+ const Sema::FormatStringType FSType;
const unsigned FirstDataArg;
const unsigned NumDataArgs;
const char *Beg; // Start of format string.
@@ -3330,16 +3443,16 @@
llvm::SmallBitVector &CheckedVarArgs;
public:
CheckFormatHandler(Sema &s, const StringLiteral *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,
llvm::SmallBitVector &CheckedVarArgs)
- : 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) {
@@ -3747,22 +3860,30 @@
namespace {
class CheckPrintfHandler : public CheckFormatHandler {
- bool ObjCContext;
public:
CheckPrintfHandler(Sema &s, const StringLiteral *fexpr,
- const Expr *origFormatExpr, unsigned firstDataArg,
- unsigned numDataArgs, bool isObjC,
+ 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,
llvm::SmallBitVector &CheckedVarArgs)
- : CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg,
+ : CheckFormatHandler(s, fexpr, origFormatExpr, type, firstDataArg,
numDataArgs, beg, hasVAListArg, Args,
- formatIdx, inFunctionCall, CallType, CheckedVarArgs),
- ObjCContext(isObjC)
+ formatIdx, inFunctionCall, CallType, CheckedVarArgs)
{}
+ 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,
@@ -4118,11 +4239,45 @@
// 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);
+ }
+
+ // 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,
@@ -4135,6 +4290,15 @@
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);
@@ -4285,7 +4449,7 @@
// Now type check the data expression that matches the
// format specifier.
const analyze_printf::ArgType &AT = FS.getArgType(S.Context,
- ObjCContext);
+ isObjCContext());
if (!AT.isValid())
return true;
@@ -4340,7 +4504,7 @@
// 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()) {
@@ -4382,7 +4546,7 @@
// 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);
+ S.Context, isObjCContext());
if (success) {
// Get the fix string from the fixed format specifier
@@ -4539,16 +4703,16 @@
class CheckScanfHandler : public CheckFormatHandler {
public:
CheckScanfHandler(Sema &s, const StringLiteral *fexpr,
- const Expr *origFormatExpr, unsigned firstDataArg,
+ 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)
- : CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg,
- numDataArgs, beg, hasVAListArg,
- Args, formatIdx, inFunctionCall, CallType,
- CheckedVarArgs)
+ : CheckFormatHandler(s, fexpr, origFormatExpr, type, firstDataArg,
+ numDataArgs, beg, hasVAListArg, Args, formatIdx,
+ inFunctionCall, CallType, CheckedVarArgs)
{}
bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS,
@@ -4716,7 +4880,7 @@
unsigned firstDataArg, FormatStringType Type,
bool inFunctionCall, VariadicCallType CallType,
llvm::SmallBitVector &CheckedVarArgs) {
-
+
// CHECK: is the format string a wide literal?
if (!FExpr->isAscii() && !FExpr->isUTF8()) {
CheckFormatHandler::EmitFormatDiagnostic(
@@ -4758,10 +4922,10 @@
}
if (Type == FST_Printf || Type == FST_NSString ||
- Type == FST_FreeBSDKPrintf || Type == FST_OSTrace) {
- CheckPrintfHandler H(*this, FExpr, OrigFormatExpr, firstDataArg,
- numDataArgs, (Type == FST_NSString || Type == FST_OSTrace),
- Str, HasVAListArg, Args, format_idx,
+ Type == FST_FreeBSDKPrintf || Type == FST_OSLog ||
+ Type == FST_OSTrace) {
+ CheckPrintfHandler H(*this, FExpr, OrigFormatExpr, Type, firstDataArg,
+ numDataArgs, Str, HasVAListArg, Args, format_idx,
inFunctionCall, CallType, CheckedVarArgs);
if (!analyze_format_string::ParsePrintfString(H, Str, Str + StrLen,
@@ -4770,7 +4934,7 @@
Type == FST_FreeBSDKPrintf))
H.DoneProcessing();
} else if (Type == FST_Scanf) {
- CheckScanfHandler H(*this, FExpr, OrigFormatExpr, firstDataArg, numDataArgs,
+ CheckScanfHandler H(*this, FExpr, OrigFormatExpr, Type, firstDataArg, numDataArgs,
Str, HasVAListArg, Args, format_idx,
inFunctionCall, CallType, CheckedVarArgs);
diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp
index ec2b825..dcc458b 100644
--- a/lib/Sema/SemaDeclAttr.cpp
+++ b/lib/Sema/SemaDeclAttr.cpp
@@ -2789,6 +2789,7 @@
.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);
diff --git a/test/CodeGen/builtins.c b/test/CodeGen/builtins.c
index 39b2c12..f0ab4f4 100644
--- a/test/CodeGen/builtins.c
+++ b/test/CodeGen/builtins.c
@@ -260,3 +260,62 @@
// CHECK: call i64 @llvm.readcyclecounter()
return __builtin_readcyclecounter();
}
+
+// Behavior of __builtin_os_log differs between platforms, so only test on X86
+#ifdef __x86_64__
+// 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);
+}
+
+#endif
diff --git a/test/CodeGenObjC/os_log.m b/test/CodeGenObjC/os_log.m
new file mode 100644
index 0000000..144d1cc
--- /dev/null
+++ b/test/CodeGenObjC/os_log.m
@@ -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
diff --git a/test/Sema/format-strings.c b/test/Sema/format-strings.c
index 6ced997..a67fd46 100644
--- a/test/Sema/format-strings.c
+++ b/test/Sema/format-strings.c
@@ -635,3 +635,30 @@
// expected-note@-1{{treat the string as an argument to avoid this}}
}
#pragma GCC diagnostic warning "-Wformat-nonliteral"
+
+void test_os_log_format(char c, const char *pc, int i, int *pi, 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, 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);
+}
diff --git a/test/SemaObjC/format-strings-objc.m b/test/SemaObjC/format-strings-objc.m
index a1ebf03..26dae2e 100644
--- a/test/SemaObjC/format-strings-objc.m
+++ b/test/SemaObjC/format-strings-objc.m
@@ -265,3 +265,17 @@
NSLog(@"%2$[tt]@ %1$[tt]s", @"Foo", @"Bar"); // expected-warning {{object format flags cannot be used with 's' conversion specifier}}
}
+// Test os_log_format primitive with ObjC string literal format argument.
+void test_os_log_format(char c, const char *pc, int i, int *pi, 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);
+}