Fail closed on namespace dunders and metadata types

This commit is contained in:
2026-07-02 18:29:47 +02:00
parent f7143e7bac
commit 3cf4a5eb52
2 changed files with 173 additions and 7 deletions
+31 -7
View File
@@ -39,6 +39,7 @@ if hasattr(ast, "TryStar"):
_CLASS_SIGNATURE_ATTRS = {"INPUT_TYPES", "RETURN_NAMES", "RETURN_TYPES"}
_DYNAMIC_NAMESPACE_MUTATION = object()
_NAMESPACE_FUNCTIONS = {"globals", "locals", "vars"}
_NAMESPACE_DUNDER_MUTATORS = {"__delitem__", "__setitem__"}
def _literal(node, env, allow_mutable_env=True):
@@ -215,6 +216,10 @@ def _namespace_mutating_call_target_names(node):
return set()
if _namespace_call_function_name(node.func.value) is None:
return set()
if node.func.attr in _NAMESPACE_DUNDER_MUTATORS:
if node.args and isinstance(node.args[0], ast.Constant) and isinstance(node.args[0].value, str):
return {node.args[0].value}
return {_DYNAMIC_NAMESPACE_MUTATION}
if node.func.attr not in _MUTATING_METHODS:
return set()
if node.func.attr != "update":
@@ -879,8 +884,8 @@ def _collect_module_env(tree, class_bindings=None):
def normalise_input_spec(spec):
first = spec[0] if isinstance(spec, (list, tuple)) and spec else spec
if isinstance(first, list):
return "COMBO"
return str(first)
return "COMBO" if all(isinstance(value, str) for value in first) else None
return first if isinstance(first, str) else None
def _class_defs(tree):
@@ -1545,7 +1550,16 @@ def _namespace_alias_mutation_target_names(stmt, aliases):
def visit_Call(self, node):
if isinstance(node.func, ast.Attribute):
if isinstance(node.func.value, ast.Name) and node.func.value.id in aliases:
if node.func.attr == "update":
if node.func.attr in _NAMESPACE_DUNDER_MUTATORS:
if (
node.args
and isinstance(node.args[0], ast.Constant)
and isinstance(node.args[0].value, str)
):
names.add(node.args[0].value)
else:
names.add(_DYNAMIC_NAMESPACE_MUTATION)
elif node.func.attr == "update":
for keyword in node.keywords:
names.add(_DYNAMIC_NAMESPACE_MUTATION if keyword.arg is None else keyword.arg)
if node.args or not node.keywords:
@@ -1772,18 +1786,28 @@ def _signature_from_class(node_type, cls, display, pack_meta, class_env, input_e
else:
values = {}
for name, spec in values.items():
inputs[str(name)] = normalise_input_spec(spec)
if not isinstance(name, str):
return None
input_type = normalise_input_spec(spec)
if input_type is None:
return None
inputs[name] = input_type
if section == "required":
required.append(str(name))
required.append(name)
output_names = []
if return_names is _MISSING:
output_names = []
elif isinstance(return_names, (list, tuple)):
output_names = [str(name) for name in return_names]
if not all(isinstance(name, str) for name in return_names):
return None
output_names = list(return_names)
else:
return None
if not all(isinstance(value, str) for value in return_types):
return None
return {
"type": node_type,
"display": display or node_type,
@@ -1791,7 +1815,7 @@ def _signature_from_class(node_type, cls, display, pack_meta, class_env, input_e
"repository": pack_meta.get("repository", ""),
"inputs": inputs,
"required": required,
"outputs": [str(value) for value in return_types],
"outputs": list(return_types),
"output_names": output_names,
"confidence": "static_exact",
}