debug.c++ 9.41 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 30 31
#if _WIN32
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
#endif

32
namespace kj {
Kenton Varda's avatar
Kenton Varda committed
33
namespace _ {  // private
34

35
LogSeverity Debug::minSeverity = LogSeverity::WARNING;
36

37 38
namespace {

39 40
Exception::Type typeOfErrno(int error) {
  switch (error) {
41
#ifdef EDQUOT
42
    case EDQUOT:
43 44
#endif
#ifdef EMFILE
45
    case EMFILE:
46 47
#endif
#ifdef ENFILE
48
    case ENFILE:
49 50
#endif
#ifdef ENOBUFS
51
    case ENOBUFS:
52 53
#endif
#ifdef ENOLCK
54
    case ENOLCK:
55 56
#endif
#ifdef ENOMEM
57
    case ENOMEM:
58 59
#endif
#ifdef ENOSPC
60
    case ENOSPC:
61 62
#endif
#ifdef ETIMEDOUT
63
    case ETIMEDOUT:
64 65
#endif
#ifdef EUSERS
66
    case EUSERS:
67
#endif
68 69
      return Exception::Type::OVERLOADED;

70
#ifdef ENOTCONN
71 72
    case ENOTCONN:
#endif
73
#ifdef ECONNABORTED
74
    case ECONNABORTED:
75 76
#endif
#ifdef ECONNREFUSED
77
    case ECONNREFUSED:
78 79
#endif
#ifdef ECONNRESET
80
    case ECONNRESET:
81 82
#endif
#ifdef EHOSTDOWN
83
    case EHOSTDOWN:
84 85
#endif
#ifdef EHOSTUNREACH
86
    case EHOSTUNREACH:
87 88
#endif
#ifdef ENETDOWN
89
    case ENETDOWN:
90 91
#endif
#ifdef ENETRESET
92
    case ENETRESET:
93 94
#endif
#ifdef ENETUNREACH
95
    case ENETUNREACH:
96 97
#endif
#ifdef ENONET
98
    case ENONET:
99 100
#endif
#ifdef EPIPE
101
    case EPIPE:
102
#endif
103 104
      return Exception::Type::DISCONNECTED;

105
#ifdef ENOSYS
106
    case ENOSYS:
107 108
#endif
#ifdef ENOTSUP
109 110
    case ENOTSUP:
#endif
111
#if defined(EOPNOTSUPP) && EOPNOTSUPP != ENOTSUP
112
    case EOPNOTSUPP:
113 114 115 116 117 118 119
#endif
#ifdef ENOPROTOOPT
    case ENOPROTOOPT:
#endif
#ifdef ENOTSOCK
    // This is really saying "syscall not implemented for non-sockets".
    case ENOTSOCK:
120 121 122 123 124 125 126 127
#endif
      return Exception::Type::UNIMPLEMENTED;

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

128 129 130 131 132 133
enum DescriptionStyle {
  LOG,
  ASSERTION,
  SYSCALL
};

134 135
static String makeDescriptionImpl(DescriptionStyle style, const char* code, int errorNumber,
                                  const char* macroArgs, ArrayPtr<String> argValues) {
136
  KJ_STACK_ARRAY(ArrayPtr<const char>, argNames, argValues.size(), 8, 64);
137 138 139 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 167 168 169 170 171 172 173 174

  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()) {
175
      getExceptionCallback().logMessage(LogSeverity::ERROR, __FILE__, __LINE__, 0,
176
          str("Failed to parse logging macro args into ",
177 178 179 180
              argValues.size(), " names: ", macroArgs, '\n'));
    }
  }

181 182 183 184 185 186 187 188 189 190 191 192
  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;
    }
  }

193 194 195 196
  if (style == ASSERTION && code == nullptr) {
    style = LOG;
  }

197
  {
Kenton Varda's avatar
Kenton Varda committed
198 199 200 201 202
    StringPtr expected = "expected ";
    StringPtr codeArray = style == LOG ? nullptr : StringPtr(code);
    StringPtr sep = " = ";
    StringPtr delim = "; ";
    StringPtr colon = ": ";
203

Kenton Varda's avatar
Kenton Varda committed
204
    StringPtr sysErrorArray;
205 206 207
#if __USE_GNU
    char buffer[256];
    if (style == SYSCALL) {
Kenton Varda's avatar
Kenton Varda committed
208
      sysErrorArray = strerror_r(errorNumber, buffer, sizeof(buffer));
209 210
    }
#else
Kenton Varda's avatar
Kenton Varda committed
211 212 213 214 215
    char buffer[256];
    if (style == SYSCALL) {
      strerror_r(errorNumber, buffer, sizeof(buffer));
      sysErrorArray = buffer;
    }
216
#endif
217 218

    size_t totalSize = 0;
219 220 221 222 223 224 225 226 227
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
        totalSize += expected.size() + codeArray.size();
        break;
      case SYSCALL:
        totalSize += codeArray.size() + colon.size() + sysErrorArray.size();
        break;
228
    }
229

230
    for (size_t i = 0; i < argValues.size(); i++) {
231 232 233
      if (i > 0 || style != LOG) {
        totalSize += delim.size();
      }
234 235 236 237 238 239
      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
240 241
    String result = heapString(totalSize);
    char* pos = result.begin();
242

243 244 245 246
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
247
        pos = _::fill(pos, expected, codeArray);
248 249
        break;
      case SYSCALL:
250
        pos = _::fill(pos, codeArray, colon, sysErrorArray);
251
        break;
252 253
    }
    for (size_t i = 0; i < argValues.size(); i++) {
254
      if (i > 0 || style != LOG) {
255
        pos = _::fill(pos, delim);
256
      }
257
      if (argNames[i].size() > 0 && argNames[i][0] != '\"') {
258
        pos = _::fill(pos, argNames[i], sep);
259
      }
260
      pos = _::fill(pos, argValues[i]);
261 262
    }

Kenton Varda's avatar
Kenton Varda committed
263
    return result;
264 265 266
  }
}

267 268
}  // namespace

269
void Debug::logInternal(const char* file, int line, LogSeverity severity, const char* macroArgs,
Kenton Varda's avatar
Kenton Varda committed
270
                        ArrayPtr<String> argValues) {
271
  getExceptionCallback().logMessage(severity, trimSourceFilename(file).cStr(), line, 0,
272
      makeDescriptionImpl(LOG, nullptr, 0, macroArgs, argValues));
273 274
}

Kenton Varda's avatar
Kenton Varda committed
275
Debug::Fault::~Fault() noexcept(false) {
276 277 278
  if (exception != nullptr) {
    Exception copy = mv(*exception);
    delete exception;
Kenton Varda's avatar
Kenton Varda committed
279
    throwRecoverableException(mv(copy), 2);
280
  }
281 282
}

Kenton Varda's avatar
Kenton Varda committed
283
void Debug::Fault::fatal() {
284 285 286
  Exception copy = mv(*exception);
  delete exception;
  exception = nullptr;
Kenton Varda's avatar
Kenton Varda committed
287
  throwFatalException(mv(copy), 2);
288 289 290
  abort();
}

291 292 293 294 295 296 297
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,
      makeDescriptionImpl(ASSERTION, condition, 0, macroArgs, argValues));
}

Kenton Varda's avatar
Kenton Varda committed
298
void Debug::Fault::init(
299
    const char* file, int line, int osErrorNumber,
300
    const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
301
  exception = new Exception(typeOfErrno(osErrorNumber), file, line,
302
      makeDescriptionImpl(SYSCALL, condition, osErrorNumber, macroArgs, argValues));
303 304
}

305 306
String Debug::makeDescriptionInternal(const char* macroArgs, ArrayPtr<String> argValues) {
  return makeDescriptionImpl(LOG, nullptr, 0, macroArgs, argValues);
307 308
}

309
int Debug::getOsErrorNumber(bool nonblocking) {
310
  int result = errno;
311 312 313 314 315 316

  // 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;
317 318
}

Kenton Varda's avatar
Kenton Varda committed
319
Debug::Context::Context(): logged(false) {}
320
Debug::Context::~Context() noexcept(false) {}
321

Kenton Varda's avatar
Kenton Varda committed
322
Debug::Context::Value Debug::Context::ensureInitialized() {
323 324 325 326 327 328 329 330 331
  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
332
void Debug::Context::onRecoverableException(Exception&& exception) {
333 334
  Value v = ensureInitialized();
  exception.wrapContext(v.file, v.line, mv(v.description));
Kenton Varda's avatar
Kenton Varda committed
335
  next.onRecoverableException(kj::mv(exception));
336
}
Kenton Varda's avatar
Kenton Varda committed
337
void Debug::Context::onFatalException(Exception&& exception) {
338 339
  Value v = ensureInitialized();
  exception.wrapContext(v.file, v.line, mv(v.description));
Kenton Varda's avatar
Kenton Varda committed
340
  next.onFatalException(kj::mv(exception));
341
}
342 343
void Debug::Context::logMessage(LogSeverity severity, const char* file, int line, int contextDepth,
                                String&& text) {
344 345
  if (!logged) {
    Value v = ensureInitialized();
346 347
    next.logMessage(LogSeverity::INFO, v.file, v.line, 0,
                    str("context: ", mv(v.description), '\n'));
348 349 350
    logged = true;
  }

351
  next.logMessage(severity, file, line, contextDepth + 1, mv(text));
352 353
}

Kenton Varda's avatar
Kenton Varda committed
354
}  // namespace _ (private)
355
}  // namespace kj