Skip to content

Commit 37e2c64

Browse files
authored
Merge pull request #392 from boriel/bugfix/infinite_loop
Bugfix/infinite loop
2 parents 85464f6 + 443adcc commit 37e2c64

5 files changed

Lines changed: 98 additions & 1 deletion

File tree

api/utils.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
# -*- coding: utf-8 -*-
33

44
import os
5+
import errno
56
import shelve
7+
import signal
8+
from functools import wraps
69
from typing import NamedTuple, List, Any
710

811
from . import constants
@@ -13,7 +16,13 @@
1316
import symbols
1417

1518

16-
__all__ = ['read_txt_file', 'open_file', 'sanitize_filename', 'flatten_list']
19+
__all__ = [
20+
'read_txt_file',
21+
'open_file',
22+
'sanitize_filename',
23+
'flatten_list',
24+
'timeout'
25+
]
1726

1827
__doc__ = """Utils module contains many helpers for several task, like reading files
1928
or path management"""
@@ -136,3 +145,22 @@ def get_final_value(symbol: symbols.SYMBOL):
136145
result = result.value
137146

138147
return result
148+
149+
150+
def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
151+
def decorator(func):
152+
def _handle_timeout(signum, frame):
153+
raise TimeoutError(error_message)
154+
155+
def wrapper(*args, **kwargs):
156+
signal.signal(signal.SIGALRM, _handle_timeout)
157+
signal.alarm(seconds)
158+
try:
159+
result = func(*args, **kwargs)
160+
finally:
161+
signal.alarm(0)
162+
return result
163+
164+
return wraps(func)(wrapper)
165+
166+
return decorator

ast_/ast.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,13 @@ def generic_visit(node: Ast):
5757
def filter_inorder(self, node, filter_func: Callable[[Any], bool]):
5858
""" Visit the tree inorder, but only those that return true for filter
5959
"""
60+
visited = set()
6061
stack = [node]
6162
while stack:
6263
node = stack.pop()
64+
if node in visited:
65+
continue
66+
visited.add(node)
6367
if filter_func(node):
6468
yield self.visit(node)
6569
elif isinstance(node, Ast):
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
org 32768
2+
__START_PROGRAM:
3+
di
4+
push ix
5+
push iy
6+
exx
7+
push hl
8+
exx
9+
ld hl, 0
10+
add hl, sp
11+
ld (__CALL_BACK__), hl
12+
ei
13+
jp __MAIN_PROGRAM__
14+
ZXBASIC_USER_DATA:
15+
; Defines USER DATA Length in bytes
16+
ZXBASIC_USER_DATA_LEN EQU ZXBASIC_USER_DATA_END - ZXBASIC_USER_DATA
17+
.__LABEL__.ZXBASIC_USER_DATA_LEN EQU ZXBASIC_USER_DATA_LEN
18+
.__LABEL__.ZXBASIC_USER_DATA EQU ZXBASIC_USER_DATA
19+
_kill:
20+
DEFB 00
21+
ZXBASIC_USER_DATA_END:
22+
__MAIN_PROGRAM__:
23+
ld hl, 0
24+
ld b, h
25+
ld c, l
26+
__END_PROGRAM:
27+
di
28+
ld hl, (__CALL_BACK__)
29+
ld sp, hl
30+
exx
31+
pop hl
32+
pop iy
33+
pop ix
34+
exx
35+
ei
36+
ret
37+
__CALL_BACK__:
38+
DEFW 0
39+
_bulletcuchillo:
40+
ld a, (_kill)
41+
or a
42+
jp nz, _bulletcuchillo__leave
43+
__LABEL__endbulletcuchillo:
44+
_bulletcuchillo__leave:
45+
ret
46+
END
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
REM This was causing an infinite loop.
2+
3+
DIM kill as Ubyte
4+
5+
sub fastcall bulletcuchillo()
6+
if kill then
7+
return
8+
end if
9+
endbulletcuchillo:
10+
end sub
11+
12+
DIM dummy as Uinteger
13+
dummy = @bulletcuchillo
14+

tests/functional/test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
sys.path.append(ZXBASIC_ROOT) # TODO: consider moving test.py to another place to avoid this
3131

3232
# Now we can import the modules from the root
33+
import api.utils # noqa
3334
import libzxbc # noqa
3435
import libzxbasm # noqa
3536
import libzxbpp # noqa
@@ -46,6 +47,7 @@
4647
STDERR = None
4748
INLINE = True # Set to false to use system Shell
4849
RAISE_EXCEPTIONS = False # True if we want the testing to abort on compiler crashes
50+
TIMEOUT = 3 # Max number of seconds a test should last
4951

5052

5153
class TempTestFile(object):
@@ -239,6 +241,7 @@ def updateTest(tfname, pattern_):
239241
f.write(''.join(lines))
240242

241243

244+
@api.utils.timeout(TIMEOUT)
242245
def testPREPRO(fname, pattern_=None, inline=None, cmdline_args=None):
243246
""" Test preprocessing file. Test is done by preprocessing the file and then
244247
comparing the output against an expected one. The output file can optionally be filtered
@@ -297,6 +300,7 @@ def testPREPRO(fname, pattern_=None, inline=None, cmdline_args=None):
297300
return result
298301

299302

303+
@api.utils.timeout(TIMEOUT)
300304
def testASM(fname, inline=None, cmdline_args=None):
301305
""" Test assembling an ASM (.asm) file. Test is done by assembling the source code into a binary and then
302306
comparing the output file against an expected binary output.
@@ -339,6 +343,7 @@ def testASM(fname, inline=None, cmdline_args=None):
339343
return result
340344

341345

346+
@api.utils.timeout(TIMEOUT)
342347
def testBAS(fname, filter_=None, inline=None, cmdline_args=None):
343348
""" Test compiling a BASIC (.bas) file. Test is done by compiling the source code into asm and then
344349
comparing the output asm against an expected asm output. The output asm file can optionally be filtered

0 commit comments

Comments
 (0)