Index: clang/include/clang/Analysis/Analyses/FormatString.h =================================================================== --- clang/include/clang/Analysis/Analyses/FormatString.h +++ clang/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) { @@ -122,12 +122,13 @@ public: enum Kind { InvalidSpecifier = 0, - // C99 conversion specifiers. + // C99 conversion specifiers. cArg, dArg, DArg, // Apple extension iArg, - IntArgBeg = dArg, IntArgEnd = iArg, + IntArgBeg = dArg, + IntArgEnd = iArg, oArg, OArg, // Apple extension @@ -135,7 +136,8 @@ UArg, // Apple extension xArg, XArg, - UIntArgBeg = oArg, UIntArgEnd = XArg, + UIntArgBeg = oArg, + UIntArgEnd = XArg, fArg, FArg, @@ -145,7 +147,8 @@ GArg, aArg, AArg, - DoubleArgBeg = fArg, DoubleArgEnd = AArg, + DoubleArgBeg = fArg, + DoubleArgEnd = AArg, sArg, pArg, @@ -154,13 +157,19 @@ 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, + ObjCObjArg, // '@' + ObjCBeg = ObjCObjArg, + ObjCEnd = ObjCObjArg, // FreeBSD kernel specific specifiers. FreeBSDbArg, @@ -169,13 +178,15 @@ FreeBSDyArg, // GlibC specific specifiers. - PrintErrno, // 'm' + 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 @@ 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 @@ 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 @@ 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 Index: clang/include/clang/Analysis/Analyses/OSLog.h =================================================================== --- /dev/null +++ clang/include/clang/Analysis/Analyses/OSLog.h @@ -0,0 +1,156 @@ +//= 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 |= 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 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 Index: clang/include/clang/Basic/Builtins.def =================================================================== --- clang/include/clang/Basic/Builtins.def +++ clang/include/clang/Basic/Builtins.def @@ -1384,6 +1384,10 @@ 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 Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7413,6 +7413,12 @@ def warn_format_non_standard_conversion_spec: Warning< "using length modifier '%0' with conversion specifier '%1' is not supported by ISO C">, InGroup, DefaultIgnore; +def warn_format_invalid_annotation : Warning< + "using '%0' format specifier annotation outside of os_log()/os_trace()">, + InGroup; +def warn_format_P_no_precision : Warning< + "using '%%P' format specifier without precision">, + InGroup; def warn_printf_ignored_flag: Warning< "flag '%0' is ignored when flag '%1' is present">, InGroup; @@ -7549,6 +7555,15 @@ "belong to the input codeset UTF-8">, InGroup>; +// 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">; Index: clang/include/clang/Sema/Sema.h =================================================================== --- clang/include/clang/Sema/Sema.h +++ clang/include/clang/Sema/Sema.h @@ -9681,6 +9681,7 @@ VariadicCallType CallType); bool CheckObjCString(Expr *Arg); + ExprResult CheckOSLogFormatStringArg(Expr *Arg); ExprResult CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, CallExpr *TheCall); @@ -9703,6 +9704,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. @@ -9740,6 +9742,7 @@ FST_Kprintf, FST_FreeBSDKPrintf, FST_OSTrace, + FST_OSLog, FST_Unknown }; static FormatStringType GetFormatStringType(const FormatAttr *Format); Index: clang/lib/Analysis/CMakeLists.txt =================================================================== --- clang/lib/Analysis/CMakeLists.txt +++ clang/lib/Analysis/CMakeLists.txt @@ -16,6 +16,7 @@ Dominators.cpp FormatString.cpp LiveVariables.cpp + OSLog.cpp ObjCNoReturn.cpp PostOrderCFGView.cpp PrintfFormatString.cpp Index: clang/lib/Analysis/FormatString.cpp =================================================================== --- clang/lib/Analysis/FormatString.cpp +++ clang/lib/Analysis/FormatString.cpp @@ -591,6 +591,8 @@ 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 @@ case ConversionSpecifier::ObjCObjArg: case ConversionSpecifier::ScanListArg: case ConversionSpecifier::PercentArg: + case ConversionSpecifier::PArg: return true; case ConversionSpecifier::CArg: case ConversionSpecifier::SArg: Index: clang/lib/Analysis/OSLog.cpp =================================================================== --- /dev/null +++ clang/lib/Analysis/OSLog.cpp @@ -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 Kind; + Optional Size; + unsigned char Flags = 0; + }; + SmallVector ArgsData; + ArrayRef 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 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 Args(E->getArgs(), E->getArgs() + E->getNumArgs()); + + const Expr *StringArg; + ArrayRef 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(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; +} Index: clang/lib/Analysis/PrintfFormatString.cpp =================================================================== --- clang/lib/Analysis/PrintfFormatString.cpp +++ clang/lib/Analysis/PrintfFormatString.cpp @@ -119,6 +119,39 @@ 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 @@ // 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 +338,7 @@ conversionPosition); return true; } - + PrintfConversionSpecifier CS(conversionPosition, k); FS.setConversionSpecifier(CS); if (CS.consumesDataArgument() && !FS.usesPositionalArg()) @@ -541,6 +578,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; @@ -900,7 +938,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: @@ -922,6 +960,7 @@ case ConversionSpecifier::sArg: case ConversionSpecifier::FreeBSDrArg: case ConversionSpecifier::FreeBSDyArg: + case ConversionSpecifier::PArg: return true; default: Index: clang/lib/CodeGen/CGBuiltin.cpp =================================================================== --- clang/lib/CodeGen/CGBuiltin.cpp +++ clang/lib/CodeGen/CGBuiltin.cpp @@ -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 @@ 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 @@ // 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 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(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(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 Index: clang/lib/Sema/SemaChecking.cpp =================================================================== --- clang/lib/Sema/SemaChecking.cpp +++ clang/lib/Sema/SemaChecking.cpp @@ -1065,6 +1065,12 @@ 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 @@ -3480,6 +3486,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(Arg); + if (!Literal) { + if (auto *ObjcLiteral = dyn_cast(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. @@ -3941,6 +3972,86 @@ return false; } +bool Sema::SemaBuiltinOSLogFormat(CallExpr *TheCall) { + unsigned BuiltinID = + cast(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 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, @@ -4557,15 +4668,16 @@ Sema::FormatStringType Sema::GetFormatStringType(const FormatAttr *Format) { return llvm::StringSwitch(Format->getType()->getName()) - .Case("scanf", FST_Scanf) - .Cases("printf", "printf0", FST_Printf) - .Cases("NSString", "CFString", FST_NSString) - .Case("strftime", FST_Strftime) - .Case("strfmon", FST_Strfmon) - .Cases("kprintf", "cmn_err", "vcmn_err", "zcmn_err", FST_Kprintf) - .Case("freebsd_kprintf", FST_FreeBSDKPrintf) - .Case("os_trace", FST_OSTrace) - .Default(FST_Unknown); + .Case("scanf", FST_Scanf) + .Cases("printf", "printf0", FST_Printf) + .Cases("NSString", "CFString", FST_NSString) + .Case("strftime", FST_Strftime) + .Case("strfmon", FST_Strfmon) + .Cases("kprintf", "cmn_err", "vcmn_err", "zcmn_err", FST_Kprintf) + .Case("freebsd_kprintf", FST_FreeBSDKPrintf) + .Case("os_trace", FST_OSLog) + .Case("os_log", FST_OSLog) + .Default(FST_Unknown); } /// CheckFormatArguments - Check calls to printf and scanf (and similar @@ -4675,6 +4787,7 @@ 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. @@ -4691,20 +4804,19 @@ 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 Args, - unsigned formatIdx, bool inFunctionCall, - Sema::VariadicCallType callType, + ArrayRef 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), - usesPositionalArgs(false), atFirstArg(true), - inFunctionCall(inFunctionCall), CallType(callType), - CheckedVarArgs(CheckedVarArgs), UncoveredArg(UncoveredArg) { + : 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) { CoveredArgs.resize(numDataArgs); CoveredArgs.reset(); } @@ -5127,24 +5239,28 @@ 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 Args, + const Expr *origFormatExpr, + const Sema::FormatStringType type, unsigned firstDataArg, + unsigned numDataArgs, bool isObjC, const char *beg, + bool hasVAListArg, ArrayRef 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, @@ -5498,11 +5614,54 @@ // 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, @@ -5515,6 +5674,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); @@ -5664,8 +5832,7 @@ 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; @@ -5720,7 +5887,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()) { @@ -5761,8 +5928,8 @@ // 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 @@ -5918,19 +6085,18 @@ class CheckScanfHandler : public CheckFormatHandler { public: CheckScanfHandler(Sema &s, const FormatStringLiteral *fexpr, - const Expr *origFormatExpr, unsigned firstDataArg, - unsigned numDataArgs, const char *beg, bool hasVAListArg, - ArrayRef Args, - unsigned formatIdx, bool inFunctionCall, - Sema::VariadicCallType CallType, + const Expr *origFormatExpr, Sema::FormatStringType type, + unsigned firstDataArg, unsigned numDataArgs, + const char *beg, bool hasVAListArg, + ArrayRef 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, unsigned specifierLen) override; @@ -6140,13 +6306,13 @@ } 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(), @@ -6154,10 +6320,9 @@ 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(), Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -2804,20 +2804,21 @@ /// types. static FormatAttrKind getFormatAttrKind(StringRef Format) { return llvm::StringSwitch(Format) - // Check for formats that get handled specially. - .Case("NSString", NSStringFormat) - .Case("CFString", CFStringFormat) - .Case("strftime", StrftimeFormat) - - // Otherwise, check for supported formats. - .Cases("scanf", "printf", "printf0", "strfmon", SupportedFormat) - .Cases("cmn_err", "vcmn_err", "zcmn_err", SupportedFormat) - .Case("kprintf", SupportedFormat) // OpenBSD. - .Case("freebsd_kprintf", SupportedFormat) // FreeBSD. - .Case("os_trace", SupportedFormat) - - .Cases("gcc_diag", "gcc_cdiag", "gcc_cxxdiag", "gcc_tdiag", IgnoredFormat) - .Default(InvalidFormat); + // Check for formats that get handled specially. + .Case("NSString", NSStringFormat) + .Case("CFString", CFStringFormat) + .Case("strftime", StrftimeFormat) + + // Otherwise, check for supported formats. + .Cases("scanf", "printf", "printf0", "strfmon", SupportedFormat) + .Cases("cmn_err", "vcmn_err", "zcmn_err", SupportedFormat) + .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); } /// Handle __attribute__((init_priority(priority))) attributes based on Index: clang/test/CodeGen/builtins.c =================================================================== --- clang/test/CodeGen/builtins.c +++ clang/test/CodeGen/builtins.c @@ -371,3 +371,145 @@ // 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); +} + +// 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); +} + +#endif Index: clang/test/CodeGenObjC/os_log.m =================================================================== --- /dev/null +++ clang/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 Index: clang/test/SemaObjC/format-strings-oslog.m =================================================================== --- /dev/null +++ clang/test/SemaObjC/format-strings-oslog.m @@ -0,0 +1,62 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +#include +#include +#define __need_wint_t +#include // 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); + + // + __builtin_os_log_format_buffer_size("no-args"); + __builtin_os_log_format(buf, "%s", "hi"); + + // + 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}} +}