Skip to content
This repository was archived by the owner on Jan 23, 2024. It is now read-only.

Commit 1d4596c

Browse files
committed
Use the full path to figure out the correct loaded module when setting the breakpoint, and avoid multiple module lookups.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=166007427
1 parent 7023ec8 commit 1d4596c

File tree

3 files changed

+68
-231
lines changed

3 files changed

+68
-231
lines changed

src/googleclouddebugger/imphook.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,4 @@ def _InvokeImportCallback(module):
155155

156156
# Clone the callbacks set, since it can change during enumeration.
157157
for callback in callbacks.copy():
158-
callback()
158+
callback(module)

src/googleclouddebugger/module_lookup.py

Lines changed: 0 additions & 152 deletions
This file was deleted.

src/googleclouddebugger/python_breakpoint.py

Lines changed: 67 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
from datetime import datetime
1818
from datetime import timedelta
1919
import os
20+
import sys
2021
from threading import Lock
2122

2223
import capture_collector
2324
import cdbg_native as native
2425
import imphook
2526
import module_explorer
26-
import module_lookup
2727
import module_search
2828

2929
# TODO(vlif): move to messages.py module.
@@ -87,6 +87,28 @@
8787
datetime.strptime('2017-01-01', '%Y-%m-%d')
8888

8989

90+
def _GetLoadedModuleByPath(abspath):
91+
"""Returns the loaded module that matches abspath or None if not found."""
92+
93+
for module in sys.modules.values():
94+
path = getattr(module, '__file__', None)
95+
if not path:
96+
continue # This is a built-in module.
97+
98+
# module.__file__ may be relative to the current directory, so we first
99+
# convert it into absolute path.
100+
module_abspath = os.path.abspath(path)
101+
102+
# Ignore file extension while comparing the file paths (e.g., /foo/bar.py vs
103+
# /foo/bar.pyc should match).
104+
if os.path.splitext(module_abspath)[0] == os.path.splitext(abspath)[0]:
105+
return module
106+
107+
108+
def _IsRootInitPy(path):
109+
return path.lstrip(os.sep) == '__init__.py'
110+
111+
90112
def _StripCommonPathPrefix(paths):
91113
"""Removes path common prefix from a list of path strings."""
92114
# Find the longest common prefix in terms of characters.
@@ -210,11 +232,14 @@ def __init__(self, definition, hub_client, breakpoints_manager,
210232
'parameters': params}}})
211233
return
212234

213-
# TODO(emrekultursay): Check both loaded and deferred modules.
214-
if not self._TryActivateBreakpoint() and not self._completed:
235+
# TODO(erezh): Handle the possible thread race condtion from lookup to hook.
236+
module = _GetLoadedModuleByPath(paths[0])
237+
if module:
238+
self._ActivateBreakpoint(module)
239+
else:
215240
self._import_hook_cleanup = imphook.AddImportCallback(
216241
paths[0],
217-
self._TryActivateBreakpoint)
242+
self._ActivateBreakpoint)
218243

219244
def Clear(self):
220245
"""Clears the breakpoint and releases all breakpoint resources.
@@ -261,26 +286,43 @@ def ExpireBreakpoint(self):
261286
'refersTo': 'BREAKPOINT_AGE',
262287
'description': {'format': message}}})
263288

264-
def _TryActivateBreakpoint(self):
265-
"""Sets the breakpoint if the module has already been loaded.
266-
267-
This function will complete the breakpoint with error if breakpoint
268-
definition is incorrect. Examples: invalid line or bad condition.
289+
def _ActivateBreakpoint(self, module):
290+
"""Sets the breakpoint in the loaded module, or complete with error."""
269291

270-
If the code object corresponding to the source path can't be found,
271-
this function returns False. In this case, the breakpoint is not
272-
completed, since the breakpoint may be deferred.
292+
# First remove the import hook (if installed).
293+
self._RemoveImportHook()
273294

274-
Returns:
275-
True if breakpoint was set or false otherwise. False can be returned
276-
for potentially deferred breakpoints or in case of a bad breakpoint
277-
definition. The self._completed flag distinguishes between the two cases.
278-
"""
295+
line = self.definition['location']['line']
279296

280297
# Find the code object in which the breakpoint is being set.
281-
code_object = self._FindCodeObject()
282-
if not code_object:
283-
return False
298+
status, codeobj = module_explorer.GetCodeObjectAtLine(module, line)
299+
if not status:
300+
# First two parameters are common: the line of the breakpoint and the
301+
# module we are trying to insert the breakpoint in.
302+
# TODO(emrekultursay): Do not display the entire path of the file. Either
303+
# strip some prefix, or display the path in the breakpoint.
304+
params = [str(line), os.path.splitext(module.__file__)[0] + '.py']
305+
306+
# The next 0, 1, or 2 parameters are the alternative lines to set the
307+
# breakpoint at, displayed for the user's convenience.
308+
alt_lines = (str(l) for l in codeobj if l is not None)
309+
params += alt_lines
310+
311+
if len(params) == 4:
312+
fmt = ERROR_LOCATION_NO_CODE_FOUND_AT_LINE_4
313+
elif len(params) == 3:
314+
fmt = ERROR_LOCATION_NO_CODE_FOUND_AT_LINE_3
315+
else:
316+
fmt = ERROR_LOCATION_NO_CODE_FOUND_AT_LINE_2
317+
318+
self._CompleteBreakpoint({
319+
'status': {
320+
'isError': True,
321+
'refersTo': 'BREAKPOINT_SOURCE_LOCATION',
322+
'description': {
323+
'format': fmt,
324+
'parameters': params}}})
325+
return
284326

285327
# Compile the breakpoint condition.
286328
condition = None
@@ -297,7 +339,8 @@ def _TryActivateBreakpoint(self):
297339
'description': {
298340
'format': 'Invalid expression',
299341
'parameters': [str(e)]}}})
300-
return False
342+
return
343+
301344
except SyntaxError as e:
302345
self._CompleteBreakpoint({
303346
'status': {
@@ -306,71 +349,17 @@ def _TryActivateBreakpoint(self):
306349
'description': {
307350
'format': 'Expression could not be compiled: $0',
308351
'parameters': [e.msg]}}})
309-
return False
310-
311-
line = self.definition['location']['line']
352+
return
312353

313354
native.LogInfo('Creating new Python breakpoint %s in %s, line %d' % (
314-
self.GetBreakpointId(), code_object, line))
355+
self.GetBreakpointId(), codeobj, line))
315356

316357
self._cookie = native.SetConditionalBreakpoint(
317-
code_object,
358+
codeobj,
318359
line,
319360
condition,
320361
self._BreakpointEvent)
321362

322-
self._RemoveImportHook()
323-
return True
324-
325-
def _FindCodeObject(self):
326-
"""Finds the target code object for the breakpoint.
327-
328-
This function completes breakpoint with error if the module was found,
329-
but the line number is invalid. When code object is not found for the
330-
breakpoint source location, this function just returns None. It does not
331-
assume error, because it might be a deferred breakpoint.
332-
333-
Returns:
334-
Python code object object in which the breakpoint will be set or None if
335-
module not found or if there is no code at the specified line.
336-
"""
337-
path = self.definition['location']['path']
338-
line = self.definition['location']['line']
339-
340-
modules = module_lookup.FindModules(path)
341-
if not modules:
342-
return None
343-
344-
# If there are multiple matches, We pick any one of the matching modules
345-
# arbitrarily. TODO(emrekultursay): Return error instead.
346-
module = modules[0]
347-
348-
status, val = module_explorer.GetCodeObjectAtLine(module, line)
349-
if not status:
350-
# module.__file__ must be defined or else it wouldn't have been returned
351-
# from FindModule
352-
params = [str(line), module.__file__]
353-
alt_lines = (str(l) for l in val if l is not None)
354-
params += alt_lines
355-
356-
if len(params) == 4:
357-
fmt = ERROR_LOCATION_NO_CODE_FOUND_AT_LINE_4
358-
elif len(params) == 3:
359-
fmt = ERROR_LOCATION_NO_CODE_FOUND_AT_LINE_3
360-
else:
361-
fmt = ERROR_LOCATION_NO_CODE_FOUND_AT_LINE_2
362-
363-
self._CompleteBreakpoint({
364-
'status': {
365-
'isError': True,
366-
'refersTo': 'BREAKPOINT_SOURCE_LOCATION',
367-
'description': {
368-
'format': fmt,
369-
'parameters': params}}})
370-
return None
371-
372-
return val
373-
374363
def _RemoveImportHook(self):
375364
"""Removes the import hook if one was installed."""
376365
if self._import_hook_cleanup:

0 commit comments

Comments
 (0)