debug.c++ 11.9 KB
Newer Older
Kenton Varda's avatar
Kenton Varda committed
1 2
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
3
//
Kenton Varda's avatar
Kenton Varda committed
4 5 6 7 8 9
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
10
//
Kenton Varda's avatar
Kenton Varda committed
11 12
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
13
//
Kenton Varda's avatar
Kenton Varda committed
14 15 16 17 18 19 20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
21

Kenton Varda's avatar
Kenton Varda committed
22
#include "debug.h"
23 24
#include <stdlib.h>
#include <ctype.h>
25 26
#include <string.h>
#include <errno.h>
27

28 29
#if _WIN32
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
30 31 32 33 34 35 36
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN 1
#define NOSERVICE 1
#define NOMCX 1
#define NOIME 1
#include <windows.h>
#include "windows-sanity.h"
37 38
#endif

39
namespace kj {
Kenton Varda's avatar
Kenton Varda committed
40
namespace _ {  // private
41

42
LogSeverity Debug::minSeverity = LogSeverity::WARNING;
43

44 45
namespace {

46 47
Exception::Type typeOfErrno(int error) {
  switch (error) {
48
#ifdef EDQUOT
49
    case EDQUOT:
50 51
#endif
#ifdef EMFILE
52
    case EMFILE:
53 54
#endif
#ifdef ENFILE
55
    case ENFILE:
56 57
#endif
#ifdef ENOBUFS
58
    case ENOBUFS:
59 60
#endif
#ifdef ENOLCK
61
    case ENOLCK:
62 63
#endif
#ifdef ENOMEM
64
    case ENOMEM:
65 66
#endif
#ifdef ENOSPC
67
    case ENOSPC:
68 69
#endif
#ifdef ETIMEDOUT
70
    case ETIMEDOUT:
71 72
#endif
#ifdef EUSERS
73
    case EUSERS:
74
#endif
75 76
      return Exception::Type::OVERLOADED;

77
#ifdef ENOTCONN
78 79
    case ENOTCONN:
#endif
80
#ifdef ECONNABORTED
81
    case ECONNABORTED:
82 83
#endif
#ifdef ECONNREFUSED
84
    case ECONNREFUSED:
85 86
#endif
#ifdef ECONNRESET
87
    case ECONNRESET:
88 89
#endif
#ifdef EHOSTDOWN
90
    case EHOSTDOWN:
91 92
#endif
#ifdef EHOSTUNREACH
93
    case EHOSTUNREACH:
94 95
#endif
#ifdef ENETDOWN
96
    case ENETDOWN:
97 98
#endif
#ifdef ENETRESET
99
    case ENETRESET:
100 101
#endif
#ifdef ENETUNREACH
102
    case ENETUNREACH:
103 104
#endif
#ifdef ENONET
105
    case ENONET:
106 107
#endif
#ifdef EPIPE
108
    case EPIPE:
109
#endif
110 111
      return Exception::Type::DISCONNECTED;

112
#ifdef ENOSYS
113
    case ENOSYS:
114 115
#endif
#ifdef ENOTSUP
116 117
    case ENOTSUP:
#endif
118
#if defined(EOPNOTSUPP) && EOPNOTSUPP != ENOTSUP
119
    case EOPNOTSUPP:
120 121 122 123 124 125 126
#endif
#ifdef ENOPROTOOPT
    case ENOPROTOOPT:
#endif
#ifdef ENOTSOCK
    // This is really saying "syscall not implemented for non-sockets".
    case ENOTSOCK:
127 128 129 130 131 132 133 134
#endif
      return Exception::Type::UNIMPLEMENTED;

    default:
      return Exception::Type::FAILED;
  }
}

135 136 137 138
#if _WIN32

Exception::Type typeOfWin32Error(DWORD error) {
  switch (error) {
139
    // TODO(soon): This needs more work.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166

    case WSAETIMEDOUT:
      return Exception::Type::OVERLOADED;

    case WSAENOTCONN:
    case WSAECONNABORTED:
    case WSAECONNREFUSED:
    case WSAECONNRESET:
    case WSAEHOSTDOWN:
    case WSAEHOSTUNREACH:
    case WSAENETDOWN:
    case WSAENETRESET:
    case WSAENETUNREACH:
      return Exception::Type::DISCONNECTED;

    case WSAEOPNOTSUPP:
    case WSAENOPROTOOPT:
    case WSAENOTSOCK:    // This is really saying "syscall not implemented for non-sockets".
      return Exception::Type::UNIMPLEMENTED;

    default:
      return Exception::Type::FAILED;
  }
}

#endif  // _WIN32

167 168 169 170 171 172
enum DescriptionStyle {
  LOG,
  ASSERTION,
  SYSCALL
};

173
static String makeDescriptionImpl(DescriptionStyle style, const char* code, int errorNumber,
174 175
                                  const char* sysErrorString, const char* macroArgs,
                                  ArrayPtr<String> argValues) {
176
  KJ_STACK_ARRAY(ArrayPtr<const char>, argNames, argValues.size(), 8, 64);
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214

  if (argValues.size() > 0) {
    size_t index = 0;
    const char* start = macroArgs;
    while (isspace(*start)) ++start;
    const char* pos = start;
    uint depth = 0;
    bool quoted = false;
    while (char c = *pos++) {
      if (quoted) {
        if (c == '\\' && *pos != '\0') {
          ++pos;
        } else if (c == '\"') {
          quoted = false;
        }
      } else {
        if (c == '(') {
          ++depth;
        } else if (c == ')') {
          --depth;
        } else if (c == '\"') {
          quoted = true;
        } else if (c == ',' && depth == 0) {
          if (index < argValues.size()) {
            argNames[index] = arrayPtr(start, pos - 1);
          }
          ++index;
          while (isspace(*pos)) ++pos;
          start = pos;
        }
      }
    }
    if (index < argValues.size()) {
      argNames[index] = arrayPtr(start, pos - 1);
    }
    ++index;

    if (index != argValues.size()) {
215
      getExceptionCallback().logMessage(LogSeverity::ERROR, __FILE__, __LINE__, 0,
216
          str("Failed to parse logging macro args into ",
217 218 219 220
              argValues.size(), " names: ", macroArgs, '\n'));
    }
  }

221 222 223 224 225 226 227 228 229 230 231 232
  if (style == SYSCALL) {
    // Strip off leading "foo = " from code, since callers will sometimes write things like:
    //   ssize_t n;
    //   RECOVERABLE_SYSCALL(n = read(fd, buffer, sizeof(buffer))) { return ""; }
    //   return std::string(buffer, n);
    const char* equalsPos = strchr(code, '=');
    if (equalsPos != nullptr && equalsPos[1] != '=') {
      code = equalsPos + 1;
      while (isspace(*code)) ++code;
    }
  }

233 234 235 236
  if (style == ASSERTION && code == nullptr) {
    style = LOG;
  }

237
  {
Kenton Varda's avatar
Kenton Varda committed
238 239 240 241 242
    StringPtr expected = "expected ";
    StringPtr codeArray = style == LOG ? nullptr : StringPtr(code);
    StringPtr sep = " = ";
    StringPtr delim = "; ";
    StringPtr colon = ": ";
243

Kenton Varda's avatar
Kenton Varda committed
244
    StringPtr sysErrorArray;
245 246 247
#if __USE_GNU
    char buffer[256];
    if (style == SYSCALL) {
248 249 250 251 252
      if (sysErrorString == nullptr) {
        sysErrorArray = strerror_r(errorNumber, buffer, sizeof(buffer));
      } else {
        sysErrorArray = sysErrorString;
      }
253 254
    }
#else
Kenton Varda's avatar
Kenton Varda committed
255 256
    char buffer[256];
    if (style == SYSCALL) {
257 258 259 260 261 262
      if (sysErrorString == nullptr) {
        strerror_r(errorNumber, buffer, sizeof(buffer));
        sysErrorArray = buffer;
      } else {
        sysErrorArray = sysErrorString;
      }
Kenton Varda's avatar
Kenton Varda committed
263
    }
264
#endif
265 266

    size_t totalSize = 0;
267 268 269 270 271 272 273 274 275
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
        totalSize += expected.size() + codeArray.size();
        break;
      case SYSCALL:
        totalSize += codeArray.size() + colon.size() + sysErrorArray.size();
        break;
276
    }
277

278
    for (size_t i = 0; i < argValues.size(); i++) {
279 280 281
      if (i > 0 || style != LOG) {
        totalSize += delim.size();
      }
282 283 284 285 286 287
      if (argNames[i].size() > 0 && argNames[i][0] != '\"') {
        totalSize += argNames[i].size() + sep.size();
      }
      totalSize += argValues[i].size();
    }

Kenton Varda's avatar
Kenton Varda committed
288 289
    String result = heapString(totalSize);
    char* pos = result.begin();
290

291 292 293 294
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
295
        pos = _::fill(pos, expected, codeArray);
296 297
        break;
      case SYSCALL:
298
        pos = _::fill(pos, codeArray, colon, sysErrorArray);
299
        break;
300
    }
301

302
    for (size_t i = 0; i < argValues.size(); i++) {
303
      if (i > 0 || style != LOG) {
304
        pos = _::fill(pos, delim);
305
      }
306
      if (argNames[i].size() > 0 && argNames[i][0] != '\"') {
307
        pos = _::fill(pos, argNames[i], sep);
308
      }
309
      pos = _::fill(pos, argValues[i]);
310 311
    }

Kenton Varda's avatar
Kenton Varda committed
312
    return result;
313 314 315
  }
}

316 317
}  // namespace

318
void Debug::logInternal(const char* file, int line, LogSeverity severity, const char* macroArgs,
Kenton Varda's avatar
Kenton Varda committed
319
                        ArrayPtr<String> argValues) {
320
  getExceptionCallback().logMessage(severity, trimSourceFilename(file).cStr(), line, 0,
321
      makeDescriptionImpl(LOG, nullptr, 0, nullptr, macroArgs, argValues));
322 323
}

Kenton Varda's avatar
Kenton Varda committed
324
Debug::Fault::~Fault() noexcept(false) {
325 326 327
  if (exception != nullptr) {
    Exception copy = mv(*exception);
    delete exception;
Kenton Varda's avatar
Kenton Varda committed
328
    throwRecoverableException(mv(copy), 2);
329
  }
330 331
}

Kenton Varda's avatar
Kenton Varda committed
332
void Debug::Fault::fatal() {
333 334 335
  Exception copy = mv(*exception);
  delete exception;
  exception = nullptr;
Kenton Varda's avatar
Kenton Varda committed
336
  throwFatalException(mv(copy), 2);
337 338 339
  abort();
}

340 341 342 343
void Debug::Fault::init(
    const char* file, int line, Exception::Type type,
    const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
  exception = new Exception(type, file, line,
344
      makeDescriptionImpl(ASSERTION, condition, 0, nullptr, macroArgs, argValues));
345 346
}

Kenton Varda's avatar
Kenton Varda committed
347
void Debug::Fault::init(
348
    const char* file, int line, int osErrorNumber,
349
    const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
350
  exception = new Exception(typeOfErrno(osErrorNumber), file, line,
351 352 353 354 355 356 357 358
      makeDescriptionImpl(SYSCALL, condition, osErrorNumber, nullptr, macroArgs, argValues));
}

#if _WIN32
void Debug::Fault::init(
    const char* file, int line, Win32Error osErrorNumber,
    const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
  LPVOID ptr;
359 360
  // TODO(soon): Use FormatMessageW() instead.
  // TODO(soon): Why doesn't this work for winsock errors?
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
  DWORD result = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                                FORMAT_MESSAGE_FROM_SYSTEM |
                                FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL, osErrorNumber.number,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                (LPTSTR) &ptr, 0, NULL);

  if (result > 0) {
    KJ_DEFER(LocalFree(ptr));
    exception = new Exception(typeOfWin32Error(osErrorNumber.number), file, line,
        makeDescriptionImpl(SYSCALL, condition, 0, reinterpret_cast<char*>(ptr),
                            macroArgs, argValues));
  } else {
    auto message = kj::str("win32 error code: ", osErrorNumber.number);
    exception = new Exception(typeOfWin32Error(osErrorNumber.number), file, line,
        makeDescriptionImpl(SYSCALL, condition, 0, message.cStr(),
                            macroArgs, argValues));
  }
379
}
380
#endif
381

382
String Debug::makeDescriptionInternal(const char* macroArgs, ArrayPtr<String> argValues) {
383
  return makeDescriptionImpl(LOG, nullptr, 0, nullptr, macroArgs, argValues);
384 385
}

386
int Debug::getOsErrorNumber(bool nonblocking) {
387
  int result = errno;
388 389 390 391 392 393

  // On many systems, EAGAIN and EWOULDBLOCK have the same value, but this is not strictly required
  // by POSIX, so we need to check both.
  return result == EINTR ? -1
       : nonblocking && (result == EAGAIN || result == EWOULDBLOCK) ? 0
       : result;
394 395
}

396 397 398 399 400 401
#if _WIN32
Debug::Win32Error Debug::getWin32Error() {
  return Win32Error(::GetLastError());
}
#endif

Kenton Varda's avatar
Kenton Varda committed
402
Debug::Context::Context(): logged(false) {}
403
Debug::Context::~Context() noexcept(false) {}
404

Kenton Varda's avatar
Kenton Varda committed
405
Debug::Context::Value Debug::Context::ensureInitialized() {
406 407 408 409 410 411 412 413 414
  KJ_IF_MAYBE(v, value) {
    return Value(v->file, v->line, heapString(v->description));
  } else {
    Value result = evaluate();
    value = Value(result.file, result.line, heapString(result.description));
    return result;
  }
}

Kenton Varda's avatar
Kenton Varda committed
415
void Debug::Context::onRecoverableException(Exception&& exception) {
416 417
  Value v = ensureInitialized();
  exception.wrapContext(v.file, v.line, mv(v.description));
Kenton Varda's avatar
Kenton Varda committed
418
  next.onRecoverableException(kj::mv(exception));
419
}
Kenton Varda's avatar
Kenton Varda committed
420
void Debug::Context::onFatalException(Exception&& exception) {
421 422
  Value v = ensureInitialized();
  exception.wrapContext(v.file, v.line, mv(v.description));
Kenton Varda's avatar
Kenton Varda committed
423
  next.onFatalException(kj::mv(exception));
424
}
425 426
void Debug::Context::logMessage(LogSeverity severity, const char* file, int line, int contextDepth,
                                String&& text) {
427 428
  if (!logged) {
    Value v = ensureInitialized();
429 430
    next.logMessage(LogSeverity::INFO, v.file, v.line, 0,
                    str("context: ", mv(v.description), '\n'));
431 432 433
    logged = true;
  }

434
  next.logMessage(severity, file, line, contextDepth + 1, mv(text));
435 436
}

Kenton Varda's avatar
Kenton Varda committed
437
}  // namespace _ (private)
438
}  // namespace kj