From df25a3295c82bb8cd6bd3a67aefe61a6ca605f38 Mon Sep 17 00:00:00 2001 From: Cisphyx Date: Thu, 16 Jan 2025 09:52:33 -0500 Subject: [PATCH] Fix function type detection (SYN-8534, SYN-8536) (#4066) --- changes/303e4458db5d4c5f9f39813b5be41ad7.yaml | 6 + changes/7a4e69c153170cce7ed678912c3857a2.yaml | 6 + synapse/lib/ast.py | 81 +++++++-- synapse/tests/test_cortex.py | 8 + synapse/tests/test_lib_ast.py | 162 ++++++++++++++++++ 5 files changed, 247 insertions(+), 16 deletions(-) create mode 100644 changes/303e4458db5d4c5f9f39813b5be41ad7.yaml create mode 100644 changes/7a4e69c153170cce7ed678912c3857a2.yaml diff --git a/changes/303e4458db5d4c5f9f39813b5be41ad7.yaml b/changes/303e4458db5d4c5f9f39813b5be41ad7.yaml new file mode 100644 index 0000000000..2d4886a75c --- /dev/null +++ b/changes/303e4458db5d4c5f9f39813b5be41ad7.yaml @@ -0,0 +1,6 @@ +--- +desc: Fixed an issue in Storm functions where using the return keyword in a subquery + used as a value could incorrectly change the function type. +prs: [] +type: bug +... diff --git a/changes/7a4e69c153170cce7ed678912c3857a2.yaml b/changes/7a4e69c153170cce7ed678912c3857a2.yaml new file mode 100644 index 0000000000..44de852536 --- /dev/null +++ b/changes/7a4e69c153170cce7ed678912c3857a2.yaml @@ -0,0 +1,6 @@ +--- +desc: Fixed an issue in Storm where attempting to iterate a non-iterable object would + raise a Python exception rather than a StormRuntimeError. +prs: [] +type: bug +... diff --git a/synapse/lib/ast.py b/synapse/lib/ast.py index 6b171d0218..a3433647fc 100644 --- a/synapse/lib/ast.py +++ b/synapse/lib/ast.py @@ -134,27 +134,27 @@ def prepare(self): pass def hasAstClass(self, clss): - hasast = self.hasast.get(clss) - if hasast is not None: + if (hasast := self.hasast.get(clss)) is not None: return hasast - retn = False + retn = self._hasAstClass(clss) + self.hasast[clss] = retn + return retn + + def _hasAstClass(self, clss): for kid in self.kids: if isinstance(kid, clss): - retn = True - break + return True - if isinstance(kid, (EditPropSet, EditCondPropSet, Function, CmdOper)): + if isinstance(kid, (Edit, Function, CmdOper, SetVarOper, SetItemOper, VarListSetOper, Value, N1Walk, LiftOper)): continue if kid.hasAstClass(clss): - retn = True - break + return True - self.hasast[clss] = retn - return retn + return False def optimize(self): [k.optimize() for k in self.kids] @@ -930,6 +930,9 @@ async def getCatchBlock(self, name, runt, path=None): class CatchBlock(AstNode): + def _hasAstClass(self, clss): + return self.kids[1].hasAstClass(clss) + async def run(self, runt, genr): async for item in self.kids[2].run(runt, genr): yield item @@ -963,6 +966,9 @@ async def catches(self, name, runt, path=None): class ForLoop(Oper): + def _hasAstClass(self, clss): + return self.kids[2].hasAstClass(clss) + def getRuntVars(self, runt): runtsafe = self.kids[1].isRuntSafe(runt) @@ -998,6 +1004,14 @@ async def run(self, runt, genr): valu = () async with contextlib.aclosing(s_coro.agen(valu)) as agen: + + try: + agen, _ = await pullone(agen) + except TypeError: + styp = await s_stormtypes.totype(valu, basetypes=True) + mesg = f"'{styp}' object is not iterable: {s_common.trimText(repr(valu))}" + raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg, type=styp)) from None + async for item in agen: if isinstance(name, (list, tuple)): @@ -1064,6 +1078,13 @@ async def run(self, runt, genr): valu = () async with contextlib.aclosing(s_coro.agen(valu)) as agen: + try: + agen, _ = await pullone(agen) + except TypeError: + styp = await s_stormtypes.totype(valu, basetypes=True) + mesg = f"'{styp}' object is not iterable: {s_common.trimText(repr(valu))}" + raise self.kids[1].addExcInfo(s_exc.StormRuntimeError(mesg=mesg, type=styp)) from None + async for item in agen: if isinstance(name, (list, tuple)): @@ -1109,6 +1130,9 @@ async def run(self, runt, genr): class WhileLoop(Oper): + def _hasAstClass(self, clss): + return self.kids[1].hasAstClass(clss) + async def run(self, runt, genr): subq = self.kids[1] node = None @@ -1162,20 +1186,21 @@ async def run(self, runt, genr): await asyncio.sleep(0) async def pullone(genr): - gotone = None - async for gotone in genr: - break + empty = False + try: + gotone = await genr.__anext__() + except StopAsyncIteration: + empty = True async def pullgenr(): - - if gotone is None: + if empty: return yield gotone async for item in genr: yield item - return pullgenr(), gotone is None + return pullgenr(), empty class CmdOper(Oper): @@ -1385,6 +1410,14 @@ async def run(self, runt, genr): class SwitchCase(Oper): + def _hasAstClass(self, clss): + + for kid in self.kids[1:]: + if kid.hasAstClass(clss): + return True + + return False + def prepare(self): self.cases = {} self.defcase = None @@ -4811,6 +4844,22 @@ class IfClause(AstNode): class IfStmt(Oper): + def _hasAstClass(self, clss): + + clauses = self.kids + + if not isinstance(clauses[-1], IfClause): + if clauses[-1].hasAstClass(clss): + return True + + clauses = clauses[:-1] + + for clause in clauses: + if clause.kids[1].hasAstClass(clss): + return True + + return False + def prepare(self): if isinstance(self.kids[-1], IfClause): self.elsequery = None diff --git a/synapse/tests/test_cortex.py b/synapse/tests/test_cortex.py index 7d6b8c6108..2cd50e42da 100644 --- a/synapse/tests/test_cortex.py +++ b/synapse/tests/test_cortex.py @@ -5230,6 +5230,14 @@ async def test_storm_forloop(self): self.eq(('inet:fqdn', 'nest.com'), nodes[0].ndef) self.eq(('inet:fqdn', 'nest.com'), nodes[1].ndef) + with self.raises(s_exc.StormRuntimeError) as err: + await core.nodes('[ it:dev:int=1 ] for $n in $node.value() { }') + self.isin("'int' object is not iterable: 1", err.exception.errinfo.get('mesg')) + + with self.raises(s_exc.StormRuntimeError) as err: + await core.nodes('for $n in { .created return($node) } { }') + self.isin("'node' object is not iterable", err.exception.errinfo.get('mesg')) + async def test_storm_whileloop(self): async with self.getTestCore() as core: diff --git a/synapse/tests/test_lib_ast.py b/synapse/tests/test_lib_ast.py index a90ece527e..0c0727f49f 100644 --- a/synapse/tests/test_lib_ast.py +++ b/synapse/tests/test_lib_ast.py @@ -4524,3 +4524,165 @@ async def test_ast_varlistset(self): text = '($x, $y) = (1)' with self.raises(s_exc.StormRuntimeError): await core.nodes(text) + + async def test_ast_functypes(self): + + async with self.getTestCore() as core: + + async def verify(q, isin=False): + msgs = await core.stormlist(q) + if isin: + self.stormIsInPrint('yep', msgs) + else: + self.stormNotInPrint('newp', msgs) + self.len(1, [m for m in msgs if m[0] == 'node']) + self.stormHasNoErr(msgs) + + q = ''' + function foo() { + for $n in { return((newp,)) } { $lib.print($n) } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + while { return((newp,)) } { $lib.print(newp) break } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + switch $lib.print({ return(newp) }) { *: { $lib.print(newp) } } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + switch $foo { *: { $lib.print(yep) return() } } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q, isin=True) + + q = ''' + function foo() { + if { return(newp) } { $lib.print(newp) } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + if (false) { $lib.print(newp) } + elif { return(newp) } { $lib.print(newp) } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + if (false) { $lib.print(newp) } + elif (true) { $lib.print(yep) return() } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + if (false) { $lib.print(newp) } + elif (false) { $lib.print(newp) } + else { $lib.print(yep) return() } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q, isin=True) + + q = ''' + function foo() { + [ it:dev:str=foo +(refs)> { $lib.print(newp) return() } ] + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + $lib.print({ return(newp) }) + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + $x = { $lib.print(newp) return() } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + ($x, $y) = { $lib.print(newp) return((foo, bar)) } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + $x = ({}) + $x.y = { $lib.print(newp) return((foo, bar)) } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + .created -({$lib.print(newp) return(refs)})> * + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + try { $lib.raise(boom) } catch { $lib.print(newp) return(newp) } as e {} + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q) + + q = ''' + function foo() { + it:dev:str={ $lib.print(newp) return(test) } + } + [ it:dev:str=test ] + $foo() + ''' + await verify(q)