From 86a74cf38ac95eae11dc7195dd91d0395401de27 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 23:15:54 -0400 Subject: [PATCH 01/16] TYPES: use mypy version 2.4 from within a virtualenv this also updates the version of the checkout action to the latest one --- .github/workflows/run_mypy.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_mypy.yaml b/.github/workflows/run_mypy.yaml index e126f78f..1337ba90 100644 --- a/.github/workflows/run_mypy.yaml +++ b/.github/workflows/run_mypy.yaml @@ -8,11 +8,14 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v7 - name: Install mypy - run: sudo apt-get install -y mypy python3-mypy + run: | + sudo apt-get install -y python3 + python3 -m venv .venv + .venv/bin/pip install mypy==2.4.0 - name: Create default local config run: cp chb/util/ConfigLocal.template chb/util/ConfigLocal.py - name: Run mypy run: | - PYTHONPATH=$PWD mypy chb + PYTHONPATH=$PWD .venv/bin/mypy chb From 0c7e00f703f25e836a6508e21357757c5d483bca Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 14:45:02 -0400 Subject: [PATCH 02/16] TYPES: add a number of cast calls to make mypy happy in strict mode also add type annotations to a couple of index parameters that were causing mypy to think we were return Any --- .../opcodes/ARMLoadMultipleIncrementAfter.py | 8 ++--- .../opcodes/ARMLoadMultipleIncrementBefore.py | 8 ++--- chb/arm/opcodes/ARMVectorMoveDS.py | 4 +-- chb/arm/opcodes/ARMVectorMultiplySubtract.py | 2 +- chb/ast/ASTDeserializer.py | 6 ++-- chb/cmdline/PatchResults.py | 33 +++++++++---------- chb/cmdline/XComparison.py | 10 +++--- chb/invariants/XConstant.py | 4 +-- chb/jsoninterface/AuxiliaryClasses.py | 6 ++-- chb/jsoninterface/JSONAppComparison.py | 18 +++++----- chb/jsoninterface/JSONAssemblyBlock.py | 6 ++-- chb/jsoninterface/JSONAssemblyInstruction.py | 10 +++--- chb/jsoninterface/JSONBlockComparison.py | 8 ++--- chb/jsoninterface/JSONCallgraph.py | 10 +++--- chb/jsoninterface/JSONCallsiteRecords.py | 18 +++++----- chb/jsoninterface/JSONControlFlowGraph.py | 21 ++++++------ chb/jsoninterface/JSONFunctionComparison.py | 22 ++++++------- .../JSONInstructionComparison.py | 6 ++-- chb/jsoninterface/JSONPatchComponent.py | 16 ++++----- .../JSONProofObligationRecord.py | 8 ++--- chb/jsoninterface/JSONStackpointerOffset.py | 4 +-- chb/simulation/SimSymbolicValue.py | 2 +- chb/userdata/UserHints.py | 16 ++++----- chb/util/fileutil.py | 24 +++++++------- 24 files changed, 134 insertions(+), 136 deletions(-) diff --git a/chb/arm/opcodes/ARMLoadMultipleIncrementAfter.py b/chb/arm/opcodes/ARMLoadMultipleIncrementAfter.py index 92bde135..e405ed62 100644 --- a/chb/arm/opcodes/ARMLoadMultipleIncrementAfter.py +++ b/chb/arm/opcodes/ARMLoadMultipleIncrementAfter.py @@ -116,7 +116,7 @@ def memrhss(self) -> List["XXpr"]: def are_memrhss_ok(self) -> bool: return all(self.is_xpr_ok(i) for i in self.memrhs_range) - def memrhs(self, index) -> "XXpr": + def memrhs(self, index: int) -> "XXpr": """returns the rhs expression at (1-based) position index""" if self.has_index(index): @@ -135,7 +135,7 @@ def rmemrhss(self) -> List["XXpr"]: def are_rmemrhss_ok(self) -> bool: return all(self.is_xpr_ok(i) for i in self.rmemrhs_range) - def rmemrhs(self, index) -> "XXpr": + def rmemrhs(self, index: int) -> "XXpr": """returns the rewritten rhs expr at (1-based) position index""" if self.has_index(index): @@ -154,7 +154,7 @@ def xaddrs(self) -> List["XXpr"]: def are_xaddrs_ok(self) -> bool: return all(self.is_xpr_ok(i) for i in self.xaddr_range) - def xaddr(self, index) -> "XXpr": + def xaddr(self, index: int) -> "XXpr": """returns the rhs addr at (1-based) position index""" if self.has_index(index): @@ -173,7 +173,7 @@ def cmemrhss(self) -> List["XXpr"]: def are_cmemrhss_ok(self) -> bool: return all(self.is_cxpr_ok(i) for i in range(0, self.regcount)) - def cmemrhs(self, index) -> "XXpr": + def cmemrhs(self, index: int) -> "XXpr": """returns the rhs addr at (1-based) position index""" if self.has_index(index): diff --git a/chb/arm/opcodes/ARMLoadMultipleIncrementBefore.py b/chb/arm/opcodes/ARMLoadMultipleIncrementBefore.py index 870a2b57..b1d33cd9 100644 --- a/chb/arm/opcodes/ARMLoadMultipleIncrementBefore.py +++ b/chb/arm/opcodes/ARMLoadMultipleIncrementBefore.py @@ -116,7 +116,7 @@ def memrhss(self) -> List["XXpr"]: def are_memrhss_ok(self) -> bool: return all(self.is_xpr_ok(i) for i in self.memrhs_range) - def memrhs(self, index) -> "XXpr": + def memrhs(self, index: int) -> "XXpr": """returns the rhs expression at (1-based) position index""" if self.has_index(index): @@ -135,7 +135,7 @@ def rmemrhss(self) -> List["XXpr"]: def are_rmemrhss_ok(self) -> bool: return all(self.is_xpr_ok(i) for i in self.rmemrhs_range) - def rmemrhs(self, index) -> "XXpr": + def rmemrhs(self, index: int) -> "XXpr": """returns the rewritten rhs expr at (1-based) position index""" if self.has_index(index): @@ -154,7 +154,7 @@ def xaddrs(self) -> List["XXpr"]: def are_xaddrs_ok(self) -> bool: return all(self.is_xpr_ok(i) for i in self.xaddr_range) - def xaddr(self, index) -> "XXpr": + def xaddr(self, index: int) -> "XXpr": """returns the rhs addr at (1-based) position index""" if self.has_index(index): @@ -173,7 +173,7 @@ def cmemrhss(self) -> List["XXpr"]: def are_cmemrhss_ok(self) -> bool: return all(self.is_cxpr_ok(i) for i in range(0, self.regcount)) - def cmemrhs(self, index) -> "XXpr": + def cmemrhs(self, index: int) -> "XXpr": """returns the rhs addr at (1-based) position index""" if self.has_index(index): diff --git a/chb/arm/opcodes/ARMVectorMoveDS.py b/chb/arm/opcodes/ARMVectorMoveDS.py index a591c531..1c3b40ed 100644 --- a/chb/arm/opcodes/ARMVectorMoveDS.py +++ b/chb/arm/opcodes/ARMVectorMoveDS.py @@ -154,14 +154,14 @@ def _unpack_imm32(self, x: XXpr) -> float: # https://stackoverflow.com/questions/33483846/how-to-convert-32-bit-binary-to-float ci = cast(XprConstant, x).intvalue - return struct.unpack('f', struct.pack('I', ci))[0] + return cast(float, struct.unpack('f', struct.pack('I', ci))[0]) def _unpack_imm64(self, x: XXpr) -> float: # from StackOverflow: # https://stackoverflow.com/questions/8751653/how-to-convert-a-binary-string-into-a-float-value ci = cast(XprConstant, x).intvalue b8 = struct.pack('Q', ci) - return struct.unpack('d', b8)[0] + return cast(float, struct.unpack('d', b8)[0]) def annotation(self, xdata: InstrXData) -> str: xd = ARMVectorMoveDSXData(xdata) diff --git a/chb/arm/opcodes/ARMVectorMultiplySubtract.py b/chb/arm/opcodes/ARMVectorMultiplySubtract.py index 6f71d8aa..a4e417b1 100644 --- a/chb/arm/opcodes/ARMVectorMultiplySubtract.py +++ b/chb/arm/opcodes/ARMVectorMultiplySubtract.py @@ -154,7 +154,7 @@ def _unpack_imm32(self, x: XXpr) -> float: # https://stackoverflow.com/questions/33483846/how-to-convert-32-bit-binary-to-float ci = cast(XprConstant, x).intvalue - return struct.unpack('f', struct.pack('I', ci))[0] + return cast(float, struct.unpack('f', struct.pack('I', ci))[0]) def ast_prov( self, diff --git a/chb/ast/ASTDeserializer.py b/chb/ast/ASTDeserializer.py index 61e32cbf..15ba5be3 100644 --- a/chb/ast/ASTDeserializer.py +++ b/chb/ast/ASTDeserializer.py @@ -26,7 +26,7 @@ # ------------------------------------------------------------------------------ """Simplified version of AST to check serialization.""" -from typing import Any, cast, Dict, List, NewType, Optional, Tuple, Union +from typing import Any, cast, Dict, List, Optional, Tuple from chb.ast.AbstractSyntaxTree import ( AbstractSyntaxTree, nooffset, voidtype, ASTSpanRecord) @@ -362,7 +362,7 @@ def find_instr_by_id( def find_instr_address_by_locationid(locationid: int) -> str: for node in fdata["spans"]: if "locationid" in node and int(node["locationid"]) == locationid: - return node["spans"][0]["base_va"] + return cast(str, node["spans"][0]["base_va"]) else: raise Exception( "No span found for locationid: " + str(locationid)) @@ -370,7 +370,7 @@ def find_instr_address_by_locationid(locationid: int) -> str: def find_expr_address_by_exprid(exprid: int) -> str: for node in fdata["spans"]: if "exprid" in node and int(node["exprid"]) == exprid: - return node["spans"][0]["base_va"] + return cast(str, node["spans"][0]["base_va"]) else: raise Exception( "Deserializer: No span found for exprid: " + str(exprid)) diff --git a/chb/cmdline/PatchResults.py b/chb/cmdline/PatchResults.py index af08fd74..6cb0efac 100644 --- a/chb/cmdline/PatchResults.py +++ b/chb/cmdline/PatchResults.py @@ -45,9 +45,8 @@ """ import json -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, cast -import chb.util.fileutil as UF from chb.util.loggingutil import chklogger @@ -58,23 +57,23 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def offset(self) -> int: - return self._d.get("offset", -1) + return cast(int, self._d.get("offset", -1)) @property def removed(self) -> int: - return self._d.get("removed", 0) + return cast(int, self._d.get("removed", 0)) @property def inserted(self) -> int: - return self._d.get("inserted", 0) + return cast(int, self._d.get("inserted", 0)) @property def offsethex(self) -> str: - return self._d.get("offsethex", "0x0") + return cast(str, self._d.get("offsethex", "0x0")) @property def vahex(self) -> str: - return self._d.get("vahex", "0x0") + return cast(str, self._d.get("vahex", "0x0")) def __str__(self) -> str: return ( @@ -94,25 +93,25 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def offset(self) -> int: - return self._d.get("offset", -1) + return cast(int, self._d.get("offset", -1)) @property def removed(self) -> int: - return self._d.get("removed", 0) + return cast(int, self._d.get("removed", 0)) @property def inserted(self) -> int: - return self._d.get("inserted", 0) + return cast(int, self._d.get("inserted", 0)) @property def offsethex(self) -> str: - return self._d.get("offsethex", "0x0") + return cast(str, self._d.get("offsethex", "0x0")) @property def vahex(self) -> str: """Returns the hex start address of the wrapper.""" - return self._d.get("vahex", "0x0") + return cast(str, self._d.get("vahex", "0x0")) def in_wrapper(self, addr: str) -> bool: """Returns true if addr is contained within the wrapper.""" @@ -156,7 +155,7 @@ def payload(self) -> PatchPayload: @property def cases(self) -> List[str]: - return self._d.get("cases", []) + return cast(List[str], self._d.get("cases", [])) def __str__(self) -> str: lines: List[str] = [] @@ -186,7 +185,7 @@ def _convert_offset(self, offset: int) -> str: return hex(offset + int(self.base, 16)) def label_offset(self, label: str) -> int: - return self._d.get(label, 0) + return cast(int, self._d.get(label, 0)) def label_address(self, label: str) -> str: return self._convert_offset(self.label_offset(label)) @@ -306,15 +305,15 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def event_type(self) -> str: - return self._d["event"] + return cast(str, self._d["event"]) @property def patchkind(self) -> str: - return self._d["patchkind"] + return cast(str, self._d["patchkind"]) @property def logicalva(self) -> str: - return self._d["logicalva"] + return cast(str, self._d["logicalva"]) @property def is_trampoline(self) -> bool: diff --git a/chb/cmdline/XComparison.py b/chb/cmdline/XComparison.py index c5a7bec3..f574fbd6 100644 --- a/chb/cmdline/XComparison.py +++ b/chb/cmdline/XComparison.py @@ -26,7 +26,7 @@ # ------------------------------------------------------------------------------ """Compare executable content of two binaries.""" -from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, cast from chb.cmdline.PatchResults import PatchResults import chb.cmdline.commandutil as UC @@ -111,11 +111,11 @@ def messages(self) -> List[str]: @property def programheaders1(self) -> List["ELFProgramHeader"]: - return self.app1.header.programheaders + return cast(List['ELFProgramHeader'], self.app1.header.programheaders) @property def programheaders2(self) -> List["ELFProgramHeader"]: - return self.app2.header.programheaders + return cast(List['ELFProgramHeader'], self.app2.header.programheaders) @property def newsegments(self) -> List["ELFProgramHeader"]: @@ -123,11 +123,11 @@ def newsegments(self) -> List["ELFProgramHeader"]: @property def sectionheaders1(self) -> List["ELFSectionHeader"]: - return self.app1.header.sectionheaders + return cast(List['ELFSectionHeader'], self.app1.header.sectionheaders) @property def sectionheaders2(self) -> List["ELFSectionHeader"]: - return self.app2.header.sectionheaders + return cast(List['ELFSectionHeader'], self.app2.header.sectionheaders) @property def newsections(self) -> List["ELFSectionHeader"]: diff --git a/chb/invariants/XConstant.py b/chb/invariants/XConstant.py index ea5dbd24..61c380b6 100644 --- a/chb/invariants/XConstant.py +++ b/chb/invariants/XConstant.py @@ -41,7 +41,7 @@ """ -from typing import Any, Dict, List, TYPE_CHECKING +from typing import Any, Dict, List, TYPE_CHECKING, cast from chb.invariants.FnDictionaryRecord import FnXprDictionaryRecord, xprregistry from chb.invariants.XNumerical import XNumerical @@ -173,7 +173,7 @@ def value(self) -> int: @property def is_global_address(self) -> bool: - return self.app.header.is_in_address_space(self.value) + return cast(bool, self.app.header.is_in_address_space(self.value)) @property def is_int_constant(self) -> bool: diff --git a/chb/jsoninterface/AuxiliaryClasses.py b/chb/jsoninterface/AuxiliaryClasses.py index 40e61c52..4f22aec8 100644 --- a/chb/jsoninterface/AuxiliaryClasses.py +++ b/chb/jsoninterface/AuxiliaryClasses.py @@ -27,7 +27,7 @@ import os -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, cast class XFilePath: @@ -41,11 +41,11 @@ def d(self) -> Dict[str, Any]: @property def path(self) -> str: - return self.d.get("path", "missing:xfilepath:path") + return cast(str, self.d.get("path", "missing:xfilepath:path")) @property def filename(self) -> str: - return self.d.get("filename", "missing:xfilepath:filename") + return cast(str, self.d.get("filename", "missing:xfilepath:filename")) @property def filepath(self) -> str: diff --git a/chb/jsoninterface/JSONAppComparison.py b/chb/jsoninterface/JSONAppComparison.py index 93653a76..58e8f91b 100644 --- a/chb/jsoninterface/JSONAppComparison.py +++ b/chb/jsoninterface/JSONAppComparison.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast import chb.jsoninterface.AuxiliaryClasses as AX from chb.jsoninterface.JSONFunctionComparison import JSONFunctionComparison @@ -42,7 +42,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def changes(self) -> List[str]: - return self.d.get("changes", []) + return cast(List[str], self.d.get("changes", [])) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_callgraph_comparison(self) @@ -55,7 +55,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def changes(self) -> List[str]: - return self.d.get("changes", []) + return cast(List[str], self.d.get("changes", [])) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_binary_comparison(self) @@ -68,7 +68,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def gaddr(self) -> str: - return self.d.get("gaddr1", self.property_missing("gaddr1")) + return cast(str, self.d.get("gaddr1", self.property_missing("gaddr1"))) @property def name(self) -> Optional[str]: @@ -89,7 +89,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def faddr(self) -> str: - return self.d.get("faddr", self.property_missing("faddr")) + return cast(str, self.d.get("faddr", self.property_missing("faddr"))) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_function_added(self) @@ -103,11 +103,11 @@ def __init__(self, d: Dict[str, str]) -> None: @property def faddr(self) -> str: - return self.d.get("faddr", self.property_missing("faddr")) + return cast(str, self.d.get("faddr", self.property_missing("faddr"))) @property def md5(self) -> str: - return self.d.get("md5", self.property_missing("md5")) + return cast(str, self.d.get("md5", self.property_missing("md5"))) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_function_md5(self) @@ -176,11 +176,11 @@ def file2(self) -> str: @property def changes(self) -> List[str]: - return self.d.get("changes", []) + return cast(List[str], self.d.get("changes", [])) @property def matches(self) -> List[str]: - return self.d.get("matches", []) + return cast(List[str], self.d.get("matches", [])) @property def functions_compared(self) -> List[str]: diff --git a/chb/jsoninterface/JSONAssemblyBlock.py b/chb/jsoninterface/JSONAssemblyBlock.py index fc070f27..da96d002 100644 --- a/chb/jsoninterface/JSONAssemblyBlock.py +++ b/chb/jsoninterface/JSONAssemblyBlock.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast from chb.jsoninterface.JSONAssemblyInstruction import JSONAssemblyInstruction from chb.jsoninterface.JSONObject import JSONObject @@ -42,11 +42,11 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def startaddr(self) -> str: - return self.d.get("startaddr", self.property_missing("startaddr")) + return cast(str, self.d.get("startaddr", self.property_missing("startaddr"))) @property def endaddr(self) -> str: - return self.d.get("endaddr", self.property_missing("endaddr")) + return cast(str, self.d.get("endaddr", self.property_missing("endaddr"))) @property def instructions(self) -> List[JSONAssemblyInstruction]: diff --git a/chb/jsoninterface/JSONAssemblyInstruction.py b/chb/jsoninterface/JSONAssemblyInstruction.py index 9bf0bb5c..8ac8c3e6 100644 --- a/chb/jsoninterface/JSONAssemblyInstruction.py +++ b/chb/jsoninterface/JSONAssemblyInstruction.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Tuple, Optional, TYPE_CHECKING +from typing import Any, Dict, List, Tuple, Optional, TYPE_CHECKING, cast from chb.jsoninterface.JSONObject import JSONObject from chb.jsoninterface.JSONProofObligationRecord import JSONProofObligationRecord @@ -44,7 +44,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def addr(self) -> List[str]: - return self.d.get("addr", self.property_missing("addr")) + return cast(List[str], self.d.get("addr", self.property_missing("addr"))) @property def stackpointer(self) -> Optional[JSONStackpointerOffset]: @@ -64,15 +64,15 @@ def proofobligations(self) -> List[JSONProofObligationRecord]: @property def bytes(self) -> str: - return self.d.get("bytes", self.property_missing("bytes")) + return cast(str, self.d.get("bytes", self.property_missing("bytes"))) @property def opcode(self) -> Tuple[str, str]: - return self.d.get("opcode", self.property_missing("opcode")) + return cast(Tuple[str, str], self.d.get("opcode", self.property_missing("opcode"))) @property def annotation(self) -> str: - return self.d.get("annotation", self.property_missing("annotation")) + return cast(str, self.d.get("annotation", self.property_missing("annotation"))) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_assembly_instruction(self) diff --git a/chb/jsoninterface/JSONBlockComparison.py b/chb/jsoninterface/JSONBlockComparison.py index 3a01ec62..e85481ec 100644 --- a/chb/jsoninterface/JSONBlockComparison.py +++ b/chb/jsoninterface/JSONBlockComparison.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, TYPE_CHECKING +from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast from chb.jsoninterface.JSONAssemblyInstruction import JSONAssemblyInstruction from chb.jsoninterface.JSONInstructionComparison import JSONInstructionComparison @@ -57,17 +57,17 @@ def _get_instr_comparison_summary(self) -> Dict[str, Any]: @property def summary_instructions_added(self) -> List[str]: instr_summary = self._get_instr_comparison_summary() - return instr_summary.get("block-instructions-added", []) + return cast(List[str], instr_summary.get("block-instructions-added", [])) @property def summary_instructions_removed(self) -> List[str]: instr_summary = self._get_instr_comparison_summary() - return instr_summary.get("block-instructions-removed", []) + return cast(List[str], instr_summary.get("block-instructions-removed", [])) @property def summary_instructions_changed(self) -> List[str]: instr_summary = self._get_instr_comparison_summary() - return instr_summary.get("block-instructions-changed", []) + return cast(List[str], instr_summary.get("block-instructions-changed", [])) @property def instructions_changed(self) -> List[JSONInstructionComparison]: diff --git a/chb/jsoninterface/JSONCallgraph.py b/chb/jsoninterface/JSONCallgraph.py index 6683bce3..1f4894c3 100644 --- a/chb/jsoninterface/JSONCallgraph.py +++ b/chb/jsoninterface/JSONCallgraph.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, TYPE_CHECKING +from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast from chb.jsoninterface.JSONObject import JSONObject @@ -41,11 +41,11 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def src(self) -> str: - return self.d.get("src", self.property_missing("src")) + return cast(str, self.d.get("src", self.property_missing("src"))) @property def tgt(self) -> str: - return self.d.get("dst", self.property_missing("dst")) + return cast(str, self.d.get("dst", self.property_missing("dst"))) @property def type(self) -> Optional[str]: @@ -62,11 +62,11 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def name(self) -> str: - return self.d.get("name", self.property_missing("name")) + return cast(str, self.d.get("name", self.property_missing("name"))) @property def label(self) -> str: - return self.d.get("label", self.name) + return cast(str, self.d.get("label", self.name)) @property def type(self) -> Optional[str]: diff --git a/chb/jsoninterface/JSONCallsiteRecords.py b/chb/jsoninterface/JSONCallsiteRecords.py index 451e15c8..6d2d4318 100644 --- a/chb/jsoninterface/JSONCallsiteRecords.py +++ b/chb/jsoninterface/JSONCallsiteRecords.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, cast from chb.jsoninterface.JSONObject import JSONObject from chb.jsoninterface.JSONCallgraph import JSONCallgraph @@ -41,11 +41,11 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def name(self) -> str: - return self.d.get("name", self.property_missing("name")) + return cast(str, self.d.get("name", self.property_missing("name"))) @property def roles(self) -> List[str]: - return self.d.get("parameter-roles", []) + return cast(List[str], self.d.get("parameter-roles", [])) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_callsite_tgt_parameter(self) @@ -59,7 +59,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def name(self) -> str: - return self.d.get("name", self.property_missing("name")) + return cast(str, self.d.get("name", self.property_missing("name"))) @property def parameters(self) -> List[JSONCallsiteTgtParameter]: @@ -72,7 +72,7 @@ def parameters(self) -> List[JSONCallsiteTgtParameter]: @property def varargs(self) -> bool: - return self.d.get("varargs", False) + return cast(bool, self.d.get("varargs", False)) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_callsite_tgt_function(self) @@ -86,11 +86,11 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def name(self) -> str: - return self.d.get("name", self.property_missing("name")) + return cast(str, self.d.get("name", self.property_missing("name"))) @property def value(self) -> str: - return self.d.get("value", self.property_missing("value")) + return cast(str, self.d.get("value", self.property_missing("value"))) @property def roles(self) -> List[Tuple[str, str]]: @@ -116,11 +116,11 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def faddr(self) -> str: - return self.d.get("faddr", self.property_missing("faddr")) + return cast(str, self.d.get("faddr", self.property_missing("faddr"))) @property def iaddr(self) -> str: - return self.d.get("iaddr", self.property_missing("iaddr")) + return cast(str, self.d.get("iaddr", self.property_missing("iaddr"))) @property def arguments(self) -> List[JSONCallsiteArgument]: diff --git a/chb/jsoninterface/JSONControlFlowGraph.py b/chb/jsoninterface/JSONControlFlowGraph.py index 7316893e..2a49a92c 100644 --- a/chb/jsoninterface/JSONControlFlowGraph.py +++ b/chb/jsoninterface/JSONControlFlowGraph.py @@ -25,9 +25,8 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast -import chb.jsoninterface.AuxiliaryClasses as AX from chb.jsoninterface.JSONAssemblyBlock import JSONAssemblyBlock from chb.jsoninterface.JSONObject import JSONObject @@ -43,7 +42,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def baddr(self) -> str: - return self.d.get("baddr", self.property_missing("baddr")) + return cast(str, self.d.get("baddr", self.property_missing("baddr"))) @property def id(self) -> str: @@ -52,7 +51,7 @@ def id(self) -> str: This normally corresponds to BasicBlock.baddr, whereas baddr above corresponds to BasicBlock.real_baddr. """ - return self.d.get("id", self.property_missing("id")) + return cast(str, self.d.get("id", self.property_missing("id"))) @property def code(self) -> JSONAssemblyBlock: @@ -79,23 +78,23 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def src(self) -> str: - return self.d.get("src", self.property_missing("src")) + return cast(str, self.d.get("src", self.property_missing("src"))) @property def tgt(self) -> str: - return self.d.get("tgt", self.property_missing("tgt")) + return cast(str, self.d.get("tgt", self.property_missing("tgt"))) @property def kind(self) -> str: - return self.d.get("kind", self.property_missing("kind")) + return cast(str, self.d.get("kind", self.property_missing("kind"))) @property def predicate(self) -> Optional[str]: if "predicate" in self.d: - return self.d.get( + return cast(str, self.d.get( "predicate", {"txtrep": "?"}).get("txtrep", - self.property_missing("predicate.txtrep")) + self.property_missing("predicate.txtrep"))) return None def accept(self, visitor: "JSONObjectVisitor") -> None: @@ -115,11 +114,11 @@ def name(self) -> Optional[str]: @property def faddr(self) -> str: - return self.d.get("faddr", self.property_missing("faddr")) + return cast(str, self.d.get("faddr", self.property_missing("faddr"))) @property def md5hash(self) -> str: - return self.d.get("md5hash", self.property_missing("md5hash")) + return cast(str, self.d.get("md5hash", self.property_missing("md5hash"))) @property def nodes(self) -> List[JSONCfgNode]: diff --git a/chb/jsoninterface/JSONFunctionComparison.py b/chb/jsoninterface/JSONFunctionComparison.py index 886afc50..5fcc9db2 100644 --- a/chb/jsoninterface/JSONFunctionComparison.py +++ b/chb/jsoninterface/JSONFunctionComparison.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, cast from chb.jsoninterface.JSONBlockComparison import JSONBlockComparison from chb.jsoninterface.JSONControlFlowGraph import JSONControlFlowGraph @@ -44,23 +44,23 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def changes(self) -> List[str]: - return self.d.get("changes", []) + return cast(List[str], self.d.get("changes", [])) @property def matches(self) -> List[str]: - return self.d.get("matches", []) + return cast(List[str], self.d.get("matches", [])) @property def cfg1_block_addr(self) -> str: - return self.d.get("cfg1-block-addr", self.property_missing("cfg1-block-addr")) + return cast(str, self.d.get("cfg1-block-addr", self.property_missing("cfg1-block-addr"))) @property def instr_count1(self) -> int: - return self.d.get("instr-count1", self.property_missing("instr-count1")) + return cast(int, self.d.get("instr-count1", self.property_missing("instr-count1"))) @property def instr_count2(self) -> int: - return self.d.get("instr-count2", self.property_missing("instr-count2")) + return cast(int, self.d.get("instr-count2", self.property_missing("instr-count2"))) @property def cfg2_blocks(self) -> List[Tuple[str, str]]: @@ -112,11 +112,11 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def faddr1(self) -> str: - return self.d.get("faddr1", self.property_missing("faddr1")) + return cast(str, self.d.get("faddr1", self.property_missing("faddr1"))) @property def faddr2(self) -> str: - return self.d.get("faddr2", self.property_missing("faddr2")) + return cast(str, self.d.get("faddr2", self.property_missing("faddr2"))) @property def name1(self) -> Optional[str]: @@ -157,7 +157,7 @@ def num_blocks2(self) -> int: @property def blocks_changed(self) -> List[str]: - return self.d.get("blocks-changed", []) + return cast(List[str], self.d.get("blocks-changed", [])) @property def num_blocks_changed(self) -> int: @@ -165,7 +165,7 @@ def num_blocks_changed(self) -> int: @property def changes(self) -> List[str]: - return self.d.get("changes", [self.property_missing("changes")]) + return cast(List[str], self.d.get("changes", [self.property_missing("changes")])) @property def cfg_block_mapping(self) -> List[JSONCfgBlockMappingItem]: @@ -178,7 +178,7 @@ def cfg_block_mapping(self) -> List[JSONCfgBlockMappingItem]: @property def matches(self) -> List[str]: - return self.d.get("matches", []) + return cast(List[str], self.d.get("matches", [])) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_function_comparison(self) diff --git a/chb/jsoninterface/JSONInstructionComparison.py b/chb/jsoninterface/JSONInstructionComparison.py index dd520275..80c565fe 100644 --- a/chb/jsoninterface/JSONInstructionComparison.py +++ b/chb/jsoninterface/JSONInstructionComparison.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, cast from chb.jsoninterface.JSONObject import JSONObject from chb.jsoninterface.JSONAssemblyInstruction import JSONAssemblyInstruction @@ -44,7 +44,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def iaddr1(self) -> str: - return self.d.get("iaddr1", self.property_missing("iaddr1")) + return cast(str, self.d.get("iaddr1", self.property_missing("iaddr1"))) @property def iaddr2(self) -> Optional[str]: @@ -52,7 +52,7 @@ def iaddr2(self) -> Optional[str]: @property def changes(self) -> List[str]: - return self.d.get("changes", []) + return cast(List[str], self.d.get("changes", [])) @property def instr1(self) -> JSONAssemblyInstruction: diff --git a/chb/jsoninterface/JSONPatchComponent.py b/chb/jsoninterface/JSONPatchComponent.py index 7947f6da..b9e6a32a 100644 --- a/chb/jsoninterface/JSONPatchComponent.py +++ b/chb/jsoninterface/JSONPatchComponent.py @@ -26,7 +26,7 @@ # ------------------------------------------------------------------------------ """JSON objects related to patch components.""" -from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING, cast from chb.jsoninterface.JSONAssemblyInstruction import JSONAssemblyInstruction from chb.jsoninterface.JSONObject import JSONObject @@ -42,19 +42,19 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def srca(self) -> str: - return self.d.get("srca", self.property_missing("srca")) + return cast(str, self.d.get("srca", self.property_missing("srca"))) @property def tgta(self) -> str: - return self.d.get("tgta", self.property_missing("tgta")) + return cast(str, self.d.get("tgta", self.property_missing("tgta"))) @property def size(self) -> int: - return self.d.get("size", self.property_missing("size")) + return cast(int, self.d.get("size", self.property_missing("size"))) @property def instr(self) -> JSONAssemblyInstruction: - return self.d.get("instr", self.property_missing("instr")) + return cast(JSONAssemblyInstruction, self.d.get("instr", self.property_missing("instr"))) def accept(self, visitor: "JSONObjectVisitor") -> None: visitor.visit_hookinstr(self) @@ -68,7 +68,7 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def starta(self) -> str: - return self.d.get("starta", self.property_missing("starta")) + return cast(str, self.d.get("starta", self.property_missing("starta"))) @property def instructions(self) -> List[JSONAssemblyInstruction]: @@ -91,11 +91,11 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def role(self) -> str: - return self.d.get("role", self.property_missing("role")) + return cast(str, self.d.get("role", self.property_missing("role"))) @property def kind(self) -> str: - return self.d.get("kind", self.property_missing("role")) + return cast(str, self.d.get("kind", self.property_missing("role"))) @property def value(self) -> Union[JSONHookInstruction, JSONCodeFragment]: diff --git a/chb/jsoninterface/JSONProofObligationRecord.py b/chb/jsoninterface/JSONProofObligationRecord.py index 0fe8ab61..4278a18f 100644 --- a/chb/jsoninterface/JSONProofObligationRecord.py +++ b/chb/jsoninterface/JSONProofObligationRecord.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, TYPE_CHECKING +from typing import Any, Dict, TYPE_CHECKING, cast from chb.jsoninterface.JSONObject import JSONObject @@ -40,15 +40,15 @@ def __init__(self, d: Dict[str, Any]) -> None: @property def predicate(self) -> str: - return self.d.get("predicate", "none") + return cast(str, self.d.get("predicate", "none")) @property def status(self) -> str: - return self.d.get("status", "none") + return cast(str, self.d.get("status", "none")) @property def msg(self) -> str: - return self.d.get("msg", "none") + return cast(str, self.d.get("msg", "none")) def __str__(self) -> str: return "PO: %s -> status: %s, msg: %s" % (self.predicate, self.status, self.msg) diff --git a/chb/jsoninterface/JSONStackpointerOffset.py b/chb/jsoninterface/JSONStackpointerOffset.py index 09d3a6f9..db307078 100644 --- a/chb/jsoninterface/JSONStackpointerOffset.py +++ b/chb/jsoninterface/JSONStackpointerOffset.py @@ -25,7 +25,7 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, cast class JSONStackpointerOffset: @@ -39,7 +39,7 @@ def d(self) -> Dict[str, Any]: @property def txtrep(self) -> str: - return self.d.get("txtrep", "missing:stackpointeroffset:txtrep") + return cast(str, self.d.get("txtrep", "missing:stackpointeroffset:txtrep")) class JSONStackpointerOffsetUnknown(JSONStackpointerOffset): diff --git a/chb/simulation/SimSymbolicValue.py b/chb/simulation/SimSymbolicValue.py index 87e53819..7ab8dc37 100644 --- a/chb/simulation/SimSymbolicValue.py +++ b/chb/simulation/SimSymbolicValue.py @@ -1158,7 +1158,7 @@ def simfilename(self) -> str: @property def fp(self) -> IO[Any]: - return self._fp + return cast(IO[Any], self._fp) @property def is_file_pointer(self) -> bool: diff --git a/chb/userdata/UserHints.py b/chb/userdata/UserHints.py index f9641d99..b474a9d7 100644 --- a/chb/userdata/UserHints.py +++ b/chb/userdata/UserHints.py @@ -665,7 +665,7 @@ def typingrule(self) -> Dict[str, Any]: @property def action(self) -> str: - a = self.typingrule.get("action", "?") + a = cast(str, self.typingrule.get("action", "?")) if a in ["enable", "disable"]: return a else: @@ -673,7 +673,7 @@ def action(self) -> str: @property def locs(self) -> List[str]: - locs = self.typingrule.get("locs", []) + locs = cast(List[str], self.typingrule.get("locs", [])) if len(locs) == 0: raise UF.CHBError("No locations specified in typing rule") else: @@ -682,7 +682,7 @@ def locs(self) -> List[str]: @property def name(self) -> str: if "name" in self.typingrule: - return self.typingrule["name"] + return cast(str, self.typingrule["name"]) else: raise UF.CHBError("Name is missing in typing rule") @@ -711,15 +711,15 @@ def rdefspec(self) -> Dict[str, Any]: @property def uselocs(self) -> List[str]: - return self.rdefspec.get("uselocs", []) + return cast(List[str], self.rdefspec.get("uselocs", [])) @property def rdeflocs(self) -> List[str]: - return self.rdefspec.get("rdeflocs", []) + return cast(List[str], self.rdefspec.get("rdeflocs", [])) @property def var(self) -> str: - return self.rdefspec.get("var", "__no_var__") + return cast(str, self.rdefspec.get("var", "__no_var__")) def to_xml(self, node: ET.Element) -> None: xremrdef = ET.Element("remove-var-rdefs") @@ -749,7 +749,7 @@ def faddr(self) -> str: if not "faddr" in self.fnannotation: chklogger.logger.warning( "Function annotation without faddr, returning 0x0") - return self.fnannotation.get("faddr", "0x0") + return cast(str, self.fnannotation.get("faddr", "0x0")) @property def stack_variable_introductions(self) -> Dict[int, StackVarIntro]: @@ -785,7 +785,7 @@ def remove_reaching_definitions(self) -> List[RemoveReachingDefinitions]: @property def const_global_variables(self) -> List[str]: - return self.fnannotation.get("const-global-variables", []) + return cast(List[str], self.fnannotation.get("const-global-variables", [])) def has_register_variable_introduction(self, iaddr: str) -> bool: return iaddr in self.register_variable_introductions diff --git a/chb/util/fileutil.py b/chb/util/fileutil.py index c0d768dd..d76dc828 100644 --- a/chb/util/fileutil.py +++ b/chb/util/fileutil.py @@ -93,7 +93,7 @@ import shutil import xml.etree.ElementTree as ET -from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple +from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, cast from chb.util.Config import Config @@ -356,7 +356,7 @@ def get_locale_file() -> Dict[str, Any]: raise CHBFileNotFoundError(filename) try: with open(filename, "r") as fp: - return json.load(fp) + return cast(Dict[str, Any], json.load(fp)) except ValueError as e: raise CHBJSONParseError(filename, e) @@ -500,7 +500,7 @@ def get_chb_json(filename: str) -> Dict[str, Any]: if os.path.isfile(filename): try: with open(filename, "r") as fp: - return json.load(fp) + return cast(Dict[str, Any], json.load(fp)) except ValueError as e: raise CHBJSONParseError(filename, e) except Exception as e: @@ -955,7 +955,7 @@ def get_cfg_replacement_texts(path: str, xfile: str) -> Dict[str, Any]: if os.path.isfile(filename): try: with open(filename, "r") as fp: - d = json.load(fp) + d = cast(Dict[str, Any], json.load(fp)) except ValueError as e: raise CHBJSONParseError(filename, e) return d @@ -1037,7 +1037,7 @@ def get_summaries_list() -> Dict[str, Any]: if os.path.isfile(summariesfile): try: with open(summariesfile, "r") as fp: - return json.load(fp) + return cast(Dict[str, Any], json.load(fp)) except ValueError as e: raise CHBJSONParseError(summariesfile, e) except Exception as e: @@ -1133,9 +1133,9 @@ def get_file_registered_options(md5: str) -> Dict[str, Any]: for f in config.commandline_options: filename = config.commandline_options[f] with open(filename, "r") as fp: - options = json.load(fp) + options = cast(Dict[str, Any], json.load(fp)) if md5 in options: - return options[md5] + return cast(Dict[str, Any], options[md5]) else: pass else: @@ -1155,11 +1155,11 @@ def get_file_registered_userdata(md5: str) -> Dict[str, Any]: for f in config.registered_userdata: filename = config.registered_userdata[f] with open(filename, "r") as fp: - userdatafile = json.load(fp) + userdatafile = cast(Dict[str, Any], json.load(fp)) if md5 in userdatafile["executables"]: - xuserdata = userdatafile["executables"][md5] + xuserdata = cast(Dict[str, Any], userdatafile["executables"][md5]) if "userdata" in xuserdata: - return xuserdata["userdata"] + return cast(Dict[str, Any], xuserdata["userdata"]) else: return {} else: @@ -1184,12 +1184,12 @@ def get_simsupport(kind: str, tag: str) -> Dict[str, Any]: if os.path.isfile(filename): try: with open(filename, "r") as fp: - simsupport = json.load(fp) + simsupport = cast(Dict[str, Any], json.load(fp)) except Exception as e: raise CHBError("Error loading " + filename + ": " + str(e)) if tag in simsupport["simsupport"]: - return simsupport["simsupport"][tag] + return cast(Dict[str, Any], simsupport["simsupport"][tag]) else: raise CHBError( "File " + filename + " does not provide data for " + tag) From 12b3ec93acf297e351bb74890c561f9ad348b872 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 18:36:59 -0400 Subject: [PATCH 03/16] INV: Fix type annotations in FnDictionaryRecord.py A number of type annotations assumed we were doing 'import chb' --- chb/invariants/FnDictionaryRecord.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chb/invariants/FnDictionaryRecord.py b/chb/invariants/FnDictionaryRecord.py index 881f4b62..d8267697 100644 --- a/chb/invariants/FnDictionaryRecord.py +++ b/chb/invariants/FnDictionaryRecord.py @@ -93,7 +93,7 @@ def __init__( self._vd = vd @property - def vd(self) -> "chb.invariants.FnVarDictionary.FnVarDictionary": + def vd(self) -> "FnVarDictionary": return self._vd @property @@ -238,7 +238,7 @@ def handler(t: type) -> type: def mk_instance( self, - xd: "chb.invariants.FnXprDictionary.FnXprDictionary", + xd: "FnXprDictionary", ixval: IndexedTableValue, superclass: Type[XdR]) -> XdR: tag = ixval.tags[0] @@ -270,7 +270,7 @@ def handler(t: type) -> type: def mk_instance( self, - invd: "chb.invariants.FnInvDictionary.FnInvDictionary", + invd: "FnInvDictionary", ixval: IndexedTableValue, superclass: Type[IdR]) -> IdR: tag = ixval.tags[0] @@ -302,7 +302,7 @@ def handler(t: type) -> type: def mk_instance( self, - varinvd: "chb.invariants.FnVarInvDictionary.FnVarInvDictionary", + varinvd: "FnVarInvDictionary", ixval: IndexedTableValue, superclass: Type[VIdR]) -> VIdR: tag = ixval.tags[0] From 820cb93aaad0dfe885e44b4c85889ef80fdf886e Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 19:13:17 -0400 Subject: [PATCH 04/16] TYPES: remove redundant casts, as reported by mypy --- chb/app/Cfg.py | 5 ++--- chb/arm/ARMFunction.py | 4 ++-- chb/ast/ASTNode.py | 1 - chb/astinterface/ASTIProvenance.py | 4 ---- chb/astinterface/ASTInterfaceBasicBlock.py | 1 - chb/bctypes/BCFiles.py | 2 +- chb/bctypes/TypeConstraintStore.py | 3 +-- chb/cmdline/commandutil.py | 1 - chb/cmdline/reportcmds.py | 2 +- chb/invariants/XConstant.py | 2 +- chb/mips/MIPSBlock.py | 3 +-- chb/mips/MIPSFunction.py | 8 +++----- chb/mips/MIPSInstruction.py | 2 +- chb/mips/simulation/MIPSimStubs.py | 5 +---- chb/pwr/PowerFunction.py | 6 ++---- chb/simulation/SimSymbolicValue.py | 3 +-- chb/userdata/UserHints.py | 3 +-- chb/x86/X86Function.py | 3 +-- 18 files changed, 19 insertions(+), 39 deletions(-) diff --git a/chb/app/Cfg.py b/chb/app/Cfg.py index 451db56b..ec514283 100644 --- a/chb/app/Cfg.py +++ b/chb/app/Cfg.py @@ -211,7 +211,7 @@ def intersect(b1: UserNodeID, b2: UserNodeID) -> UserNodeID: def idom(n: UserNodeID) -> UserNodeID: i = idoms[n] assert i is not None # Should always hit start node. - return cast(UserNodeID, i) + return i finger1: UserNodeID = b1 finger2: UserNodeID = b2 @@ -627,8 +627,7 @@ def switch_case_stmts(succ, mb_nextsucc): bodystmts.append(defaultcase) switchbody = astree.mk_block(bodystmts) - switchstmt = cast(AST.ASTSwitchStmt, - astree.mk_switch_stmt(switchcondition, switchbody, mergeaddr)) + switchstmt = astree.mk_switch_stmt(switchcondition, switchbody, mergeaddr) return (xstmts + [switchstmt]) # We don't use with_last_insn_span(x, switchstmt) because when we have # a switch lifted from a conditional jump (for explicit cases) plus an diff --git a/chb/arm/ARMFunction.py b/chb/arm/ARMFunction.py index d4ecba91..74daffb9 100644 --- a/chb/arm/ARMFunction.py +++ b/chb/arm/ARMFunction.py @@ -209,12 +209,12 @@ def f(baddr: str, block: ARMBlock) -> None: def iter_blocks(self, f: Callable[[str, ARMBlock], None]) -> None: for (ba, block) in self.blocks.items(): - armblock = cast(ARMBlock, block) + armblock = block f(ba, armblock) def iter_instructions(self, f: Callable[[str, ARMInstruction], None]) -> None: for (ia, instr) in self.instructions.items(): - arminstr = cast(ARMInstruction, instr) + arminstr = instr f(ia, arminstr) @property diff --git a/chb/ast/ASTNode.py b/chb/ast/ASTNode.py index 76af620d..e3eb7c0c 100644 --- a/chb/ast/ASTNode.py +++ b/chb/ast/ASTNode.py @@ -2904,7 +2904,6 @@ def field_at_offset(self, offset: int) -> Tuple["ASTFieldInfo", int]: prev = (i, fname) else: if prev is not None: - prev = cast(Tuple[int, str], prev) return (self.fieldinfo(prev[1]), offset - prev[0]) else: raise Exception( diff --git a/chb/astinterface/ASTIProvenance.py b/chb/astinterface/ASTIProvenance.py index 0ae892d8..741cd2f7 100644 --- a/chb/astinterface/ASTIProvenance.py +++ b/chb/astinterface/ASTIProvenance.py @@ -424,7 +424,6 @@ def set_ast_provenance(self, p: ASTProvenance) -> None: def resolve_reaching_defs(self) -> None: for (xid, rds) in self.expr_rdefs.items(): for rd in rds: - rd = cast("ReachingDefFact", rd) v = str(rd.variable) if v == "PC": continue @@ -494,7 +493,6 @@ def resolve_reaching_defs(self) -> None: def resolve_flag_reaching_defs(self) -> None: for (xid, frds) in self.flag_expr_rdefs.items(): for frd in frds: - frd = cast("FlagReachingDefFact", frd) addrs = [str(d) for d in frd.deflocations] for addr in addrs: if addr in self.address_instructions: @@ -507,7 +505,6 @@ def resolve_flag_reaching_defs(self) -> None: def resolve_definitions_used(self) -> None: for (lvalid, defuse) in self.lval_defuses.items(): - defuse = cast("DefUse", defuse) addrs = [str(u) for u in defuse.uselocations if str(u) != "exit"] for addr in addrs: if addr in self.address_instructions: @@ -520,7 +517,6 @@ def resolve_definitions_used(self) -> None: def resolve_definitions_used_high(self) -> None: for (lvalid, defuse) in self.lval_defuses_high.items(): - defuse = cast("DefUseHigh", defuse) addrs = [str(u) for u in defuse.uselocations if str(u) != "exit"] for addr in addrs: if addr in self.address_instructions: diff --git a/chb/astinterface/ASTInterfaceBasicBlock.py b/chb/astinterface/ASTInterfaceBasicBlock.py index 5028e462..2c0d2e2b 100644 --- a/chb/astinterface/ASTInterfaceBasicBlock.py +++ b/chb/astinterface/ASTInterfaceBasicBlock.py @@ -400,7 +400,6 @@ def trampoline_payload_ast(self, astree: "ASTInterface") -> AST.ASTStmt: # case 1 if chkinstr2.mnemonic_stem == "MOV": - chkinstr2 = cast("ARMInstruction", chkinstr2) if chkinstr2.has_instruction_condition(): if chkinstr2.has_valid_instruction_c_condition(): condition = chkinstr2.get_instruction_c_condition() diff --git a/chb/bctypes/BCFiles.py b/chb/bctypes/BCFiles.py index 98589102..5f467223 100644 --- a/chb/bctypes/BCFiles.py +++ b/chb/bctypes/BCFiles.py @@ -90,7 +90,7 @@ def fsum_compinfos(self) -> List[BCCompInfo]: if typ.is_typedef: typedef = cast("BCTypNamed", typ).typedef if typedef: - typ = typ = cast("BCTypeInfo", typedef).ttype + typ = typedef.ttype if typ.is_struct: compinfo = cast("BCTypComp", typ).compinfo result.append(compinfo) diff --git a/chb/bctypes/TypeConstraintStore.py b/chb/bctypes/TypeConstraintStore.py index 94161112..83b04c74 100644 --- a/chb/bctypes/TypeConstraintStore.py +++ b/chb/bctypes/TypeConstraintStore.py @@ -342,8 +342,7 @@ def function_type_constraints(self) -> Dict[str, FunctionTypeConstraints]: addr = c.typevar.base_addr if c.typevar.is_function: self.add_function_constraints(addr) - self._functionconstraints[addr].add_var_constraint( - cast(TC.TypeVariableConstraint, c)) + self._functionconstraints[addr].add_var_constraint(c) elif c.is_subtype_constraint: c = cast(TC.SubTypeConstraint, c) basevars = c.basevars diff --git a/chb/cmdline/commandutil.py b/chb/cmdline/commandutil.py index 96524154..175b4276 100644 --- a/chb/cmdline/commandutil.py +++ b/chb/cmdline/commandutil.py @@ -203,7 +203,6 @@ def get_app(path: str, xfile: str, xinfo: XI.XInfo) -> AppAccess: def get_asm(app: AppAccess) -> Assembly: if isinstance(app, MIPSAccess): - app = cast(MIPSAccess, app) return MIPSAssembly(app, UF.get_mips_asm_xnode(app.path, app.filename)) elif isinstance(app, ARMAccess): return ARMAssembly(app, UF.get_arm_asm_xnode(app.path, app.filename)) diff --git a/chb/cmdline/reportcmds.py b/chb/cmdline/reportcmds.py index fe11ce8a..c53eb942 100644 --- a/chb/cmdline/reportcmds.py +++ b/chb/cmdline/reportcmds.py @@ -1807,7 +1807,7 @@ def is_cmd_delegation(po: 'ProofObligation') -> bool: if is_cmd_delegation(po): os_cmd_delegations[po.status.get_iaddr()].append(instr.iaddr) if type(po.xpo) == XPOTrustedOsCmdFmtString: - trusted_po = cast('XPOTrustedOsCmdFmtString', po.xpo) + trusted_po = po.xpo os_cmd_construction.append((faddr, instr, calltgt.name, trusted_po)) if calltgt.is_app_target and calltgt.name not in app_functions: app_functions[calltgt.name] = str(cast('AppTarget', calltgt).address) diff --git a/chb/invariants/XConstant.py b/chb/invariants/XConstant.py index 61c380b6..cd8dd4e3 100644 --- a/chb/invariants/XConstant.py +++ b/chb/invariants/XConstant.py @@ -173,7 +173,7 @@ def value(self) -> int: @property def is_global_address(self) -> bool: - return cast(bool, self.app.header.is_in_address_space(self.value)) + return self.app.header.is_in_address_space(self.value) @property def is_int_constant(self) -> bool: diff --git a/chb/mips/MIPSBlock.py b/chb/mips/MIPSBlock.py index d86ed7f1..3cba7039 100644 --- a/chb/mips/MIPSBlock.py +++ b/chb/mips/MIPSBlock.py @@ -78,8 +78,7 @@ def instructions(self) -> Dict[str, MIPSInstruction]: def iter(self, f: Callable[[str, MIPSInstruction], None]) -> None: for (ia, instr) in self.instructions.items(): - mipsinstr = cast(MIPSInstruction, instr) - f(ia, mipsinstr) + f(ia, instr) def get_sliced_instructions( self, registers: List[str]) -> List[MIPSInstruction]: diff --git a/chb/mips/MIPSFunction.py b/chb/mips/MIPSFunction.py index c19d998d..ca5682e1 100644 --- a/chb/mips/MIPSFunction.py +++ b/chb/mips/MIPSFunction.py @@ -133,13 +133,11 @@ def f(baddr: str, block: MIPSBlock) -> None: def iter_blocks(self, f: Callable[[str, MIPSBlock], None]) -> None: for (ba, block) in self.blocks.items(): - mipsblock = cast(MIPSBlock, block) - f(ba, mipsblock) + f(ba, block) def iter_instructions(self, f: Callable[[str, MIPSInstruction], None]) -> None: for (ia, instr) in self.instructions.items(): - mipsinstr = cast(MIPSInstruction, instr) - f(ia, mipsinstr) + f(ia, instr) @property def branchconditions(self) -> Mapping[str, MIPSInstruction]: @@ -149,7 +147,7 @@ def branchconditions(self) -> Mapping[str, MIPSInstruction]: if lastinstr.is_branch_instruction: ftconditions = lastinstr.ft_conditions if len(ftconditions) > 0: - result[b.baddr] = cast(MIPSInstruction, lastinstr) + result[b.baddr] = lastinstr return result def set_fnvar_dictionary(self, xnode: ET.Element) -> FnVarDictionary: diff --git a/chb/mips/MIPSInstruction.py b/chb/mips/MIPSInstruction.py index 54fbdb2b..81fe2b9f 100644 --- a/chb/mips/MIPSInstruction.py +++ b/chb/mips/MIPSInstruction.py @@ -310,7 +310,7 @@ def has_branch_condition(self) -> bool: def branch_condition(self) -> XXpr: if self.has_branch_condition(): opc = cast(MIPSBranchOpcode, self.opcode) - return cast(XXpr, opc.branch_condition(self.xdata)) + return opc.branch_condition(self.xdata) else: raise UF.CHBError("Instruction does not have a branch condition") diff --git a/chb/mips/simulation/MIPSimStubs.py b/chb/mips/simulation/MIPSimStubs.py index 4a5f49fa..4df47eca 100644 --- a/chb/mips/simulation/MIPSimStubs.py +++ b/chb/mips/simulation/MIPSimStubs.py @@ -370,7 +370,6 @@ def get_string_at_address( iaddr, "String argument is not a valid address: " + str(saddr)) elif gaddr.is_address: - gaddr = cast(SSV.SimGlobalAddress, gaddr) return simstate.get_string_from_memaddr(iaddr, gaddr) raise SU.CHBSimError( @@ -1458,7 +1457,6 @@ def simulate(self, iaddr: str, simstate: "SimulationState") -> str: simstate.set_register(iaddr, "v0", fpresult) returnval = str(fpresult) elif a0.is_symbol: - a0 = cast(SSV.SimSymbol, a0) symresult = SSV.SimSymbol(a0.name + '_fildes') simstate.set_register(iaddr, "v0", symresult) returnval = str(symresult) @@ -3744,7 +3742,6 @@ def simulate(self, iaddr: str, simstate: "SimulationState") -> str: a0 = cast(SSV.SimBaseAddress, a0) if a0.has_buffer_size(): a0buffersize = cast(int, a0.buffersize) - a0 = cast(SSV.SimBaseAddress, a0) for i in range(0, a0buffersize): srcaddr = a0.add_offset(i) tgtaddr = address.add_offset(i) @@ -5200,7 +5197,7 @@ def simulate(self, iaddr: str, simstate: "SimulationState") -> str: "some argument to strstr is undefined") if a0.is_address: - addr: SV.SimValue = cast(SV.SimValue, a0) + addr: SV.SimValue = a0 elif a0.is_literal: addr = simstate.resolve_literal_address(iaddr, a0.literal_value) diff --git a/chb/pwr/PowerFunction.py b/chb/pwr/PowerFunction.py index 60b0eae2..3adf325b 100644 --- a/chb/pwr/PowerFunction.py +++ b/chb/pwr/PowerFunction.py @@ -117,14 +117,12 @@ def f(baddr: str, block: PowerBlock) -> None: def iter_blocks(self, f: Callable[[str, PowerBlock], None]) -> None: for (ba, block) in self.blocks.items(): - pwrblock = cast(PowerBlock, block) - f(ba, pwrblock) + f(ba, block) def iter_instructions( self, f: Callable[[str, PowerInstruction], None]) -> None: for (ia, instr) in self.instructions.items(): - pwrinstr = cast(PowerInstruction, instr) - f(ia, pwrinstr) + f(ia, instr) @property def branchconditions(self) -> Mapping[str, PowerInstruction]: diff --git a/chb/simulation/SimSymbolicValue.py b/chb/simulation/SimSymbolicValue.py index 7ab8dc37..9339bc5b 100644 --- a/chb/simulation/SimSymbolicValue.py +++ b/chb/simulation/SimSymbolicValue.py @@ -705,8 +705,7 @@ def add_offset(self, v: int) -> "SimBaseAddress": self.base, newoffset, buffersize=self.buffersize, tgttype=self.tgttype) def align(self, v: int) -> "SimBaseAddress": - newoffset = cast( - SV.SimDoubleWordValue, self.offset.bitwise_and(SV.mk_simvalue(v))) + newoffset = self.offset.bitwise_and(SV.mk_simvalue(v)) return SimBaseAddress( self.base, newoffset, buffersize=self.buffersize, tgttype=self.tgttype) diff --git a/chb/userdata/UserHints.py b/chb/userdata/UserHints.py index b474a9d7..cc4270b1 100644 --- a/chb/userdata/UserHints.py +++ b/chb/userdata/UserHints.py @@ -475,7 +475,7 @@ def has_start_address(self, addr: str) -> bool: def update(self, d: List[Dict[str, Union[List[str], str]]]) -> None: for db in d: - addr = cast(str, db["r"][0]) + addr = db["r"][0] if not self.has_start_address(addr): self._datablocks.append(db) self._addrs.append(addr) @@ -1451,7 +1451,6 @@ def to_xml(self, node: ET.Element) -> None: xgv = ET.Element("syma") xaddrs.append(xgv) xgv.set("a", gv) - gvname = cast(str, gvname) xgv.set("name", gvname) ptnode = ET.Element("type") xgv.append(ptnode) diff --git a/chb/x86/X86Function.py b/chb/x86/X86Function.py index 13ad5e08..566bf0a1 100644 --- a/chb/x86/X86Function.py +++ b/chb/x86/X86Function.py @@ -172,8 +172,7 @@ def cfg(self) -> X86Cfg: def iter_instructions(self, f: Callable[[str, X86Instruction], None]) -> None: for (ia, instr) in self.instructions.items(): - x86instr = cast(X86Instruction, instr) - f(ia, x86instr) + f(ia, instr) def arg_count(self) -> int: xvalues = self.vardictionary.constant_value_variables() From 5bac9a74e6b0fdfcfd86ee37de3c2b3bfaa982a7 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 19:22:36 -0400 Subject: [PATCH 05/16] ELF: fix some issues raised by type checking - get_relocation_tables: sh.index is a string, self.sections is a dict with int keys. move the converstion to int outside the if check so the check and subsequent access are valid - as_dictionary: fix possible typo, using s from a previous loop instead of ss from current loop --- chb/elfformat/ELFHeader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chb/elfformat/ELFHeader.py b/chb/elfformat/ELFHeader.py index 361dffa3..d5123b63 100644 --- a/chb/elfformat/ELFHeader.py +++ b/chb/elfformat/ELFHeader.py @@ -410,10 +410,10 @@ def get_relocation_tables(self) -> List[Tuple[ELFSectionHeader, ELFSection]]: result: List[Tuple[ELFSectionHeader, ELFSection]] = [] for sh in self.sectionheaders: if sh.is_relocation_table: - if sh.index not in self.sections: + index = int(sh.index) + if index not in self.sections: xsection = UF.get_elf_section_xnode( self.pathname, self.filename, sh.index) - index = int(sh.index) self._sections[index] = ELFRelocationTable( self, self.sectionheaders[index], xsection) result.append((sh, self.sections[index])) @@ -465,7 +465,7 @@ def as_dictionary(self) -> Dict[str, Any]: if self.has_string_table(): result["stringtables"] = {} for ss in self.get_string_tables(): - result["stringtables"][ss.name] = s.as_dictionary() + result["stringtables"][ss.name] = ss.as_dictionary() if self.has_symbol_table(): result["symboltable"] = self.get_symbol_table().as_dictionary() if self.has_dynamic_symbol_table(): From 8fcb7bfa34190f3efd5a2c8b17f34b1a94412b3a Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 22:45:38 -0400 Subject: [PATCH 06/16] CFG: Add type annotations to some of the nested functions mypy is still unhappy though. We're combining ASTStmtLabels with ASTStmts which are not compatible. There doesn't seem to be a good solution that doesn't rock the boat on this poorly understood function so we're doing type:ignore for the time being. --- chb/app/Cfg.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/chb/app/Cfg.py b/chb/app/Cfg.py index ec514283..6a78e07f 100644 --- a/chb/app/Cfg.py +++ b/chb/app/Cfg.py @@ -582,7 +582,7 @@ def node_within(x: str, merges: List[str], ctx: ControlFlowContext) -> List[AST. for succ in succs: if jumptable.has_target(succ) and succ not in succlabels: cvs = jumptable.get_target(succ) - def rawlabel(c): + def rawlabel(c: int) -> AST.ASTCaseLabel: r = astree.mk_case_label(astree.mk_integer_constant(c)) astree.add_instruction_span(r.locationid, succ, "") return r @@ -598,7 +598,7 @@ def switch_case_ctx(mb_nextsucc: Optional[str]) -> ControlFlowContext: return ctx.in_switch(mergeaddr, mb_nextsucc) return ctx - def switch_case_stmts(succ, mb_nextsucc): + def switch_case_stmts(succ: str, mb_nextsucc: Optional[str]) -> List[AST.ASTStmt]: sctx = switch_case_ctx(mb_nextsucc) casebody = do_branch(x, succ, sctx) if casebody == [] and mb_nextsucc is None: @@ -607,7 +607,12 @@ def switch_case_stmts(succ, mb_nextsucc): # we insert a synthetic break statement to maintain # syntactic compatibility. casebody = [astree.mk_break_stmt()] - return succlabels.get(succ, []) + casebody + # typing note: mypy complains that we're mixing up ASTStmts (casebody) with + # ASTStmtLabels (from succlabels). With the current implementation, + # making ASTStmtLabels into ASTStmts would cause other issues. + # This function is also not well understood so we're wary of mucking with + # it too much. type: ignore is the best approach for the time being. + return succlabels.get(succ, []) + casebody #type: ignore bodystmts: List[AST.ASTStmt] = [] for idx in range(1, len(succs)): From cfe05db24d3c65bfc1530e3ebdb97659c634376e Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 23:05:03 -0400 Subject: [PATCH 07/16] TYPES: misc typing fixes mostly missing type annotations and wrong imports --- chb/api/BTerm.py | 2 +- chb/api/FormatStringSpec.py | 2 +- chb/app/Cfg.py | 2 +- chb/cmdline/jsonresultutil.py | 2 +- chb/cmdline/reportcmds.py | 5 +++-- chb/invariants/XXprUtil.py | 5 +++-- chb/tests/ELFThumbDisassemblyTestSet.py | 10 +++++----- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/chb/api/BTerm.py b/chb/api/BTerm.py index c840a54a..ca681414 100644 --- a/chb/api/BTerm.py +++ b/chb/api/BTerm.py @@ -59,7 +59,7 @@ from chb.util.IndexedTable import IndexedTableValue if TYPE_CHECKING: - from chb.api.InterfaceDictionary import FtsParameter + from chb.api.FtsParameter import FtsParameter from chb.api.InterfaceDictionary import InterfaceDictionary diff --git a/chb/api/FormatStringSpec.py b/chb/api/FormatStringSpec.py index 81cdc974..bc5880ce 100644 --- a/chb/api/FormatStringSpec.py +++ b/chb/api/FormatStringSpec.py @@ -124,7 +124,7 @@ def arg_type(self) -> str: def flags(self) -> str: return "".join(chr(i) for i in self.args) - def __str__(self): + def __str__(self) -> str: return ( "%" + self.flags diff --git a/chb/app/Cfg.py b/chb/app/Cfg.py index 6a78e07f..12a78a93 100644 --- a/chb/app/Cfg.py +++ b/chb/app/Cfg.py @@ -188,7 +188,7 @@ def inverse_with_phantom_exit_node(self) -> 'FlowGraph': augedges[phantomend] = terminators return FlowGraph(list(self.nodes) + [phantomend], augedges, phantomend) - def ipostdoms(rrg: 'FlowGraph') -> Dict[UserNodeID, UserNodeID]: + def ipostdoms(self, rrg: 'FlowGraph') -> Dict[UserNodeID, UserNodeID]: idoms = rrg.idoms.copy() # The start node of the reverse graph is a phantom node that doesn't # exist in the original graph. diff --git a/chb/cmdline/jsonresultutil.py b/chb/cmdline/jsonresultutil.py index 02e0ca49..8c35216a 100644 --- a/chb/cmdline/jsonresultutil.py +++ b/chb/cmdline/jsonresultutil.py @@ -77,7 +77,7 @@ def jsonok(schemaname: str, content: Dict[str, Any]) -> Dict[str, Any]: return jresult -def jsonappdata(xinfo: "XInfo", includepath=True) -> Dict[str, str]: +def jsonappdata(xinfo: "XInfo", includepath: bool = True) -> Dict[str, str]: result: Dict[str, str] = {} if includepath: result["path"] = xinfo.path diff --git a/chb/cmdline/reportcmds.py b/chb/cmdline/reportcmds.py index c53eb942..c97bc2b6 100644 --- a/chb/cmdline/reportcmds.py +++ b/chb/cmdline/reportcmds.py @@ -1410,10 +1410,11 @@ def report_buffer_bounds(args: argparse.Namespace) -> NoReturn: def collect_known_fn_addrs(app: "AppAccess", patchcallsites: list) -> dict: function_addr: Dict[str, str] = {} - def consider_pair(faddr: str, fname: Optional[str]): + def consider_pair(faddr: str, fname: Optional[str]) -> None: if fname and fname not in function_addr: function_addr[fname] = faddr - def consider(faddr: str): + + def consider(faddr: str) -> None: fname = ( app.function_name(faddr) if app.has_function_name(faddr) diff --git a/chb/invariants/XXprUtil.py b/chb/invariants/XXprUtil.py index 2eb36b76..84a7f519 100644 --- a/chb/invariants/XXprUtil.py +++ b/chb/invariants/XXprUtil.py @@ -126,6 +126,7 @@ VMemoryOffsetArrayIndexOffset, VMemoryOffsetBasePtrArrayIndexOffset, VMemoryOffsetIndexOffset) + from chb.invariants.XVariable import XVariable from chb.mips.MIPSRegister import MIPSRegister @@ -1649,7 +1650,7 @@ def xxpr_to_ast_def_expr( def xvariable_to_ast_lvals( - xv: X.XVariable, + xv: 'XVariable', xdata: "InstrXData", astree: ASTInterface, size: int = 4, @@ -1883,7 +1884,7 @@ def global_variable_to_ast_lval( def xvariable_to_ast_lval( - xv: X.XVariable, + xv: 'XVariable', xdata: "InstrXData", iaddr: str, astree: ASTInterface, diff --git a/chb/tests/ELFThumbDisassemblyTestSet.py b/chb/tests/ELFThumbDisassemblyTestSet.py index bfba80b8..b3c901c6 100644 --- a/chb/tests/ELFThumbDisassemblyTestSet.py +++ b/chb/tests/ELFThumbDisassemblyTestSet.py @@ -51,8 +51,8 @@ class ELFThumbDisassemblyTestSet: def __init__(self) -> None: pass - def create_test(self, r) -> None: - files: Dict = {} + def create_test(self, r: List[str]) -> None: + files: Dict[str, str] = {} name = r[0] tca = ELFARMTestCreator(name, r[1], suite="DT") elfheader = tca.create_elf_header() @@ -63,19 +63,19 @@ def create_test(self, r) -> None: UF.save_test_files(name, "arm32", "elf", "DT", name, files, xinfo) - def remove_test(self, r) -> None: + def remove_test(self, r: List[str]) -> None: testfilename = UF.get_test_filename("arm32", "elf", "DT", r[0]) (path, xfile) = UC.get_path_filename(testfilename) os.remove(os.path.join(path, xfile)) shutil.rmtree(os.path.join(path, xfile + ".ch")) - def run_test(self, r) -> None: + def run_test(self, r: List[str]) -> None: testfilename = UF.get_test_filename("arm32", "elf", "DT", r[0]) result = self.analyze_test_case(testfilename) if result != 0: print("Error in analysis of " + testfilename) - def check_test(self, r) -> None: + def check_test(self, r: List[str]) -> None: testfilename = UF.get_test_filename("arm32", "elf", "DT", r[0]) try: (path, xfile) = UC.get_path_filename(testfilename) From 7e5326038c7cb3b514e43e545b846f0af8e42514 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 23:05:58 -0400 Subject: [PATCH 08/16] Fix CallTarget usage The code was comparing CallTarget to strings. This commit changes various locations to use `CallTarget.name` for the comparison. is_call_to_app_function required a bigger change: first, we need to check that the returned call target is an AppTarget (which is what the function is looking for) and then we need to compare its address since that is what the passed function argument represents. Address is an attribute that only some of the CallTarget classes provide so we cannot access it willy-nilly. Hat tip to the overlord for catching this. --- chb/invariants/XXpr.py | 6 +++--- chb/mips/MIPSInstruction.py | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/chb/invariants/XXpr.py b/chb/invariants/XXpr.py index b1836e3b..8b71ebe1 100644 --- a/chb/invariants/XXpr.py +++ b/chb/invariants/XXpr.py @@ -506,10 +506,10 @@ def to_input_constraint_value(self) -> Optional[ICV.InputConstraintValue]: tgt = self.returnval_target() if tgt is None: return None - if tgt == 'getenv': + if tgt.name == 'getenv': envarg = self.returnval_arguments()[0] return ICV.EnvironmentInputValue(str(envarg)) - if tgt in ['strchr', 'strrchr']: + if tgt.name in ['strchr', 'strrchr']: strk = self.returnval_arguments()[0].to_input_constraint_value() if strk is not None: cchar = self.returnval_arguments()[1] @@ -517,7 +517,7 @@ def to_input_constraint_value(self) -> Optional[ICV.InputConstraintValue]: charval = cchar.constant.value charcode = "'" + chr(charval) + "'" return ICV.StringSuffixValue( - strk, charcode, lastpos=(tgt == 'strrchr')) + strk, charcode, lastpos=(tgt.name == 'strrchr')) else: return None else: diff --git a/chb/mips/MIPSInstruction.py b/chb/mips/MIPSInstruction.py index 81fe2b9f..4cb34741 100644 --- a/chb/mips/MIPSInstruction.py +++ b/chb/mips/MIPSInstruction.py @@ -64,6 +64,7 @@ from chb.mips.MIPSFunction import MIPSFunction from chb.mips.opcodes.MIPSLoadWord import MIPSLoadWord from chb.simulation.SimulationState import SimulationState + from chb.api.CallTarget import AppTarget class MIPSInstruction(Instruction): @@ -245,8 +246,11 @@ def return_value(self) -> Optional[XXpr]: def is_call_to_app_function(self, tgtaddr: str) -> bool: if self.is_call_instruction and self.opcode.has_call_target(self.xdata): opc = cast(MIPSJumpLinkRegister, self.opcode) - ctgtaddr = opc.call_target(self.xdata) - return ctgtaddr == tgtaddr + call_target = opc.call_target(self.xdata) + if not call_target.is_app_target: + return False + call_target = cast(AppTarget, call_target) + return str(call_target.address) == tgtaddr return False def call_facts(self) -> Mapping[str, Any]: @@ -257,7 +261,7 @@ def call_facts(self) -> Mapping[str, Any]: if callargs: result['args'] = callargs tgt = self.call_target - if tgt == 'call-target:u': + if tgt.name == 'call-target:u': result['t'] = '?' else: result['t'] = str(tgt) From 90be292fc15d3b32f80470cc631d93cf79b116d6 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 11 Sep 2026 23:19:26 -0400 Subject: [PATCH 09/16] XML: check that getroot doesn't return None. Mostly to make mypy happy --- chb/util/xmlutil.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chb/util/xmlutil.py b/chb/util/xmlutil.py index 0e51df0a..50499f65 100644 --- a/chb/util/xmlutil.py +++ b/chb/util/xmlutil.py @@ -85,7 +85,9 @@ def element_to_pretty(e: ET.Element, indent: int = 0) -> List[str]: def doc_to_pretty(t: ET.ElementTree) -> str: lines = ['\n'] - lines.extend(element_to_pretty(t.getroot())) + root = t.getroot() + assert root is not None + lines.extend(element_to_pretty(root)) return "".join(lines) From 51b2e855b8ee9a01a32663f7dafd7c438a002136 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 16 Oct 2026 10:54:17 -0400 Subject: [PATCH 10/16] TYPES: Add details on the types contained in various lists and dicts --- chb/cmdline/PatchResults.py | 2 +- chb/cmdline/commandutil.py | 2 +- chb/cmdline/reportcmds.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/chb/cmdline/PatchResults.py b/chb/cmdline/PatchResults.py index 6cb0efac..636924fc 100644 --- a/chb/cmdline/PatchResults.py +++ b/chb/cmdline/PatchResults.py @@ -243,7 +243,7 @@ def return_dst(self) -> str: return hex(self._d.get("return", 0)) def __str__(self) -> str: - lines: List = [] + lines: List[str] = [] lines.append("payload : " + self.payload) lines.append("fallthrough : " + self.fallthrough) lines.append("break : " + self.break_dst) diff --git a/chb/cmdline/commandutil.py b/chb/cmdline/commandutil.py index 175b4276..129cfbd6 100644 --- a/chb/cmdline/commandutil.py +++ b/chb/cmdline/commandutil.py @@ -607,7 +607,7 @@ def analyzecmd(args: argparse.Namespace) -> NoReturn: udata = userdata["userdata"] = {} dbdata = udata["data-blocks"] = [] for db in datablocks: - dbrec: Dict = {} + dbrec: Dict[str, List[str]] = {} dbrec["r"] = [db.startaddr, db.endaddr] dbdata.append(dbrec) with open(outputfile + ".json", "w") as fp: diff --git a/chb/cmdline/reportcmds.py b/chb/cmdline/reportcmds.py index c97bc2b6..f6b68fb9 100644 --- a/chb/cmdline/reportcmds.py +++ b/chb/cmdline/reportcmds.py @@ -766,16 +766,16 @@ def report_proofobligations(args: argparse.Namespace) -> NoReturn: # fn_counts: faddr -> status_tag -> count fn_counts: Dict[str, Dict[str, int]] = {} # fn_open_pos: faddr -> iaddr -> list of open ProofObligations - fn_open_pos: Dict[str, Dict[str, List]] = {} + fn_open_pos: Dict[str, Dict[str, List['ProofObligation']]] = {} # fn_detail_pos: faddr -> iaddr -> list of safe/delegated ProofObligations - fn_detail_pos: Dict[str, Dict[str, List]] = {} + fn_detail_pos: Dict[str, Dict[str, List['ProofObligation']]] = {} # fn_labels: faddr -> display string fn_labels: Dict[str, str] = {} detail_statuses = {"dis", "local", "del"} # flat list of records for --json export - po_records: List[Dict] = [] + po_records: List[Dict[str, Any]] = [] for fn in app.functions.values(): faddr = fn.faddr @@ -1408,7 +1408,7 @@ def report_buffer_bounds(args: argparse.Namespace) -> NoReturn: exit(0) -def collect_known_fn_addrs(app: "AppAccess", patchcallsites: list) -> dict: +def collect_known_fn_addrs(app: "AppAccess", patchcallsites: list[LibraryCallSideeffect]) -> dict[str, str]: function_addr: Dict[str, str] = {} def consider_pair(faddr: str, fname: Optional[str]) -> None: if fname and fname not in function_addr: From efbe1aa33c26cad0894374df0898f6676e5098a1 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Mon, 19 Oct 2026 11:50:26 -0400 Subject: [PATCH 11/16] TYPES: work around mypy weirdness when using the '**' operator Switch to using left shift instead. That could break if shiftamount is negative but I don't think that's possible? --- chb/arm/ARMOperandKind.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chb/arm/ARMOperandKind.py b/chb/arm/ARMOperandKind.py index c938f0e6..8971ccac 100644 --- a/chb/arm/ARMOperandKind.py +++ b/chb/arm/ARMOperandKind.py @@ -612,7 +612,11 @@ def scale_factor(self) -> Optional[int]: return 1 else: if srt.is_shift_left: - return 2 ** shiftamount + # For some unknown reason, mypy (2.4.0) complains that we're returning + # Any here instead of [int | None]. This only happens when we use the + # `**` operator, so work around it by using `<<` Original code was: + # return 2 ** shiftamount + return 2 << (shiftamount - 1) else: return None else: From bd54d47c55da5e793356844d8f31f2f467d44d97 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Mon, 19 Oct 2026 12:39:12 -0400 Subject: [PATCH 12/16] TYPES: fix typing issues with new log_id logging code --- chb/util/loggingutil.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/chb/util/loggingutil.py b/chb/util/loggingutil.py index af913da4..10350732 100644 --- a/chb/util/loggingutil.py +++ b/chb/util/loggingutil.py @@ -31,13 +31,19 @@ from contextlib import contextmanager import logging from enum import Enum -from typing import Optional, List +from typing import Optional, List, Iterator, TYPE_CHECKING, Any, MutableMapping + +# Work around mypy issue with LoggerAdapter: https://github.com/python/typeshed/issues/7855 +if TYPE_CHECKING: + _LoggerAdapter = logging.LoggerAdapter[logging.Logger] +else: + _LoggerAdapter = logging.LoggerAdapter _current_faddr: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar( "chkx_faddr", default=None) @contextmanager -def function_context(faddr: str): +def function_context(faddr: str) -> Iterator[None]: token = _current_faddr.set(faddr) try: yield @@ -68,7 +74,7 @@ def options(cls) -> List[str]: class NativeJsonFormatter(logging.Formatter): - def format(self, record): + def format(self, record: logging.LogRecord) -> str: log_payload = { "ts": self.formatTime(record, self.datefmt), "lvl": record.levelname, @@ -133,8 +139,11 @@ class CHKLogID(str, Enum): USER_STRCTDEF_0002 = "USER-STRCTDEF-0002" # idem -class IDLogger(logging.LoggerAdapter): - def process(self, msg, kwargs): +class IDLogger(_LoggerAdapter): + def process( + self, + msg: Any, + kwargs: MutableMapping[str, Any]) -> tuple[Any, MutableMapping[str, Any]]: """ Interrupts the logging call and safely moves the dynamic 'log_id' from kwargs into the final 'extra' dictionary. @@ -145,23 +154,23 @@ def process(self, msg, kwargs): extra.setdefault('faddr', _current_faddr.get()) return msg, kwargs - def log_with_id(self, level, log_id, msg, *args, **kwargs): + def log_with_id(self, level: int, log_id: CHKLogID, msg: object, *args: Any, **kwargs: Any) -> None: kwargs['log_id'] = log_id.value self.log(level, msg, *args, **kwargs, stacklevel=4) - def debug_id(self, log_id: CHKLogID, msg, *args, **kwargs): + def debug_id(self, log_id: CHKLogID, msg: object, *args: Any, **kwargs: Any) -> None: self.log_with_id(logging.DEBUG, log_id, msg, *args, **kwargs) - def info_id(self, log_id: CHKLogID, msg, *args, **kwargs): + def info_id(self, log_id: CHKLogID, msg: object, *args: Any, **kwargs: Any) -> None: self.log_with_id(logging.INFO, log_id, msg, *args, **kwargs) - def warning_id(self, log_id: CHKLogID, msg, *args, **kwargs): + def warning_id(self, log_id: CHKLogID, msg: object, *args: Any, **kwargs: Any) -> None: self.log_with_id(logging.WARNING, log_id, msg, *args, **kwargs) - def error_id(self, log_id: CHKLogID, msg, *args, **kwargs): + def error_id(self, log_id: CHKLogID, msg: object, *args: Any, **kwargs: Any) -> None: self.log_with_id(logging.ERROR, log_id, msg, *args, **kwargs) - def critical_id(self, log_id: CHKLogID, msg, *args, **kwargs): + def critical_id(self, log_id: CHKLogID, msg: object, *args: Any, **kwargs: Any) -> None: self.log_with_id(logging.CRITICAL, log_id, msg, *args, **kwargs) From 2474fa3e0ced3a361e2b3d5a8b98e1839ec4d3a1 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Fri, 16 Oct 2026 10:17:01 -0400 Subject: [PATCH 13/16] TYPES: AppAccess' file format is no longer a generic This was making mypy really mad and fixing it while keeping the generic would have required a lot of refactoring. Instead, we now have a FileHeader ABC, which ELFHeader and PEHeader inherit from. AppAccess and its users switch to that. AppAccess also gets two new methods to get the ELF/PE-typed headers so that code that only supports one or the other can be explicit about that. The methods in the ABC represent what the current code calls generically. I had to add a is_big_endian method to PEHeader which always returns False, according to the internet PE binaries are always little endian. Along the way, fix some issues where we check for None being returned, print an error, but then continued to use the invalid value. For the archaelogists: AppAccess was originally made generic in 28bb704a427fddfeea5a84ec6969d078e3ac8512 --- chb/app/AppAccess.py | 35 +++++++++++++-------- chb/arm/ARMAccess.py | 8 ++--- chb/cmdline/XComparison.py | 8 ++--- chb/cmdline/commandutil.py | 19 ++++++++---- chb/cmdline/fileformatcmds.py | 8 ++--- chb/elfformat/ELFHeader.py | 4 +-- chb/mips/MIPSAccess.py | 9 +++--- chb/peformat/PEHeader.py | 9 ++++-- chb/pwr/PowerAccess.py | 6 ++-- chb/simulation/SimulationState.py | 6 ++-- chb/util/fileutil.py | 51 ++++++++++++++++++++++++++++++- chb/x86/X86Access.py | 9 +++--- 12 files changed, 121 insertions(+), 51 deletions(-) diff --git a/chb/app/AppAccess.py b/chb/app/AppAccess.py index 74b87a3d..ea519394 100644 --- a/chb/app/AppAccess.py +++ b/chb/app/AppAccess.py @@ -30,7 +30,6 @@ from abc import ABC, abstractmethod from typing import ( - Any, Callable, cast, Dict, @@ -38,13 +37,10 @@ Mapping, Optional, Sequence, - Generic, Tuple, Type, TYPE_CHECKING, - TypeVar, - Union, - overload) +) from chb.api.CallTarget import CallTarget, IndirectTarget, CallbackTableTarget from chb.api.InterfaceDictionary import InterfaceDictionary @@ -91,25 +87,23 @@ if TYPE_CHECKING: from chb.app.Instruction import Instruction -HeaderTy = TypeVar('HeaderTy', PEHeader, ELFHeader, Union[PEHeader, ELFHeader]) - -class AppAccess(ABC, Generic[HeaderTy]): +class AppAccess(ABC): def __init__( self, path: str, filename: str, - fileformat: Type[HeaderTy], + fileformat: Type[UF.FileHeader], deps: List[str] = []) -> None: """Initializes access to analysis results.""" self._path = path self._filename = filename self._deps = deps # list of summary zips registered as dependencies - self._header_ty: Type[HeaderTy] = fileformat # currently supported: elf, pe + self._header_ty: Type[UF.FileHeader] = fileformat # currently supported: elf, pe self._userdata: Optional[UserData] = None - self._header: Optional[HeaderTy] = None + self._header: Optional[UF.FileHeader] = None # functions self._appresultdata: Optional[AppResultData] = None @@ -227,13 +221,30 @@ def bcfiles(self) -> BCFiles: # File format -------------------------------------------------------------- @property - def header(self) -> HeaderTy: + def header(self) -> UF.FileHeader: if self._header is None: x = self._header_ty.get_xnode(self.path, self.filename) self._header = self._header_ty( self.path, self.filename, x, self.dependencies) return self._header + # Format specific. Will raise exceptions if the wrong format is requested + @property + def elf_header(self) -> ELFHeader: + if not self.elf: + raise RuntimeError("Requested ELF header but binary is not ELF: %s" % self._header_ty) + header = self.header + assert isinstance(header, ELFHeader) + return header + + @property + def pe_header(self) -> PEHeader: + if not self.pe: + raise RuntimeError("Requested PE header but binary is not PE: %s" % self._header_ty) + header = self.header + assert isinstance(header, PEHeader) + return header + # Systeminfo --------------------------------------------------------------- @property diff --git a/chb/arm/ARMAccess.py b/chb/arm/ARMAccess.py index a510fdad..227ca504 100644 --- a/chb/arm/ARMAccess.py +++ b/chb/arm/ARMAccess.py @@ -29,24 +29,22 @@ from typing import Callable, Dict, List, Mapping, Optional, Type from chb.app.Callgraph import Callgraph, mk_tgt_callgraph_node, mk_app_callgraph_node -from chb.app.AppAccess import AppAccess, HeaderTy +from chb.app.AppAccess import AppAccess from chb.arm.ARMDictionary import ARMDictionary from chb.arm.ARMFunction import ARMFunction from chb.arm.ARMInstruction import ARMInstruction -from chb.elfformat.ELFHeader import ELFHeader - import chb.util.fileutil as UF -class ARMAccess(AppAccess[HeaderTy]): +class ARMAccess(AppAccess): def __init__( self, path: str, filename: str, - fileformat: Type[HeaderTy], + fileformat: Type[UF.FileHeader], deps: List[str] = []) -> None: AppAccess.__init__(self, path, filename, fileformat, deps) self._armd: Optional[ARMDictionary] = None diff --git a/chb/cmdline/XComparison.py b/chb/cmdline/XComparison.py index f574fbd6..9001d0fe 100644 --- a/chb/cmdline/XComparison.py +++ b/chb/cmdline/XComparison.py @@ -111,11 +111,11 @@ def messages(self) -> List[str]: @property def programheaders1(self) -> List["ELFProgramHeader"]: - return cast(List['ELFProgramHeader'], self.app1.header.programheaders) + return self.app1.elf_header.programheaders @property def programheaders2(self) -> List["ELFProgramHeader"]: - return cast(List['ELFProgramHeader'], self.app2.header.programheaders) + return self.app2.elf_header.programheaders @property def newsegments(self) -> List["ELFProgramHeader"]: @@ -210,7 +210,7 @@ def compare_segments(self) -> None: for ph1 in self.programheaders1: index = ph1.index if not index in self._programheaderpairs: - ph2 = self.app2.header.get_programheader_by_index(index) + ph2 = self.app2.elf_header.get_programheader_by_index(index) self._programheaderpairs[index] = (ph1, ph2) else: chklogger.logger.warning("Duplicate header index: %d", index) @@ -240,7 +240,7 @@ def compare_sections(self) -> None: for sh1 in self.sectionheaders1: name = sh1.name if not name in self._sectionheaderpairs: - sh2 = self.app2.header.get_sectionheader_by_name(name) + sh2 = self.app2.elf_header.get_sectionheader_by_name(name) self._sectionheaderpairs[name] = (sh1, sh2) else: chklogger.logger.warning("Duplicate section name: " + name) diff --git a/chb/cmdline/commandutil.py b/chb/cmdline/commandutil.py index 129cfbd6..3844579e 100644 --- a/chb/cmdline/commandutil.py +++ b/chb/cmdline/commandutil.py @@ -1846,7 +1846,7 @@ def results_extract(args: argparse.Namespace) -> NoReturn: exit(1) app = get_app(path, xfile, xinfo) - elfheader = app.header + elfheader = app.elf_header iaddr = int(addr, 16) sectionindex = elfheader.get_elf_section_index(iaddr) @@ -1898,6 +1898,7 @@ def results_extract(args: argparse.Namespace) -> NoReturn: sectionindex = elfheader.get_elf_section_index(iaddr) if sectionindex is None: print_error("Address not found in elf section: " + addr) + continue stable: List[str] = [] while True: data = elfheader.get_doubleword_value( @@ -1907,6 +1908,9 @@ def results_extract(args: argparse.Namespace) -> NoReturn: break else: strindex = elfheader.get_elf_section_index(data) + if strindex is None: + print_error("No section index found for %#x" % data) + continue strdata = elfheader.get_string(strindex, data) stable.append(strdata) table.append(stable) @@ -1925,7 +1929,9 @@ def results_extract(args: argparse.Namespace) -> NoReturn: strindex = elfheader.get_elf_section_index(data) if strindex is None: print_error("String address not found: " + hex(data)) - strdata = elfheader.get_string(strindex, data) + strdata = hex(data) + else: + strdata = elfheader.get_string(strindex, data) r.append(strdata) elif t == "int": r.append(str(data)) @@ -2013,6 +2019,7 @@ def results_struct_arguments(args: argparse.Namespace) -> NoReturn: xinfo.load(path, xfile) app = get_app(path, xfile, xinfo) + elf_header = app.elf_header results: Dict[int, Tuple[int, "BCCompInfo"]] = {} callinstrs = app.call_instructions() @@ -2057,7 +2064,7 @@ def results_struct_arguments(args: argparse.Namespace) -> NoReturn: structtype = cast("BCTypComp", structtype) addr = x.intvalue - elfsection = app.header.get_elf_section_index(addr) + elfsection = elf_header.get_elf_section_index(addr) if elfsection is not None: print( "struct: " @@ -2071,7 +2078,7 @@ def results_struct_arguments(args: argparse.Namespace) -> NoReturn: for (addr, (elfsection, compinfo)) in sorted(results.items()): print("\nstruct " + compinfo.cname + " at " + hex(addr)) for (foffset, fieldinfo) in compinfo.fieldoffsets(): - fieldval = app.header.get_doubleword_value(elfsection, addr + foffset) + fieldval = elf_header.get_doubleword_value(elfsection, addr + foffset) print( str(foffset).rjust(4) + " " @@ -2083,9 +2090,9 @@ def results_struct_arguments(args: argparse.Namespace) -> NoReturn: if ( fieldtype.tgttyp.is_integer and fieldtype.tgttyp.byte_size() == 1): - fieldsection = app.header.get_elf_section_index(fieldval) + fieldsection = elf_header.get_elf_section_index(fieldval) if fieldsection is not None: - fieldstring = app.header.get_string( + fieldstring = elf_header.get_string( fieldsection, fieldval) print(" Constant string: " + fieldstring) diff --git a/chb/cmdline/fileformatcmds.py b/chb/cmdline/fileformatcmds.py index 2b0a16ba..dd4a0666 100644 --- a/chb/cmdline/fileformatcmds.py +++ b/chb/cmdline/fileformatcmds.py @@ -79,7 +79,7 @@ def pedatacmd(args: argparse.Namespace) -> NoReturn: exit(1) app = UC.get_app(path, xfile, xinfo) - peheader = app.header + peheader = app.pe_header if headeronly: print(peheader) exit(0) @@ -140,7 +140,7 @@ def elfdatacmd(args: argparse.Namespace) -> NoReturn: try: app = UC.get_app(path, xfile, xinfo) - elfheader = app.header + elfheader = app.elf_header except UF.CHBError as e: print(str(e.wrap())) exit(1) @@ -167,8 +167,8 @@ def elfdatacmd(args: argparse.Namespace) -> NoReturn: result["file"] = xinfo.file result["md5"] = xinfo.md5 result["section-headers"] = [] - for s in elfheader.sectionheaders: - result["section-headers"].append(s.attribute_values()) + for sh in elfheader.sectionheaders: + result["section-headers"].append(sh.attribute_values()) filename = xname + "_section_headers.json" with open(filename, "w") as fp: json.dump(result, fp, indent=3) diff --git a/chb/elfformat/ELFHeader.py b/chb/elfformat/ELFHeader.py index d5123b63..96d5007d 100644 --- a/chb/elfformat/ELFHeader.py +++ b/chb/elfformat/ELFHeader.py @@ -116,7 +116,7 @@ def get_value(d: Dict[str, str], v: str) -> str: } -class ELFHeader: +class ELFHeader(UF.FileHeader): @staticmethod def fmt_name() -> str: @@ -132,7 +132,7 @@ def __init__( filename: str, xnode: ET.Element, # ignored, used for compatibility with PEHeader - deps: Sequence[str] = []) -> None: + _: Sequence[str]) -> None: self._pathname = pathname self._filename = filename self.xnode = xnode diff --git a/chb/mips/MIPSAccess.py b/chb/mips/MIPSAccess.py index 725835ef..a7e48e74 100644 --- a/chb/mips/MIPSAccess.py +++ b/chb/mips/MIPSAccess.py @@ -27,13 +27,12 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from chb.elfformat.ELFHeader import ELFHeader -from typing import Callable, Type, cast, Dict, List, Mapping, Optional, Sequence, Tuple +from typing import Callable, Type, cast, Dict, List, Mapping, Optional, Tuple from chb.api.CallTarget import CallTarget, IndirectTarget from chb.app.Callgraph import Callgraph, mk_tgt_callgraph_node, mk_app_callgraph_node -from chb.app.AppAccess import AppAccess, HeaderTy +from chb.app.AppAccess import AppAccess from chb.mips.MIPSDictionary import MIPSDictionary from chb.mips.MIPSFunction import MIPSFunction @@ -42,13 +41,13 @@ import chb.util.fileutil as UF -class MIPSAccess(AppAccess[HeaderTy]): +class MIPSAccess(AppAccess): def __init__( self, path: str, filename: str, - fileformat: Type[HeaderTy], + fileformat: Type[UF.FileHeader], deps: List[str] = []) -> None: AppAccess.__init__(self, path, filename, fileformat, deps) self._mipsd: Optional[MIPSDictionary] = None diff --git a/chb/peformat/PEHeader.py b/chb/peformat/PEHeader.py index 99c1127e..da45acc1 100644 --- a/chb/peformat/PEHeader.py +++ b/chb/peformat/PEHeader.py @@ -239,7 +239,7 @@ def subsystem(self) -> str: return self._get("subsystem") -class PEHeader: +class PEHeader(UF.FileHeader): """Main entry point to access the raw data in the executable.""" @staticmethod @@ -255,7 +255,7 @@ def __init__( pathname: str, filename: str, xnode: ET.Element, - deps: Sequence[str] = []) -> None: + deps: Sequence[str]) -> None: self._pathname = pathname self._filename = filename self.xnode = xnode @@ -462,6 +462,11 @@ def subsystem(self) -> str: def file_characteristics_text(self) -> List[str]: return self.coff_header.file_characteristics_text + @property + def is_big_endian(self) -> bool: + # According to the internet windows PE binaries are always little endian + return False + def as_dictionary(self) -> Dict[str, Any]: name = self.filename result: Dict[str, Any] = {} diff --git a/chb/pwr/PowerAccess.py b/chb/pwr/PowerAccess.py index 187db638..46c4acbf 100644 --- a/chb/pwr/PowerAccess.py +++ b/chb/pwr/PowerAccess.py @@ -29,7 +29,7 @@ from typing import Callable, Dict, List, Mapping, Optional, Type from chb.app.Callgraph import Callgraph, mk_tgt_callgraph_node, mk_app_callgraph_node -from chb.app.AppAccess import AppAccess, HeaderTy +from chb.app.AppAccess import AppAccess from chb.elfformat.ELFHeader import ELFHeader @@ -39,13 +39,13 @@ import chb.util.fileutil as UF -class PowerAccess(AppAccess[HeaderTy]): +class PowerAccess(AppAccess): def __init__( self, path: str, filename: str, - fileformat: Type[HeaderTy], + fileformat: Type[UF.FileHeader], deps: List[str] = []) -> None: AppAccess.__init__(self, path, filename, fileformat, deps) self._pwrd: Optional[PowerDictionary] = None diff --git a/chb/simulation/SimulationState.py b/chb/simulation/SimulationState.py index 9330c5f3..45b434dc 100644 --- a/chb/simulation/SimulationState.py +++ b/chb/simulation/SimulationState.py @@ -148,6 +148,8 @@ def __init__( base: str, max_addr: str, loadaddr: Optional[str] = None) -> None: + if not app.elf: + raise NotImplementedError("Only have support for ELF binaries") self._name = name self._app = app self._base = base # base address in hex @@ -213,7 +215,7 @@ def imports(self) -> Dict[int, str]: @property def exports(self) -> Dict[str, int]: if len(self._exports) == 0: - symtab = cast("ELFSymbolTable", self.app.header.get_dynamic_symbol_table()) + symtab = cast("ELFSymbolTable", self.app.elf_header.get_dynamic_symbol_table()) for (sid, sym) in symtab.symbols.items(): if sym.is_exported: self._exports[sym.st_name] = int(sym.value, 16) @@ -295,7 +297,7 @@ def __init__( module: SimModule) -> None: self._module = module self._simstate = simstate - self._globalmem = ELFSimGlobalMemory(self, self.module.app.header) + self._globalmem = ELFSimGlobalMemory(self, self.module.app.elf_header) @property def module(self) -> SimModule: diff --git a/chb/util/fileutil.py b/chb/util/fileutil.py index d76dc828..3708009a 100644 --- a/chb/util/fileutil.py +++ b/chb/util/fileutil.py @@ -90,8 +90,8 @@ import json import os import subprocess -import shutil import xml.etree.ElementTree as ET +from abc import ABC, abstractmethod from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, cast @@ -283,6 +283,55 @@ def __str__(self) -> str: + self.msg) +class FileHeader(ABC): + """Interface for binary header classes + + It provides the main entry point to access the raw data in the executable. + + The abstract classes enables us to treat different types of binaries (ELF, PE) + the same way regardless of the underlying details. + """ + @staticmethod + @abstractmethod + def fmt_name() -> str: ... + + @staticmethod + @abstractmethod + def get_xnode(path: str, filename: str) -> ET.Element: ... + + + @abstractmethod + def __init__( + self, + pathname: str, + filename: str, + xnode: ET.Element, + deps: Sequence[str]) -> None: ... + + @property + @abstractmethod + def pathname(self) -> str: ... + + @property + @abstractmethod + def filename(self) -> str: ... + + @property + @abstractmethod + def image_base(self) -> str: ... + + @property + @abstractmethod + def max_address_space(self) -> str: ... + + @abstractmethod + def is_in_address_space(self, addr: int) -> bool: ... + + @property + @abstractmethod + def is_big_endian(self) -> bool: ... + + def get_path_filename(name: str) -> Tuple[str, str]: """Returns the path and filename of the target executable indicated by name.""" name = os.path.abspath(name) diff --git a/chb/x86/X86Access.py b/chb/x86/X86Access.py index 009e36b0..579a3ece 100644 --- a/chb/x86/X86Access.py +++ b/chb/x86/X86Access.py @@ -27,11 +27,10 @@ # SOFTWARE. # ------------------------------------------------------------------------------ -from chb.elfformat.ELFHeader import ELFHeader -from typing import Dict, List, Mapping, Optional, Sequence, Type +from typing import Dict, List, Mapping, Optional, Type from chb.app.Callgraph import Callgraph -from chb.app.AppAccess import AppAccess, HeaderTy +from chb.app.AppAccess import AppAccess import chb.util.fileutil as UF @@ -40,13 +39,13 @@ from chb.x86.X86Instruction import X86Instruction -class X86Access(AppAccess[HeaderTy]): +class X86Access(AppAccess): def __init__( self, path: str, filename: str, - fileformat: Type[HeaderTy], + fileformat: Type[UF.FileHeader], deps: List[str] = []) -> None: AppAccess.__init__(self, path, filename, fileformat, deps) self._x86d: Optional[X86Dictionary] = None From 11a3441874b0bf89fa8fec53e5ef85ef2f639ae8 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Mon, 19 Oct 2026 11:06:28 -0400 Subject: [PATCH 14/16] TYPES: XComparison only supports ELF binaries, make that explicit We raise an exception at constructor time if given an AppAccess wrapping a non-ELF binary. Also, compare_executable_content and relational_compare_elfdata were accessing an ELF only field in their app's header causing mypy to complain. But since the code doesn't use the sectionheaders we can just remove the access --- chb/cmdline/XComparison.py | 10 +++++++--- chb/cmdline/relationalcmds.py | 6 ------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/chb/cmdline/XComparison.py b/chb/cmdline/XComparison.py index 9001d0fe..aa332185 100644 --- a/chb/cmdline/XComparison.py +++ b/chb/cmdline/XComparison.py @@ -26,7 +26,7 @@ # ------------------------------------------------------------------------------ """Compare executable content of two binaries.""" -from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, cast +from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING from chb.cmdline.PatchResults import PatchResults import chb.cmdline.commandutil as UC @@ -53,6 +53,10 @@ def __init__( app1: "AppAccess", app2: "AppAccess", pdfiledata: Optional[Dict[str, Any]] = None) -> None: + # We only support ELF binaries. Complain loudly if we're not given one + if not app1.elf or not app2.elf: + raise NotImplementedError("XComparison only supports ELF binaries. Was given " + "%s and %s" % (app1._header_ty, app2._header_ty)) self._is_thumb = is_thumb self._path1 = path1 self._path2 = path2 @@ -123,11 +127,11 @@ def newsegments(self) -> List["ELFProgramHeader"]: @property def sectionheaders1(self) -> List["ELFSectionHeader"]: - return cast(List['ELFSectionHeader'], self.app1.header.sectionheaders) + return self.app1.elf_header.sectionheaders @property def sectionheaders2(self) -> List["ELFSectionHeader"]: - return cast(List['ELFSectionHeader'], self.app2.header.sectionheaders) + return self.app2.elf_header.sectionheaders @property def newsections(self) -> List["ELFSectionHeader"]: diff --git a/chb/cmdline/relationalcmds.py b/chb/cmdline/relationalcmds.py index 5da061f7..934889f1 100644 --- a/chb/cmdline/relationalcmds.py +++ b/chb/cmdline/relationalcmds.py @@ -122,9 +122,6 @@ def compare_executable_content( app1 = UC.get_app(path1, xfile1, xinfo1) app2 = UC.get_app(path2, xfile2, xinfo2) - sectionheaders1 = app1.header.sectionheaders - sectionheaders2 = app2.header.sectionheaders - xcomparison = XComparison( is_thumb, path1, @@ -1128,9 +1125,6 @@ def relational_compare_elfdata(args: argparse.Namespace) -> NoReturn: app1 = UC.get_app(path1, xfile1, xinfo1) app2 = UC.get_app(path2, xfile2, xinfo2) - sectionheaders1 = app1.header.sectionheaders - sectionheaders2 = app2.header.sectionheaders - xcomparison = XComparison( False, path1, From 0ada3a05f37ca297a1e72712d3f959c3f00d3126 Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Mon, 26 Oct 2026 09:08:53 -0400 Subject: [PATCH 15/16] TYPES: Enable strict mode in mypy It was the whole point of this set of commits and I forgot to do it :-) --- .github/workflows/run_mypy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_mypy.yaml b/.github/workflows/run_mypy.yaml index 1337ba90..cf267ebe 100644 --- a/.github/workflows/run_mypy.yaml +++ b/.github/workflows/run_mypy.yaml @@ -18,4 +18,4 @@ jobs: run: cp chb/util/ConfigLocal.template chb/util/ConfigLocal.py - name: Run mypy run: | - PYTHONPATH=$PWD .venv/bin/mypy chb + PYTHONPATH=$PWD .venv/bin/mypy --strict chb From caf299f9dad395d1411f079241a429c8ad7fe87e Mon Sep 17 00:00:00 2001 From: Ricardo Baratto Date: Mon, 26 Oct 2026 09:14:16 -0400 Subject: [PATCH 16/16] TYPES: Various fixes from code review - Narrow the mypy error that we're ignoring in chb/app/Cfg.py. As suggested by the overlord, instead of ignoring all type issues we only ignore the error about returning the wrong thing (Incompatible return value type) - Use `1 << shiftamount` instead of `2 << (shiftamount - 1)` in fix for mypy issue with `**` operator (see efbe1aa33c26cad0894374df0898f6676e5098a1 for details). - Fix doc typo --- chb/app/Cfg.py | 2 +- chb/arm/ARMOperandKind.py | 4 ++-- chb/util/fileutil.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chb/app/Cfg.py b/chb/app/Cfg.py index 12a78a93..d108c0eb 100644 --- a/chb/app/Cfg.py +++ b/chb/app/Cfg.py @@ -612,7 +612,7 @@ def switch_case_stmts(succ: str, mb_nextsucc: Optional[str]) -> List[AST.ASTStmt # making ASTStmtLabels into ASTStmts would cause other issues. # This function is also not well understood so we're wary of mucking with # it too much. type: ignore is the best approach for the time being. - return succlabels.get(succ, []) + casebody #type: ignore + return succlabels.get(succ, []) + casebody # type: ignore[return-value] bodystmts: List[AST.ASTStmt] = [] for idx in range(1, len(succs)): diff --git a/chb/arm/ARMOperandKind.py b/chb/arm/ARMOperandKind.py index 8971ccac..d86ec04a 100644 --- a/chb/arm/ARMOperandKind.py +++ b/chb/arm/ARMOperandKind.py @@ -613,10 +613,10 @@ def scale_factor(self) -> Optional[int]: else: if srt.is_shift_left: # For some unknown reason, mypy (2.4.0) complains that we're returning - # Any here instead of [int | None]. This only happens when we use the + # `Any` here instead of [int | None]. This only happens when we use the # `**` operator, so work around it by using `<<` Original code was: # return 2 ** shiftamount - return 2 << (shiftamount - 1) + return 1 << shiftamount else: return None else: diff --git a/chb/util/fileutil.py b/chb/util/fileutil.py index 3708009a..bf09b9fb 100644 --- a/chb/util/fileutil.py +++ b/chb/util/fileutil.py @@ -288,7 +288,7 @@ class FileHeader(ABC): It provides the main entry point to access the raw data in the executable. - The abstract classes enables us to treat different types of binaries (ELF, PE) + The abstract class enables us to treat different types of binaries (ELF, PE) the same way regardless of the underlying details. """ @staticmethod