Coverage Report

Created: 2025-08-29 06:38

/src/libtorrent/src/assert.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
3
Copyright (c) 2007-2020, Arvid Norberg
4
Copyright (c) 2008, Andrew Resch
5
Copyright (c) 2016-2017, Alden Torres
6
Copyright (c) 2017, Steven Siloti
7
Copyright (c) 2020, Tiger Wang
8
All rights reserved.
9
10
Redistribution and use in source and binary forms, with or without
11
modification, are permitted provided that the following conditions
12
are met:
13
14
    * Redistributions of source code must retain the above copyright
15
      notice, this list of conditions and the following disclaimer.
16
    * Redistributions in binary form must reproduce the above copyright
17
      notice, this list of conditions and the following disclaimer in
18
      the documentation and/or other materials provided with the distribution.
19
    * Neither the name of the author nor the names of its
20
      contributors may be used to endorse or promote products derived
21
      from this software without specific prior written permission.
22
23
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
POSSIBILITY OF SUCH DAMAGE.
34
35
*/
36
37
#include "libtorrent/config.hpp"
38
#include "libtorrent/assert.hpp"
39
40
#include "libtorrent/aux_/disable_warnings_push.hpp"
41
42
#ifdef TORRENT_PRODUCTION_ASSERTS
43
#include <atomic>
44
#endif
45
46
#if TORRENT_USE_ASSERTS \
47
  || defined TORRENT_ASIO_DEBUGGING \
48
  || defined TORRENT_PROFILE_CALLS \
49
  || defined TORRENT_DEBUG_BUFFERS
50
51
#ifdef __APPLE__
52
#include <AvailabilityMacros.h>
53
#endif
54
55
#include <string>
56
#include <cstring>
57
#include <cstdlib>
58
#include <cstdarg>
59
#include <cstdio> // for snprintf
60
#include <cinttypes> // for PRId64 et.al.
61
#include <array>
62
63
#include "libtorrent/aux_/disable_warnings_pop.hpp"
64
65
// uClibc++ doesn't have cxxabi.h
66
#if defined __GNUC__ && __GNUC__ >= 3 \
67
  && !defined __UCLIBCXX_MAJOR__
68
69
#include <cxxabi.h>
70
71
namespace libtorrent {
72
std::string demangle(char const* name)
73
0
{
74
// in case this string comes
75
  // this is needed on linux
76
0
  char const* start = std::strchr(name, '(');
77
0
  if (start != nullptr)
78
0
  {
79
0
    ++start;
80
0
  }
81
0
  else
82
0
  {
83
    // this is needed on macos x
84
0
    start = strstr(name, "0x");
85
0
    if (start != nullptr)
86
0
    {
87
0
      start = std::strchr(start, ' ');
88
0
      if (start != nullptr) ++start;
89
0
      else start = name;
90
0
    }
91
0
    else start = name;
92
0
  }
93
94
0
  char const* end = std::strchr(start, '+');
95
0
  if (end) while (*(end-1) == ' ') --end;
96
97
0
  std::string in;
98
0
  if (end == nullptr) in.assign(start);
99
0
  else in.assign(start, end);
100
101
0
  size_t len;
102
0
  int status;
103
0
  char* unmangled = ::abi::__cxa_demangle(in.c_str(), nullptr, &len, &status);
104
0
  if (unmangled == nullptr) return in;
105
0
  std::string ret(unmangled);
106
0
  ::free(unmangled);
107
0
  return ret;
108
0
}
109
}
110
#elif defined _WIN32 && !defined TORRENT_WINRT
111
112
#include "libtorrent/aux_/windows.hpp"
113
#include <DbgHelp.h>
114
115
namespace libtorrent {
116
std::string demangle(char const* name)
117
{
118
  char demangled_name[256];
119
  if (UnDecorateSymbolName(name, demangled_name, sizeof(demangled_name), UNDNAME_NO_THROW_SIGNATURES) == 0)
120
    demangled_name[0] = 0;
121
  return demangled_name;
122
}
123
}
124
125
#else
126
namespace libtorrent {
127
std::string demangle(char const* name) { return name; }
128
}
129
#endif
130
131
#include <cstdlib>
132
#include <cstdio>
133
#include <csignal>
134
#include "libtorrent/version.hpp"
135
136
#if TORRENT_USE_EXECINFO
137
#include <execinfo.h>
138
139
namespace libtorrent {
140
141
TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth, void*)
142
0
{
143
0
  void* stack[50];
144
0
  int size = ::backtrace(stack, 50);
145
0
  char** symbols = ::backtrace_symbols(stack, size);
146
147
0
  for (int i = 1; i < size && len > 0; ++i)
148
0
  {
149
0
    int ret = std::snprintf(out, std::size_t(len), "%d: %s\n", i, demangle(symbols[i]).c_str());
150
0
    out += ret;
151
0
    len -= ret;
152
0
    if (i - 1 == max_depth && max_depth > 0) break;
153
0
  }
154
155
0
  ::free(symbols);
156
0
}
157
}
158
159
#elif defined _WIN32 && !defined TORRENT_WINRT
160
161
#include "libtorrent/aux_/windows.hpp"
162
#include "libtorrent/utf8.hpp"
163
#include <mutex>
164
165
#include <WinBase.h>
166
#include <DbgHelp.h>
167
168
namespace libtorrent {
169
170
TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth
171
  , void* ctx)
172
{
173
  // all calls to DbgHlp.dll are thread-unsafe. i.e. they all need to be
174
  // synchronized and not called concurrently. This mutex serializes access
175
  static std::mutex dbghlp_mutex;
176
  std::lock_guard<std::mutex> l(dbghlp_mutex);
177
178
  CONTEXT context_record;
179
  if (ctx)
180
  {
181
    context_record = *static_cast<CONTEXT*>(ctx);
182
  }
183
  else
184
  {
185
    // use the current thread's context
186
    RtlCaptureContext(&context_record);
187
  }
188
189
  int size = 0;
190
  std::array<void*, 50> stack;
191
192
  STACKFRAME64 stack_frame = {};
193
#if defined(_M_IX86)
194
  int const machine_type = IMAGE_FILE_MACHINE_I386;
195
  stack_frame.AddrPC.Offset = context_record.Eip;
196
  stack_frame.AddrFrame.Offset = context_record.Ebp;
197
  stack_frame.AddrStack.Offset = context_record.Esp;
198
#elif defined(_M_X64)
199
  int const machine_type = IMAGE_FILE_MACHINE_AMD64;
200
  stack_frame.AddrPC.Offset = context_record.Rip;
201
  stack_frame.AddrFrame.Offset = context_record.Rbp;
202
  stack_frame.AddrStack.Offset = context_record.Rsp;
203
#elif defined(_M_ARM64)
204
  int const machine_type = IMAGE_FILE_MACHINE_ARM64;
205
  stack_frame.AddrPC.Offset = context_record.Pc;
206
  stack_frame.AddrFrame.Offset = context_record.Fp;
207
  stack_frame.AddrStack.Offset = context_record.Sp;
208
#endif
209
  stack_frame.AddrPC.Mode = AddrModeFlat;
210
  stack_frame.AddrFrame.Mode = AddrModeFlat;
211
  stack_frame.AddrStack.Mode = AddrModeFlat;
212
  while (StackWalk64(machine_type,
213
    GetCurrentProcess(),
214
    GetCurrentThread(),
215
    &stack_frame,
216
    &context_record,
217
    nullptr,
218
    &SymFunctionTableAccess64,
219
    &SymGetModuleBase64,
220
    nullptr) && size < int(stack.size()))
221
  {
222
    stack[size++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset);
223
  }
224
225
  struct symbol_bundle : SYMBOL_INFO
226
  {
227
    wchar_t name[MAX_SYM_NAME];
228
  };
229
230
  HANDLE p = GetCurrentProcess();
231
  static bool sym_initialized = false;
232
  if (!sym_initialized)
233
  {
234
    sym_initialized = true;
235
    SymInitialize(p, nullptr, true);
236
  }
237
  SymRefreshModuleList(p);
238
  for (int i = 0; i < size && len > 0; ++i)
239
  {
240
    DWORD_PTR frame_ptr = reinterpret_cast<DWORD_PTR>(stack[i]);
241
242
    DWORD64 displacement = 0;
243
    symbol_bundle symbol;
244
    symbol.MaxNameLen = MAX_SYM_NAME;
245
    symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
246
    BOOL const has_symbol = SymFromAddr(p, frame_ptr, &displacement, &symbol);
247
248
    DWORD line_displacement = 0;
249
    IMAGEHLP_LINE64 line = {};
250
    line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
251
    BOOL const has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame_ptr,
252
      &line_displacement, &line);
253
254
    int ret = std::snprintf(out, len, "%2d: %p", i, stack[i]);
255
    out += ret; len -= ret; if (len <= 0) break;
256
257
    if (has_symbol)
258
    {
259
      ret = std::snprintf(out, len, " %s +%-4" PRId64
260
        , demangle(symbol.Name).c_str(), displacement);
261
      out += ret; len -= ret; if (len <= 0) break;
262
    }
263
264
    if (has_line)
265
    {
266
      ret = std::snprintf(out, len, " %s:%d"
267
        , line.FileName, int(line.LineNumber));
268
      out += ret; len -= ret; if (len <= 0) break;
269
    }
270
271
272
    ret = std::snprintf(out, len, "\n");
273
    out += ret;
274
    len -= ret;
275
276
    if (i == max_depth && max_depth > 0) break;
277
  }
278
}
279
}
280
281
#else
282
283
namespace libtorrent {
284
285
TORRENT_EXPORT void print_backtrace(char* out, int len, int /*max_depth*/, void* /* ctx */)
286
{
287
  out[0] = 0;
288
  std::strncat(out, "<not supported>", std::size_t(len));
289
}
290
291
}
292
293
#endif
294
295
#endif
296
297
#if (TORRENT_USE_ASSERTS || defined TORRENT_ASIO_DEBUGGING) && \
298
  defined TORRENT_PRODUCTION_ASSERTS
299
char const* libtorrent_assert_log = "asserts.log";
300
namespace {
301
// the number of asserts we've printed to the log
302
std::atomic<int> assert_counter(0);
303
}
304
#endif
305
306
namespace libtorrent {
307
308
#if TORRENT_USE_ASSERTS || defined TORRENT_ASIO_DEBUGGING
309
310
TORRENT_FORMAT(1,2)
311
TORRENT_EXPORT void assert_print(char const* fmt, ...)
312
0
{
313
#ifdef TORRENT_PRODUCTION_ASSERTS
314
  if (assert_counter > 500) return;
315
316
  FILE* out = fopen(libtorrent_assert_log, "a+");
317
  if (out == nullptr) out = stderr;
318
#else
319
0
  FILE* out = stderr;
320
0
#endif
321
0
  va_list va;
322
0
  va_start(va, fmt);
323
0
  std::vfprintf(out, fmt, va);
324
0
  va_end(va);
325
326
#ifdef TORRENT_PRODUCTION_ASSERTS
327
  if (out != stderr) fclose(out);
328
#endif
329
0
}
330
331
// we deliberately don't want asserts to be marked as no-return, since that
332
// would trigger warnings in debug builds of any code coming after the assert
333
#ifdef __clang__
334
#pragma clang diagnostic push
335
#pragma clang diagnostic ignored "-Wmissing-noreturn"
336
#endif
337
338
TORRENT_EXPORT void assert_fail(char const* expr, int line
339
  , char const* file, char const* function, char const* value, int kind)
340
0
{
341
#ifdef TORRENT_PRODUCTION_ASSERTS
342
  // no need to flood the assert log with infinite number of asserts
343
  if (assert_counter.fetch_add(1) + 1 > 500) return;
344
#endif
345
346
0
  char stack[8192];
347
0
  stack[0] = '\0';
348
0
  print_backtrace(stack, sizeof(stack), 0);
349
350
0
  char const* message = "assertion failed. Please file a bugreport at "
351
0
    "https://github.com/arvidn/libtorrent/issues\n"
352
0
    "Please include the following information:\n\n"
353
0
    "version: " LIBTORRENT_VERSION "-" LIBTORRENT_REVISION "\n";
354
355
0
  switch (kind)
356
0
  {
357
0
    case 1:
358
0
      message = "A precondition of a libtorrent function has been violated.\n"
359
0
        "This indicates a bug in the client application using libtorrent\n";
360
0
  }
361
362
0
  assert_print("%s\n"
363
#ifdef TORRENT_PRODUCTION_ASSERTS
364
    "#: %d\n"
365
#endif
366
0
    "file: '%s'\n"
367
0
    "line: %d\n"
368
0
    "function: %s\n"
369
0
    "expression: %s\n"
370
0
    "%s%s\n"
371
0
    "stack:\n"
372
0
    "%s\n"
373
0
    , message
374
#ifdef TORRENT_PRODUCTION_ASSERTS
375
    , assert_counter.load()
376
#endif
377
0
    , file, line, function, expr
378
0
    , value ? value : "", value ? "\n" : ""
379
0
    , stack);
380
381
  // if production asserts are defined, don't abort, just print the error
382
0
#ifndef TORRENT_PRODUCTION_ASSERTS
383
#ifdef TORRENT_WINDOWS
384
  // SIGABRT doesn't trigger a break with msvc
385
  __debugbreak();
386
#else
387
  // send SIGABRT to the current process
388
  // to break into the debugger
389
0
  std::raise(SIGABRT);
390
0
#endif
391
0
  std::abort();
392
0
#endif
393
0
}
394
395
#ifdef __clang__
396
#pragma clang diagnostic pop
397
#endif
398
399
#elif !TORRENT_USE_ASSERTS
400
401
// these are just here to make it possible for a client that built with debug
402
// enable to be able to link against a release build (just possible, not
403
// necessarily supported)
404
TORRENT_FORMAT(1,2)
405
TORRENT_EXPORT void assert_print(char const*, ...) {}
406
TORRENT_EXPORT void assert_fail(char const*, int, char const*
407
  , char const*, char const*, int) {}
408
409
#endif
410
411
} // libtorrent namespace
412