Fail closed on namespace dunders and metadata types
This commit is contained in:
@@ -1187,6 +1187,52 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
self.assertEqual({}, result["nodes"])
|
self.assertEqual({}, result["nodes"])
|
||||||
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_input_types_with_non_string_input_name_skips_node(self):
|
||||||
|
source = '''
|
||||||
|
class NonStringInputNameNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
1: ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"NonStringInputNameNode": NonStringInputNameNode,
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "non-string-input-name-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_input_types_with_non_string_input_type_skips_node(self):
|
||||||
|
source = '''
|
||||||
|
class NonStringInputTypeNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": (2,),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"NonStringInputTypeNode": NonStringInputTypeNode,
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "non-string-input-type-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_dynamic_return_types_reassignment_skips_node(self):
|
def test_dynamic_return_types_reassignment_skips_node(self):
|
||||||
source = '''
|
source = '''
|
||||||
def build_outputs():
|
def build_outputs():
|
||||||
@@ -1876,6 +1922,53 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
self.assertEqual({}, result["nodes"])
|
self.assertEqual({}, result["nodes"])
|
||||||
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_non_string_return_type_entry_skips_node(self):
|
||||||
|
source = '''
|
||||||
|
class NonStringReturnTypeNode:
|
||||||
|
RETURN_TYPES = (123,)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"NonStringReturnTypeNode": NonStringReturnTypeNode,
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "non-string-return-type-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_non_string_return_name_entry_skips_node(self):
|
||||||
|
source = '''
|
||||||
|
class NonStringReturnNameNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
RETURN_NAMES = (456,)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"NonStringReturnNameNode": NonStringReturnNameNode,
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "non-string-return-name-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_missing_return_names_is_allowed(self):
|
def test_missing_return_names_is_allowed(self):
|
||||||
source = '''
|
source = '''
|
||||||
class MissingReturnNamesNode:
|
class MissingReturnNamesNode:
|
||||||
@@ -3028,6 +3121,55 @@ G.update(NODE_CLASS_MAPPINGS={})
|
|||||||
self.assertEqual({}, result["nodes"])
|
self.assertEqual({}, result["nodes"])
|
||||||
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_globals_dunder_setitem_invalidates_static_node_mapping(self):
|
||||||
|
source = '''
|
||||||
|
class GlobalDunderSetitemNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"GlobalDunderSetitemNode": GlobalDunderSetitemNode,
|
||||||
|
}
|
||||||
|
globals().__setitem__("NODE_CLASS_MAPPINGS", {})
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "global-dunder-setitem-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_globals_alias_dunder_setitem_invalidates_static_node_mapping(self):
|
||||||
|
source = '''
|
||||||
|
class GlobalAliasDunderSetitemNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"GlobalAliasDunderSetitemNode": GlobalAliasDunderSetitemNode,
|
||||||
|
}
|
||||||
|
ns = globals()
|
||||||
|
ns.__setitem__("NODE_CLASS_MAPPINGS", {})
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "global-alias-dunder-setitem-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_arbitrary_call_invalidates_static_node_mapping(self):
|
def test_arbitrary_call_invalidates_static_node_mapping(self):
|
||||||
source = '''
|
source = '''
|
||||||
class ArbitraryCallMappingNode:
|
class ArbitraryCallMappingNode:
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ if hasattr(ast, "TryStar"):
|
|||||||
_CLASS_SIGNATURE_ATTRS = {"INPUT_TYPES", "RETURN_NAMES", "RETURN_TYPES"}
|
_CLASS_SIGNATURE_ATTRS = {"INPUT_TYPES", "RETURN_NAMES", "RETURN_TYPES"}
|
||||||
_DYNAMIC_NAMESPACE_MUTATION = object()
|
_DYNAMIC_NAMESPACE_MUTATION = object()
|
||||||
_NAMESPACE_FUNCTIONS = {"globals", "locals", "vars"}
|
_NAMESPACE_FUNCTIONS = {"globals", "locals", "vars"}
|
||||||
|
_NAMESPACE_DUNDER_MUTATORS = {"__delitem__", "__setitem__"}
|
||||||
|
|
||||||
|
|
||||||
def _literal(node, env, allow_mutable_env=True):
|
def _literal(node, env, allow_mutable_env=True):
|
||||||
@@ -215,6 +216,10 @@ def _namespace_mutating_call_target_names(node):
|
|||||||
return set()
|
return set()
|
||||||
if _namespace_call_function_name(node.func.value) is None:
|
if _namespace_call_function_name(node.func.value) is None:
|
||||||
return set()
|
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:
|
if node.func.attr not in _MUTATING_METHODS:
|
||||||
return set()
|
return set()
|
||||||
if node.func.attr != "update":
|
if node.func.attr != "update":
|
||||||
@@ -879,8 +884,8 @@ def _collect_module_env(tree, class_bindings=None):
|
|||||||
def normalise_input_spec(spec):
|
def normalise_input_spec(spec):
|
||||||
first = spec[0] if isinstance(spec, (list, tuple)) and spec else spec
|
first = spec[0] if isinstance(spec, (list, tuple)) and spec else spec
|
||||||
if isinstance(first, list):
|
if isinstance(first, list):
|
||||||
return "COMBO"
|
return "COMBO" if all(isinstance(value, str) for value in first) else None
|
||||||
return str(first)
|
return first if isinstance(first, str) else None
|
||||||
|
|
||||||
|
|
||||||
def _class_defs(tree):
|
def _class_defs(tree):
|
||||||
@@ -1545,7 +1550,16 @@ def _namespace_alias_mutation_target_names(stmt, aliases):
|
|||||||
def visit_Call(self, node):
|
def visit_Call(self, node):
|
||||||
if isinstance(node.func, ast.Attribute):
|
if isinstance(node.func, ast.Attribute):
|
||||||
if isinstance(node.func.value, ast.Name) and node.func.value.id in aliases:
|
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:
|
for keyword in node.keywords:
|
||||||
names.add(_DYNAMIC_NAMESPACE_MUTATION if keyword.arg is None else keyword.arg)
|
names.add(_DYNAMIC_NAMESPACE_MUTATION if keyword.arg is None else keyword.arg)
|
||||||
if node.args or not node.keywords:
|
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:
|
else:
|
||||||
values = {}
|
values = {}
|
||||||
for name, spec in values.items():
|
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":
|
if section == "required":
|
||||||
required.append(str(name))
|
required.append(name)
|
||||||
|
|
||||||
output_names = []
|
output_names = []
|
||||||
if return_names is _MISSING:
|
if return_names is _MISSING:
|
||||||
output_names = []
|
output_names = []
|
||||||
elif isinstance(return_names, (list, tuple)):
|
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:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if not all(isinstance(value, str) for value in return_types):
|
||||||
|
return None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"type": node_type,
|
"type": node_type,
|
||||||
"display": display or 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", ""),
|
"repository": pack_meta.get("repository", ""),
|
||||||
"inputs": inputs,
|
"inputs": inputs,
|
||||||
"required": required,
|
"required": required,
|
||||||
"outputs": [str(value) for value in return_types],
|
"outputs": list(return_types),
|
||||||
"output_names": output_names,
|
"output_names": output_names,
|
||||||
"confidence": "static_exact",
|
"confidence": "static_exact",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user