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
This commit is contained in:
Willi Ballenthin
2026-04-22 17:14:21 +03:00
committed by Willi Ballenthin
parent 96cabbcc6b
commit 2b536e2f53
4 changed files with 36 additions and 35 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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()