Commit 9067a589 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Merge pull request #564 from toshok/incremental-traceback

Incremental traceback
parents cf145fb8 d034a63e
......@@ -69,13 +69,10 @@ private:
ASTInterpreter* interpreter;
public:
RegisterHelper(ASTInterpreter* interpreter, void* frame_addr);
RegisterHelper();
~RegisterHelper();
static void deregister(void* frame_addr) {
assert(s_interpreterMap.count(frame_addr));
s_interpreterMap.erase(frame_addr);
}
void doRegister(void* frame_addr, ASTInterpreter* interpreter);
static void deregister(void* frame_addr);
};
union Value {
......@@ -102,11 +99,13 @@ public:
void initArguments(int nargs, BoxedClosure* closure, BoxedGenerator* generator, Box* arg1, Box* arg2, Box* arg3,
Box** args);
static Value execute(ASTInterpreter& interpreter, CFGBlock* start_block = NULL, AST_stmt* start_at = NULL);
// This must not be inlined, because we rely on being able to detect when we're inside of it (by checking whether
// %rip is inside its instruction range) during a stack-trace in order to produce tracebacks inside interpreted
// code.
__attribute__((__no_inline__)) static Value
execute(ASTInterpreter& interpreter, CFGBlock* start_block = NULL, AST_stmt* start_at = NULL);
executeInner(ASTInterpreter& interpreter, CFGBlock* start_block, AST_stmt* start_at, RegisterHelper* reg);
private:
Box* createFunction(AST* node, AST_arguments* args, const std::vector<AST_stmt*>& body);
......@@ -318,22 +317,35 @@ void ASTInterpreter::initArguments(int nargs, BoxedClosure* _closure, BoxedGener
}
}
RegisterHelper::RegisterHelper(ASTInterpreter* interpreter, void* frame_addr)
: frame_addr(frame_addr), interpreter(interpreter) {
interpreter->frame_addr = frame_addr;
s_interpreterMap[frame_addr] = interpreter;
RegisterHelper::RegisterHelper() : frame_addr(NULL), interpreter(NULL) {
}
RegisterHelper::~RegisterHelper() {
assert(interpreter);
assert(interpreter->frame_addr == frame_addr);
interpreter->frame_addr = nullptr;
deregister(frame_addr);
}
Value ASTInterpreter::execute(ASTInterpreter& interpreter, CFGBlock* start_block, AST_stmt* start_at) {
STAT_TIMER(t0, "us_timer_astinterpreter_execute");
void RegisterHelper::doRegister(void* frame_addr, ASTInterpreter* interpreter) {
assert(!this->interpreter);
assert(!this->frame_addr);
this->frame_addr = frame_addr;
this->interpreter = interpreter;
interpreter->frame_addr = frame_addr;
s_interpreterMap[frame_addr] = interpreter;
}
void RegisterHelper::deregister(void* frame_addr) {
assert(frame_addr);
assert(s_interpreterMap.count(frame_addr));
s_interpreterMap.erase(frame_addr);
}
Value ASTInterpreter::executeInner(ASTInterpreter& interpreter, CFGBlock* start_block, AST_stmt* start_at,
RegisterHelper* reg) {
void* frame_addr = __builtin_frame_address(0);
RegisterHelper frame_registerer(&interpreter, frame_addr);
reg->doRegister(frame_addr, &interpreter);
Value v;
......@@ -373,6 +385,14 @@ Value ASTInterpreter::execute(ASTInterpreter& interpreter, CFGBlock* start_block
return v;
}
Value ASTInterpreter::execute(ASTInterpreter& interpreter, CFGBlock* start_block, AST_stmt* start_at) {
STAT_TIMER(t0, "us_timer_astinterpreter_execute");
RegisterHelper frame_registerer;
return executeInner(interpreter, start_block, start_at, &frame_registerer);
}
Value ASTInterpreter::doBinOp(Box* left, Box* right, int op, BinExpType exp_type) {
switch (exp_type) {
case BinExpType::AugBinOp:
......@@ -610,6 +630,10 @@ Value ASTInterpreter::visit_invoke(AST_Invoke* node) {
v = visit_stmt(node->stmt);
next_block = node->normal_dest;
} catch (ExcInfo e) {
auto source = getCF()->clfunc->source.get();
exceptionCaughtInInterpreter(LineInfo(node->lineno, node->col_offset, source->fn, source->getName()), &e);
next_block = node->exc_dest;
last_exception = e;
}
......@@ -1269,7 +1293,7 @@ Value ASTInterpreter::visit_attribute(AST_Attribute* node) {
}
}
const void* interpreter_instr_addr = (void*)&ASTInterpreter::execute;
const void* interpreter_instr_addr = (void*)&ASTInterpreter::executeInner;
Box* astInterpretFunction(CompiledFunction* cf, int nargs, Box* closure, Box* generator, Box* globals, Box* arg1,
Box* arg2, Box* arg3, Box** args) {
......
......@@ -110,8 +110,8 @@ static llvm::Value* getClosureElementGep(IREmitter& emitter, llvm::Value* closur
static llvm::Value* getBoxedLocalsGep(llvm::IRBuilder<true>& builder, llvm::Value* v) {
static_assert(offsetof(FrameInfo, exc) == 0, "");
static_assert(sizeof(ExcInfo) == 24, "");
static_assert(offsetof(FrameInfo, boxedLocals) == 24, "");
static_assert(sizeof(ExcInfo) == 32, "");
static_assert(offsetof(FrameInfo, boxedLocals) == 32, "");
return builder.CreateConstInBoundsGEP2_32(v, 0, 1);
}
......@@ -122,9 +122,9 @@ static llvm::Value* getExcinfoGep(llvm::IRBuilder<true>& builder, llvm::Value* v
static llvm::Value* getFrameObjGep(llvm::IRBuilder<true>& builder, llvm::Value* v) {
static_assert(offsetof(FrameInfo, exc) == 0, "");
static_assert(sizeof(ExcInfo) == 24, "");
static_assert(sizeof(ExcInfo) == 32, "");
static_assert(sizeof(Box*) == 8, "");
static_assert(offsetof(FrameInfo, frame_obj) == 32, "");
static_assert(offsetof(FrameInfo, frame_obj) == 40, "");
return builder.CreateConstInBoundsGEP2_32(v, 0, 2);
// TODO: this could be made more resilient by doing something like
// gep->accumulateConstantOffset(g.tm->getDataLayout(), ap_offset)
......
......@@ -58,6 +58,8 @@ struct uw_table_entry {
namespace pyston {
static BoxedClass* unwind_session_cls;
// Parse an .eh_frame section, and construct a "binary search table" such as you would find in a .eh_frame_hdr section.
// Currently only supports .eh_frame sections with exactly one fde.
// See http://www.airs.com/blog/archives/460 for some useful info.
......@@ -277,6 +279,10 @@ struct PythonFrameId {
uint64_t ip;
uint64_t bp;
PythonFrameId() {}
PythonFrameId(FrameType type, uint64_t ip, uint64_t bp) : type(type), ip(ip), bp(bp) {}
bool operator==(const PythonFrameId& rhs) const { return (this->type == rhs.type) && (this->ip == rhs.ip); }
};
......@@ -289,13 +295,11 @@ public:
intptr_t regs[16];
uint16_t regs_valid;
PythonFrameIteratorImpl(const PythonFrameIteratorImpl&) = delete;
void operator=(const PythonFrameIteratorImpl&) = delete;
PythonFrameIteratorImpl(const PythonFrameIteratorImpl&&) = delete;
void operator=(const PythonFrameIteratorImpl&&) = delete;
PythonFrameIteratorImpl() : regs_valid(0) {}
PythonFrameIteratorImpl(PythonFrameId::FrameType type, uint64_t ip, uint64_t bp, CompiledFunction* cf)
: id(PythonFrameId(type, ip, bp)), cf(cf), regs_valid(0) {}
CompiledFunction* getCF() const {
assert(cf);
return cf;
......@@ -411,45 +415,47 @@ static unw_word_t getFunctionEnd(unw_word_t ip) {
return pip.end_ip;
}
// While I'm not a huge fan of the callback-passing style, libunwind cursors are only valid for
// the stack frame that they were created in, so we need to use this approach (as opposed to
// C++11 range loops, for example).
// Return true from the handler to stop iteration at that frame.
void unwindPythonStack(std::function<bool(std::unique_ptr<PythonFrameIteratorImpl>)> func) {
static bool inASTInterpreterExecuteInner(unw_word_t ip) {
static unw_word_t interpreter_instr_end = getFunctionEnd((unw_word_t)interpreter_instr_addr);
static unw_word_t generator_entry_end = getFunctionEnd((unw_word_t)generatorEntry);
unw_context_t ctx;
unw_cursor_t cursor;
unw_getcontext(&ctx);
unw_init_local(&cursor, &ctx);
return ((unw_word_t)interpreter_instr_addr <= ip && ip < interpreter_instr_end);
}
// If the previous (lower, ie newer) frame was a frame that got OSR'd into,
// we skip the previous frame, its OSR parent.
bool was_osr = false;
static bool inGeneratorEntry(unw_word_t ip) {
static unw_word_t generator_entry_end = getFunctionEnd((unw_word_t)generatorEntry);
return ((unw_word_t)generatorEntry <= ip && ip < generator_entry_end);
}
while (true) {
int r = unw_step(&cursor);
assert(r >= 0);
if (r == 0)
break;
static inline unw_word_t get_cursor_reg(unw_cursor_t* cursor, int reg) {
unw_word_t v;
unw_get_reg(cursor, reg, &v);
return v;
}
static inline unw_word_t get_cursor_ip(unw_cursor_t* cursor) {
return get_cursor_reg(cursor, UNW_REG_IP) - 1;
}
static inline unw_word_t get_cursor_bp(unw_cursor_t* cursor) {
return get_cursor_reg(cursor, UNW_TDEP_BP);
}
// if the given ip/bp correspond to a jitted frame or
// ASTInterpreter::execute_inner frame, return true and return the
// frame information through the PythonFrameIteratorImpl* info arg.
bool frameIsPythonFrame(unw_word_t ip, unw_word_t bp, unw_cursor_t* cursor, PythonFrameIteratorImpl* info) {
CompiledFunction* cf = getCFForAddress(ip);
bool jitted = cf != NULL;
if (!cf) {
if (inASTInterpreterExecuteInner(ip)) {
cf = getCFForInterpretedFrame((void*)bp);
assert(cf);
}
}
unw_word_t ip;
unw_get_reg(&cursor, UNW_REG_IP, &ip);
unw_word_t bp;
unw_get_reg(&cursor, UNW_TDEP_BP, &bp);
if (!cf)
return false;
CompiledFunction* cf = getCFForAddress(ip);
if (cf) {
std::unique_ptr<PythonFrameIteratorImpl> info(new PythonFrameIteratorImpl());
info->id.type = PythonFrameId::COMPILED;
info->id.ip = ip;
info->id.bp = bp;
info->cf = cf;
if (!was_osr) {
*info = PythonFrameIteratorImpl(jitted ? PythonFrameId::COMPILED : PythonFrameId::INTERPRETED, ip, bp, cf);
if (jitted) {
// Try getting all the callee-save registers, and save the ones we were able to get.
// Some of them may be inaccessible, I think because they weren't defined by that
// stack frame, which can show up as a -UNW_EBADREG return code.
......@@ -457,41 +463,213 @@ void unwindPythonStack(std::function<bool(std::unique_ptr<PythonFrameIteratorImp
if (!assembler::Register::fromDwarf(i).isCalleeSave())
continue;
unw_word_t r;
int code = unw_get_reg(&cursor, i, &r);
int code = unw_get_reg(cursor, i, &r);
ASSERT(code == 0 || code == -UNW_EBADREG, "%d %d", code, i);
if (code == 0) {
info->regs[i] = r;
info->regs_valid |= (1 << i);
}
}
}
bool stop = func(std::move(info));
if (stop)
break;
return true;
}
class PythonUnwindSession : public Box {
ExcInfo exc_info;
bool skip;
bool is_active;
Timer t;
public:
DEFAULT_CLASS_SIMPLE(unwind_session_cls);
PythonUnwindSession() : exc_info(NULL, NULL, NULL), skip(false), is_active(false), t(/*min_usec=*/10000) {}
ExcInfo* getExcInfoStorage() {
RELEASE_ASSERT(is_active, "");
return &exc_info;
}
was_osr = (bool)cf->entry_descriptor;
continue;
bool shouldSkipFrame() const { return skip; }
void setShouldSkipNextFrame(bool skip) { this->skip = skip; }
bool isActive() const { return is_active; }
void begin() {
RELEASE_ASSERT(!is_active, "");
exc_info = ExcInfo(NULL, NULL, NULL);
skip = false;
is_active = true;
t.restart();
static StatCounter stat("unwind_sessions");
stat.log();
}
void end() {
RELEASE_ASSERT(is_active, "");
is_active = false;
static StatCounter stat("us_unwind_session");
stat.log(t.end());
}
void addTraceback(const LineInfo& line_info) {
RELEASE_ASSERT(is_active, "");
if (exc_info.reraise) {
exc_info.reraise = false;
return;
}
BoxedTraceback::here(line_info, &exc_info.traceback);
}
void logException() {
#if STAT_EXCEPTIONS
static StatCounter num_exceptions("num_exceptions");
num_exceptions.log();
std::string stat_name;
if (PyType_Check(exc_info.type))
stat_name = "num_exceptions_" + std::string(static_cast<BoxedClass*>(exc_info.type)->tp_name);
else
stat_name = "num_exceptions_" + std::string(exc_info.value->cls->tp_name);
Stats::log(Stats::getStatCounter(stat_name));
#if STAT_EXCEPTIONS_LOCATION
logByCurrentPythonLine(stat_name);
#endif
#endif
}
static void gcHandler(GCVisitor* v, Box* _o) {
assert(_o->cls == unwind_session_cls);
PythonUnwindSession* o = static_cast<PythonUnwindSession*>(_o);
// this is our hack for eventually collecting
// exceptions/tracebacks after the exception has been caught.
// If a collection happens and a given thread's
// PythonUnwindSession isn't active, its exception info can be
// collected.
if (!o->is_active)
return;
v->visitIf(o->exc_info.type);
v->visitIf(o->exc_info.value);
v->visitIf(o->exc_info.traceback);
}
};
static __thread PythonUnwindSession* cur_unwind;
PythonUnwindSession* beginPythonUnwindSession() {
if (!cur_unwind) {
cur_unwind = new PythonUnwindSession();
pyston::gc::registerPermanentRoot(cur_unwind);
}
cur_unwind->begin();
return cur_unwind;
}
PythonUnwindSession* getActivePythonUnwindSession() {
RELEASE_ASSERT(cur_unwind && cur_unwind->isActive(), "");
return cur_unwind;
}
if ((unw_word_t)interpreter_instr_addr <= ip && ip < interpreter_instr_end) {
std::unique_ptr<PythonFrameIteratorImpl> info(new PythonFrameIteratorImpl());
info->id.type = PythonFrameId::INTERPRETED;
info->id.ip = ip;
info->id.bp = bp;
cf = info->cf = getCFForInterpretedFrame((void*)bp);
void endPythonUnwindSession(PythonUnwindSession* unwind) {
RELEASE_ASSERT(unwind && unwind == cur_unwind, "");
unwind->end();
}
void* getPythonUnwindSessionExceptionStorage(PythonUnwindSession* unwind) {
RELEASE_ASSERT(unwind && unwind == cur_unwind, "");
PythonUnwindSession* state = static_cast<PythonUnwindSession*>(unwind);
return state->getExcInfoStorage();
}
void throwingException(PythonUnwindSession* unwind) {
RELEASE_ASSERT(unwind && unwind == cur_unwind, "");
unwind->logException();
}
static const LineInfo lineInfoForFrame(PythonFrameIteratorImpl* frame_it) {
AST_stmt* current_stmt = frame_it->getCurrentStatement();
auto* cf = frame_it->getCF();
assert(cf);
if (!was_osr) {
bool stop = func(std::move(info));
if (stop)
break;
auto source = cf->clfunc->source.get();
return LineInfo(current_stmt->lineno, current_stmt->col_offset, source->fn, source->getName());
}
void exceptionCaughtInInterpreter(LineInfo line_info, ExcInfo* exc_info) {
// basically the same as PythonUnwindSession::addTraceback, but needs to
// be callable after an PythonUnwindSession has ended. The interpreter
// will call this from catch blocks if it needs to ensure that a
// line is added. Right now this only happens in
// ASTInterpreter::visit_invoke.
// It's basically the same except for one thing: we don't have to
// worry about the 'skip' (osr) state that PythonUnwindSession handles
// here, because the only way we could have gotten into the ast
// interpreter is if the exception wasn't caught, and if there was
// the osr frame for the one the interpreter is running, it would
// have already caught it.
if (exc_info->reraise) {
exc_info->reraise = false;
return;
}
BoxedTraceback::here(line_info, &exc_info->traceback);
}
was_osr = (bool)cf->entry_descriptor;
continue;
void unwindingThroughFrame(PythonUnwindSession* unwind_session, unw_cursor_t* cursor) {
unw_word_t ip = get_cursor_ip(cursor);
unw_word_t bp = get_cursor_bp(cursor);
PythonFrameIteratorImpl frame_iter;
if (frameIsPythonFrame(ip, bp, cursor, &frame_iter)) {
if (!unwind_session->shouldSkipFrame())
unwind_session->addTraceback(lineInfoForFrame(&frame_iter));
// frame_iter->cf->entry_descriptor will be non-null for OSR frames.
unwind_session->setShouldSkipNextFrame((bool)frame_iter.cf->entry_descriptor);
}
}
// While I'm not a huge fan of the callback-passing style, libunwind cursors are only valid for
// the stack frame that they were created in, so we need to use this approach (as opposed to
// C++11 range loops, for example).
// Return true from the handler to stop iteration at that frame.
template <typename Func> void unwindPythonStack(Func func) {
PythonUnwindSession* unwind_session = new PythonUnwindSession();
unwind_session->begin();
if ((unw_word_t)generatorEntry <= ip && ip < generator_entry_end) {
unw_context_t ctx;
unw_cursor_t cursor;
unw_getcontext(&ctx);
unw_init_local(&cursor, &ctx);
while (true) {
int r = unw_step(&cursor);
assert(r >= 0);
if (r == 0)
break;
unw_word_t ip = get_cursor_ip(&cursor);
unw_word_t bp = get_cursor_bp(&cursor);
bool stop_unwinding = false;
PythonFrameIteratorImpl frame_iter;
if (frameIsPythonFrame(ip, bp, &cursor, &frame_iter)) {
if (!unwind_session->shouldSkipFrame())
stop_unwinding = func(&frame_iter);
// frame_iter->cf->entry_descriptor will be non-null for OSR frames.
unwind_session->setShouldSkipNextFrame((bool)frame_iter.cf->entry_descriptor);
}
if (stop_unwinding)
break;
if (inGeneratorEntry(ip)) {
// for generators continue unwinding in the context in which the generator got called
Context* remote_ctx = getReturnContextForGeneratorFrame((void*)bp);
// setup unw_context_t struct from the infos we have, seems like this is enough to make unwinding work.
......@@ -509,28 +687,20 @@ void unwindPythonStack(std::function<bool(std::unique_ptr<PythonFrameIteratorImp
// keep unwinding
}
unwind_session->end();
}
static std::unique_ptr<PythonFrameIteratorImpl> getTopPythonFrame() {
STAT_TIMER(t0, "us_timer_getTopPythonFrame");
std::unique_ptr<PythonFrameIteratorImpl> rtn(nullptr);
unwindPythonStack([&](std::unique_ptr<PythonFrameIteratorImpl> iter) {
rtn = std::move(iter);
unwindPythonStack([&](PythonFrameIteratorImpl* iter) {
rtn = std::unique_ptr<PythonFrameIteratorImpl>(new PythonFrameIteratorImpl(*iter));
return true;
});
return rtn;
}
static const LineInfo* lineInfoForFrame(PythonFrameIteratorImpl& frame_it) {
AST_stmt* current_stmt = frame_it.getCurrentStatement();
auto* cf = frame_it.getCF();
assert(cf);
auto source = cf->clfunc->source.get();
return new LineInfo(current_stmt->lineno, current_stmt->col_offset, source->fn, source->getName());
}
// To produce a traceback, we:
//
// 1. Use libunwind to produce a cursor into our stack.
......@@ -584,20 +754,16 @@ BoxedTraceback* getTraceback() {
Timer _t("getTraceback", 1000);
std::vector<const LineInfo*> entries;
unwindPythonStack([&](std::unique_ptr<PythonFrameIteratorImpl> frame_iter) {
const LineInfo* line_info = lineInfoForFrame(*frame_iter.get());
if (line_info)
entries.push_back(line_info);
Box* tb = new BoxedTraceback();
unwindPythonStack([&](PythonFrameIteratorImpl* frame_iter) {
BoxedTraceback::here(lineInfoForFrame(frame_iter), &tb);
return false;
});
std::reverse(entries.begin(), entries.end());
long us = _t.end();
us_gettraceback.log(us);
return new BoxedTraceback(std::move(entries));
return static_cast<BoxedTraceback*>(tb);
}
ExcInfo* getFrameExcInfo() {
......@@ -605,7 +771,7 @@ ExcInfo* getFrameExcInfo() {
ExcInfo* copy_from_exc = NULL;
ExcInfo* cur_exc = NULL;
unwindPythonStack([&](std::unique_ptr<PythonFrameIteratorImpl> frame_iter) {
unwindPythonStack([&](PythonFrameIteratorImpl* frame_iter) {
FrameInfo* frame_info = frame_iter->getFrameInfo();
copy_from_exc = &frame_info->exc;
......@@ -665,9 +831,9 @@ BoxedModule* getCurrentModule() {
PythonFrameIterator getPythonFrame(int depth) {
std::unique_ptr<PythonFrameIteratorImpl> rtn(nullptr);
unwindPythonStack([&](std::unique_ptr<PythonFrameIteratorImpl> frame_iter) {
unwindPythonStack([&](PythonFrameIteratorImpl* frame_iter) {
if (depth == 0) {
rtn = std::move(frame_iter);
rtn = std::unique_ptr<PythonFrameIteratorImpl>(new PythonFrameIteratorImpl(*frame_iter));
return true;
}
depth--;
......@@ -691,13 +857,13 @@ PythonFrameIterator::PythonFrameIterator(std::unique_ptr<PythonFrameIteratorImpl
std::swap(this->impl, impl);
}
// TODO factor getStackLoclasIncludingUserHidden and fastLocalsToBoxedLocals
// TODO factor getStackLocalsIncludingUserHidden and fastLocalsToBoxedLocals
// because they are pretty ugly but have a pretty repetitive pattern.
FrameStackState getFrameStackState() {
FrameStackState rtn(NULL, NULL);
bool found = false;
unwindPythonStack([&](std::unique_ptr<PythonFrameIteratorImpl> frame_iter) {
unwindPythonStack([&](PythonFrameIteratorImpl* frame_iter) {
BoxedDict* d;
BoxedClosure* closure;
CompiledFunction* cf;
......@@ -957,9 +1123,9 @@ FrameInfo* PythonFrameIterator::getFrameInfo() {
PythonFrameIterator PythonFrameIterator::getCurrentVersion() {
std::unique_ptr<PythonFrameIteratorImpl> rtn(nullptr);
auto& impl = this->impl;
unwindPythonStack([&](std::unique_ptr<PythonFrameIteratorImpl> frame_iter) {
unwindPythonStack([&](PythonFrameIteratorImpl* frame_iter) {
if (frame_iter->pointsToTheSameAs(*impl.get())) {
rtn = std::move(frame_iter);
rtn = std::unique_ptr<PythonFrameIteratorImpl>(new PythonFrameIteratorImpl(*frame_iter));
return true;
}
return false;
......@@ -975,9 +1141,9 @@ PythonFrameIterator PythonFrameIterator::back() {
std::unique_ptr<PythonFrameIteratorImpl> rtn(nullptr);
auto& impl = this->impl;
bool found = false;
unwindPythonStack([&](std::unique_ptr<PythonFrameIteratorImpl> frame_iter) {
unwindPythonStack([&](PythonFrameIteratorImpl* frame_iter) {
if (found) {
rtn = std::move(frame_iter);
rtn = std::unique_ptr<PythonFrameIteratorImpl>(new PythonFrameIteratorImpl(*frame_iter));
return true;
}
......@@ -1015,4 +1181,10 @@ void logByCurrentPythonLine(const std::string& stat_name) {
llvm::JITEventListener* makeTracebacksListener() {
return new TracebacksEventListener();
}
void setupUnwinding() {
unwind_session_cls = BoxedHeapClass::create(type_cls, object_cls, PythonUnwindSession::gcHandler, 0, 0,
sizeof(PythonUnwindSession), false, "unwind_session");
unwind_session_cls->freeze();
}
}
......@@ -19,6 +19,10 @@
#include "codegen/codegen.h"
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#undef UNW_LOCAL_ONLY
namespace pyston {
class Box;
......@@ -29,6 +33,7 @@ struct FrameInfo;
void registerDynamicEhFrame(uint64_t code_addr, size_t code_size, uint64_t eh_frame_addr, size_t eh_frame_size);
void setupUnwinding();
BoxedModule* getCurrentModule();
Box* getGlobals(); // returns either the module or a globals dict
Box* getGlobalsDict(); // always returns a dict-like object
......@@ -36,6 +41,16 @@ CompiledFunction* getCFForAddress(uint64_t addr);
BoxedTraceback* getTraceback();
class PythonUnwindSession;
PythonUnwindSession* beginPythonUnwindSession();
PythonUnwindSession* getActivePythonUnwindSession();
void throwingException(PythonUnwindSession* unwind_session);
void endPythonUnwindSession(PythonUnwindSession* unwind_session);
void* getPythonUnwindSessionExceptionStorage(PythonUnwindSession* unwind_session);
void unwindingThroughFrame(PythonUnwindSession* unwind_session, unw_cursor_t* cursor);
void exceptionCaughtInInterpreter(LineInfo line_info, ExcInfo* exc_info);
struct ExecutionPoint {
CompiledFunction* cf;
AST_stmt* current_stmt;
......
......@@ -88,6 +88,10 @@ public:
GCVisitor(TraceStack* stack) : stack(stack) {}
// These all work on *user* pointers, ie pointers to the user_data section of GCAllocations
void visitIf(void* p) {
if (p)
visit(p);
}
void visit(void* p);
void visitRange(void* const* start, void* const* end);
void visitPotential(void* p);
......@@ -666,7 +670,7 @@ void raiseSyntaxErrorHelper(llvm::StringRef file, llvm::StringRef func, AST* nod
struct LineInfo {
public:
const int line, column;
int line, column;
std::string file, func;
LineInfo(int line, int column, llvm::StringRef file, llvm::StringRef func)
......@@ -675,11 +679,12 @@ public:
struct ExcInfo {
Box* type, *value, *traceback;
bool reraise;
#ifndef NDEBUG
ExcInfo(Box* type, Box* value, Box* traceback);
#else
ExcInfo(Box* type, Box* value, Box* traceback) : type(type), value(value), traceback(traceback) {}
ExcInfo(Box* type, Box* value, Box* traceback) : type(type), value(value), traceback(traceback), reraise(false) {}
#endif
bool matches(BoxedClass* cls) const;
void printExcAndTraceback() const;
......
......@@ -926,7 +926,7 @@ void checkAndThrowCAPIException() {
RELEASE_ASSERT(value->cls == type, "unsupported");
if (tb != None)
raiseRaw(ExcInfo(value->cls, value, tb));
throw ExcInfo(value->cls, value, tb);
raiseExc(value);
}
}
......
......@@ -162,7 +162,7 @@ static Box* classobjGetattribute(Box* _cls, Box* _attr) {
}
}
Box* r = classLookup(cls, std::string(attr->s()));
Box* r = classLookup(cls, attr->s());
if (!r)
raiseExcHelper(AttributeError, "class %s has no attribute '%s'", cls->name->data(), attr->data());
......
......@@ -27,6 +27,7 @@
#include "core/types.h" // for ExcInfo
#include "core/util.h" // Timer
#include "runtime/generator.h" // generatorEntry
#include "runtime/traceback.h" // BoxedTraceback::addLine
#define UNW_LOCAL_ONLY
#include <libunwind.h>
......@@ -35,9 +36,6 @@
#define NORETURN __attribute__((__noreturn__))
// canary used in ExcData in debug mode to catch exception-value corruption.
#define CANARY_VALUE 0xdeadbeef
// An action of 0 in the LSDA action table indicates cleanup.
#define CLEANUP_ACTION 0
......@@ -73,42 +71,13 @@ template <typename T> static inline void check(T x) {
namespace pyston {
struct ExcData;
extern thread_local ExcData exception_ferry;
struct ExcData {
ExcInfo exc;
#ifndef NDEBUG
unsigned canary = CANARY_VALUE;
#endif
ExcData() : exc(nullptr, nullptr, nullptr) {}
ExcData(ExcInfo e) : exc(e) {}
ExcData(Box* type, Box* value, Box* traceback) : exc(type, value, traceback) {}
void check() const {
assert(this);
assert(canary == CANARY_VALUE);
assert(exc.type && exc.value && exc.traceback);
ASSERT(gc::isValidGCObject(exc.type), "%p", exc.type);
ASSERT(gc::isValidGCObject(exc.value), "%p", exc.value);
ASSERT(gc::isValidGCObject(exc.traceback), "%p", exc.traceback);
assert(this == &exception_ferry);
}
};
thread_local ExcData exception_ferry;
static_assert(offsetof(ExcData, exc) == 0, "wrong offset");
// Timer that auto-logs.
struct LogTimer {
StatCounter& counter;
Timer timer;
LogTimer(const char* desc, StatCounter& ctr, long min_usec = -1) : counter(ctr), timer(desc, min_usec) {}
~LogTimer() { counter.log(timer.end()); }
};
void checkExcInfo(const ExcInfo* exc) {
assert(exc);
assert(exc->type && exc->value && exc->traceback);
ASSERT(gc::isValidGCObject(exc->type), "%p", exc->type);
ASSERT(gc::isValidGCObject(exc->value), "%p", exc->value);
ASSERT(gc::isValidGCObject(exc->traceback), "%p", exc->traceback);
}
static StatCounter us_unwind_loop("us_unwind_loop");
static StatCounter us_unwind_resume_catch("us_unwind_resume_catch");
......@@ -422,8 +391,8 @@ static inline bool find_call_site_entry(const lsda_info_t* info, const uint8_t*
}
static inline NORETURN void resume(unw_cursor_t* cursor, const uint8_t* landing_pad, int64_t switch_value,
const ExcData* exc_data) {
exc_data->check();
const ExcInfo* exc_data) {
checkExcInfo(exc_data);
assert(landing_pad);
if (VERBOSITY("cxx_unwind") >= 4)
printf(" * RESUMED: ip %p switch_value %ld\n", (const void*)landing_pad, (long)switch_value);
......@@ -508,16 +477,8 @@ static inline int64_t determine_action(const lsda_info_t* info, const call_site_
RELEASE_ASSERT(0, "action chain exhausted and no cleanup indicated");
}
static inline int step(unw_cursor_t* cp) {
LogTimer t("unw_step", us_unwind_step, 5);
return unw_step(cp);
}
// The stack-unwinding loop.
// TODO: integrate incremental traceback generation into this function
static inline void unwind_loop(const ExcData* exc_data) {
Timer t("unwind_loop", 50);
static inline void unwind_loop(ExcInfo* exc_data) {
// NB. https://monoinfinito.wordpress.com/series/exception-handling-in-c/ is a very useful resource
// as are http://www.airs.com/blog/archives/460 and http://www.airs.com/blog/archives/464
unw_cursor_t cursor;
......@@ -530,13 +491,14 @@ static inline void unwind_loop(const ExcData* exc_data) {
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
while (step(&cursor) > 0) {
auto unwind_session = getActivePythonUnwindSession();
while (unw_step(&cursor) > 0) {
unw_proc_info_t pip;
{
// NB. unw_get_proc_info is slow; a significant chunk of all time spent unwinding is spent here.
LogTimer t_procinfo("get_proc_info", us_unwind_get_proc_info, 10);
check(unw_get_proc_info(&cursor, &pip));
}
assert((pip.lsda == 0) == (pip.handler == 0));
assert(pip.flags == 0);
......@@ -544,6 +506,11 @@ static inline void unwind_loop(const ExcData* exc_data) {
print_frame(&cursor, &pip);
}
// let the PythonUnwindSession know that we're in a new frame,
// giving it a chance to possibly add a traceback entry for
// it.
unwindingThroughFrame(unwind_session, &cursor);
// Skip frames without handlers
if (pip.handler == 0) {
continue;
......@@ -560,8 +527,6 @@ static inline void unwind_loop(const ExcData* exc_data) {
call_site_entry_t entry;
{
LogTimer t_call_site("find_call_site_entry", us_unwind_find_call_site_entry, 10);
// 2. Find our current IP in the call site table.
unw_word_t ip;
unw_get_reg(&cursor, UNW_REG_IP, &ip);
......@@ -592,18 +557,35 @@ static inline void unwind_loop(const ExcData* exc_data) {
}
int64_t switch_value = determine_action(&info, &entry);
us_unwind_loop.log(t.end());
if (switch_value != CLEANUP_ACTION) {
// we're transfering control to a non-cleanup landing pad.
// i.e. a catch block. thus ends our unwind session.
endPythonUnwindSession(unwind_session);
}
static_assert(THREADING_USE_GIL, "have to make the unwind session usage in this file thread safe!");
// there is a python unwinding implementation detail leaked
// here - that the unwind session can be ended but its
// exception storage is still around.
//
// this manifests itself as this short window here where we've
// (possibly) ended the unwind session above but we still need
// to pass exc_data (which is the exceptionStorage for this
// unwind session) to resume().
//
// the only way this could bite us is if we somehow clobber
// the PythonUnwindSession's storage, or cause a GC to occur, before
// transfering control to the landing pad in resume().
//
resume(&cursor, entry.landing_pad, switch_value, exc_data);
}
us_unwind_loop.log(t.end());
// Hit end of stack! return & let unwindException determine what to do.
}
// The unwinder entry-point.
static void unwind(const ExcData* exc) {
exc->check();
static void unwind(ExcInfo* exc) {
checkExcInfo(exc);
unwind_loop(exc);
// unwind_loop returned, couldn't find any handler. ruh-roh.
panic();
......@@ -637,7 +619,7 @@ extern "C" void _Unwind_Resume(struct _Unwind_Exception* _exc) {
if (VERBOSITY("cxx_unwind") >= 4)
printf("***** _Unwind_Resume() *****\n");
// we give `_exc' type `struct _Unwind_Exception*' because unwind.h demands it; it's not actually accurate
const pyston::ExcData* data = (const pyston::ExcData*)_exc;
pyston::ExcInfo* data = (pyston::ExcInfo*)_exc;
pyston::unwind(data);
}
......@@ -648,45 +630,24 @@ extern "C" void* __cxa_allocate_exception(size_t size) noexcept {
// we should only ever be throwing ExcInfos
RELEASE_ASSERT(size == sizeof(pyston::ExcInfo), "allocating exception whose size doesn't match ExcInfo");
// Instead of allocating memory for this exception, we return a pointer to a pre-allocated thread-local variable.
//
// This variable, pyston::exception_ferry, is used only while we are unwinding, and should not be used outside of
// the unwinder. Since it's a thread-local variable, we *cannot* throw any exceptions while it is live, otherwise we
// would clobber it and forget our old exception.
//
// Q: Why can't we just use cur_thread_state.curexc_{type,value,traceback}?
//
// A: Because that conflates the space used to store exceptions during C++ unwinding with the space used to store
// them during C-API return-code based unwinding! This actually comes up in practice - the original version *did*
// use curexc_{type,value,traceback}, and it had a bug.
//
// In particular, we need to unset the C API exception at an appropriate point so as not to make C-API functions
// *think* an exception is being thrown when one isn't. The natural place is __cxa_begin_catch, BUT we need some way
// to communicate the exception info to the inside of the catch block - and all we get is the return value of
// __cxa_begin_catch, which is a single pointer, when we need three!
//
// You might think we could get away with only unsetting the C-API information in __cxa_end_catch, but you'd be
// wrong! Firstly, this would prohibit calling C-API functions inside a catch-block. Secondly, __cxa_end_catch is
// always called when leaving a catch block, even if we're leaving it by re-raising the exception. So if we store
// our exception info in curexc_*, and then unset these in __cxa_end_catch, then we'll wipe our exception info
// during unwinding!
return (void*)&pyston::exception_ferry;
// we begin the unwind session here rather than in __cxa_throw
// because we need to return the session's exception storage
// from this method.
return pyston::getPythonUnwindSessionExceptionStorage(pyston::beginPythonUnwindSession());
}
// Takes the value that resume() sent us in RAX, and returns a pointer to the exception object actually thrown. In our
// case, these are the same, and should always be &pyston::exception_ferry.
extern "C" void* __cxa_begin_catch(void* exc_obj_in) noexcept {
pyston::gc::endGCUnexpectedRegion();
assert(exc_obj_in);
pyston::us_unwind_resume_catch.log(pyston::per_thread_resume_catch_timer.end());
if (VERBOSITY("cxx_unwind") >= 4)
printf("***** __cxa_begin_catch() *****\n");
pyston::ExcData* e = (pyston::ExcData*)exc_obj_in;
e->check();
return (void*)&e->exc;
pyston::ExcInfo* e = (pyston::ExcInfo*)exc_obj_in;
checkExcInfo(e);
return e;
}
extern "C" void __cxa_end_catch() {
......@@ -700,7 +661,6 @@ extern "C" void __cxa_end_catch() {
extern "C" std::type_info EXCINFO_TYPE_INFO;
extern "C" void __cxa_throw(void* exc_obj, std::type_info* tinfo, void (*dtor)(void*)) {
pyston::gc::startGCUnexpectedRegion();
assert(!pyston::in_cleanup_code);
assert(exc_obj);
RELEASE_ASSERT(tinfo == &EXCINFO_TYPE_INFO, "can't throw a non-ExcInfo value! type info: %p", tinfo);
......@@ -708,16 +668,19 @@ extern "C" void __cxa_throw(void* exc_obj, std::type_info* tinfo, void (*dtor)(v
if (VERBOSITY("cxx_unwind") >= 4)
printf("***** __cxa_throw() *****\n");
const pyston::ExcData* exc_data = (const pyston::ExcData*)exc_obj;
exc_data->check();
pyston::ExcInfo* exc_data = (pyston::ExcInfo*)exc_obj;
checkExcInfo(exc_data);
// let unwinding.cpp know we've started unwinding
pyston::throwingException(pyston::getActivePythonUnwindSession());
pyston::unwind(exc_data);
}
extern "C" void* __cxa_get_exception_ptr(void* exc_obj_in) noexcept {
assert(exc_obj_in);
pyston::ExcData* e = (pyston::ExcData*)exc_obj_in;
e->check();
return (void*)&e->exc;
pyston::ExcInfo* e = (pyston::ExcInfo*)exc_obj_in;
checkExcInfo(e);
return e;
}
// We deliberately don't implement rethrowing because we can't implement it correctly with our current strategy for
......
......@@ -155,7 +155,7 @@ static void generatorSendInternal(BoxedGenerator* self, Box* v) {
freeGeneratorStack(self);
// don't raise StopIteration exceptions because those are handled specially.
if (!self->exception.matches(StopIteration))
raiseRaw(self->exception);
throw self->exception;
return;
}
......@@ -193,7 +193,7 @@ Box* generatorSend(Box* s, Box* v) {
self->exception = ExcInfo(nullptr, nullptr, nullptr);
if (old_exc.type == NULL)
raiseExcHelper(StopIteration, (const char*)nullptr);
raiseRaw(old_exc);
throw old_exc;
}
return self->returnValue;
......@@ -272,7 +272,7 @@ extern "C" Box* yield(BoxedGenerator* obj, Box* value) {
if (self->exception.type) {
ExcInfo e = self->exception;
self->exception = ExcInfo(NULL, NULL, NULL);
raiseRaw(e);
throw e;
}
return self->returnValue;
}
......
......@@ -49,7 +49,7 @@ Box* createAndRunModule(const std::string& name, const std::string& fn) {
compileAndRunModule(ast, module);
} catch (ExcInfo e) {
removeModule(name);
raiseRaw(e);
throw e;
}
Box* r = getSysModulesDict()->getOrNull(boxString(name));
......@@ -74,7 +74,7 @@ static Box* createAndRunModule(const std::string& name, const std::string& fn, c
compileAndRunModule(ast, module);
} catch (ExcInfo e) {
removeModule(name);
raiseRaw(e);
throw e;
}
Box* r = getSysModulesDict()->getOrNull(boxString(name));
......@@ -223,7 +223,7 @@ SearchResult findModule(const std::string& name, const std::string& full_name, B
BoxedString* p = static_cast<BoxedString*>(_p);
joined_path.clear();
llvm::sys::path::append(joined_path, std::string(p->s()), name);
llvm::sys::path::append(joined_path, p->s(), name);
std::string dn(joined_path.str());
llvm::sys::path::append(joined_path, "__init__.py");
......@@ -406,7 +406,7 @@ static Box* importSub(const std::string& name, const std::string& full_name, Box
RELEASE_ASSERT(0, "%d", sr.type);
} catch (ExcInfo e) {
removeModule(name);
raiseRaw(e);
throw e;
}
if (parent_module && parent_module != None)
......@@ -560,7 +560,7 @@ static void ensureFromlist(Box* module, Box* fromlist, std::string& buf, bool re
pathlist = getattrInternal(module, path_str, NULL);
} catch (ExcInfo e) {
if (!e.matches(AttributeError))
raiseRaw(e);
throw e;
}
if (pathlist == NULL) {
......
......@@ -727,7 +727,7 @@ void listSort(BoxedList* self, Box* cmp, Box* key, Box* reverse) {
std::stable_sort<Box**, PyLt>(self->elts->elts, self->elts->elts + self->size, PyLt());
} catch (ExcInfo e) {
remove_keys();
raiseRaw(e);
throw e;
}
remove_keys();
......
......@@ -36,7 +36,6 @@ ExcInfo excInfoForRaise(Box*, Box*, Box*);
extern "C" void raise0() __attribute__((__noreturn__));
extern "C" void raise3(Box*, Box*, Box*) __attribute__((__noreturn__));
void raiseExc(Box* exc_obj) __attribute__((__noreturn__));
void raiseRaw(const ExcInfo& e) __attribute__((__noreturn__));
void _printStacktrace();
extern "C" Box* deopt(AST_expr* expr, Box* value);
......
......@@ -46,37 +46,8 @@ void showBacktrace() {
}
}
void raiseRaw(const ExcInfo& e) __attribute__((__noreturn__));
void raiseRaw(const ExcInfo& e) {
STAT_TIMER(t0, "us_timer_raiseraw");
// Should set these to None rather than null before getting here:
assert(e.type);
assert(e.value);
assert(e.traceback);
assert(gc::isValidGCObject(e.type));
assert(gc::isValidGCObject(e.value));
assert(gc::isValidGCObject(e.traceback));
#if STAT_EXCEPTIONS
static StatCounter num_exceptions("num_exceptions");
num_exceptions.log();
std::string stat_name;
if (PyType_Check(e.type))
stat_name = "num_exceptions_" + std::string(static_cast<BoxedClass*>(e.type)->tp_name);
else
stat_name = "num_exceptions_" + std::string(e.value->cls->tp_name);
Stats::log(Stats::getStatCounter(stat_name));
#if STAT_EXCEPTIONS_LOCATION
logByCurrentPythonLine(stat_name);
#endif
#endif
throw e;
}
void raiseExc(Box* exc_obj) {
raiseRaw(ExcInfo(exc_obj->cls, exc_obj, getTraceback()));
throw ExcInfo(exc_obj->cls, exc_obj, new BoxedTraceback());
}
// Have a special helper function for syntax errors, since we want to include the location
......@@ -84,10 +55,8 @@ void raiseExc(Box* exc_obj) {
void raiseSyntaxError(const char* msg, int lineno, int col_offset, llvm::StringRef file, llvm::StringRef func) {
Box* exc = runtimeCall(SyntaxError, ArgPassSpec(1), boxString(msg), NULL, NULL, NULL, NULL);
auto tb = getTraceback();
std::vector<const LineInfo*> entries = tb->lines;
entries.push_back(new LineInfo(lineno, col_offset, file, func));
raiseRaw(ExcInfo(exc->cls, exc, new BoxedTraceback(std::move(entries))));
auto tb = new BoxedTraceback(LineInfo(lineno, col_offset, file, func), None);
throw ExcInfo(exc->cls, exc, tb);
}
void raiseSyntaxErrorHelper(llvm::StringRef file, llvm::StringRef func, AST* node_at, const char* msg, ...) {
......@@ -205,11 +174,13 @@ extern "C" void raise0() {
if (exc_info->type == None)
raiseExcHelper(TypeError, "exceptions must be old-style classes or derived from BaseException, not NoneType");
raiseRaw(*exc_info);
exc_info->reraise = true;
throw * exc_info;
}
#ifndef NDEBUG
ExcInfo::ExcInfo(Box* type, Box* value, Box* traceback) : type(type), value(value), traceback(traceback) {
ExcInfo::ExcInfo(Box* type, Box* value, Box* traceback)
: type(type), value(value), traceback(traceback), reraise(false) {
}
#endif
......@@ -229,8 +200,12 @@ ExcInfo excInfoForRaise(Box* type, Box* value, Box* tb) {
assert(type && value && tb); // use None for default behavior, not nullptr
// TODO switch this to PyErr_Normalize
if (tb == None)
tb = getTraceback();
if (tb == None) {
tb = NULL;
} else if (tb != NULL && !PyTraceBack_Check(tb)) {
raiseExcHelper(TypeError, "raise: arg 3 must be a traceback or None");
}
/* Next, repeatedly, replace a tuple exception with its first item */
while (PyTuple_Check(type) && PyTuple_Size(type) > 0) {
......@@ -242,6 +217,7 @@ ExcInfo excInfoForRaise(Box* type, Box* value, Box* tb) {
if (PyExceptionClass_Check(type)) {
PyErr_NormalizeException(&type, &value, &tb);
if (!PyExceptionInstance_Check(value)) {
raiseExcHelper(TypeError, "calling %s() should have returned an instance of "
"BaseException, not '%s'",
......@@ -268,11 +244,19 @@ ExcInfo excInfoForRaise(Box* type, Box* value, Box* tb) {
assert(PyExceptionClass_Check(type));
if (tb == NULL) {
tb = new BoxedTraceback();
}
return ExcInfo(type, value, tb);
}
extern "C" void raise3(Box* arg0, Box* arg1, Box* arg2) {
raiseRaw(excInfoForRaise(arg0, arg1, arg2));
bool reraise = arg2 != NULL && arg2 != None;
auto exc_info = excInfoForRaise(arg0, arg1, arg2);
exc_info.reraise = reraise;
throw exc_info;
}
void raiseExcHelper(BoxedClass* cls, Box* arg) {
......
......@@ -128,7 +128,7 @@ Box* superGetattribute(Box* _s, Box* _attr) {
}
}
Box* r = typeLookup(s->cls, std::string(attr->s()), NULL);
Box* r = typeLookup(s->cls, attr->s(), NULL);
// TODO implement this
RELEASE_ASSERT(r, "should call the equivalent of objectGetattr here");
return processDescriptor(r, s, s->cls);
......
......@@ -24,6 +24,7 @@
#include "core/stats.h"
#include "core/types.h"
#include "gc/collector.h"
#include "runtime/list.h"
#include "runtime/objmodel.h"
#include "runtime/types.h"
#include "runtime/util.h"
......@@ -40,6 +41,8 @@ void BoxedTraceback::gcHandler(GCVisitor* v, Box* b) {
if (self->py_lines)
v->visit(self->py_lines);
if (self->tb_next)
v->visit(self->tb_next);
boxGCHandler(v, b);
}
......@@ -53,16 +56,17 @@ void printTraceback(Box* b) {
fprintf(stderr, "Traceback (most recent call last):\n");
for (auto line : tb->lines) {
fprintf(stderr, " File \"%s\", line %d, in %s:\n", line->file.c_str(), line->line, line->func.c_str());
for (; tb && tb != None; tb = static_cast<BoxedTraceback*>(tb->tb_next)) {
auto& line = tb->line;
fprintf(stderr, " File \"%s\", line %d, in %s:\n", line.file.c_str(), line.line, line.func.c_str());
if (line->line < 0)
if (line.line < 0)
continue;
FILE* f = fopen(line->file.c_str(), "r");
FILE* f = fopen(line.file.c_str(), "r");
if (f) {
assert(line->line < 10000000 && "Refusing to try to seek that many lines forward");
for (int i = 1; i < line->line; i++) {
assert(line.line < 10000000 && "Refusing to try to seek that many lines forward");
for (int i = 1; i < line.line; i++) {
char* buf = NULL;
size_t size;
size_t r = getline(&buf, &size, f);
......@@ -97,17 +101,23 @@ Box* BoxedTraceback::getLines(Box* b) {
if (!tb->py_lines) {
BoxedList* lines = new BoxedList();
lines->ensure(tb->lines.size());
for (auto line : tb->lines) {
auto l = BoxedTuple::create({ boxString(line->file), boxString(line->func), boxInt(line->line) });
for (BoxedTraceback* wtb = tb; wtb && wtb != None; wtb = static_cast<BoxedTraceback*>(wtb->tb_next)) {
if (wtb->has_line) {
auto& line = wtb->line;
auto l = BoxedTuple::create({ boxString(line.file), boxString(line.func), boxInt(line.line) });
listAppendInternal(lines, l);
}
}
tb->py_lines = lines;
}
return tb->py_lines;
}
void BoxedTraceback::here(LineInfo lineInfo, Box** tb) {
*tb = new BoxedTraceback(lineInfo, *tb);
}
void setupTraceback() {
traceback_cls = BoxedHeapClass::create(type_cls, object_cls, BoxedTraceback::gcHandler, 0, 0,
sizeof(BoxedTraceback), false, "traceback");
......
......@@ -27,19 +27,22 @@ class GCVisitor;
extern "C" BoxedClass* traceback_cls;
class BoxedTraceback : public Box {
public:
std::vector<const LineInfo*> lines;
Box* tb_next;
bool has_line;
LineInfo line;
Box* py_lines;
BoxedTraceback(std::vector<const LineInfo*> lines) : lines(std::move(lines)), py_lines(NULL) {}
BoxedTraceback() : py_lines(NULL) {}
BoxedTraceback(LineInfo line, Box* tb_next) : tb_next(tb_next), has_line(true), line(line), py_lines(NULL) {}
BoxedTraceback() : tb_next(None), has_line(false), line(-1, -1, "", ""), py_lines(NULL) {}
DEFAULT_CLASS(traceback_cls);
void addLine(const LineInfo* line);
static Box* getLines(Box* b);
static void gcHandler(gc::GCVisitor* v, Box* b);
// somewhat equivalent to PyTraceBack_Here
static void here(LineInfo lineInfo, Box** tb);
};
void printTraceback(Box* b);
......
......@@ -2703,6 +2703,7 @@ void setupRuntime() {
closure_cls->freeze();
setupUnwinding();
setupInterpreter();
setupCAPI();
......
import traceback
import sys
def f():
a, b, c = sys.exc_info()
raise a, b, c
et0, ev0, tb0 = None, None, None
try:
1/0
except:
pass
for i in xrange(10):
try:
f()
except:
et0, ev0, tb0 = sys.exc_info()
print "******** 0", ''.join(traceback.format_exception(et0, ev0, tb0))
et1, ev1, tb1 = None, None, None
et2, ev2, tb2 = None, None, None
def f1():
raise
def f2():
f1()
def f21():
raise Exception()
def f3():
try:
f21()
except:
global et1, tv1, tb1
et1, tv1, tb1 = sys.exc_info()
f2()
try:
f3()
except:
et2, tv2, tb2 = sys.exc_info()
print "******** 1", ''.join(traceback.format_exception(et1, ev1, tb1))
print "******** 2", ''.join(traceback.format_exception(et2, ev2, tb2))
print et1 is et2
print ev1 is ev2
print tb1 is tb2
# expected: fail
# - we don't stop tracebacks at the catching except handler. this is hard do the way it gets added to
# (ie a bare "raise" statement will add more traceback entries to the traceback it raises)
import sys
import traceback
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment