Skip to content

Commit d9219e7

Browse files
committed
committed backend changes
1 parent 1f8c249 commit d9219e7

12 files changed

Lines changed: 2840 additions & 37 deletions

v3/callback_module.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# this module contains a simple callback function to test whether
2+
# callback functions get properly displayed in the trace
3+
4+
def callback_func(func_arg):
5+
func_arg()

v3/pg_logger.py

Lines changed: 140 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,24 @@
7171
resource_module_loaded = False
7272

7373

74+
75+
# These could lead to XSS or other code injection attacks, so be careful:
76+
__html__ = None
77+
def setHTML(htmlStr):
78+
global __html__
79+
__html__ = htmlStr
80+
81+
__css__ = None
82+
def setCSS(cssStr):
83+
global __css__
84+
__css__ = cssStr
85+
86+
__js__ = None
87+
def setJS(jsStr):
88+
global __js__
89+
__js__ = jsStr
90+
91+
7492
# ugh, I can't figure out why in Python 2, __builtins__ seems to
7593
# be a dict, but in Python 3, __builtins__ seems to be a module,
7694
# so just handle both cases ... UGLY!
@@ -82,24 +100,43 @@
82100

83101

84102
# whitelist of module imports
85-
ALLOWED_MODULE_IMPORTS = ('math', 'random', 'datetime',
103+
ALLOWED_STDLIB_MODULE_IMPORTS = ('math', 'random', 'datetime',
86104
'functools', 'operator', 'string',
87105
'collections', 're', 'json',
88106
'heapq', 'bisect')
89107

108+
# whitelist of custom modules to import into OPT
109+
# (TODO: support modules in a subdirectory, but there are various
110+
# logistical problems with doing so that I can't overcome at the moment,
111+
# especially getting setHTML, setCSS, and setJS to work in the imported
112+
# modules.)
113+
CUSTOM_MODULE_IMPORTS = ('callback_module', 'ttt_module')
114+
115+
90116
# PREEMPTIVELY import all of these modules, so that when the user's
91117
# script imports them, it won't try to do a file read (since they've
92118
# already been imported and cached in memory). Remember that when
93119
# the user's code runs, resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0))
94120
# will already be in effect, so no more files can be opened.
95-
for m in ALLOWED_MODULE_IMPORTS:
121+
#
122+
# NB: All modules in CUSTOM_MODULE_IMPORTS will be imported, warts and
123+
# all, so they better work on Python 2 and 3!
124+
for m in ALLOWED_STDLIB_MODULE_IMPORTS + CUSTOM_MODULE_IMPORTS:
96125
__import__(m)
97126

98127

99128
# Restrict imports to a whitelist
100129
def __restricted_import__(*args):
101-
if args[0] in ALLOWED_MODULE_IMPORTS:
102-
return BUILTIN_IMPORT(*args)
130+
if args[0] in ALLOWED_STDLIB_MODULE_IMPORTS + CUSTOM_MODULE_IMPORTS:
131+
imported_mod = BUILTIN_IMPORT(*args)
132+
133+
if args[0] in CUSTOM_MODULE_IMPORTS:
134+
# add special magical functions to custom imported modules
135+
setattr(imported_mod, 'setHTML', setHTML)
136+
setattr(imported_mod, 'setCSS', setCSS)
137+
setattr(imported_mod, 'setJS', setJS)
138+
139+
return imported_mod
103140
else:
104141
raise ImportError('{0} not supported'.format(args[0]))
105142

@@ -109,7 +146,7 @@ def __restricted_import__(*args):
109146
# 1. running the entire program up to a call to raw_input (or input in py3),
110147
# 2. bailing and returning a trace ending in a special 'raw_input' event,
111148
# 3. letting the web frontend issue a prompt to the user to grab a string,
112-
# 4. RE-RUNNING the whole program with that string added to raw_input_queue,
149+
# 4. RE-RUNNING the whole program with that string added to input_string_queue,
113150
# 5. which should bring execution to the next raw_input call (if
114151
# available), or to termination.
115152
# Repeat until no more raw_input calls are encountered.
@@ -118,23 +155,33 @@ def __restricted_import__(*args):
118155
# TODO: To make this technique more deterministic,
119156
# save away and restore the random seed.
120157

121-
# queue of input strings passed from the outside
122-
raw_input_queue = []
158+
# queue of input strings passed from either raw_input or mouse_input
159+
input_string_queue = []
123160

124161
class RawInputException(Exception):
125162
pass
126163

127164
def raw_input_wrapper(prompt=''):
128-
if raw_input_queue:
129-
return raw_input_queue.pop(0)
165+
if input_string_queue:
166+
return input_string_queue.pop(0)
130167
raise RawInputException(prompt)
131168

169+
class MouseInputException(Exception):
170+
pass
171+
172+
def mouse_input_wrapper(prompt=''):
173+
if input_string_queue:
174+
return input_string_queue.pop(0)
175+
raise MouseInputException(prompt)
176+
177+
132178

133179
# blacklist of builtins
134-
BANNED_BUILTINS = ['reload', 'apply', 'open', 'compile',
180+
BANNED_BUILTINS = ['reload', 'open', 'compile',
135181
'file', 'eval', 'exec', 'execfile',
136182
'exit', 'quit', 'help',
137183
'dir', 'globals', 'locals', 'vars']
184+
# Peter says 'apply' isn't dangerous, so don't ban it
138185

139186
# ban input() in Python 2 since it does an eval!
140187
# (Python 3 input is Python 2 raw_input, so we're okay)
@@ -408,6 +455,9 @@ def user_exception(self, frame, exc_info):
408455
if exc_type_name == 'RawInputException':
409456
self.trace.append(dict(event='raw_input', prompt=exc_value.args[0]))
410457
self.done = True
458+
elif exc_type_name == 'MouseInputException':
459+
self.trace.append(dict(event='mouse_input', prompt=exc_value.args[0]))
460+
self.done = True
411461
else:
412462
self.interaction(frame, exc_traceback, 'exception')
413463

@@ -420,13 +470,43 @@ def interaction(self, frame, traceback, event_type):
420470
top_frame = tos[0]
421471
lineno = tos[1]
422472

473+
474+
# debug ...
475+
'''
476+
print >> sys.stderr, '=== STACK ==='
477+
for (e,ln) in self.stack:
478+
print >> sys.stderr, e.f_code.co_name + ' ' + e.f_code.co_filename + ' ' + str(ln)
479+
print >> sys.stderr, "top_frame", top_frame.f_code.co_name
480+
print >> sys.stderr
481+
'''
482+
483+
423484
# don't trace inside of ANY functions that aren't user-written code
424485
# (e.g., those from imported modules -- e.g., random, re -- or the
425486
# __restricted_import__ function in this file)
426487
#
427488
# empirically, it seems like the FIRST entry in self.stack is
428489
# the 'run' function from bdb.py, but everything else on the
429490
# stack is the user program's "real stack"
491+
492+
# Look only at the "topmost" frame on the stack ...
493+
494+
# it seems like user-written code has a filename of '<string>',
495+
# but maybe there are false positives too?
496+
if self.canonic(top_frame.f_code.co_filename) != '<string>':
497+
return
498+
# also don't trace inside of the magic "constructor" code
499+
if top_frame.f_code.co_name == '__new__':
500+
return
501+
# or __repr__, which is often called when running print statements
502+
if top_frame.f_code.co_name == '__repr__':
503+
return
504+
505+
506+
# OLD CODE -- bail if any element on the stack matches these conditions
507+
# note that the old code passes tests/backend-tests/namedtuple.txt
508+
# but the new code above doesn't :/
509+
'''
430510
for (cur_frame, cur_line) in self.stack[1:]:
431511
# it seems like user-written code has a filename of '<string>',
432512
# but maybe there are false positives too?
@@ -438,14 +518,6 @@ def interaction(self, frame, traceback, event_type):
438518
# or __repr__, which is often called when running print statements
439519
if cur_frame.f_code.co_name == '__repr__':
440520
return
441-
442-
443-
# debug ...
444-
'''
445-
print >> sys.stderr, '==='
446-
for (e,ln) in self.stack:
447-
print >> sys.stderr, e.f_code.co_name + ' ' + e.f_code.co_filename + ' ' + str(ln)
448-
print >> sys.stderr
449521
'''
450522

451523

@@ -629,7 +701,25 @@ def create_encoded_stack_entry(cur_frame):
629701
if cur_name == '<module>':
630702
break
631703

632-
encoded_stack_locals.append(create_encoded_stack_entry(cur_frame))
704+
# do this check because in some cases, certain frames on the
705+
# stack might NOT be tracked, so don't push a stack entry for
706+
# those frames. this happens when you have a callback function
707+
# in an imported module. e.g., your code:
708+
# def foo():
709+
# bar(baz)
710+
#
711+
# def baz(): pass
712+
#
713+
# imported module code:
714+
# def bar(callback_func):
715+
# callback_func()
716+
#
717+
# when baz is executing, the real stack is [foo, bar, baz] but
718+
# bar is in imported module code, so pg_logger doesn't trace
719+
# it, and it doesn't show up in frame_ordered_ids. thus, the
720+
# stack to render should only be [foo, baz].
721+
if cur_frame in self.frame_ordered_ids:
722+
encoded_stack_locals.append(create_encoded_stack_entry(cur_frame))
633723
i -= 1
634724

635725
zombie_encoded_stack_locals = [create_encoded_stack_entry(e) for e in zombie_frames_to_render]
@@ -721,6 +811,15 @@ def create_encoded_stack_entry(cur_frame):
721811
heap=self.encoder.get_heap(),
722812
stdout=get_user_stdout(tos[0]))
723813

814+
# TODO: refactor into a non-global
815+
global __html__, __css__, __js__
816+
if __html__:
817+
trace_entry['html_output'] = __html__
818+
if __css__:
819+
trace_entry['css_output'] = __css__
820+
if __js__:
821+
trace_entry['js_output'] = __js__
822+
724823
# if there's an exception, then record its info:
725824
if event_type == 'exception':
726825
# always check in f_locals
@@ -792,6 +891,14 @@ def _runscript(self, script_str):
792891
else:
793892
user_builtins[k] = v
794893

894+
user_builtins['mouse_input'] = mouse_input_wrapper
895+
896+
# uncomment to ease debugging, but in production,
897+
# don't expose these directly to user since it's more of a
898+
# security risk. instead, expose them only to imported modules
899+
#user_builtins['setHTML'] = setHTML
900+
#user_builtins['setCSS'] = setCSS
901+
#user_builtins['setJS'] = setJS
795902

796903
user_stdout = cStringIO.StringIO()
797904

@@ -911,11 +1018,15 @@ def finalize(self):
9111018
def exec_script_str(script_str, raw_input_lst_json, cumulative_mode, heap_primitives, finalizer_func):
9121019
logger = PGLogger(cumulative_mode, heap_primitives, finalizer_func)
9131020

914-
global raw_input_queue
915-
raw_input_queue = []
1021+
# TODO: refactor these NOT to be globals
1022+
global input_string_queue
1023+
input_string_queue = []
9161024
if raw_input_lst_json:
9171025
# TODO: if we want to support unicode, remove str() cast
918-
raw_input_queue = [str(e) for e in json.loads(raw_input_lst_json)]
1026+
input_string_queue = [str(e) for e in json.loads(raw_input_lst_json)]
1027+
1028+
global __html__, __css__, __js__
1029+
__html__, __css__, __js__ = None, None, None
9191030

9201031
try:
9211032
logger._runscript(script_str)
@@ -931,11 +1042,15 @@ def exec_script_str(script_str, raw_input_lst_json, cumulative_mode, heap_primit
9311042
def exec_script_str_local(script_str, raw_input_lst_json, cumulative_mode, heap_primitives, finalizer_func):
9321043
logger = PGLogger(cumulative_mode, heap_primitives, finalizer_func, disable_security_checks=True)
9331044

934-
global raw_input_queue
935-
raw_input_queue = []
1045+
# TODO: refactor these NOT to be globals
1046+
global input_string_queue
1047+
input_string_queue = []
9361048
if raw_input_lst_json:
9371049
# TODO: if we want to support unicode, remove str() cast
938-
raw_input_queue = [str(e) for e in json.loads(raw_input_lst_json)]
1050+
input_string_queue = [str(e) for e in json.loads(raw_input_lst_json)]
1051+
1052+
global __html__, __css__, __js__
1053+
__html__, __css__, __js__ = None, None, None
9391054

9401055
try:
9411056
logger._runscript(script_str)

0 commit comments

Comments
 (0)