1import itertools 2import json 3import linecache 4import os 5import platform 6import sys 7from functools import partial 8 9import pydevd_file_utils 10from _pydev_bundle import pydev_log 11from _pydevd_bundle._debug_adapter import pydevd_base_schema, pydevd_schema 12from _pydevd_bundle._debug_adapter.pydevd_schema import ( 13 CompletionsResponseBody, EvaluateResponseBody, ExceptionOptions, 14 GotoTargetsResponseBody, ModulesResponseBody, ProcessEventBody, 15 ProcessEvent, Scope, ScopesResponseBody, SetExpressionResponseBody, 16 SetVariableResponseBody, SourceBreakpoint, SourceResponseBody, 17 VariablesResponseBody, SetBreakpointsResponseBody, Response, 18 Capabilities, PydevdAuthorizeRequest, Request) 19from _pydevd_bundle.pydevd_api import PyDevdAPI 20from _pydevd_bundle.pydevd_breakpoints import get_exception_class 21from _pydevd_bundle.pydevd_comm_constants import ( 22 CMD_PROCESS_EVENT, CMD_RETURN, CMD_SET_NEXT_STATEMENT, CMD_STEP_INTO, 23 CMD_STEP_INTO_MY_CODE, CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, file_system_encoding, 24 CMD_STEP_RETURN_MY_CODE, CMD_STEP_RETURN) 25from _pydevd_bundle.pydevd_filtering import ExcludeFilter 26from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options, DebugOptions 27from _pydevd_bundle.pydevd_net_command import NetCommand 28from _pydevd_bundle.pydevd_utils import convert_dap_log_message_to_expression, ScopeRequest 29from _pydevd_bundle.pydevd_constants import (PY_IMPL_NAME, DebugInfoHolder, PY_VERSION_STR, 30 PY_IMPL_VERSION_STR, IS_64BIT_PROCESS) 31from _pydevd_bundle.pydevd_trace_dispatch import USING_CYTHON 32from _pydevd_frame_eval.pydevd_frame_eval_main import USING_FRAME_EVAL 33 34 35def _convert_rules_to_exclude_filters(rules, on_error): 36 exclude_filters = [] 37 if not isinstance(rules, list): 38 on_error('Invalid "rules" (expected list of dicts). Found: %s' % (rules,)) 39 40 else: 41 directory_exclude_filters = [] 42 module_exclude_filters = [] 43 glob_exclude_filters = [] 44 45 for rule in rules: 46 if not isinstance(rule, dict): 47 on_error('Invalid "rules" (expected list of dicts). Found: %s' % (rules,)) 48 continue 49 50 include = rule.get('include') 51 if include is None: 52 on_error('Invalid "rule" (expected dict with "include"). Found: %s' % (rule,)) 53 continue 54 55 path = rule.get('path') 56 module = rule.get('module') 57 if path is None and module is None: 58 on_error('Invalid "rule" (expected dict with "path" or "module"). Found: %s' % (rule,)) 59 continue 60 61 if path is not None: 62 glob_pattern = path 63 if '*' not in path and '?' not in path: 64 if os.path.isdir(glob_pattern): 65 # If a directory was specified, add a '/**' 66 # to be consistent with the glob pattern required 67 # by pydevd. 68 if not glob_pattern.endswith('/') and not glob_pattern.endswith('\\'): 69 glob_pattern += '/' 70 glob_pattern += '**' 71 directory_exclude_filters.append(ExcludeFilter(glob_pattern, not include, True)) 72 else: 73 glob_exclude_filters.append(ExcludeFilter(glob_pattern, not include, True)) 74 75 elif module is not None: 76 module_exclude_filters.append(ExcludeFilter(module, not include, False)) 77 78 else: 79 on_error('Internal error: expected path or module to be specified.') 80 81 # Note that we have to sort the directory/module exclude filters so that the biggest 82 # paths match first. 83 # i.e.: if we have: 84 # /sub1/sub2/sub3 85 # a rule with /sub1/sub2 would match before a rule only with /sub1. 86 directory_exclude_filters = sorted(directory_exclude_filters, key=lambda exclude_filter:-len(exclude_filter.name)) 87 module_exclude_filters = sorted(module_exclude_filters, key=lambda exclude_filter:-len(exclude_filter.name)) 88 exclude_filters = directory_exclude_filters + glob_exclude_filters + module_exclude_filters 89 90 return exclude_filters 91 92 93class IDMap(object): 94 95 def __init__(self): 96 self._value_to_key = {} 97 self._key_to_value = {} 98 self._next_id = partial(next, itertools.count(0)) 99 100 def obtain_value(self, key): 101 return self._key_to_value[key] 102 103 def obtain_key(self, value): 104 try: 105 key = self._value_to_key[value] 106 except KeyError: 107 key = self._next_id() 108 self._key_to_value[key] = value 109 self._value_to_key[value] = key 110 return key 111 112 113class PyDevJsonCommandProcessor(object): 114 115 def __init__(self, from_json): 116 self.from_json = from_json 117 self.api = PyDevdAPI() 118 self._options = DebugOptions() 119 self._next_breakpoint_id = partial(next, itertools.count(0)) 120 self._goto_targets_map = IDMap() 121 self._launch_or_attach_request_done = False 122 123 def process_net_command_json(self, py_db, json_contents, send_response=True): 124 ''' 125 Processes a debug adapter protocol json command. 126 ''' 127 128 DEBUG = False 129 130 try: 131 if isinstance(json_contents, bytes): 132 json_contents = json_contents.decode('utf-8') 133 134 request = self.from_json(json_contents, update_ids_from_dap=True) 135 except Exception as e: 136 try: 137 loaded_json = json.loads(json_contents) 138 request = Request(loaded_json.get('command', '<unknown>'), loaded_json['seq']) 139 except: 140 # There's not much we can do in this case... 141 pydev_log.exception('Error loading json: %s', json_contents) 142 return 143 144 error_msg = str(e) 145 if error_msg.startswith("'") and error_msg.endswith("'"): 146 error_msg = error_msg[1:-1] 147 148 # This means a failure processing the request (but we were able to load the seq, 149 # so, answer with a failure response). 150 def on_request(py_db, request): 151 error_response = { 152 'type': 'response', 153 'request_seq': request.seq, 154 'success': False, 155 'command': request.command, 156 'message': error_msg, 157 } 158 return NetCommand(CMD_RETURN, 0, error_response, is_json=True) 159 160 else: 161 if DebugInfoHolder.DEBUG_RECORD_SOCKET_READS and DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1: 162 pydev_log.info('Process %s: %s\n' % ( 163 request.__class__.__name__, json.dumps(request.to_dict(update_ids_to_dap=True), indent=4, sort_keys=True),)) 164 165 assert request.type == 'request' 166 method_name = 'on_%s_request' % (request.command.lower(),) 167 on_request = getattr(self, method_name, None) 168 if on_request is None: 169 print('Unhandled: %s not available in PyDevJsonCommandProcessor.\n' % (method_name,)) 170 return 171 172 if DEBUG: 173 print('Handled in pydevd: %s (in PyDevJsonCommandProcessor).\n' % (method_name,)) 174 175 with py_db._main_lock: 176 if request.__class__ == PydevdAuthorizeRequest: 177 authorize_request = request # : :type authorize_request: PydevdAuthorizeRequest 178 access_token = authorize_request.arguments.debugServerAccessToken 179 py_db.authentication.login(access_token) 180 181 if not py_db.authentication.is_authenticated(): 182 response = Response( 183 request.seq, success=False, command=request.command, message='Client not authenticated.', body={}) 184 cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) 185 py_db.writer.add_command(cmd) 186 return 187 188 cmd = on_request(py_db, request) 189 if cmd is not None and send_response: 190 py_db.writer.add_command(cmd) 191 192 def on_pydevdauthorize_request(self, py_db, request): 193 client_access_token = py_db.authentication.client_access_token 194 body = {'clientAccessToken': None} 195 if client_access_token: 196 body['clientAccessToken'] = client_access_token 197 198 response = pydevd_base_schema.build_response(request, kwargs={'body': body}) 199 return NetCommand(CMD_RETURN, 0, response, is_json=True) 200 201 def on_initialize_request(self, py_db, request): 202 body = Capabilities( 203 # Supported. 204 supportsConfigurationDoneRequest=True, 205 supportsConditionalBreakpoints=True, 206 supportsHitConditionalBreakpoints=True, 207 supportsEvaluateForHovers=True, 208 supportsSetVariable=True, 209 supportsGotoTargetsRequest=True, 210 supportsCompletionsRequest=True, 211 supportsModulesRequest=True, 212 supportsExceptionOptions=True, 213 supportsValueFormattingOptions=True, 214 supportsExceptionInfoRequest=True, 215 supportTerminateDebuggee=True, 216 supportsDelayedStackTraceLoading=True, 217 supportsLogPoints=True, 218 supportsSetExpression=True, 219 supportsTerminateRequest=True, 220 supportsClipboardContext=True, 221 222 exceptionBreakpointFilters=[ 223 {'filter': 'raised', 'label': 'Raised Exceptions', 'default': False}, 224 {'filter': 'uncaught', 'label': 'Uncaught Exceptions', 'default': True}, 225 {"filter": "userUnhandled", "label": "User Uncaught Exceptions", "default": False}, 226 ], 227 228 # Not supported. 229 supportsFunctionBreakpoints=False, 230 supportsStepBack=False, 231 supportsRestartFrame=False, 232 supportsStepInTargetsRequest=False, 233 supportsRestartRequest=False, 234 supportsLoadedSourcesRequest=False, 235 supportsTerminateThreadsRequest=False, 236 supportsDataBreakpoints=False, 237 supportsReadMemoryRequest=False, 238 supportsDisassembleRequest=False, 239 additionalModuleColumns=[], 240 completionTriggerCharacters=[], 241 supportedChecksumAlgorithms=[], 242 ).to_dict() 243 244 # Non-standard capabilities/info below. 245 body['supportsDebuggerProperties'] = True 246 247 body['pydevd'] = pydevd_info = {} 248 pydevd_info['processId'] = os.getpid() 249 self.api.notify_initialize(py_db) 250 response = pydevd_base_schema.build_response(request, kwargs={'body': body}) 251 return NetCommand(CMD_RETURN, 0, response, is_json=True) 252 253 def on_configurationdone_request(self, py_db, request): 254 ''' 255 :param ConfigurationDoneRequest request: 256 ''' 257 if not self._launch_or_attach_request_done: 258 pydev_log.critical('Missing launch request or attach request before configuration done request.') 259 260 self.api.run(py_db) 261 self.api.notify_configuration_done(py_db) 262 263 configuration_done_response = pydevd_base_schema.build_response(request) 264 return NetCommand(CMD_RETURN, 0, configuration_done_response, is_json=True) 265 266 def on_threads_request(self, py_db, request): 267 ''' 268 :param ThreadsRequest request: 269 ''' 270 return self.api.list_threads(py_db, request.seq) 271 272 def on_terminate_request(self, py_db, request): 273 ''' 274 :param TerminateRequest request: 275 ''' 276 self._request_terminate_process(py_db) 277 response = pydevd_base_schema.build_response(request) 278 return NetCommand(CMD_RETURN, 0, response, is_json=True) 279 280 def _request_terminate_process(self, py_db): 281 self.api.request_terminate_process(py_db) 282 283 def on_completions_request(self, py_db, request): 284 ''' 285 :param CompletionsRequest request: 286 ''' 287 arguments = request.arguments # : :type arguments: CompletionsArguments 288 seq = request.seq 289 text = arguments.text 290 frame_id = arguments.frameId 291 thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( 292 frame_id) 293 294 if thread_id is None: 295 body = CompletionsResponseBody([]) 296 variables_response = pydevd_base_schema.build_response( 297 request, 298 kwargs={ 299 'body': body, 300 'success': False, 301 'message': 'Thread to get completions seems to have resumed already.' 302 }) 303 return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) 304 305 # Note: line and column are 1-based (convert to 0-based for pydevd). 306 column = arguments.column - 1 307 308 if arguments.line is None: 309 # line is optional 310 line = -1 311 else: 312 line = arguments.line - 1 313 314 self.api.request_completions(py_db, seq, thread_id, frame_id, text, line=line, column=column) 315 316 def _resolve_remote_root(self, local_root, remote_root): 317 if remote_root == '.': 318 cwd = os.getcwd() 319 append_pathsep = local_root.endswith('\\') or local_root.endswith('/') 320 return cwd + (os.path.sep if append_pathsep else '') 321 return remote_root 322 323 def _set_debug_options(self, py_db, args, start_reason): 324 rules = args.get('rules') 325 stepping_resumes_all_threads = args.get('steppingResumesAllThreads', True) 326 self.api.set_stepping_resumes_all_threads(py_db, stepping_resumes_all_threads) 327 328 terminate_child_processes = args.get('terminateChildProcesses', True) 329 self.api.set_terminate_child_processes(py_db, terminate_child_processes) 330 331 variable_presentation = args.get('variablePresentation', None) 332 if isinstance(variable_presentation, dict): 333 334 def get_variable_presentation(setting, default): 335 value = variable_presentation.get(setting, default) 336 if value not in ('group', 'inline', 'hide'): 337 pydev_log.info( 338 'The value set for "%s" (%s) in the variablePresentation is not valid. Valid values are: "group", "inline", "hide"' % ( 339 setting, value,)) 340 value = default 341 342 return value 343 344 default = get_variable_presentation('all', 'group') 345 346 special_presentation = get_variable_presentation('special', default) 347 function_presentation = get_variable_presentation('function', default) 348 class_presentation = get_variable_presentation('class', default) 349 protected_presentation = get_variable_presentation('protected', default) 350 351 self.api.set_variable_presentation(py_db, self.api.VariablePresentation( 352 special_presentation, 353 function_presentation, 354 class_presentation, 355 protected_presentation 356 )) 357 358 exclude_filters = [] 359 360 if rules is not None: 361 exclude_filters = _convert_rules_to_exclude_filters( 362 rules, lambda msg: self.api.send_error_message(py_db, msg)) 363 364 self.api.set_exclude_filters(py_db, exclude_filters) 365 366 debug_options = _extract_debug_options( 367 args.get('options'), 368 args.get('debugOptions'), 369 ) 370 self._options.update_fom_debug_options(debug_options) 371 self._options.update_from_args(args) 372 373 self.api.set_use_libraries_filter(py_db, self._options.just_my_code) 374 375 path_mappings = [] 376 for pathMapping in args.get('pathMappings', []): 377 localRoot = pathMapping.get('localRoot', '') 378 remoteRoot = pathMapping.get('remoteRoot', '') 379 remoteRoot = self._resolve_remote_root(localRoot, remoteRoot) 380 if (localRoot != '') and (remoteRoot != ''): 381 path_mappings.append((localRoot, remoteRoot)) 382 383 if bool(path_mappings): 384 pydevd_file_utils.setup_client_server_paths(path_mappings) 385 386 if self._options.redirect_output: 387 py_db.enable_output_redirection(True, True) 388 else: 389 py_db.enable_output_redirection(False, False) 390 391 self.api.set_show_return_values(py_db, self._options.show_return_value) 392 393 if not self._options.break_system_exit_zero: 394 ignore_system_exit_codes = [0, None] 395 if self._options.django_debug: 396 ignore_system_exit_codes += [3] 397 398 self.api.set_ignore_system_exit_codes(py_db, ignore_system_exit_codes) 399 400 if self._options.stop_on_entry and start_reason == 'launch': 401 self.api.stop_on_entry() 402 403 def _send_process_event(self, py_db, start_method): 404 argv = getattr(sys, 'argv', []) 405 if len(argv) > 0: 406 name = argv[0] 407 else: 408 name = '' 409 410 if isinstance(name, bytes): 411 name = name.decode(file_system_encoding, 'replace') 412 name = name.encode('utf-8') 413 414 body = ProcessEventBody( 415 name=name, 416 systemProcessId=os.getpid(), 417 isLocalProcess=True, 418 startMethod=start_method, 419 ) 420 event = ProcessEvent(body) 421 py_db.writer.add_command(NetCommand(CMD_PROCESS_EVENT, 0, event, is_json=True)) 422 423 def _handle_launch_or_attach_request(self, py_db, request, start_reason): 424 self._send_process_event(py_db, start_reason) 425 self._launch_or_attach_request_done = True 426 self.api.set_enable_thread_notifications(py_db, True) 427 self._set_debug_options(py_db, request.arguments.kwargs, start_reason=start_reason) 428 response = pydevd_base_schema.build_response(request) 429 return NetCommand(CMD_RETURN, 0, response, is_json=True) 430 431 def on_launch_request(self, py_db, request): 432 ''' 433 :param LaunchRequest request: 434 ''' 435 return self._handle_launch_or_attach_request(py_db, request, start_reason='launch') 436 437 def on_attach_request(self, py_db, request): 438 ''' 439 :param AttachRequest request: 440 ''' 441 return self._handle_launch_or_attach_request(py_db, request, start_reason='attach') 442 443 def on_pause_request(self, py_db, request): 444 ''' 445 :param PauseRequest request: 446 ''' 447 arguments = request.arguments # : :type arguments: PauseArguments 448 thread_id = arguments.threadId 449 450 self.api.request_suspend_thread(py_db, thread_id=thread_id) 451 452 response = pydevd_base_schema.build_response(request) 453 return NetCommand(CMD_RETURN, 0, response, is_json=True) 454 455 def on_continue_request(self, py_db, request): 456 ''' 457 :param ContinueRequest request: 458 ''' 459 arguments = request.arguments # : :type arguments: ContinueArguments 460 thread_id = arguments.threadId 461 462 def on_resumed(): 463 body = {'allThreadsContinued': thread_id == '*'} 464 response = pydevd_base_schema.build_response(request, kwargs={'body': body}) 465 cmd = NetCommand(CMD_RETURN, 0, response, is_json=True) 466 py_db.writer.add_command(cmd) 467 468 # Only send resumed notification when it has actually resumed! 469 # (otherwise the user could send a continue, receive the notification and then 470 # request a new pause which would be paused without sending any notification as 471 # it didn't really run in the first place). 472 py_db.threads_suspended_single_notification.add_on_resumed_callback(on_resumed) 473 self.api.request_resume_thread(thread_id) 474 475 def on_next_request(self, py_db, request): 476 ''' 477 :param NextRequest request: 478 ''' 479 arguments = request.arguments # : :type arguments: NextArguments 480 thread_id = arguments.threadId 481 482 if py_db.get_use_libraries_filter(): 483 step_cmd_id = CMD_STEP_OVER_MY_CODE 484 else: 485 step_cmd_id = CMD_STEP_OVER 486 487 self.api.request_step(py_db, thread_id, step_cmd_id) 488 489 response = pydevd_base_schema.build_response(request) 490 return NetCommand(CMD_RETURN, 0, response, is_json=True) 491 492 def on_stepin_request(self, py_db, request): 493 ''' 494 :param StepInRequest request: 495 ''' 496 arguments = request.arguments # : :type arguments: StepInArguments 497 thread_id = arguments.threadId 498 499 if py_db.get_use_libraries_filter(): 500 step_cmd_id = CMD_STEP_INTO_MY_CODE 501 else: 502 step_cmd_id = CMD_STEP_INTO 503 504 self.api.request_step(py_db, thread_id, step_cmd_id) 505 506 response = pydevd_base_schema.build_response(request) 507 return NetCommand(CMD_RETURN, 0, response, is_json=True) 508 509 def on_stepout_request(self, py_db, request): 510 ''' 511 :param StepOutRequest request: 512 ''' 513 arguments = request.arguments # : :type arguments: StepOutArguments 514 thread_id = arguments.threadId 515 516 if py_db.get_use_libraries_filter(): 517 step_cmd_id = CMD_STEP_RETURN_MY_CODE 518 else: 519 step_cmd_id = CMD_STEP_RETURN 520 521 self.api.request_step(py_db, thread_id, step_cmd_id) 522 523 response = pydevd_base_schema.build_response(request) 524 return NetCommand(CMD_RETURN, 0, response, is_json=True) 525 526 def _get_hit_condition_expression(self, hit_condition): 527 '''Following hit condition values are supported 528 529 * x or == x when breakpoint is hit x times 530 * >= x when breakpoint is hit more than or equal to x times 531 * % x when breakpoint is hit multiple of x times 532 533 Returns '@HIT@ == x' where @HIT@ will be replaced by number of hits 534 ''' 535 if not hit_condition: 536 return None 537 538 expr = hit_condition.strip() 539 try: 540 int(expr) 541 return '@HIT@ == {}'.format(expr) 542 except ValueError: 543 pass 544 545 if expr.startswith('%'): 546 return '@HIT@ {} == 0'.format(expr) 547 548 if expr.startswith('==') or \ 549 expr.startswith('>') or \ 550 expr.startswith('<'): 551 return '@HIT@ {}'.format(expr) 552 553 return hit_condition 554 555 def on_disconnect_request(self, py_db, request): 556 ''' 557 :param DisconnectRequest request: 558 ''' 559 if request.arguments.terminateDebuggee: 560 self._request_terminate_process(py_db) 561 response = pydevd_base_schema.build_response(request) 562 return NetCommand(CMD_RETURN, 0, response, is_json=True) 563 564 self._launch_or_attach_request_done = False 565 py_db.enable_output_redirection(False, False) 566 self.api.request_disconnect(py_db, resume_threads=True) 567 568 response = pydevd_base_schema.build_response(request) 569 return NetCommand(CMD_RETURN, 0, response, is_json=True) 570 571 def on_setbreakpoints_request(self, py_db, request): 572 ''' 573 :param SetBreakpointsRequest request: 574 ''' 575 if not self._launch_or_attach_request_done: 576 # Note that to validate the breakpoints we need the launch request to be done already 577 # (otherwise the filters wouldn't be set for the breakpoint validation). 578 body = SetBreakpointsResponseBody([]) 579 response = pydevd_base_schema.build_response( 580 request, 581 kwargs={ 582 'body': body, 583 'success': False, 584 'message': 'Breakpoints may only be set after the launch request is received.' 585 }) 586 return NetCommand(CMD_RETURN, 0, response, is_json=True) 587 588 arguments = request.arguments # : :type arguments: SetBreakpointsArguments 589 # TODO: Path is optional here it could be source reference. 590 filename = self.api.filename_to_str(arguments.source.path) 591 func_name = 'None' 592 593 self.api.remove_all_breakpoints(py_db, filename) 594 595 btype = 'python-line' 596 suspend_policy = 'ALL' 597 598 if not filename.lower().endswith('.py'): # Note: check based on original file, not mapping. 599 if self._options.django_debug: 600 btype = 'django-line' 601 elif self._options.flask_debug: 602 btype = 'jinja2-line' 603 604 breakpoints_set = [] 605 606 for source_breakpoint in arguments.breakpoints: 607 source_breakpoint = SourceBreakpoint(**source_breakpoint) 608 line = source_breakpoint.line 609 condition = source_breakpoint.condition 610 breakpoint_id = line 611 612 hit_condition = self._get_hit_condition_expression(source_breakpoint.hitCondition) 613 log_message = source_breakpoint.logMessage 614 if not log_message: 615 is_logpoint = None 616 expression = None 617 else: 618 is_logpoint = True 619 expression = convert_dap_log_message_to_expression(log_message) 620 621 result = self.api.add_breakpoint( 622 py_db, filename, btype, breakpoint_id, line, condition, func_name, expression, suspend_policy, hit_condition, is_logpoint, adjust_line=True) 623 error_code = result.error_code 624 625 if error_code: 626 if error_code == self.api.ADD_BREAKPOINT_FILE_NOT_FOUND: 627 error_msg = 'Breakpoint in file that does not exist.' 628 629 elif error_code == self.api.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS: 630 error_msg = 'Breakpoint in file excluded by filters.' 631 if py_db.get_use_libraries_filter(): 632 error_msg += ('\nNote: may be excluded because of "justMyCode" option (default == true).' 633 'Try setting \"justMyCode\": false in the debug configuration (e.g., launch.json).\n') 634 635 else: 636 # Shouldn't get here. 637 error_msg = 'Breakpoint not validated (reason unknown -- please report as bug).' 638 639 breakpoints_set.append(pydevd_schema.Breakpoint( 640 verified=False, line=result.translated_line, message=error_msg, source=arguments.source).to_dict()) 641 else: 642 # Note that the id is made up (the id for pydevd is unique only within a file, so, the 643 # line is used for it). 644 # Also, the id is currently not used afterwards, so, we don't even keep a mapping. 645 breakpoints_set.append(pydevd_schema.Breakpoint( 646 verified=True, id=self._next_breakpoint_id(), line=result.translated_line, source=arguments.source).to_dict()) 647 648 body = {'breakpoints': breakpoints_set} 649 set_breakpoints_response = pydevd_base_schema.build_response(request, kwargs={'body': body}) 650 return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) 651 652 def on_setexceptionbreakpoints_request(self, py_db, request): 653 ''' 654 :param SetExceptionBreakpointsRequest request: 655 ''' 656 # : :type arguments: SetExceptionBreakpointsArguments 657 arguments = request.arguments 658 filters = arguments.filters 659 exception_options = arguments.exceptionOptions 660 self.api.remove_all_exception_breakpoints(py_db) 661 662 # Can't set these in the DAP. 663 condition = None 664 expression = None 665 notify_on_first_raise_only = False 666 667 ignore_libraries = 1 if py_db.get_use_libraries_filter() else 0 668 669 if exception_options: 670 break_raised = False 671 break_uncaught = False 672 673 for option in exception_options: 674 option = ExceptionOptions(**option) 675 if not option.path: 676 continue 677 678 # never: never breaks 679 # 680 # always: always breaks 681 # 682 # unhandled: breaks when exception unhandled 683 # 684 # userUnhandled: breaks if the exception is not handled by user code 685 686 notify_on_handled_exceptions = 1 if option.breakMode == 'always' else 0 687 notify_on_unhandled_exceptions = 1 if option.breakMode == 'unhandled' else 0 688 notify_on_user_unhandled_exceptions = 1 if option.breakMode == 'userUnhandled' else 0 689 exception_paths = option.path 690 break_raised |= notify_on_handled_exceptions 691 break_uncaught |= notify_on_unhandled_exceptions 692 693 exception_names = [] 694 if len(exception_paths) == 0: 695 continue 696 697 elif len(exception_paths) == 1: 698 if 'Python Exceptions' in exception_paths[0]['names']: 699 exception_names = ['BaseException'] 700 701 else: 702 path_iterator = iter(exception_paths) 703 if 'Python Exceptions' in next(path_iterator)['names']: 704 for path in path_iterator: 705 for ex_name in path['names']: 706 exception_names.append(ex_name) 707 708 for exception_name in exception_names: 709 self.api.add_python_exception_breakpoint( 710 py_db, 711 exception_name, 712 condition, 713 expression, 714 notify_on_handled_exceptions, 715 notify_on_unhandled_exceptions, 716 notify_on_user_unhandled_exceptions, 717 notify_on_first_raise_only, 718 ignore_libraries 719 ) 720 721 else: 722 break_raised = 'raised' in filters 723 break_uncaught = 'uncaught' in filters 724 break_user = 'userUnhandled' in filters 725 if break_raised or break_uncaught or break_user: 726 notify_on_handled_exceptions = 1 if break_raised else 0 727 notify_on_unhandled_exceptions = 1 if break_uncaught else 0 728 notify_on_user_unhandled_exceptions = 1 if break_user else 0 729 exception = 'BaseException' 730 731 self.api.add_python_exception_breakpoint( 732 py_db, 733 exception, 734 condition, 735 expression, 736 notify_on_handled_exceptions, 737 notify_on_unhandled_exceptions, 738 notify_on_user_unhandled_exceptions, 739 notify_on_first_raise_only, 740 ignore_libraries 741 ) 742 743 if break_raised: 744 btype = None 745 if self._options.django_debug: 746 btype = 'django' 747 elif self._options.flask_debug: 748 btype = 'jinja2' 749 750 if btype: 751 self.api.add_plugins_exception_breakpoint( 752 py_db, btype, 'BaseException') # Note: Exception name could be anything here. 753 754 # Note: no body required on success. 755 set_breakpoints_response = pydevd_base_schema.build_response(request) 756 return NetCommand(CMD_RETURN, 0, set_breakpoints_response, is_json=True) 757 758 def on_stacktrace_request(self, py_db, request): 759 ''' 760 :param StackTraceRequest request: 761 ''' 762 # : :type stack_trace_arguments: StackTraceArguments 763 stack_trace_arguments = request.arguments 764 thread_id = stack_trace_arguments.threadId 765 start_frame = stack_trace_arguments.startFrame 766 levels = stack_trace_arguments.levels 767 768 fmt = stack_trace_arguments.format 769 if hasattr(fmt, 'to_dict'): 770 fmt = fmt.to_dict() 771 self.api.request_stack(py_db, request.seq, thread_id, fmt=fmt, start_frame=start_frame, levels=levels) 772 773 def on_exceptioninfo_request(self, py_db, request): 774 ''' 775 :param ExceptionInfoRequest request: 776 ''' 777 # : :type exception_into_arguments: ExceptionInfoArguments 778 exception_into_arguments = request.arguments 779 thread_id = exception_into_arguments.threadId 780 max_frames = self._options.max_exception_stack_frames 781 self.api.request_exception_info_json(py_db, request, thread_id, max_frames) 782 783 def on_scopes_request(self, py_db, request): 784 ''' 785 Scopes are the top-level items which appear for a frame (so, we receive the frame id 786 and provide the scopes it has). 787 788 :param ScopesRequest request: 789 ''' 790 frame_id = request.arguments.frameId 791 792 variables_reference = frame_id 793 scopes = [ 794 Scope('Locals', ScopeRequest(int(variables_reference), 'locals'), False, presentationHint='locals'), 795 Scope('Globals', ScopeRequest(int(variables_reference), 'globals'), False), 796 ] 797 body = ScopesResponseBody(scopes) 798 scopes_response = pydevd_base_schema.build_response(request, kwargs={'body': body}) 799 return NetCommand(CMD_RETURN, 0, scopes_response, is_json=True) 800 801 def on_evaluate_request(self, py_db, request): 802 ''' 803 :param EvaluateRequest request: 804 ''' 805 # : :type arguments: EvaluateArguments 806 arguments = request.arguments 807 808 if arguments.frameId is None: 809 self.api.request_exec_or_evaluate_json(py_db, request, thread_id='*') 810 else: 811 thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( 812 arguments.frameId) 813 814 if thread_id is not None: 815 self.api.request_exec_or_evaluate_json( 816 py_db, request, thread_id) 817 else: 818 body = EvaluateResponseBody('', 0) 819 response = pydevd_base_schema.build_response( 820 request, 821 kwargs={ 822 'body': body, 823 'success': False, 824 'message': 'Unable to find thread for evaluation.' 825 }) 826 return NetCommand(CMD_RETURN, 0, response, is_json=True) 827 828 def on_setexpression_request(self, py_db, request): 829 # : :type arguments: SetExpressionArguments 830 arguments = request.arguments 831 832 thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( 833 arguments.frameId) 834 835 if thread_id is not None: 836 self.api.request_set_expression_json(py_db, request, thread_id) 837 else: 838 body = SetExpressionResponseBody('') 839 response = pydevd_base_schema.build_response( 840 request, 841 kwargs={ 842 'body': body, 843 'success': False, 844 'message': 'Unable to find thread to set expression.' 845 }) 846 return NetCommand(CMD_RETURN, 0, response, is_json=True) 847 848 def on_variables_request(self, py_db, request): 849 ''' 850 Variables can be asked whenever some place returned a variables reference (so, it 851 can be a scope gotten from on_scopes_request, the result of some evaluation, etc.). 852 853 Note that in the DAP the variables reference requires a unique int... the way this works for 854 pydevd is that an instance is generated for that specific variable reference and we use its 855 id(instance) to identify it to make sure all items are unique (and the actual {id->instance} 856 is added to a dict which is only valid while the thread is suspended and later cleared when 857 the related thread resumes execution). 858 859 see: SuspendedFramesManager 860 861 :param VariablesRequest request: 862 ''' 863 arguments = request.arguments # : :type arguments: VariablesArguments 864 variables_reference = arguments.variablesReference 865 866 if isinstance(variables_reference, ScopeRequest): 867 variables_reference = variables_reference.variable_reference 868 869 thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( 870 variables_reference) 871 if thread_id is not None: 872 self.api.request_get_variable_json(py_db, request, thread_id) 873 else: 874 variables = [] 875 body = VariablesResponseBody(variables) 876 variables_response = pydevd_base_schema.build_response(request, kwargs={ 877 'body': body, 878 'success': False, 879 'message': 'Unable to find thread to evaluate variable reference.' 880 }) 881 return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) 882 883 def on_setvariable_request(self, py_db, request): 884 arguments = request.arguments # : :type arguments: SetVariableArguments 885 variables_reference = arguments.variablesReference 886 887 if isinstance(variables_reference, ScopeRequest): 888 variables_reference = variables_reference.variable_reference 889 890 if arguments.name.startswith('(return) '): 891 response = pydevd_base_schema.build_response( 892 request, 893 kwargs={ 894 'body': SetVariableResponseBody(''), 895 'success': False, 896 'message': 'Cannot change return value' 897 }) 898 return NetCommand(CMD_RETURN, 0, response, is_json=True) 899 900 thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference( 901 variables_reference) 902 903 if thread_id is not None: 904 self.api.request_change_variable_json(py_db, request, thread_id) 905 else: 906 response = pydevd_base_schema.build_response( 907 request, 908 kwargs={ 909 'body': SetVariableResponseBody(''), 910 'success': False, 911 'message': 'Unable to find thread to evaluate variable reference.' 912 }) 913 return NetCommand(CMD_RETURN, 0, response, is_json=True) 914 915 def on_modules_request(self, py_db, request): 916 modules_manager = py_db.cmd_factory.modules_manager # : :type modules_manager: ModulesManager 917 modules_info = modules_manager.get_modules_info() 918 body = ModulesResponseBody(modules_info) 919 variables_response = pydevd_base_schema.build_response(request, kwargs={'body': body}) 920 return NetCommand(CMD_RETURN, 0, variables_response, is_json=True) 921 922 def on_source_request(self, py_db, request): 923 ''' 924 :param SourceRequest request: 925 ''' 926 source_reference = request.arguments.sourceReference 927 server_filename = None 928 content = None 929 930 if source_reference != 0: 931 server_filename = pydevd_file_utils.get_server_filename_from_source_reference(source_reference) 932 if not server_filename: 933 server_filename = pydevd_file_utils.get_source_reference_filename_from_linecache(source_reference) 934 935 if server_filename: 936 # Try direct file access first - it's much faster when available. 937 try: 938 with open(server_filename, 'r') as stream: 939 content = stream.read() 940 except: 941 pass 942 943 if content is None: 944 # File might not exist at all, or we might not have a permission to read it, 945 # but it might also be inside a zipfile, or an IPython cell. In this case, 946 # linecache might still be able to retrieve the source. 947 lines = (linecache.getline(server_filename, i) for i in itertools.count(1)) 948 lines = itertools.takewhile(bool, lines) # empty lines are '\n', EOF is '' 949 950 # If we didn't get at least one line back, reset it to None so that it's 951 # reported as error below, and not as an empty file. 952 content = ''.join(lines) or None 953 954 if content is None: 955 frame_id = pydevd_file_utils.get_frame_id_from_source_reference(source_reference) 956 pydev_log.debug('Found frame id: %s for source reference: %s', frame_id, source_reference) 957 if frame_id is not None: 958 try: 959 content = self.api.get_decompiled_source_from_frame_id(py_db, frame_id) 960 except Exception: 961 pydev_log.exception('Error getting source for frame id: %s', frame_id) 962 content = None 963 964 body = SourceResponseBody(content or '') 965 response_args = {'body': body} 966 967 if content is None: 968 if source_reference == 0: 969 message = 'Source unavailable' 970 elif server_filename: 971 message = 'Unable to retrieve source for %s' % (server_filename,) 972 else: 973 message = 'Invalid sourceReference %d' % (source_reference,) 974 response_args.update({'success': False, 'message': message}) 975 976 response = pydevd_base_schema.build_response(request, kwargs=response_args) 977 return NetCommand(CMD_RETURN, 0, response, is_json=True) 978 979 def on_gototargets_request(self, py_db, request): 980 path = request.arguments.source.path 981 line = request.arguments.line 982 target_id = self._goto_targets_map.obtain_key((path, line)) 983 target = { 984 'id': target_id, 985 'label': '%s:%s' % (path, line), 986 'line': line 987 } 988 body = GotoTargetsResponseBody(targets=[target]) 989 response_args = {'body': body} 990 response = pydevd_base_schema.build_response(request, kwargs=response_args) 991 return NetCommand(CMD_RETURN, 0, response, is_json=True) 992 993 def on_goto_request(self, py_db, request): 994 target_id = int(request.arguments.targetId) 995 thread_id = request.arguments.threadId 996 try: 997 path, line = self._goto_targets_map.obtain_value(target_id) 998 except KeyError: 999 response = pydevd_base_schema.build_response( 1000 request, 1001 kwargs={ 1002 'body': {}, 1003 'success': False, 1004 'message': 'Unknown goto target id: %d' % (target_id,), 1005 }) 1006 return NetCommand(CMD_RETURN, 0, response, is_json=True) 1007 1008 self.api.request_set_next(py_db, request.seq, thread_id, CMD_SET_NEXT_STATEMENT, path, line, '*') 1009 # See 'NetCommandFactoryJson.make_set_next_stmnt_status_message' for response 1010 return None 1011 1012 def on_setdebuggerproperty_request(self, py_db, request): 1013 args = request.arguments # : :type args: SetDebuggerPropertyArguments 1014 if args.ideOS is not None: 1015 self.api.set_ide_os(args.ideOS) 1016 1017 if args.dontTraceStartPatterns is not None and args.dontTraceEndPatterns is not None: 1018 start_patterns = tuple(args.dontTraceStartPatterns) 1019 end_patterns = tuple(args.dontTraceEndPatterns) 1020 self.api.set_dont_trace_start_end_patterns(py_db, start_patterns, end_patterns) 1021 1022 if args.skipSuspendOnBreakpointException is not None: 1023 py_db.skip_suspend_on_breakpoint_exception = tuple( 1024 get_exception_class(x) for x in args.skipSuspendOnBreakpointException) 1025 1026 if args.skipPrintBreakpointException is not None: 1027 py_db.skip_print_breakpoint_exception = tuple( 1028 get_exception_class(x) for x in args.skipPrintBreakpointException) 1029 1030 if args.multiThreadsSingleNotification is not None: 1031 py_db.multi_threads_single_notification = args.multiThreadsSingleNotification 1032 1033 # TODO: Support other common settings. Note that not all of these might be relevant to python. 1034 # JustMyCodeStepping: 0 or 1 1035 # AllowOutOfProcessSymbols: 0 or 1 1036 # DisableJITOptimization: 0 or 1 1037 # InterpreterOptions: 0 or 1 1038 # StopOnExceptionCrossingManagedBoundary: 0 or 1 1039 # WarnIfNoUserCodeOnLaunch: 0 or 1 1040 # EnableStepFiltering: true of false 1041 1042 response = pydevd_base_schema.build_response(request, kwargs={'body': {}}) 1043 return NetCommand(CMD_RETURN, 0, response, is_json=True) 1044 1045 def on_pydevdsysteminfo_request(self, py_db, request): 1046 try: 1047 pid = os.getpid() 1048 except AttributeError: 1049 pid = None 1050 1051 # It's possible to have the ppid reported from args. In this case, use that instead of the 1052 # real ppid (athough we're using `ppid`, what we want in meaning is the `launcher_pid` -- 1053 # so, if a python process is launched from another python process, consider that process the 1054 # parent and not any intermediary stubs). 1055 1056 ppid = py_db.get_arg_ppid() or self.api.get_ppid() 1057 1058 try: 1059 impl_desc = platform.python_implementation() 1060 except AttributeError: 1061 impl_desc = PY_IMPL_NAME 1062 1063 py_info = pydevd_schema.PydevdPythonInfo( 1064 version=PY_VERSION_STR, 1065 implementation=pydevd_schema.PydevdPythonImplementationInfo( 1066 name=PY_IMPL_NAME, 1067 version=PY_IMPL_VERSION_STR, 1068 description=impl_desc, 1069 ) 1070 ) 1071 platform_info = pydevd_schema.PydevdPlatformInfo(name=sys.platform) 1072 process_info = pydevd_schema.PydevdProcessInfo( 1073 pid=pid, 1074 ppid=ppid, 1075 executable=sys.executable, 1076 bitness=64 if IS_64BIT_PROCESS else 32, 1077 ) 1078 pydevd_info = pydevd_schema.PydevdInfo( 1079 usingCython=USING_CYTHON, 1080 usingFrameEval=USING_FRAME_EVAL, 1081 ) 1082 body = { 1083 'python': py_info, 1084 'platform': platform_info, 1085 'process': process_info, 1086 'pydevd': pydevd_info, 1087 } 1088 response = pydevd_base_schema.build_response(request, kwargs={'body': body}) 1089 return NetCommand(CMD_RETURN, 0, response, is_json=True) 1090 1091 def on_setpydevdsourcemap_request(self, py_db, request): 1092 args = request.arguments # : :type args: SetPydevdSourceMapArguments 1093 SourceMappingEntry = self.api.SourceMappingEntry 1094 1095 path = args.source.path 1096 source_maps = args.pydevdSourceMaps 1097 # : :type source_map: PydevdSourceMap 1098 new_mappings = [ 1099 SourceMappingEntry( 1100 source_map['line'], 1101 source_map['endLine'], 1102 source_map['runtimeLine'], 1103 self.api.filename_to_str(source_map['runtimeSource']['path']) 1104 ) for source_map in source_maps 1105 ] 1106 1107 error_msg = self.api.set_source_mapping(py_db, path, new_mappings) 1108 if error_msg: 1109 response = pydevd_base_schema.build_response( 1110 request, 1111 kwargs={ 1112 'body': {}, 1113 'success': False, 1114 'message': error_msg, 1115 }) 1116 return NetCommand(CMD_RETURN, 0, response, is_json=True) 1117 1118 response = pydevd_base_schema.build_response(request) 1119 return NetCommand(CMD_RETURN, 0, response, is_json=True) 1120 1121