Skip to content

Commit

Permalink
Add line number information to exceptions raised during function calls (
Browse files Browse the repository at this point in the history
  • Loading branch information
Cisphyx authored Dec 26, 2024
1 parent 5841b06 commit 5517cfb
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 11 deletions.
6 changes: 6 additions & 0 deletions changes/e25248c44d5ba33a43ae937a0bb25fd5.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
desc: Fixed an issue where certain exceptions raised while calling a function in Storm
were not providing appropriate details about the origin of the exception.
prs: []
type: bug
...
34 changes: 24 additions & 10 deletions synapse/lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -3579,10 +3579,23 @@ async def compute(self, runt, path):
kwargs = {k: v for (k, v) in await self.kids[2].compute(runt, path)}

with s_scope.enter({'runt': runt}):
retn = func(*argv, **kwargs)
if s_coro.iscoro(retn):
return await retn
return retn
try:
retn = func(*argv, **kwargs)
if s_coro.iscoro(retn):
return await retn
return retn

except TypeError as e:
mesg = str(e)
if (funcpath := getattr(func, '_storm_funcpath', None)) is not None:
mesg = f"{funcpath}(){mesg.split(')', 1)[1]}"

raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg))

except s_exc.SynErr as e:
if getattr(func, '_storm_runtime_lib_func', None) is not None:
e.errinfo.pop('highlight', None)
raise self.addExcInfo(e)

class DollarExpr(Value):
'''
Expand Down Expand Up @@ -4891,8 +4904,9 @@ async def once():

@s_stormtypes.stormfunc(readonly=True)
async def realfunc(*args, **kwargs):
return await self.callfunc(runt, argdefs, args, kwargs)
return await self.callfunc(runt, argdefs, args, kwargs, realfunc._storm_funcpath)

realfunc._storm_funcpath = self.name
await runt.setVar(self.name, realfunc)

count = 0
Expand All @@ -4914,7 +4928,7 @@ def validate(self, runt):
# var scope validation occurs in the sub-runtime
pass

async def callfunc(self, runt, argdefs, args, kwargs):
async def callfunc(self, runt, argdefs, args, kwargs, funcpath):
'''
Execute a function call using the given runtime.
Expand All @@ -4925,7 +4939,7 @@ async def callfunc(self, runt, argdefs, args, kwargs):

argcount = len(args) + len(kwargs)
if argcount > len(argdefs):
mesg = f'{self.name}() takes {len(argdefs)} arguments but {argcount} were provided'
mesg = f'{funcpath}() takes {len(argdefs)} arguments but {argcount} were provided'
raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg))

# Fill in the positional arguments
Expand All @@ -4939,7 +4953,7 @@ async def callfunc(self, runt, argdefs, args, kwargs):
valu = kwargs.pop(name, s_common.novalu)
if valu is s_common.novalu:
if defv is s_common.novalu:
mesg = f'{self.name}() missing required argument {name}'
mesg = f'{funcpath}() missing required argument {name}'
raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg))
valu = defv

Expand All @@ -4950,11 +4964,11 @@ async def callfunc(self, runt, argdefs, args, kwargs):
# used a kwarg not defined.
kwkeys = list(kwargs.keys())
if kwkeys[0] in posnames:
mesg = f'{self.name}() got multiple values for parameter {kwkeys[0]}'
mesg = f'{funcpath}() got multiple values for parameter {kwkeys[0]}'
raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg))

plural = 's' if len(kwargs) > 1 else ''
mesg = f'{self.name}() got unexpected keyword argument{plural}: {",".join(kwkeys)}'
mesg = f'{funcpath}() got unexpected keyword argument{plural}: {",".join(kwkeys)}'
raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg))

assert len(mergargs) == len(argdefs)
Expand Down
19 changes: 19 additions & 0 deletions synapse/lib/stormtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,29 @@ def registerLib(self, ctor):
raise Exception('no key!')
self.addStormLib(path, ctor)

for info in ctor._storm_locals:
rtype = info.get('type')
if isinstance(rtype, dict) and rtype.get('type') == 'function':
if (fname := rtype.get('_funcname')) == '_storm_query':
continue

if (func := getattr(ctor, fname, None)) is not None:
funcpath = '.'.join(('lib',) + ctor._storm_lib_path + (info['name'],))
func._storm_funcpath = f"${funcpath}"

return ctor

def registerType(self, ctor):
'''Decorator to register a StormPrim'''
self.addStormType(ctor.__name__, ctor)

for info in ctor._storm_locals:
rtype = info.get('type')
if isinstance(rtype, dict) and rtype.get('type') == 'function':
fname = rtype.get('_funcname')
if (func := getattr(ctor, fname, None)) is not None:
func._storm_funcpath = f"{ctor._storm_typename}.{info['name']}"

return ctor

def iterLibs(self):
Expand Down Expand Up @@ -628,6 +646,7 @@ async def initLibAsync(self):
if callable(v) and v.__name__ == 'realfunc':
v._storm_runtime_lib = self
v._storm_runtime_lib_func = k
v._storm_funcpath = f'${".".join(("lib",) + self.name + (k,))}'

self.locls[k] = v

Expand Down
4 changes: 3 additions & 1 deletion synapse/tests/test_lib_agenda.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,9 @@ def looptime():

appt = await agenda.get(guid)
self.eq(appt.isrunning, False)
self.eq(appt.lastresult, "raised exception StormRaise: errname='OmgWtfBbq' mesg='boom'")
self.isin("raised exception StormRaise: errname='OmgWtfBbq'", appt.lastresult)
self.isin("highlight={'hash': '6736b8252d9413221a9b693b2b19cf53'", appt.lastresult)
self.isin("mesg='boom'", appt.lastresult)

# Test setting the global enable/disable flag
await agenda.delete(guid)
Expand Down
42 changes: 42 additions & 0 deletions synapse/tests/test_lib_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -3119,6 +3119,48 @@ async def test_ast_highlight(self):
off, end = errm[1][1]['highlight']['offsets']
self.eq('newp', text[off:end])

visi = (await core.addUser('visi'))['iden']
text = '$users=$lib.auth.users.list() $lib.print($users.0.profile)'
msgs = await core.stormlist(text, opts={'user': visi})
errm = [m for m in msgs if m[0] == 'err'][0]
off, end = errm[1][1]['highlight']['offsets']
self.eq('lib.print($users.0.profile)', text[off:end])

text = '$lib.len(foo, bar)'
msgs = await core.stormlist(text)
errm = [m for m in msgs if m[0] == 'err'][0]
off, end = errm[1][1]['highlight']['offsets']
self.eq('lib.len(foo, bar)', text[off:end])
self.stormIsInErr('$lib.len()', msgs)

text = '$foo=$lib.pkg.get $foo()'
msgs = await core.stormlist(text)
errm = [m for m in msgs if m[0] == 'err'][0]
off, end = errm[1][1]['highlight']['offsets']
self.eq('foo()', text[off:end])
self.stormIsInErr('$lib.pkg.get()', msgs)

text = '$obj = $lib.pipe.gen(${ $obj.put() }) $obj.put(foo, bar, baz)'
msgs = await core.stormlist(text)
errm = [m for m in msgs if m[0] == 'err'][0]
off, end = errm[1][1]['highlight']['offsets']
self.eq('obj.put(foo, bar, baz)', text[off:end])
self.stormIsInErr('pipe.put()', msgs)

text = '$lib.gen.campaign(foo, bar, baz)'
msgs = await core.stormlist(text)
errm = [m for m in msgs if m[0] == 'err'][0]
off, end = errm[1][1]['highlight']['offsets']
self.eq('lib.gen.campaign(foo, bar, baz)', text[off:end])
self.stormIsInErr('$lib.gen.campaign()', msgs)

text = '$gen = $lib.gen.campaign $gen(foo, bar, baz)'
msgs = await core.stormlist(text)
errm = [m for m in msgs if m[0] == 'err'][0]
off, end = errm[1][1]['highlight']['offsets']
self.eq('gen(foo, bar, baz)', text[off:end])
self.stormIsInErr('$lib.gen.campaign()', msgs)

async def test_ast_bulkedges(self):

async with self.getTestCore() as core:
Expand Down

0 comments on commit 5517cfb

Please sign in to comment.