mirror of https://github.com/KLayout/klayout.git
478 lines
13 KiB
C++
478 lines
13 KiB
C++
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
|
// Licensed under the MIT License:
|
|
//
|
|
// 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:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// 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.
|
|
|
|
#if _WIN32 || __CYGWIN__
|
|
#include "win32-api-version.h"
|
|
#endif
|
|
|
|
#include "debug.h"
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#if _WIN32 || __CYGWIN__
|
|
#if !__CYGWIN__
|
|
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
|
|
#endif
|
|
#include <windows.h>
|
|
#include "windows-sanity.h"
|
|
#include "encoding.h"
|
|
#include <wchar.h>
|
|
#endif
|
|
|
|
namespace kj {
|
|
namespace _ { // private
|
|
|
|
LogSeverity Debug::minSeverity = LogSeverity::WARNING;
|
|
|
|
namespace {
|
|
|
|
Exception::Type typeOfErrno(int error) {
|
|
switch (error) {
|
|
#ifdef EDQUOT
|
|
case EDQUOT:
|
|
#endif
|
|
#ifdef EMFILE
|
|
case EMFILE:
|
|
#endif
|
|
#ifdef ENFILE
|
|
case ENFILE:
|
|
#endif
|
|
#ifdef ENOBUFS
|
|
case ENOBUFS:
|
|
#endif
|
|
#ifdef ENOLCK
|
|
case ENOLCK:
|
|
#endif
|
|
#ifdef ENOMEM
|
|
case ENOMEM:
|
|
#endif
|
|
#ifdef ENOSPC
|
|
case ENOSPC:
|
|
#endif
|
|
#ifdef ETIMEDOUT
|
|
case ETIMEDOUT:
|
|
#endif
|
|
#ifdef EUSERS
|
|
case EUSERS:
|
|
#endif
|
|
return Exception::Type::OVERLOADED;
|
|
|
|
#ifdef ENOTCONN
|
|
case ENOTCONN:
|
|
#endif
|
|
#ifdef ECONNABORTED
|
|
case ECONNABORTED:
|
|
#endif
|
|
#ifdef ECONNREFUSED
|
|
case ECONNREFUSED:
|
|
#endif
|
|
#ifdef ECONNRESET
|
|
case ECONNRESET:
|
|
#endif
|
|
#ifdef EHOSTDOWN
|
|
case EHOSTDOWN:
|
|
#endif
|
|
#ifdef EHOSTUNREACH
|
|
case EHOSTUNREACH:
|
|
#endif
|
|
#ifdef ENETDOWN
|
|
case ENETDOWN:
|
|
#endif
|
|
#ifdef ENETRESET
|
|
case ENETRESET:
|
|
#endif
|
|
#ifdef ENETUNREACH
|
|
case ENETUNREACH:
|
|
#endif
|
|
#ifdef ENONET
|
|
case ENONET:
|
|
#endif
|
|
#ifdef EPIPE
|
|
case EPIPE:
|
|
#endif
|
|
return Exception::Type::DISCONNECTED;
|
|
|
|
#ifdef ENOSYS
|
|
case ENOSYS:
|
|
#endif
|
|
#ifdef ENOTSUP
|
|
case ENOTSUP:
|
|
#endif
|
|
#if defined(EOPNOTSUPP) && EOPNOTSUPP != ENOTSUP
|
|
case EOPNOTSUPP:
|
|
#endif
|
|
#ifdef ENOPROTOOPT
|
|
case ENOPROTOOPT:
|
|
#endif
|
|
#ifdef ENOTSOCK
|
|
// This is really saying "syscall not implemented for non-sockets".
|
|
case ENOTSOCK:
|
|
#endif
|
|
return Exception::Type::UNIMPLEMENTED;
|
|
|
|
default:
|
|
return Exception::Type::FAILED;
|
|
}
|
|
}
|
|
|
|
#if _WIN32 || __CYGWIN__
|
|
|
|
Exception::Type typeOfWin32Error(DWORD error) {
|
|
switch (error) {
|
|
// TODO(someday): This needs more work.
|
|
|
|
case WSAETIMEDOUT:
|
|
return Exception::Type::OVERLOADED;
|
|
|
|
case WSAENOTCONN:
|
|
case WSAECONNABORTED:
|
|
case WSAECONNREFUSED:
|
|
case WSAECONNRESET:
|
|
case WSAEHOSTDOWN:
|
|
case WSAEHOSTUNREACH:
|
|
case WSAENETDOWN:
|
|
case WSAENETRESET:
|
|
case WSAENETUNREACH:
|
|
case WSAESHUTDOWN:
|
|
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
|
|
|
|
enum DescriptionStyle {
|
|
LOG,
|
|
ASSERTION,
|
|
SYSCALL
|
|
};
|
|
|
|
static String makeDescriptionImpl(DescriptionStyle style, const char* code, int errorNumber,
|
|
const char* sysErrorString, const char* macroArgs,
|
|
ArrayPtr<String> argValues) {
|
|
KJ_STACK_ARRAY(ArrayPtr<const char>, argNames, argValues.size(), 8, 64);
|
|
|
|
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);
|
|
}
|
|
while (isspace(*pos)) ++pos;
|
|
start = pos;
|
|
if (*pos == '\0') {
|
|
// ignore trailing comma
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (index < argValues.size()) {
|
|
argNames[index++] = arrayPtr(start, pos - 1);
|
|
}
|
|
|
|
if (index != argValues.size()) {
|
|
getExceptionCallback().logMessage(LogSeverity::ERROR, __FILE__, __LINE__, 0,
|
|
str("Failed to parse logging macro args into ",
|
|
argValues.size(), " names: ", macroArgs, '\n'));
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (style == ASSERTION && code == nullptr) {
|
|
style = LOG;
|
|
}
|
|
|
|
{
|
|
StringPtr expected = "expected ";
|
|
StringPtr codeArray = style == LOG ? nullptr : StringPtr(code);
|
|
StringPtr sep = " = ";
|
|
StringPtr delim = "; ";
|
|
StringPtr colon = ": ";
|
|
StringPtr openBracket = " [";
|
|
StringPtr closeBracket = "]";
|
|
|
|
StringPtr sysErrorArray;
|
|
// 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)
|
|
char buffer[256];
|
|
if (style == SYSCALL) {
|
|
if (sysErrorString == nullptr) {
|
|
sysErrorArray = strerror_r(errorNumber, buffer, sizeof(buffer));
|
|
} else {
|
|
sysErrorArray = sysErrorString;
|
|
}
|
|
}
|
|
#else
|
|
char buffer[256];
|
|
if (style == SYSCALL) {
|
|
if (sysErrorString == nullptr) {
|
|
strerror_r(errorNumber, buffer, sizeof(buffer));
|
|
sysErrorArray = buffer;
|
|
} else {
|
|
sysErrorArray = sysErrorString;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
size_t totalSize = 0;
|
|
switch (style) {
|
|
case LOG:
|
|
break;
|
|
case ASSERTION:
|
|
totalSize += expected.size() + codeArray.size();
|
|
break;
|
|
case SYSCALL:
|
|
totalSize += codeArray.size() + colon.size() + sysErrorArray.size();
|
|
break;
|
|
}
|
|
|
|
auto needsLabel = [](ArrayPtr<const char> &argName) -> bool {
|
|
return (argName.size() > 0 && argName[0] != '\"' &&
|
|
!(argName.size() >= 8 && memcmp(argName.begin(), "kj::str(", 8) == 0));
|
|
};
|
|
|
|
for (size_t i = 0; i < argValues.size(); i++) {
|
|
if (argNames[i] == "_kjCondition"_kj) {
|
|
// Special handling: don't output delimiter, we want to append this to the previous item,
|
|
// in brackets. Also, if it's just "[false]" (meaning we didn't manage to extract a
|
|
// comparison), don't add it at all.
|
|
if (argValues[i] != "false") {
|
|
totalSize += openBracket.size() + argValues[i].size() + closeBracket.size();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (i > 0 || style != LOG) {
|
|
totalSize += delim.size();
|
|
}
|
|
if (needsLabel(argNames[i])) {
|
|
totalSize += argNames[i].size() + sep.size();
|
|
}
|
|
totalSize += argValues[i].size();
|
|
}
|
|
|
|
String result = heapString(totalSize);
|
|
char* pos = result.begin();
|
|
|
|
switch (style) {
|
|
case LOG:
|
|
break;
|
|
case ASSERTION:
|
|
pos = _::fill(pos, expected, codeArray);
|
|
break;
|
|
case SYSCALL:
|
|
pos = _::fill(pos, codeArray, colon, sysErrorArray);
|
|
break;
|
|
}
|
|
|
|
for (size_t i = 0; i < argValues.size(); i++) {
|
|
if (argNames[i] == "_kjCondition"_kj) {
|
|
// Special handling: don't output delimiter, we want to append this to the previous item,
|
|
// in brackets. Also, if it's just "[false]" (meaning we didn't manage to extract a
|
|
// comparison), don't add it at all.
|
|
if (argValues[i] != "false") {
|
|
pos = _::fill(pos, openBracket, argValues[i], closeBracket);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (i > 0 || style != LOG) {
|
|
pos = _::fill(pos, delim);
|
|
}
|
|
if (needsLabel(argNames[i])) {
|
|
pos = _::fill(pos, argNames[i], sep);
|
|
}
|
|
pos = _::fill(pos, argValues[i]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void Debug::logInternal(const char* file, int line, LogSeverity severity, const char* macroArgs,
|
|
ArrayPtr<String> argValues) {
|
|
getExceptionCallback().logMessage(severity, trimSourceFilename(file).cStr(), line, 0,
|
|
makeDescriptionImpl(LOG, nullptr, 0, nullptr, macroArgs, argValues));
|
|
}
|
|
|
|
Debug::Fault::~Fault() noexcept(false) {
|
|
if (exception != nullptr) {
|
|
Exception copy = mv(*exception);
|
|
delete exception;
|
|
throwRecoverableException(mv(copy), 1);
|
|
}
|
|
}
|
|
|
|
void Debug::Fault::fatal() {
|
|
Exception copy = mv(*exception);
|
|
delete exception;
|
|
exception = nullptr;
|
|
throwFatalException(mv(copy), 1);
|
|
KJ_KNOWN_UNREACHABLE(abort());
|
|
}
|
|
|
|
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, nullptr, macroArgs, argValues));
|
|
}
|
|
|
|
void Debug::Fault::init(
|
|
const char* file, int line, int osErrorNumber,
|
|
const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
|
|
exception = new Exception(typeOfErrno(osErrorNumber), file, line,
|
|
makeDescriptionImpl(SYSCALL, condition, osErrorNumber, nullptr, macroArgs, argValues));
|
|
}
|
|
|
|
#if _WIN32 || __CYGWIN__
|
|
void Debug::Fault::init(
|
|
const char* file, int line, Win32Result osErrorNumber,
|
|
const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
|
|
LPVOID ptr;
|
|
// TODO(someday): Why doesn't this work for winsock errors?
|
|
DWORD result = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL, osErrorNumber.number,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPWSTR) &ptr, 0, NULL);
|
|
|
|
String message;
|
|
if (result > 0) {
|
|
KJ_DEFER(LocalFree(ptr));
|
|
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)));
|
|
} else {
|
|
message = kj::str("win32 error code: ", osErrorNumber.number);
|
|
}
|
|
|
|
exception = new Exception(typeOfWin32Error(osErrorNumber.number), file, line,
|
|
makeDescriptionImpl(SYSCALL, condition, 0, message.cStr(),
|
|
macroArgs, argValues));
|
|
}
|
|
#endif
|
|
|
|
String Debug::makeDescriptionInternal(const char* macroArgs, ArrayPtr<String> argValues) {
|
|
return makeDescriptionImpl(LOG, nullptr, 0, nullptr, macroArgs, argValues);
|
|
}
|
|
|
|
int Debug::getOsErrorNumber(bool nonblocking) {
|
|
int result = errno;
|
|
|
|
// 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;
|
|
}
|
|
|
|
#if _WIN32 || __CYGWIN__
|
|
uint Debug::getWin32ErrorCode() {
|
|
return ::GetLastError();
|
|
}
|
|
#endif
|
|
|
|
Debug::Context::Context(): logged(false) {}
|
|
Debug::Context::~Context() noexcept(false) {}
|
|
|
|
Debug::Context::Value Debug::Context::ensureInitialized() {
|
|
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;
|
|
}
|
|
}
|
|
|
|
void Debug::Context::onRecoverableException(Exception&& exception) {
|
|
Value v = ensureInitialized();
|
|
exception.wrapContext(v.file, v.line, mv(v.description));
|
|
next.onRecoverableException(kj::mv(exception));
|
|
}
|
|
void Debug::Context::onFatalException(Exception&& exception) {
|
|
Value v = ensureInitialized();
|
|
exception.wrapContext(v.file, v.line, mv(v.description));
|
|
next.onFatalException(kj::mv(exception));
|
|
}
|
|
void Debug::Context::logMessage(LogSeverity severity, const char* file, int line, int contextDepth,
|
|
String&& text) {
|
|
if (!logged) {
|
|
Value v = ensureInitialized();
|
|
next.logMessage(LogSeverity::INFO, trimSourceFilename(v.file).cStr(), v.line, 0,
|
|
str("context: ", mv(v.description), '\n'));
|
|
logged = true;
|
|
}
|
|
|
|
next.logMessage(severity, file, line, contextDepth + 1, mv(text));
|
|
}
|
|
|
|
} // namespace _ (private)
|
|
} // namespace kj
|