-
Notifications
You must be signed in to change notification settings - Fork 4
/
oracle.py
103 lines (94 loc) · 3.08 KB
/
oracle.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import time
from lark import Lark
import tempfile
import subprocess
import os
"""
This file gives classes to use as "Oracles" in the Arvada algorithm.
"""
class ParseException(Exception):
pass
class ExternalOracle:
"""
An ExternalOracle is a wrapper around an oracle that takes the form of a shell
command accepting a file as input. We assume the oracle returns True if the
exit code is 0 (no error). If the external oracle takes >3 seconds to execute,
we conservatively assume the oracle returns True.
"""
def __init__(self, command):
"""
`command` is a string representing the oracle command, i.e. `command` = "readpng"
in the oracle call:
$ readpng <MY_FILE>
"""
self.command = command
self.cache_set = {}
self.parse_calls = 0
self.real_calls = 0
self.time_spent = 0
def _parse_internal(self, string, timeout = 3):
"""
Does the work of calling the subprocess.
"""
self.real_calls +=1
FNULL = open(os.devnull, 'w')
f = tempfile.NamedTemporaryFile()
f.write(bytes(string, 'utf-8'))
f_name = f.name
f.flush()
try:
# With check = True, throws a CalledProcessError if the exit code is non-zero
subprocess.run([self.command, f_name], stdout=FNULL, stderr=FNULL, timeout=timeout, check=True)
f.close()
FNULL.close()
return True
except subprocess.CalledProcessError as e:
f.close()
FNULL.close()
return False
except subprocess.TimeoutExpired as e:
print(f"Caused timeout: {string}")
f.close()
FNULL.close()
return True
def parse(self, string, timeout=3):
"""
Caching wrapper around _parse_internal
"""
self.parse_calls += 1
if string in self.cache_set:
if self.cache_set[string]:
return True
else:
raise ParseException(f"doesn't parse: {string}")
else:
s = time.time()
res = self._parse_internal(string, timeout)
self.time_spent += time.time() - s
self.cache_set[string] = res
if res:
return True
else:
raise ParseException(f"doesn't parse: {string}")
class CachingOracle:
"""
Wraps a "Lark" parser object to provide caching of previous calls.
"""
def __init__(self, oracle: Lark):
self.oracle = oracle
self.cache_set = {}
self.parse_calls = 0
def parse(self, string):
self.parse_calls += 1
if string in self.cache_set:
if self.cache_set[string]:
return True
else:
raise ParseException("doesn't parse")
else:
try:
self.oracle.parse(string)
self.cache_set[string] = True
except Exception as e:
self.cache_set[string] = False
raise ParseException("doesn't parse")