1717from datetime import datetime
1818from datetime import timedelta
1919import os
20+ import sys
2021from threading import Lock
2122
2223import capture_collector
2324import cdbg_native as native
2425import imphook
2526import module_explorer
26- import module_lookup
2727import module_search
2828
2929# TODO(vlif): move to messages.py module.
8787datetime .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+
90112def _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