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