debug.c++ 7.99 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

Kenton Varda's avatar
Kenton Varda committed
35
Debug::Severity Debug::minSeverity = Debug::Severity::WARNING;
36

Kenton Varda's avatar
Kenton Varda committed
37
ArrayPtr<const char> KJ_STRINGIFY(Debug::Severity severity) {
38 39 40 41
  static const char* SEVERITY_STRINGS[] = {
    "info",
    "warning",
    "error",
42 43
    "fatal",
    "debug"
44 45 46 47 48 49
  };

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

50 51 52 53 54 55 56 57
namespace {

enum DescriptionStyle {
  LOG,
  ASSERTION,
  SYSCALL
};

Kenton Varda's avatar
Kenton Varda committed
58 59
static String makeDescription(DescriptionStyle style, const char* code, int errorNumber,
                              const char* macroArgs, ArrayPtr<String> argValues) {
60
  KJ_STACK_ARRAY(ArrayPtr<const char>, argNames, argValues.size(), 8, 64);
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98

  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()) {
99 100
      getExceptionCallback().logMessage(__FILE__, __LINE__, 0,
          str("Failed to parse logging macro args into ",
101 102 103 104
              argValues.size(), " names: ", macroArgs, '\n'));
    }
  }

105 106 107 108 109 110 111 112 113 114 115 116
  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;
    }
  }

117 118 119 120
  if (style == ASSERTION && code == nullptr) {
    style = LOG;
  }

121
  {
Kenton Varda's avatar
Kenton Varda committed
122 123 124 125 126
    StringPtr expected = "expected ";
    StringPtr codeArray = style == LOG ? nullptr : StringPtr(code);
    StringPtr sep = " = ";
    StringPtr delim = "; ";
    StringPtr colon = ": ";
127

Kenton Varda's avatar
Kenton Varda committed
128
    StringPtr sysErrorArray;
129 130 131
#if __USE_GNU
    char buffer[256];
    if (style == SYSCALL) {
Kenton Varda's avatar
Kenton Varda committed
132
      sysErrorArray = strerror_r(errorNumber, buffer, sizeof(buffer));
133 134
    }
#else
Kenton Varda's avatar
Kenton Varda committed
135 136 137 138 139
    char buffer[256];
    if (style == SYSCALL) {
      strerror_r(errorNumber, buffer, sizeof(buffer));
      sysErrorArray = buffer;
    }
140
#endif
141 142

    size_t totalSize = 0;
143 144 145 146 147 148 149 150 151
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
        totalSize += expected.size() + codeArray.size();
        break;
      case SYSCALL:
        totalSize += codeArray.size() + colon.size() + sysErrorArray.size();
        break;
152
    }
153

154
    for (size_t i = 0; i < argValues.size(); i++) {
155 156 157
      if (i > 0 || style != LOG) {
        totalSize += delim.size();
      }
158 159 160 161 162 163
      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
164 165
    String result = heapString(totalSize);
    char* pos = result.begin();
166

167 168 169 170
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
171
        pos = _::fill(pos, expected, codeArray);
172 173
        break;
      case SYSCALL:
174
        pos = _::fill(pos, codeArray, colon, sysErrorArray);
175
        break;
176 177
    }
    for (size_t i = 0; i < argValues.size(); i++) {
178
      if (i > 0 || style != LOG) {
179
        pos = _::fill(pos, delim);
180
      }
181
      if (argNames[i].size() > 0 && argNames[i][0] != '\"') {
182
        pos = _::fill(pos, argNames[i], sep);
183
      }
184
      pos = _::fill(pos, argValues[i]);
185 186
    }

Kenton Varda's avatar
Kenton Varda committed
187
    return result;
188 189 190
  }
}

191 192
}  // namespace

Kenton Varda's avatar
Kenton Varda committed
193 194
void Debug::logInternal(const char* file, int line, Severity severity, const char* macroArgs,
                        ArrayPtr<String> argValues) {
195 196
  getExceptionCallback().logMessage(file, line, 0,
      str(severity, ": ", makeDescription(LOG, nullptr, 0, macroArgs, argValues), '\n'));
197 198
}

Kenton Varda's avatar
Kenton Varda committed
199
Debug::Fault::~Fault() noexcept(false) {
200 201 202
  if (exception != nullptr) {
    Exception copy = mv(*exception);
    delete exception;
203
    throwRecoverableException(mv(copy));
204
  }
205 206
}

Kenton Varda's avatar
Kenton Varda committed
207
void Debug::Fault::fatal() {
208 209 210
  Exception copy = mv(*exception);
  delete exception;
  exception = nullptr;
211
  throwFatalException(mv(copy));
212 213 214
  abort();
}

Kenton Varda's avatar
Kenton Varda committed
215
void Debug::Fault::init(
216 217 218 219 220
    const char* file, int line, Exception::Nature nature, int errorNumber,
    const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
  exception = new Exception(nature, Exception::Durability::PERMANENT, file, line,
      makeDescription(nature == Exception::Nature::OS_ERROR ? SYSCALL : ASSERTION,
                      condition, errorNumber, macroArgs, argValues));
221 222
}

Kenton Varda's avatar
Kenton Varda committed
223
String Debug::makeContextDescriptionInternal(const char* macroArgs, ArrayPtr<String> argValues) {
224
  return makeDescription(LOG, nullptr, 0, macroArgs, argValues);
225 226
}

227
int Debug::getOsErrorNumber(bool nonblocking) {
228
  int result = errno;
229 230 231 232 233 234

  // 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;
235 236
}

Kenton Varda's avatar
Kenton Varda committed
237
Debug::Context::Context(): logged(false) {}
238
Debug::Context::~Context() noexcept(false) {}
239

Kenton Varda's avatar
Kenton Varda committed
240
Debug::Context::Value Debug::Context::ensureInitialized() {
241 242 243 244 245 246 247 248 249
  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
250
void Debug::Context::onRecoverableException(Exception&& exception) {
251 252
  Value v = ensureInitialized();
  exception.wrapContext(v.file, v.line, mv(v.description));
Kenton Varda's avatar
Kenton Varda committed
253
  next.onRecoverableException(kj::mv(exception));
254
}
Kenton Varda's avatar
Kenton Varda committed
255
void Debug::Context::onFatalException(Exception&& exception) {
256 257
  Value v = ensureInitialized();
  exception.wrapContext(v.file, v.line, mv(v.description));
Kenton Varda's avatar
Kenton Varda committed
258
  next.onFatalException(kj::mv(exception));
259
}
Kenton Varda's avatar
Kenton Varda committed
260
void Debug::Context::logMessage(const char* file, int line, int contextDepth, String&& text) {
261 262 263 264 265 266 267
  if (!logged) {
    Value v = ensureInitialized();
    next.logMessage(v.file, v.line, 0, str("context: ", mv(v.description), '\n'));
    logged = true;
  }

  next.logMessage(file, line, contextDepth + 1, mv(text));
268 269
}

Kenton Varda's avatar
Kenton Varda committed
270
}  // namespace _ (private)
271
}  // namespace kj