Skip to content

Commit 19d7534

Browse files
authored
Improve tracebacks (mypyc/mypyc#691)
* Use "logical" function names in traceback rather than the "real" function name. (For example, in a nested function, use the function name instead of __call__.) * Omit glue and helper functions from tracebacks. (This is implemented by skipping tracebacks for functions that don't have a "logical" name.) Closes mypyc/mypyc#428, mypyc/mypyc#614.
1 parent cd74d32 commit 19d7534

File tree

8 files changed

+66
-39
lines changed

8 files changed

+66
-39
lines changed

mypyc/emitfunc.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Code generation for native function bodies."""
22

33

4-
from mypyc.common import REG_PREFIX, NATIVE_PREFIX, STATIC_PREFIX, TYPE_PREFIX, TOP_LEVEL_NAME
4+
from mypyc.common import REG_PREFIX, NATIVE_PREFIX, STATIC_PREFIX, TYPE_PREFIX
55
from mypyc.emit import Emitter
66
from mypyc.ops import (
77
FuncIR, OpVisitor, Goto, Branch, Return, Assign, LoadInt, LoadErrorValue, GetAttr, SetAttr,
@@ -53,7 +53,7 @@ def generate_native_function(fn: FuncIR,
5353
module_name: str) -> None:
5454
declarations = Emitter(emitter.context, fn.env)
5555
body = Emitter(emitter.context, fn.env)
56-
visitor = FunctionEmitterVisitor(body, declarations, fn.name, source_path, module_name)
56+
visitor = FunctionEmitterVisitor(body, declarations, source_path, module_name)
5757

5858
declarations.emit_line('{} {{'.format(native_function_header(fn.decl, emitter)))
5959
body.indent()
@@ -91,14 +91,12 @@ class FunctionEmitterVisitor(OpVisitor[None], EmitterInterface):
9191
def __init__(self,
9292
emitter: Emitter,
9393
declarations: Emitter,
94-
func_name: str,
9594
source_path: str,
9695
module_name: str) -> None:
9796
self.emitter = emitter
9897
self.names = emitter.names
9998
self.declarations = declarations
10099
self.env = self.emitter.env
101-
self.func_name = func_name
102100
self.source_path = source_path
103101
self.module_name = module_name
104102

@@ -139,13 +137,10 @@ def visit_branch(self, op: Branch) -> None:
139137

140138
if op.traceback_entry is not None:
141139
globals_static = self.emitter.static_name('globals', self.module_name)
142-
func_name = self.func_name
143-
if func_name == TOP_LEVEL_NAME:
144-
func_name = '<module>' # Like normal Python tracebacks
145140
self.emit_line('CPy_AddTraceback("%s", "%s", %d, %s);' % (
146141
self.source_path.replace("\\", "\\\\"),
147-
func_name,
148-
op.line,
142+
op.traceback_entry[0],
143+
op.traceback_entry[1],
149144
globals_static))
150145
if DEBUG_ERRORS:
151146
self.emit_line('assert(PyErr_Occurred() != NULL && "failure w/o err!");')

mypyc/emitmodule.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ def generate_c_for_modules(self) -> List[Tuple[str, str]]:
160160
generate_native_function(fn, emitter, self.source_paths[module_name], module_name)
161161
if fn.name != TOP_LEVEL_NAME:
162162
emitter.emit_line()
163-
generate_wrapper_function(fn, emitter)
163+
generate_wrapper_function(
164+
fn, emitter, self.source_paths[module_name], module_name)
164165

165166
if multi_file:
166167
name = ('__native_{}.c'.format(emitter.names.private_name(module_name)))

mypyc/emitwrapper.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,29 @@ def make_format_string(func_name: str, groups: List[List[RuntimeArg]]) -> str:
3636
return '{}:{}'.format(main_format, func_name)
3737

3838

39-
def generate_wrapper_function(fn: FuncIR, emitter: Emitter) -> None:
39+
def generate_wrapper_function(fn: FuncIR,
40+
emitter: Emitter,
41+
source_path: str,
42+
module_name: str) -> None:
4043
"""Generates a CPython-compatible wrapper function for a native function.
4144
4245
In particular, this handles unboxing the arguments, calling the native function, and
4346
then boxing the return value.
4447
"""
4548
emitter.emit_line('{} {{'.format(wrapper_function_header(fn, emitter.names)))
4649

50+
# If we hit an error while processing arguments, then we emit a
51+
# traceback frame to make it possible to debug where it happened.
52+
# Unlike traceback frames added for exceptions seen in IR, we do this
53+
# even if there is no `traceback_name`. This is because the error will
54+
# have originated here and so we need it in the traceback.
55+
globals_static = emitter.static_name('globals', module_name)
56+
traceback_code = 'CPy_AddTraceback("%s", "%s", %d, %s);' % (
57+
source_path.replace("\\", "\\\\"),
58+
fn.traceback_name or fn.name,
59+
fn.line,
60+
globals_static)
61+
4762
# If fn is a method, then the first argument is a self param
4863
real_args = list(fn.args)
4964
if fn.class_name and not fn.decl.kind == FUNC_STATICMETHOD:
@@ -77,7 +92,8 @@ def generate_wrapper_function(fn: FuncIR, emitter: Emitter) -> None:
7792
'return NULL;',
7893
'}')
7994
generate_wrapper_core(fn, emitter, groups[ARG_OPT] + groups[ARG_NAMED_OPT],
80-
cleanups=cleanups)
95+
cleanups=cleanups,
96+
traceback_code=traceback_code)
8197

8298
emitter.emit_line('}')
8399

@@ -199,7 +215,8 @@ def generate_bool_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
199215
def generate_wrapper_core(fn: FuncIR, emitter: Emitter,
200216
optional_args: Optional[List[RuntimeArg]] = None,
201217
arg_names: Optional[List[str]] = None,
202-
cleanups: Optional[List[str]] = None) -> None:
218+
cleanups: Optional[List[str]] = None,
219+
traceback_code: Optional[str] = None) -> None:
203220
"""Generates the core part of a wrapper function for a native function.
204221
This expects each argument as a PyObject * named obj_{arg} as a precondition.
205222
It converts the PyObject *s to the necessary types, checking and unboxing if necessary,
@@ -208,36 +225,39 @@ def generate_wrapper_core(fn: FuncIR, emitter: Emitter,
208225

209226
optional_args = optional_args or []
210227
cleanups = cleanups or []
211-
error_code = 'return NULL;' if not cleanups else 'goto fail;'
228+
use_goto = bool(cleanups or traceback_code)
229+
error_code = 'return NULL;' if not use_goto else 'goto fail;'
212230

213231
arg_names = arg_names or [arg.name for arg in fn.args]
214232
for arg_name, arg in zip(arg_names, fn.args):
215233
# Suppress the argument check for *args/**kwargs, since we know it must be right.
216234
typ = arg.type if arg.kind not in (ARG_STAR, ARG_STAR2) else object_rprimitive
217235
generate_arg_check(arg_name, typ, emitter, error_code, arg in optional_args)
218236
native_args = ', '.join('arg_{}'.format(arg) for arg in arg_names)
219-
if fn.ret_type.is_unboxed or cleanups:
237+
if fn.ret_type.is_unboxed or use_goto:
220238
# TODO: The Py_RETURN macros return the correct PyObject * with reference count handling.
221239
# Are they relevant?
222240
emitter.emit_line('{}retval = {}{}({});'.format(emitter.ctype_spaced(fn.ret_type),
223241
NATIVE_PREFIX,
224242
fn.cname(emitter.names),
225243
native_args))
244+
emitter.emit_lines(*cleanups)
226245
if fn.ret_type.is_unboxed:
227-
emitter.emit_error_check('retval', fn.ret_type, error_code)
246+
emitter.emit_error_check('retval', fn.ret_type, 'return NULL;')
228247
emitter.emit_box('retval', 'retbox', fn.ret_type, declare_dest=True)
229248

230-
emitter.emit_lines(*cleanups)
231249
emitter.emit_line('return {};'.format('retbox' if fn.ret_type.is_unboxed else 'retval'))
232250
else:
233251
emitter.emit_line('return {}{}({});'.format(NATIVE_PREFIX,
234252
fn.cname(emitter.names),
235253
native_args))
236254
# TODO: Tracebacks?
237255

238-
if cleanups:
256+
if use_goto:
239257
emitter.emit_label('fail')
240258
emitter.emit_lines(*cleanups)
259+
if traceback_code:
260+
emitter.emit_lines(traceback_code)
241261
emitter.emit_lines('return NULL;')
242262

243263

mypyc/exceptions.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
only be placed at the end of a basic block.
1010
"""
1111

12-
from typing import List
12+
from typing import List, Optional
1313

1414
from mypyc.ops import (
1515
FuncIR, BasicBlock, LoadErrorValue, Return, Branch, RegisterOp,
@@ -28,7 +28,7 @@ def insert_exception_handling(ir: FuncIR) -> None:
2828
error_label = add_handler_block(ir)
2929
break
3030
if error_label:
31-
ir.blocks = split_blocks_at_errors(ir.blocks, error_label, ir.name)
31+
ir.blocks = split_blocks_at_errors(ir.blocks, error_label, ir.traceback_name)
3232

3333

3434
def add_handler_block(ir: FuncIR) -> BasicBlock:
@@ -43,7 +43,7 @@ def add_handler_block(ir: FuncIR) -> BasicBlock:
4343

4444
def split_blocks_at_errors(blocks: List[BasicBlock],
4545
default_error_handler: BasicBlock,
46-
func: str) -> List[BasicBlock]:
46+
func_name: Optional[str]) -> List[BasicBlock]:
4747
new_blocks = [] # type: List[BasicBlock]
4848

4949
# First split blocks on ops that may raise.
@@ -86,8 +86,8 @@ def split_blocks_at_errors(blocks: List[BasicBlock],
8686
op=variant,
8787
line=op.line)
8888
branch.negated = negated
89-
if op.line != NO_TRACEBACK_LINE_NO:
90-
branch.traceback_entry = (func, op.line)
89+
if op.line != NO_TRACEBACK_LINE_NO and func_name is not None:
90+
branch.traceback_entry = (func_name, op.line)
9191
cur_block.ops.append(branch)
9292
cur_block = new_block
9393

mypyc/genops.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,8 @@ def visit_mypy_file(self, mypyfile: MypyFile) -> None:
10811081
# Generate special function representing module top level.
10821082
blocks, env, ret_type, _ = self.leave()
10831083
sig = FuncSignature([], none_rprimitive)
1084-
func_ir = FuncIR(FuncDecl(TOP_LEVEL_NAME, None, self.module_name, sig), blocks, env)
1084+
func_ir = FuncIR(FuncDecl(TOP_LEVEL_NAME, None, self.module_name, sig), blocks, env,
1085+
traceback_name="<module>")
10851086
self.functions.append(func_ir)
10861087

10871088
def handle_ext_method(self, cdef: ClassDef, fdef: FuncDef) -> None:
@@ -1937,9 +1938,11 @@ def gen_func_ir(self,
19371938
if fn_info.is_decorated:
19381939
class_name = None if cdef is None else cdef.name
19391940
func_decl = FuncDecl(fn_info.name, class_name, self.module_name, sig)
1940-
func_ir = FuncIR(func_decl, blocks, env)
1941+
func_ir = FuncIR(func_decl, blocks, env, fn_info.fitem.line,
1942+
traceback_name=fn_info.fitem.name())
19411943
else:
1942-
func_ir = FuncIR(self.mapper.func_to_decl[fn_info.fitem], blocks, env)
1944+
func_ir = FuncIR(self.mapper.func_to_decl[fn_info.fitem], blocks, env,
1945+
fn_info.fitem.line, traceback_name=fn_info.fitem.name())
19431946
return (func_ir, func_reg)
19441947

19451948
def load_decorated_func(self, fdef: FuncDef, orig_func_reg: Value) -> Value:
@@ -3233,7 +3236,7 @@ def gen_method_call(self,
32333236
return target
32343237

32353238
# Fall back to Python method call
3236-
return self.py_method_call(base, name, arg_values, base.line, arg_kinds, arg_names)
3239+
return self.py_method_call(base, name, arg_values, line, arg_kinds, arg_names)
32373240

32383241
def union_method_call(self,
32393242
base: Value,
@@ -4812,7 +4815,8 @@ def add_call_to_callable_class(self,
48124815
"""
48134816
sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive),) + sig.args, sig.ret_type)
48144817
call_fn_decl = FuncDecl('__call__', fn_info.callable_class.ir.name, self.module_name, sig)
4815-
call_fn_ir = FuncIR(call_fn_decl, blocks, env)
4818+
call_fn_ir = FuncIR(call_fn_decl, blocks, env,
4819+
fn_info.fitem.line, traceback_name=fn_info.fitem.name())
48164820
fn_info.callable_class.ir.methods['__call__'] = call_fn_ir
48174821
return call_fn_ir
48184822

@@ -4961,7 +4965,8 @@ def add_helper_to_generator_class(self,
49614965
), sig.ret_type)
49624966
helper_fn_decl = FuncDecl('__mypyc_generator_helper__', fn_info.generator_class.ir.name,
49634967
self.module_name, sig)
4964-
helper_fn_ir = FuncIR(helper_fn_decl, blocks, env)
4968+
helper_fn_ir = FuncIR(helper_fn_decl, blocks, env,
4969+
fn_info.fitem.line, traceback_name=fn_info.fitem.name())
49654970
fn_info.generator_class.ir.methods['__mypyc_generator_helper__'] = helper_fn_ir
49664971
self.functions.append(helper_fn_ir)
49674972
return helper_fn_decl

mypyc/ops.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1428,10 +1428,17 @@ class FuncIR:
14281428
def __init__(self,
14291429
decl: FuncDecl,
14301430
blocks: List[BasicBlock],
1431-
env: Environment) -> None:
1431+
env: Environment,
1432+
line: int = -1,
1433+
traceback_name: Optional[str] = None) -> None:
14321434
self.decl = decl
14331435
self.blocks = blocks
14341436
self.env = env
1437+
self.line = line
1438+
# The name that should be displayed for tracebacks that
1439+
# include this function. Function will be omitted from
1440+
# tracebacks if None.
1441+
self.traceback_name = traceback_name
14351442

14361443
@property
14371444
def args(self) -> Sequence[RuntimeArg]:

mypyc/test/test_emitfunc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ def setUp(self) -> None:
5050
self.context = EmitterContext(['mod'])
5151
self.emitter = Emitter(self.context, self.env)
5252
self.declarations = Emitter(self.context, self.env)
53-
self.visitor = FunctionEmitterVisitor(self.emitter, self.declarations, 'func', 'prog.py',
54-
'prog')
53+
self.visitor = FunctionEmitterVisitor(self.emitter, self.declarations, 'prog.py', 'prog')
5554

5655
def test_goto(self) -> None:
5756
self.assert_emit(Goto(BasicBlock(2)),

test-data/run.test

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3814,21 +3814,21 @@ with ctx_manager() as c:
38143814
raise Exception('exception')
38153815

38163816
[out]
3817-
File "native.py", line 10, in __mypyc_generator_helper__
3817+
File "native.py", line 10, in generator
38183818
yield 3
3819-
File "native.py", line 9, in __mypyc_generator_helper__
3819+
File "native.py", line 9, in generator
38203820
yield 2
3821-
File "native.py", line 8, in __mypyc_generator_helper__
3821+
File "native.py", line 8, in generator
38223822
yield 1
38233823
File "driver.py", line 31, in <module>
38243824
raise Exception
3825-
File "native.py", line 10, in __mypyc_generator_helper__
3825+
File "native.py", line 10, in generator
38263826
yield 3
3827-
File "native.py", line 30, in __mypyc_generator_helper__
3827+
File "native.py", line 30, in wrapper
38283828
return (yield from x)
3829-
File "native.py", line 9, in __mypyc_generator_helper__
3829+
File "native.py", line 9, in generator
38303830
yield 2
3831-
File "native.py", line 30, in __mypyc_generator_helper__
3831+
File "native.py", line 30, in wrapper
38323832
return (yield from x)
38333833
caught exception without value
38343834
caught exception with value some string

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy