OSLog.cpp   [plain text]


//===--- 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;
}