Commit e22da6f3 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Basic 'traceback' object support

Previously we were just passing around a vector<> of LineInfos; now, they
get encapsulated in a BoxedTraceback object.  This has a couple benefits:
1) they can participate in the existing sys.exc_info passing+handling
2) we can enable a basic form of the traceback module.

2 means that we can finally test our tracebacks support, since I was constantly
fixing one issue only to break it in another place.

1 means that we now generate the right traceback for the current exception!
Before this change, the traceback we would generate was determined using a different
system than the exc_info-based exception raising, so sometimes they would diverge
and be horribly confusing.

There's a pretty big limitation with the current implementation: our tracebacks
don't span the right stack frames.  In CPython, a traceback spans the stack frames
between the raise and the catch, but in Pyston the traceback includes all stack frames.
It's not easy to provide this behavior, since the tracebacks are supposed to get updated
as they get rethrown through each stack frame.

We could do some complicated stuff in irgen to make sure this happens.  I think the better
but more complicated approach is for us to create the custom exception unwinder we've been
wanting.  This would let us add custom traceback-handling support as we unwound the stack.

Another limitation is that tracebacks are supposed to automatically include a reference
to the entire frame stack (tb.tb_frame.f_back.f_back.f_back....).  In Pyston, we're not
automatically generating those frame objects, so we would either need to do that and take
a perf hit, or (more likely?) generate the frame objects on-demand when they're needed.

It's not really clear that they're actually needed for traceback objects, so I implemented
a different traceback object API and changed the traceback.py library, under the assumption
that almost-noone actually deals with the traceback object internals.
parent ee448c5f
"""Extract, format and print information about Python stack traces."""
# This module has been heavily modified for Pyston, since we don't provide the
# same traceback objects as CPython.
import linecache
import sys
import types
......@@ -56,7 +59,20 @@ def print_tb(tb, limit=None, file=None):
if limit is None:
if hasattr(sys, 'tracebacklimit'):
limit = sys.tracebacklimit
n = 0
# Pyston change:
for (filename, name, lineno) in tb.getLines():
if limit and n >= limit:
break
_print(file,
' File "%s", line %d, in %s' % (filename, lineno, name))
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, None)
if line: _print(file, ' ' + line.strip())
n = n+1
"""
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
......@@ -70,6 +86,7 @@ def print_tb(tb, limit=None, file=None):
if line: _print(file, ' ' + line.strip())
tb = tb.tb_next
n = n+1
"""
def format_tb(tb, limit = None):
"""A shorthand for 'format_list(extract_tb(tb, limit))'."""
......@@ -91,6 +108,18 @@ def extract_tb(tb, limit = None):
limit = sys.tracebacklimit
list = []
n = 0
# Pyston change:
for (filename, name, lineno) in tb.getLines():
if limit and n >= limit:
break
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, None)
if line: line = line.strip()
else: line = None
list.append((filename, lineno, name, line))
n = n+1
"""
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
......@@ -104,6 +133,7 @@ def extract_tb(tb, limit = None):
list.append((filename, lineno, name, line))
tb = tb.tb_next
n = n+1
"""
return list
......@@ -262,6 +292,8 @@ def print_stack(f=None, limit=None, file=None):
stack frame at which to start. The optional 'limit' and 'file'
arguments have the same meaning as for print_exception().
"""
raise NotImplementedError("This function is currently not implemented in Pyston")
if f is None:
try:
raise ZeroDivisionError
......@@ -271,6 +303,8 @@ def print_stack(f=None, limit=None, file=None):
def format_stack(f=None, limit=None):
"""Shorthand for 'format_list(extract_stack(f, limit))'."""
raise NotImplementedError("This function is currently not implemented in Pyston")
if f is None:
try:
raise ZeroDivisionError
......@@ -287,6 +321,8 @@ def extract_stack(f=None, limit = None):
line number, function name, text), and the entries are in order
from oldest to newest stack frame.
"""
raise NotImplementedError("This function is currently not implemented in Pyston")
if f is None:
try:
raise ZeroDivisionError
......@@ -317,4 +353,6 @@ def tb_lineno(tb):
Obsolete in 2.3.
"""
raise NotImplementedError("This function is currently not implemented in Pyston")
return tb.tb_lineno
......@@ -83,7 +83,7 @@ const std::string SourceInfo::getName() {
case AST_TYPE::Lambda:
return "<lambda>";
case AST_TYPE::Module:
return this->parent_module->name();
return "<module>";
default:
RELEASE_ASSERT(0, "%d", ast->type);
}
......
......@@ -29,6 +29,7 @@
#include "codegen/irgen/hooks.h"
#include "codegen/stackmaps.h"
#include "core/util.h"
#include "runtime/traceback.h"
#include "runtime/types.h"
......@@ -438,22 +439,22 @@ static const LineInfo* lineInfoForFrame(PythonFrameIterator& frame_it) {
AST_stmt* current_stmt = frame_it.getCurrentStatement();
auto* cf = frame_it.getCF();
assert(cf);
return new LineInfo(current_stmt->lineno, current_stmt->col_offset, cf->clfunc->source->parent_module->fn,
cf->clfunc->source->getName());
}
std::vector<const LineInfo*> getTracebackEntries() {
std::vector<const LineInfo*> entries;
auto source = cf->clfunc->source;
return new LineInfo(current_stmt->lineno, current_stmt->col_offset, source->parent_module->fn, source->getName());
}
BoxedTraceback* getTraceback() {
if (!ENABLE_FRAME_INTROSPECTION) {
static bool printed_warning = false;
if (!printed_warning) {
printed_warning = true;
fprintf(stderr, "Warning: can't get traceback since ENABLE_FRAME_INTROSPECTION=0\n");
}
return entries;
return new BoxedTraceback();
}
std::vector<const LineInfo*> entries;
for (auto& frame_info : unwindPythonFrames()) {
const LineInfo* line_info = lineInfoForFrame(frame_info);
if (line_info)
......@@ -461,12 +462,7 @@ std::vector<const LineInfo*> getTracebackEntries() {
}
std::reverse(entries.begin(), entries.end());
return entries;
}
const LineInfo* getMostRecentLineInfo() {
std::unique_ptr<PythonFrameIterator> frame = getTopPythonFrame();
return lineInfoForFrame(*frame);
return new BoxedTraceback(std::move(entries));
}
ExcInfo* getFrameExcInfo() {
......
......@@ -21,11 +21,12 @@
namespace pyston {
std::vector<const LineInfo*> getTracebackEntries();
const LineInfo* getMostRecentLineInfo();
class BoxedModule;
BoxedModule* getCurrentModule();
class BoxedTraceback;
BoxedTraceback* getTraceback();
class BoxedDict;
BoxedDict* getLocals(bool only_user_visible);
......
......@@ -463,7 +463,6 @@ void prependToSysPath(const std::string& path);
void addToSysArgv(const char* str);
std::string formatException(Box* e);
void printLastTraceback();
// Raise a SyntaxError that occurs at a specific location.
// The traceback given to the user will include this,
......@@ -488,6 +487,7 @@ struct ExcInfo {
ExcInfo(Box* type, Box* value, Box* traceback) : type(type), value(value), traceback(traceback) {}
#endif
bool matches(BoxedClass* cls) const;
void printExcAndTraceback() const;
};
struct FrameInfo {
......
......@@ -157,9 +157,7 @@ int main(int argc, char** argv) {
printf("Warning: ignoring SystemExit code\n");
return 1;
} else {
std::string msg = formatException(e.value);
printLastTraceback();
fprintf(stderr, "%s\n", msg.c_str());
e.printExcAndTraceback();
return 1;
}
}
......@@ -226,9 +224,7 @@ int main(int argc, char** argv) {
printf("Warning: ignoring SystemExit code\n");
return 1;
} else {
std::string msg = formatException(e.value);
printLastTraceback();
fprintf(stderr, "%s\n", msg.c_str());
e.printExcAndTraceback();
}
}
}
......
......@@ -32,9 +32,7 @@ static void* thread_start(Box* target, Box* varargs, Box* kwargs) {
try {
runtimeCall(target, ArgPassSpec(0, 0, true, kwargs != NULL), varargs, kwargs, NULL, NULL, NULL);
} catch (ExcInfo e) {
std::string msg = formatException(e.value);
printLastTraceback();
fprintf(stderr, "%s\n", msg.c_str());
e.printExcAndTraceback();
}
return NULL;
}
......
......@@ -25,9 +25,11 @@
#include "core/threading.h"
#include "core/types.h"
#include "runtime/classobj.h"
#include "runtime/file.h"
#include "runtime/import.h"
#include "runtime/objmodel.h"
#include "runtime/rewrite_args.h"
#include "runtime/traceback.h"
#include "runtime/types.h"
namespace pyston {
......@@ -691,8 +693,6 @@ void checkAndThrowCAPIException() {
assert(!cur_thread_state.curexc_value);
if (_type) {
RELEASE_ASSERT(cur_thread_state.curexc_traceback == NULL || cur_thread_state.curexc_traceback == None,
"unsupported");
BoxedClass* type = static_cast<BoxedClass*>(_type);
assert(isInstance(_type, type_cls) && isSubclass(static_cast<BoxedClass*>(type), BaseException)
&& "Only support throwing subclass of BaseException for now");
......@@ -701,6 +701,10 @@ void checkAndThrowCAPIException() {
if (!value)
value = None;
Box* tb = cur_thread_state.curexc_traceback;
if (!tb)
tb = None;
// This is similar to PyErr_NormalizeException:
if (!isInstance(value, type)) {
if (value->cls == tuple_cls) {
......@@ -716,6 +720,8 @@ void checkAndThrowCAPIException() {
RELEASE_ASSERT(value->cls == type, "unsupported");
PyErr_Clear();
if (tb != None)
raiseRaw(ExcInfo(value->cls, value, tb));
raiseExc(value);
}
}
......@@ -770,7 +776,10 @@ extern "C" PyObject* PyExceptionInstance_Class(PyObject* o) noexcept {
}
extern "C" int PyTraceBack_Print(PyObject* v, PyObject* f) noexcept {
Py_FatalError("unimplemented");
RELEASE_ASSERT(f->cls == file_cls && static_cast<BoxedFile*>(f)->f_fp == stderr,
"sorry will only print tracebacks to stderr right now");
printTraceback(v);
return 0;
}
#define Py_DEFAULT_RECURSION_LIMIT 1000
......
......@@ -1710,9 +1710,10 @@ extern "C" bool nonzero(Box* obj) {
if (func == NULL) {
ASSERT(isUserDefined(obj->cls) || obj->cls == classobj_cls || obj->cls == type_cls
|| isSubclass(obj->cls, Exception) || obj->cls == file_cls,
|| isSubclass(obj->cls, Exception) || obj->cls == file_cls || obj->cls == traceback_cls,
"%s.__nonzero__",
getTypeName(obj)); // TODO
// TODO should rewrite these?
return true;
}
......
......@@ -22,6 +22,7 @@
#include "core/options.h"
#include "gc/collector.h"
#include "runtime/objmodel.h"
#include "runtime/traceback.h"
#include "runtime/types.h"
#include "runtime/util.h"
......@@ -95,8 +96,6 @@ void unwindExc(Box* exc_obj) {
abort();
}
static std::vector<const LineInfo*> last_tb;
void raiseRaw(const ExcInfo& e) __attribute__((__noreturn__));
void raiseRaw(const ExcInfo& e) {
// Should set these to None before getting here:
......@@ -112,10 +111,7 @@ void raiseRaw(const ExcInfo& e) {
}
void raiseExc(Box* exc_obj) {
auto entries = getTracebackEntries();
last_tb = std::move(entries);
raiseRaw(ExcInfo(exc_obj->cls, exc_obj, None));
raiseRaw(ExcInfo(exc_obj->cls, exc_obj, getTraceback()));
}
// Have a special helper function for syntax errors, since we want to include the location
......@@ -123,59 +119,16 @@ void raiseExc(Box* exc_obj) {
void raiseSyntaxError(const char* msg, int lineno, int col_offset, const std::string& file, const std::string& func) {
Box* exc = exceptionNew2(SyntaxError, boxStrConstant(msg));
auto entries = getTracebackEntries();
last_tb = std::move(entries);
// TODO: leaks this!
last_tb.push_back(new LineInfo(lineno, col_offset, file, func));
auto tb = getTraceback();
// TODO: push the syntax error line back on it:
//// TODO: leaks this!
// last_tb.push_back(new LineInfo(lineno, col_offset, file, func));
raiseRaw(ExcInfo(exc->cls, exc, None));
}
static void _printTraceback(const std::vector<const LineInfo*>& tb) {
fprintf(stderr, "Traceback (most recent call last):\n");
for (auto line : tb) {
fprintf(stderr, " File \"%s\", line %d, in %s:\n", line->file.c_str(), line->line, line->func.c_str());
if (line->line < 0)
continue;
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++) {
char* buf = NULL;
size_t size;
size_t r = getline(&buf, &size, f);
if (r != -1)
free(buf);
}
char* buf = NULL;
size_t size;
size_t r = getline(&buf, &size, f);
if (r != -1) {
while (buf[r - 1] == '\n' or buf[r - 1] == '\r')
r--;
char* ptr = buf;
while (*ptr == ' ' || *ptr == '\t') {
ptr++;
r--;
}
fprintf(stderr, " %.*s\n", (int)r, ptr);
free(buf);
}
}
}
}
void printLastTraceback() {
_printTraceback(last_tb);
raiseRaw(ExcInfo(exc->cls, exc, tb));
}
void _printStacktrace() {
_printTraceback(getTracebackEntries());
printTraceback(getTraceback());
}
// where should this go...
......@@ -237,6 +190,12 @@ ExcInfo::ExcInfo(Box* type, Box* value, Box* traceback) : type(type), value(valu
}
#endif
void ExcInfo::printExcAndTraceback() const {
std::string msg = formatException(value);
printTraceback(traceback);
fprintf(stderr, "%s\n", msg.c_str());
}
bool ExcInfo::matches(BoxedClass* cls) const {
assert(this->type);
RELEASE_ASSERT(isSubclass(this->type->cls, type_cls), "throwing old-style objects not supported yet (%s)",
......@@ -247,6 +206,9 @@ bool ExcInfo::matches(BoxedClass* cls) const {
void raise3(Box* arg0, Box* arg1, Box* arg2) {
// TODO switch this to PyErr_Normalize
if (arg2 == None)
arg2 = getTraceback();
if (isSubclass(arg0->cls, type_cls)) {
BoxedClass* c = static_cast<BoxedClass*>(arg0);
if (isSubclass(c, BaseException)) {
......
// Copyright (c) 2014-2015 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "runtime/traceback.h"
#include <algorithm>
#include <cstring>
#include <sstream>
#include "capi/types.h"
#include "core/ast.h"
#include "core/common.h"
#include "core/stats.h"
#include "core/types.h"
#include "gc/collector.h"
#include "runtime/objmodel.h"
#include "runtime/types.h"
#include "runtime/util.h"
namespace pyston {
extern "C" {
BoxedClass* traceback_cls;
}
void BoxedTraceback::gcHandler(GCVisitor* v, Box* b) {
assert(b->cls == traceback_cls);
BoxedTraceback* self = static_cast<BoxedTraceback*>(b);
if (self->py_lines)
v->visit(self->py_lines);
boxGCHandler(v, b);
}
void printTraceback(Box* b) {
if (b == None)
return;
assert(b->cls == traceback_cls);
BoxedTraceback* tb = static_cast<BoxedTraceback*>(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());
if (line->line < 0)
continue;
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++) {
char* buf = NULL;
size_t size;
size_t r = getline(&buf, &size, f);
if (r != -1)
free(buf);
}
char* buf = NULL;
size_t size;
size_t r = getline(&buf, &size, f);
if (r != -1) {
while (buf[r - 1] == '\n' or buf[r - 1] == '\r')
r--;
char* ptr = buf;
while (*ptr == ' ' || *ptr == '\t') {
ptr++;
r--;
}
fprintf(stderr, " %.*s\n", (int)r, ptr);
free(buf);
}
}
}
}
Box* BoxedTraceback::getLines(Box* b) {
assert(b->cls == traceback_cls);
BoxedTraceback* tb = static_cast<BoxedTraceback*>(b);
if (!tb->py_lines) {
BoxedList* lines = new BoxedList();
for (auto line : tb->lines) {
auto l = new BoxedTuple({ boxString(line->file), boxString(line->func), boxInt(line->line) });
listAppendInternal(lines, l);
}
tb->py_lines = lines;
}
return tb->py_lines;
}
void setupTraceback() {
traceback_cls
= new BoxedHeapClass(object_cls, BoxedTraceback::gcHandler, 0, sizeof(BoxedTraceback), false, "traceback");
traceback_cls->giveAttr("getLines", new BoxedFunction(boxRTFunction((void*)BoxedTraceback::getLines, UNKNOWN, 1)));
traceback_cls->freeze();
}
}
// Copyright (c) 2014-2015 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PYSTON_RUNTIME_TRACEBACK_H
#define PYSTON_RUNTIME_TRACEBACK_H
#include "core/types.h"
#include "runtime/types.h"
namespace pyston {
namespace gc {
class GCVisitor;
}
extern "C" BoxedClass* traceback_cls;
class BoxedTraceback : public Box {
public:
std::vector<const LineInfo*> lines;
Box* py_lines;
BoxedTraceback(std::vector<const LineInfo*>&& lines) : lines(std::move(lines)), py_lines(NULL) {}
BoxedTraceback() : 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);
};
void printTraceback(Box* b);
void setupTraceback();
}
#endif
......@@ -35,6 +35,7 @@
#include "runtime/objmodel.h"
#include "runtime/set.h"
#include "runtime/super.h"
#include "runtime/traceback.h"
#include "runtime/util.h"
extern "C" void initerrno();
......@@ -1176,6 +1177,7 @@ void setupRuntime() {
setupSuper();
setupUnicode();
setupDescr();
setupTraceback();
function_cls->giveAttr("__name__", new BoxedGetsetDescriptor(func_name, func_set_name, NULL));
function_cls->giveAttr("__repr__", new BoxedFunction(boxRTFunction((void*)functionRepr, STR, 1)));
......
# allow-warning: converting unicode literal to str
# 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
def f1():
print "f1"
def f2():
try:
1/0
except:
traceback.print_exc(file=sys.stdout)
raise
try:
f2()
except:
traceback.print_exc(file=sys.stdout)
f1()
# allow-warning: converting unicode literal to str
# A test of both the tracebacks we generate and the tracebacks module
#
# (We keep fixing tracebacks in one case to break them in another, so it's time for a test.)
#
# All of these tests involve except handlers at the top scope, since we currently generate extra-long
# tracebacks when an exception gets caught inside a function.
# - In CPython the traceback goes from the point of the exception to the point of the handler
# - In Pyston the traceback always goes to the top-most frame
#
# See traceback_limits.py
import sys
import traceback
try:
1/0
except:
traceback.print_exc(file=sys.stdout)
def f():
traceback.print_exc(file=sys.stdout)
try:
1/0
except:
f()
traceback.print_exc(file=sys.stdout)
try:
1/0
except:
a, b, c = sys.exc_info()
try:
[][1]
except:
pass
traceback.print_exc(file=sys.stdout)
try:
raise a, b, c
except:
traceback.print_exc(file=sys.stdout)
traceback.print_exc(file=sys.stdout)
def f():
1/0
try:
f()
except:
traceback.print_exc(file=sys.stdout)
def f():
def g():
1/0
g()
try:
f()
except:
a, b, t = sys.exc_info()
# For print_tb, the 'limit' parameter starts from the bottommost call frame:
traceback.print_tb(t, limit=1)
try:
1/0
except AttributeError:
pass
except:
traceback.print_exc(file=sys.stdout)
try:
try:
1/0
except AttributeError:
pass
except:
traceback.print_exc(file=sys.stdout)
try:
try:
1/0
except:
raise
except:
traceback.print_exc(file=sys.stdout)
try:
raise AttributeError()
except:
traceback.print_exc(file=sys.stdout)
def f():
1/0
try:
f()
except:
traceback.print_exc(file=sys.stdout)
a, b, t = sys.exc_info()
try:
raise a, b, None
except:
traceback.print_exc(file=sys.stdout)
# Output some extra stuff at the end so that it doesn't look like the script crashed with an exception:
print
print "done!"
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