1//===- Library.cpp - Library calls for llubi ------------------------------===//
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// This file implements common libcalls for llubi.
10//
11//===----------------------------------------------------------------------===//
12
13#include "Library.h"
14#include "llvm/ADT/STLExtras.h"
15#include "llvm/Analysis/TargetLibraryInfo.h"
16#include "llvm/IR/InstrTypes.h"
17#include "llvm/Support/Format.h"
18#include "llvm/Support/raw_ostream.h"
19
20namespace llvm::ubi {
21
22static uint64_t getMaxAlign(const DataLayout &DL, unsigned AS) {
23 // Return an alignment of 16 for 64-bit platforms, and 8 for 32-bit ones.
24 return DL.getPointerABIAlignment(AS).value() >= 8 ? 16 : 8;
25}
26
27Library::Library(Context &Ctx, EventHandler &Handler, const DataLayout &DL,
28 ExecutorBase &Executor)
29 : Ctx(Ctx), Handler(Handler), DL(DL), Executor(Executor) {}
30
31std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
32 std::string Result;
33 const APInt &Address = Ptr.address();
34 uint64_t Offset = 0;
35
36 while (true) {
37 auto [MO, ValidOffset] = Executor.verifyMemAccess(
38 Ptr: Ptr.getWithNewAddr(NewAddr: Address + Offset), AccessSize: 1, Alignment: Align(1), /*IsStore=*/false);
39 if (!MO)
40 return std::nullopt;
41
42 Byte B = (*MO)[ValidOffset];
43 if (B.ConcreteMask != 0xFF) {
44 Executor.reportImmediateUB()
45 << "Read uninitialized or poison memory while "
46 "parsing C-string at offset "
47 << Offset << ".";
48 return std::nullopt;
49 }
50
51 if (B.Value == 0)
52 break;
53
54 Result.push_back(c: static_cast<char>(B.Value));
55 ++Offset;
56 }
57
58 return Result;
59}
60
61AnyValue Library::executeMalloc(StringRef Name, Type *Type,
62 ArrayRef<AnyValue> Args,
63 MemAllocKind AllocKind) {
64 assert((AllocKind == MemAllocKind::Malloc || AllocKind == MemAllocKind::New ||
65 AllocKind == MemAllocKind::NewArray) &&
66 "Unexpected MemAllocKind for malloc()/new/new[]");
67
68 const auto &SizeVal = Args[0];
69
70 const uint64_t AllocSize = SizeVal.asInteger().getLimitedValue();
71 const unsigned AS = Type->getPointerAddressSpace();
72
73 const IntrusiveRefCntPtr<MemoryObject> Obj =
74 Ctx.allocate(Size: AllocSize, Align: getMaxAlign(DL, AS), Name, AS,
75 InitKind: MemInitKind::Uninitialized, AllocKind);
76
77 if (!Obj) {
78 if (AllocKind == MemAllocKind::New || AllocKind == MemAllocKind::NewArray) {
79 // FIXME: As llubi doesn't support stack unwinding yet, we report an error
80 // when new/new[] fails.
81 Executor.reportError() << "Insufficient heap space.";
82 return AnyValue::poison();
83 }
84 return AnyValue::getNullValue(Ctx, Ty: Type);
85 }
86
87 return Ctx.deriveFromMemoryObject(Obj);
88}
89
90AnyValue Library::executeCalloc(StringRef Name, Type *Type,
91 ArrayRef<AnyValue> Args,
92 MemAllocKind AllocKind) {
93 assert(AllocKind == MemAllocKind::Malloc &&
94 "Unexpected MemAllocKind for calloc()");
95
96 const auto &CountVal = Args[0];
97 const auto &SizeVal = Args[1];
98
99 const APInt &Count = CountVal.asInteger();
100 const APInt &Size = SizeVal.asInteger();
101
102 bool Overflow = false;
103 const APInt AllocSize = Count.umul_ov(RHS: Size, Overflow);
104 if (Overflow)
105 return AnyValue::getNullValue(Ctx, Ty: Type);
106 const unsigned AS = Type->getPointerAddressSpace();
107
108 const IntrusiveRefCntPtr<MemoryObject> Obj =
109 Ctx.allocate(Size: AllocSize.getLimitedValue(), Align: getMaxAlign(DL, AS), Name, AS,
110 InitKind: MemInitKind::Zeroed, AllocKind);
111
112 if (!Obj)
113 return AnyValue::getNullValue(Ctx, Ty: Type);
114
115 return Ctx.deriveFromMemoryObject(Obj);
116}
117
118AnyValue Library::executeFree(ArrayRef<AnyValue> Args, unsigned AS) {
119 const auto &PtrVal = Args[0];
120
121 auto &Ptr = PtrVal.asPointer();
122 // no-op when free is called with a null pointer.
123 if (Ptr.isNullPtr(AS, DL))
124 return AnyValue();
125
126 MemoryObject *Obj = Ctx.checkProvenance(Ptr, Check: [](const Provenance &) {
127 // TODO: check nofree
128 return true;
129 });
130 if (!Obj) {
131 Executor.reportImmediateUB()
132 << "freeing a pointer with nullary provenance.";
133 return AnyValue();
134 }
135
136 if (const uint64_t Address = Ptr.address().getZExtValue();
137 Address != Obj->getAddress()) {
138 Executor.reportImmediateUB()
139 << "freeing a pointer that does not point to "
140 "the start of an allocation. Pointer address: 0x"
141 << Twine::utohexstr(Val: Address) << ", allocation base: 0x"
142 << Twine::utohexstr(Val: Obj->getAddress()) << ".";
143 return AnyValue();
144 }
145
146 if (Obj->getState() == MemoryObjectState::Freed) {
147 Executor.reportImmediateUB()
148 << "double-freeing a memory object allocated at 0x"
149 << Twine::utohexstr(Val: Obj->getAddress()) << ".";
150 return AnyValue();
151 }
152
153 if (!Obj->isHeapAllocated()) {
154 Executor.reportImmediateUB() << "freeing a non-heap allocation at 0x"
155 << Twine::utohexstr(Val: Obj->getAddress()) << ".";
156 return AnyValue();
157 }
158
159 // Currently we don't check for cases where a memory allocated with C
160 // allocation family (malloc, calloc, etc.) is freed with a different free
161 // function comes from a different family (C++ delete, etc.)
162
163 if (!Ctx.free(Obj: *Obj)) {
164 Executor.reportImmediateUB()
165 << "freeing an invalid pointer at 0x"
166 << Twine::utohexstr(Val: Ptr.address().getZExtValue()) << ".";
167 return AnyValue::poison();
168 }
169
170 return AnyValue();
171}
172
173AnyValue Library::executePuts(ArrayRef<AnyValue> Args) {
174 const auto &PtrVal = Args[0];
175
176 const auto StrOpt = readStringFromMemory(Ptr: PtrVal.asPointer());
177 if (!StrOpt)
178 return AnyValue::poison();
179
180 Handler.onPrint(Msg: *StrOpt + "\n");
181 return AnyValue(APInt(Executor.getIntSize(), 1));
182}
183
184AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
185 const auto &FormatPtrVal = Args[0];
186
187 const auto FormatStrOpt = readStringFromMemory(Ptr: FormatPtrVal.asPointer());
188 if (!FormatStrOpt)
189 return AnyValue::poison();
190
191 const std::string &FormatStr = *FormatStrOpt;
192 std::string Output;
193 raw_string_ostream OS(Output);
194 unsigned ArgIndex = 1; // Start from 1 since 0 is the format string.
195
196 for (unsigned I = 0; I < FormatStr.size();) {
197 if (FormatStr[I] != '%') {
198 OS << FormatStr[I++];
199 continue;
200 }
201
202 const size_t Start = I++;
203 if (I < FormatStr.size() && FormatStr[I] == '%') {
204 OS << '%';
205 ++I;
206 continue;
207 }
208
209 while (I < FormatStr.size() &&
210 StringRef("-= #0123456789").contains(C: FormatStr[I]))
211 ++I;
212
213 while (I < FormatStr.size() && StringRef("hljzt").contains(C: FormatStr[I]))
214 ++I;
215
216 if (I >= FormatStr.size()) {
217 Executor.reportImmediateUB()
218 << "Invalid format string in printf: missing conversion specifier.";
219 return AnyValue::poison();
220 }
221
222 char Specifier = FormatStr[I++];
223 std::string CleanChunk = FormatStr.substr(pos: Start, n: I - Start - 1);
224 CleanChunk.erase(
225 first: llvm::remove_if(Range&: CleanChunk,
226 P: [](char C) { return StringRef("hljzt").contains(C); }),
227 last: CleanChunk.end());
228
229 if (ArgIndex >= Args.size()) {
230 Executor.reportImmediateUB() << "Not enough arguments provided for the "
231 "format string. Required argument for '"
232 << Specifier << "'.";
233 return AnyValue::poison();
234 }
235
236 const auto &Arg = Args[ArgIndex++];
237 if (Arg.isPoison()) {
238 Executor.reportImmediateUB()
239 << "Poison argument passed to printf for format specifier '"
240 << Specifier << "' at argument index " << ArgIndex << ".";
241 return AnyValue::poison();
242 }
243
244 bool TypeMismatch =
245 (StringRef("diuoxXc").contains(C: Specifier) &&
246 !(Arg.isInteger() && (Arg.asInteger().getBitWidth() == 32 ||
247 Arg.asInteger().getBitWidth() == 64))) ||
248 (StringRef("feEgGaA").contains(C: Specifier) &&
249 !(Arg.isFloat() &&
250 &Arg.asFloat().getSemantics() == &APFloat::IEEEdouble())) ||
251 (StringRef("nps").contains(C: Specifier) &&
252 !(Arg.isPointer() && Arg.asPointer().address().getBitWidth() <= 64));
253
254 if (TypeMismatch) {
255 Executor.reportImmediateUB()
256 << "Argument type mismatch in printf for format specifier '"
257 << Specifier << "' at argument index " << (ArgIndex - 1) << ".";
258 return AnyValue::poison();
259 }
260
261 switch (Specifier) {
262 case 'd':
263 case 'i': {
264 std::string HostFmt = CleanChunk + "ll" + Specifier;
265 OS << format(Fmt: HostFmt.c_str(),
266 Vals: static_cast<long long>(Arg.asInteger().getSExtValue()));
267 break;
268 }
269 case 'u':
270 case 'o':
271 case 'x':
272 case 'X': {
273 // FIXME: The format specifiers "b" and "B" are not implemented here
274 // since currently MSVC doesn't support it.
275 std::string HostFmt = CleanChunk + "ll" + Specifier;
276 OS << format(Fmt: HostFmt.c_str(), Vals: static_cast<unsigned long long>(
277 Arg.asInteger().getZExtValue()));
278 break;
279 }
280 case 'c': {
281 std::string HostFmt = CleanChunk + Specifier;
282 OS << format(Fmt: HostFmt.c_str(),
283 Vals: static_cast<int>(Arg.asInteger().getZExtValue()));
284 break;
285 }
286 case 'f':
287 case 'e':
288 case 'E':
289 case 'g':
290 case 'G':
291 case 'a':
292 case 'A': {
293 std::string HostFmt = CleanChunk + Specifier;
294 OS << format(Fmt: HostFmt.c_str(), Vals: Arg.asFloat().convertToDouble());
295 break;
296 }
297 case 'n': {
298 OS.flush();
299 Executor.store(Ptr: Arg, Alignment: Align(4), Val: AnyValue(APInt(32, Output.size())),
300 ValTy: Type::getInt32Ty(C&: Ctx.getContext()));
301 break;
302 }
303 case 'p': {
304 std::string HostFmt = CleanChunk + "llx";
305 OS << "0x"
306 << format(Fmt: HostFmt.c_str(),
307 Vals: static_cast<unsigned long long>(
308 Arg.asPointer().address().getZExtValue()));
309 break;
310 }
311 case 's': {
312 auto StrOpt = readStringFromMemory(Ptr: Arg.asPointer());
313 if (!StrOpt)
314 return AnyValue::poison();
315 std::string HostFmt = CleanChunk + "s";
316 OS << format(Fmt: HostFmt.c_str(), Vals: StrOpt->c_str());
317 break;
318 }
319 default:
320 Executor.reportImmediateUB()
321 << "Unknown or unsupported format specifier '" << Specifier
322 << "' in printf.";
323 return AnyValue::poison();
324 }
325 }
326
327 OS.flush();
328 Handler.onPrint(Msg: Output);
329 return AnyValue(APInt(Executor.getIntSize(), Output.size()));
330}
331
332AnyValue Library::executeExit(ArrayRef<AnyValue> Args) {
333 const auto &RetCodeVal = Args[0];
334
335 Executor.requestProgramExit(Kind: ProgramExitInfo::ProgramExitKind::Exited,
336 ExitCode: RetCodeVal.asInteger().getZExtValue());
337 return AnyValue();
338}
339
340AnyValue Library::executeAbort() {
341 Executor.requestProgramExit(Kind: ProgramExitInfo::ProgramExitKind::Aborted);
342 return AnyValue();
343}
344
345AnyValue Library::executeTerminate() {
346 Executor.requestProgramExit(Kind: ProgramExitInfo::ProgramExitKind::Terminated);
347 return AnyValue();
348}
349
350std::optional<AnyValue> Library::executeLibcall(LibFunc LF, StringRef Name,
351 FunctionType *FuncType,
352 ArrayRef<AnyValue> Args) {
353 Type *Type = FuncType->getReturnType();
354
355 unsigned Index = 0;
356 for (const AnyValue &Arg : Args) {
357 if (Arg.isPoison()) {
358 Executor.reportImmediateUB()
359 << "Poison argument passed to a library call at argument index "
360 << Index << ".";
361 return AnyValue::poison();
362 }
363 ++Index;
364 }
365
366 switch (LF) {
367 case LibFunc_malloc:
368 return executeMalloc(Name, Type, Args, AllocKind: MemAllocKind::Malloc);
369 case LibFunc_Znwm:
370 return executeMalloc(Name, Type, Args, AllocKind: MemAllocKind::New);
371 case LibFunc_Znam:
372 return executeMalloc(Name, Type, Args, AllocKind: MemAllocKind::NewArray);
373
374 case LibFunc_calloc:
375 return executeCalloc(Name, Type, Args, AllocKind: MemAllocKind::Malloc);
376
377 case LibFunc_free:
378 case LibFunc_ZdaPv:
379 case LibFunc_ZdlPv:
380 return executeFree(Args,
381 AS: FuncType->getParamType(i: 0)->getPointerAddressSpace());
382
383 case LibFunc_puts:
384 return executePuts(Args);
385
386 case LibFunc_printf:
387 return executePrintf(Args);
388
389 case LibFunc_exit:
390 return executeExit(Args);
391
392 case LibFunc_abort:
393 return executeAbort();
394
395 case LibFunc_terminate:
396 return executeTerminate();
397
398 default:
399 return std::nullopt;
400 }
401}
402} // namespace llvm::ubi
403