Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent functions from throwing loop control flow exceptions; convert them into StormRuntimeError. SYN-8397 #4025

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
013bdb2
Prevent functions from throwing loop control flow exceptions; convert…
vEpiphyte Dec 5, 2024
174d26c
embed statement into the exception
vEpiphyte Dec 5, 2024
e4f4c57
Add StormLoopCtrl stop
vEpiphyte Dec 5, 2024
4cd34ae
Feedback
vEpiphyte Dec 6, 2024
1b47a1e
Merge branch 'master' into bug_func_ctrlflow_leak
vEpiphyte Dec 6, 2024
19faf66
wip
vEpiphyte Dec 6, 2024
d1a4f59
Merge branch 'master' into bug_func_ctrlflow_leak
vEpiphyte Dec 26, 2024
5e72afc
Update SynErr.errinfo manipulation to use set / update APIs.
vEpiphyte Dec 26, 2024
59bf7e3
Add code to StormCtrlFlow to make them seem close to SynErr exception…
vEpiphyte Dec 26, 2024
32a59bc
Add a FIXME / DISCUSS comment.
vEpiphyte Dec 26, 2024
2172afa
Refactor StormCtrlFlow implementation.
vEpiphyte Dec 26, 2024
335ec71
Fix up item references & Exception refs
vEpiphyte Dec 26, 2024
ad96e69
Move the Execption class into the mixin...
vEpiphyte Dec 26, 2024
a5c62da
Merge branch 'master' into bug_func_ctrlflow_leak
vEpiphyte Dec 30, 2024
b93ce40
Tidy tests for stop keyword
vEpiphyte Dec 30, 2024
6e0c1cc
Add break and continue tests
vEpiphyte Dec 30, 2024
8a893af
Merge branch 'master' into bug_func_ctrlflow_leak
vEpiphyte Dec 31, 2024
57a5d81
Chagnelog
vEpiphyte Dec 31, 2024
ab0196a
fix typo
vEpiphyte Dec 31, 2024
b4585d1
Add test for synerrmixin behavior
vEpiphyte Dec 31, 2024
1e7e5d5
Additional test coverage
vEpiphyte Dec 31, 2024
5667da7
additional exc coverage
vEpiphyte Dec 31, 2024
474a042
Add coverage for callStorm as well.
vEpiphyte Dec 31, 2024
a041e66
remove some extra calls to getLogExtra()
vEpiphyte Dec 31, 2024
fa30ce7
Apply suggestions from code review
vEpiphyte Dec 31, 2024
6f16f36
Merge branch 'bug_func_ctrlflow_leak' of github.com:vertexproject/syn…
vEpiphyte Dec 31, 2024
e993f46
updates / comments
vEpiphyte Dec 31, 2024
803ada5
Update synapse/lib/stormctrl.py
Cisphyx Jan 3, 2025
0f48d66
fix some uses of StormRuntimeError without a mesg= argument
vEpiphyte Jan 6, 2025
40a9851
Merge branch 'master' into bug_func_ctrlflow_leak
vEpiphyte Jan 6, 2025
5967611
Merge branch 'master' into bug_func_ctrlflow_leak
vEpiphyte Jan 8, 2025
1d7f6d5
Add highlight info for emit use outside of emitter functions.
vEpiphyte Jan 8, 2025
d999581
fix comment
vEpiphyte Jan 8, 2025
352f75d
Update changes/b4642a502f49353787b4f6632a6a6566.yaml
vEpiphyte Jan 8, 2025
9406e05
formatting
vEpiphyte Jan 8, 2025
993025d
remove comment
vEpiphyte Jan 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changes/b4642a502f49353787b4f6632a6a6566.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
desc: Fixed an issue with the Storm loop and generator keywords, ``continue``, ``break``,
and ``stop``. Using these keywords to exit a function will now raise a ``StormRuntimeError``
vEpiphyte marked this conversation as resolved.
Show resolved Hide resolved
vEpiphyte marked this conversation as resolved.
Show resolved Hide resolved
exception. Using these keywords to tear down the Storm runtime will now emit an
``err`` message with the type ``StormRuntimeError`` and a message indicating the
invalid use of the keywords.
prs: []
type: bug
...
9 changes: 3 additions & 6 deletions synapse/cortex.py
Original file line number Diff line number Diff line change
Expand Up @@ -5910,7 +5910,6 @@ async def storm(self, text, opts=None):
opts = self._initStormOpts(opts)

if self.stormpool is not None and opts.get('mirror', True):
extra = await self.getLogExtra(text=text)
proxy = await self._getMirrorProxy(opts)

if proxy is not None:
Expand All @@ -5930,7 +5929,7 @@ async def storm(self, text, opts=None):

except s_exc.TimeOut:
mesg = 'Timeout waiting for query mirror, running locally instead.'
logger.warning(mesg)
logger.warning(mesg, extra=extra)

if (nexsoffs := opts.get('nexsoffs')) is not None:
if not await self.waitNexsOffs(nexsoffs, timeout=opts.get('nexstimeout')):
Expand All @@ -5945,7 +5944,6 @@ async def callStorm(self, text, opts=None):
opts = self._initStormOpts(opts)

if self.stormpool is not None and opts.get('mirror', True):
extra = await self.getLogExtra(text=text)
proxy = await self._getMirrorProxy(opts)

if proxy is not None:
Expand All @@ -5962,7 +5960,7 @@ async def callStorm(self, text, opts=None):
return await proxy.callStorm(text, opts=mirropts)
except s_exc.TimeOut:
mesg = 'Timeout waiting for query mirror, running locally instead.'
logger.warning(mesg)
logger.warning(mesg, extra=extra)

if (nexsoffs := opts.get('nexsoffs')) is not None:
if not await self.waitNexsOffs(nexsoffs, timeout=opts.get('nexstimeout')):
Expand All @@ -5975,7 +5973,6 @@ async def exportStorm(self, text, opts=None):
opts = self._initStormOpts(opts)

if self.stormpool is not None and opts.get('mirror', True):
extra = await self.getLogExtra(text=text)
proxy = await self._getMirrorProxy(opts)

if proxy is not None:
Expand All @@ -5995,7 +5992,7 @@ async def exportStorm(self, text, opts=None):

except s_exc.TimeOut:
mesg = 'Timeout waiting for query mirror, running locally instead.'
logger.warning(mesg)
logger.warning(mesg, extra=extra)

if (nexsoffs := opts.get('nexsoffs')) is not None:
if not await self.waitNexsOffs(nexsoffs, timeout=opts.get('nexstimeout')):
Expand Down
2 changes: 1 addition & 1 deletion synapse/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def setdefault(self, name, valu):

def update(self, items: dict):
'''Update multiple items in the errinfo dict at once.'''
self.errinfo.update(**items)
self.errinfo.update(items)
self._setExcMesg()

class StormRaise(SynErr):
Expand Down
57 changes: 34 additions & 23 deletions synapse/lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -1020,13 +1020,13 @@ async def run(self, runt, genr):
yield item

except s_stormctrl.StormBreak as e:
if e.item is not None:
yield e.item
if (eitem := e.get('item')) is not None:
yield eitem
break

except s_stormctrl.StormContinue as e:
if e.item is not None:
yield e.item
if (eitem := e.get('item')) is not None:
yield eitem
continue

finally:
Expand Down Expand Up @@ -1079,13 +1079,13 @@ async def run(self, runt, genr):
yield jtem

except s_stormctrl.StormBreak as e:
if e.item is not None:
yield e.item
if (eitem := e.get('item')) is not None:
yield eitem
break

except s_stormctrl.StormContinue as e:
if e.item is not None:
yield e.item
if (eitem := e.get('item')) is not None:
yield eitem
continue

finally:
Expand All @@ -1109,13 +1109,13 @@ async def run(self, runt, genr):
await asyncio.sleep(0)

except s_stormctrl.StormBreak as e:
if e.item is not None:
yield e.item
if (eitem := e.get('item')) is not None:
yield eitem
break

except s_stormctrl.StormContinue as e:
if e.item is not None:
yield e.item
if (eitem := e.get('item')) is not None:
yield eitem
continue

finally:
Expand All @@ -1133,13 +1133,13 @@ async def run(self, runt, genr):
await asyncio.sleep(0)

except s_stormctrl.StormBreak as e:
if e.item is not None:
yield e.item
if (eitem := e.get('item')) is not None:
yield eitem
break

except s_stormctrl.StormContinue as e:
if e.item is not None:
yield e.item
if (eitem := e.get('item')) is not None:
yield eitem
continue

finally:
Expand Down Expand Up @@ -4724,9 +4724,9 @@ async def run(self, runt, genr):
yield _

async for node, path in genr:
raise s_stormctrl.StormBreak(item=(node, path))
raise self.addExcInfo(s_stormctrl.StormBreak(item=(node, path)))

raise s_stormctrl.StormBreak()
raise self.addExcInfo(s_stormctrl.StormBreak())

class ContinueOper(AstNode):

Expand All @@ -4737,9 +4737,9 @@ async def run(self, runt, genr):
yield _

async for node, path in genr:
raise s_stormctrl.StormContinue(item=(node, path))
raise self.addExcInfo(s_stormctrl.StormContinue(item=(node, path)))

raise s_stormctrl.StormContinue()
raise self.addExcInfo(s_stormctrl.StormContinue())

class IfClause(AstNode):
pass
Expand Down Expand Up @@ -4842,8 +4842,8 @@ class Stop(Oper):
async def run(self, runt, genr):
for _ in (): yield _
async for node, path in genr:
raise s_stormctrl.StormStop()
raise s_stormctrl.StormStop()
raise self.addExcInfo(s_stormctrl.StormStop())
raise self.addExcInfo(s_stormctrl.StormStop())

class FuncArgs(AstNode):
'''
Expand Down Expand Up @@ -4987,9 +4987,16 @@ async def callfunc(self, runt, argdefs, args, kwargs, funcpath):
await asyncio.sleep(0)

return None

except s_stormctrl.StormReturn as e:
return e.item
except s_stormctrl.StormLoopCtrl as e:
mesg = f'function {self.name} - Loop control statement "{e.statement}" used outside of a loop.'
raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, function=self.name,
statement=e.statement)) from e
except s_stormctrl.StormGenrCtrl as e:
mesg = f'function {self.name} - Generator control statement "{e.statement}" used outside of a generator function.'
raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, function=self.name,
statement=e.statement)) from e

async def genr():
async with runt.getSubRuntime(self.kids[2], opts=opts) as subr:
Expand All @@ -5009,5 +5016,9 @@ async def genr():
yield node, path
except s_stormctrl.StormStop:
return
except s_stormctrl.StormLoopCtrl as e:
mesg = f'function {self.name} - Loop control statement "{e.statement}" used outside of a loop.'
raise self.addExcInfo(s_exc.StormRuntimeError(mesg=mesg, function=self.name,
statement=e.statement)) from e

return genr()
94 changes: 88 additions & 6 deletions synapse/lib/stormctrl.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,91 @@
class StormCtrlFlow(Exception):
'''
Base class all StormCtrlFlow exceptions derive from.
'''
def __init__(self):
raise NotImplementedError

class _SynErrMixin(Exception):
'''
An exception mixin to give some control flow classes functionality like SynErr.
'''
def __init__(self, *args, **info):
self.errinfo = info
Exception.__init__(self, self._getExcMsg())

def _getExcMsg(self):
props = sorted(self.errinfo.items())
displ = ' '.join(['%s=%r' % (p, v) for (p, v) in props])
return '%s: %s' % (self.__class__.__name__, displ)

def _setExcMesg(self):
'''Should be called when self.errinfo is modified.'''
self.args = (self._getExcMsg(),)

def __setstate__(self, state):
'''Pickle support.'''
super(StormCtrlFlow, self).__setstate__(state)
self._setExcMesg()

def items(self):
return {k: v for k, v in self.errinfo.items()}

def get(self, name, defv=None):
'''
Return a value from the errinfo dict.

Example:

try:
foothing()
except SynErr as e:
blah = e.get('blah')

'''
return self.errinfo.get(name, defv)

def set(self, name, valu):
'''
Set a value in the errinfo dict.
'''
self.errinfo[name] = valu
self._setExcMesg()

def setdefault(self, name, valu):
'''
Set a value in errinfo dict if it is not already set.
'''
if name in self.errinfo:
return
self.errinfo[name] = valu
self._setExcMesg()

def update(self, items: dict):
'''Update multiple items in the errinfo dict at once.'''
self.errinfo.update(items)
self._setExcMesg()

class StormLoopCtrl(_SynErrMixin):
# Control flow statements for WHILE and FOR loop control
statement = ''

class StormGenrCtrl(_SynErrMixin):
# Control flow statements for GENERATOR control
statement = ''

class StormStop(StormGenrCtrl, StormCtrlFlow):
statement = 'stop'

class StormBreak(StormLoopCtrl, StormCtrlFlow):
statement = 'break'

class StormContinue(StormLoopCtrl, StormCtrlFlow):
statement = 'continue'

class StormExit(_SynErrMixin, StormCtrlFlow): pass

# StormReturn is kept thin since it is commonly used and just
# needs to be the container for moving an item up a frame.
class StormReturn(StormCtrlFlow):
def __init__(self, item=None):
self.item = item

class StormExit(StormCtrlFlow): pass
class StormStop(StormCtrlFlow): pass
class StormBreak(StormCtrlFlow): pass
class StormReturn(StormCtrlFlow): pass
class StormContinue(StormCtrlFlow): pass
1 change: 1 addition & 0 deletions synapse/lib/stormlib/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ async def _runCallback(self, key):
except s_stormctrl.StormReturn as e:
return await s_stormtypes.toprim(e.item)
except s_stormctrl.StormCtrlFlow:
# DISCUSS / FIXME: This should probably convert the CtrlFlow into a StormRuntimeError?
vEpiphyte marked this conversation as resolved.
Show resolved Hide resolved
pass

async def _reqKey(self, key):
Expand Down
2 changes: 1 addition & 1 deletion synapse/lib/stormtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1663,7 +1663,7 @@ async def _exit(self, mesg=None, **kwargs):
if mesg:
mesg = await self._get_mesg(mesg, **kwargs)
await self.runt.warn(mesg, log=False)
raise s_stormctrl.StormExit(mesg)
raise s_stormctrl.StormExit(mesg=mesg)
raise s_stormctrl.StormExit()

@stormfunc(readonly=True)
Expand Down
23 changes: 20 additions & 3 deletions synapse/lib/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,6 @@ def isForkOf(self, viewiden):
async def _calcForkLayers(self):
# recompute the proper set of layers for a forked view
# (this may only be called from within a nexus handler)

'''
We spent a lot of time thinking/talking about this so some hefty
comments are in order:
Expand Down Expand Up @@ -953,6 +952,15 @@ async def callStorm(self, text, opts=None):
extra={'synapse': {'text': text, 'username': user.name, 'user': user.iden}})
raise

except (s_stormctrl.StormLoopCtrl, s_stormctrl.StormGenrCtrl) as e:
if isinstance(e, s_stormctrl.StormLoopCtrl):
mesg = f'Loop control statement "{e.statement}" used outside of a loop.'
else:
mesg = f'Generator control statement "{e.statement}" used outside of a generator function.'
logmesg = f'Error during storm execution for {{ {text} }} - {mesg}'
logger.exception(logmesg, extra={'synapse': {'text': text, 'username': user.name, 'user': user.iden}})
raise s_exc.StormRuntimeError(mesg=mesg, statement=e.statement, highlight=e.get('highlight')) from e

except Exception:
logger.exception(f'Error during callStorm execution for {{ {text} }}',
extra={'synapse': {'text': text, 'username': user.name, 'user': user.iden}})
Expand Down Expand Up @@ -1055,8 +1063,17 @@ async def runStorm():
raise

except Exception as e:
logger.exception(f'Error during storm execution for {{ {text} }}',
extra={'synapse': {'text': text, 'username': user.name, 'user': user.iden}})
mesg = ''
if isinstance(e, s_stormctrl.StormLoopCtrl):
mesg = f'Loop control statement "{e.statement}" used outside of a loop.'
e = s_exc.StormRuntimeError(mesg=mesg, statement=e.statement, highlight=e.get('highlight'))
elif isinstance(e, s_stormctrl.StormGenrCtrl):
mesg = f'Generator control statement "{e.statement}" used outside of a generator function.'
e = s_exc.StormRuntimeError(mesg=mesg, statement=e.statement, highlight=e.get('highlight'))
logmesg = f'Error during storm execution for {{ {text} }}'
if mesg:
logmesg = f'{logmesg} - {mesg}'
logger.exception(logmesg, extra={'synapse': {'text': text, 'username': user.name, 'user': user.iden}})
enfo = s_common.err(e)
enfo[1].pop('esrc', None)
enfo[1].pop('ename', None)
Expand Down
Loading
Loading