From 2b536e2f53dd0bb7833a8e83a190f47efc69333a Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Wed, 22 Apr 2026 17:14:21 +0300 Subject: [PATCH] fix: address type override, alias parameter, and Statement.children issues (chunk 5) - engine.py: type: ignore for children/replace_child hasattr-guarded subclass attrs - result_document.py: type: ignore for Pydantic analysis field override and alias args - render/proto/__init__.py: type: ignore on Pydantic alias argument lines - rules/__init__.py: type: ignore on ensure_feature_valid_for_scopes StringFactory calls - result_document.py: fix Scope|None by extracting scope var with assert --- capa/engine.py | 4 ++-- capa/render/proto/__init__.py | 33 +++++++++++++++++---------------- capa/render/result_document.py | 28 ++++++++++++++-------------- capa/rules/__init__.py | 6 +++--- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/capa/engine.py b/capa/engine.py index 1296198c..e7be9970 100644 --- a/capa/engine.py +++ b/capa/engine.py @@ -78,7 +78,7 @@ class Statement: yield child if hasattr(self, "children"): - for child in self.children: + for child in self.children: # type: ignore # children defined in subclasses, guarded by hasattr assert isinstance(child, (Statement, Feature)) yield child @@ -90,7 +90,7 @@ class Statement: self.child = new if hasattr(self, "children"): - children = self.children + children = self.children # type: ignore # children defined in subclasses, guarded by hasattr for i, child in enumerate(children): if child is existing: children[i] = new diff --git a/capa/render/proto/__init__.py b/capa/render/proto/__init__.py index 8c204fca..ae25992e 100644 --- a/capa/render/proto/__init__.py +++ b/capa/render/proto/__init__.py @@ -886,14 +886,17 @@ def feature_from_pb2(f: capa_pb2.FeatureNode) -> frzf.Feature: elif type_ == "operand_number": ff = f.operand_number return frzf.OperandNumberFeature( - index=ff.index, operand_number=number_from_pb2(ff.operand_number), description=ff.description or None - ) # type: ignore + index=ff.index, + operand_number=number_from_pb2(ff.operand_number), + description=ff.description or None, # type: ignore # Pydantic alias operand-number + ) elif type_ == "operand_offset": ff = f.operand_offset return frzf.OperandOffsetFeature( - index=ff.index, operand_offset=int_from_pb2(ff.operand_offset), description=ff.description or None - ) # type: ignore - # Mypy is unable to recognize `operand_offset` as an argument due to aliasing + index=ff.index, + operand_offset=int_from_pb2(ff.operand_offset), + description=ff.description or None, # type: ignore # Pydantic alias operand-offset + ) elif type_ == "basic_block": ff = f.basic_block return frzf.BasicBlockFeature(description=ff.description or None) @@ -948,13 +951,12 @@ def mbc_from_pb2(pb: capa_pb2.MBCSpec) -> rd.MBCSpec: def maec_from_pb2(pb: capa_pb2.MaecMetadata) -> rd.MaecMetadata: return rd.MaecMetadata( - analysis_conclusion=pb.analysis_conclusion or None, - analysis_conclusion_ov=pb.analysis_conclusion_ov or None, - malware_family=pb.malware_family or None, - malware_category=pb.malware_category or None, - malware_category_ov=pb.malware_category_ov or None, - ) # type: ignore - # Mypy is unable to recognise arguments due to alias + analysis_conclusion=pb.analysis_conclusion or None, # type: ignore # Pydantic alias analysis-conclusion + analysis_conclusion_ov=pb.analysis_conclusion_ov or None, # type: ignore # Pydantic alias analysis-conclusion-ov + malware_family=pb.malware_family or None, # type: ignore # Pydantic alias malware-family + malware_category=pb.malware_category or None, # type: ignore # Pydantic alias malware-category + malware_category_ov=pb.malware_category_ov or None, # type: ignore # Pydantic alias malware-category-ov + ) def rule_metadata_from_pb2(pb: capa_pb2.RuleMetadata) -> rd.RuleMetadata: @@ -963,16 +965,15 @@ def rule_metadata_from_pb2(pb: capa_pb2.RuleMetadata) -> rd.RuleMetadata: namespace=pb.namespace or None, authors=tuple(pb.authors), scopes=scopes_from_pb2(pb.scopes), - attack=tuple([attack_from_pb2(attack) for attack in pb.attack]), + attack=tuple([attack_from_pb2(attack) for attack in pb.attack]), # type: ignore # Pydantic alias att&ck; populate_by_name=True mbc=tuple([mbc_from_pb2(mbc) for mbc in pb.mbc]), references=tuple(pb.references), examples=tuple(pb.examples), description=pb.description, lib=pb.lib, - is_subscope_rule=pb.is_subscope_rule, + is_subscope_rule=pb.is_subscope_rule, # type: ignore # Pydantic alias capa/subscope; populate_by_name=True maec=maec_from_pb2(pb.maec), - ) # type: ignore - # Mypy is unable to recognise `attack` and `is_subscope_rule` as arguments due to alias + ) def doc_from_pb2(doc: capa_pb2.ResultDocument) -> rd.ResultDocument: diff --git a/capa/render/result_document.py b/capa/render/result_document.py index 0093624a..55e0cdbc 100644 --- a/capa/render/result_document.py +++ b/capa/render/result_document.py @@ -155,12 +155,12 @@ class Metadata(Model): class StaticMetadata(Metadata): flavor: Flavor = Flavor.STATIC - analysis: StaticAnalysis + analysis: StaticAnalysis # type: ignore # narrows Analysis union to StaticAnalysis in Pydantic subclass class DynamicMetadata(Metadata): flavor: Flavor = Flavor.DYNAMIC - analysis: DynamicAnalysis + analysis: DynamicAnalysis # type: ignore # narrows Analysis union to DynamicAnalysis in Pydantic subclass class CompoundStatementType: @@ -386,9 +386,11 @@ class Match(FrozenModel): # note! replace `node` # subscopes cannot have both a static and dynamic scope set assert None in (rule.scopes.static, rule.scopes.dynamic) + scope = rule.scopes.static or rule.scopes.dynamic + assert scope is not None node = StatementNode( statement=SubscopeStatement( - scope=rule.scopes.static or rule.scopes.dynamic, + scope=scope, ) ) @@ -668,23 +670,21 @@ class RuleMetadata(FrozenModel): namespace=rule.meta.get("namespace"), authors=rule.meta.get("authors"), scopes=capa.rules.Scopes.from_dict(rule.meta.get("scopes")), - attack=tuple(map(AttackSpec.from_str, rule.meta.get("att&ck", []))), + attack=tuple(map(AttackSpec.from_str, rule.meta.get("att&ck", []))), # type: ignore # Pydantic alias att&ck; populate_by_name=True mbc=tuple(map(MBCSpec.from_str, rule.meta.get("mbc", []))), references=rule.meta.get("references", []), examples=rule.meta.get("examples", []), description=rule.meta.get("description", ""), lib=rule.meta.get("lib", False), - is_subscope_rule=rule.meta.get("capa/subscope", False), + is_subscope_rule=rule.meta.get("capa/subscope", False), # type: ignore # Pydantic alias capa/subscope; populate_by_name=True maec=MaecMetadata( - analysis_conclusion=rule.meta.get("maec/analysis-conclusion"), - analysis_conclusion_ov=rule.meta.get("maec/analysis-conclusion-ov"), - malware_family=rule.meta.get("maec/malware-family"), - malware_category=rule.meta.get("maec/malware-category"), - malware_category_ov=rule.meta.get("maec/malware-category-ov"), - ), # type: ignore - # Mypy is unable to recognise arguments due to alias - ) # type: ignore - # Mypy is unable to recognise arguments due to alias + analysis_conclusion=rule.meta.get("maec/analysis-conclusion"), # type: ignore # Pydantic alias analysis-conclusion + analysis_conclusion_ov=rule.meta.get("maec/analysis-conclusion-ov"), # type: ignore # Pydantic alias + malware_family=rule.meta.get("maec/malware-family"), # type: ignore # Pydantic alias malware-family + malware_category=rule.meta.get("maec/malware-category"), # type: ignore # Pydantic alias malware-category + malware_category_ov=rule.meta.get("maec/malware-category-ov"), # type: ignore # Pydantic alias + ), + ) model_config = ConfigDict(frozen=True, populate_by_name=True) diff --git a/capa/rules/__init__.py b/capa/rules/__init__.py index d62665e9..6ca7ee01 100644 --- a/capa/rules/__init__.py +++ b/capa/rules/__init__.py @@ -789,7 +789,7 @@ def build_statements(d, scopes: Scopes): feature = Feature(arg) else: feature = Feature() # type: ignore[call-arg] # Feature is a runtime union; constructor args vary per subclass - ensure_feature_valid_for_scopes(scopes, feature) + ensure_feature_valid_for_scopes(scopes, feature) # type: ignore[arg-type] # StringFactory.__new__ returns Feature subclass at runtime count = d[key] if isinstance(count, int): @@ -888,7 +888,7 @@ def build_statements(d, scopes: Scopes): feature = Feature(value, description=description) # type: ignore[misc] # Feature is a runtime union; constructor args vary per subclass except ValueError as e: raise InvalidRule(str(e)) from e - ensure_feature_valid_for_scopes(scopes, feature) + ensure_feature_valid_for_scopes(scopes, feature) # type: ignore[arg-type] # StringFactory.__new__ returns Feature subclass at runtime return feature @@ -1102,7 +1102,7 @@ class Rule: if not isinstance(meta.get("mbc", []), list): raise InvalidRule("MBC mapping must be a list") - return cls(name, scopes, build_statements(statements[0], scopes), meta, definition) + return cls(name, scopes, build_statements(statements[0], scopes), meta, definition) # type: ignore[arg-type] # build_statements infers wide union but top-level always returns Statement @staticmethod @lru_cache()