@@ -104,11 +104,42 @@ def __restricted_import__(*args):
104104 raise ImportError ('{0} not supported' .format (args [0 ]))
105105
106106
107+ # Support interactive user input by:
108+ #
109+ # 1. running the entire program up to a call to raw_input (or input in py3),
110+ # 2. bailing and returning a trace ending in a special 'raw_input' event,
111+ # 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,
113+ # 5. which should bring execution to the next raw_input call (if
114+ # available), or to termination.
115+ # Repeat until no more raw_input calls are encountered.
116+ # Note that this is mad inefficient, but is simple to implement!
117+ #
118+ # TODO: To make this technique more deterministic,
119+ # save away and restore the random seed.
120+
121+ # queue of input strings passed from the outside
122+ raw_input_queue = []
123+
124+ class RawInputException (Exception ):
125+ pass
126+
127+ def raw_input_wrapper (prompt = '' ):
128+ if raw_input_queue :
129+ return raw_input_queue .pop (0 )
130+ raise RawInputException (prompt )
131+
132+
107133# blacklist of builtins
108- BANNED_BUILTINS = ( 'reload' , 'input ' , 'apply' , 'open' , 'compile' ,
134+ BANNED_BUILTINS = [ 'reload' , 'apply' , 'open' , 'compile' ,
109135 'file' , 'eval' , 'exec' , 'execfile' ,
110- 'exit' , 'quit' , 'raw_input' , 'help' ,
111- 'dir' , 'globals' , 'locals' , 'vars' )
136+ 'exit' , 'quit' , 'help' ,
137+ 'dir' , 'globals' , 'locals' , 'vars' ]
138+
139+ # ban input() in Python 2 since it does an eval!
140+ # (Python 3 input is Python 2 raw_input, so we're okay)
141+ if not is_python3 :
142+ BANNED_BUILTINS .append ('input' )
112143
113144
114145IGNORE_VARS = set (('__user_stdout__' , '__builtins__' , '__name__' , '__exception__' , '__doc__' , '__package__' ))
@@ -211,6 +242,9 @@ def __init__(self, cumulative_mode, heap_primitives, finalizer_func, disable_sec
211242 # executed line
212243 self .trace = []
213244
245+ # if this is true, don't put any more stuff into self.trace
246+ self .done = False
247+
214248 #http://stackoverflow.com/questions/2112396/in-python-in-google-app-engine-how-do-you-capture-output-produced-by-the-print
215249 self .GAE_STDOUT = sys .stdout
216250
@@ -323,6 +357,10 @@ def setup(self, f, t):
323357 # Override Bdb methods
324358
325359 def user_call (self , frame , argument_list ):
360+ # TODO: figure out a way to move this down to 'def interaction'
361+ # or right before self.trace.append ...
362+ if self .done : return
363+
326364 """This method is called when there is the remote possibility
327365 that we ever need to stop in this function."""
328366 if self ._wait_for_mainpyfile :
@@ -339,6 +377,8 @@ def user_call(self, frame, argument_list):
339377 self .interaction (frame , None , 'call' )
340378
341379 def user_line (self , frame ):
380+ if self .done : return
381+
342382 """This function is called when we stop or break at this line."""
343383 if self ._wait_for_mainpyfile :
344384 if (self .canonic (frame .f_code .co_filename ) != "<string>" or
@@ -348,19 +388,28 @@ def user_line(self, frame):
348388 self .interaction (frame , None , 'step_line' )
349389
350390 def user_return (self , frame , return_value ):
391+ if self .done : return
392+
351393 """This function is called when a return trap is set here."""
352394 frame .f_locals ['__return__' ] = return_value
353395 self .interaction (frame , None , 'return' )
354396
355397 def user_exception (self , frame , exc_info ):
398+ if self .done : return
399+
356400 exc_type , exc_value , exc_traceback = exc_info
357401 """This function is called if an exception occurs,
358402 but only if we are to stop at or just below this level."""
359403 frame .f_locals ['__exception__' ] = exc_type , exc_value
360404 if type (exc_type ) == type ('' ):
361405 exc_type_name = exc_type
362406 else : exc_type_name = exc_type .__name__
363- self .interaction (frame , exc_traceback , 'exception' )
407+
408+ if exc_type_name == 'RawInputException' :
409+ self .trace .append (dict (event = 'raw_input' , prompt = exc_value .args [0 ]))
410+ self .done = True
411+ else :
412+ self .interaction (frame , exc_traceback , 'exception' )
364413
365414
366415 # General interaction function
@@ -392,10 +441,12 @@ def interaction(self, frame, traceback, event_type):
392441
393442
394443 # debug ...
395- #print('===', file=sys.stderr)
396- #for (e,ln) in self.stack:
397- # print(e.f_code.co_name + ' ' + e.f_code.co_filename + ' ' + str(ln), file=sys.stderr)
398- #print('', file=sys.stderr)
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
449+ '''
399450
400451
401452 # don't trace inside of our __restricted_import__ helper function
@@ -733,7 +784,13 @@ def _runscript(self, script_str):
733784 elif k == '__import__' :
734785 user_builtins [k ] = __restricted_import__
735786 else :
736- user_builtins [k ] = v
787+ if k == 'raw_input' :
788+ user_builtins [k ] = raw_input_wrapper
789+ elif k == 'input' and is_python3 :
790+ # Python 3 input() is Python 2 raw_input()
791+ user_builtins [k ] = raw_input_wrapper
792+ else :
793+ user_builtins [k ] = v
737794
738795
739796 user_stdout = cStringIO .StringIO ()
@@ -806,7 +863,8 @@ def _runscript(self, script_str):
806863 break
807864
808865 if not already_caught :
809- self .trace .append (trace_entry )
866+ if not self .done :
867+ self .trace .append (trace_entry )
810868
811869 raise bdb .BdbQuit # need to forceably STOP execution
812870
@@ -847,11 +905,18 @@ def finalize(self):
847905 return self .finalizer_func (self .executed_script , self .trace )
848906
849907
908+ import json
850909
851910# the MAIN meaty function!!!
852- def exec_script_str (script_str , cumulative_mode , heap_primitives , finalizer_func ):
911+ def exec_script_str (script_str , raw_input_lst_json , cumulative_mode , heap_primitives , finalizer_func ):
853912 logger = PGLogger (cumulative_mode , heap_primitives , finalizer_func )
854913
914+ global raw_input_queue
915+ raw_input_queue = []
916+ if raw_input_lst_json :
917+ # 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 )]
919+
855920 try :
856921 logger ._runscript (script_str )
857922 except bdb .BdbQuit :
@@ -863,13 +928,18 @@ def exec_script_str(script_str, cumulative_mode, heap_primitives, finalizer_func
863928# disables security check and returns the result of finalizer_func
864929# WARNING: ONLY RUN THIS LOCALLY and never over the web, since
865930# security checks are disabled
866- def exec_script_str_local (script_str , cumulative_mode , heap_primitives , finalizer_func ):
931+ def exec_script_str_local (script_str , raw_input_lst_json , cumulative_mode , heap_primitives , finalizer_func ):
867932 logger = PGLogger (cumulative_mode , heap_primitives , finalizer_func , disable_security_checks = True )
868933
934+ global raw_input_queue
935+ raw_input_queue = []
936+ if raw_input_lst_json :
937+ # 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 )]
939+
869940 try :
870941 logger ._runscript (script_str )
871942 except bdb .BdbQuit :
872943 pass
873944 finally :
874945 return logger .finalize ()
875-
0 commit comments