Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions ddtrace/debugging/_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ def instanceof(value: Any, type_qname: str) -> bool:
return False


def isdefined(predicate: Callable[[Mapping[str, Any]], Any], _locals: Mapping[str, Any]) -> bool:
try:
predicate(_locals)
except BaseException:
return False
return True


def get_local(_locals: Mapping[str, Any], name: str) -> Any:
try:
return _locals[name]
Expand All @@ -115,26 +123,25 @@ def __index__(cls, o, i):
def __ref__(cls, x):
return x

def _make_function(self, ast: DDASTType, args: Tuple[str, ...], name: str) -> FunctionType:
compiled = self._compile_predicate(ast)
if compiled is None:
raise ValueError("Invalid predicate: %r" % ast)
def _make_function(self, instrs: List[Instr], args: Tuple[str, ...], name: str) -> FunctionType:
abstract_code = Bytecode([*instrs, Instr("RETURN_VALUE")])

instrs = compiled + [Instr("RETURN_VALUE")]
if sys.version_info >= (3, 11):
instrs.insert(0, Instr("RESUME", 0))

abstract_code = Bytecode(instrs)
abstract_code.argcount = len(args)
abstract_code.argnames = args
abstract_code.name = name

if sys.version_info >= (3, 11):
abstract_code.insert(0, Instr("RESUME", 0))

return FunctionType(abstract_code.to_code(), {}, name, (), None)

def _make_lambda(self, ast: DDASTType) -> Callable[[Any, Any], Any]:
self._lambda_level += 1
if (predicate := self._compile_predicate(ast)) is None:
raise ValueError("Invalid predicate: %r" % ast)

try:
return self._make_function(ast, ("_dd_it", "_dd_key", "_dd_value", "_locals"), "<lambda>")
return self._make_function(predicate, ("_dd_it", "_dd_key", "_dd_value", "_locals"), "<lambda>")
finally:
assert self._lambda_level > 0 # nosec
self._lambda_level -= 1
Expand All @@ -155,8 +162,11 @@ def _compile_direct_predicate(self, ast: DDASTType) -> Optional[List[Instr]]:
raise ValueError("Invalid argument: %r" % arg)

if _type == "isDefined":
value.append(Instr("LOAD_FAST", "_locals"))
value.append(IN_OPERATOR_INSTR)
value = self._call_function(
isdefined,
[Instr("LOAD_CONST", self._make_function(value, ("_locals",), "<isDefined-predicate>"))],
[Instr("LOAD_FAST", "_locals")],
)
else:
if PY >= (3, 13):
# UNARY_NOT requires a boolean value
Expand Down Expand Up @@ -378,7 +388,10 @@ def _compile_predicate(self, ast: DDASTType) -> Optional[List[Instr]]:
)

def compile(self, ast: DDASTType) -> Callable[[Mapping[str, Any]], Any]:
return self._make_function(ast, ("_locals",), "<expr>")
if (predicate := self._compile_predicate(ast)) is None:
raise ValueError("Invalid predicate: %r" % ast)

return self._make_function(predicate, ("_locals",), "<expr>")


dd_compile = DDCompiler().compile
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
fixes:
- |
dynamic instrumentation: fixed an issue that caused condition expressions
containing ``isDefined`` to result in an evaluation error.
4 changes: 2 additions & 2 deletions tests/debugging/test_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ def __getitem__(self, name):
({"and": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 0}, 0),
({"or": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 0}, NameError),
({"and": [{"ref": "bar"}, {"ref": "foo"}]}, {"bar": 42}, NameError),
({"isDefined": "foobar"}, {"bar": 42}, False),
({"isDefined": "bar"}, {"bar": 42}, True),
({"isDefined": {"ref": "foobar"}}, {"bar": 42}, False),
({"isDefined": {"ref": "bar"}}, {"bar": 42}, True),
({"instanceof": [{"ref": "bar"}, "int"]}, {"bar": 42}, True),
({"instanceof": [{"ref": "bar"}, "BaseException"]}, {"bar": RuntimeError()}, True),
(
Expand Down
Loading