Skip to content

Commit 25506e5

Browse files
committed
Merge pull request #6652 from cpcloud/gh-5198-stack-blow-eval-mixed-type-operations
BUG: disallow mixed dtype operations in eval/query
2 parents 9ed9c37 + 0df3f06 commit 25506e5

File tree

4 files changed

+38
-1
lines changed

4 files changed

+38
-1
lines changed

doc/source/release.rst

+2
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ API Changes
137137
- A tuple passed to ``DataFame.sort_index`` will be interpreted as the levels of
138138
the index, rather than requiring a list of tuple (:issue:`4370`)
139139

140+
- Fix a bug where invalid eval/query operations would blow the stack (:issue:`5198`)
141+
140142
Deprecations
141143
~~~~~~~~~~~~
142144

pandas/computation/expr.py

+5
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,11 @@ def _possibly_evaluate_binop(self, op, op_class, lhs, rhs,
377377
'<=', '>=')):
378378
res = op(lhs, rhs)
379379

380+
if res.has_invalid_return_type:
381+
raise TypeError("unsupported operand type(s) for {0}:"
382+
" '{1}' and '{2}'".format(res.op, lhs.type,
383+
rhs.type))
384+
380385
if self.engine != 'pytables':
381386
if (res.op in _cmp_ops_syms
382387
and getattr(lhs, 'is_datetime', False)

pandas/computation/ops.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def name(self):
169169

170170
class Op(StringMixin):
171171

172-
"""Hold an operator of unknown arity
172+
"""Hold an operator of arbitrary arity
173173
"""
174174

175175
def __init__(self, op, operands, *args, **kwargs):
@@ -195,6 +195,16 @@ def return_type(self):
195195
return np.bool_
196196
return _result_type_many(*(term.type for term in com.flatten(self)))
197197

198+
@property
199+
def has_invalid_return_type(self):
200+
types = self.operand_types
201+
obj_dtype_set = frozenset([np.dtype('object')])
202+
return self.return_type == object and types - obj_dtype_set
203+
204+
@property
205+
def operand_types(self):
206+
return frozenset(term.type for term in com.flatten(self))
207+
198208
@property
199209
def isscalar(self):
200210
return all(operand.isscalar for operand in self.operands)
@@ -412,6 +422,10 @@ def _disallow_scalar_only_bool_ops(self):
412422
raise NotImplementedError("cannot evaluate scalar only bool ops")
413423

414424

425+
def isnumeric(dtype):
426+
return issubclass(np.dtype(dtype).type, np.number)
427+
428+
415429
class Div(BinOp):
416430

417431
"""Div operator to special case casting.
@@ -428,6 +442,12 @@ class Div(BinOp):
428442
def __init__(self, lhs, rhs, truediv, *args, **kwargs):
429443
super(Div, self).__init__('/', lhs, rhs, *args, **kwargs)
430444

445+
if not isnumeric(lhs.return_type) or not isnumeric(rhs.return_type):
446+
raise TypeError("unsupported operand type(s) for {0}:"
447+
" '{1}' and '{2}'".format(self.op,
448+
lhs.return_type,
449+
rhs.return_type))
450+
431451
if truediv or PY3:
432452
_cast_inplace(com.flatten(self), np.float_)
433453

pandas/tests/test_frame.py

+10
Original file line numberDiff line numberDiff line change
@@ -13243,6 +13243,16 @@ def test_bool_arith_expr(self):
1324313243
expect = self.frame.a[self.frame.a < 1] + self.frame.b
1324413244
assert_series_equal(res, expect)
1324513245

13246+
def test_invalid_type_for_operator_raises(self):
13247+
df = DataFrame({'a': [1, 2], 'b': ['c', 'd']})
13248+
ops = '+', '-', '*', '/'
13249+
for op in ops:
13250+
with tm.assertRaisesRegexp(TypeError,
13251+
"unsupported operand type\(s\) for "
13252+
".+: '.+' and '.+'"):
13253+
df.eval('a {0} b'.format(op), engine=self.engine,
13254+
parser=self.parser)
13255+
1324613256

1324713257
class TestDataFrameEvalNumExprPython(TestDataFrameEvalNumExprPandas):
1324813258

0 commit comments

Comments
 (0)