Files

132 lines
6.1 KiB
Python

from pathlib import Path
import os
import resource
import signal
import subprocess
import tempfile
ROOT = Path(__file__).resolve().parents[1]
def invoke(*args, ok=True, data=None, preexec_fn=None):
result = subprocess.run([str(ROOT / '.build/remy'), *map(str, args)], input=data,
capture_output=True, text=True, timeout=30, preexec_fn=preexec_fn)
assert (result.returncode == 0) == ok, (args, result.stdout, result.stderr)
return result.stdout if ok else result.stderr
def main():
with tempfile.TemporaryDirectory() as directory:
source, binary = Path(directory) / 'source', Path(directory) / 'binary'
for text, expected in [('0x0',0),('0xff',255),('0X7F',127),('0xffffffffffffffff',(1 << 64)-1),
('(+ 0xffffffffffffffff 1)',0),('(< 0x8000000000000000 0xffffffffffffffff)',1)]:
source.write_text(text)
assert int(invoke('eval',source,'0','0','0')) == expected, text
source.write_text('(+ x y)')
assert int(invoke('eval',source,'0xFF','0x1','0x0')) == 256
invoke(source,binary)
assert int(invoke('simulate',binary,'0xFF','0x1','0x0')) == 256
invoke('simulate',binary,'invalid','0','0',ok=False)
source.write_text('(let x1 7 (let x1 (+ x1 2) (* x1 x1)))')
assert int(invoke('eval',source,'0','0','0')) == 81
source.write_text('(let 1x 3 1x)')
invoke(source,binary,ok=False)
for text in ['(+ x; ignore ) tokens\ny) ; end', '(+ x; comment\r\ny)', '(+ x y);end']:
source.write_text(text)
assert int(invoke('eval',source,'3','4','0')) == 7, text
for text, position in [('(+ x)',3),('(div x y)',1),('(let a 3 b)',4),('(+ x y',4),('(+ x y) 1',5)]:
source.write_text(text)
assert f'token {position}\n' in invoke(source,binary,ok=False), text
source.write_text('(+ 1 ' * 129 + '0' + ')' * 129)
assert 'parse depth limit' in invoke(source,binary,ok=False)
large = '0'
for _ in range(12):
large = f'(+ {large} {large})'
source.write_text(large)
assert '4096 nodes' in invoke(source,binary,ok=False)
source.write_text('(+ x y)')
invoke('limits',4,3,2,source,binary)
for depth, nodes, stack, message in [(4,2,2,'2 nodes'),(4,3,1,'1 words'),
(0,3,2,'parse depth'),(129,3,2,'compiler bounds'),('bad',3,2,'invalid resource')]:
binary.write_bytes(b'preserve')
assert message in invoke('limits',depth,nodes,stack,source,binary,ok=False)
assert binary.read_bytes() == b'preserve'
source.write_text('(+ x y)')
invoke(source,binary)
listing = invoke('disassemble',binary)
assert 'mov rax r9' in listing and 'arithmetic add' in listing and 'ret' in listing
assert 'push' not in listing and 'pop' not in listing
source.write_text('(- x (- y (- t x)))')
invoke(source,binary)
listing = invoke('disassemble',binary)
assert 'push rax' in listing and 'pop rcx' in listing
source.write_text('(if x (+ y 7) (- t 9))')
invoke(source,binary)
listing = invoke('disassemble',binary)
assert 'jz ' in listing and 'jmp ' in listing and 'conditional ' not in listing
invoke('check',binary)
original = binary.read_bytes()
branch = original.index(bytes.fromhex('48 85 c0 0f 84'))
for offset in (0,4,0x7fffffff,0xffffffff):
damaged = bytearray(original)
damaged[branch+5:branch+9] = offset.to_bytes(4,'little')
binary.write_bytes(damaged)
invoke('check',binary,ok=False)
for cut in range(branch+1,branch+9):
binary.write_bytes(original[:cut]+bytes([195]))
invoke('check',binary,ok=False)
for malformed in [b'',bytes([195,0]),bytes([72,199,192,0]),bytes([80])]:
binary.write_bytes(malformed)
assert 'invalid native' in invoke('disassemble',binary,ok=False)
binary.write_bytes(bytes([72,184,42,0,0,0,0,0,0,0,195]))
assert '10 ret' in invoke('disassemble',binary)
source.write_text('(+ x y)')
invoke(source,binary)
assert 'verified' in invoke('check',binary)
for code in [[88,195],[80,195],[65,88,195],[73,137,192,195],[72,139,68,36,0,195]]:
binary.write_bytes(bytes(code))
invoke('check',binary,ok=False)
binary.write_bytes(bytes([72,199,192,42,0,0,0,195]))
invoke('check',binary)
source.write_text('(+ x y)')
binary.write_bytes(b'preserve')
binary.chmod(0o600)
invoke(source,binary,preexec_fn=lambda: os.umask(0))
assert binary.stat().st_mode & 0o777 == 0o600
def restricted():
signal.signal(signal.SIGXFSZ,signal.SIG_IGN)
resource.setrlimit(resource.RLIMIT_FSIZE,(1,1))
for args in [(source,binary),('vectors',source,binary)]:
binary.write_bytes(b'preserve')
invoke(*args,ok=False,preexec_fn=restricted)
assert binary.read_bytes() == b'preserve'
assert not list(binary.parent.glob('.binary.*'))
target = Path(directory) / 'folder'
target.mkdir()
invoke(source,target,ok=False)
assert not list(target.parent.glob('.folder.*'))
source.write_text('(+ x y)')
invoke(source,source)
assert source.read_bytes().endswith(bytes([195]))
invoke('-',binary,data='(+ x y)')
assert int(invoke('eval','-',3,4,0,data='(+ x y)')) == 7
invoke('vectors','-',binary,data='(+ x y)')
assert binary.stat().st_size == 16384
for data in ['(+ x)', ' ' * 65537]:
binary.write_bytes(b'preserve')
invoke('-',binary,data=data,ok=False)
assert binary.read_bytes() == b'preserve'
source.write_text('é')
assert 'ASCII' in invoke(source,binary,ok=False)
for text in ['0x','0x10000000000000000','0xfg','0xffextra','-0x1']:
source.write_text(text)
binary.write_bytes(b'preserve')
invoke(source,binary,ok=False)
assert binary.read_bytes() == b'preserve', text
print('CLI literal and rejection checks passed')
if __name__ == '__main__':
main()