1//===-- tsan_report.cpp ---------------------------------------------------===//
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 is a part of ThreadSanitizer (TSan), a race detector.
10//
11//===----------------------------------------------------------------------===//
12#include "tsan_report.h"
13#include "tsan_platform.h"
14#include "tsan_rtl.h"
15#include "sanitizer_common/sanitizer_file.h"
16#include "sanitizer_common/sanitizer_placement_new.h"
17#include "sanitizer_common/sanitizer_report_decorator.h"
18#include "sanitizer_common/sanitizer_stacktrace_printer.h"
19
20namespace __tsan {
21
22class Decorator: public __sanitizer::SanitizerCommonDecorator {
23 public:
24 Decorator() : SanitizerCommonDecorator() { }
25 const char *Access() { return Blue(); }
26 const char *ThreadDescription() { return Cyan(); }
27 const char *Location() { return Green(); }
28 const char *Sleep() { return Yellow(); }
29 const char *Mutex() { return Magenta(); }
30};
31
32ReportDesc::ReportDesc() = default;
33
34ReportMop::ReportMop() = default;
35ReportMop::~ReportMop() = default;
36
37// FIXME(dvyukov): it must be leaking a lot of memory.
38ReportDesc::~ReportDesc() = default;
39
40#if !SANITIZER_GO
41
42const int kThreadBufSize = 32;
43const char *thread_name(char *buf, Tid tid) {
44 if (tid == kMainTid)
45 return "main thread";
46 internal_snprintf(buffer: buf, length: kThreadBufSize, format: "thread T%d", tid);
47 return buf;
48}
49
50static const char *ReportTypeString(ReportType typ, uptr tag) {
51 switch (typ) {
52 case ReportTypeRace:
53 return "data race";
54 case ReportTypeVptrRace:
55 return "data race on vptr (ctor/dtor vs virtual call)";
56 case ReportTypeUseAfterFree:
57 return "heap-use-after-free";
58 case ReportTypeVptrUseAfterFree:
59 return "heap-use-after-free (virtual call vs free)";
60 case ReportTypeExternalRace: {
61 const char *str = GetReportHeaderFromTag(tag);
62 return str ? str : "race on external object";
63 }
64 case ReportTypeThreadLeak:
65 return "thread leak";
66 case ReportTypeMutexDestroyLocked:
67 return "destroy of a locked mutex";
68 case ReportTypeMutexDoubleLock:
69 return "double lock of a mutex";
70 case ReportTypeMutexInvalidAccess:
71 return "use of an invalid mutex (e.g. uninitialized or destroyed)";
72 case ReportTypeMutexBadUnlock:
73 return "unlock of an unlocked mutex (or by a wrong thread)";
74 case ReportTypeMutexBadReadLock:
75 return "read lock of a write locked mutex";
76 case ReportTypeMutexBadReadUnlock:
77 return "read unlock of a write locked mutex";
78 case ReportTypeSignalUnsafe:
79 return "signal-unsafe call inside of a signal";
80 case ReportTypeErrnoInSignal:
81 return "signal handler spoils errno";
82 case ReportTypeDeadlock:
83 return "lock-order-inversion (potential deadlock)";
84 case ReportTypeMutexHeldWrongContext:
85 return "mutex held in the wrong context";
86 // No default case so compiler warns us if we miss one
87 }
88 UNREACHABLE("missing case");
89}
90
91void PrintStack(const ReportStack *ent) {
92 if (ent == 0 || ent->frames == 0) {
93 Printf(format: " [failed to restore the stack]\n\n");
94 return;
95 }
96 SymbolizedStack *frame = ent->frames;
97 for (int i = 0; frame && frame->info.address; frame = frame->next, i++) {
98 InternalScopedString res;
99 StackTracePrinter::GetOrInit()->RenderFrame(
100 buffer: &res, format: common_flags()->stack_trace_format, frame_no: i, address: frame->info.address,
101 info: &frame->info, vs_style: common_flags()->symbolize_vs_style,
102 strip_path_prefix: common_flags()->strip_path_prefix);
103 Printf(format: "%s\n", res.data());
104 }
105 Printf(format: "\n");
106}
107
108static void PrintMutexSet(Vector<ReportMopMutex> const& mset) {
109 for (uptr i = 0; i < mset.Size(); i++) {
110 if (i == 0)
111 Printf(format: " (mutexes:");
112 const ReportMopMutex m = mset[i];
113 Printf(format: " %s M%u", m.write ? "write" : "read", m.id);
114 Printf(format: i == mset.Size() - 1 ? ")" : ",");
115 }
116}
117
118static const char *MopDesc(bool first, bool write, bool atomic) {
119 return atomic ? (first ? (write ? "Atomic write" : "Atomic read")
120 : (write ? "Previous atomic write" : "Previous atomic read"))
121 : (first ? (write ? "Write" : "Read")
122 : (write ? "Previous write" : "Previous read"));
123}
124
125static const char *ExternalMopDesc(bool first, bool write) {
126 return first ? (write ? "Modifying" : "Read-only")
127 : (write ? "Previous modifying" : "Previous read-only");
128}
129
130static void PrintMop(const ReportMop *mop, bool first) {
131 Decorator d;
132 char thrbuf[kThreadBufSize];
133 Printf(format: "%s", d.Access());
134 if (mop->external_tag == kExternalTagNone) {
135 Printf(format: " %s of size %d at %p by %s",
136 MopDesc(first, write: mop->write, atomic: mop->atomic), mop->size,
137 (void *)mop->addr, thread_name(buf: thrbuf, tid: mop->tid));
138 } else {
139 const char *object_type = GetObjectTypeFromTag(tag: mop->external_tag);
140 if (object_type == nullptr)
141 object_type = "external object";
142 Printf(format: " %s access of %s at %p by %s",
143 ExternalMopDesc(first, write: mop->write), object_type,
144 (void *)mop->addr, thread_name(buf: thrbuf, tid: mop->tid));
145 }
146 PrintMutexSet(mset: mop->mset);
147 Printf(format: ":\n");
148 Printf(format: "%s", d.Default());
149 PrintStack(ent: mop->stack);
150}
151
152static void PrintLocation(const ReportLocation *loc) {
153 Decorator d;
154 char thrbuf[kThreadBufSize];
155 bool print_stack = false;
156 Printf(format: "%s", d.Location());
157 if (loc->type == ReportLocationGlobal) {
158 const DataInfo &global = loc->global;
159 if (global.size != 0)
160 Printf(format: " Location is global '%s' of size %zu at %p (%s+0x%zx)\n\n",
161 global.name, global.size, reinterpret_cast<void *>(global.start),
162 StripModuleName(module: global.module), global.module_offset);
163 else
164 Printf(format: " Location is global '%s' at %p (%s+0x%zx)\n\n", global.name,
165 reinterpret_cast<void *>(global.start),
166 StripModuleName(module: global.module), global.module_offset);
167 } else if (loc->type == ReportLocationHeap) {
168 char thrbuf[kThreadBufSize];
169 const char *object_type = GetObjectTypeFromTag(tag: loc->external_tag);
170 if (!object_type) {
171 Printf(format: " Location is heap block of size %zu at %p allocated by %s:\n",
172 loc->heap_chunk_size,
173 reinterpret_cast<void *>(loc->heap_chunk_start),
174 thread_name(buf: thrbuf, tid: loc->tid));
175 } else {
176 Printf(format: " Location is %s of size %zu at %p allocated by %s:\n",
177 object_type, loc->heap_chunk_size,
178 reinterpret_cast<void *>(loc->heap_chunk_start),
179 thread_name(buf: thrbuf, tid: loc->tid));
180 }
181 print_stack = true;
182 } else if (loc->type == ReportLocationStack) {
183 Printf(format: " Location is stack of %s.\n\n", thread_name(buf: thrbuf, tid: loc->tid));
184 } else if (loc->type == ReportLocationTLS) {
185 Printf(format: " Location is TLS of %s.\n\n", thread_name(buf: thrbuf, tid: loc->tid));
186 } else if (loc->type == ReportLocationFD) {
187 Printf(format: " Location is file descriptor %d %s by %s at:\n", loc->fd,
188 loc->fd_closed ? "destroyed" : "created",
189 thread_name(buf: thrbuf, tid: loc->tid));
190 print_stack = true;
191 }
192 Printf(format: "%s", d.Default());
193 if (print_stack)
194 PrintStack(ent: loc->stack);
195}
196
197static void PrintMutexShort(const ReportMutex *rm, const char *after) {
198 Decorator d;
199 Printf(format: "%sM%d%s%s", d.Mutex(), rm->id, d.Default(), after);
200}
201
202static void PrintMutexShortWithAddress(const ReportMutex *rm,
203 const char *after) {
204 Decorator d;
205 Printf(format: "%sM%d (%p)%s%s", d.Mutex(), rm->id,
206 reinterpret_cast<void *>(rm->addr), d.Default(), after);
207}
208
209static void PrintMutex(const ReportMutex *rm) {
210 Decorator d;
211 Printf(format: "%s", d.Mutex());
212 Printf(format: " Mutex M%u (%p) created at:\n", rm->id,
213 reinterpret_cast<void *>(rm->addr));
214 Printf(format: "%s", d.Default());
215 PrintStack(ent: rm->stack);
216}
217
218static void PrintThread(const ReportThread *rt) {
219 Decorator d;
220 if (rt->id == kMainTid) // Little sense in describing the main thread.
221 return;
222 Printf(format: "%s", d.ThreadDescription());
223 Printf(format: " Thread T%d", rt->id);
224 if (rt->name && rt->name[0] != '\0')
225 Printf(format: " '%s'", rt->name);
226 char thrbuf[kThreadBufSize];
227 const char *thread_status = rt->running ? "running" : "finished";
228 if (rt->thread_type == ThreadType::Worker) {
229 Printf(format: " (tid=%llu, %s) is a GCD worker thread\n", rt->os_id,
230 thread_status);
231 Printf(format: "\n");
232 Printf(format: "%s", d.Default());
233 return;
234 }
235 Printf(format: " (tid=%llu, %s) created by %s", rt->os_id, thread_status,
236 thread_name(buf: thrbuf, tid: rt->parent_tid));
237 if (rt->stack)
238 Printf(format: " at:");
239 Printf(format: "\n");
240 Printf(format: "%s", d.Default());
241 PrintStack(ent: rt->stack);
242}
243
244static void PrintSleep(const ReportStack *s) {
245 Decorator d;
246 Printf(format: "%s", d.Sleep());
247 Printf(format: " As if synchronized via sleep:\n");
248 Printf(format: "%s", d.Default());
249 PrintStack(ent: s);
250}
251
252static ReportStack *ChooseSummaryStack(const ReportDesc *rep) {
253 if (rep->mops.Size())
254 return rep->mops[0]->stack;
255 if (rep->stacks.Size())
256 return rep->stacks[0];
257 if (rep->mutexes.Size())
258 return rep->mutexes[0]->stack;
259 if (rep->threads.Size())
260 return rep->threads[0]->stack;
261 return 0;
262}
263
264static const SymbolizedStack *SkipTsanInternalFrames(SymbolizedStack *frames) {
265 if (const SymbolizedStack *f = SkipInternalFrames(frames))
266 return f;
267 return frames; // Fallback to the top frame.
268}
269
270void PrintReport(const ReportDesc *rep) {
271 Decorator d;
272 Printf(format: "==================\n");
273 const char *rep_typ_str = ReportTypeString(typ: rep->typ, tag: rep->tag);
274 Printf(format: "%s", d.Warning());
275 Printf(format: "WARNING: ThreadSanitizer: %s (pid=%d)\n", rep_typ_str,
276 (int)internal_getpid());
277 Printf(format: "%s", d.Default());
278
279 if (rep->typ == ReportTypeErrnoInSignal)
280 Printf(format: " Signal %u handler invoked at:\n", rep->signum);
281
282 if (rep->typ == ReportTypeDeadlock) {
283 char thrbuf[kThreadBufSize];
284 Printf(format: " Cycle in lock order graph: ");
285 for (uptr i = 0; i < rep->mutexes.Size(); i++)
286 PrintMutexShortWithAddress(rm: rep->mutexes[i], after: " => ");
287 PrintMutexShort(rm: rep->mutexes[0], after: "\n\n");
288 CHECK_GT(rep->mutexes.Size(), 0U);
289 CHECK_EQ(rep->mutexes.Size() * (flags()->second_deadlock_stack ? 2 : 1),
290 rep->stacks.Size());
291 for (uptr i = 0; i < rep->mutexes.Size(); i++) {
292 Printf(format: " Mutex ");
293 PrintMutexShort(rm: rep->mutexes[(i + 1) % rep->mutexes.Size()],
294 after: " acquired here while holding mutex ");
295 PrintMutexShort(rm: rep->mutexes[i], after: " in ");
296 Printf(format: "%s", d.ThreadDescription());
297 Printf(format: "%s:\n", thread_name(buf: thrbuf, tid: rep->unique_tids[i]));
298 Printf(format: "%s", d.Default());
299 if (flags()->second_deadlock_stack) {
300 PrintStack(ent: rep->stacks[2*i]);
301 Printf(format: " Mutex ");
302 PrintMutexShort(rm: rep->mutexes[i],
303 after: " previously acquired by the same thread here:\n");
304 PrintStack(ent: rep->stacks[2*i+1]);
305 } else {
306 PrintStack(ent: rep->stacks[i]);
307 if (i == 0)
308 Printf(
309 format: " HINT: use TSAN_OPTIONS=second_deadlock_stack=1 "
310 "to get more informative warning message\n\n");
311 }
312 }
313 } else {
314 for (uptr i = 0; i < rep->stacks.Size(); i++) {
315 if (i)
316 Printf(format: " and:\n");
317 PrintStack(ent: rep->stacks[i]);
318 }
319 }
320
321 for (uptr i = 0; i < rep->mops.Size(); i++)
322 PrintMop(mop: rep->mops[i], first: i == 0);
323
324 if (rep->sleep)
325 PrintSleep(s: rep->sleep);
326
327 for (uptr i = 0; i < rep->locs.Size(); i++)
328 PrintLocation(loc: rep->locs[i]);
329
330 if (rep->typ != ReportTypeDeadlock) {
331 for (uptr i = 0; i < rep->mutexes.Size(); i++)
332 PrintMutex(rm: rep->mutexes[i]);
333 }
334
335 for (uptr i = 0; i < rep->threads.Size(); i++)
336 PrintThread(rt: rep->threads[i]);
337
338 if (rep->typ == ReportTypeThreadLeak && rep->count > 1)
339 Printf(format: " And %d more similar thread leaks.\n\n", rep->count - 1);
340
341 if (ReportStack *stack = ChooseSummaryStack(rep)) {
342 if (const SymbolizedStack *frame = SkipTsanInternalFrames(frames: stack->frames))
343 ReportErrorSummary(error_type: rep_typ_str, info: frame->info);
344 }
345
346 if (common_flags()->print_module_map == 2)
347 DumpProcessMap();
348
349 Printf(format: "==================\n");
350}
351
352#else // #if !SANITIZER_GO
353
354const Tid kMainGoroutineId = 1;
355
356void PrintStack(const ReportStack *ent) {
357 if (ent == 0 || ent->frames == 0) {
358 Printf(" [failed to restore the stack]\n");
359 return;
360 }
361 SymbolizedStack *frame = ent->frames;
362 for (int i = 0; frame; frame = frame->next, i++) {
363 const AddressInfo &info = frame->info;
364 Printf(" %s()\n %s:%d +0x%zx\n", info.function,
365 StripPathPrefix(info.file, common_flags()->strip_path_prefix),
366 info.line, info.module_offset);
367 }
368}
369
370static void PrintMop(const ReportMop *mop, bool first) {
371 Printf("\n");
372 Printf("%s at %p by ",
373 (first ? (mop->write ? "Write" : "Read")
374 : (mop->write ? "Previous write" : "Previous read")),
375 reinterpret_cast<void *>(mop->addr));
376 if (mop->tid == kMainGoroutineId)
377 Printf("main goroutine:\n");
378 else
379 Printf("goroutine %d:\n", mop->tid);
380 PrintStack(mop->stack);
381}
382
383static void PrintLocation(const ReportLocation *loc) {
384 switch (loc->type) {
385 case ReportLocationHeap: {
386 Printf("\n");
387 Printf("Heap block of size %zu at %p allocated by ", loc->heap_chunk_size,
388 reinterpret_cast<void *>(loc->heap_chunk_start));
389 if (loc->tid == kMainGoroutineId)
390 Printf("main goroutine:\n");
391 else
392 Printf("goroutine %d:\n", loc->tid);
393 PrintStack(loc->stack);
394 break;
395 }
396 case ReportLocationGlobal: {
397 Printf("\n");
398 Printf("Global var %s of size %zu at %p declared at %s:%zu\n",
399 loc->global.name, loc->global.size,
400 reinterpret_cast<void *>(loc->global.start), loc->global.file,
401 loc->global.line);
402 break;
403 }
404 default:
405 break;
406 }
407}
408
409static void PrintThread(const ReportThread *rt) {
410 if (rt->id == kMainGoroutineId)
411 return;
412 Printf("\n");
413 Printf("Goroutine %d (%s) created at:\n",
414 rt->id, rt->running ? "running" : "finished");
415 PrintStack(rt->stack);
416}
417
418void PrintReport(const ReportDesc *rep) {
419 Printf("==================\n");
420 if (rep->typ == ReportTypeRace) {
421 Printf("WARNING: DATA RACE");
422 for (uptr i = 0; i < rep->mops.Size(); i++)
423 PrintMop(rep->mops[i], i == 0);
424 for (uptr i = 0; i < rep->locs.Size(); i++)
425 PrintLocation(rep->locs[i]);
426 for (uptr i = 0; i < rep->threads.Size(); i++)
427 PrintThread(rep->threads[i]);
428 } else if (rep->typ == ReportTypeDeadlock) {
429 Printf("WARNING: DEADLOCK\n");
430 for (uptr i = 0; i < rep->mutexes.Size(); i++) {
431 Printf("Goroutine %d lock mutex %u while holding mutex %u:\n", 999,
432 rep->mutexes[i]->id,
433 rep->mutexes[(i + 1) % rep->mutexes.Size()]->id);
434 PrintStack(rep->stacks[2*i]);
435 Printf("\n");
436 Printf("Mutex %u was previously locked here:\n",
437 rep->mutexes[(i + 1) % rep->mutexes.Size()]->id);
438 PrintStack(rep->stacks[2*i + 1]);
439 Printf("\n");
440 }
441 }
442 Printf("==================\n");
443}
444
445#endif
446
447} // namespace __tsan
448