Skip to content

Commit 189c326

Browse files
committed
Deal with exceptions in hasattr/dir/isinstance. Fixes microsoft/ptvsd#2076
1 parent 9631bc8 commit 189c326

9 files changed

Lines changed: 142 additions & 43 deletions

File tree

src/debugpy/_vendored/pydevd/_pydev_bundle/_pydev_imports_tipper.py

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44

55
from _pydev_bundle._pydev_tipper_common import do_find
66
from _pydevd_bundle.pydevd_constants import IS_PY2
7+
from _pydevd_bundle.pydevd_utils import hasattr_checked, dir_checked
78

89
if IS_PY2:
910
from inspect import getargspec as _originalgetargspec
11+
1012
def getargspec(*args, **kwargs):
1113
ret = list(_originalgetargspec(*args, **kwargs))
1214
ret.append([])
1315
ret.append({})
1416
return ret
15-
17+
1618
else:
1719
from inspect import getfullargspec
1820

@@ -25,14 +27,15 @@ def getargspec(*args, **kwargs):
2527
except:
2628
xrange = range
2729

28-
#completion types.
30+
# completion types.
2931
TYPE_IMPORT = '0'
3032
TYPE_CLASS = '1'
3133
TYPE_FUNCTION = '2'
3234
TYPE_ATTR = '3'
3335
TYPE_BUILTIN = '4'
3436
TYPE_PARAM = '5'
3537

38+
3639
def _imp(name, log=None):
3740
try:
3841
return __import__(name)
@@ -58,32 +61,32 @@ def _imp(name, log=None):
5861
if sys.platform == 'cli':
5962
IS_IPY = True
6063
_old_imp = _imp
64+
6165
def _imp(name, log=None):
62-
#We must add a reference in clr for .Net
63-
import clr #@UnresolvedImport
66+
# We must add a reference in clr for .Net
67+
import clr # @UnresolvedImport
6468
initial_name = name
6569
while '.' in name:
6670
try:
6771
clr.AddReference(name)
68-
break #If it worked, that's OK.
72+
break # If it worked, that's OK.
6973
except:
7074
name = name[0:name.rfind('.')]
7175
else:
7276
try:
7377
clr.AddReference(name)
7478
except:
75-
pass #That's OK (not dot net module).
79+
pass # That's OK (not dot net module).
7680

7781
return _old_imp(initial_name, log)
7882

7983

80-
8184
def get_file(mod):
8285
f = None
8386
try:
8487
f = inspect.getsourcefile(mod) or inspect.getfile(mod)
8588
except:
86-
if hasattr(mod, '__file__'):
89+
if hasattr_checked(mod, '__file__'):
8790
f = mod.__file__
8891
if f.lower(f[-4:]) in ['.pyc', '.pyo']:
8992
filename = f[:-4] + '.py'
@@ -92,6 +95,7 @@ def get_file(mod):
9295

9396
return f
9497

98+
9599
def Find(name, log=None):
96100
f = None
97101

@@ -107,9 +111,9 @@ def Find(name, log=None):
107111
old_comp = None
108112
for comp in components[1:]:
109113
try:
110-
#this happens in the following case:
111-
#we have mx.DateTime.mxDateTime.mxDateTime.pyd
112-
#but after importing it, mx.DateTime.mxDateTime shadows access to mxDateTime.pyd
114+
# this happens in the following case:
115+
# we have mx.DateTime.mxDateTime.mxDateTime.pyd
116+
# but after importing it, mx.DateTime.mxDateTime shadows access to mxDateTime.pyd
113117
mod = getattr(mod, comp)
114118
except AttributeError:
115119
if old_comp != comp:
@@ -126,6 +130,7 @@ def Find(name, log=None):
126130

127131
return f, mod, parent, foundAs
128132

133+
129134
def search_definition(data):
130135
'''@return file, line, col
131136
'''
@@ -146,7 +151,7 @@ def generate_tip(data, log=None):
146151
data = data.rstrip('.')
147152

148153
f, mod, parent, foundAs = Find(data, log)
149-
#print_ >> open('temp.txt', 'w'), f
154+
# print_ >> open('temp.txt', 'w'), f
150155
tips = generate_imports_tip_for_module(mod)
151156
return f, tips
152157

@@ -156,8 +161,10 @@ def check_char(c):
156161
return '_'
157162
return c
158163

164+
159165
_SENTINEL = object()
160166

167+
161168
def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=getattr, filter=lambda name:True):
162169
'''
163170
@param obj_to_complete: the object from where we should get the completions
@@ -170,17 +177,17 @@ def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=get
170177
ret = []
171178

172179
if dir_comps is None:
173-
dir_comps = dir(obj_to_complete)
174-
if hasattr(obj_to_complete, '__dict__'):
180+
dir_comps = dir_checked(obj_to_complete)
181+
if hasattr_checked(obj_to_complete, '__dict__'):
175182
dir_comps.append('__dict__')
176-
if hasattr(obj_to_complete, '__class__'):
183+
if hasattr_checked(obj_to_complete, '__class__'):
177184
dir_comps.append('__class__')
178185

179186
get_complete_info = True
180187

181188
if len(dir_comps) > 1000:
182-
#ok, we don't want to let our users wait forever...
183-
#no complete info for you...
189+
# ok, we don't want to let our users wait forever...
190+
# no complete info for you...
184191

185192
get_complete_info = False
186193

@@ -200,15 +207,15 @@ def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=get
200207
obj = getattr(obj_to_complete.__class__, d)
201208
except:
202209
obj = getattr(obj_to_complete, d)
203-
except: #just ignore and get it without additional info
210+
except: # just ignore and get it without additional info
204211
ret.append((d, '', args, TYPE_BUILTIN))
205212
else:
206213

207214
if get_complete_info:
208215
try:
209216
retType = TYPE_BUILTIN
210217

211-
#check if we have to get docs
218+
# check if we have to get docs
212219
getDoc = True
213220
for class_ in dontGetDocsOn:
214221

@@ -218,32 +225,31 @@ def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=get
218225

219226
doc = ''
220227
if getDoc:
221-
#no need to get this info... too many constants are defined and
222-
#makes things much slower (passing all that through sockets takes quite some time)
228+
# no need to get this info... too many constants are defined and
229+
# makes things much slower (passing all that through sockets takes quite some time)
223230
try:
224231
doc = inspect.getdoc(obj)
225232
if doc is None:
226233
doc = ''
227-
except: #may happen on jython when checking java classes (so, just ignore it)
234+
except: # may happen on jython when checking java classes (so, just ignore it)
228235
doc = ''
229236

230-
231237
if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
232238
try:
233239
args, vargs, kwargs, defaults, kwonly_args, kwonly_defaults = getargspec(obj)
234240

235241
args = args[:]
236-
242+
237243
for kwonly_arg in kwonly_args:
238244
default = kwonly_defaults.get(kwonly_arg, _SENTINEL)
239245
if default is not _SENTINEL:
240246
args.append('%s=%s' % (kwonly_arg, default))
241247
else:
242248
args.append(str(kwonly_arg))
243-
249+
244250
args = '(%s)' % (', '.join(args))
245251
except TypeError:
246-
#ok, let's see if we can get the arguments from the doc
252+
# ok, let's see if we can get the arguments from the doc
247253
args, doc = signature_from_docstring(doc, getattr(obj, '__name__', None))
248254

249255
retType = TYPE_FUNCTION
@@ -257,14 +263,13 @@ def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=get
257263
else:
258264
retType = TYPE_ATTR
259265

260-
261-
#add token and doc to return - assure only strings.
266+
# add token and doc to return - assure only strings.
262267
ret.append((d, doc, args, retType))
263268

264-
except: #just ignore and get it without aditional info
269+
except: # just ignore and get it without aditional info
265270
ret.append((d, '', args, TYPE_BUILTIN))
266271

267-
else: #get_complete_info == False
272+
else: # get_complete_info == False
268273
if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
269274
retType = TYPE_FUNCTION
270275

@@ -276,8 +281,8 @@ def generate_imports_tip_for_module(obj_to_complete, dir_comps=None, getattr=get
276281

277282
else:
278283
retType = TYPE_ATTR
279-
#ok, no complete info, let's try to do this as fast and clean as possible
280-
#so, no docs for this kind of information, only the signatures
284+
# ok, no complete info, let's try to do this as fast and clean as possible
285+
# so, no docs for this kind of information, only the signatures
281286
ret.append((d, '', str(args), retType))
282287

283288
return ret

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
import sys
9797
import traceback
9898
from _pydevd_bundle.pydevd_utils import quote_smart as quote, compare_object_attrs_key, \
99-
notify_about_gevent_if_needed
99+
notify_about_gevent_if_needed, isinstance_checked
100100
from _pydev_bundle import pydev_log
101101
from _pydev_bundle.pydev_log import exception as pydev_log_exception
102102
from _pydev_bundle import _pydev_completer
@@ -1005,7 +1005,7 @@ def __create_frame():
10051005
else:
10061006
frame = py_db.find_frame(thread_id, frame_id)
10071007
eval_result = pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=False)
1008-
is_error = isinstance(eval_result, ExceptionOnEvaluate)
1008+
is_error = isinstance_checked(eval_result, ExceptionOnEvaluate)
10091009
if is_error:
10101010
if context == 'hover': # In a hover it doesn't make sense to do an exec.
10111011
_evaluate_response(py_db, request, result='', error_message='Exception occurred during evaluation.')

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_resolver.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from _pydev_bundle import pydev_log
2+
from _pydevd_bundle.pydevd_utils import hasattr_checked
23
try:
34
import StringIO
45
except:
@@ -113,7 +114,7 @@ def _get_jy_dictionary(self, obj):
113114
found = java.util.HashMap()
114115

115116
original = obj
116-
if hasattr(obj, '__class__') and obj.__class__ == java.lang.Class:
117+
if hasattr_checked(obj, '__class__') and obj.__class__ == java.lang.Class:
117118

118119
# get info about superclasses
119120
classes = []
@@ -171,7 +172,7 @@ def get_names(self, var):
171172
except Exception:
172173
names = []
173174
if not names:
174-
if hasattr(var, '__dict__'):
175+
if hasattr_checked(var, '__dict__'):
175176
names = dict_keys(var.__dict__)
176177
used___dict__ = True
177178
return names, used___dict__

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,28 @@ def notify_about_gevent_if_needed(stream=None):
271271
return True
272272

273273
return False
274+
275+
276+
def hasattr_checked(obj, name):
277+
try:
278+
getattr(obj, name)
279+
except:
280+
# i.e.: Handle any exception, not only AttributeError.
281+
return False
282+
else:
283+
return True
284+
285+
286+
def dir_checked(obj):
287+
try:
288+
return dir(obj)
289+
except:
290+
return []
291+
292+
293+
def isinstance_checked(obj, cls):
294+
try:
295+
return isinstance(obj, cls)
296+
except:
297+
return False
298+

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_xml.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
DEFAULT_VALUE
88
from _pydev_bundle.pydev_imports import quote
99
from _pydevd_bundle.pydevd_extension_api import TypeResolveProvider, StrPresentationProvider
10+
from _pydevd_bundle.pydevd_utils import isinstance_checked, hasattr_checked
1011

1112
try:
1213
import types
@@ -155,7 +156,7 @@ def _get_type(self, o, type_object, type_name):
155156
return type_object, type_name, resolver
156157

157158
for t in self._default_type_map:
158-
if isinstance(o, t[0]):
159+
if isinstance_checked(o, t[0]):
159160
# Cache it
160161
resolver = t[1]
161162
self._type_to_resolver_cache[type_object] = resolver
@@ -223,7 +224,7 @@ def is_builtin(x):
223224

224225

225226
def should_evaluate_full_value(val):
226-
return not LOAD_VALUES_ASYNC or (is_builtin(type(val)) and not isinstance(val, (list, tuple, dict)))
227+
return not LOAD_VALUES_ASYNC or (is_builtin(type(val)) and not isinstance_checked(val, (list, tuple, dict)))
227228

228229

229230
def return_values_from_dict_to_xml(return_dict):
@@ -294,7 +295,7 @@ def get_variable_details(val, evaluate_full_value=True, to_string=None):
294295
elif to_string is not None:
295296
value = to_string(v)
296297

297-
elif hasattr(v, '__class__'):
298+
elif hasattr_checked(v, '__class__'):
298299
if v.__class__ == frame_type:
299300
value = pydevd_resolver.frameResolver.get_frame_name(v)
300301

src/debugpy/_vendored/pydevd/tests/test_simpleTipper.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,17 @@ def test_dot_net_libraries(self):
218218
tip = _pydev_imports_tipper.generate_tip('System.Drawing.Brushes')
219219
self.assert_in('Aqua' , tip)
220220

221+
def test_tips_hasattr_failure(self):
222+
223+
class MyClass(object):
224+
225+
def __getattribute__(self, attr):
226+
raise RuntimeError()
227+
228+
obj = MyClass()
229+
230+
_pydev_imports_tipper.generate_imports_tip_for_module(obj)
231+
221232
def test_inspect(self):
222233

223234
class C(object):
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class MyClass(object):
2+
3+
def __getattribute__(self, attr):
4+
raise RuntimeError()
5+
6+
7+
obj = MyClass()
8+
9+
if __name__ == '__main__':
10+
print('TEST SUCEEDED') # break here

0 commit comments

Comments
 (0)