class _PyDevCommandProcessor(object): def __init__(self): self.api = PyDevdAPI() def process_net_command(self, py_db, cmd_id, seq, text): '''Processes a command received from the Java side @param cmd_id: the id of the command @param seq: the sequence of the command @param text: the text received in the command ''' # We can only proceed if the client is already authenticated or if it's the # command to authenticate. if cmd_id != CMD_AUTHENTICATE and not py_db.authentication.is_authenticated( ): cmd = py_db.cmd_factory.make_error_message( seq, 'Client not authenticated.') py_db.writer.add_command(cmd) return meaning = ID_TO_MEANING[str(cmd_id)] # print('Handling %s (%s)' % (meaning, text)) method_name = meaning.lower() on_command = getattr(self, method_name.lower(), None) if on_command is None: # I have no idea what this is all about cmd = py_db.cmd_factory.make_error_message( seq, "unexpected command " + str(cmd_id)) py_db.writer.add_command(cmd) return lock = py_db._main_lock if method_name == 'cmd_thread_dump_to_stderr': # We can skip the main debugger locks for cases where we know it's not needed. lock = NULL with lock: try: cmd = on_command(py_db, cmd_id, seq, text) if cmd is not None: py_db.writer.add_command(cmd) except: if traceback is not None and sys is not None and pydev_log_exception is not None: pydev_log_exception() stream = StringIO() traceback.print_exc(file=stream) cmd = py_db.cmd_factory.make_error_message( seq, "Unexpected exception in process_net_command.\nInitial params: %s. Exception: %s" % (((cmd_id, seq, text), stream.getvalue()))) if cmd is not None: py_db.writer.add_command(cmd) def cmd_authenticate(self, py_db, cmd_id, seq, text): access_token = text py_db.authentication.login(access_token) if py_db.authentication.is_authenticated(): return NetCommand(cmd_id, seq, py_db.authentication.client_access_token) return py_db.cmd_factory.make_error_message( seq, 'Client not authenticated.') def cmd_run(self, py_db, cmd_id, seq, text): return self.api.run(py_db) def cmd_list_threads(self, py_db, cmd_id, seq, text): return self.api.list_threads(py_db, seq) def cmd_get_completions(self, py_db, cmd_id, seq, text): # we received some command to get a variable # the text is: thread_id\tframe_id\tactivation token thread_id, frame_id, _scope, act_tok = text.split('\t', 3) return self.api.request_completions(py_db, seq, thread_id, frame_id, act_tok) def cmd_get_thread_stack(self, py_db, cmd_id, seq, text): # Receives a thread_id and a given timeout, which is the time we should # wait to the provide the stack if a given thread is still not suspended. if '\t' in text: thread_id, timeout = text.split('\t') timeout = float(timeout) else: thread_id = text timeout = .5 # Default timeout is .5 seconds return self.api.request_stack(py_db, seq, thread_id, fmt={}, timeout=timeout) def cmd_set_protocol(self, py_db, cmd_id, seq, text): return self.api.set_protocol(py_db, seq, text.strip()) def cmd_thread_suspend(self, py_db, cmd_id, seq, text): return self.api.request_suspend_thread(py_db, text.strip()) def cmd_version(self, py_db, cmd_id, seq, text): if IS_PY2 and isinstance(text, unicode): text = text.encode('utf-8') # Default based on server process (although ideally the IDE should # provide it). if IS_WINDOWS: ide_os = 'WINDOWS' else: ide_os = 'UNIX' # Breakpoints can be grouped by 'LINE' or by 'ID'. breakpoints_by = 'LINE' splitted = text.split('\t') if len(splitted) == 1: _local_version = splitted elif len(splitted) == 2: _local_version, ide_os = splitted elif len(splitted) == 3: _local_version, ide_os, breakpoints_by = splitted version_msg = self.api.set_ide_os_and_breakpoints_by( py_db, seq, ide_os, breakpoints_by) # Enable thread notifications after the version command is completed. self.api.set_enable_thread_notifications(py_db, True) return version_msg def cmd_thread_run(self, py_db, cmd_id, seq, text): return self.api.request_resume_thread(text.strip()) def _cmd_step(self, py_db, cmd_id, seq, text): return self.api.request_step(py_db, text.strip(), cmd_id) cmd_step_into = _cmd_step cmd_step_into_my_code = _cmd_step cmd_step_over = _cmd_step cmd_step_over_my_code = _cmd_step cmd_step_return = _cmd_step cmd_step_return_my_code = _cmd_step def _cmd_set_next(self, py_db, cmd_id, seq, text): thread_id, line, func_name = text.split('\t', 2) return self.api.request_set_next(py_db, seq, thread_id, cmd_id, line, func_name) cmd_run_to_line = _cmd_set_next cmd_set_next_statement = _cmd_set_next cmd_smart_step_into = _cmd_set_next def cmd_reload_code(self, py_db, cmd_id, seq, text): module_name = text.strip() self.api.request_reload_code(py_db, seq, module_name) def cmd_change_variable(self, py_db, cmd_id, seq, text): # the text is: thread\tstackframe\tFRAME|GLOBAL\tattribute_to_change\tvalue_to_change thread_id, frame_id, scope, attr_and_value = text.split('\t', 3) tab_index = attr_and_value.rindex('\t') attr = attr_and_value[0:tab_index].replace('\t', '.') value = attr_and_value[tab_index + 1:] self.api.request_change_variable(py_db, seq, thread_id, frame_id, scope, attr, value) def cmd_get_variable(self, py_db, cmd_id, seq, text): # we received some command to get a variable # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tattributes* thread_id, frame_id, scopeattrs = text.split('\t', 2) if scopeattrs.find('\t') != -1: # there are attributes beyond scope scope, attrs = scopeattrs.split('\t', 1) else: scope, attrs = (scopeattrs, None) self.api.request_get_variable(py_db, seq, thread_id, frame_id, scope, attrs) def cmd_get_array(self, py_db, cmd_id, seq, text): # Note: untested and unused in pydev # we received some command to get an array variable # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tname\ttemp\troffs\tcoffs\trows\tcols\tformat roffset, coffset, rows, cols, format, thread_id, frame_id, scopeattrs = text.split( '\t', 7) if scopeattrs.find('\t') != -1: # there are attributes beyond scope scope, attrs = scopeattrs.split('\t', 1) else: scope, attrs = (scopeattrs, None) self.api.request_get_array(py_db, seq, roffset, coffset, rows, cols, format, thread_id, frame_id, scope, attrs) def cmd_show_return_values(self, py_db, cmd_id, seq, text): show_return_values = text.split('\t')[1] self.api.set_show_return_values(py_db, int(show_return_values) == 1) def cmd_load_full_value(self, py_db, cmd_id, seq, text): # Note: untested and unused in pydev thread_id, frame_id, scopeattrs = text.split('\t', 2) vars = scopeattrs.split(NEXT_VALUE_SEPARATOR) self.api.request_load_full_value(py_db, seq, thread_id, frame_id, vars) def cmd_get_description(self, py_db, cmd_id, seq, text): # Note: untested and unused in pydev thread_id, frame_id, expression = text.split('\t', 2) self.api.request_get_description(py_db, seq, thread_id, frame_id, expression) def cmd_get_frame(self, py_db, cmd_id, seq, text): thread_id, frame_id, scope = text.split('\t', 2) self.api.request_get_frame(py_db, seq, thread_id, frame_id) def cmd_set_break(self, py_db, cmd_id, seq, text): # func name: 'None': match anything. Empty: match global, specified: only method context. # command to add some breakpoint. # text is filename\tline. Add to breakpoints dictionary suspend_policy = u"NONE" # Can be 'NONE' or 'ALL' is_logpoint = False hit_condition = None if py_db._set_breakpoints_with_id: try: try: breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint, suspend_policy = text.split( u'\t', 9) except ValueError: # not enough values to unpack # No suspend_policy passed (use default). breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint = text.split( u'\t', 8) is_logpoint = is_logpoint == u'True' except ValueError: # not enough values to unpack breakpoint_id, btype, filename, line, func_name, condition, expression = text.split( u'\t', 6) breakpoint_id = int(breakpoint_id) line = int(line) # We must restore new lines and tabs as done in # AbstractDebugTarget.breakpointAdded condition = condition.replace(u"@_@NEW_LINE_CHAR@_@", u'\n').\ replace(u"@_@TAB_CHAR@_@", u'\t').strip() expression = expression.replace(u"@_@NEW_LINE_CHAR@_@", u'\n').\ replace(u"@_@TAB_CHAR@_@", u'\t').strip() else: # Note: this else should be removed after PyCharm migrates to setting # breakpoints by id (and ideally also provides func_name). btype, filename, line, func_name, suspend_policy, condition, expression = text.split( u'\t', 6) # If we don't have an id given for each breakpoint, consider # the id to be the line. breakpoint_id = line = int(line) condition = condition.replace(u"@_@NEW_LINE_CHAR@_@", u'\n'). \ replace(u"@_@TAB_CHAR@_@", u'\t').strip() expression = expression.replace(u"@_@NEW_LINE_CHAR@_@", u'\n'). \ replace(u"@_@TAB_CHAR@_@", u'\t').strip() if condition is not None and (len(condition) <= 0 or condition == u"None"): condition = None if expression is not None and (len(expression) <= 0 or expression == u"None"): expression = None if hit_condition is not None and (len(hit_condition) <= 0 or hit_condition == u"None"): hit_condition = None result = self.api.add_breakpoint(py_db, self.api.filename_to_str(filename), btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint) error_code = result.error_code if error_code: translated_filename = result.translated_filename if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND: pydev_log.critical( 'pydev debugger: warning: Trying to add breakpoint to file that does not exist: %s (will have no effect).' % (translated_filename, )) elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS: pydev_log.critical( 'pydev debugger: warning: Trying to add breakpoint to file that is excluded by filters: %s (will have no effect).' % (translated_filename, )) else: # Shouldn't get here. pydev_log.critical( 'pydev debugger: warning: Breakpoint not validated (reason unknown -- please report as error): %s.' % (translated_filename, )) def cmd_remove_break(self, py_db, cmd_id, seq, text): # command to remove some breakpoint # text is type\file\tid. Remove from breakpoints dictionary breakpoint_type, filename, breakpoint_id = text.split('\t', 2) filename = self.api.filename_to_str(filename) try: breakpoint_id = int(breakpoint_id) except ValueError: pydev_log.critical( 'Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s', breakpoint_id) else: self.api.remove_breakpoint(py_db, filename, breakpoint_type, breakpoint_id) def _cmd_exec_or_evaluate_expression(self, py_db, cmd_id, seq, text): # command to evaluate the given expression # text is: thread\tstackframe\tLOCAL\texpression attr_to_set_result = "" try: thread_id, frame_id, scope, expression, trim, attr_to_set_result = text.split( '\t', 5) except ValueError: thread_id, frame_id, scope, expression, trim = text.split('\t', 4) is_exec = cmd_id == CMD_EXEC_EXPRESSION trim_if_too_big = int(trim) == 1 self.api.request_exec_or_evaluate(py_db, seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result) cmd_evaluate_expression = _cmd_exec_or_evaluate_expression cmd_exec_expression = _cmd_exec_or_evaluate_expression def cmd_console_exec(self, py_db, cmd_id, seq, text): # command to exec expression in console, in case expression is only partially valid 'False' is returned # text is: thread\tstackframe\tLOCAL\texpression thread_id, frame_id, scope, expression = text.split('\t', 3) self.api.request_console_exec(py_db, seq, thread_id, frame_id, expression) def cmd_set_py_exception(self, py_db, cmd_id, seq, text): # Command which receives set of exceptions on which user wants to break the debugger # text is: # # break_on_uncaught; # break_on_caught; # skip_on_exceptions_thrown_in_same_context; # ignore_exceptions_thrown_in_lines_with_ignore_exception; # ignore_libraries; # TypeError;ImportError;zipimport.ZipImportError; # # i.e.: true;true;true;true;true;TypeError;ImportError;zipimport.ZipImportError; # # This API is optional and works 'in bulk' -- it's possible # to get finer-grained control with CMD_ADD_EXCEPTION_BREAK/CMD_REMOVE_EXCEPTION_BREAK # which allows setting caught/uncaught per exception. splitted = text.split(';') py_db.break_on_uncaught_exceptions = {} py_db.break_on_caught_exceptions = {} py_db.break_on_user_uncaught_exceptions = {} if len(splitted) >= 5: if splitted[0] == 'true': break_on_uncaught = True else: break_on_uncaught = False if splitted[1] == 'true': break_on_caught = True else: break_on_caught = False if splitted[2] == 'true': py_db.skip_on_exceptions_thrown_in_same_context = True else: py_db.skip_on_exceptions_thrown_in_same_context = False if splitted[3] == 'true': py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = True else: py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = False if splitted[4] == 'true': ignore_libraries = True else: ignore_libraries = False for exception_type in splitted[5:]: exception_type = exception_type.strip() if not exception_type: continue exception_breakpoint = py_db.add_break_on_exception( exception_type, condition=None, expression=None, notify_on_handled_exceptions=break_on_caught, notify_on_unhandled_exceptions=break_on_uncaught, notify_on_user_unhandled_exceptions= False, # TODO (not currently supported in this API). notify_on_first_raise_only=True, ignore_libraries=ignore_libraries, ) py_db.on_breakpoints_changed() else: sys.stderr.write( "Error when setting exception list. Received: %s\n" % (text, )) def _load_source(self, py_db, cmd_id, seq, text): filename = text filename = self.api.filename_to_str(filename) self.api.request_load_source(py_db, seq, filename) cmd_load_source = _load_source cmd_get_file_contents = _load_source def cmd_load_source_from_frame_id(self, py_db, cmd_id, seq, text): frame_id = text self.api.request_load_source_from_frame_id(py_db, seq, frame_id) def cmd_set_property_trace(self, py_db, cmd_id, seq, text): # Command which receives whether to trace property getter/setter/deleter # text is feature_state(true/false);disable_getter/disable_setter/disable_deleter if text: splitted = text.split(';') if len(splitted) >= 3: if py_db.disable_property_trace is False and splitted[ 0] == 'true': # Replacing property by custom property only when the debugger starts pydevd_traceproperty.replace_builtin_property() py_db.disable_property_trace = True # Enable/Disable tracing of the property getter if splitted[1] == 'true': py_db.disable_property_getter_trace = True else: py_db.disable_property_getter_trace = False # Enable/Disable tracing of the property setter if splitted[2] == 'true': py_db.disable_property_setter_trace = True else: py_db.disable_property_setter_trace = False # Enable/Disable tracing of the property deleter if splitted[3] == 'true': py_db.disable_property_deleter_trace = True else: py_db.disable_property_deleter_trace = False def cmd_add_exception_break(self, py_db, cmd_id, seq, text): # Note that this message has some idiosyncrasies... # # notify_on_handled_exceptions can be 0, 1 or 2 # 0 means we should not stop on handled exceptions. # 1 means we should stop on handled exceptions showing it on all frames where the exception passes. # 2 means we should stop on handled exceptions but we should only notify about it once. # # To ignore_libraries properly, besides setting ignore_libraries to 1, the IDE_PROJECT_ROOTS environment # variable must be set (so, we'll ignore anything not below IDE_PROJECT_ROOTS) -- this is not ideal as # the environment variable may not be properly set if it didn't start from the debugger (we should # create a custom message for that). # # There are 2 global settings which can only be set in CMD_SET_PY_EXCEPTION. Namely: # # py_db.skip_on_exceptions_thrown_in_same_context # - If True, we should only show the exception in a caller, not where it was first raised. # # py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception # - If True exceptions thrown in lines with '@IgnoreException' will not be shown. condition = "" expression = "" if text.find('\t') != -1: try: exception, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split( '\t', 5) except: exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split( '\t', 3) else: exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text, 0, 0, 0 condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').replace("@_@TAB_CHAR@_@", '\t').strip() if condition is not None and (len(condition) == 0 or condition == "None"): condition = None expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').replace("@_@TAB_CHAR@_@", '\t').strip() if expression is not None and (len(expression) == 0 or expression == "None"): expression = None if exception.find('-') != -1: breakpoint_type, exception = exception.split('-') else: breakpoint_type = 'python' if breakpoint_type == 'python': self.api.add_python_exception_breakpoint( py_db, exception, condition, expression, notify_on_handled_exceptions=int(notify_on_handled_exceptions) > 0, notify_on_unhandled_exceptions=int( notify_on_unhandled_exceptions) == 1, notify_on_user_unhandled_exceptions= 0, # TODO (not currently supported in this API). notify_on_first_raise_only=int( notify_on_handled_exceptions) == 2, ignore_libraries=int(ignore_libraries) > 0, ) else: self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type, exception) def cmd_remove_exception_break(self, py_db, cmd_id, seq, text): exception = text if exception.find('-') != -1: exception_type, exception = exception.split('-') else: exception_type = 'python' if exception_type == 'python': self.api.remove_python_exception_breakpoint(py_db, exception) else: self.api.remove_plugins_exception_breakpoint( py_db, exception_type, exception) def cmd_add_django_exception_break(self, py_db, cmd_id, seq, text): self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type='django', exception=text) def cmd_remove_django_exception_break(self, py_db, cmd_id, seq, text): self.api.remove_plugins_exception_breakpoint(py_db, exception_type='django', exception=text) def cmd_evaluate_console_expression(self, py_db, cmd_id, seq, text): # Command which takes care for the debug console communication if text != "": thread_id, frame_id, console_command = text.split('\t', 2) console_command, line = console_command.split('\t') if console_command == 'EVALUATE': int_cmd = InternalEvaluateConsoleExpression(seq, thread_id, frame_id, line, buffer_output=True) elif console_command == 'EVALUATE_UNBUFFERED': int_cmd = InternalEvaluateConsoleExpression( seq, thread_id, frame_id, line, buffer_output=False) elif console_command == 'GET_COMPLETIONS': int_cmd = InternalConsoleGetCompletions( seq, thread_id, frame_id, line) else: raise ValueError('Unrecognized command: %s' % (console_command, )) py_db.post_internal_command(int_cmd, thread_id) def cmd_run_custom_operation(self, py_db, cmd_id, seq, text): # Command which runs a custom operation if text != "": try: location, custom = text.split('||', 1) except: sys.stderr.write( 'Custom operation now needs a || separator. Found: %s\n' % (text, )) raise thread_id, frame_id, scopeattrs = location.split('\t', 2) if scopeattrs.find( '\t') != -1: # there are attributes beyond scope scope, attrs = scopeattrs.split('\t', 1) else: scope, attrs = (scopeattrs, None) # : style: EXECFILE or EXEC # : encoded_code_or_file: file to execute or code # : fname: name of function to be executed in the resulting namespace style, encoded_code_or_file, fnname = custom.split('\t', 3) int_cmd = InternalRunCustomOperation(seq, thread_id, frame_id, scope, attrs, style, encoded_code_or_file, fnname) py_db.post_internal_command(int_cmd, thread_id) def cmd_ignore_thrown_exception_at(self, py_db, cmd_id, seq, text): if text: replace = 'REPLACE:' # Not all 3.x versions support u'REPLACE:', so, doing workaround. if not IS_PY3K: replace = unicode(replace) # noqa if text.startswith(replace): text = text[8:] py_db.filename_to_lines_where_exceptions_are_ignored.clear() if text: for line in text.split( '||'): # Can be bulk-created (one in each line) filename, line_number = line.split('|') filename = self.api.filename_to_server(filename) if os.path.exists(filename): lines_ignored = py_db.filename_to_lines_where_exceptions_are_ignored.get( filename) if lines_ignored is None: lines_ignored = py_db.filename_to_lines_where_exceptions_are_ignored[ filename] = {} lines_ignored[int(line_number)] = 1 else: sys.stderr.write('pydev debugger: warning: trying to ignore exception thrown'\ ' on file that does not exist: %s (will have no effect)\n' % (filename,)) def cmd_enable_dont_trace(self, py_db, cmd_id, seq, text): if text: true_str = 'true' # Not all 3.x versions support u'str', so, doing workaround. if not IS_PY3K: true_str = unicode(true_str) # noqa mode = text.strip() == true_str pydevd_dont_trace.trace_filter(mode) def cmd_redirect_output(self, py_db, cmd_id, seq, text): if text: py_db.enable_output_redirection('STDOUT' in text, 'STDERR' in text) def cmd_get_next_statement_targets(self, py_db, cmd_id, seq, text): thread_id, frame_id = text.split('\t', 1) py_db.post_method_as_internal_command( thread_id, internal_get_next_statement_targets, seq, thread_id, frame_id) def cmd_set_project_roots(self, py_db, cmd_id, seq, text): self.api.set_project_roots(py_db, text.split(u'\t')) def cmd_thread_dump_to_stderr(self, py_db, cmd_id, seq, text): pydevd_utils.dump_threads() def cmd_stop_on_start(self, py_db, cmd_id, seq, text): if text.strip() in ('True', 'true', '1'): self.api.stop_on_entry() def cmd_pydevd_json_config(self, py_db, cmd_id, seq, text): # Expected to receive a json string as: # { # 'skip_suspend_on_breakpoint_exception': [<exception names where we should suspend>], # 'skip_print_breakpoint_exception': [<exception names where we should print>], # 'multi_threads_single_notification': bool, # } msg = json.loads(text.strip()) if 'skip_suspend_on_breakpoint_exception' in msg: py_db.skip_suspend_on_breakpoint_exception = tuple( get_exception_class(x) for x in msg['skip_suspend_on_breakpoint_exception']) if 'skip_print_breakpoint_exception' in msg: py_db.skip_print_breakpoint_exception = tuple( get_exception_class(x) for x in msg['skip_print_breakpoint_exception']) if 'multi_threads_single_notification' in msg: py_db.multi_threads_single_notification = msg[ 'multi_threads_single_notification'] def cmd_get_exception_details(self, py_db, cmd_id, seq, text): thread_id = text t = pydevd_find_thread_by_id(thread_id) frame = None if t and not getattr(t, 'pydev_do_not_trace', None): additional_info = set_additional_thread_info(t) frame = additional_info.get_topmost_frame(t) try: return py_db.cmd_factory.make_get_exception_details_message( py_db, seq, thread_id, frame) finally: frame = None t = None
class PyDevJsonCommandProcessor(object): def __init__(self, from_json): self.from_json = from_json self.api = PyDevdAPI() self._debug_options = {} self._next_breakpoint_id = partial(next, itertools.count(0)) self._goto_targets_map = IDMap() self._launch_or_attach_request_done = False def process_net_command_json(self, py_db, json_contents, send_response=True): ''' Processes a debug adapter protocol json command. ''' DEBUG = False try: request = self.from_json(json_contents, update_ids_from_dap=True) except KeyError as e: request = self.from_json(json_contents, update_ids_from_dap=False) error_msg = str(e) if error_msg.startswith("'") and error_msg.endswith("'"): error_msg = error_msg[1:-1] # This means a failure updating ids from the DAP (the client sent a key we didn't send). def on_request(py_db, request): error_response = { 'type': 'response', 'request_seq': request.seq, 'success': False, 'command': request.command, 'message': error_msg, } return NetCommand(CMD_RETURN, 0, error_response, is_json=True) else: if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: pydev_log.info('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True), )) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(), ) on_request = getattr(self, method_name, None) if on_request is None: print( 'Unhandled: %s not available in PyDevJsonCommandProcessor.\n' % (method_name, )) return if DEBUG: print( 'Handled in pydevd: %s (in PyDevJsonCommandProcessor).\n' % (method_name, )) with py_db._main_lock: if request.__class__ == PydevdAuthorizeRequest: authorize_request = request # : :type authorize_request: PydevdAuthorizeRequest access_token = authorize_request.arguments.debugServerAccessToken py_db.authentication.login(access_token) if not py_db.authentication.is_authenticated(): response = Response(request.seq, success=False, command=request.command, message='Client not authenticated.', body={}) cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) py_db.writer.add_command(cmd) return cmd = on_request(py_db, request) if cmd is not None and send_response: py_db.writer.add_command(cmd) def on_pydevdauthorize_request(self, py_db, request): ide_access_token = py_db.authentication.ide_access_token body = {'clientAccessToken': None} if ide_access_token: body['clientAccessToken'] = ide_access_token response = pydevd_base_schema.build_response(request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_initialize_request(self, py_db, request): body = Capabilities( # Supported. supportsConfigurationDoneRequest=True, supportsConditionalBreakpoints=True, supportsHitConditionalBreakpoints=True, supportsEvaluateForHovers=True, supportsSetVariable=True, supportsGotoTargetsRequest=True, supportsCompletionsRequest=True, supportsModulesRequest=True, supportsExceptionOptions=True, supportsValueFormattingOptions=True, supportsExceptionInfoRequest=True, supportTerminateDebuggee=True, supportsDelayedStackTraceLoading=True, supportsLogPoints=True, supportsSetExpression=True, supportsTerminateRequest=True, exceptionBreakpointFilters=[ { 'filter': 'raised', 'label': 'Raised Exceptions', 'default': False }, { 'filter': 'uncaught', 'label': 'Uncaught Exceptions', 'default': True }, ], # Not supported. supportsFunctionBreakpoints=False, supportsStepBack=False, supportsRestartFrame=False, supportsStepInTargetsRequest=False, supportsRestartRequest=False, supportsLoadedSourcesRequest=False, supportsTerminateThreadsRequest=False, supportsDataBreakpoints=False, supportsReadMemoryRequest=False, supportsDisassembleRequest=False, additionalModuleColumns=[], completionTriggerCharacters=[], supportedChecksumAlgorithms=[], ).to_dict() # Non-standard capabilities/info below. body['supportsDebuggerProperties'] = True body['pydevd'] = pydevd_info = {} pydevd_info['processId'] = os.getpid() self.api.notify_initialize(py_db) response = pydevd_base_schema.build_response(request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_configurationdone_request(self, py_db, request): ''' :param ConfigurationDoneRequest request: ''' if not self._launch_or_attach_request_done: pydev_log.critical( 'Missing launch request or attach request before configuration done request.' ) self.api.run(py_db) self.api.notify_configuration_done(py_db) configuration_done_response = pydevd_base_schema.build_response( request) return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True) def on_threads_request(self, py_db, request): ''' :param ThreadsRequest request: ''' return self.api.list_threads(py_db, request.seq) def on_terminate_request(self, py_db, request): ''' :param TerminateRequest request: ''' self._request_terminate_process(py_db) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def _request_terminate_process(self, py_db): self.api.request_terminate_process(py_db) def on_completions_request(self, py_db, request): ''' :param CompletionsRequest request: ''' arguments = request.arguments # : :type arguments: CompletionsArguments seq = request.seq text = arguments.text frame_id = arguments.frameId thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( frame_id) if thread_id is None: body = CompletionsResponseBody([]) variables_response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Thread to get completions seems to have resumed already.' }) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) # Note: line and column are 1-based (convert to 0-based for pydevd). column = arguments.column - 1 if arguments.line is None: # line is optional line = -1 else: line = arguments.line - 1 self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column) def _resolve_remote_root(self, local_root, remote_root): if remote_root == '.': cwd = os.getcwd() append_pathsep = local_root.endswith('\\') or local_root.endswith( '/') return cwd + (os.path.sep if append_pathsep else '') return remote_root def _set_debug_options(self, py_db, args, start_reason): rules = args.get('rules') stepping_resumes_all_threads = args.get('steppingResumesAllThreads', True) self.api.set_stepping_resumes_all_threads( py_db, stepping_resumes_all_threads) terminate_child_processes = args.get('terminateChildProcesses', True) self.api.set_terminate_child_processes(py_db, terminate_child_processes) exclude_filters = [] if rules is not None: exclude_filters = _convert_rules_to_exclude_filters( rules, self.api.filename_to_server, lambda msg: self.api.send_error_message(py_db, msg)) self.api.set_exclude_filters(py_db, exclude_filters) self._debug_options = _extract_debug_options( args.get('options'), args.get('debugOptions'), ) self._debug_options['args'] = args debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False) self.api.set_use_libraries_filter(py_db, not debug_stdlib) path_mappings = [] for pathMapping in args.get('pathMappings', []): localRoot = pathMapping.get('localRoot', '') remoteRoot = pathMapping.get('remoteRoot', '') remoteRoot = self._resolve_remote_root(localRoot, remoteRoot) if (localRoot != '') and (remoteRoot != ''): path_mappings.append((localRoot, remoteRoot)) if bool(path_mappings): pydevd_file_utils.setup_client_server_paths(path_mappings) if self._debug_options.get('REDIRECT_OUTPUT', False): py_db.enable_output_redirection(True, True) else: py_db.enable_output_redirection(False, False) self.api.set_show_return_values( py_db, self._debug_options.get('SHOW_RETURN_VALUE', False)) if not self._debug_options.get('BREAK_SYSTEMEXIT_ZERO', False): ignore_system_exit_codes = [0] if self._debug_options.get('DJANGO_DEBUG', False): ignore_system_exit_codes += [3] self.api.set_ignore_system_exit_codes(py_db, ignore_system_exit_codes) if self._debug_options.get('STOP_ON_ENTRY', False) and start_reason == 'launch': self.api.stop_on_entry() def _send_process_event(self, py_db, start_method): if len(sys.argv) > 0: name = sys.argv[0] else: name = '' if isinstance(name, bytes): name = name.decode(file_system_encoding, 'replace') name = name.encode('utf-8') body = ProcessEventBody( name=name, systemProcessId=os.getpid(), isLocalProcess=True, startMethod=start_method, ) event = ProcessEvent(body) py_db.writer.add_command( NetCommand(CMD_PROCESS_EVENT, 0, event, is_json=True)) def _handle_launch_or_attach_request(self, py_db, request, start_reason): self._send_process_event(py_db, start_reason) self._launch_or_attach_request_done = True self.api.set_enable_thread_notifications(py_db, True) self._set_debug_options(py_db, request.arguments.kwargs, start_reason=start_reason) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_launch_request(self, py_db, request): ''' :param LaunchRequest request: ''' return self._handle_launch_or_attach_request(py_db, request, start_reason='launch') def on_attach_request(self, py_db, request): ''' :param AttachRequest request: ''' return self._handle_launch_or_attach_request(py_db, request, start_reason='attach') def on_pause_request(self, py_db, request): ''' :param PauseRequest request: ''' arguments = request.arguments # : :type arguments: PauseArguments thread_id = arguments.threadId self.api.request_suspend_thread(py_db, thread_id=thread_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_continue_request(self, py_db, request): ''' :param ContinueRequest request: ''' arguments = request.arguments # : :type arguments: ContinueArguments thread_id = arguments.threadId def on_resumed(): body = {'allThreadsContinued': thread_id == '*'} response = pydevd_base_schema.build_response(request, kwargs={'body': body}) cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) py_db.writer.add_command(cmd) # Only send resumed notification when it has actually resumed! # (otherwise the user could send a continue, receive the notification and then # request a new pause which would be paused without sending any notification as # it didn't really run in the first place). py_db.threads_suspended_single_notification.add_on_resumed_callback( on_resumed) self.api.request_resume_thread(thread_id) def on_next_request(self, py_db, request): ''' :param NextRequest request: ''' arguments = request.arguments # : :type arguments: NextArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_OVER_MY_CODE else: step_cmd_id = CMD_STEP_OVER self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_stepin_request(self, py_db, request): ''' :param StepInRequest request: ''' arguments = request.arguments # : :type arguments: StepInArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_INTO_MY_CODE else: step_cmd_id = CMD_STEP_INTO self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_stepout_request(self, py_db, request): ''' :param StepOutRequest request: ''' arguments = request.arguments # : :type arguments: StepOutArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_RETURN_MY_CODE else: step_cmd_id = CMD_STEP_RETURN self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def _get_hit_condition_expression(self, hit_condition): '''Following hit condition values are supported * x or == x when breakpoint is hit x times * >= x when breakpoint is hit more than or equal to x times * % x when breakpoint is hit multiple of x times Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits ''' if not hit_condition: return None expr = hit_condition.strip() try: int(expr) return '@HIT@ == {}'.format(expr) except ValueError: pass if expr.startswith('%'): return '@HIT@ {} == 0'.format(expr) if expr.startswith('==') or \ expr.startswith('>') or \ expr.startswith('<'): return '@HIT@ {}'.format(expr) return hit_condition def on_disconnect_request(self, py_db, request): ''' :param DisconnectRequest request: ''' if request.arguments.terminateDebuggee: self._request_terminate_process(py_db) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) self._launch_or_attach_request_done = False py_db.enable_output_redirection(False, False) self.api.request_disconnect(py_db, resume_threads=True) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_setbreakpoints_request(self, py_db, request): ''' :param SetBreakpointsRequest request: ''' if not self._launch_or_attach_request_done: # Note that to validate the breakpoints we need the launch request to be done already # (otherwise the filters wouldn't be set for the breakpoint validation). body = SetBreakpointsResponseBody([]) response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Breakpoints may only be set after the launch request is received.' }) return NetCommand(CMD_RETURN, 0, response, is_json=True) arguments = request.arguments # : :type arguments: SetBreakpointsArguments # TODO: Path is optional here it could be source reference. filename = self.api.filename_to_str(arguments.source.path) func_name = 'None' self.api.remove_all_breakpoints(py_db, filename) btype = 'python-line' suspend_policy = 'ALL' if not filename.lower().endswith( '.py'): # Note: check based on original file, not mapping. if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django-line' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2-line' breakpoints_set = [] for source_breakpoint in arguments.breakpoints: source_breakpoint = SourceBreakpoint(**source_breakpoint) line = source_breakpoint.line condition = source_breakpoint.condition breakpoint_id = line hit_condition = self._get_hit_condition_expression( source_breakpoint.hitCondition) log_message = source_breakpoint.logMessage if not log_message: is_logpoint = None expression = None else: is_logpoint = True expression = convert_dap_log_message_to_expression(log_message) result = self.api.add_breakpoint(py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint, adjust_line=True) error_code = result.error_code if error_code: if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND: error_msg = 'Breakpoint in file that does not exist.' elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS: error_msg = 'Breakpoint in file excluded by filters.' if py_db.get_use_libraries_filter(): error_msg += ( '\nNote: may be excluded because of "justMyCode" option (default == true).' 'Try setting \"justMyCode\": false in the debug configuration (e.g., launch.json).' ) else: # Shouldn't get here. error_msg = 'Breakpoint not validated (reason unknown -- please report as bug).' breakpoints_set.append( pydevd_schema.Breakpoint( verified=False, line=result.translated_line, message=error_msg, source=arguments.source).to_dict()) else: # Note that the id is made up (the id for pydevd is unique only within a file, so, the # line is used for it). # Also, the id is currently not used afterwards, so, we don't even keep a mapping. breakpoints_set.append( pydevd_schema.Breakpoint( verified=True, id=self._next_breakpoint_id(), line=result.translated_line, source=arguments.source).to_dict()) body = {'breakpoints': breakpoints_set} set_breakpoints_response = pydevd_base_schema.build_response( request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) def on_setexceptionbreakpoints_request(self, py_db, request): ''' :param SetExceptionBreakpointsRequest request: ''' # : :type arguments: SetExceptionBreakpointsArguments arguments = request.arguments filters = arguments.filters exception_options = arguments.exceptionOptions self.api.remove_all_exception_breakpoints(py_db) # Can't set these in the DAP. condition = None expression = None notify_on_first_raise_only = False ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0 if exception_options: break_raised = True break_uncaught = True for option in exception_options: option = ExceptionOptions(**option) if not option.path: continue notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0 notify_on_unhandled_exceptions = 1 if option.breakMode in ( 'unhandled', 'userUnhandled') else 0 exception_paths = option.path exception_names = [] if len(exception_paths) == 0: continue elif len(exception_paths) == 1: if 'Python Exceptions' in exception_paths[0]['names']: exception_names = ['BaseException'] else: path_iterator = iter(exception_paths) if 'Python Exceptions' in next(path_iterator)['names']: for path in path_iterator: for ex_name in path['names']: exception_names.append(ex_name) for exception_name in exception_names: self.api.add_python_exception_breakpoint( py_db, exception_name, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, notify_on_first_raise_only, ignore_libraries) else: break_raised = 'raised' in filters break_uncaught = 'uncaught' in filters if break_raised or break_uncaught: notify_on_handled_exceptions = 1 if break_raised else 0 notify_on_unhandled_exceptions = 1 if break_uncaught else 0 exception = 'BaseException' self.api.add_python_exception_breakpoint( py_db, exception, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, notify_on_first_raise_only, ignore_libraries) if break_raised or break_uncaught: btype = None if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2' if btype: self.api.add_plugins_exception_breakpoint( py_db, btype, 'BaseException' ) # Note: Exception name could be anything here. # Note: no body required on success. set_breakpoints_response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) def on_stacktrace_request(self, py_db, request): ''' :param StackTraceRequest request: ''' # : :type stack_trace_arguments: StackTraceArguments stack_trace_arguments = request.arguments thread_id = stack_trace_arguments.threadId start_frame = stack_trace_arguments.startFrame levels = stack_trace_arguments.levels fmt = stack_trace_arguments.format if hasattr(fmt, 'to_dict'): fmt = fmt.to_dict() self.api.request_stack(py_db, request.seq, thread_id, fmt=fmt, start_frame=start_frame, levels=levels) def on_exceptioninfo_request(self, py_db, request): ''' :param ExceptionInfoRequest request: ''' # : :type exception_into_arguments: ExceptionInfoArguments exception_into_arguments = request.arguments thread_id = exception_into_arguments.threadId max_frames = int(self._debug_options['args'].get( 'maxExceptionStackFrames', 0)) self.api.request_exception_info_json(py_db, request, thread_id, max_frames) def on_scopes_request(self, py_db, request): ''' Scopes are the top-level items which appear for a frame (so, we receive the frame id and provide the scopes it has). :param ScopesRequest request: ''' frame_id = request.arguments.frameId variables_reference = frame_id scopes = [Scope('Locals', int(variables_reference), False).to_dict()] body = ScopesResponseBody(scopes) scopes_response = pydevd_base_schema.build_response( request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True) def on_evaluate_request(self, py_db, request): ''' :param EvaluateRequest request: ''' # : :type arguments: EvaluateArguments arguments = request.arguments if arguments.frameId is None: self.api.request_exec_or_evaluate_json(py_db, request, thread_id='*') else: thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( arguments.frameId) if thread_id is not None: self.api.request_exec_or_evaluate_json(py_db, request, thread_id) else: body = EvaluateResponseBody('', 0) response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Unable to find thread for evaluation.' }) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_setexpression_request(self, py_db, request): # : :type arguments: SetExpressionArguments arguments = request.arguments thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( arguments.frameId) if thread_id is not None: self.api.request_set_expression_json(py_db, request, thread_id) else: body = SetExpressionResponseBody('') response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Unable to find thread to set expression.' }) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_variables_request(self, py_db, request): ''' Variables can be asked whenever some place returned a variables reference (so, it can be a scope gotten from on_scopes_request, the result of some evaluation, etc.). Note that in the DAP the variables reference requires a unique int... the way this works for pydevd is that an instance is generated for that specific variable reference and we use its id(instance) to identify it to make sure all items are unique (and the actual {id->instance} is added to a dict which is only valid while the thread is suspended and later cleared when the related thread resumes execution). see: SuspendedFramesManager :param VariablesRequest request: ''' arguments = request.arguments # : :type arguments: VariablesArguments variables_reference = arguments.variablesReference thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: self.api.request_get_variable_json(py_db, request, thread_id) else: variables = [] body = VariablesResponseBody(variables) variables_response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Unable to find thread to evaluate variable reference.' }) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_setvariable_request(self, py_db, request): arguments = request.arguments # : :type arguments: SetVariableArguments variables_reference = arguments.variablesReference if arguments.name.startswith('(return) '): response = pydevd_base_schema.build_response( request, kwargs={ 'body': SetVariableResponseBody(''), 'success': False, 'message': 'Cannot change return value' }) return NetCommand(CMD_RETURN, 0, response, is_json=True) thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: self.api.request_change_variable_json(py_db, request, thread_id) else: response = pydevd_base_schema.build_response( request, kwargs={ 'body': SetVariableResponseBody(''), 'success': False, 'message': 'Unable to find thread to evaluate variable reference.' }) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_modules_request(self, py_db, request): modules_manager = py_db.cmd_factory.modules_manager # : :type modules_manager: ModulesManager modules_info = modules_manager.get_modules_info() body = ModulesResponseBody(modules_info) variables_response = pydevd_base_schema.build_response( request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_source_request(self, py_db, request): ''' :param SourceRequest request: ''' source_reference = request.arguments.sourceReference server_filename = None content = None if source_reference != 0: server_filename = pydevd_file_utils.get_server_filename_from_source_reference( source_reference) if server_filename: # Try direct file access first - it's much faster when available. try: with open(server_filename, 'r') as stream: content = stream.read() except: pass if content is None: # File might not exist at all, or we might not have a permission to read it, # but it might also be inside a zipfile, or an IPython cell. In this case, # linecache might still be able to retrieve the source. lines = (linecache.getline(server_filename, i) for i in itertools.count(1)) lines = itertools.takewhile( bool, lines) # empty lines are '\n', EOF is '' # If we didn't get at least one line back, reset it to None so that it's # reported as error below, and not as an empty file. content = ''.join(lines) or None body = SourceResponseBody(content or '') response_args = {'body': body} if content is None: if source_reference == 0: message = 'Source unavailable' elif server_filename: message = 'Unable to retrieve source for %s' % ( server_filename, ) else: message = 'Invalid sourceReference %d' % (source_reference, ) response_args.update({'success': False, 'message': message}) response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_gototargets_request(self, py_db, request): path = request.arguments.source.path line = request.arguments.line target_id = self._goto_targets_map.obtain_key((path, line)) target = { 'id': target_id, 'label': '%s:%s' % (path, line), 'line': line } body = GotoTargetsResponseBody(targets=[target]) response_args = {'body': body} response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_goto_request(self, py_db, request): target_id = int(request.arguments.targetId) thread_id = request.arguments.threadId try: _, line = self._goto_targets_map.obtain_value(target_id) except KeyError: response = pydevd_base_schema.build_response( request, kwargs={ 'body': {}, 'success': False, 'message': 'Unknown goto target id: %d' % (target_id, ), }) return NetCommand(CMD_RETURN, 0, response, is_json=True) self.api.request_set_next(py_db, request.seq, thread_id, CMD_SET_NEXT_STATEMENT, line, '*') # See 'NetCommandFactoryJson.make_set_next_stmnt_status_message' for response return None def on_setdebuggerproperty_request(self, py_db, request): args = request.arguments # : :type args: SetDebuggerPropertyArguments if args.ideOS is not None: self.api.set_ide_os(args.ideOS) if args.dontTraceStartPatterns is not None and args.dontTraceEndPatterns is not None: start_patterns = tuple(args.dontTraceStartPatterns) end_patterns = tuple(args.dontTraceEndPatterns) self.api.set_dont_trace_start_end_patterns(py_db, start_patterns, end_patterns) if args.skipSuspendOnBreakpointException is not None: py_db.skip_suspend_on_breakpoint_exception = tuple( get_exception_class(x) for x in args.skipSuspendOnBreakpointException) if args.skipPrintBreakpointException is not None: py_db.skip_print_breakpoint_exception = tuple( get_exception_class(x) for x in args.skipPrintBreakpointException) if args.multiThreadsSingleNotification is not None: py_db.multi_threads_single_notification = args.multiThreadsSingleNotification # TODO: Support other common settings. Note that not all of these might be relevant to python. # JustMyCodeStepping: 0 or 1 # AllowOutOfProcessSymbols: 0 or 1 # DisableJITOptimization: 0 or 1 # InterpreterOptions: 0 or 1 # StopOnExceptionCrossingManagedBoundary: 0 or 1 # WarnIfNoUserCodeOnLaunch: 0 or 1 # EnableStepFiltering: true of false response = pydevd_base_schema.build_response(request, kwargs={'body': {}}) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_pydevdsysteminfo_request(self, py_db, request): try: pid = os.getpid() except AttributeError: pid = None try: ppid = os.getppid() except AttributeError: ppid = None try: impl_desc = platform.python_implementation() except AttributeError: impl_desc = PY_IMPL_NAME py_info = pydevd_schema.PydevdPythonInfo( version=PY_VERSION_STR, implementation=pydevd_schema.PydevdPythonImplementationInfo( name=PY_IMPL_NAME, version=PY_IMPL_VERSION_STR, description=impl_desc, )) platform_info = pydevd_schema.PydevdPlatformInfo(name=sys.platform) process_info = pydevd_schema.PydevdProcessInfo( pid=pid, ppid=ppid, executable=sys.executable, bitness=64 if IS_64BIT_PROCESS else 32, ) body = { 'python': py_info, 'platform': platform_info, 'process': process_info, } response = pydevd_base_schema.build_response(request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_setpydevdsourcemap_request(self, py_db, request): args = request.arguments # : :type args: SetPydevdSourceMapArguments SourceMappingEntry = self.api.SourceMappingEntry path = args.source.path source_maps = args.pydevdSourceMaps # : :type source_map: PydevdSourceMap new_mappings = [ SourceMappingEntry( source_map['line'], source_map['endLine'], source_map['runtimeLine'], self.api.filename_to_str(source_map['runtimeSource']['path'])) for source_map in source_maps ] error_msg = self.api.set_source_mapping(py_db, path, new_mappings) if error_msg: response = pydevd_base_schema.build_response(request, kwargs={ 'body': {}, 'success': False, 'message': error_msg, }) return NetCommand(CMD_RETURN, 0, response, is_json=True) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True)
class _PyDevJsonCommandProcessor(object): def __init__(self, from_json): self.from_json = from_json self.api = PyDevdAPI() self._debug_options = {} self._next_breakpoint_id = partial(next, itertools.count(0)) self._goto_targets_map = IDMap() def process_net_command_json(self, py_db, json_contents): ''' Processes a debug adapter protocol json command. ''' DEBUG = False request = self.from_json(json_contents, update_ids_from_dap=True) if DEBUG: print('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True), )) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(), ) on_request = getattr(self, method_name, None) if on_request is None: print( 'Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name, )) return if DEBUG: print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name, )) py_db._main_lock.acquire() try: cmd = on_request(py_db, request) if cmd is not None: py_db.writer.add_command(cmd) finally: py_db._main_lock.release() def on_configurationdone_request(self, py_db, request): ''' :param ConfigurationDoneRequest request: ''' self.api.run(py_db) configuration_done_response = pydevd_base_schema.build_response( request) return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True) def on_threads_request(self, py_db, request): ''' :param ThreadsRequest request: ''' return self.api.list_threads(py_db, request.seq) def on_completions_request(self, py_db, request): ''' :param CompletionsRequest request: ''' arguments = request.arguments # : :type arguments: CompletionsArguments seq = request.seq text = arguments.text frame_id = arguments.frameId thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( frame_id) # Note: line and column are 1-based (convert to 0-based for pydevd). column = arguments.column - 1 if arguments.line is None: # line is optional line = -1 else: line = arguments.line - 1 self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column) def _set_debug_options(self, py_db, args): rules = args.get('rules') exclude_filters = [] if rules is not None: exclude_filters = _convert_rules_to_exclude_filters( rules, self.api.filename_to_server, lambda msg: self.api.send_error_message(py_db, msg)) self.api.set_exclude_filters(py_db, exclude_filters) self._debug_options = _extract_debug_options( args.get('options'), args.get('debugOptions'), ) debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False) self.api.set_use_libraries_filter(py_db, not debug_stdlib) def on_launch_request(self, py_db, request): ''' :param LaunchRequest request: ''' self._set_debug_options(py_db, request.arguments.kwargs) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_attach_request(self, py_db, request): ''' :param AttachRequest request: ''' self._set_debug_options(py_db, request.arguments.kwargs) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_pause_request(self, py_db, request): ''' :param PauseRequest request: ''' arguments = request.arguments # : :type arguments: PauseArguments thread_id = arguments.threadId self.api.request_suspend_thread(py_db, thread_id=thread_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_continue_request(self, py_db, request): ''' :param ContinueRequest request: ''' arguments = request.arguments # : :type arguments: ContinueArguments thread_id = arguments.threadId def on_resumed(): body = {'allThreadsContinued': thread_id == '*'} response = pydevd_base_schema.build_response(request, kwargs={'body': body}) cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) py_db.writer.add_command(cmd) # Only send resumed notification when it has actually resumed! # (otherwise the user could send a continue, receive the notification and then # request a new pause which would be paused without sending any notification as # it didn't really run in the first place). py_db.threads_suspended_single_notification.add_on_resumed_callback( on_resumed) self.api.request_resume_thread(thread_id) def on_next_request(self, py_db, request): ''' :param NextRequest request: ''' arguments = request.arguments # : :type arguments: NextArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_OVER_MY_CODE else: step_cmd_id = CMD_STEP_OVER self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_stepin_request(self, py_db, request): ''' :param StepInRequest request: ''' arguments = request.arguments # : :type arguments: StepInArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_INTO_MY_CODE else: step_cmd_id = CMD_STEP_INTO self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_stepout_request(self, py_db, request): ''' :param StepOutRequest request: ''' arguments = request.arguments # : :type arguments: StepOutArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_RETURN_MY_CODE else: step_cmd_id = CMD_STEP_RETURN self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def _get_hit_condition_expression(self, hit_condition): '''Following hit condition values are supported * x or == x when breakpoint is hit x times * >= x when breakpoint is hit more than or equal to x times * % x when breakpoint is hit multiple of x times Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits ''' if not hit_condition: return None expr = hit_condition.strip() try: int(expr) return '@HIT@ == {}'.format(expr) except ValueError: pass if expr.startswith('%'): return '@HIT@ {} == 0'.format(expr) if expr.startswith('==') or \ expr.startswith('>') or \ expr.startswith('<'): return '@HIT@ {}'.format(expr) return hit_condition def on_disconnect_request(self, py_db, request): ''' :param DisconnectRequest request: ''' self.api.remove_all_breakpoints(py_db, filename='*') self.api.remove_all_exception_breakpoints(py_db) self.api.request_resume_thread(thread_id='*') response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_setbreakpoints_request(self, py_db, request): ''' :param SetBreakpointsRequest request: ''' arguments = request.arguments # : :type arguments: SetBreakpointsArguments filename = arguments.source.path filename = self.api.filename_to_server(filename) func_name = 'None' self.api.remove_all_breakpoints(py_db, filename) btype = 'python-line' suspend_policy = 'ALL' if not filename.lower().endswith('.py'): if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django-line' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2-line' breakpoints_set = [] for source_breakpoint in arguments.breakpoints: source_breakpoint = SourceBreakpoint(**source_breakpoint) line = source_breakpoint.line condition = source_breakpoint.condition breakpoint_id = line hit_condition = self._get_hit_condition_expression( source_breakpoint.hitCondition) log_message = source_breakpoint.logMessage if not log_message: is_logpoint = None expression = None else: is_logpoint = True expression = convert_dap_log_message_to_expression(log_message) self.api.add_breakpoint(py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint) # Note that the id is made up (the id for pydevd is unique only within a file, so, the # line is used for it). # Also, the id is currently not used afterwards, so, we don't even keep a mapping. breakpoints_set.append({ 'id': self._next_breakpoint_id(), 'verified': True, 'line': line }) body = {'breakpoints': breakpoints_set} set_breakpoints_response = pydevd_base_schema.build_response( request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) def on_setexceptionbreakpoints_request(self, py_db, request): ''' :param SetExceptionBreakpointsRequest request: ''' # : :type arguments: SetExceptionBreakpointsArguments arguments = request.arguments filters = arguments.filters exception_options = arguments.exceptionOptions self.api.remove_all_exception_breakpoints(py_db) # Can't set these in the DAP. condition = None expression = None notify_on_first_raise_only = False ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0 if exception_options: break_raised = True break_uncaught = True for option in exception_options: option = ExceptionOptions(**option) if not option.path: continue notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0 notify_on_unhandled_exceptions = 1 if option.breakMode in ( 'unhandled', 'userUnhandled') else 0 exception_paths = option.path exception_names = [] if len(exception_paths) == 0: continue elif len(exception_paths) == 1: if 'Python Exceptions' in exception_paths[0]['names']: exception_names = ['BaseException'] else: path_iterator = iter(exception_paths) if 'Python Exceptions' in next(path_iterator)['names']: for path in path_iterator: for ex_name in path['names']: exception_names.append(ex_name) for exception_name in exception_names: self.api.add_python_exception_breakpoint( py_db, exception_name, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, notify_on_first_raise_only, ignore_libraries) else: break_raised = 'raised' in filters break_uncaught = 'uncaught' in filters if break_raised or break_uncaught: notify_on_handled_exceptions = 1 if break_raised else 0 notify_on_unhandled_exceptions = 1 if break_uncaught else 0 exception = 'BaseException' self.api.add_python_exception_breakpoint( py_db, exception, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, notify_on_first_raise_only, ignore_libraries) if break_raised or break_uncaught: btype = None if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2' if btype: self.api.add_plugins_exception_breakpoint( py_db, btype, 'BaseException' ) # Note: Exception name could be anything here. # Note: no body required on success. set_breakpoints_response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) def on_stacktrace_request(self, py_db, request): ''' :param StackTraceRequest request: ''' # : :type stack_trace_arguments: StackTraceArguments stack_trace_arguments = request.arguments thread_id = stack_trace_arguments.threadId fmt = stack_trace_arguments.format if hasattr(fmt, 'to_dict'): fmt = fmt.to_dict() self.api.request_stack(py_db, request.seq, thread_id, fmt) def on_exceptioninfo_request(self, py_db, request): ''' :param ExceptionInfoRequest request: ''' # : :type exception_into_arguments: ExceptionInfoArguments exception_into_arguments = request.arguments thread_id = exception_into_arguments.threadId self.api.request_exception_info_json(py_db, request, thread_id) def on_scopes_request(self, py_db, request): ''' Scopes are the top-level items which appear for a frame (so, we receive the frame id and provide the scopes it has). :param ScopesRequest request: ''' frame_id = request.arguments.frameId variables_reference = frame_id scopes = [Scope('Locals', int(variables_reference), False).to_dict()] body = ScopesResponseBody(scopes) scopes_response = pydevd_base_schema.build_response( request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True) def on_evaluate_request(self, py_db, request): ''' :param EvaluateRequest request: ''' # : :type arguments: EvaluateArguments arguments = request.arguments thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( arguments.frameId) self.api.request_exec_or_evaluate_json(py_db, request, thread_id) def on_setexpression_request(self, py_db, request): # : :type arguments: SetExpressionArguments arguments = request.arguments thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( arguments.frameId) self.api.request_set_expression_json(py_db, request, thread_id) def on_variables_request(self, py_db, request): ''' Variables can be asked whenever some place returned a variables reference (so, it can be a scope gotten from on_scopes_request, the result of some evaluation, etc.). Note that in the DAP the variables reference requires a unique int... the way this works for pydevd is that an instance is generated for that specific variable reference and we use its id(instance) to identify it to make sure all items are unique (and the actual {id->instance} is added to a dict which is only valid while the thread is suspended and later cleared when the related thread resumes execution). see: SuspendedFramesManager :param VariablesRequest request: ''' arguments = request.arguments # : :type arguments: VariablesArguments variables_reference = arguments.variablesReference thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: self.api.request_get_variable_json(py_db, request, thread_id) else: variables = [] body = VariablesResponseBody(variables) variables_response = pydevd_base_schema.build_response( request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_setvariable_request(self, py_db, request): arguments = request.arguments # : :type arguments: SetVariableArguments variables_reference = arguments.variablesReference thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: self.api.request_change_variable_json(py_db, request, thread_id) else: body = SetVariableResponseBody('') variables_response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Unable to find thread to evaluate variable reference.' }) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_modules_request(self, py_db, request): modules_manager = py_db.cmd_factory.modules_manager # : :type modules_manager: ModulesManager modules_info = modules_manager.get_modules_info() body = ModulesResponseBody(modules_info) variables_response = pydevd_base_schema.build_response( request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_source_request(self, py_db, request): ''' :param SourceRequest request: ''' source_reference = request.arguments.sourceReference server_filename = None content = None if source_reference != 0: server_filename = pydevd_file_utils.get_server_filename_from_source_reference( source_reference) if server_filename: # Try direct file access first - it's much faster when available. try: with open(server_filename, 'r') as stream: content = stream.read() except: pass if content is None: # File might not exist at all, or we might not have a permission to read it, # but it might also be inside a zipfile, or an IPython cell. In this case, # linecache might still be able to retrieve the source. lines = (linecache.getline(server_filename, i) for i in itertools.count(1)) lines = itertools.takewhile( bool, lines) # empty lines are '\n', EOF is '' # If we didn't get at least one line back, reset it to None so that it's # reported as error below, and not as an empty file. content = ''.join(lines) or None body = SourceResponseBody(content or '') response_args = {'body': body} if content is None: if server_filename: message = 'Unable to retrieve source for %s' % ( server_filename, ) else: message = 'Invalid sourceReference %d' % (source_reference, ) response_args.update({'success': False, 'message': message}) response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_gototargets_request(self, py_db, request): path = request.arguments.source.path line = request.arguments.line target_id = self._goto_targets_map.obtain_key((path, line)) target = { 'id': target_id, 'label': '%s:%s' % (path, line), 'line': line } body = GotoTargetsResponseBody(targets=[target]) response_args = {'body': body} response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_goto_request(self, py_db, request): target_id = int(request.arguments.targetId) thread_id = request.arguments.threadId try: _, line = self._goto_targets_map.obtain_value(target_id) except KeyError: response = pydevd_base_schema.build_response( request, kwargs={ 'body': {}, 'success': False, 'message': 'Unknown goto target id: %d' % (target_id, ), }) return NetCommand(CMD_RETURN, 0, response, is_json=True) self.api.request_set_next(py_db, thread_id, CMD_SET_NEXT_STATEMENT, line, '*') response = pydevd_base_schema.build_response(request, kwargs={'body': {}}) return NetCommand(CMD_RETURN, 0, response, is_json=True)
class _PyDevCommandProcessor(object): def __init__(self): self.api = PyDevdAPI() def process_net_command(self, py_db, cmd_id, seq, text): '''Processes a command received from the Java side @param cmd_id: the id of the command @param seq: the sequence of the command @param text: the text received in the command ''' meaning = ID_TO_MEANING[str(cmd_id)] # print('Handling %s (%s)' % (meaning, text)) method_name = meaning.lower() on_command = getattr(self, method_name.lower(), None) if on_command is None: # I have no idea what this is all about cmd = py_db.cmd_factory.make_error_message(seq, "unexpected command " + str(cmd_id)) py_db.writer.add_command(cmd) return py_db._main_lock.acquire() try: cmd = on_command(py_db, cmd_id, seq, text) if cmd is not None: py_db.writer.add_command(cmd) except: if traceback is not None and sys is not None and pydev_log_exception is not None: pydev_log_exception() stream = StringIO() traceback.print_exc(file=stream) cmd = py_db.cmd_factory.make_error_message( seq, "Unexpected exception in process_net_command.\nInitial params: %s. Exception: %s" % ( ((cmd_id, seq, text), stream.getvalue()) ) ) if cmd is not None: py_db.writer.add_command(cmd) finally: py_db._main_lock.release() def cmd_run(self, py_db, cmd_id, seq, text): return self.api.run(py_db) def cmd_list_threads(self, py_db, cmd_id, seq, text): return self.api.list_threads(py_db, seq) def cmd_get_completions(self, py_db, cmd_id, seq, text): # we received some command to get a variable # the text is: thread_id\tframe_id\tactivation token thread_id, frame_id, _scope, act_tok = text.split('\t', 3) return self.api.request_completions(py_db, seq, thread_id, frame_id, act_tok) def cmd_get_thread_stack(self, py_db, cmd_id, seq, text): # Receives a thread_id and a given timeout, which is the time we should # wait to the provide the stack if a given thread is still not suspended. if '\t' in text: thread_id, timeout = text.split('\t') timeout = float(timeout) else: thread_id = text timeout = .5 # Default timeout is .5 seconds return self.api.request_stack(py_db, seq, thread_id, fmt={}, timeout=timeout) def cmd_set_protocol(self, py_db, cmd_id, seq, text): return self.api.set_protocol(py_db, seq, text.strip()) def cmd_thread_suspend(self, py_db, cmd_id, seq, text): return self.api.request_suspend_thread(py_db, text.strip()) def cmd_version(self, py_db, cmd_id, seq, text): # Default based on server process (although ideally the IDE should # provide it). if IS_WINDOWS: ide_os = 'WINDOWS' else: ide_os = 'UNIX' # Breakpoints can be grouped by 'LINE' or by 'ID'. breakpoints_by = 'LINE' splitted = text.split('\t') if len(splitted) == 1: _local_version = splitted elif len(splitted) == 2: _local_version, ide_os = splitted elif len(splitted) == 3: _local_version, ide_os, breakpoints_by = splitted version_msg = self.api.set_ide_os_and_breakpoints_by(py_db, seq, ide_os, breakpoints_by) # Enable thread notifications after the version command is completed. self.api.set_enable_thread_notifications(py_db, True) return version_msg def cmd_thread_run(self, py_db, cmd_id, seq, text): return self.api.request_resume_thread(text.strip()) def _cmd_step(self, py_db, cmd_id, seq, text): return self.api.request_step(py_db, text.strip(), cmd_id) cmd_step_into = _cmd_step cmd_step_into_my_code = _cmd_step cmd_step_over = _cmd_step cmd_step_over_my_code = _cmd_step cmd_step_return = _cmd_step cmd_step_return_my_code = _cmd_step def _cmd_set_next(self, py_db, cmd_id, seq, text): thread_id, line, func_name = text.split('\t', 2) return self.api.request_set_next(py_db, seq, thread_id, cmd_id, line, func_name) cmd_run_to_line = _cmd_set_next cmd_set_next_statement = _cmd_set_next cmd_smart_step_into = _cmd_set_next def cmd_reload_code(self, py_db, cmd_id, seq, text): module_name = text.strip() self.api.request_reload_code(py_db, seq, module_name) def cmd_change_variable(self, py_db, cmd_id, seq, text): # the text is: thread\tstackframe\tFRAME|GLOBAL\tattribute_to_change\tvalue_to_change thread_id, frame_id, scope, attr_and_value = text.split('\t', 3) tab_index = attr_and_value.rindex('\t') attr = attr_and_value[0:tab_index].replace('\t', '.') value = attr_and_value[tab_index + 1:] self.api.request_change_variable(py_db, seq, thread_id, frame_id, scope, attr, value) def cmd_get_variable(self, py_db, cmd_id, seq, text): # we received some command to get a variable # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tattributes* thread_id, frame_id, scopeattrs = text.split('\t', 2) if scopeattrs.find('\t') != -1: # there are attributes beyond scope scope, attrs = scopeattrs.split('\t', 1) else: scope, attrs = (scopeattrs, None) self.api.request_get_variable(py_db, seq, thread_id, frame_id, scope, attrs) def cmd_get_array(self, py_db, cmd_id, seq, text): # Note: untested and unused in pydev # we received some command to get an array variable # the text is: thread_id\tframe_id\tFRAME|GLOBAL\tname\ttemp\troffs\tcoffs\trows\tcols\tformat roffset, coffset, rows, cols, format, thread_id, frame_id, scopeattrs = text.split('\t', 7) if scopeattrs.find('\t') != -1: # there are attributes beyond scope scope, attrs = scopeattrs.split('\t', 1) else: scope, attrs = (scopeattrs, None) self.api.request_get_array(py_db, seq, roffset, coffset, rows, cols, format, thread_id, frame_id, scope, attrs) def cmd_show_return_values(self, py_db, cmd_id, seq, text): show_return_values = text.split('\t')[1] self.api.set_show_return_values(py_db, int(show_return_values) == 1) def cmd_load_full_value(self, py_db, cmd_id, seq, text): # Note: untested and unused in pydev thread_id, frame_id, scopeattrs = text.split('\t', 2) vars = scopeattrs.split(NEXT_VALUE_SEPARATOR) self.api.request_load_full_value(py_db, seq, thread_id, frame_id, vars) def cmd_get_description(self, py_db, cmd_id, seq, text): # Note: untested and unused in pydev thread_id, frame_id, expression = text.split('\t', 2) self.api.request_get_description(py_db, seq, thread_id, frame_id, expression) def cmd_get_frame(self, py_db, cmd_id, seq, text): thread_id, frame_id, scope = text.split('\t', 2) self.api.request_get_frame(py_db, seq, thread_id, frame_id) def cmd_set_break(self, py_db, cmd_id, seq, text): # func name: 'None': match anything. Empty: match global, specified: only method context. # command to add some breakpoint. # text is filename\tline. Add to breakpoints dictionary suspend_policy = "NONE" # Can be 'NONE' or 'ALL' is_logpoint = False hit_condition = None if py_db._set_breakpoints_with_id: try: try: breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint, suspend_policy = text.split('\t', 9) except ValueError: # not enough values to unpack # No suspend_policy passed (use default). breakpoint_id, btype, filename, line, func_name, condition, expression, hit_condition, is_logpoint = text.split('\t', 8) is_logpoint = is_logpoint == 'True' except ValueError: # not enough values to unpack breakpoint_id, btype, filename, line, func_name, condition, expression = text.split('\t', 6) breakpoint_id = int(breakpoint_id) line = int(line) # We must restore new lines and tabs as done in # AbstractDebugTarget.breakpointAdded condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').\ replace("@_@TAB_CHAR@_@", '\t').strip() expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').\ replace("@_@TAB_CHAR@_@", '\t').strip() else: # Note: this else should be removed after PyCharm migrates to setting # breakpoints by id (and ideally also provides func_name). btype, filename, line, func_name, suspend_policy, condition, expression = text.split('\t', 6) # If we don't have an id given for each breakpoint, consider # the id to be the line. breakpoint_id = line = int(line) condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n'). \ replace("@_@TAB_CHAR@_@", '\t').strip() expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n'). \ replace("@_@TAB_CHAR@_@", '\t').strip() if condition is not None and (len(condition) <= 0 or condition == "None"): condition = None if expression is not None and (len(expression) <= 0 or expression == "None"): expression = None if hit_condition is not None and (len(hit_condition) <= 0 or hit_condition == "None"): hit_condition = None filename = self.api.filename_to_server(filename) func_name = self.api.to_str(func_name) error_code = self.api.add_breakpoint( py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint) if error_code: if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND: pydev_log.critical('pydev debugger: warning: Trying to add breakpoint to file that does not exist: %s (will have no effect).' % (filename,)) elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS: pydev_log.critical('pydev debugger: warning: Trying to add breakpoint to file that is excluded by filters: %s (will have no effect).' % (filename,)) else: # Shouldn't get here. pydev_log.critical('pydev debugger: warning: Breakpoint not validated (reason unknown -- please report as error): %s.' % (filename,)) def cmd_remove_break(self, py_db, cmd_id, seq, text): # command to remove some breakpoint # text is type\file\tid. Remove from breakpoints dictionary breakpoint_type, filename, breakpoint_id = text.split('\t', 2) filename = self.api.filename_to_server(filename) try: breakpoint_id = int(breakpoint_id) except ValueError: pydev_log.critical('Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s', breakpoint_id) else: self.api.remove_breakpoint(py_db, filename, breakpoint_type, breakpoint_id) def _cmd_exec_or_evaluate_expression(self, py_db, cmd_id, seq, text): # command to evaluate the given expression # text is: thread\tstackframe\tLOCAL\texpression attr_to_set_result = "" try: thread_id, frame_id, scope, expression, trim, attr_to_set_result = text.split('\t', 5) except ValueError: thread_id, frame_id, scope, expression, trim = text.split('\t', 4) is_exec = cmd_id == CMD_EXEC_EXPRESSION trim_if_too_big = int(trim) == 1 self.api.request_exec_or_evaluate( py_db, seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result) cmd_evaluate_expression = _cmd_exec_or_evaluate_expression cmd_exec_expression = _cmd_exec_or_evaluate_expression def cmd_console_exec(self, py_db, cmd_id, seq, text): # command to exec expression in console, in case expression is only partially valid 'False' is returned # text is: thread\tstackframe\tLOCAL\texpression thread_id, frame_id, scope, expression = text.split('\t', 3) self.api.request_console_exec(py_db, seq, thread_id, frame_id, expression) def cmd_set_py_exception(self, py_db, cmd_id, seq, text): # Command which receives set of exceptions on which user wants to break the debugger # text is: # # break_on_uncaught; # break_on_caught; # skip_on_exceptions_thrown_in_same_context; # ignore_exceptions_thrown_in_lines_with_ignore_exception; # ignore_libraries; # TypeError;ImportError;zipimport.ZipImportError; # # i.e.: true;true;true;true;true;TypeError;ImportError;zipimport.ZipImportError; # # This API is optional and works 'in bulk' -- it's possible # to get finer-grained control with CMD_ADD_EXCEPTION_BREAK/CMD_REMOVE_EXCEPTION_BREAK # which allows setting caught/uncaught per exception. splitted = text.split(';') py_db.break_on_uncaught_exceptions = {} py_db.break_on_caught_exceptions = {} if len(splitted) >= 5: if splitted[0] == 'true': break_on_uncaught = True else: break_on_uncaught = False if splitted[1] == 'true': break_on_caught = True else: break_on_caught = False if splitted[2] == 'true': py_db.skip_on_exceptions_thrown_in_same_context = True else: py_db.skip_on_exceptions_thrown_in_same_context = False if splitted[3] == 'true': py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = True else: py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception = False if splitted[4] == 'true': ignore_libraries = True else: ignore_libraries = False for exception_type in splitted[5:]: exception_type = exception_type.strip() if not exception_type: continue exception_breakpoint = py_db.add_break_on_exception( exception_type, condition=None, expression=None, notify_on_handled_exceptions=break_on_caught, notify_on_unhandled_exceptions=break_on_uncaught, notify_on_first_raise_only=True, ignore_libraries=ignore_libraries, ) py_db.on_breakpoints_changed() else: sys.stderr.write("Error when setting exception list. Received: %s\n" % (text,)) def _load_source(self, py_db, cmd_id, seq, text): filename = text filename = self.api.filename_to_server(filename) self.api.request_load_source(py_db, seq, filename) cmd_load_source = _load_source cmd_get_file_contents = _load_source def cmd_set_property_trace(self, py_db, cmd_id, seq, text): # Command which receives whether to trace property getter/setter/deleter # text is feature_state(true/false);disable_getter/disable_setter/disable_deleter if text: splitted = text.split(';') if len(splitted) >= 3: if py_db.disable_property_trace is False and splitted[0] == 'true': # Replacing property by custom property only when the debugger starts pydevd_traceproperty.replace_builtin_property() py_db.disable_property_trace = True # Enable/Disable tracing of the property getter if splitted[1] == 'true': py_db.disable_property_getter_trace = True else: py_db.disable_property_getter_trace = False # Enable/Disable tracing of the property setter if splitted[2] == 'true': py_db.disable_property_setter_trace = True else: py_db.disable_property_setter_trace = False # Enable/Disable tracing of the property deleter if splitted[3] == 'true': py_db.disable_property_deleter_trace = True else: py_db.disable_property_deleter_trace = False def cmd_add_exception_break(self, py_db, cmd_id, seq, text): # Note that this message has some idiosyncrasies... # # notify_on_handled_exceptions can be 0, 1 or 2 # 0 means we should not stop on handled exceptions. # 1 means we should stop on handled exceptions showing it on all frames where the exception passes. # 2 means we should stop on handled exceptions but we should only notify about it once. # # To ignore_libraries properly, besides setting ignore_libraries to 1, the IDE_PROJECT_ROOTS environment # variable must be set (so, we'll ignore anything not below IDE_PROJECT_ROOTS) -- this is not ideal as # the environment variable may not be properly set if it didn't start from the debugger (we should # create a custom message for that). # # There are 2 global settings which can only be set in CMD_SET_PY_EXCEPTION. Namely: # # py_db.skip_on_exceptions_thrown_in_same_context # - If True, we should only show the exception in a caller, not where it was first raised. # # py_db.ignore_exceptions_thrown_in_lines_with_ignore_exception # - If True exceptions thrown in lines with '@IgnoreException' will not be shown. condition = "" expression = "" if text.find('\t') != -1: try: exception, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split('\t', 5) except: exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text.split('\t', 3) else: exception, notify_on_handled_exceptions, notify_on_unhandled_exceptions, ignore_libraries = text, 0, 0, 0 condition = condition.replace("@_@NEW_LINE_CHAR@_@", '\n').replace("@_@TAB_CHAR@_@", '\t').strip() if condition is not None and (len(condition) == 0 or condition == "None"): condition = None expression = expression.replace("@_@NEW_LINE_CHAR@_@", '\n').replace("@_@TAB_CHAR@_@", '\t').strip() if expression is not None and (len(expression) == 0 or expression == "None"): expression = None if exception.find('-') != -1: breakpoint_type, exception = exception.split('-') else: breakpoint_type = 'python' if breakpoint_type == 'python': self.api.add_python_exception_breakpoint( py_db, exception, condition, expression, notify_on_handled_exceptions=int(notify_on_handled_exceptions) > 0, notify_on_unhandled_exceptions=int(notify_on_unhandled_exceptions) == 1, notify_on_first_raise_only=int(notify_on_handled_exceptions) == 2, ignore_libraries=int(ignore_libraries) > 0, ) else: self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type, exception) def cmd_remove_exception_break(self, py_db, cmd_id, seq, text): exception = text if exception.find('-') != -1: exception_type, exception = exception.split('-') else: exception_type = 'python' if exception_type == 'python': self.api.remove_python_exception_breakpoint(py_db, exception) else: self.api.remove_plugins_exception_breakpoint(py_db, exception_type, exception) def cmd_add_django_exception_break(self, py_db, cmd_id, seq, text): self.api.add_plugins_exception_breakpoint(py_db, breakpoint_type='django', exception=text) def cmd_remove_django_exception_break(self, py_db, cmd_id, seq, text): self.api.remove_plugins_exception_breakpoint(py_db, exception_type='django', exception=text) def cmd_evaluate_console_expression(self, py_db, cmd_id, seq, text): # Command which takes care for the debug console communication if text != "": thread_id, frame_id, console_command = text.split('\t', 2) console_command, line = console_command.split('\t') if console_command == 'EVALUATE': int_cmd = InternalEvaluateConsoleExpression( seq, thread_id, frame_id, line, buffer_output=True) elif console_command == 'EVALUATE_UNBUFFERED': int_cmd = InternalEvaluateConsoleExpression( seq, thread_id, frame_id, line, buffer_output=False) elif console_command == 'GET_COMPLETIONS': int_cmd = InternalConsoleGetCompletions(seq, thread_id, frame_id, line) else: raise ValueError('Unrecognized command: %s' % (console_command,)) py_db.post_internal_command(int_cmd, thread_id) def cmd_run_custom_operation(self, py_db, cmd_id, seq, text): # Command which runs a custom operation if text != "": try: location, custom = text.split('||', 1) except: sys.stderr.write('Custom operation now needs a || separator. Found: %s\n' % (text,)) raise thread_id, frame_id, scopeattrs = location.split('\t', 2) if scopeattrs.find('\t') != -1: # there are attributes beyond scope scope, attrs = scopeattrs.split('\t', 1) else: scope, attrs = (scopeattrs, None) # : style: EXECFILE or EXEC # : encoded_code_or_file: file to execute or code # : fname: name of function to be executed in the resulting namespace style, encoded_code_or_file, fnname = custom.split('\t', 3) int_cmd = InternalRunCustomOperation(seq, thread_id, frame_id, scope, attrs, style, encoded_code_or_file, fnname) py_db.post_internal_command(int_cmd, thread_id) def cmd_ignore_thrown_exception_at(self, py_db, cmd_id, seq, text): if text: replace = 'REPLACE:' # Not all 3.x versions support u'REPLACE:', so, doing workaround. if not IS_PY3K: replace = unicode(replace) # noqa if text.startswith(replace): text = text[8:] py_db.filename_to_lines_where_exceptions_are_ignored.clear() if text: for line in text.split('||'): # Can be bulk-created (one in each line) filename, line_number = line.split('|') filename = self.api.filename_to_server(filename) if os.path.exists(filename): lines_ignored = py_db.filename_to_lines_where_exceptions_are_ignored.get(filename) if lines_ignored is None: lines_ignored = py_db.filename_to_lines_where_exceptions_are_ignored[filename] = {} lines_ignored[int(line_number)] = 1 else: sys.stderr.write('pydev debugger: warning: trying to ignore exception thrown'\ ' on file that does not exist: %s (will have no effect)\n' % (filename,)) def cmd_enable_dont_trace(self, py_db, cmd_id, seq, text): if text: true_str = 'true' # Not all 3.x versions support u'str', so, doing workaround. if not IS_PY3K: true_str = unicode(true_str) # noqa mode = text.strip() == true_str pydevd_dont_trace.trace_filter(mode) def cmd_redirect_output(self, py_db, cmd_id, seq, text): if text: py_db.enable_output_redirection('STDOUT' in text, 'STDERR' in text) def cmd_get_next_statement_targets(self, py_db, cmd_id, seq, text): thread_id, frame_id = text.split('\t', 1) py_db.post_method_as_internal_command( thread_id, internal_get_next_statement_targets, seq, thread_id, frame_id) def cmd_set_project_roots(self, py_db, cmd_id, seq, text): self.api.set_project_roots(py_db, text.split(u'\t')) def cmd_thread_dump_to_stderr(self, py_db, cmd_id, seq, text): pydevd_utils.dump_threads() def cmd_stop_on_start(self, py_db, cmd_id, seq, text): if text.strip() in ('True', 'true', '1'): self.api.stop_on_entry() def cmd_pydevd_json_config(self, py_db, cmd_id, seq, text): # Expected to receive a json string as: # { # 'skip_suspend_on_breakpoint_exception': [<exception names where we should suspend>], # 'skip_print_breakpoint_exception': [<exception names where we should print>], # 'multi_threads_single_notification': bool, # } msg = json.loads(text.strip()) if 'skip_suspend_on_breakpoint_exception' in msg: py_db.skip_suspend_on_breakpoint_exception = tuple( get_exception_class(x) for x in msg['skip_suspend_on_breakpoint_exception']) if 'skip_print_breakpoint_exception' in msg: py_db.skip_print_breakpoint_exception = tuple( get_exception_class(x) for x in msg['skip_print_breakpoint_exception']) if 'multi_threads_single_notification' in msg: py_db.multi_threads_single_notification = msg['multi_threads_single_notification'] def cmd_get_exception_details(self, py_db, cmd_id, seq, text): thread_id = text t = pydevd_find_thread_by_id(thread_id) frame = None if t and not getattr(t, 'pydev_do_not_trace', None): additional_info = set_additional_thread_info(t) frame = additional_info.get_topmost_frame(t) try: return py_db.cmd_factory.make_get_exception_details_message(seq, thread_id, frame) finally: frame = None t = None
class _PyDevJsonCommandProcessor(object): def __init__(self, from_json): self.from_json = from_json self.api = PyDevdAPI() self._debug_options = {} self._next_breakpoint_id = partial(next, itertools.count(0)) self._goto_targets_map = IDMap() self._launch_or_attach_request_done = False def process_net_command_json(self, py_db, json_contents): ''' Processes a debug adapter protocol json command. ''' DEBUG = False try: request = self.from_json(json_contents, update_ids_from_dap=True) except KeyError as e: request = self.from_json(json_contents, update_ids_from_dap=False) error_msg = str(e) if error_msg.startswith("'") and error_msg.endswith("'"): error_msg = error_msg[1:-1] # This means a failure updating ids from the DAP (the client sent a key we didn't send). def on_request(py_db, request): error_response = { 'type': 'response', 'request_seq': request.seq, 'success': False, 'command': request.command, 'message': error_msg, } return NetCommand(CMD_RETURN, 0, error_response, is_json=True) else: if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: pydev_log.info('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),)) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(),) on_request = getattr(self, method_name, None) if on_request is None: print('Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name,)) return if DEBUG: print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name,)) with py_db._main_lock: cmd = on_request(py_db, request) if cmd is not None: py_db.writer.add_command(cmd) def on_configurationdone_request(self, py_db, request): ''' :param ConfigurationDoneRequest request: ''' if not self._launch_or_attach_request_done: pydev_log.critical('Missing launch request or attach request before configuration done request.') self.api.run(py_db) self.api.notify_configuration_done(py_db) configuration_done_response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True) def on_threads_request(self, py_db, request): ''' :param ThreadsRequest request: ''' return self.api.list_threads(py_db, request.seq) def on_completions_request(self, py_db, request): ''' :param CompletionsRequest request: ''' arguments = request.arguments # : :type arguments: CompletionsArguments seq = request.seq text = arguments.text frame_id = arguments.frameId thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( frame_id) if thread_id is None: body = CompletionsResponseBody([]) variables_response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Thread to get completions seems to have resumed already.' }) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) # Note: line and column are 1-based (convert to 0-based for pydevd). column = arguments.column - 1 if arguments.line is None: # line is optional line = -1 else: line = arguments.line - 1 self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column) def _resolve_remote_root(self, local_root, remote_root): if remote_root == '.': cwd = os.getcwd() append_pathsep = local_root.endswith('\\') or local_root.endswith('/') return cwd + (os.path.sep if append_pathsep else '') return remote_root def _set_debug_options(self, py_db, args, start_reason): rules = args.get('rules') exclude_filters = [] if rules is not None: exclude_filters = _convert_rules_to_exclude_filters( rules, self.api.filename_to_server, lambda msg: self.api.send_error_message(py_db, msg)) self.api.set_exclude_filters(py_db, exclude_filters) self._debug_options = _extract_debug_options( args.get('options'), args.get('debugOptions'), ) self._debug_options['args'] = args debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False) self.api.set_use_libraries_filter(py_db, not debug_stdlib) path_mappings = [] for pathMapping in args.get('pathMappings', []): localRoot = pathMapping.get('localRoot', '') remoteRoot = pathMapping.get('remoteRoot', '') remoteRoot = self._resolve_remote_root(localRoot, remoteRoot) if (localRoot != '') and (remoteRoot != ''): path_mappings.append((localRoot, remoteRoot)) if bool(path_mappings): pydevd_file_utils.setup_client_server_paths(path_mappings) if self._debug_options.get('REDIRECT_OUTPUT', False): py_db.enable_output_redirection(True, True) else: py_db.enable_output_redirection(False, False) self.api.set_show_return_values(py_db, self._debug_options.get('SHOW_RETURN_VALUE', False)) if self._debug_options.get('STOP_ON_ENTRY', False) and start_reason == 'launch': self.api.stop_on_entry() def _send_process_event(self, py_db, start_method): if len(sys.argv) > 0: name = sys.argv[0] else: name = '' body = ProcessEventBody( name=name, systemProcessId=os.getpid(), isLocalProcess=True, startMethod=start_method, ) event = ProcessEvent(body) py_db.writer.add_command(NetCommand(CMD_PROCESS_EVENT, 0, event, is_json=True)) def _handle_launch_or_attach_request(self, py_db, request, start_reason): self._send_process_event(py_db, start_reason) self._launch_or_attach_request_done = True self.api.set_enable_thread_notifications(py_db, True) self._set_debug_options(py_db, request.arguments.kwargs, start_reason=start_reason) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_launch_request(self, py_db, request): ''' :param LaunchRequest request: ''' return self._handle_launch_or_attach_request(py_db, request, start_reason='launch') def on_attach_request(self, py_db, request): ''' :param AttachRequest request: ''' return self._handle_launch_or_attach_request(py_db, request, start_reason='attach') def on_pause_request(self, py_db, request): ''' :param PauseRequest request: ''' arguments = request.arguments # : :type arguments: PauseArguments thread_id = arguments.threadId self.api.request_suspend_thread(py_db, thread_id=thread_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_continue_request(self, py_db, request): ''' :param ContinueRequest request: ''' arguments = request.arguments # : :type arguments: ContinueArguments thread_id = arguments.threadId def on_resumed(): body = {'allThreadsContinued': thread_id == '*'} response = pydevd_base_schema.build_response(request, kwargs={'body': body}) cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) py_db.writer.add_command(cmd) # Only send resumed notification when it has actually resumed! # (otherwise the user could send a continue, receive the notification and then # request a new pause which would be paused without sending any notification as # it didn't really run in the first place). py_db.threads_suspended_single_notification.add_on_resumed_callback(on_resumed) self.api.request_resume_thread(thread_id) def on_next_request(self, py_db, request): ''' :param NextRequest request: ''' arguments = request.arguments # : :type arguments: NextArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_OVER_MY_CODE else: step_cmd_id = CMD_STEP_OVER self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_stepin_request(self, py_db, request): ''' :param StepInRequest request: ''' arguments = request.arguments # : :type arguments: StepInArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_INTO_MY_CODE else: step_cmd_id = CMD_STEP_INTO self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_stepout_request(self, py_db, request): ''' :param StepOutRequest request: ''' arguments = request.arguments # : :type arguments: StepOutArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_RETURN_MY_CODE else: step_cmd_id = CMD_STEP_RETURN self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def _get_hit_condition_expression(self, hit_condition): '''Following hit condition values are supported * x or == x when breakpoint is hit x times * >= x when breakpoint is hit more than or equal to x times * % x when breakpoint is hit multiple of x times Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits ''' if not hit_condition: return None expr = hit_condition.strip() try: int(expr) return '@HIT@ == {}'.format(expr) except ValueError: pass if expr.startswith('%'): return '@HIT@ {} == 0'.format(expr) if expr.startswith('==') or \ expr.startswith('>') or \ expr.startswith('<'): return '@HIT@ {}'.format(expr) return hit_condition def on_disconnect_request(self, py_db, request): ''' :param DisconnectRequest request: ''' self._launch_or_attach_request_done = False py_db.enable_output_redirection(False, False) self.api.request_disconnect(py_db, resume_threads=True) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_setbreakpoints_request(self, py_db, request): ''' :param SetBreakpointsRequest request: ''' if not self._launch_or_attach_request_done: # Note that to validate the breakpoints we need the launch request to be done already # (otherwise the filters wouldn't be set for the breakpoint validation). body = SetBreakpointsResponseBody([]) response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Breakpoints may only be set after the launch request is received.' }) return NetCommand(CMD_RETURN, 0, response, is_json=True) arguments = request.arguments # : :type arguments: SetBreakpointsArguments # TODO: Path is optional here it could be source reference. filename = arguments.source.path filename = self.api.filename_to_server(filename) func_name = 'None' self.api.remove_all_breakpoints(py_db, filename) # Validate breakpoints and adjust their positions. try: lines = sorted(_get_code_lines(filename)) except Exception: pass else: for bp in arguments.breakpoints: line = bp['line'] if line not in lines: # Adjust to the first preceding valid line. idx = bisect.bisect_left(lines, line) if idx > 0: bp['line'] = lines[idx - 1] btype = 'python-line' suspend_policy = 'ALL' if not filename.lower().endswith('.py'): if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django-line' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2-line' breakpoints_set = [] for source_breakpoint in arguments.breakpoints: source_breakpoint = SourceBreakpoint(**source_breakpoint) line = source_breakpoint.line condition = source_breakpoint.condition breakpoint_id = line hit_condition = self._get_hit_condition_expression(source_breakpoint.hitCondition) log_message = source_breakpoint.logMessage if not log_message: is_logpoint = None expression = None else: is_logpoint = True expression = convert_dap_log_message_to_expression(log_message) error_code = self.api.add_breakpoint( py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint) if error_code: if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND: error_msg = 'Breakpoint in file that does not exist.' elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS: error_msg = 'Breakpoint in file excluded by filters.' if py_db.get_use_libraries_filter(): error_msg += '\nNote: may be excluded because of "justMyCode" option (default == true).' else: # Shouldn't get here. error_msg = 'Breakpoint not validated (reason unknown -- please report as bug).' breakpoints_set.append(pydevd_schema.Breakpoint( verified=False, line=line, message=error_msg).to_dict()) else: # Note that the id is made up (the id for pydevd is unique only within a file, so, the # line is used for it). # Also, the id is currently not used afterwards, so, we don't even keep a mapping. breakpoints_set.append(pydevd_schema.Breakpoint( verified=True, id=self._next_breakpoint_id(), line=line).to_dict()) body = {'breakpoints': breakpoints_set} set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) def on_setexceptionbreakpoints_request(self, py_db, request): ''' :param SetExceptionBreakpointsRequest request: ''' # : :type arguments: SetExceptionBreakpointsArguments arguments = request.arguments filters = arguments.filters exception_options = arguments.exceptionOptions self.api.remove_all_exception_breakpoints(py_db) # Can't set these in the DAP. condition = None expression = None notify_on_first_raise_only = False ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0 if exception_options: break_raised = True break_uncaught = True for option in exception_options: option = ExceptionOptions(**option) if not option.path: continue notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0 notify_on_unhandled_exceptions = 1 if option.breakMode in ('unhandled', 'userUnhandled') else 0 exception_paths = option.path exception_names = [] if len(exception_paths) == 0: continue elif len(exception_paths) == 1: if 'Python Exceptions' in exception_paths[0]['names']: exception_names = ['BaseException'] else: path_iterator = iter(exception_paths) if 'Python Exceptions' in next(path_iterator)['names']: for path in path_iterator: for ex_name in path['names']: exception_names.append(ex_name) for exception_name in exception_names: self.api.add_python_exception_breakpoint( py_db, exception_name, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, notify_on_first_raise_only, ignore_libraries ) else: break_raised = 'raised' in filters break_uncaught = 'uncaught' in filters if break_raised or break_uncaught: notify_on_handled_exceptions = 1 if break_raised else 0 notify_on_unhandled_exceptions = 1 if break_uncaught else 0 exception = 'BaseException' self.api.add_python_exception_breakpoint( py_db, exception, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, notify_on_first_raise_only, ignore_libraries ) if break_raised or break_uncaught: btype = None if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2' if btype: self.api.add_plugins_exception_breakpoint( py_db, btype, 'BaseException') # Note: Exception name could be anything here. # Note: no body required on success. set_breakpoints_response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) def on_stacktrace_request(self, py_db, request): ''' :param StackTraceRequest request: ''' # : :type stack_trace_arguments: StackTraceArguments stack_trace_arguments = request.arguments thread_id = stack_trace_arguments.threadId start_frame = stack_trace_arguments.startFrame levels = stack_trace_arguments.levels fmt = stack_trace_arguments.format if hasattr(fmt, 'to_dict'): fmt = fmt.to_dict() self.api.request_stack(py_db, request.seq, thread_id, fmt=fmt, start_frame=start_frame, levels=levels) def on_exceptioninfo_request(self, py_db, request): ''' :param ExceptionInfoRequest request: ''' # : :type exception_into_arguments: ExceptionInfoArguments exception_into_arguments = request.arguments thread_id = exception_into_arguments.threadId max_frames = int(self._debug_options['args'].get('maxExceptionStackFrames', 0)) self.api.request_exception_info_json(py_db, request, thread_id, max_frames) def on_scopes_request(self, py_db, request): ''' Scopes are the top-level items which appear for a frame (so, we receive the frame id and provide the scopes it has). :param ScopesRequest request: ''' frame_id = request.arguments.frameId variables_reference = frame_id scopes = [Scope('Locals', int(variables_reference), False).to_dict()] body = ScopesResponseBody(scopes) scopes_response = pydevd_base_schema.build_response(request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True) def on_evaluate_request(self, py_db, request): ''' :param EvaluateRequest request: ''' # : :type arguments: EvaluateArguments arguments = request.arguments thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( arguments.frameId) if thread_id is not None: self.api.request_exec_or_evaluate_json( py_db, request, thread_id) else: body = EvaluateResponseBody('', 0) response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Unable to find thread for evaluation.' }) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_setexpression_request(self, py_db, request): # : :type arguments: SetExpressionArguments arguments = request.arguments thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( arguments.frameId) if thread_id is not None: self.api.request_set_expression_json(py_db, request, thread_id) else: body = SetExpressionResponseBody('') response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Unable to find thread to set expression.' }) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_variables_request(self, py_db, request): ''' Variables can be asked whenever some place returned a variables reference (so, it can be a scope gotten from on_scopes_request, the result of some evaluation, etc.). Note that in the DAP the variables reference requires a unique int... the way this works for pydevd is that an instance is generated for that specific variable reference and we use its id(instance) to identify it to make sure all items are unique (and the actual {id->instance} is added to a dict which is only valid while the thread is suspended and later cleared when the related thread resumes execution). see: SuspendedFramesManager :param VariablesRequest request: ''' arguments = request.arguments # : :type arguments: VariablesArguments variables_reference = arguments.variablesReference thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: self.api.request_get_variable_json(py_db, request, thread_id) else: variables = [] body = VariablesResponseBody(variables) variables_response = pydevd_base_schema.build_response(request, kwargs={ 'body': body, 'success': False, 'message': 'Unable to find thread to evaluate variable reference.' }) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_setvariable_request(self, py_db, request): arguments = request.arguments # : :type arguments: SetVariableArguments variables_reference = arguments.variablesReference thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: self.api.request_change_variable_json(py_db, request, thread_id) else: body = SetVariableResponseBody('') variables_response = pydevd_base_schema.build_response( request, kwargs={ 'body': body, 'success': False, 'message': 'Unable to find thread to evaluate variable reference.' }) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_modules_request(self, py_db, request): modules_manager = py_db.cmd_factory.modules_manager # : :type modules_manager: ModulesManager modules_info = modules_manager.get_modules_info() body = ModulesResponseBody(modules_info) variables_response = pydevd_base_schema.build_response(request, kwargs={'body': body}) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_source_request(self, py_db, request): ''' :param SourceRequest request: ''' source_reference = request.arguments.sourceReference server_filename = None content = None if source_reference != 0: server_filename = pydevd_file_utils.get_server_filename_from_source_reference(source_reference) if server_filename: # Try direct file access first - it's much faster when available. try: with open(server_filename, 'r') as stream: content = stream.read() except: pass if content is None: # File might not exist at all, or we might not have a permission to read it, # but it might also be inside a zipfile, or an IPython cell. In this case, # linecache might still be able to retrieve the source. lines = (linecache.getline(server_filename, i) for i in itertools.count(1)) lines = itertools.takewhile(bool, lines) # empty lines are '\n', EOF is '' # If we didn't get at least one line back, reset it to None so that it's # reported as error below, and not as an empty file. content = ''.join(lines) or None body = SourceResponseBody(content or '') response_args = {'body': body} if content is None: if source_reference == 0: message = 'Source unavailable' elif server_filename: message = 'Unable to retrieve source for %s' % (server_filename,) else: message = 'Invalid sourceReference %d' % (source_reference,) response_args.update({'success': False, 'message': message}) response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_gototargets_request(self, py_db, request): path = request.arguments.source.path line = request.arguments.line target_id = self._goto_targets_map.obtain_key((path, line)) target = { 'id': target_id, 'label': '%s:%s' % (path, line), 'line': line } body = GotoTargetsResponseBody(targets=[target]) response_args = {'body': body} response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_goto_request(self, py_db, request): target_id = int(request.arguments.targetId) thread_id = request.arguments.threadId try: _, line = self._goto_targets_map.obtain_value(target_id) except KeyError: response = pydevd_base_schema.build_response( request, kwargs={ 'body': {}, 'success': False, 'message': 'Unknown goto target id: %d' % (target_id,), }) return NetCommand(CMD_RETURN, 0, response, is_json=True) self.api.request_set_next(py_db, request.seq, thread_id, CMD_SET_NEXT_STATEMENT, line, '*') # See 'NetCommandFactoryJson.make_set_next_stmnt_status_message' for response return None def on_setdebuggerproperty_request(self, py_db, request): args = request.arguments if args.ideOS is not None: self.api.set_ide_os(args.ideOS) if args.dontTraceStartPatterns is not None and args.dontTraceEndPatterns is not None: start_patterns = tuple(args.dontTraceStartPatterns) end_patterns = tuple(args.dontTraceEndPatterns) self.api.set_dont_trace_start_end_patterns(py_db, start_patterns, end_patterns) if args.skipSuspendOnBreakpointException is not None: py_db.skip_suspend_on_breakpoint_exception = tuple( get_exception_class(x) for x in args.skipSuspendOnBreakpointException) if args.skipPrintBreakpointException is not None: py_db.skip_print_breakpoint_exception = tuple( get_exception_class(x) for x in args.skipPrintBreakpointException) if args.multiThreadsSingleNotification is not None: py_db.multi_threads_single_notification = args.multiThreadsSingleNotification # TODO: Support other common settings. Note that not all of these might be relevant to python. # JustMyCodeStepping: 0 or 1 # AllowOutOfProcessSymbols: 0 or 1 # DisableJITOptimization: 0 or 1 # InterpreterOptions: 0 or 1 # StopOnExceptionCrossingManagedBoundary: 0 or 1 # WarnIfNoUserCodeOnLaunch: 0 or 1 # EnableStepFiltering: true of false response = pydevd_base_schema.build_response(request, kwargs={'body': {}}) return NetCommand(CMD_RETURN, 0, response, is_json=True)
class _PyDevJsonCommandProcessor(object): def __init__(self, from_json): self.from_json = from_json self.api = PyDevdAPI() self._debug_options = {} self._next_breakpoint_id = partial(next, itertools.count(0)) def process_net_command_json(self, py_db, json_contents): ''' Processes a debug adapter protocol json command. ''' DEBUG = False request = self.from_json(json_contents) if DEBUG: print('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),)) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(),) on_request = getattr(self, method_name, None) if on_request is None: print('Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name,)) return if DEBUG: print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name,)) py_db._main_lock.acquire() try: cmd = on_request(py_db, request) if cmd is not None: py_db.writer.add_command(cmd) finally: py_db._main_lock.release() def on_configurationdone_request(self, py_db, request): ''' :param ConfigurationDoneRequest request: ''' self.api.run(py_db) configuration_done_response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, configuration_done_response.to_dict(), is_json=True) def on_threads_request(self, py_db, request): ''' :param ThreadsRequest request: ''' return self.api.list_threads(py_db, request.seq) def on_completions_request(self, py_db, request): ''' :param CompletionsRequest request: ''' arguments = request.arguments # : :type arguments: CompletionsArguments seq = request.seq text = arguments.text thread_id, frame_id = arguments.frameId # Note: line and column are 1-based (convert to 0-based for pydevd). column = arguments.column - 1 if arguments.line is None: # line is optional line = -1 else: line = arguments.line - 1 self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column) def _set_debug_options(self, py_db, args): rules = args.get('rules') exclude_filters = [] if rules is not None: exclude_filters = _convert_rules_to_exclude_filters( rules, self.api.filename_to_server, lambda msg:self.api.send_error_message(py_db, msg)) self.api.set_exclude_filters(py_db, exclude_filters) self._debug_options = _extract_debug_options( args.get('options'), args.get('debugOptions'), ) debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False) self.api.set_use_libraries_filter(py_db, not debug_stdlib) def on_launch_request(self, py_db, request): ''' :param LaunchRequest request: ''' self._set_debug_options(py_db, request.arguments.kwargs) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True) def on_attach_request(self, py_db, request): ''' :param AttachRequest request: ''' self._set_debug_options(py_db, request.arguments.kwargs) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True) def _get_hit_condition_expression(self, hit_condition): '''Following hit condition values are supported * x or == x when breakpoint is hit x times * >= x when breakpoint is hit more than or equal to x times * % x when breakpoint is hit multiple of x times Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits ''' if not hit_condition: return None expr = hit_condition.strip() try: int(expr) return '@HIT@ == {}'.format(expr) except ValueError: pass if expr.startswith('%'): return '@HIT@ {} == 0'.format(expr) if expr.startswith('==') or \ expr.startswith('>') or \ expr.startswith('<'): return '@HIT@ {}'.format(expr) return hit_condition def on_disconnect_request(self, py_db, request): ''' :param DisconnectRequest request: ''' self.api.remove_all_breakpoints(py_db, filename='*') self.api.request_resume_thread(thread_id='*') response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response.to_dict(), is_json=True) def on_setbreakpoints_request(self, py_db, request): ''' :param SetBreakpointsRequest request: ''' arguments = request.arguments # : :type arguments: SetBreakpointsArguments filename = arguments.source.path filename = self.api.filename_to_server(filename) func_name = 'None' self.api.remove_all_breakpoints(py_db, filename) btype = 'python-line' suspend_policy = 'ALL' if not filename.lower().endswith('.py'): if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django-line' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2-line' breakpoints_set = [] for source_breakpoint in arguments.breakpoints: source_breakpoint = SourceBreakpoint(**source_breakpoint) line = source_breakpoint.line condition = source_breakpoint.condition breakpoint_id = line hit_condition = self._get_hit_condition_expression(source_breakpoint.hitCondition) log_message = source_breakpoint.logMessage if not log_message: is_logpoint = None expression = None else: is_logpoint = True expression = convert_dap_log_message_to_expression(log_message) self.api.add_breakpoint( py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint) # Note that the id is made up (the id for pydevd is unique only within a file, so, the # line is used for it). # Also, the id is currently not used afterwards, so, we don't even keep a mapping. breakpoints_set.append({'id':self._next_breakpoint_id(), 'verified': True, 'line': line}) body = {'breakpoints': breakpoints_set} set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body':body}) return NetCommand(CMD_RETURN, 0, set_breakpoints_response.to_dict(), is_json=True)
class _PyDevJsonCommandProcessor(object): def __init__(self, from_json): self.from_json = from_json self.api = PyDevdAPI() self._debug_options = {} self._next_breakpoint_id = partial(next, itertools.count(0)) self._goto_targets_map = IDMap() def process_net_command_json(self, py_db, json_contents): ''' Processes a debug adapter protocol json command. ''' DEBUG = False request = self.from_json(json_contents, update_ids_from_dap=True) if DEBUG: print('Process %s: %s\n' % ( request.__class__.__name__, json.dumps(request.to_dict(), indent=4, sort_keys=True),)) assert request.type == 'request' method_name = 'on_%s_request' % (request.command.lower(),) on_request = getattr(self, method_name, None) if on_request is None: print('Unhandled: %s not available in _PyDevJsonCommandProcessor.\n' % (method_name,)) return if DEBUG: print('Handled in pydevd: %s (in _PyDevJsonCommandProcessor).\n' % (method_name,)) py_db._main_lock.acquire() try: cmd = on_request(py_db, request) if cmd is not None: py_db.writer.add_command(cmd) finally: py_db._main_lock.release() def on_configurationdone_request(self, py_db, request): ''' :param ConfigurationDoneRequest request: ''' self.api.run(py_db) configuration_done_response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True) def on_threads_request(self, py_db, request): ''' :param ThreadsRequest request: ''' return self.api.list_threads(py_db, request.seq) def on_completions_request(self, py_db, request): ''' :param CompletionsRequest request: ''' arguments = request.arguments # : :type arguments: CompletionsArguments seq = request.seq text = arguments.text frame_id = arguments.frameId thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( frame_id) # Note: line and column are 1-based (convert to 0-based for pydevd). column = arguments.column - 1 if arguments.line is None: # line is optional line = -1 else: line = arguments.line - 1 self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column) def _set_debug_options(self, py_db, args): rules = args.get('rules') exclude_filters = [] if rules is not None: exclude_filters = _convert_rules_to_exclude_filters( rules, self.api.filename_to_server, lambda msg:self.api.send_error_message(py_db, msg)) self.api.set_exclude_filters(py_db, exclude_filters) self._debug_options = _extract_debug_options( args.get('options'), args.get('debugOptions'), ) self._debug_options['args'] = args debug_stdlib = self._debug_options.get('DEBUG_STDLIB', False) self.api.set_use_libraries_filter(py_db, not debug_stdlib) def on_launch_request(self, py_db, request): ''' :param LaunchRequest request: ''' self._set_debug_options(py_db, request.arguments.kwargs) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_attach_request(self, py_db, request): ''' :param AttachRequest request: ''' self._set_debug_options(py_db, request.arguments.kwargs) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_pause_request(self, py_db, request): ''' :param PauseRequest request: ''' arguments = request.arguments # : :type arguments: PauseArguments thread_id = arguments.threadId self.api.request_suspend_thread(py_db, thread_id=thread_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_continue_request(self, py_db, request): ''' :param ContinueRequest request: ''' arguments = request.arguments # : :type arguments: ContinueArguments thread_id = arguments.threadId def on_resumed(): body = {'allThreadsContinued': thread_id == '*'} response = pydevd_base_schema.build_response(request, kwargs={'body': body}) cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) py_db.writer.add_command(cmd) # Only send resumed notification when it has actually resumed! # (otherwise the user could send a continue, receive the notification and then # request a new pause which would be paused without sending any notification as # it didn't really run in the first place). py_db.threads_suspended_single_notification.add_on_resumed_callback(on_resumed) self.api.request_resume_thread(thread_id) def on_next_request(self, py_db, request): ''' :param NextRequest request: ''' arguments = request.arguments # : :type arguments: NextArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_OVER_MY_CODE else: step_cmd_id = CMD_STEP_OVER self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_stepin_request(self, py_db, request): ''' :param StepInRequest request: ''' arguments = request.arguments # : :type arguments: StepInArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_INTO_MY_CODE else: step_cmd_id = CMD_STEP_INTO self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_stepout_request(self, py_db, request): ''' :param StepOutRequest request: ''' arguments = request.arguments # : :type arguments: StepOutArguments thread_id = arguments.threadId if py_db.get_use_libraries_filter(): step_cmd_id = CMD_STEP_RETURN_MY_CODE else: step_cmd_id = CMD_STEP_RETURN self.api.request_step(py_db, thread_id, step_cmd_id) response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def _get_hit_condition_expression(self, hit_condition): '''Following hit condition values are supported * x or == x when breakpoint is hit x times * >= x when breakpoint is hit more than or equal to x times * % x when breakpoint is hit multiple of x times Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits ''' if not hit_condition: return None expr = hit_condition.strip() try: int(expr) return '@HIT@ == {}'.format(expr) except ValueError: pass if expr.startswith('%'): return '@HIT@ {} == 0'.format(expr) if expr.startswith('==') or \ expr.startswith('>') or \ expr.startswith('<'): return '@HIT@ {}'.format(expr) return hit_condition def on_disconnect_request(self, py_db, request): ''' :param DisconnectRequest request: ''' self.api.remove_all_breakpoints(py_db, filename='*') self.api.remove_all_exception_breakpoints(py_db) self.api.request_resume_thread(thread_id='*') response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_setbreakpoints_request(self, py_db, request): ''' :param SetBreakpointsRequest request: ''' arguments = request.arguments # : :type arguments: SetBreakpointsArguments filename = arguments.source.path filename = self.api.filename_to_server(filename) func_name = 'None' self.api.remove_all_breakpoints(py_db, filename) btype = 'python-line' suspend_policy = 'ALL' if not filename.lower().endswith('.py'): if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django-line' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2-line' breakpoints_set = [] for source_breakpoint in arguments.breakpoints: source_breakpoint = SourceBreakpoint(**source_breakpoint) line = source_breakpoint.line condition = source_breakpoint.condition breakpoint_id = line hit_condition = self._get_hit_condition_expression(source_breakpoint.hitCondition) log_message = source_breakpoint.logMessage if not log_message: is_logpoint = None expression = None else: is_logpoint = True expression = convert_dap_log_message_to_expression(log_message) self.api.add_breakpoint( py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint) # Note that the id is made up (the id for pydevd is unique only within a file, so, the # line is used for it). # Also, the id is currently not used afterwards, so, we don't even keep a mapping. breakpoints_set.append({'id':self._next_breakpoint_id(), 'verified': True, 'line': line}) body = {'breakpoints': breakpoints_set} set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body':body}) return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) def on_setexceptionbreakpoints_request(self, py_db, request): ''' :param SetExceptionBreakpointsRequest request: ''' # : :type arguments: SetExceptionBreakpointsArguments arguments = request.arguments filters = arguments.filters exception_options = arguments.exceptionOptions self.api.remove_all_exception_breakpoints(py_db) # Can't set these in the DAP. condition = None expression = None notify_on_first_raise_only = False ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0 if exception_options: break_raised = True break_uncaught = True for option in exception_options: option = ExceptionOptions(**option) if not option.path: continue notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0 notify_on_unhandled_exceptions = 1 if option.breakMode in ('unhandled', 'userUnhandled') else 0 exception_paths = option.path exception_names = [] if len(exception_paths) == 0: continue elif len(exception_paths) == 1: if 'Python Exceptions' in exception_paths[0]['names']: exception_names = ['BaseException'] else: path_iterator = iter(exception_paths) if 'Python Exceptions' in next(path_iterator)['names']: for path in path_iterator: for ex_name in path['names']: exception_names.append(ex_name) for exception_name in exception_names: self.api.add_python_exception_breakpoint( py_db, exception_name, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, notify_on_first_raise_only, ignore_libraries ) else: break_raised = 'raised' in filters break_uncaught = 'uncaught' in filters if break_raised or break_uncaught: notify_on_handled_exceptions = 1 if break_raised else 0 notify_on_unhandled_exceptions = 1 if break_uncaught else 0 exception = 'BaseException' self.api.add_python_exception_breakpoint( py_db, exception, condition, expression, notify_on_handled_exceptions, notify_on_unhandled_exceptions, notify_on_first_raise_only, ignore_libraries ) if break_raised or break_uncaught: btype = None if self._debug_options.get('DJANGO_DEBUG', False): btype = 'django' elif self._debug_options.get('FLASK_DEBUG', False): btype = 'jinja2' if btype: self.api.add_plugins_exception_breakpoint( py_db, btype, 'BaseException') # Note: Exception name could be anything here. # Note: no body required on success. set_breakpoints_response = pydevd_base_schema.build_response(request) return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) def on_stacktrace_request(self, py_db, request): ''' :param StackTraceRequest request: ''' # : :type stack_trace_arguments: StackTraceArguments stack_trace_arguments = request.arguments thread_id = stack_trace_arguments.threadId fmt = stack_trace_arguments.format if hasattr(fmt, 'to_dict'): fmt = fmt.to_dict() self.api.request_stack(py_db, request.seq, thread_id, fmt) def on_exceptioninfo_request(self, py_db, request): ''' :param ExceptionInfoRequest request: ''' # : :type exception_into_arguments: ExceptionInfoArguments exception_into_arguments = request.arguments thread_id = exception_into_arguments.threadId max_frames = int(self._debug_options['args'].get('maxExceptionStackFrames', 0)) self.api.request_exception_info_json(py_db, request, thread_id, max_frames) def on_scopes_request(self, py_db, request): ''' Scopes are the top-level items which appear for a frame (so, we receive the frame id and provide the scopes it has). :param ScopesRequest request: ''' frame_id = request.arguments.frameId variables_reference = frame_id scopes = [Scope('Locals', int(variables_reference), False).to_dict()] body = ScopesResponseBody(scopes) scopes_response = pydevd_base_schema.build_response(request, kwargs={'body':body}) return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True) def on_evaluate_request(self, py_db, request): ''' :param EvaluateRequest request: ''' # : :type arguments: EvaluateArguments arguments = request.arguments thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( arguments.frameId) self.api.request_exec_or_evaluate_json( py_db, request, thread_id) def on_setexpression_request(self, py_db, request): # : :type arguments: SetExpressionArguments arguments = request.arguments thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( arguments.frameId) self.api.request_set_expression_json( py_db, request, thread_id) def on_variables_request(self, py_db, request): ''' Variables can be asked whenever some place returned a variables reference (so, it can be a scope gotten from on_scopes_request, the result of some evaluation, etc.). Note that in the DAP the variables reference requires a unique int... the way this works for pydevd is that an instance is generated for that specific variable reference and we use its id(instance) to identify it to make sure all items are unique (and the actual {id->instance} is added to a dict which is only valid while the thread is suspended and later cleared when the related thread resumes execution). see: SuspendedFramesManager :param VariablesRequest request: ''' arguments = request.arguments # : :type arguments: VariablesArguments variables_reference = arguments.variablesReference thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: self.api.request_get_variable_json(py_db, request, thread_id) else: variables = [] body = VariablesResponseBody(variables) variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body}) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_setvariable_request(self, py_db, request): arguments = request.arguments # : :type arguments: SetVariableArguments variables_reference = arguments.variablesReference thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( variables_reference) if thread_id is not None: self.api.request_change_variable_json(py_db, request, thread_id) else: body = SetVariableResponseBody('') variables_response = pydevd_base_schema.build_response( request, kwargs={ 'body':body, 'success': False, 'message': 'Unable to find thread to evaluate variable reference.' }) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_modules_request(self, py_db, request): modules_manager = py_db.cmd_factory.modules_manager # : :type modules_manager: ModulesManager modules_info = modules_manager.get_modules_info() body = ModulesResponseBody(modules_info) variables_response = pydevd_base_schema.build_response(request, kwargs={'body':body}) return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) def on_source_request(self, py_db, request): ''' :param SourceRequest request: ''' source_reference = request.arguments.sourceReference server_filename = None content = None if source_reference != 0: server_filename = pydevd_file_utils.get_server_filename_from_source_reference(source_reference) if server_filename: # Try direct file access first - it's much faster when available. try: with open(server_filename, 'r') as stream: content = stream.read() except: pass if content is None: # File might not exist at all, or we might not have a permission to read it, # but it might also be inside a zipfile, or an IPython cell. In this case, # linecache might still be able to retrieve the source. lines = (linecache.getline(server_filename, i) for i in itertools.count(1)) lines = itertools.takewhile(bool, lines) # empty lines are '\n', EOF is '' # If we didn't get at least one line back, reset it to None so that it's # reported as error below, and not as an empty file. content = ''.join(lines) or None body = SourceResponseBody(content or '') response_args = {'body': body} if content is None: if server_filename: message = 'Unable to retrieve source for %s' % (server_filename,) else: message = 'Invalid sourceReference %d' % (source_reference,) response_args.update({'success': False, 'message': message}) response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_gototargets_request(self, py_db, request): path = request.arguments.source.path line = request.arguments.line target_id = self._goto_targets_map.obtain_key((path, line)) target = { 'id': target_id, 'label': '%s:%s' % (path, line), 'line': line } body = GotoTargetsResponseBody(targets=[target]) response_args = {'body': body} response = pydevd_base_schema.build_response(request, kwargs=response_args) return NetCommand(CMD_RETURN, 0, response, is_json=True) def on_goto_request(self, py_db, request): target_id = int(request.arguments.targetId) thread_id = request.arguments.threadId try: _, line = self._goto_targets_map.obtain_value(target_id) except KeyError: response = pydevd_base_schema.build_response(request, kwargs={ 'body': {}, 'success': False, 'message': 'Unknown goto target id: %d' % (target_id,), }) return NetCommand(CMD_RETURN, 0, response, is_json=True) self.api.request_set_next(py_db, thread_id, CMD_SET_NEXT_STATEMENT, line, '*') response = pydevd_base_schema.build_response(request, kwargs={'body': {}}) return NetCommand(CMD_RETURN, 0, response, is_json=True)