debug.c++ 12.1 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
#include "encoding.h"
38 39
#endif

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

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

45 46
namespace {

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

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

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

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

136 137 138 139
#if _WIN32

Exception::Type typeOfWin32Error(DWORD error) {
  switch (error) {
140
    // TODO(someday): This needs more work.
141 142 143 144 145 146 147 148 149 150 151 152 153

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

    case WSAENOTCONN:
    case WSAECONNABORTED:
    case WSAECONNREFUSED:
    case WSAECONNRESET:
    case WSAEHOSTDOWN:
    case WSAEHOSTUNREACH:
    case WSAENETDOWN:
    case WSAENETRESET:
    case WSAENETUNREACH:
154
    case WSAESHUTDOWN:
155 156 157 158 159 160 161 162 163 164 165 166 167 168
      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

169 170 171 172 173 174
enum DescriptionStyle {
  LOG,
  ASSERTION,
  SYSCALL
};

175
static String makeDescriptionImpl(DescriptionStyle style, const char* code, int errorNumber,
176 177
                                  const char* sysErrorString, const char* macroArgs,
                                  ArrayPtr<String> argValues) {
178
  KJ_STACK_ARRAY(ArrayPtr<const char>, argNames, argValues.size(), 8, 64);
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 215 216

  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()) {
217
      getExceptionCallback().logMessage(LogSeverity::ERROR, __FILE__, __LINE__, 0,
218
          str("Failed to parse logging macro args into ",
219 220 221 222
              argValues.size(), " names: ", macroArgs, '\n'));
    }
  }

223 224 225 226 227 228 229 230 231 232 233 234
  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;
    }
  }

235 236 237 238
  if (style == ASSERTION && code == nullptr) {
    style = LOG;
  }

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

Kenton Varda's avatar
Kenton Varda committed
246
    StringPtr sysErrorArray;
247 248 249
// On android before marshmallow only the posix version of stderror_r was
// available, even with __USE_GNU.
#if __USE_GNU && !(defined(__ANDROID_API__) && __ANDROID_API__ < 23)
250 251
    char buffer[256];
    if (style == SYSCALL) {
252 253 254 255 256
      if (sysErrorString == nullptr) {
        sysErrorArray = strerror_r(errorNumber, buffer, sizeof(buffer));
      } else {
        sysErrorArray = sysErrorString;
      }
257 258
    }
#else
Kenton Varda's avatar
Kenton Varda committed
259 260
    char buffer[256];
    if (style == SYSCALL) {
261 262 263 264 265 266
      if (sysErrorString == nullptr) {
        strerror_r(errorNumber, buffer, sizeof(buffer));
        sysErrorArray = buffer;
      } else {
        sysErrorArray = sysErrorString;
      }
Kenton Varda's avatar
Kenton Varda committed
267
    }
268
#endif
269 270

    size_t totalSize = 0;
271 272 273 274 275 276 277 278 279
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
        totalSize += expected.size() + codeArray.size();
        break;
      case SYSCALL:
        totalSize += codeArray.size() + colon.size() + sysErrorArray.size();
        break;
280
    }
281

282
    for (size_t i = 0; i < argValues.size(); i++) {
283 284 285
      if (i > 0 || style != LOG) {
        totalSize += delim.size();
      }
286 287 288 289 290 291
      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
292 293
    String result = heapString(totalSize);
    char* pos = result.begin();
294

295 296 297 298
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
299
        pos = _::fill(pos, expected, codeArray);
300 301
        break;
      case SYSCALL:
302
        pos = _::fill(pos, codeArray, colon, sysErrorArray);
303
        break;
304
    }
305

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

Kenton Varda's avatar
Kenton Varda committed
316
    return result;
317 318 319
  }
}

320 321
}  // namespace

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

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

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

344 345 346 347
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,
348
      makeDescriptionImpl(ASSERTION, condition, 0, nullptr, macroArgs, argValues));
349 350
}

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

#if _WIN32
void Debug::Fault::init(
360
    const char* file, int line, Win32Result osErrorNumber,
361 362
    const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
  LPVOID ptr;
363
  // TODO(someday): Why doesn't this work for winsock errors?
364
  DWORD result = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
365 366 367 368
                                FORMAT_MESSAGE_FROM_SYSTEM |
                                FORMAT_MESSAGE_IGNORE_INSERTS,
                                NULL, osErrorNumber.number,
                                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
369
                                (LPWSTR) &ptr, 0, NULL);
370

371
  String message;
372 373
  if (result > 0) {
    KJ_DEFER(LocalFree(ptr));
374 375 376 377 378 379
    const wchar_t* desc = reinterpret_cast<wchar_t*>(ptr);
    size_t len = wcslen(desc);
    if (len > 0 && desc[len-1] == '\n') --len;
    if (len > 0 && desc[len-1] == '\r') --len;
    message = kj::str('#', osErrorNumber.number, ' ',
        decodeWideString(arrayPtr(desc, len)));
380
  } else {
381
    message = kj::str("win32 error code: ", osErrorNumber.number);
382
  }
383 384 385 386

  exception = new Exception(typeOfWin32Error(osErrorNumber.number), file, line,
      makeDescriptionImpl(SYSCALL, condition, 0, message.cStr(),
                          macroArgs, argValues));
387
}
388
#endif
389

390
String Debug::makeDescriptionInternal(const char* macroArgs, ArrayPtr<String> argValues) {
391
  return makeDescriptionImpl(LOG, nullptr, 0, nullptr, macroArgs, argValues);
392 393
}

394
int Debug::getOsErrorNumber(bool nonblocking) {
395
  int result = errno;
396 397 398 399 400 401

  // 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;
402 403
}

404
#if _WIN32
405 406
uint Debug::getWin32ErrorCode() {
  return ::GetLastError();
407 408 409
}
#endif

Kenton Varda's avatar
Kenton Varda committed
410
Debug::Context::Context(): logged(false) {}
411
Debug::Context::~Context() noexcept(false) {}
412

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

442
  next.logMessage(severity, file, line, contextDepth + 1, mv(text));
443 444
}

Kenton Varda's avatar
Kenton Varda committed
445
}  // namespace _ (private)
446
}  // namespace kj