diff --git a/chb/app/CHVersion.py b/chb/app/CHVersion.py index 31856ce1..7a28da42 100644 --- a/chb/app/CHVersion.py +++ b/chb/app/CHVersion.py @@ -1,3 +1,3 @@ -chbversion: str = "0.3.0-20260802" +chbversion: str = "0.3.0-20260811" minimum_required_chb_version = "0.6.0_20260802" diff --git a/chb/arm/ARMCallOpcode.py b/chb/arm/ARMCallOpcode.py index 777bbdec..0e1b9835 100644 --- a/chb/arm/ARMCallOpcode.py +++ b/chb/arm/ARMCallOpcode.py @@ -49,7 +49,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -297,8 +297,9 @@ def ast_call_prov( if finfo.has_call_target(iaddr): calltarget = finfo.call_target(iaddr) if calltarget.is_unknown: - chklogger.logger.error( - "BL: Indirect call not yet handled at address %s", iaddr) + chklogger.diagnostic( + DC.UNSUPPORTED, + "Indirect call not yet handled at address %s", iaddr) if finfo.has_call_target_info(iaddr): ctinfo = finfo.call_target_info(iaddr) @@ -509,7 +510,8 @@ def ast_call_prov( hl_arg = astree.mk_address_of( astree.mk_vinfo_lval(vinfo)) else: - chklogger.logger.error( + chklogger.diagnostic( + DC.USERDATA, ("Unknown global address %s as call " + "argument at address %s"), hexgaddr, iaddr) diff --git a/chb/arm/ARMOpcode.py b/chb/arm/ARMOpcode.py index 8c6662dd..a053cb10 100644 --- a/chb/arm/ARMOpcode.py +++ b/chb/arm/ARMOpcode.py @@ -52,7 +52,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -440,8 +440,9 @@ def ast_prov( List[AST.ASTInstruction], List[AST.ASTInstruction]]: """Return default; should be overridden by instruction opcodes.""" - chklogger.logger.error( - "no lifting support available for instruction %s at address %s", + chklogger.diagnostic( + DC.UNSUPPORTED, + "No lifting support available for instruction %s at address %s", self.mnemonic, iaddr) instrs = self.ast(astree, iaddr, bytestring, xdata) return (instrs, instrs) @@ -497,8 +498,9 @@ def ast_cc_condition_prov( return (hl_astcond, ll_astcond) else: - chklogger.logger.error( - "No condition found at address %s", iaddr) + chklogger.diagnostic( + DC.UNSUPPORTED, + "Conditional branch without inferred condition %s", iaddr) hl_astcond = astree.mk_temp_lval_expression() return (hl_astcond, ll_astcond) diff --git a/chb/arm/opcodes/ARMAdd.py b/chb/arm/opcodes/ARMAdd.py index b92a8d57..ba34f057 100644 --- a/chb/arm/opcodes/ARMAdd.py +++ b/chb/arm/opcodes/ARMAdd.py @@ -40,7 +40,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -260,7 +260,8 @@ def ast_prov( annotations: List[str] = [iaddr, "ADD"] if xdata.is_aggregate_jumptable: - chklogger.logger.warning( + chklogger.diagnostic( + DC.UNSUPPORTED, "ADD: aggregate jumptable at address %s not yet handled", iaddr) return ([], []) @@ -306,7 +307,8 @@ def has_cast() -> bool: xrhs = xd.result else: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "ADD: Encountered error value for rhs at address %s", iaddr) return ([], [ll_assign]) diff --git a/chb/arm/opcodes/ARMBranch.py b/chb/arm/opcodes/ARMBranch.py index ce275902..1f724627 100644 --- a/chb/arm/opcodes/ARMBranch.py +++ b/chb/arm/opcodes/ARMBranch.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -50,7 +50,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -374,8 +374,9 @@ def default(condition: XXpr) -> AST.ASTExpr: else: condition = xd.txpr else: - chklogger.logger.error( - "Bcc: conditional branch without branch conditions " + chklogger.diagnostic( + DC.UNSUPPORTED, + "Conditional branch without inferred branch condition " + "at address %s", iaddr) hl_astcond = astree.mk_temp_lval_expression() return (hl_astcond, ll_astcond) diff --git a/chb/arm/opcodes/ARMLoadMultipleIncrementAfter.py b/chb/arm/opcodes/ARMLoadMultipleIncrementAfter.py index 92bde135..081f02fa 100644 --- a/chb/arm/opcodes/ARMLoadMultipleIncrementAfter.py +++ b/chb/arm/opcodes/ARMLoadMultipleIncrementAfter.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -40,7 +40,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: from chb.arm.ARMDictionary import ARMDictionary @@ -282,7 +282,8 @@ def ast_prov( rhsexprs = xd.memrhss else: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "LDM: Error value encountered at address %s", iaddr) return ([], []) diff --git a/chb/arm/opcodes/ARMLoadRegister.py b/chb/arm/opcodes/ARMLoadRegister.py index 965f8d3d..aff4ca76 100644 --- a/chb/arm/opcodes/ARMLoadRegister.py +++ b/chb/arm/opcodes/ARMLoadRegister.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -42,7 +42,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -255,7 +255,8 @@ def has_cast() -> bool: lhs = xd.vrt if str(lhs) == "PC": - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "LDR: Indirect call via Load to PC not yet handled at %s", iaddr) return ([], (ll_pre + [ll_assign] + ll_post)) @@ -279,7 +280,8 @@ def has_cast() -> bool: hl_rhs = XU.xmemory_dereference_lval_expr( xaddr, xdata, iaddr, astree) - chklogger.logger.warning( + chklogger.diagnostic( + DC.INTERNAL, "LDR: Unable to use a C expression for rhs. Fall back to " + "native byte-based address: %s to form rhs %s at address %s", str(xaddr), str(hl_rhs), iaddr) diff --git a/chb/arm/opcodes/ARMLoadRegisterByte.py b/chb/arm/opcodes/ARMLoadRegisterByte.py index ee10b223..503b8f4f 100644 --- a/chb/arm/opcodes/ARMLoadRegisterByte.py +++ b/chb/arm/opcodes/ARMLoadRegisterByte.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -42,7 +42,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -278,13 +278,15 @@ def has_cast() -> bool: hl_rhs = XU.xmemory_dereference_lval_expr( xaddr, xdata, iaddr, astree) - chklogger.logger.warning( + chklogger.diagnostic( + DC.INTERNAL, "LDRB: Unable to use a C expression for rhs. Fall back to " + "native byte-based address: %s to form rhs %s at address %s", str(xaddr), str(hl_rhs), iaddr) else: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "LDRB: both memory value and address values are error values " + "at address %s: ", iaddr) return ([], (ll_pre + [ll_assign] + ll_post)) diff --git a/chb/arm/opcodes/ARMLoadRegisterHalfword.py b/chb/arm/opcodes/ARMLoadRegisterHalfword.py index a09991fc..326d3fc7 100644 --- a/chb/arm/opcodes/ARMLoadRegisterHalfword.py +++ b/chb/arm/opcodes/ARMLoadRegisterHalfword.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -42,7 +42,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: from chb.arm.ARMDictionary import ARMDictionary @@ -271,13 +271,15 @@ def has_cast() -> bool: hl_rhs = XU.xmemory_dereference_lval_expr( xaddr, xdata, iaddr, astree) - chklogger.logger.warning( + chklogger.diagnostic( + DC.INTERNAL, "LDRH: Unable to use a C expression for rhs. Fall back to " + "native byte-based address: %s to form rhs %s at address %s", str(xaddr), str(hl_rhs), iaddr) else: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "LDRH: both memory value and address values are error values " + "at address %s: ", iaddr) return ([], (ll_pre + [ll_assign] + ll_post)) diff --git a/chb/arm/opcodes/ARMMove.py b/chb/arm/opcodes/ARMMove.py index 7e154c14..fec2ea30 100644 --- a/chb/arm/opcodes/ARMMove.py +++ b/chb/arm/opcodes/ARMMove.py @@ -41,7 +41,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -518,7 +518,8 @@ def ast_prov_ternary_assign( xd = ARMMoveXData(xdata) if xd.is_nondet_ternary_assign: - chklogger.logger.warning( + chklogger.diagnostic( + DC.INTERNAL, "Ternary assignment without associated predicate at address %s", iaddr) return ([], [ll_assign]) diff --git a/chb/arm/opcodes/ARMPop.py b/chb/arm/opcodes/ARMPop.py index b6613ce8..f38a4fcc 100644 --- a/chb/arm/opcodes/ARMPop.py +++ b/chb/arm/opcodes/ARMPop.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -40,7 +40,7 @@ import chb.invariants.XXprUtil as XU import chb.util.fileutil as UF -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC from chb.util.IndexedTable import IndexedTableValue if TYPE_CHECKING: @@ -236,8 +236,9 @@ def ast_prov( xd = ARMPopXData(xdata) if not xd.is_ok: - chklogger.logger.error( - "Encountered error value at address %s", iaddr) + chklogger.diagnostic( + DC.INTERNAL, + "POP: Encountered error value at address %s", iaddr) return ([], []) splhs = xd.splhs diff --git a/chb/arm/opcodes/ARMStoreMultipleIncrementAfter.py b/chb/arm/opcodes/ARMStoreMultipleIncrementAfter.py index 7d4fa6a1..faf9e040 100644 --- a/chb/arm/opcodes/ARMStoreMultipleIncrementAfter.py +++ b/chb/arm/opcodes/ARMStoreMultipleIncrementAfter.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -40,7 +40,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -511,7 +511,8 @@ def ast_prov( elif xd.are_memlhss_ok: memlhss = xd.memlhss else: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "STM: Error value encountered in LHSs at address %s", iaddr) return ([], []) @@ -520,7 +521,8 @@ def ast_prov( elif xd.are_rrhss_ok: regrhss = xd.rhss else: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "STM: Error value encountered in RHSs at address %s", iaddr) return ([], []) diff --git a/chb/arm/opcodes/ARMStoreMultipleIncrementBefore.py b/chb/arm/opcodes/ARMStoreMultipleIncrementBefore.py index 3e0f4777..de2cf365 100644 --- a/chb/arm/opcodes/ARMStoreMultipleIncrementBefore.py +++ b/chb/arm/opcodes/ARMStoreMultipleIncrementBefore.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -40,7 +40,7 @@ import chb.util.fileutil as UF from chb.util.IndexedTable import IndexedTableValue -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -135,7 +135,8 @@ def ast_prov( xd = ARMStoreMultipleIncrementBeforeXData(xdata) if not xd.is_ok: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "STMIB: Error value encountered at address %s", iaddr) return ([], []) diff --git a/chb/astinterface/ASTIProvenance.py b/chb/astinterface/ASTIProvenance.py index 29a20a56..931ccd27 100644 --- a/chb/astinterface/ASTIProvenance.py +++ b/chb/astinterface/ASTIProvenance.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2022-2025 Aarno Labs LLC +# Copyright (c) 2022-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -32,7 +32,7 @@ from chb.ast.ASTProvenance import ASTProvenance import chb.util.fileutil as UF -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: from chb.invariants.VarInvariantFact import ( @@ -478,7 +478,8 @@ def resolve_reaching_defs(self) -> None: else: # temporarily silence warnings for payload addresses if not addr.startswith("F"): - chklogger.logger.warning( + chklogger.diagnostic( + DC.INTERNAL, "Reaching definition address %s for variable %s " + " not found", str(addr), str(v)) diff --git a/chb/astinterface/ASTInterface.py b/chb/astinterface/ASTInterface.py index 4c3cdb93..165a66b7 100644 --- a/chb/astinterface/ASTInterface.py +++ b/chb/astinterface/ASTInterface.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -66,7 +66,7 @@ FunctionAnnotation, RegisterVarIntro, StackVarIntro) import chb.util.fileutil as UF -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -104,6 +104,7 @@ def __init__( functionannotation: Optional[FunctionAnnotation] = None, stackvarintros: Dict[int, str] = {}, patchevents: Dict[str, "PatchEvent"] = {}, + registersizes: Dict[str, int] = {}, verbose: bool = False) -> None: self._astree = astree self._srcprototype = srcprototype @@ -115,6 +116,7 @@ def __init__( self._stackvarintros = stackvarintros self._patchevents = patchevents self._typconverter = typconverter + self._register_sizes = registersizes self._verbose = verbose self._ctyper = ASTBasicCTyper(astree.globalsymboltable) self._bytesizecalculator = ASTByteSizeCalculator( @@ -192,6 +194,10 @@ def ignoredlhs(self) -> AST.ASTLval: def typconverter(self) -> "BC2ASTConverter": return self._typconverter + @property + def register_sizes(self) -> Dict[str, int]: + return self._register_sizes + @property def verbose(self) -> bool: return self._verbose @@ -1195,7 +1201,14 @@ def mk_ssa_register_variable_lval( ssavalue: Optional[AST.ASTExpr] = None) -> AST.ASTLval: vinfo = self.mk_ssa_register_varinfo( name, iaddr, vtype=vtype, save_loc=True) - storage = self.astree.mk_register_storage(name) + if name in self.register_sizes: + storage = self.astree.mk_register_storage(name) + else: + chklogger.diagnostic( + DC.INTERNAL, + "Analysis generated an unknown register name: %s at address %s", + name, iaddr) + storage = None if ssavalue is not None: self.set_ssa_value(vinfo.vname, ssavalue) return self.astree.mk_vinfo_lval(vinfo, storage=storage) diff --git a/chb/cmdline/astcmds.py b/chb/cmdline/astcmds.py index df4a1292..31ca1c67 100644 --- a/chb/cmdline/astcmds.py +++ b/chb/cmdline/astcmds.py @@ -48,6 +48,7 @@ from chb.ast.ASTReturnSequences import ASTReturnSequences from chb.ast.ASTSerializer import ASTSerializer from chb.ast.ASTSymbolTable import ASTGlobalSymbolTable, ASTLocalSymbolTable +from chb.ast.ASTVariablesReferenced import ASTVariablesReferenced from chb.astinterface.ASTInterface import ASTInterface from chb.astinterface.ASTInterfaceFunction import ASTInterfaceFunction @@ -66,7 +67,7 @@ from chb.util.DotGraph import DotGraph import chb.util.fileutil as UF import chb.util.graphutil as UG -from chb.util.loggingutil import chklogger, LogLevel +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC, LogLevel if TYPE_CHECKING: @@ -286,6 +287,9 @@ def buildast(args: argparse.Namespace) -> NoReturn: functions_lifted: int = 0 functions_failed: int = 0 + functions_clean: int = 0 + functions_typing_gaps: int = 0 + functions_with_diagnostics: int = 0 for faddr in functions: if app.has_function(faddr): @@ -345,6 +349,7 @@ def buildast(args: argparse.Namespace) -> NoReturn: functionannotation=functionannotation, stackvarintros=fstackvarintros, patchevents=patchevents, + registersizes=support.register_sizes, verbose=verbose) # Introduce ssa variables for all reaching definitions referenced in @@ -373,6 +378,8 @@ def buildast(args: argparse.Namespace) -> NoReturn: astfunction = ASTInterfaceFunction( faddr, fname, f, astinterface, patchevents=patchevents) + chklogger.reset_diagnostic_count() + try: asts = astfunction.mk_asts(support) except UF.CHBError as e: @@ -400,6 +407,31 @@ def buildast(args: argparse.Namespace) -> NoReturn: print(prettyprinter.to_c(asts[0], include_globals=(not hide_globals))) functions_lifted += 1 + if chklogger.diagnostic_count > 0: + # Diagnostics were already logged during the creation of the + # lifting, indicating more severe problems. Skip the typing + # check. + functions_with_diagnostics += 1 + else: + referenced = ASTVariablesReferenced().variables_referenced( + asts[0]) + untypedlocals = sorted( + vinfo.vname + for vinfo in localsymboltable.symbols + if (vinfo.vname in referenced + and not localsymboltable.is_formal(vinfo.vname) + and vinfo.vtype is None)) + if untypedlocals: + chklogger.diagnostic( + DC.TYPING, + "%s: %s referenced local variable(s) have " + + "unresolved type: %s", + faddr, str(len(untypedlocals)), + ", ".join(untypedlocals)) + functions_typing_gaps += 1 + else: + functions_clean += 1 + else: print("\nUnable to generate a lifting for " + faddr) functions_failed += 1 @@ -448,7 +480,10 @@ def buildast(args: argparse.Namespace) -> NoReturn: if functions_lifted > 1: UC.print_status_update( - "Successfully lifted " + str(functions_lifted) + " functions") + "Lifted " + str(functions_lifted) + " functions" + + " (" + str(functions_clean) + " clean, " + + str(functions_typing_gaps) + " with unresolved local types, " + + str(functions_with_diagnostics) + " with other diagnostics)") if functions_failed > 0: UC.print_status_update( "Failures: " + str(functions_failed) + " functions") diff --git a/chb/invariants/XXprUtil.py b/chb/invariants/XXprUtil.py index 775cbe9e..328b184d 100644 --- a/chb/invariants/XXprUtil.py +++ b/chb/invariants/XXprUtil.py @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # The MIT License (MIT) # -# Copyright (c) 2021-2025 Aarno Labs LLC +# Copyright (c) 2021-2026 Aarno Labs LLC # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -101,7 +101,7 @@ import chb.invariants.XXpr as X import chb.util.fileutil as UF -from chb.util.loggingutil import chklogger +from chb.util.loggingutil import chklogger, DiagnosticCategory as DC if TYPE_CHECKING: @@ -235,9 +235,16 @@ def xconstant_to_ast_expr( else: if not anonymous: - chklogger.logger.error( - "AST conversion of constant %s not yet supported at address %s", - str(xc), iaddr) + if str(xc) == "false" or str(xc) == "true": + chklogger.diagnostic( + DC.INTERNAL, + "Encountered constant-value boolean %s at address %s", + str(xc), iaddr) + else: + chklogger.diagnostic( + DC.UNSUPPORTED, + "AST conversion of constant %s not yet supported at address %s", + str(xc), iaddr) return astree.mk_temp_lval_expression() @@ -334,7 +341,8 @@ def vreturn_deref_value_to_ast_lval_expression( return astree.mk_memref_expr(vexpr, anonymous=anonymous) else: if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "Non-struct pointer type %s not yet handled at %s", str(vtype), iaddr) return astree.mk_temp_lval_expression() @@ -384,7 +392,8 @@ def field_pointer_to_ast_memref_expr( if not compinfo.has_fields(): if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.TYPING, "Struct definition is missing for %s at address %s (no fields found)", compinfo.compname, iaddr) return astree.mk_temp_lval_expression() @@ -505,7 +514,8 @@ def memory_variable_to_lval_expression( if not astree.globalsymboltable.has_symbol(name): if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "AST conversion of memory variable %s not in global symbol " + "table not yet supported at address %s", name, iaddr) @@ -597,7 +607,8 @@ def stack_variable_to_lval_expression( return astree.mk_vinfo_lval_expression( vinfo, astoffset, anonymous=anonymous) - chklogger.logger.warning( + chklogger.diagnostic( + DC.UNSUPPORTED, "Stack variable offset %s of %s not yet handled at address %s", str(offset.offset), str(vinfo), iaddr) return astree.mk_temp_lval_expression() @@ -671,12 +682,14 @@ def global_variable_to_lval_expression( if not anonymous: if vinfo is None: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "Conversion of global variable with address %s and offset " + "%s at address %s not yet supported", str(hexgaddr), str(offset.offset), iaddr) else: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "Conversion of global variable %s access with offset " + "%s at address %s not yet supported", str(vinfo), str(offset.offset), iaddr) @@ -851,7 +864,8 @@ def stack_argument_to_ast_lval_expression( return astree.mk_lval_expression(arglvals[0], anonymous=anonymous) else: if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "Cannot determine argument index for initial stack argument %s " + "at address %s", str(offset), iaddr) @@ -941,7 +955,8 @@ def xvariable_to_ast_def_lval_expression( and xdata.function.has_var_disequality(iaddr, xvar)): if (not anonymous): - chklogger.logger.warning( + chklogger.diagnostic( + DC.UNSUPPORTED, "AST def conversion of initial memory value %s that may have " + "changed reverted to original variable at %s", str(xvar), str(iaddr)) @@ -1002,14 +1017,16 @@ def xvariable_to_ast_def_lval_expression( vinfo, anonymous=anonymous) else: if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "Rdef: %s has not yet been introduced at address %s", regrdefs[0], iaddr) return astree.mk_temp_lval_expression() if len(regrdefs) == 0: if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.INTERNAL, "No rdefs found for %s at address %s", str(reg), iaddr) return astree.mk_temp_lval_expression() @@ -1084,7 +1101,8 @@ def xvariable_to_ast_def_lval_expression( return astree.mk_lval_expr(stacklval) if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "AST def conversion of variable %s to lval-expression at address " + "%s not yet supported", str(xvar), iaddr) @@ -1177,7 +1195,8 @@ def default() -> AST.ASTExpr: if not axpr2.is_integer_constant: if not anonymous: - chklogger.logger.warning( + chklogger.diagnostic( + DC.INTERNAL, "AST def conversion of pointer expression encountered non-constant " + " addend: %s at address %s", str(axpr2), iaddr) @@ -1187,7 +1206,8 @@ def default() -> AST.ASTExpr: if not (axpr1.is_ast_lval_expr or axpr1.is_ast_addressof): if not anonymous: - chklogger.logger.warning( + chklogger.diagnostic( + DC.INTERNAL, "AST def conversion of pointer expression encountered unexpected " + " base expression %s at address %s", str(axpr1), iaddr) @@ -1198,7 +1218,8 @@ def default() -> AST.ASTExpr: compkey = tgttyp.compkey if not astree.globalsymboltable.has_compinfo(compkey): if not anonymous: - chklogger.logger.warning( + chklogger.diagnostic( + DC.INTERNAL, "AST def conversion of pointer expression encountered unknown " + " compinfo key %d (%s) at address %s", compkey, tgttyp.compname, iaddr) @@ -1208,7 +1229,8 @@ def default() -> AST.ASTExpr: compinfo = astree.globalsymboltable.compinfo(compkey) if not compinfo.has_field_offsets(): if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.TYPING, "No fields are specified for compinfo %s (at address %s)", compinfo.compname, iaddr) return astree.mk_temp_lval_expression() @@ -1228,7 +1250,8 @@ def default() -> AST.ASTExpr: fcompkey = cast(AST.ASTTypComp, field.fieldtype).compkey if not astree.has_compinfo(fcompkey): if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.TYPING, "Encountered field compinfo key without definition in " + "symbol table: %d", compkey) @@ -1237,7 +1260,8 @@ def default() -> AST.ASTExpr: (subfield, subrestoffset) = fcompinfo.field_at_offset(restoffset) if subrestoffset > 0: if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "Second-level rest offset in field-pointer memref " + "address not yet handled for %s at %s with fields: " + "%s and %s and original offset %s: %d", @@ -1308,7 +1332,8 @@ def default() -> AST.ASTExpr: return astree.mk_binary_expression(operator, astxpr1, astxpr2) else: if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "AST def conversion of binary expression %s, %s with operator %s " + "at address %s not yet supported", str(xpr1), str(xpr2), operator, iaddr) @@ -1439,7 +1464,8 @@ def default() -> AST.ASTExpr: if not compinfo.has_fields(): if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.TYPING, "Struct definition is missing for %s at address %s " + "(no fields found)", compinfo.compname, iaddr) @@ -1471,7 +1497,8 @@ def default() -> AST.ASTExpr: if not hl_addr.op == "plus": if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "Address expression %s with operator %s not yet supported at %s", str(xaddr), hl_addr.op, iaddr) return default() @@ -1497,7 +1524,8 @@ def default() -> AST.ASTExpr: if not compinfo.has_fields(): if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.TYPING, "Struct definition is missing for %s at address %s " + "(no fields found)", compinfo.compname, iaddr) @@ -1624,7 +1652,8 @@ def stack_variable_to_ast_lval( offset.offsetvalue(), vtype=ctype) if not anonymous: - chklogger.logger.error( + chklogger.diagnostic( + DC.UNSUPPORTED, "Stack variable with size %d not yet supported at addresss %s", size, iaddr) return astree.mk_temp_lval() @@ -1632,7 +1661,8 @@ def stack_variable_to_ast_lval( if offset.is_constant_offset: stackoffset = offset.offsetconstant if not stackoffset in astree.stack_varinfos: - chklogger.logger.warning( + chklogger.diagnostic( + DC.UNSUPPORTED, "No stack varinfo found at offset %s at address %s", str(stackoffset), iaddr) return astree.mk_temp_lval() @@ -1651,7 +1681,8 @@ def stack_variable_to_ast_lval( return astree.mk_vinfo_lval(vinfo, offset=astoffset, anonymous=anonymous) if not anonymous: - chklogger.logger.warning( + chklogger.diagnostic( + DC.UNSUPPORTED, "Stack variable with offset %s not yet supported at address %s", str(stackoffset), iaddr) return astree.mk_temp_lval() diff --git a/chb/util/loggingutil.py b/chb/util/loggingutil.py index 163f8ed3..c259c642 100644 --- a/chb/util/loggingutil.py +++ b/chb/util/loggingutil.py @@ -52,16 +52,82 @@ def options(cls) -> List[str]: return [x.value for x in cls] + ["NONE"] +class DiagnosticCategory(str, Enum): + """Classification of diagnostics for logging messages. + + Addition of a separate category to logging messages to + enable downstream users to take appropriate action. + + USERDATA: data provided by the user via user data is + incorrect or incomplete. This should be fixed by + the user. + + TYPING: data provided by the user via header files or + typing information provided in function annotations + in the userdata is inconsistent or incomplete. This + can often be fixed by the user. + + UNSUPPORTED: a known gap in analyzer support: an + unimplemented instruction, expression form, or + statement kind. Not fixable by the user: the function + cannot currently be processed; it requires an extension + to the analyzer. + + INTERNAL: an internal invariant was violated: analysis reached + a program point without a fact (e.g., a reaching definition, + or a non-error value) that the surrounding code assumed would + be present. Not fixable by the user; indicates a possible + defect in the analyzer rather than a known missing feature, + and should be fixed by CodeHawk maintainers. + """ + + USERDATA = "USERDATA" + TYPING = "TYPING" + UNSUPPORTED = "UNSUPPORTED" + INTERNAL = "INTERNAL" + + class CHKLogger: def __init__(self) -> None: self._logger = logging.getLogger("silent") self._logger.addHandler(logging.NullHandler()) + self._diagnostic_count = 0 @property def logger(self) -> logging.Logger: return self._logger + @property + def diagnostic_count(self) -> int: + """Number of diagnostics logged since the last reset_diagnostic_count(). + """ + + return self._diagnostic_count + + def reset_diagnostic_count(self) -> None: + self._diagnostic_count = 0 + + def diagnostic( + self, + category: DiagnosticCategory, + msg: str, + *args: object) -> None: + """Log a diagnostic tagged with its category. + + The category is emitted as the first token of the message + (e.g., UNSUPPORTED: no lifting support available for instruction ...). + + stacklevel=2 attributes the log record's %(module)s:%(lineno)d to + the caller of diagnostic(), not to this method -- without it, every + diagnostic would report its location as loggingutil: + instead of the actual call site (e.g. XXprUtil:1005), which is what + the [module:lineno] suffix exists to identify. + """ + self._diagnostic_count += 1 + self._logger.error( + category.value + ": " + msg, *args, stacklevel=2) + def set_chkx_logger( self, initmsg: str = "",