1// TODO: header template
2
3#include "clang/AST/OSLog.h"
4#include "clang/AST/Attr.h"
5#include "clang/AST/Decl.h"
6#include "clang/AST/DeclCXX.h"
7#include "clang/AST/ExprObjC.h"
8#include "clang/AST/FormatString.h"
9#include "clang/Basic/Builtins.h"
10#include "llvm/ADT/SmallBitVector.h"
11#include <optional>
12
13using namespace clang;
14
15using clang::analyze_os_log::OSLogBufferItem;
16using clang::analyze_os_log::OSLogBufferLayout;
17
18namespace {
19class OSLogFormatStringHandler
20    : public analyze_format_string::FormatStringHandler {
21private:
22  struct ArgData {
23    const Expr *E = nullptr;
24    std::optional<OSLogBufferItem::Kind> Kind;
25    std::optional<unsigned> Size;
26    std::optional<const Expr *> Count;
27    std::optional<const Expr *> Precision;
28    std::optional<const Expr *> FieldWidth;
29    unsigned char Flags = 0;
30    StringRef MaskType;
31  };
32  SmallVector<ArgData, 4> ArgsData;
33  ArrayRef<const Expr *> Args;
34
35  OSLogBufferItem::Kind
36  getKind(analyze_format_string::ConversionSpecifier::Kind K) {
37    switch (K) {
38    case clang::analyze_format_string::ConversionSpecifier::sArg: // "%s"
39      return OSLogBufferItem::StringKind;
40    case clang::analyze_format_string::ConversionSpecifier::SArg: // "%S"
41      return OSLogBufferItem::WideStringKind;
42    case clang::analyze_format_string::ConversionSpecifier::PArg: { // "%P"
43      return OSLogBufferItem::PointerKind;
44    case clang::analyze_format_string::ConversionSpecifier::ObjCObjArg: // "%@"
45      return OSLogBufferItem::ObjCObjKind;
46    case clang::analyze_format_string::ConversionSpecifier::PrintErrno: // "%m"
47      return OSLogBufferItem::ErrnoKind;
48    default:
49      return OSLogBufferItem::ScalarKind;
50    }
51    }
52  }
53
54public:
55  OSLogFormatStringHandler(ArrayRef<const Expr *> Args) : Args(Args) {
56    ArgsData.reserve(Args.size());
57  }
58
59  bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS,
60                             const char *StartSpecifier, unsigned SpecifierLen,
61                             const TargetInfo &) override {
62    if (!FS.consumesDataArgument() &&
63        FS.getConversionSpecifier().getKind() !=
64            clang::analyze_format_string::ConversionSpecifier::PrintErrno)
65      return true;
66
67    ArgsData.emplace_back();
68    unsigned ArgIndex = FS.getArgIndex();
69    if (ArgIndex < Args.size())
70      ArgsData.back().E = Args[ArgIndex];
71
72    // First get the Kind
73    ArgsData.back().Kind = getKind(FS.getConversionSpecifier().getKind());
74    if (ArgsData.back().Kind != OSLogBufferItem::ErrnoKind &&
75        !ArgsData.back().E) {
76      // missing argument
77      ArgsData.pop_back();
78      return false;
79    }
80
81    switch (FS.getConversionSpecifier().getKind()) {
82    case clang::analyze_format_string::ConversionSpecifier::sArg:   // "%s"
83    case clang::analyze_format_string::ConversionSpecifier::SArg: { // "%S"
84      auto &precision = FS.getPrecision();
85      switch (precision.getHowSpecified()) {
86      case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%s"
87        break;
88      case clang::analyze_format_string::OptionalAmount::Constant: // "%.16s"
89        ArgsData.back().Size = precision.getConstantAmount();
90        break;
91      case clang::analyze_format_string::OptionalAmount::Arg: // "%.*s"
92        ArgsData.back().Count = Args[precision.getArgIndex()];
93        break;
94      case clang::analyze_format_string::OptionalAmount::Invalid:
95        return false;
96      }
97      break;
98    }
99    case clang::analyze_format_string::ConversionSpecifier::PArg: { // "%P"
100      auto &precision = FS.getPrecision();
101      switch (precision.getHowSpecified()) {
102      case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%P"
103        return false; // length must be supplied with pointer format specifier
104      case clang::analyze_format_string::OptionalAmount::Constant: // "%.16P"
105        ArgsData.back().Size = precision.getConstantAmount();
106        break;
107      case clang::analyze_format_string::OptionalAmount::Arg: // "%.*P"
108        ArgsData.back().Count = Args[precision.getArgIndex()];
109        break;
110      case clang::analyze_format_string::OptionalAmount::Invalid:
111        return false;
112      }
113      break;
114    }
115    default:
116      if (FS.getPrecision().hasDataArgument()) {
117        ArgsData.back().Precision = Args[FS.getPrecision().getArgIndex()];
118      }
119      break;
120    }
121    if (FS.getFieldWidth().hasDataArgument()) {
122      ArgsData.back().FieldWidth = Args[FS.getFieldWidth().getArgIndex()];
123    }
124
125    if (FS.isSensitive())
126      ArgsData.back().Flags |= OSLogBufferItem::IsSensitive;
127    else if (FS.isPrivate())
128      ArgsData.back().Flags |= OSLogBufferItem::IsPrivate;
129    else if (FS.isPublic())
130      ArgsData.back().Flags |= OSLogBufferItem::IsPublic;
131
132    ArgsData.back().MaskType = FS.getMaskType();
133    return true;
134  }
135
136  void computeLayout(ASTContext &Ctx, OSLogBufferLayout &Layout) const {
137    Layout.Items.clear();
138    for (auto &Data : ArgsData) {
139      if (!Data.MaskType.empty()) {
140        CharUnits Size = CharUnits::fromQuantity(8);
141        Layout.Items.emplace_back(OSLogBufferItem::MaskKind, nullptr,
142                                  Size, 0, Data.MaskType);
143      }
144
145      if (Data.FieldWidth) {
146        CharUnits Size = Ctx.getTypeSizeInChars((*Data.FieldWidth)->getType());
147        Layout.Items.emplace_back(OSLogBufferItem::ScalarKind, *Data.FieldWidth,
148                                  Size, 0);
149      }
150      if (Data.Precision) {
151        CharUnits Size = Ctx.getTypeSizeInChars((*Data.Precision)->getType());
152        Layout.Items.emplace_back(OSLogBufferItem::ScalarKind, *Data.Precision,
153                                  Size, 0);
154      }
155      if (Data.Count) {
156        // "%.*P" has an extra "count" that we insert before the argument.
157        CharUnits Size = Ctx.getTypeSizeInChars((*Data.Count)->getType());
158        Layout.Items.emplace_back(OSLogBufferItem::CountKind, *Data.Count, Size,
159                                  0);
160      }
161      if (Data.Size)
162        Layout.Items.emplace_back(Ctx, CharUnits::fromQuantity(*Data.Size),
163                                  Data.Flags);
164      if (Data.Kind) {
165        CharUnits Size;
166        if (*Data.Kind == OSLogBufferItem::ErrnoKind)
167          Size = CharUnits::Zero();
168        else
169          Size = Ctx.getTypeSizeInChars(Data.E->getType());
170        Layout.Items.emplace_back(*Data.Kind, Data.E, Size, Data.Flags);
171      } else {
172        auto Size = Ctx.getTypeSizeInChars(Data.E->getType());
173        Layout.Items.emplace_back(OSLogBufferItem::ScalarKind, Data.E, Size,
174                                  Data.Flags);
175      }
176    }
177  }
178};
179} // end anonymous namespace
180
181bool clang::analyze_os_log::computeOSLogBufferLayout(
182    ASTContext &Ctx, const CallExpr *E, OSLogBufferLayout &Layout) {
183  ArrayRef<const Expr *> Args(E->getArgs(), E->getArgs() + E->getNumArgs());
184
185  const Expr *StringArg;
186  ArrayRef<const Expr *> VarArgs;
187  switch (E->getBuiltinCallee()) {
188  case Builtin::BI__builtin_os_log_format_buffer_size:
189    assert(E->getNumArgs() >= 1 &&
190           "__builtin_os_log_format_buffer_size takes at least 1 argument");
191    StringArg = E->getArg(0);
192    VarArgs = Args.slice(1);
193    break;
194  case Builtin::BI__builtin_os_log_format:
195    assert(E->getNumArgs() >= 2 &&
196           "__builtin_os_log_format takes at least 2 arguments");
197    StringArg = E->getArg(1);
198    VarArgs = Args.slice(2);
199    break;
200  default:
201    llvm_unreachable("non-os_log builtin passed to computeOSLogBufferLayout");
202  }
203
204  const StringLiteral *Lit = cast<StringLiteral>(StringArg->IgnoreParenCasts());
205  assert(Lit && (Lit->isOrdinary() || Lit->isUTF8()));
206  StringRef Data = Lit->getString();
207  OSLogFormatStringHandler H(VarArgs);
208  ParsePrintfString(H, Data.begin(), Data.end(), Ctx.getLangOpts(),
209                    Ctx.getTargetInfo(), /*isFreeBSDKPrintf*/ false);
210
211  H.computeLayout(Ctx, Layout);
212  return true;
213}
214