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