Skip to content

Commit

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

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

llvm-svn: 284990
  • Loading branch information
joker-eph committed Oct 24, 2016
1 parent b05bac9 commit 2903436
Show file tree
Hide file tree
Showing 15 changed files with 994 additions and 95 deletions.
47 changes: 32 additions & 15 deletions clang/include/clang/Analysis/Analyses/FormatString.h
Expand Up @@ -35,7 +35,7 @@ class OptionalFlag {
public:
OptionalFlag(const char *Representation)
: representation(Representation), flag(false) {}
bool isSet() { return flag; }
bool isSet() const { return flag; }
void set() { flag = true; }
void clear() { flag = false; }
void setPosition(const char *position) {
Expand Down Expand Up @@ -122,20 +122,22 @@ class ConversionSpecifier {
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
uArg,
UArg, // Apple extension
xArg,
XArg,
UIntArgBeg = oArg, UIntArgEnd = XArg,
UIntArgBeg = oArg,
UIntArgEnd = XArg,

fArg,
FArg,
Expand All @@ -145,7 +147,8 @@ class ConversionSpecifier {
GArg,
aArg,
AArg,
DoubleArgBeg = fArg, DoubleArgEnd = AArg,
DoubleArgBeg = fArg,
DoubleArgEnd = AArg,

sArg,
pArg,
Expand All @@ -154,13 +157,19 @@ class ConversionSpecifier {
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,
Expand All @@ -169,13 +178,15 @@ class ConversionSpecifier {
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)
Expand Down Expand Up @@ -437,13 +448,15 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier {
OptionalFlag HasAlternativeForm; // '#'
OptionalFlag HasLeadingZeroes; // '0'
OptionalFlag HasObjCTechnicalTerm; // '[tt]'
OptionalFlag IsPrivate; // '{private}'
OptionalFlag IsPublic; // '{public}'
OptionalAmount Precision;
public:
PrintfSpecifier() :
FormatSpecifier(/* isPrintf = */ true),
HasThousandsGrouping("'"), IsLeftJustified("-"), HasPlusPrefix("+"),
HasSpacePrefix(" "), HasAlternativeForm("#"), HasLeadingZeroes("0"),
HasObjCTechnicalTerm("tt") {}
PrintfSpecifier()
: FormatSpecifier(/* isPrintf = */ true), HasThousandsGrouping("'"),
IsLeftJustified("-"), HasPlusPrefix("+"), HasSpacePrefix(" "),
HasAlternativeForm("#"), HasLeadingZeroes("0"),
HasObjCTechnicalTerm("tt"), IsPrivate("private"), IsPublic("public") {}

static PrintfSpecifier Parse(const char *beg, const char *end);

Expand Down Expand Up @@ -472,6 +485,8 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier {
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.
Expand Down Expand Up @@ -509,6 +524,8 @@ class PrintfSpecifier : public analyze_format_string::FormatSpecifier {
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
Expand Down
155 changes: 155 additions & 0 deletions clang/include/clang/Analysis/Analyses/OSLog.h
@@ -0,0 +1,155 @@
//= OSLog.h - Analysis of calls to os_log builtins --*- C++ -*-===============//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines APIs for determining the layout of the data buffer for
// os_log() and os_trace().
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_OSLOG_H
#define LLVM_CLANG_ANALYSIS_ANALYSES_OSLOG_H

#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"

namespace clang {
namespace analyze_os_log {

/// An OSLogBufferItem represents a single item in the data written by a call
/// to os_log() or os_trace().
class OSLogBufferItem {
public:
enum Kind {
// The item is a scalar (int, float, raw pointer, etc.). No further copying
// is required. This is the only kind allowed by os_trace().
ScalarKind = 0,

// The item is a count, which describes the length of the following item to
// be copied. A count may only be followed by an item of kind StringKind,
// WideStringKind, or PointerKind.
CountKind,

// The item is a pointer to a C string. If preceded by a count 'n',
// os_log() will copy at most 'n' bytes from the pointer.
StringKind,

// The item is a pointer to a block of raw data. This item must be preceded
// by a count 'n'. os_log() will copy exactly 'n' bytes from the pointer.
PointerKind,

// The item is a pointer to an Objective-C object. os_log() may retain the
// object for later processing.
ObjCObjKind,

// The item is a pointer to wide-char string.
WideStringKind,

// The item is corresponding to the '%m' format specifier, no value is
// populated in the buffer and the runtime is loading the errno value.
ErrnoKind
};

enum {
// The item is marked "private" in the format string.
IsPrivate = 0x1,

// The item is marked "public" in the format string.
IsPublic = 0x2
};

private:
Kind TheKind = ScalarKind;
const Expr *TheExpr = nullptr;
CharUnits ConstValue;
CharUnits Size; // size of the data, not including the header bytes
unsigned Flags = 0;

public:
OSLogBufferItem(Kind kind, const Expr *expr, CharUnits size, unsigned flags)
: TheKind(kind), TheExpr(expr), Size(size), Flags(flags) {}

OSLogBufferItem(ASTContext &Ctx, CharUnits value, unsigned flags)
: TheKind(CountKind), ConstValue(value),
Size(Ctx.getTypeSizeInChars(Ctx.IntTy)), Flags(flags) {}

unsigned char getDescriptorByte() const {
unsigned char result = 0;
if (getIsPrivate())
result |= IsPrivate;
if (getIsPublic())
result |= IsPublic;
result |= ((unsigned)getKind()) << 4;
return result;
}

unsigned char getSizeByte() const { return size().getQuantity(); }

Kind getKind() const { return TheKind; }
bool getIsPrivate() const { return (Flags & IsPrivate) != 0; }
bool getIsPublic() const { return (Flags & IsPublic) != 0; }

const Expr *getExpr() const { return TheExpr; }
CharUnits getConstValue() const { return ConstValue; }
CharUnits size() const { return Size; }
};

class OSLogBufferLayout {
public:
SmallVector<OSLogBufferItem, 4> Items;

enum Flags { HasPrivateItems = 1, HasNonScalarItems = 1 << 1 };

CharUnits size() const {
CharUnits result;
result += CharUnits::fromQuantity(2); // summary byte, num-args byte
for (auto &item : Items) {
// descriptor byte, size byte
result += item.size() + CharUnits::fromQuantity(2);
}
return result;
}

bool hasPrivateItems() const {
return llvm::any_of(
Items, [](const OSLogBufferItem &Item) { return Item.getIsPrivate(); });
}

bool hasPublicItems() const {
return llvm::any_of(
Items, [](const OSLogBufferItem &Item) { return Item.getIsPublic(); });
}

bool hasNonScalar() const {
return llvm::any_of(Items, [](const OSLogBufferItem &Item) {
return Item.getKind() != OSLogBufferItem::ScalarKind;
});
}

unsigned char getSummaryByte() const {
unsigned char result = 0;
if (hasPrivateItems())
result |= HasPrivateItems;
if (hasNonScalar())
result |= HasNonScalarItems;
return result;
}

unsigned char getNumArgsByte() const { return Items.size(); }
};

// Given a call 'E' to one of the builtins __builtin_os_log_format() or
// __builtin_os_log_format_buffer_size(), compute the layout of the buffer that
// the call will write into and store it in 'layout'. Returns 'false' if there
// was some error encountered while computing the layout, and 'true' otherwise.
bool computeOSLogBufferLayout(clang::ASTContext &Ctx, const clang::CallExpr *E,
OSLogBufferLayout &layout);

} // namespace analyze_os_log
} // namespace clang
#endif
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/Builtins.def
Expand Up @@ -1384,6 +1384,10 @@ LANGBUILTIN(to_global, "v*v*", "tn", OCLC20_LANG)
LANGBUILTIN(to_local, "v*v*", "tn", OCLC20_LANG)
LANGBUILTIN(to_private, "v*v*", "tn", OCLC20_LANG)

// Builtins for os_log/os_trace
BUILTIN(__builtin_os_log_format_buffer_size, "zcC*.", "p:0:nut")
BUILTIN(__builtin_os_log_format, "v*v*cC*.", "p:0:nt")

#undef BUILTIN
#undef LIBBUILTIN
#undef LANGBUILTIN
15 changes: 15 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -7413,6 +7413,12 @@ def warn_format_non_standard: Warning<
def warn_format_non_standard_conversion_spec: Warning<
"using length modifier '%0' with conversion specifier '%1' is not supported by ISO C">,
InGroup<FormatNonStandard>, DefaultIgnore;
def warn_format_invalid_annotation : Warning<
"using '%0' format specifier annotation outside of os_log()/os_trace()">,
InGroup<Format>;
def warn_format_P_no_precision : Warning<
"using '%%P' format specifier without precision">,
InGroup<Format>;
def warn_printf_ignored_flag: Warning<
"flag '%0' is ignored when flag '%1' is present">,
InGroup<Format>;
Expand Down Expand Up @@ -7549,6 +7555,15 @@ def warn_cfstring_truncated : Warning<
"belong to the input codeset UTF-8">,
InGroup<DiagGroup<"CFString-literal">>;

// os_log checking
// TODO: separate diagnostic for os_trace()
def err_os_log_format_not_string_constant : Error<
"os_log() format argument is not a string constant">;
def err_os_log_argument_too_big : Error<
"os_log() argument %d is too big (%d bytes, max %d)">;
def warn_os_log_format_narg : Error<
"os_log() '%%n' format specifier is not allowed">, DefaultError;

// Statements.
def err_continue_not_in_loop : Error<
"'continue' statement not in loop statement">;
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -9679,6 +9679,7 @@ class Sema {
VariadicCallType CallType);

bool CheckObjCString(Expr *Arg);
ExprResult CheckOSLogFormatStringArg(Expr *Arg);

ExprResult CheckBuiltinFunctionCall(FunctionDecl *FDecl,
unsigned BuiltinID, CallExpr *TheCall);
Expand All @@ -9701,6 +9702,7 @@ class Sema {
bool SemaBuiltinVAStartARM(CallExpr *Call);
bool SemaBuiltinUnorderedCompare(CallExpr *TheCall);
bool SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs);
bool SemaBuiltinOSLogFormat(CallExpr *TheCall);

public:
// Used by C++ template instantiation.
Expand Down Expand Up @@ -9738,6 +9740,7 @@ class Sema {
FST_Kprintf,
FST_FreeBSDKPrintf,
FST_OSTrace,
FST_OSLog,
FST_Unknown
};
static FormatStringType GetFormatStringType(const FormatAttr *Format);
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Analysis/CMakeLists.txt
Expand Up @@ -16,6 +16,7 @@ add_clang_library(clangAnalysis
Dominators.cpp
FormatString.cpp
LiveVariables.cpp
OSLog.cpp
ObjCNoReturn.cpp
PostOrderCFGView.cpp
PrintfFormatString.cpp
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Analysis/FormatString.cpp
Expand Up @@ -591,6 +591,8 @@ const char *ConversionSpecifier::toString() const {
case cArg: return "c";
case sArg: return "s";
case pArg: return "p";
case PArg:
return "P";
case nArg: return "n";
case PercentArg: return "%";
case ScanListArg: return "[";
Expand Down Expand Up @@ -866,6 +868,7 @@ bool FormatSpecifier::hasStandardConversionSpecifier(
case ConversionSpecifier::ObjCObjArg:
case ConversionSpecifier::ScanListArg:
case ConversionSpecifier::PercentArg:
case ConversionSpecifier::PArg:
return true;
case ConversionSpecifier::CArg:
case ConversionSpecifier::SArg:
Expand Down

0 comments on commit 2903436

Please sign in to comment.