blob: 70e45e9fc304a9c86e30da2ebd1158456db0b47d [file] [log] [blame]
//===--- 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
if (!FS.consumesDataArgument())
return false;
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;
}