1import sys
2import bisect
3import types
4
5from _pydev_imps._pydev_saved_modules import threading
6from _pydevd_bundle import pydevd_utils, pydevd_source_mapping
7from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
8from _pydevd_bundle.pydevd_comm import (InternalGetThreadStack, internal_get_completions,
9    InternalSetNextStatementThread, internal_reload_code,
10    InternalGetVariable, InternalGetArray, InternalLoadFullValue,
11    internal_get_description, internal_get_frame, internal_evaluate_expression, InternalConsoleExec,
12    internal_get_variable_json, internal_change_variable, internal_change_variable_json,
13    internal_evaluate_expression_json, internal_set_expression_json, internal_get_exception_details_json,
14    internal_step_in_thread)
15from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, file_system_encoding,
16    CMD_STEP_INTO_MY_CODE, CMD_STOP_ON_START)
17from _pydevd_bundle.pydevd_constants import (get_current_thread_id, set_protocol, get_protocol,
18    HTTP_JSON_PROTOCOL, JSON_PROTOCOL, IS_PY3K, DebugInfoHolder, dict_keys, dict_items, IS_WINDOWS)
19from _pydevd_bundle.pydevd_net_command_factory_json import NetCommandFactoryJson
20from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
21import pydevd_file_utils
22from _pydev_bundle import pydev_log
23from _pydevd_bundle.pydevd_breakpoints import LineBreakpoint
24from pydevd_tracing import get_exception_traceback_str
25import os
26import subprocess
27import ctypes
28from _pydevd_bundle.pydevd_collect_bytecode_info import code_to_bytecode_representation
29import itertools
30import linecache
31from _pydevd_bundle.pydevd_utils import DAPGrouper
32from _pydevd_bundle.pydevd_daemon_thread import run_as_pydevd_daemon_thread
33from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id, resume_threads
34
35try:
36    import dis
37except ImportError:
38
39    def _get_code_lines(code):
40        raise NotImplementedError
41
42else:
43
44    def _get_code_lines(code):
45        if not isinstance(code, types.CodeType):
46            path = code
47            with open(path) as f:
48                src = f.read()
49            code = compile(src, path, 'exec', 0, dont_inherit=True)
50            return _get_code_lines(code)
51
52        def iterate():
53            # First, get all line starts for this code object. This does not include
54            # bodies of nested class and function definitions, as they have their
55            # own objects.
56            for _, lineno in dis.findlinestarts(code):
57                yield lineno
58
59            # For nested class and function definitions, their respective code objects
60            # are constants referenced by this object.
61            for const in code.co_consts:
62                if isinstance(const, types.CodeType) and const.co_filename == code.co_filename:
63                    for lineno in _get_code_lines(const):
64                        yield lineno
65
66        return iterate()
67
68
69class PyDevdAPI(object):
70
71    class VariablePresentation(object):
72
73        def __init__(self, special='group', function='group', class_='group', protected='inline'):
74            self._presentation = {
75                DAPGrouper.SCOPE_SPECIAL_VARS: special,
76                DAPGrouper.SCOPE_FUNCTION_VARS: function,
77                DAPGrouper.SCOPE_CLASS_VARS: class_,
78                DAPGrouper.SCOPE_PROTECTED_VARS: protected,
79            }
80
81        def get_presentation(self, scope):
82            return self._presentation[scope]
83
84    def run(self, py_db):
85        py_db.ready_to_run = True
86
87    def notify_initialize(self, py_db):
88        py_db.on_initialize()
89
90    def notify_configuration_done(self, py_db):
91        py_db.on_configuration_done()
92
93    def notify_disconnect(self, py_db):
94        py_db.on_disconnect()
95
96    def set_protocol(self, py_db, seq, protocol):
97        set_protocol(protocol.strip())
98        if get_protocol() in (HTTP_JSON_PROTOCOL, JSON_PROTOCOL):
99            cmd_factory_class = NetCommandFactoryJson
100        else:
101            cmd_factory_class = NetCommandFactory
102
103        if not isinstance(py_db.cmd_factory, cmd_factory_class):
104            py_db.cmd_factory = cmd_factory_class()
105
106        return py_db.cmd_factory.make_protocol_set_message(seq)
107
108    def set_ide_os_and_breakpoints_by(self, py_db, seq, ide_os, breakpoints_by):
109        '''
110        :param ide_os: 'WINDOWS' or 'UNIX'
111        :param breakpoints_by: 'ID' or 'LINE'
112        '''
113        if breakpoints_by == 'ID':
114            py_db._set_breakpoints_with_id = True
115        else:
116            py_db._set_breakpoints_with_id = False
117
118        self.set_ide_os(ide_os)
119
120        return py_db.cmd_factory.make_version_message(seq)
121
122    def set_ide_os(self, ide_os):
123        '''
124        :param ide_os: 'WINDOWS' or 'UNIX'
125        '''
126        pydevd_file_utils.set_ide_os(ide_os)
127
128    def send_error_message(self, py_db, msg):
129        sys.stderr.write('pydevd: %s\n' % (msg,))
130
131    def set_show_return_values(self, py_db, show_return_values):
132        if show_return_values:
133            py_db.show_return_values = True
134        else:
135            if py_db.show_return_values:
136                # We should remove saved return values
137                py_db.remove_return_values_flag = True
138            py_db.show_return_values = False
139        pydev_log.debug("Show return values: %s", py_db.show_return_values)
140
141    def list_threads(self, py_db, seq):
142        # Response is the command with the list of threads to be added to the writer thread.
143        return py_db.cmd_factory.make_list_threads_message(py_db, seq)
144
145    def request_suspend_thread(self, py_db, thread_id='*'):
146        # Yes, thread suspend is done at this point, not through an internal command.
147        threads = []
148        suspend_all = thread_id.strip() == '*'
149        if suspend_all:
150            threads = pydevd_utils.get_non_pydevd_threads()
151
152        elif thread_id.startswith('__frame__:'):
153            sys.stderr.write("Can't suspend tasklet: %s\n" % (thread_id,))
154
155        else:
156            threads = [pydevd_find_thread_by_id(thread_id)]
157
158        for t in threads:
159            if t is None:
160                continue
161            py_db.set_suspend(
162                t,
163                CMD_THREAD_SUSPEND,
164                suspend_other_threads=suspend_all,
165                is_pause=True,
166            )
167            # Break here (even if it's suspend all) as py_db.set_suspend will
168            # take care of suspending other threads.
169            break
170
171    def set_enable_thread_notifications(self, py_db, enable):
172        '''
173        When disabled, no thread notifications (for creation/removal) will be
174        issued until it's re-enabled.
175
176        Note that when it's re-enabled, a creation notification will be sent for
177        all existing threads even if it was previously sent (this is meant to
178        be used on disconnect/reconnect).
179        '''
180        py_db.set_enable_thread_notifications(enable)
181
182    def request_disconnect(self, py_db, resume_threads):
183        self.set_enable_thread_notifications(py_db, False)
184        self.remove_all_breakpoints(py_db, filename='*')
185        self.remove_all_exception_breakpoints(py_db)
186        self.notify_disconnect(py_db)
187
188        if resume_threads:
189            self.request_resume_thread(thread_id='*')
190
191    def request_resume_thread(self, thread_id):
192        resume_threads(thread_id)
193
194    def request_completions(self, py_db, seq, thread_id, frame_id, act_tok, line=-1, column=-1):
195        py_db.post_method_as_internal_command(
196            thread_id, internal_get_completions, seq, thread_id, frame_id, act_tok, line=line, column=column)
197
198    def request_stack(self, py_db, seq, thread_id, fmt=None, timeout=.5, start_frame=0, levels=0):
199        # If it's already suspended, get it right away.
200        internal_get_thread_stack = InternalGetThreadStack(
201            seq, thread_id, py_db, set_additional_thread_info, fmt=fmt, timeout=timeout, start_frame=start_frame, levels=levels)
202        if internal_get_thread_stack.can_be_executed_by(get_current_thread_id(threading.current_thread())):
203            internal_get_thread_stack.do_it(py_db)
204        else:
205            py_db.post_internal_command(internal_get_thread_stack, '*')
206
207    def request_exception_info_json(self, py_db, request, thread_id, max_frames):
208        py_db.post_method_as_internal_command(
209            thread_id,
210            internal_get_exception_details_json,
211            request,
212            thread_id,
213            max_frames,
214            set_additional_thread_info=set_additional_thread_info,
215            iter_visible_frames_info=py_db.cmd_factory._iter_visible_frames_info,
216        )
217
218    def request_step(self, py_db, thread_id, step_cmd_id):
219        t = pydevd_find_thread_by_id(thread_id)
220        if t:
221            py_db.post_method_as_internal_command(
222                thread_id,
223                internal_step_in_thread,
224                thread_id,
225                step_cmd_id,
226                set_additional_thread_info=set_additional_thread_info,
227            )
228        elif thread_id.startswith('__frame__:'):
229            sys.stderr.write("Can't make tasklet step command: %s\n" % (thread_id,))
230
231    def request_set_next(self, py_db, seq, thread_id, set_next_cmd_id, original_filename, line, func_name):
232        '''
233        :param Optional[str] original_filename:
234            If available, the filename may be source translated, otherwise no translation will take
235            place (the set next just needs the line afterwards as it executes locally, but for
236            the Jupyter integration, the source mapping may change the actual lines and not only
237            the filename).
238        '''
239        t = pydevd_find_thread_by_id(thread_id)
240        if t:
241            if original_filename is not None:
242                translated_filename = self.filename_to_server(original_filename)  # Apply user path mapping.
243                pydev_log.debug('Set next (after path translation) in: %s line: %s', translated_filename, line)
244                func_name = self.to_str(func_name)
245
246                assert translated_filename.__class__ == str  # i.e.: bytes on py2 and str on py3
247                assert func_name.__class__ == str  # i.e.: bytes on py2 and str on py3
248
249                # Apply source mapping (i.e.: ipython).
250                _source_mapped_filename, new_line, multi_mapping_applied = py_db.source_mapping.map_to_server(
251                    translated_filename, line)
252                if multi_mapping_applied:
253                    pydev_log.debug('Set next (after source mapping) in: %s line: %s', translated_filename, line)
254                    line = new_line
255
256            int_cmd = InternalSetNextStatementThread(thread_id, set_next_cmd_id, line, func_name, seq=seq)
257            py_db.post_internal_command(int_cmd, thread_id)
258        elif thread_id.startswith('__frame__:'):
259            sys.stderr.write("Can't set next statement in tasklet: %s\n" % (thread_id,))
260
261    def request_reload_code(self, py_db, seq, module_name):
262        thread_id = '*'  # Any thread
263        # Note: not going for the main thread because in this case it'd only do the load
264        # when we stopped on a breakpoint.
265        py_db.post_method_as_internal_command(
266            thread_id, internal_reload_code, seq, module_name)
267
268    def request_change_variable(self, py_db, seq, thread_id, frame_id, scope, attr, value):
269        '''
270        :param scope: 'FRAME' or 'GLOBAL'
271        '''
272        py_db.post_method_as_internal_command(
273            thread_id, internal_change_variable, seq, thread_id, frame_id, scope, attr, value)
274
275    def request_get_variable(self, py_db, seq, thread_id, frame_id, scope, attrs):
276        '''
277        :param scope: 'FRAME' or 'GLOBAL'
278        '''
279        int_cmd = InternalGetVariable(seq, thread_id, frame_id, scope, attrs)
280        py_db.post_internal_command(int_cmd, thread_id)
281
282    def request_get_array(self, py_db, seq, roffset, coffset, rows, cols, fmt, thread_id, frame_id, scope, attrs):
283        int_cmd = InternalGetArray(seq, roffset, coffset, rows, cols, fmt, thread_id, frame_id, scope, attrs)
284        py_db.post_internal_command(int_cmd, thread_id)
285
286    def request_load_full_value(self, py_db, seq, thread_id, frame_id, vars):
287        int_cmd = InternalLoadFullValue(seq, thread_id, frame_id, vars)
288        py_db.post_internal_command(int_cmd, thread_id)
289
290    def request_get_description(self, py_db, seq, thread_id, frame_id, expression):
291        py_db.post_method_as_internal_command(
292            thread_id, internal_get_description, seq, thread_id, frame_id, expression)
293
294    def request_get_frame(self, py_db, seq, thread_id, frame_id):
295        py_db.post_method_as_internal_command(
296            thread_id, internal_get_frame, seq, thread_id, frame_id)
297
298    def to_str(self, s):
299        '''
300        In py2 converts a unicode to str (bytes) using utf-8.
301        -- in py3 raises an error if it's not str already.
302        '''
303        if s.__class__ != str:
304            if not IS_PY3K:
305                s = s.encode('utf-8')
306            else:
307                raise AssertionError('Expected to have str on Python 3. Found: %s (%s)' % (s, s.__class__))
308        return s
309
310    def filename_to_str(self, filename):
311        '''
312        In py2 converts a unicode to str (bytes) using the file system encoding.
313        -- in py3 raises an error if it's not str already.
314        '''
315        if filename.__class__ != str:
316            if not IS_PY3K:
317                filename = filename.encode(file_system_encoding)
318            else:
319                raise AssertionError('Expected to have str on Python 3. Found: %s (%s)' % (filename, filename.__class__))
320        return filename
321
322    def filename_to_server(self, filename):
323        filename = self.filename_to_str(filename)
324        filename = pydevd_file_utils.map_file_to_server(filename)
325        return filename
326
327    class _DummyFrame(object):
328        '''
329        Dummy frame to be used with PyDB.apply_files_filter (as we don't really have the
330        related frame as breakpoints are added before execution).
331        '''
332
333        class _DummyCode(object):
334
335            def __init__(self, filename):
336                self.co_firstlineno = 1
337                self.co_filename = filename
338                self.co_name = 'invalid func name '
339
340        def __init__(self, filename):
341            self.f_code = self._DummyCode(filename)
342            self.f_globals = {}
343
344    ADD_BREAKPOINT_NO_ERROR = 0
345    ADD_BREAKPOINT_FILE_NOT_FOUND = 1
346    ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS = 2
347
348    class _AddBreakpointResult(object):
349
350        # :see: ADD_BREAKPOINT_NO_ERROR = 0
351        # :see: ADD_BREAKPOINT_FILE_NOT_FOUND = 1
352        # :see: ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS = 2
353
354        __slots__ = ['error_code', 'translated_filename', 'translated_line']
355
356        def __init__(self, translated_filename, translated_line):
357            self.error_code = PyDevdAPI.ADD_BREAKPOINT_NO_ERROR
358            self.translated_filename = translated_filename
359            self.translated_line = translated_line
360
361    def add_breakpoint(
362            self, py_db, original_filename, breakpoint_type, breakpoint_id, line, condition, func_name,
363            expression, suspend_policy, hit_condition, is_logpoint, adjust_line=False):
364        '''
365        :param str original_filename:
366            Note: must be sent as it was received in the protocol. It may be translated in this
367            function and its final value will be available in the returned _AddBreakpointResult.
368
369        :param str breakpoint_type:
370            One of: 'python-line', 'django-line', 'jinja2-line'.
371
372        :param int breakpoint_id:
373
374        :param int line:
375            Note: it's possible that a new line was actually used. If that's the case its
376            final value will be available in the returned _AddBreakpointResult.
377
378        :param condition:
379            Either None or the condition to activate the breakpoint.
380
381        :param str func_name:
382            If "None" (str), may hit in any context.
383            Empty string will hit only top level.
384            Any other value must match the scope of the method to be matched.
385
386        :param str expression:
387            None or the expression to be evaluated.
388
389        :param suspend_policy:
390            Either "NONE" (to suspend only the current thread when the breakpoint is hit) or
391            "ALL" (to suspend all threads when a breakpoint is hit).
392
393        :param str hit_condition:
394            An expression where `@HIT@` will be replaced by the number of hits.
395            i.e.: `@HIT@ == x` or `@HIT@ >= x`
396
397        :param bool is_logpoint:
398            If True and an expression is passed, pydevd will create an io message command with the
399            result of the evaluation.
400
401        :return _AddBreakpointResult:
402        '''
403        assert original_filename.__class__ == str, 'Expected str, found: %s' % (original_filename.__class__,)  # i.e.: bytes on py2 and str on py3
404
405        pydev_log.debug('Request for breakpoint in: %s line: %s', original_filename, line)
406        # Parameters to reapply breakpoint.
407        api_add_breakpoint_params = (original_filename, breakpoint_type, breakpoint_id, line, condition, func_name,
408            expression, suspend_policy, hit_condition, is_logpoint)
409
410        translated_filename = self.filename_to_server(original_filename)  # Apply user path mapping.
411        pydev_log.debug('Breakpoint (after path translation) in: %s line: %s', translated_filename, line)
412        func_name = self.to_str(func_name)
413
414        assert translated_filename.__class__ == str  # i.e.: bytes on py2 and str on py3
415        assert func_name.__class__ == str  # i.e.: bytes on py2 and str on py3
416
417        # Apply source mapping (i.e.: ipython).
418        source_mapped_filename, new_line, multi_mapping_applied = py_db.source_mapping.map_to_server(
419            translated_filename, line)
420
421        if multi_mapping_applied:
422            pydev_log.debug('Breakpoint (after source mapping) in: %s line: %s', source_mapped_filename, new_line)
423            # Note that source mapping is internal and does not change the resulting filename nor line
424            # (we want the outside world to see the line in the original file and not in the ipython
425            # cell, otherwise the editor wouldn't be correct as the returned line is the line to
426            # which the breakpoint will be moved in the editor).
427            result = self._AddBreakpointResult(original_filename, line)
428
429            # If a multi-mapping was applied, consider it the canonical / source mapped version (translated to ipython cell).
430            translated_absolute_filename = source_mapped_filename
431            canonical_normalized_filename = pydevd_file_utils.normcase(source_mapped_filename)
432            line = new_line
433
434        else:
435            translated_absolute_filename = pydevd_file_utils.absolute_path(translated_filename)
436            canonical_normalized_filename = pydevd_file_utils.canonical_normalized_path(translated_filename)
437
438            if adjust_line and not translated_absolute_filename.startswith('<'):
439                # Validate breakpoints and adjust their positions.
440                try:
441                    lines = sorted(_get_code_lines(translated_absolute_filename))
442                except Exception:
443                    pass
444                else:
445                    if line not in lines:
446                        # Adjust to the first preceding valid line.
447                        idx = bisect.bisect_left(lines, line)
448                        if idx > 0:
449                            line = lines[idx - 1]
450
451            result = self._AddBreakpointResult(original_filename, line)
452
453        py_db.api_received_breakpoints[(original_filename, breakpoint_id)] = (canonical_normalized_filename, api_add_breakpoint_params)
454
455        if not translated_absolute_filename.startswith('<'):
456            # Note: if a mapping pointed to a file starting with '<', don't validate.
457
458            if not pydevd_file_utils.exists(translated_absolute_filename):
459                result.error_code = self.ADD_BREAKPOINT_FILE_NOT_FOUND
460                return result
461
462            if (
463                    py_db.is_files_filter_enabled and
464                    not py_db.get_require_module_for_filters() and
465                    py_db.apply_files_filter(self._DummyFrame(translated_absolute_filename), translated_absolute_filename, False)
466                ):
467                # Note that if `get_require_module_for_filters()` returns False, we don't do this check.
468                # This is because we don't have the module name given a file at this point (in
469                # runtime it's gotten from the frame.f_globals).
470                # An option could be calculate it based on the filename and current sys.path,
471                # but on some occasions that may be wrong (for instance with `__main__` or if
472                # the user dynamically changes the PYTHONPATH).
473
474                # Note: depending on the use-case, filters may be changed, so, keep on going and add the
475                # breakpoint even with the error code.
476                result.error_code = self.ADD_BREAKPOINT_FILE_EXCLUDED_BY_FILTERS
477
478        if breakpoint_type == 'python-line':
479            added_breakpoint = LineBreakpoint(line, condition, func_name, expression, suspend_policy, hit_condition=hit_condition, is_logpoint=is_logpoint)
480            breakpoints = py_db.breakpoints
481            file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint
482            supported_type = True
483
484        else:
485            add_plugin_breakpoint_result = None
486            plugin = py_db.get_plugin_lazy_init()
487            if plugin is not None:
488                add_plugin_breakpoint_result = plugin.add_breakpoint(
489                    'add_line_breakpoint', py_db, breakpoint_type, canonical_normalized_filename, line, condition, expression, func_name, hit_condition=hit_condition, is_logpoint=is_logpoint)
490
491            if add_plugin_breakpoint_result is not None:
492                supported_type = True
493                added_breakpoint, breakpoints = add_plugin_breakpoint_result
494                file_to_id_to_breakpoint = py_db.file_to_id_to_plugin_breakpoint
495            else:
496                supported_type = False
497
498        if not supported_type:
499            raise NameError(breakpoint_type)
500
501        if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
502            pydev_log.debug('Added breakpoint:%s - line:%s - func_name:%s\n', canonical_normalized_filename, line, func_name)
503
504        if canonical_normalized_filename in file_to_id_to_breakpoint:
505            id_to_pybreakpoint = file_to_id_to_breakpoint[canonical_normalized_filename]
506        else:
507            id_to_pybreakpoint = file_to_id_to_breakpoint[canonical_normalized_filename] = {}
508
509        id_to_pybreakpoint[breakpoint_id] = added_breakpoint
510        py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, breakpoints)
511        if py_db.plugin is not None:
512            py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks()
513
514        py_db.on_breakpoints_changed()
515        return result
516
517    def reapply_breakpoints(self, py_db):
518        '''
519        Reapplies all the received breakpoints as they were received by the API (so, new
520        translations are applied).
521        '''
522        pydev_log.debug('Reapplying breakpoints.')
523        items = dict_items(py_db.api_received_breakpoints)  # Create a copy with items to reapply.
524        self.remove_all_breakpoints(py_db, '*')
525        for _key, val in items:
526            _new_filename, api_add_breakpoint_params = val
527            self.add_breakpoint(py_db, *api_add_breakpoint_params)
528
529    def remove_all_breakpoints(self, py_db, filename):
530        '''
531        Removes all the breakpoints from a given file or from all files if filename == '*'.
532
533        :param str filename:
534            Note: must be sent as it was received in the protocol. It may be translated in this
535            function.
536        '''
537        assert filename.__class__ == str  # i.e.: bytes on py2 and str on py3
538        changed = False
539        lst = [
540            py_db.file_to_id_to_line_breakpoint,
541            py_db.file_to_id_to_plugin_breakpoint,
542            py_db.breakpoints
543        ]
544        if hasattr(py_db, 'django_breakpoints'):
545            lst.append(py_db.django_breakpoints)
546
547        if hasattr(py_db, 'jinja2_breakpoints'):
548            lst.append(py_db.jinja2_breakpoints)
549
550        if filename == '*':
551            py_db.api_received_breakpoints.clear()
552
553            for file_to_id_to_breakpoint in lst:
554                if file_to_id_to_breakpoint:
555                    file_to_id_to_breakpoint.clear()
556                    changed = True
557
558        else:
559            items = dict_items(py_db.api_received_breakpoints)  # Create a copy to remove items.
560            translated_filenames = []
561            for key, val in items:
562                original_filename, _breakpoint_id = key
563                if original_filename == filename:
564                    canonical_normalized_filename, _api_add_breakpoint_params = val
565                    # Note: there can be actually 1:N mappings due to source mapping (i.e.: ipython).
566                    translated_filenames.append(canonical_normalized_filename)
567                    del py_db.api_received_breakpoints[key]
568
569            for canonical_normalized_filename in translated_filenames:
570                for file_to_id_to_breakpoint in lst:
571                    if canonical_normalized_filename in file_to_id_to_breakpoint:
572                        file_to_id_to_breakpoint.pop(canonical_normalized_filename, None)
573                        changed = True
574
575        if changed:
576            py_db.on_breakpoints_changed(removed=True)
577
578    def remove_breakpoint(self, py_db, received_filename, breakpoint_type, breakpoint_id):
579        '''
580        :param str received_filename:
581            Note: must be sent as it was received in the protocol. It may be translated in this
582            function.
583
584        :param str breakpoint_type:
585            One of: 'python-line', 'django-line', 'jinja2-line'.
586
587        :param int breakpoint_id:
588        '''
589        for key, val in dict_items(py_db.api_received_breakpoints):
590            original_filename, existing_breakpoint_id = key
591            _new_filename, _api_add_breakpoint_params = val
592            if received_filename == original_filename and existing_breakpoint_id == breakpoint_id:
593                del py_db.api_received_breakpoints[key]
594                break
595        else:
596            pydev_log.info(
597                'Did not find breakpoint to remove: %s (breakpoint id: %s)', received_filename, breakpoint_id)
598
599        file_to_id_to_breakpoint = None
600        received_filename = self.filename_to_server(received_filename)
601        canonical_normalized_filename = pydevd_file_utils.canonical_normalized_path(received_filename)
602
603        if breakpoint_type == 'python-line':
604            breakpoints = py_db.breakpoints
605            file_to_id_to_breakpoint = py_db.file_to_id_to_line_breakpoint
606
607        elif py_db.plugin is not None:
608            result = py_db.plugin.get_breakpoints(py_db, breakpoint_type)
609            if result is not None:
610                file_to_id_to_breakpoint = py_db.file_to_id_to_plugin_breakpoint
611                breakpoints = result
612
613        if file_to_id_to_breakpoint is None:
614            pydev_log.critical('Error removing breakpoint. Cannot handle breakpoint of type %s', breakpoint_type)
615
616        else:
617            try:
618                id_to_pybreakpoint = file_to_id_to_breakpoint.get(canonical_normalized_filename, {})
619                if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
620                    existing = id_to_pybreakpoint[breakpoint_id]
621                    pydev_log.info('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % (
622                        canonical_normalized_filename, existing.line, existing.func_name.encode('utf-8'), breakpoint_id))
623
624                del id_to_pybreakpoint[breakpoint_id]
625                py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, breakpoints)
626                if py_db.plugin is not None:
627                    py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks()
628
629            except KeyError:
630                pydev_log.info("Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n",
631                    canonical_normalized_filename, breakpoint_id, dict_keys(id_to_pybreakpoint))
632
633        py_db.on_breakpoints_changed(removed=True)
634
635    def request_exec_or_evaluate(
636            self, py_db, seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result):
637        py_db.post_method_as_internal_command(
638            thread_id, internal_evaluate_expression,
639            seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result)
640
641    def request_exec_or_evaluate_json(
642            self, py_db, request, thread_id):
643        py_db.post_method_as_internal_command(
644            thread_id, internal_evaluate_expression_json, request, thread_id)
645
646    def request_set_expression_json(self, py_db, request, thread_id):
647        py_db.post_method_as_internal_command(
648            thread_id, internal_set_expression_json, request, thread_id)
649
650    def request_console_exec(self, py_db, seq, thread_id, frame_id, expression):
651        int_cmd = InternalConsoleExec(seq, thread_id, frame_id, expression)
652        py_db.post_internal_command(int_cmd, thread_id)
653
654    def request_load_source(self, py_db, seq, filename):
655        '''
656        :param str filename:
657            Note: must be sent as it was received in the protocol. It may be translated in this
658            function.
659        '''
660        try:
661            filename = self.filename_to_server(filename)
662            assert filename.__class__ == str  # i.e.: bytes on py2 and str on py3
663
664            with open(filename, 'r') as stream:
665                source = stream.read()
666            cmd = py_db.cmd_factory.make_load_source_message(seq, source)
667        except:
668            cmd = py_db.cmd_factory.make_error_message(seq, get_exception_traceback_str())
669
670        py_db.writer.add_command(cmd)
671
672    def get_decompiled_source_from_frame_id(self, py_db, frame_id):
673        '''
674        :param py_db:
675        :param frame_id:
676        :throws Exception:
677            If unable to get the frame in the currently paused frames or if some error happened
678            when decompiling.
679        '''
680        variable = py_db.suspended_frames_manager.get_variable(int(frame_id))
681        frame = variable.value
682
683        # Check if it's in the linecache first.
684        lines = (linecache.getline(frame.f_code.co_filename, i) for i in itertools.count(1))
685        lines = itertools.takewhile(bool, lines)  # empty lines are '\n', EOF is ''
686
687        source = ''.join(lines)
688        if not source:
689            source = code_to_bytecode_representation(frame.f_code)
690
691        return source
692
693    def request_load_source_from_frame_id(self, py_db, seq, frame_id):
694        try:
695            source = self.get_decompiled_source_from_frame_id(py_db, frame_id)
696            cmd = py_db.cmd_factory.make_load_source_from_frame_id_message(seq, source)
697        except:
698            cmd = py_db.cmd_factory.make_error_message(seq, get_exception_traceback_str())
699
700        py_db.writer.add_command(cmd)
701
702    def add_python_exception_breakpoint(
703            self,
704            py_db,
705            exception,
706            condition,
707            expression,
708            notify_on_handled_exceptions,
709            notify_on_unhandled_exceptions,
710            notify_on_user_unhandled_exceptions,
711            notify_on_first_raise_only,
712            ignore_libraries,
713        ):
714        exception_breakpoint = py_db.add_break_on_exception(
715            exception,
716            condition=condition,
717            expression=expression,
718            notify_on_handled_exceptions=notify_on_handled_exceptions,
719            notify_on_unhandled_exceptions=notify_on_unhandled_exceptions,
720            notify_on_user_unhandled_exceptions=notify_on_user_unhandled_exceptions,
721            notify_on_first_raise_only=notify_on_first_raise_only,
722            ignore_libraries=ignore_libraries,
723        )
724
725        if exception_breakpoint is not None:
726            py_db.on_breakpoints_changed()
727
728    def add_plugins_exception_breakpoint(self, py_db, breakpoint_type, exception):
729        supported_type = False
730        plugin = py_db.get_plugin_lazy_init()
731        if plugin is not None:
732            supported_type = plugin.add_breakpoint('add_exception_breakpoint', py_db, breakpoint_type, exception)
733
734        if supported_type:
735            py_db.has_plugin_exception_breaks = py_db.plugin.has_exception_breaks()
736            py_db.on_breakpoints_changed()
737        else:
738            raise NameError(breakpoint_type)
739
740    def remove_python_exception_breakpoint(self, py_db, exception):
741        try:
742            cp = py_db.break_on_uncaught_exceptions.copy()
743            cp.pop(exception, None)
744            py_db.break_on_uncaught_exceptions = cp
745
746            cp = py_db.break_on_caught_exceptions.copy()
747            cp.pop(exception, None)
748            py_db.break_on_caught_exceptions = cp
749
750            cp = py_db.break_on_user_uncaught_exceptions.copy()
751            cp.pop(exception, None)
752            py_db.break_on_user_uncaught_exceptions = cp
753        except:
754            pydev_log.exception("Error while removing exception %s", sys.exc_info()[0])
755
756        py_db.on_breakpoints_changed(removed=True)
757
758    def remove_plugins_exception_breakpoint(self, py_db, exception_type, exception):
759        # I.e.: no need to initialize lazy (if we didn't have it in the first place, we can't remove
760        # anything from it anyways).
761        plugin = py_db.plugin
762        if plugin is None:
763            return
764
765        supported_type = plugin.remove_exception_breakpoint(py_db, exception_type, exception)
766
767        if supported_type:
768            py_db.has_plugin_exception_breaks = py_db.plugin.has_exception_breaks()
769        else:
770            pydev_log.info('No exception of type: %s was previously registered.', exception_type)
771
772        py_db.on_breakpoints_changed(removed=True)
773
774    def remove_all_exception_breakpoints(self, py_db):
775        py_db.break_on_uncaught_exceptions = {}
776        py_db.break_on_caught_exceptions = {}
777        py_db.break_on_user_uncaught_exceptions = {}
778
779        plugin = py_db.plugin
780        if plugin is not None:
781            plugin.remove_all_exception_breakpoints(py_db)
782        py_db.on_breakpoints_changed(removed=True)
783
784    def set_project_roots(self, py_db, project_roots):
785        '''
786        :param unicode project_roots:
787        '''
788        py_db.set_project_roots(project_roots)
789
790    def set_stepping_resumes_all_threads(self, py_db, stepping_resumes_all_threads):
791        py_db.stepping_resumes_all_threads = stepping_resumes_all_threads
792
793    # Add it to the namespace so that it's available as PyDevdAPI.ExcludeFilter
794    from _pydevd_bundle.pydevd_filtering import ExcludeFilter  # noqa
795
796    def set_exclude_filters(self, py_db, exclude_filters):
797        '''
798        :param list(PyDevdAPI.ExcludeFilter) exclude_filters:
799        '''
800        py_db.set_exclude_filters(exclude_filters)
801
802    def set_use_libraries_filter(self, py_db, use_libraries_filter):
803        py_db.set_use_libraries_filter(use_libraries_filter)
804
805    def request_get_variable_json(self, py_db, request, thread_id):
806        '''
807        :param VariablesRequest request:
808        '''
809        py_db.post_method_as_internal_command(
810            thread_id, internal_get_variable_json, request)
811
812    def request_change_variable_json(self, py_db, request, thread_id):
813        '''
814        :param SetVariableRequest request:
815        '''
816        py_db.post_method_as_internal_command(
817            thread_id, internal_change_variable_json, request)
818
819    def set_dont_trace_start_end_patterns(self, py_db, start_patterns, end_patterns):
820        # Note: start/end patterns normalized internally.
821        start_patterns = tuple(pydevd_file_utils.normcase(x) for x in start_patterns)
822        end_patterns = tuple(pydevd_file_utils.normcase(x) for x in end_patterns)
823
824        # After it's set the first time, we can still change it, but we need to reset the
825        # related caches.
826        reset_caches = False
827        dont_trace_start_end_patterns_previously_set = \
828            py_db.dont_trace_external_files.__name__ == 'custom_dont_trace_external_files'
829
830        if not dont_trace_start_end_patterns_previously_set and not start_patterns and not end_patterns:
831            # If it wasn't set previously and start and end patterns are empty we don't need to do anything.
832            return
833
834        if not py_db.is_cache_file_type_empty():
835            # i.e.: custom function set in set_dont_trace_start_end_patterns.
836            if dont_trace_start_end_patterns_previously_set:
837                reset_caches = py_db.dont_trace_external_files.start_patterns != start_patterns or \
838                    py_db.dont_trace_external_files.end_patterns != end_patterns
839
840            else:
841                reset_caches = True
842
843        def custom_dont_trace_external_files(abs_path):
844            normalized_abs_path = pydevd_file_utils.normcase(abs_path)
845            return normalized_abs_path.startswith(start_patterns) or normalized_abs_path.endswith(end_patterns)
846
847        custom_dont_trace_external_files.start_patterns = start_patterns
848        custom_dont_trace_external_files.end_patterns = end_patterns
849        py_db.dont_trace_external_files = custom_dont_trace_external_files
850
851        if reset_caches:
852            py_db.clear_dont_trace_start_end_patterns_caches()
853
854    def stop_on_entry(self):
855        main_thread = pydevd_utils.get_main_thread()
856        if main_thread is None:
857            pydev_log.critical('Could not find main thread while setting Stop on Entry.')
858        else:
859            info = set_additional_thread_info(main_thread)
860            info.pydev_original_step_cmd = CMD_STOP_ON_START
861            info.pydev_step_cmd = CMD_STEP_INTO_MY_CODE
862
863    def set_ignore_system_exit_codes(self, py_db, ignore_system_exit_codes):
864        py_db.set_ignore_system_exit_codes(ignore_system_exit_codes)
865
866    SourceMappingEntry = pydevd_source_mapping.SourceMappingEntry
867
868    def set_source_mapping(self, py_db, source_filename, mapping):
869        '''
870        :param str source_filename:
871            The filename for the source mapping (bytes on py2 and str on py3).
872            This filename will be made absolute in this function.
873
874        :param list(SourceMappingEntry) mapping:
875            A list with the source mapping entries to be applied to the given filename.
876
877        :return str:
878            An error message if it was not possible to set the mapping or an empty string if
879            everything is ok.
880        '''
881        source_filename = self.filename_to_server(source_filename)
882        absolute_source_filename = pydevd_file_utils.absolute_path(source_filename)
883        for map_entry in mapping:
884            map_entry.source_filename = absolute_source_filename
885        error_msg = py_db.source_mapping.set_source_mapping(absolute_source_filename, mapping)
886        if error_msg:
887            return error_msg
888
889        self.reapply_breakpoints(py_db)
890        return ''
891
892    def set_variable_presentation(self, py_db, variable_presentation):
893        assert isinstance(variable_presentation, self.VariablePresentation)
894        py_db.variable_presentation = variable_presentation
895
896    def get_ppid(self):
897        '''
898        Provides the parent pid (even for older versions of Python on Windows).
899        '''
900        ppid = None
901
902        try:
903            ppid = os.getppid()
904        except AttributeError:
905            pass
906
907        if ppid is None and IS_WINDOWS:
908            ppid = self._get_windows_ppid()
909
910        return ppid
911
912    def _get_windows_ppid(self):
913        this_pid = os.getpid()
914        for ppid, pid in _list_ppid_and_pid():
915            if pid == this_pid:
916                return ppid
917
918        return None
919
920    def _terminate_child_processes_windows(self, dont_terminate_child_pids):
921        this_pid = os.getpid()
922        for _ in range(50):  # Try this at most 50 times before giving up.
923
924            # Note: we can't kill the process itself with taskkill, so, we
925            # list immediate children, kill that tree and then exit this process.
926
927            children_pids = []
928            for ppid, pid in _list_ppid_and_pid():
929                if ppid == this_pid:
930                    if pid not in dont_terminate_child_pids:
931                        children_pids.append(pid)
932
933            if not children_pids:
934                break
935            else:
936                for pid in children_pids:
937                    self._call(
938                        ['taskkill', '/F', '/PID', str(pid), '/T'],
939                        stdout=subprocess.PIPE,
940                        stderr=subprocess.PIPE
941                    )
942
943                del children_pids[:]
944
945    def _terminate_child_processes_linux_and_mac(self, dont_terminate_child_pids):
946        this_pid = os.getpid()
947
948        def list_children_and_stop_forking(initial_pid, stop=True):
949            children_pids = []
950            if stop:
951                # Ask to stop forking (shouldn't be called for this process, only subprocesses).
952                self._call(
953                    ['kill', '-STOP', str(initial_pid)],
954                    stdout=subprocess.PIPE,
955                    stderr=subprocess.PIPE
956                )
957
958            list_popen = self._popen(
959                ['pgrep', '-P', str(initial_pid)],
960                stdout=subprocess.PIPE,
961                stderr=subprocess.PIPE
962            )
963
964            if list_popen is not None:
965                stdout, _ = list_popen.communicate()
966                for line in stdout.splitlines():
967                    line = line.decode('ascii').strip()
968                    if line:
969                        pid = str(line)
970                        if pid in dont_terminate_child_pids:
971                            continue
972                        children_pids.append(pid)
973                        # Recursively get children.
974                        children_pids.extend(list_children_and_stop_forking(pid))
975            return children_pids
976
977        previously_found = set()
978
979        for _ in range(50):  # Try this at most 50 times before giving up.
980
981            children_pids = list_children_and_stop_forking(this_pid, stop=False)
982            found_new = False
983
984            for pid in children_pids:
985                if pid not in previously_found:
986                    found_new = True
987                    previously_found.add(pid)
988                    self._call(
989                        ['kill', '-KILL', str(pid)],
990                        stdout=subprocess.PIPE,
991                        stderr=subprocess.PIPE
992                    )
993
994            if not found_new:
995                break
996
997    def _popen(self, cmdline, **kwargs):
998        try:
999            return subprocess.Popen(cmdline, **kwargs)
1000        except:
1001            if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
1002                pydev_log.exception('Error running: %s' % (' '.join(cmdline)))
1003            return None
1004
1005    def _call(self, cmdline, **kwargs):
1006        try:
1007            subprocess.check_call(cmdline, **kwargs)
1008        except:
1009            if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
1010                pydev_log.exception('Error running: %s' % (' '.join(cmdline)))
1011
1012    def set_terminate_child_processes(self, py_db, terminate_child_processes):
1013        py_db.terminate_child_processes = terminate_child_processes
1014
1015    def terminate_process(self, py_db):
1016        '''
1017        Terminates the current process (and child processes if the option to also terminate
1018        child processes is enabled).
1019        '''
1020        try:
1021            if py_db.terminate_child_processes:
1022                pydev_log.debug('Terminating child processes.')
1023                if IS_WINDOWS:
1024                    self._terminate_child_processes_windows(py_db.dont_terminate_child_pids)
1025                else:
1026                    self._terminate_child_processes_linux_and_mac(py_db.dont_terminate_child_pids)
1027        finally:
1028            pydev_log.debug('Exiting process (os._exit(0)).')
1029            os._exit(0)
1030
1031    def _terminate_if_commands_processed(self, py_db):
1032        py_db.dispose_and_kill_all_pydevd_threads()
1033        self.terminate_process(py_db)
1034
1035    def request_terminate_process(self, py_db):
1036        # We mark with a terminate_requested to avoid that paused threads start running
1037        # (we should terminate as is without letting any paused thread run).
1038        py_db.terminate_requested = True
1039        run_as_pydevd_daemon_thread(py_db, self._terminate_if_commands_processed, py_db)
1040
1041
1042def _list_ppid_and_pid():
1043    _TH32CS_SNAPPROCESS = 0x00000002
1044
1045    class PROCESSENTRY32(ctypes.Structure):
1046        _fields_ = [("dwSize", ctypes.c_uint32),
1047                    ("cntUsage", ctypes.c_uint32),
1048                    ("th32ProcessID", ctypes.c_uint32),
1049                    ("th32DefaultHeapID", ctypes.c_size_t),
1050                    ("th32ModuleID", ctypes.c_uint32),
1051                    ("cntThreads", ctypes.c_uint32),
1052                    ("th32ParentProcessID", ctypes.c_uint32),
1053                    ("pcPriClassBase", ctypes.c_long),
1054                    ("dwFlags", ctypes.c_uint32),
1055                    ("szExeFile", ctypes.c_char * 260)]
1056
1057    kernel32 = ctypes.windll.kernel32
1058    snapshot = kernel32.CreateToolhelp32Snapshot(_TH32CS_SNAPPROCESS, 0)
1059    ppid_and_pids = []
1060    try:
1061        process_entry = PROCESSENTRY32()
1062        process_entry.dwSize = ctypes.sizeof(PROCESSENTRY32)
1063        if not kernel32.Process32First(snapshot, ctypes.byref(process_entry)):
1064            pydev_log.critical('Process32First failed (getting process from CreateToolhelp32Snapshot).')
1065        else:
1066            while True:
1067                ppid_and_pids.append((process_entry.th32ParentProcessID, process_entry.th32ProcessID))
1068                if not kernel32.Process32Next(snapshot, ctypes.byref(process_entry)):
1069                    break
1070    finally:
1071        kernel32.CloseHandle(snapshot)
1072
1073    return ppid_and_pids
1074