exception.c++ 14.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "exception.h"
Kenton Varda's avatar
Kenton Varda committed
25
#include "string.h"
Kenton Varda's avatar
Kenton Varda committed
26
#include "debug.h"
27
#include <unistd.h>
28
#include <stdlib.h>
29
#include <exception>
30

31 32 33 34
#ifndef __CYGWIN__
#include <execinfo.h>
#endif

35 36
#if defined(__linux__) && !defined(NDEBUG)
#include <stdio.h>
37
#include <pthread.h>
38 39
#endif

40
namespace kj {
41

42 43 44 45
namespace {

String getStackSymbols(ArrayPtr<void* const> trace) {
#if defined(__linux__) && !defined(NDEBUG)
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
  // We want to generate a human-readable stack trace.

  // TODO(someday):  It would be really great if we could avoid farming out to addr2line and do
  //   this all in-process, but that may involve onerous requirements like large library
  //   dependencies or using -rdynamic.

  // The environment manipulation is not thread-safe, so lock a mutex.  This could still be
  // problematic if another thread is manipulating the environment in unrelated code, but there's
  // not much we can do about that.  This is debug-only anyway and only an issue when LD_PRELOAD
  // is in use.
  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  pthread_mutex_lock(&mutex);

  // Don't heapcheck / intercept syscalls for addr2line.
  const char* preload = getenv("LD_PRELOAD");
  String oldPreload;
  if (preload != nullptr) {
    oldPreload = heapString(preload);
    unsetenv("LD_PRELOAD");
  }

67 68 69 70
  // Get executable name from /proc/self/exe, then pass it and the stack trace to addr2line to
  // get file/line pairs.
  char exe[512];
  ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe));
Kenton Varda's avatar
Kenton Varda committed
71
  if (n < 0 || n >= static_cast<ssize_t>(sizeof(exe))) {
72 73 74 75
    return nullptr;
  }
  exe[n] = '\0';

Kenton Varda's avatar
Kenton Varda committed
76
  String lines[8];
77 78 79 80 81 82 83 84 85 86 87

  FILE* p = popen(str("addr2line -e ", exe, ' ', strArray(trace, " ")).cStr(), "r");
  if (p == nullptr) {
    return nullptr;
  }

  char line[512];
  size_t i = 0;
  while (i < KJ_ARRAY_SIZE(lines) && fgets(line, sizeof(line), p) != nullptr) {
    // Don't include exception-handling infrastructure in stack trace.
    if (i == 0 &&
Kenton Varda's avatar
Kenton Varda committed
88 89
        (strstr(line, "kj/common.c++") != nullptr ||
         strstr(line, "kj/exception.") != nullptr ||
90 91 92 93 94 95
         strstr(line, "kj/debug.") != nullptr)) {
      continue;
    }

    size_t len = strlen(line);
    if (len > 0 && line[len-1] == '\n') line[len-1] = '\0';
Kenton Varda's avatar
Kenton Varda committed
96
    lines[i++] = str("\n", line, ": called here");
97 98 99 100 101 102 103
  }

  // Skip remaining input.
  while (fgets(line, sizeof(line), p) != nullptr) {}

  pclose(p);

104 105 106 107 108 109
  if (oldPreload != nullptr) {
    setenv("LD_PRELOAD", oldPreload.cStr(), true);
  }

  pthread_mutex_unlock(&mutex);

110 111 112 113 114 115 116 117
  return strArray(arrayPtr(lines, i), "");
#else
  return nullptr;
#endif
}

}  // namespace

Kenton Varda's avatar
Kenton Varda committed
118
ArrayPtr<const char> KJ_STRINGIFY(Exception::Nature nature) {
119
  static const char* NATURE_STRINGS[] = {
120
    "requirement not met",
121
    "bug in code",
122
    "error from OS",
123 124 125 126 127 128 129 130
    "network failure",
    "error"
  };

  const char* s = NATURE_STRINGS[static_cast<uint>(nature)];
  return arrayPtr(s, strlen(s));
}

Kenton Varda's avatar
Kenton Varda committed
131
ArrayPtr<const char> KJ_STRINGIFY(Exception::Durability durability) {
132 133 134 135 136 137 138 139 140
  static const char* DURABILITY_STRINGS[] = {
    "temporary",
    "permanent"
  };

  const char* s = DURABILITY_STRINGS[static_cast<uint>(durability)];
  return arrayPtr(s, strlen(s));
}

141 142 143 144 145 146 147
String KJ_STRINGIFY(const Exception& e) {
  uint contextDepth = 0;

  Maybe<const Exception::Context&> contextPtr = e.getContext();
  for (;;) {
    KJ_IF_MAYBE(c, contextPtr) {
      ++contextDepth;
148
      contextPtr = c->next;
149 150 151 152 153 154 155 156 157 158 159 160 161
    } else {
      break;
    }
  }

  Array<String> contextText = heapArray<String>(contextDepth);

  contextDepth = 0;
  contextPtr = e.getContext();
  for (;;) {
    KJ_IF_MAYBE(c, contextPtr) {
      contextText[contextDepth++] =
          str(c->file, ":", c->line, ": context: ", c->description, "\n");
162
      contextPtr = c->next;
163 164 165 166 167 168 169 170 171
    } else {
      break;
    }
  }

  return str(strArray(contextText, ""),
             e.getFile(), ":", e.getLine(), ": ", e.getNature(),
             e.getDurability() == Exception::Durability::TEMPORARY ? " (temporary)" : "",
             e.getDescription() == nullptr ? "" : ": ", e.getDescription(),
172 173
             e.getStackTrace().size() > 0 ? "\nstack: " : "", strArray(e.getStackTrace(), " "),
             getStackSymbols(e.getStackTrace()));
174 175
}

176
Exception::Exception(Nature nature, Durability durability, const char* file, int line,
Kenton Varda's avatar
Kenton Varda committed
177
                     String description) noexcept
178
    : file(file), line(line), nature(nature), durability(durability),
Kenton Varda's avatar
Kenton Varda committed
179
      description(mv(description)) {
180 181 182
#ifdef __CYGWIN__
  traceCount = 0;
#else
183
  traceCount = backtrace(trace, 16);
184
#endif
185 186
}

187 188 189 190 191 192 193 194 195 196 197
Exception::Exception(Nature nature, Durability durability, String file, int line,
                     String description) noexcept
    : ownFile(kj::mv(file)), file(ownFile.cStr()), line(line), nature(nature),
      durability(durability), description(mv(description)) {
#ifdef __CYGWIN__
  traceCount = 0;
#else
  traceCount = backtrace(trace, 16);
#endif
}

198 199
Exception::Exception(const Exception& other) noexcept
    : file(other.file), line(other.line), nature(other.nature), durability(other.durability),
200 201 202 203 204 205
      description(heapString(other.description)), traceCount(other.traceCount) {
  if (file == other.ownFile.cStr()) {
    ownFile = heapString(other.ownFile);
    file = ownFile.cStr();
  }

206
  memcpy(trace, other.trace, sizeof(trace[0]) * traceCount);
207

208
  KJ_IF_MAYBE(c, other.context) {
209
    context = heap(*c);
210
  }
211
}
212 213 214

Exception::~Exception() noexcept {}

215 216
Exception::Context::Context(const Context& other) noexcept
    : file(other.file), line(other.line), description(str(other.description)) {
217
  KJ_IF_MAYBE(n, other.next) {
218
    next = heap(*n);
219 220 221
  }
}

Kenton Varda's avatar
Kenton Varda committed
222
void Exception::wrapContext(const char* file, int line, String&& description) {
Kenton Varda's avatar
Kenton Varda committed
223
  context = heap<Context>(file, line, mv(description), mv(context));
224 225
}

226 227 228 229 230
class ExceptionImpl: public Exception, public std::exception {
public:
  inline ExceptionImpl(Exception&& other): Exception(mv(other)) {}
  ExceptionImpl(const ExceptionImpl& other): Exception(other) {
    // No need to copy whatBuffer since it's just to hold the return value of what().
231 232
  }

233
  const char* what() const noexcept override;
234

235 236 237
private:
  mutable String whatBuffer;
};
238

239 240
const char* ExceptionImpl::what() const noexcept {
  whatBuffer = str(*this);
241
  return whatBuffer.begin();
242 243 244 245 246 247 248 249 250 251
}

// =======================================================================================

namespace {

#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
#define thread_local __thread
#endif

252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
thread_local ExceptionCallback* threadLocalCallback = nullptr;

class RepeatChar {
  // A pseudo-sequence of characters that is actually just one character repeated.
  //
  // TODO(cleanup):  Put this somewhere reusable.  Maybe templatize it too.

public:
  inline RepeatChar(char c, uint size): c(c), size_(size) {}

  class Iterator {
  public:
    Iterator() = default;
    inline Iterator(char c, uint index): c(c), index(index) {}

    inline Iterator& operator++() { ++index; return *this; }
    inline Iterator operator++(int) { ++index; return Iterator(c, index - 1); }

    inline char operator*() const { return c; }

    inline bool operator==(const Iterator& other) const { return index == other.index; }
    inline bool operator!=(const Iterator& other) const { return index != other.index; }

  private:
    char c;
    uint index;
  };

  inline uint size() const { return size_; }
  inline Iterator begin() const { return Iterator(c, 0); }
  inline Iterator end() const { return Iterator(c, size_); }

private:
  char c;
  uint size_;
};
inline RepeatChar KJ_STRINGIFY(RepeatChar value) { return value; }
289 290 291

}  // namespace

292 293 294 295 296 297 298 299 300 301 302
ExceptionCallback::ExceptionCallback(): next(getExceptionCallback()) {
  char stackVar;
  ptrdiff_t offset = reinterpret_cast<char*>(this) - &stackVar;
  KJ_ASSERT(offset < 4096 && offset > -4096,
            "ExceptionCallback must be allocated on the stack.");

  threadLocalCallback = this;
}

ExceptionCallback::ExceptionCallback(ExceptionCallback& next): next(next) {}

303
ExceptionCallback::~ExceptionCallback() noexcept(false) {
304 305
  if (&next != this) {
    threadLocalCallback = &next;
306 307 308 309
  }
}

void ExceptionCallback::onRecoverableException(Exception&& exception) {
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
  next.onRecoverableException(mv(exception));
}

void ExceptionCallback::onFatalException(Exception&& exception) {
  next.onFatalException(mv(exception));
}

void ExceptionCallback::logMessage(const char* file, int line, int contextDepth, String&& text) {
  next.logMessage(file, line, contextDepth, mv(text));
}

class ExceptionCallback::RootExceptionCallback: public ExceptionCallback {
public:
  RootExceptionCallback(): ExceptionCallback(*this) {}

  void onRecoverableException(Exception&& exception) override {
Kenton Varda's avatar
Kenton Varda committed
326
#if KJ_NO_EXCEPTIONS
327
    logException(mv(exception));
328
#else
329
    throw ExceptionImpl(mv(exception));
330
#endif
331
  }
332

333
  void onFatalException(Exception&& exception) override {
Kenton Varda's avatar
Kenton Varda committed
334
#if KJ_NO_EXCEPTIONS
335
    logException(mv(exception));
336
#else
337
    throw ExceptionImpl(mv(exception));
338
#endif
339
  }
340

341
  void logMessage(const char* file, int line, int contextDepth, String&& text) override {
342
    text = str(RepeatChar('_', contextDepth), file, ":", line, ": ", mv(text));
343

344
    StringPtr textPtr = text;
345

346 347 348 349 350 351 352 353 354
    while (text != nullptr) {
      ssize_t n = write(STDERR_FILENO, textPtr.begin(), textPtr.size());
      if (n <= 0) {
        // stderr is broken.  Give up.
        return;
      }
      textPtr = textPtr.slice(n);
    }
  }
355

356 357 358 359 360 361 362 363 364 365
private:
  void logException(Exception&& e) {
    // We intentionally go back to the top exception callback on the stack because we don't want to
    // bypass whatever log processing is in effect.
    //
    // We intentionally don't log the context since it should get re-added by the exception callback
    // anyway.
    getExceptionCallback().logMessage(e.getFile(), e.getLine(), 0, str(
        e.getNature(), e.getDurability() == Exception::Durability::TEMPORARY ? " (temporary)" : "",
        e.getDescription() == nullptr ? "" : ": ", e.getDescription(),
366
        "\nstack: ", strArray(e.getStackTrace(), " "), "\n"));
367 368
  }
};
369

370
ExceptionCallback& getExceptionCallback() {
371 372 373
  static ExceptionCallback::RootExceptionCallback defaultCallback;
  ExceptionCallback* scoped = threadLocalCallback;
  return scoped != nullptr ? *scoped : defaultCallback;
374 375
}

376 377
// =======================================================================================

378
namespace _ {  // private
379 380 381

#if __GNUC__

382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
// Horrible -- but working -- hack:  We can dig into __cxa_get_globals() in order to extract the
// count of uncaught exceptions.  This function is part of the C++ ABI implementation used on Linux,
// OSX, and probably other platforms that use GCC.  Unfortunately, __cxa_get_globals() is only
// actually defined in cxxabi.h on some platforms (e.g. Linux, but not OSX), and even where it is
// defined, it returns an incomplete type.  Here we use the same hack used by Evgeny Panasyuk:
//   https://github.com/panaseleus/stack_unwinding/blob/master/boost/exception/uncaught_exception_count.hpp
//
// Notice that a similar hack is possible on MSVC -- if its C++11 support ever gets to the point of
// supporting KJ in the first place.
//
// It appears likely that a future version of the C++ standard may include an
// uncaught_exception_count() function in the standard library, or an equivalent language feature.
// Some discussion:
//   https://groups.google.com/a/isocpp.org/d/msg/std-proposals/HglEslyZFYs/kKdu5jJw5AgJ

struct FakeEhGlobals {
  // Fake

  void* caughtExceptions;
401 402 403
  uint uncaughtExceptions;
};

404 405 406 407 408 409 410
// Because of the 'extern "C"', the symbol name is not mangled and thus the namespace is effectively
// ignored for linking.  Thus it doesn't matter that we are declaring __cxa_get_globals() in a
// different namespace from the ABI's definition.
extern "C" {
FakeEhGlobals* __cxa_get_globals();
}

411 412 413
uint uncaughtExceptionCount() {
  // TODO(perf):  Use __cxa_get_globals_fast()?  Requires that __cxa_get_globals() has been called
  //   from somewhere.
414
  return __cxa_get_globals()->uncaughtExceptions;
415 416 417 418 419 420
}

#else
#error "This needs to be ported to your compiler / C++ ABI."
#endif

421
}  // namespace _ (private)
422

423
UnwindDetector::UnwindDetector(): uncaughtCount(_::uncaughtExceptionCount()) {}
424 425

bool UnwindDetector::isUnwinding() const {
426
  return _::uncaughtExceptionCount() > uncaughtCount;
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
}

void UnwindDetector::catchExceptionsAsSecondaryFaults(_::Runnable& runnable) const {
  // TODO(someday):  Attach the secondary exception to whatever primary exception is causing
  //   the unwind.  For now we just drop it on the floor as this is probably fine most of the
  //   time.
  runCatchingExceptions(runnable);
}

namespace _ {  // private

class RecoverableExceptionCatcher: public ExceptionCallback {
  // Catches a recoverable exception without using try/catch.  Used when compiled with
  // -fno-exceptions.

public:
443
  virtual ~RecoverableExceptionCatcher() noexcept(false) {}
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478

  void onRecoverableException(Exception&& exception) override {
    if (caught == nullptr) {
      caught = mv(exception);
    } else {
      // TODO(someday):  Consider it a secondary fault?
    }
  }

  Maybe<Exception> caught;
};

Maybe<Exception> runCatchingExceptions(Runnable& runnable) {
#if KJ_NO_EXCEPTIONS
  RecoverableExceptionCatcher catcher;
  runnable.run();
  return mv(catcher.caught);
#else
  try {
    runnable.run();
    return nullptr;
  } catch (Exception& e) {
    return kj::mv(e);
  } catch (std::exception& e) {
    return Exception(Exception::Nature::OTHER, Exception::Durability::PERMANENT,
                     "(unknown)", -1, str("std::exception: ", e.what()));
  } catch (...) {
    return Exception(Exception::Nature::OTHER, Exception::Durability::PERMANENT,
                     "(unknown)", -1, str("Unknown non-KJ exception."));
  }
#endif
}

}  // namespace _ (private)

479
}  // namespace kj