diff --git a/chc/app/CAttributes.py b/chc/app/CAttributes.py
index 2a72e62..891b406 100644
--- a/chc/app/CAttributes.py
+++ b/chc/app/CAttributes.py
@@ -6,7 +6,7 @@
#
# Copyright (c) 2017-2020 Kestrel Technology LLC
# Copyright (c) 2020-2022 Henny B. Sipma
-# Copyright (c) 2023-2024 Aarno Labs LLC
+# Copyright (c) 2023-2025 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,6 +40,7 @@
from chc.app.CDictionary import CDictionary
from chc.app.CTyp import CTyp
from chc.app.CTypsig import CTypsig
+ from chc.app.CVisitor import CVisitor
class CAttr(CDictionaryRecord):
@@ -557,5 +558,8 @@ def attributes(self) -> List[CAttribute]:
def length(self) -> int:
return len(self.attributes)
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_attributes(self)
+
def __str__(self) -> str:
return ",".join([str(p) for p in self.attributes])
diff --git a/chc/app/CExp.py b/chc/app/CExp.py
index 6c26089..3c0a3ee 100644
--- a/chc/app/CExp.py
+++ b/chc/app/CExp.py
@@ -6,7 +6,7 @@
#
# Copyright (c) 2017-2020 Kestrel Technology LLC
# Copyright (c) 2020-2022 Henny B. Sipma
-# Copyright (c) 2023-2024 Aarno Labs LLC
+# Copyright (c) 2023-2025 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
@@ -39,6 +39,7 @@
from chc.app.CConst import CConst
from chc.app.CTyp import CTyp
from chc.app.CLval import CLval
+ from chc.app.CVisitor import CVisitor
binoperatorstrings = {
@@ -147,6 +148,9 @@ def get_strings(self) -> List[str]:
def get_variable_uses(self, vid: int) -> int:
return 0
+ def accept(self, visitor: "CVisitor") -> None:
+ raise Exception("CExp.accept: " + str(self))
+
def to_dict(self) -> Dict[str, Any]:
return {"base": "exp"}
@@ -179,6 +183,9 @@ def constant(self) -> "CConst":
def get_strings(self) -> List[str]:
return self.constant.get_strings()
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_constexp(self)
+
def to_dict(self) -> Dict[str, Any]:
return {"base": "const", "value": str(self.constant)}
diff --git a/chc/app/CFile.py b/chc/app/CFile.py
index b154cb9..4a4e0a0 100644
--- a/chc/app/CFile.py
+++ b/chc/app/CFile.py
@@ -6,7 +6,7 @@
#
# Copyright (c) 2017-2020 Kestrel Technology LLC
# Copyright (c) 2020-2022 Henny B. Sipma
-# Copyright (c) 2023-2024 Aarno Labs LLC
+# Copyright (c) 2023-2025 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
@@ -51,6 +51,7 @@
from chc.app.CFileGlobals import CGVarDecl
from chc.app.CFileGlobals import CGVarDef
from chc.app.CGXrefs import CGXrefs
+from chc.app.CPrettyPrinter import CPrettyPrinter
from chc.proof.CFilePredicateDictionary import CFilePredicateDictionary
from chc.proof.CFunctionPO import CFunctionPO
@@ -183,6 +184,36 @@ def cfileglobals(self) -> CFileGlobals:
def gfunctions(self) -> Dict[int, "CGFunction"]:
return self.cfileglobals.gfunctions
+ def header_declarations(
+ self,
+ gvarnames: Dict[str, str] = {},
+ fnnames: Dict[str, str] = {}) -> str:
+ lines: List[str] = []
+ srcfilename = self.name + ".c"
+ lines.append("// " + srcfilename + "\n")
+ gvars = self.gvardefs.values()
+ if len(gvars) > 0:
+ lines.append("// static and global variable definitions\n")
+ for gvar in gvars:
+ pp = CPrettyPrinter()
+ binloc: Optional[str] = gvarnames.get(gvar.varinfo.vname)
+ gvardef = pp.gvar_definition_str(
+ gvar.varinfo, srcloc=srcfilename, binloc=binloc)
+ lines.append(gvardef)
+
+ fns = self.gfunctions.values()
+ if len(gvars) > 0 and len(fns) > 0:
+ lines.append("// function signatures\n")
+ for fn in fns:
+ if fn.is_system_function:
+ continue
+ pp = CPrettyPrinter()
+ binloc = fnnames.get(fn.varinfo.vname)
+ fndecl = pp.function_declaration_str(
+ fn.varinfo, srcloc=srcfilename, binloc=binloc)
+ lines.append(fndecl)
+ return "\n".join(lines)
+
@property
def functioncount(self) -> int:
return self.cfileglobals.functioncount
diff --git a/chc/app/CFileGlobals.py b/chc/app/CFileGlobals.py
index c352ae2..a908803 100644
--- a/chc/app/CFileGlobals.py
+++ b/chc/app/CFileGlobals.py
@@ -6,7 +6,7 @@
#
# Copyright (c) 2017-2020 Kestrel Technology LLC
# Copyright (c) 2020-2023 Henny B. Sipma
-# Copyright (c) 2024 Aarno Labs LLC
+# Copyright (c) 2024-2025 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
@@ -105,6 +105,11 @@ def vtype(self) -> "CTyp":
def line(self) -> int:
return self.varinfo.line
+ @property
+ def is_system_function(self) -> bool:
+ vdecl = self.varinfo.vdecl
+ return vdecl is None or vdecl.file.startswith("/")
+
def __str__(self) -> str:
return f"{self.vname}: {self.vtype}"
diff --git a/chc/app/CPrettyPrinter.py b/chc/app/CPrettyPrinter.py
new file mode 100644
index 0000000..d74f4df
--- /dev/null
+++ b/chc/app/CPrettyPrinter.py
@@ -0,0 +1,367 @@
+# ------------------------------------------------------------------------------
+# CodeHawk C Analyzer
+# Author: Henny Sipma
+# ------------------------------------------------------------------------------
+# The MIT License (MIT)
+#
+# Copyright (c) 2025 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
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ------------------------------------------------------------------------------
+
+from typing import cast, List, Optional, TYPE_CHECKING
+
+import chc.app.CAttributes as CA
+import chc.app.CExp as CE
+import chc.app.CTyp as CT
+from chc.app.CVarInfo import CVarInfo
+
+from chc.app.CVisitor import CVisitor
+
+
+integernames = {
+ "ichar": "char",
+ "ischar": "signed char",
+ "iuchar": "unsigned char",
+ "ibool": "bool",
+ "iint": "int",
+ "iuint": "unsigned int",
+ "ishort": "short",
+ "iushort": "unsigned short",
+ "ilong": "long",
+ "iulong": "unsigned long",
+ "ilonglong": "long long",
+ "iulonglong": "unsigned long long",
+}
+
+floatnames = {"float": "float", "fdouble": "double", "flongdouble": "long double"}
+
+
+class CPrettyCode:
+
+ def __init__(self) -> None:
+ self._outputlines: List[str] = []
+ self._pos: int = 0
+
+ @property
+ def outputlines(self) -> List[str]:
+ return self._outputlines
+
+ @property
+ def pos(self) -> int:
+ return self._pos
+
+ def newline(self, indent: int = 0) -> None:
+ self._outputlines.append(" " * indent)
+ self._pos = indent
+
+ def write(self, s: str) -> None:
+ self._outputlines[-1] += s
+ self._pos += len(s)
+
+ def __str__(self) -> str:
+ return "\n".join(self.outputlines)
+
+
+class CPrettyPrinter(CVisitor):
+
+ def __init__(self, indentation: int = 2) -> None:
+ self._indentation = indentation
+ self._indent = 0
+ self._ccode = CPrettyCode()
+
+ @property
+ def indentation(self) -> int:
+ return self._indentation
+
+ @property
+ def indent(self) -> int:
+ return self._indent
+
+ @property
+ def ccode(self) -> CPrettyCode:
+ return self._ccode
+
+ def increase_indent(self) -> None:
+ self._indent += self.indentation
+
+ def decrease_indent(self) -> None:
+ self._indent -= self.indentation
+
+ def visit_voidtyp(self, t: CT.CTypVoid) -> None:
+ t.attributes.accept(self)
+ self.ccode.write("void")
+
+ def visit_inttyp(self, t: CT.CTypInt) -> None:
+ t.attributes.accept(self)
+ self.ccode.write(integernames[t.ikind])
+
+ def visit_floattyp(self, t: CT.CTypFloat) -> None:
+ t.attributes.accept(self)
+ self.ccode.write(str(t))
+
+ def visit_namedtyp(self, t: CT.CTypNamed) -> None:
+ t.attributes.accept(self)
+ t.expand().accept(self)
+
+ def visit_comptyp(self, t: CT.CTypComp) -> None:
+ t.attributes.accept(self)
+ if t.is_comp:
+ self.ccode.write("struct " + self.de_anon_name(t.name))
+ else:
+ self.ccode.write("union " + self.de_anon_name(t.name))
+
+ def de_anon_name(self, name: str) -> str:
+ if name.startswith("__anonstruct_") and len(name) > 24:
+ return name[13:-10] + "st"
+ else:
+ return name
+
+ def visit_enumtyp(self, t: CT.CTypEnum) -> None:
+ t.attributes.accept(self)
+ self.ccode.write("enum " + t.name)
+
+ def visit_builtinvaargs(self, t: CT.CTypBuiltinVaargs) -> None:
+ pass
+
+ def visit_ptrtyp(self, t: CT.CTypPtr) -> None:
+ t.attributes.accept(self)
+ t.pointedto_type.accept(self)
+ self.ccode.write(" *")
+
+ def visit_arraytyp(self, t: CT.CTypArray) -> None:
+ t.array_basetype.accept(self)
+ self.ccode.write("[")
+ if t.array_size_expr is not None:
+ # self.ccode.write("?")
+ t.array_size_expr.accept(self)
+ self.ccode.write("]")
+
+ def visit_funtyp(self, t: CT.CTypFun) -> None:
+ """Emits a function type without name."""
+
+ t.return_type.accept(self)
+ self.ccode.write(" (")
+ if t.funargs is not None:
+ t.funargs.accept(self)
+ self.ccode.write(")")
+
+ def write_chkx_srcloc_attribute(
+ self, srcfile: str, line: int, binloc: Optional[str]) -> None:
+ self.ccode.newline(indent=3)
+ self.ccode.write("__attribute__ (( chkc_srcloc(")
+ self.ccode.write('"')
+ self.ccode.write(srcfile)
+ self.ccode.write('"')
+ self.ccode.write(",")
+ self.ccode.write(str(line))
+ self.ccode.write(")")
+ if binloc is not None:
+ self.ccode.write(", chkx_binloc(")
+ self.ccode.write('"')
+ self.ccode.write(binloc)
+ self.ccode.write('"')
+ self.ccode.write(")")
+ self.ccode.write(" ))")
+
+ def gvar_definition_str(
+ self,
+ vinfo: CVarInfo,
+ srcloc: Optional[str] = None,
+ binloc: Optional[str] = None) -> str:
+ if vinfo.vtype.is_function:
+ return "error"
+
+ if vinfo.vtype.is_array:
+ sizexps: List[Optional[CE.CExp]] = []
+ self.ccode.newline()
+ atype = cast(CT.CTypArray, vinfo.vtype)
+ sizexps.append(atype.array_size_expr)
+ basetype = atype.array_basetype
+ while basetype.is_array:
+ atype = cast(CT.CTypArray, basetype)
+ sizexps.append(atype.array_size_expr)
+ basetype = atype.array_basetype
+ basetype.accept(self)
+ self.ccode.write(" ")
+ self.ccode.write(vinfo.vname)
+ for s in sizexps:
+ self.ccode.write("[")
+ if s:
+ s.accept(self)
+ self.ccode.write("]")
+ self.ccode.write(";")
+ self.ccode.newline()
+ return str(self.ccode)
+
+ self.ccode.newline()
+ vinfo.vtype.accept(self)
+ self.ccode.write(" ")
+ self.ccode.write(vinfo.vname)
+ if srcloc is not None:
+ self.write_chkx_srcloc_attribute(srcloc, vinfo.line, binloc)
+ self.ccode.write(";")
+ self.ccode.newline()
+ return str(self.ccode)
+
+ def function_declaration_str(
+ self,
+ vinfo: CVarInfo,
+ srcloc: Optional[str] = None,
+ binloc: Optional[str] = None) -> str:
+ if not vinfo.vtype.is_function:
+ return "error"
+
+ self.ccode.newline()
+ ftype = cast(CT.CTypFun, vinfo.vtype)
+ if ftype.return_type.is_function_pointer:
+ returntyp = cast(CT.CTypPtr, ftype.return_type)
+ retfntyp = cast(CT.CTypFun, returntyp.pointedto_type)
+ retfntyp.return_type.accept(self)
+ self.ccode.write(" (*")
+ self.ccode.write(vinfo.vname)
+ self.ccode.write("(")
+ if ftype.funargs is not None:
+ ftype.funargs.accept(self)
+ self.ccode.write("))(")
+ if retfntyp.funargs is not None:
+ retfntyp.funargs.accept(self)
+ self.ccode.write(")")
+ if srcloc is not None:
+ self.write_chkx_srcloc_attribute(srcloc, vinfo.line, binloc)
+ self.ccode.write(";")
+ self.ccode.newline()
+ return str(self.ccode)
+
+ else:
+ ftype.return_type.accept(self)
+ self.ccode.write(" ")
+ self.ccode.write(vinfo.vname)
+ self.ccode.write("(")
+ if ftype.funargs is not None:
+ ftype.funargs.accept(self)
+ if ftype.is_vararg:
+ self.ccode.write(", ...")
+ self.ccode.write(")")
+ if srcloc is not None:
+ self.write_chkx_srcloc_attribute(srcloc, vinfo.line, binloc)
+ self.ccode.write(";")
+ self.ccode.newline()
+ return str(self.ccode)
+
+ def funtypptr_with_name(self, t: CT.CTypFun, name: str) -> None:
+ if t.return_type.is_function_pointer:
+ returntyp = cast(CT.CTypPtr, t.return_type)
+ retty = cast(CT.CTypFun, returntyp.pointedto_type)
+ self.funtypptr_with_name(retty, name)
+ self.ccode.write("(")
+ if t.funargs is not None:
+ t.funargs.accept(self)
+ self.ccode.write(")")
+ else:
+ t.return_type.accept(self)
+ self.ccode.write(" (*")
+ self.ccode.write(name)
+ self.ccode.write(")(")
+ if t.funargs is not None:
+ t.funargs.accept(self)
+ self.ccode.write(")")
+
+ def visit_funargs(self, funargs: CT.CFunArgs) -> None:
+ args = funargs.arguments
+ if len(args) == 0:
+ self.ccode.write("void")
+ else:
+ for arg in args[:-1]:
+ arg.accept(self)
+ self.ccode.write(", ")
+ args[-1].accept(self)
+
+ def visit_funarg(self, funarg: CT.CFunArg) -> None:
+ if funarg.typ.is_function_pointer:
+ argtyp = cast(CT.CTypPtr, funarg.typ)
+ fnptr = cast(CT.CTypFun, argtyp.pointedto_type)
+ self.funtypptr_with_name(fnptr, funarg.name)
+ return
+
+ if funarg.typ.is_pointer:
+ argtyp = cast(CT.CTypPtr, funarg.typ)
+ ptargtyp = argtyp.pointedto_type
+ if ptargtyp.is_named_type:
+ ptx = cast(CT.CTypNamed, ptargtyp).expand()
+ if ptx.is_function:
+ ptx = cast(CT.CTypFun, ptx)
+ self.funtypptr_with_name(ptx, funarg.name)
+ return
+
+ if (
+ argtyp.attributes.length > 0
+ and argtyp.attributes.attributes[0].name == "arraylen"):
+ self.ptrarg_with_attribute_length(
+ ptargtyp, argtyp.attributes.attributes[0], funarg.name)
+ return
+
+ funarg.typ.accept(self)
+ self.ccode.write(" ")
+ self.ccode.write(funarg.name)
+
+ def ptrarg_with_attribute_length(
+ self, t: CT.CTyp, a: CA.CAttribute, name: str) -> None:
+ """retrieve arraylength from arraylen attribute.
+
+ In C arrays passed as arguments to functions become pointers,
+ that is, the function call:
+
+ f(int a[20]);
+
+ is compiled into
+
+ f(int *a);
+
+ It appears that CIL in this case records the length in an
+ attribute with the name arraylen.
+ """
+ if not a.name == "arraylen":
+ raise Exception("Not an arraylen attribute")
+ if not (len(a.params) == 1):
+ raise Exception("Expected one argument for arraylen")
+ aparam = a.params[0]
+ if not aparam.is_int:
+ raise Exception("Expected an integer argument for arraylen")
+ aparam = cast(CA.CAttrInt, aparam)
+
+ t.accept(self)
+ self.ccode.write(" ")
+ self.ccode.write(name)
+ self.ccode.write("[")
+ self.ccode.write(str(aparam.intvalue))
+ self.ccode.write("]")
+
+ def visit_attributes(self, a: CA.CAttributes) -> None:
+ if a.length == 0:
+ return
+ if a.attributes[0].name in ["const", "pconst"]:
+ self.ccode.write("const ")
+ elif a.attributes[0].name == "volatile":
+ self.ccode.write("volatile ")
+ else:
+ self.ccode.write("[[" + a.attributes[0].name + " not yet supported]]")
+
+ def visit_constexp(self, e: CE.CExpConst) -> None:
+ self.ccode.write(str(e))
diff --git a/chc/app/CTyp.py b/chc/app/CTyp.py
index a5906da..3c9e666 100644
--- a/chc/app/CTyp.py
+++ b/chc/app/CTyp.py
@@ -6,7 +6,7 @@
#
# Copyright (c) 2017-2020 Kestrel Technology LLC
# Copyright (c) 2020-2022 Henny B. Sipma
-# Copyright (c) 2023-2024 Aarno Labs LLC
+# Copyright (c) 2023-2025 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
@@ -44,6 +44,7 @@
from chc.app.CAttributes import CAttributes
from chc.app.CCompInfo import CCompInfo
from chc.app.CConst import CConst, CConstInt
+ from chc.app.CVisitor import CVisitor
integernames = {
@@ -163,6 +164,10 @@ def is_float(self) -> bool:
def is_function(self) -> bool:
return False
+ @property
+ def is_function_pointer(self) -> bool:
+ return False
+
@property
def is_int(self) -> bool:
return False
@@ -187,6 +192,9 @@ def is_void(self) -> bool:
def is_default_function_prototype(self) -> bool:
return False
+ def accept(self, visitor: "CVisitor") -> None:
+ raise Exception("CTyp.accept: " + str(self))
+
def writexml(self, cnode: ET.Element) -> None:
cnode.set("ix", str(self.index))
cnode.set("tags", ",".join(self.tags))
@@ -216,6 +224,9 @@ def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
def is_void(self) -> bool:
return True
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_voidtyp(self)
+
def get_opaque_type(self) -> CTyp:
return self
@@ -242,6 +253,9 @@ def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
def is_int(self) -> bool:
return True
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_inttyp(self)
+
@property
def size(self) -> int:
k = self.ikind
@@ -280,6 +294,9 @@ def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
def is_float(self) -> bool:
return True
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_floattyp(self)
+
@property
def fkind(self) -> str:
return self.tags[1]
@@ -317,6 +334,9 @@ def name(self) -> str:
def expand(self) -> CTyp:
return self.cd.decls.expand(self.name)
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_namedtyp(self)
+
@property
def size(self) -> int:
return self.expand().size
@@ -368,6 +388,9 @@ def name(self) -> str:
def is_struct(self) -> bool:
return self.compinfo.is_struct
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_comptyp(self)
+
@property
def size(self) -> int:
return self.compinfo.size
@@ -419,6 +442,9 @@ def size(self) -> int:
def is_enum(self) -> bool:
return True
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_enumtyp(self)
+
def get_opaque_type(self) -> CTyp:
tags = ["tint", "iint"]
args: List[int] = []
@@ -446,6 +472,9 @@ def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
def is_builtin_vaargs(self) -> bool:
return True
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_builtinvaargs(self)
+
def get_opaque_type(self) -> CTyp:
return self
@@ -479,6 +508,13 @@ def size(self) -> int:
def is_pointer(self) -> bool:
return True
+ @property
+ def is_function_pointer(self) -> bool:
+ return self.pointedto_type.is_function
+
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_ptrtyp(self)
+
def get_opaque_type(self) -> CTyp:
tgttype = self.pointedto_type.get_opaque_type()
tags = ["tptr"]
@@ -536,6 +572,9 @@ def size(self) -> int:
def is_array(self) -> bool:
return True
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_arraytyp(self)
+
def get_opaque_type(self) -> CTyp:
tags = ["tvoid"]
args: List[int] = []
@@ -582,6 +621,9 @@ def size(self) -> int:
def is_function(self) -> bool:
return True
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_funtyp(self)
+
def get_opaque_type(self) -> CTyp:
tags = ["tvoid"]
args: List[int] = []
@@ -652,6 +694,9 @@ def name(self) -> str:
def typ(self) -> CTyp:
return self.cd.get_typ(self.args[0])
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_funarg(self)
+
def to_dict(self) -> Dict[str, Any]:
return self.typ.to_dict()
@@ -672,6 +717,9 @@ def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
def arguments(self) -> List[CFunArg]:
return [self.cd.get_funarg(i) for i in self.args]
+ def accept(self, visitor: "CVisitor") -> None:
+ visitor.visit_funargs(self)
+
def to_dict(self) -> List[Dict[str, Any]]:
return [a.to_dict() for a in self.arguments]
diff --git a/chc/app/CVisitor.py b/chc/app/CVisitor.py
new file mode 100644
index 0000000..0844c5d
--- /dev/null
+++ b/chc/app/CVisitor.py
@@ -0,0 +1,96 @@
+# ------------------------------------------------------------------------------
+# CodeHawk C Analyzer
+# Author: Henny Sipma
+# ------------------------------------------------------------------------------
+# The MIT License (MIT)
+#
+# Copyright (c) 2025 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
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# ------------------------------------------------------------------------------
+
+
+from abc import ABC, abstractmethod
+
+import chc.app.CAttributes as CA
+import chc.app.CExp as CE
+import chc.app.CTyp as CT
+from chc.app.CVarInfo import CVarInfo
+
+
+class CVisitor(ABC):
+
+ def __init__(self) -> None:
+ pass
+
+ @abstractmethod
+ def visit_voidtyp(self, ctyp: CT.CTypVoid) -> None:
+ ...
+
+ @abstractmethod
+ def visit_inttyp(self, ctyp: CT.CTypInt) -> None:
+ ...
+
+ @abstractmethod
+ def visit_floattyp(self, ctyp: CT.CTypFloat) -> None:
+ ...
+
+ @abstractmethod
+ def visit_namedtyp(self, ctyp: CT.CTypNamed) -> None:
+ ...
+
+ @abstractmethod
+ def visit_comptyp(self, ctyp: CT.CTypComp) -> None:
+ ...
+
+ @abstractmethod
+ def visit_enumtyp(self, ctyp: CT.CTypEnum) -> None:
+ ...
+
+ @abstractmethod
+ def visit_builtinvaargs(self, ctyp: CT.CTypBuiltinVaargs) -> None:
+ ...
+
+ @abstractmethod
+ def visit_ptrtyp(self, ctyp: CT.CTypPtr) -> None:
+ ...
+
+ @abstractmethod
+ def visit_arraytyp(self, ctyp: CT.CTypArray) -> None:
+ ...
+
+ @abstractmethod
+ def visit_funtyp(self, ctyp: CT.CTypFun) -> None:
+ ...
+
+ @abstractmethod
+ def visit_funarg(self, ctyp: CT.CFunArg) -> None:
+ ...
+
+ @abstractmethod
+ def visit_funargs(self, ctyp: CT.CFunArgs) -> None:
+ ...
+
+ @abstractmethod
+ def visit_attributes(self, a: CA.CAttributes) -> None:
+ ...
+
+ @abstractmethod
+ def visit_constexp(self, a: CE.CExpConst) -> None:
+ ...
diff --git a/chc/cmdline/c_file/cfileutil.py b/chc/cmdline/c_file/cfileutil.py
index 0253fdc..abdc97c 100644
--- a/chc/cmdline/c_file/cfileutil.py
+++ b/chc/cmdline/c_file/cfileutil.py
@@ -4,7 +4,7 @@
# ------------------------------------------------------------------------------
# The MIT License (MIT)
#
-# Copyright (c) 2023-2024 Aarno Labs, LLC
+# Copyright (c) 2023-2025 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
@@ -174,6 +174,80 @@ def cfile_parse_file(args: argparse.Namespace) -> NoReturn:
exit(0)
+def cfile_mk_headerfile(args: argparse.Namespace) -> NoReturn:
+ """Produces a .c file with prototype definitions of functions and global variables.
+
+ This command uses the results of the parse command to create a .c file
+ that contains prototypes of all function definitions and the definitions
+ of all global variables.
+ """
+
+ # arguments
+ xcfilename: str = args.filename
+ opttgtpath: Optional[str] = args.tgtpath
+ loglevel: str = args.loglevel
+ logfilename: Optional[str] = args.logfilename
+ logfilemode: str = args.logfilemode
+ xoutput: Optional[str] = args.output
+
+ projectpath = os.path.dirname(os.path.abspath(xcfilename))
+ targetpath = projectpath if opttgtpath is None else opttgtpath
+ targetpath = os.path.abspath(targetpath)
+ cfilename_ext = os.path.basename(xcfilename)
+ cfilename = cfilename_ext[:-2]
+ projectname = cfilename
+
+ set_logging(
+ loglevel,
+ targetpath,
+ logfilename=logfilename,
+ mode=logfilemode,
+ msg="Command cfile mk-headerfile was invoked")
+
+ chklogger.logger.info(
+ "Project path: %s; target path: %s", projectpath, targetpath)
+
+ parsearchive = UF.get_parse_archive(targetpath, projectname)
+
+ if not os.path.isfile(parsearchive):
+ print_error("Please run parser first on c file")
+ exit(1)
+
+ if os.path.isfile(parsearchive):
+ chklogger.logger.info("Directory is changed to %s", targetpath)
+ os.chdir(targetpath)
+ tarname = os.path.basename(parsearchive)
+ cmd = ["tar", "xfz", os.path.basename(tarname)]
+ chklogger.logger.info("Semantics is extracted from %s", tarname)
+ result = subprocess.call(cmd, cwd=targetpath, stderr=subprocess.STDOUT)
+ if result != 0:
+ print_error("Error in extracting " + tarname)
+ exit(1)
+ chklogger.logger.info(
+ "Semantics was successfully extracted from %s", tarname)
+
+ contractpath = os.path.join(targetpath, "chc_contracts")
+
+ capp = CApplication(
+ projectpath, projectname, targetpath, contractpath, singlefile=True)
+
+ capp.initialize_single_file(cfilename)
+ cfile = capp.get_cfile()
+
+ fheader = cfile.header_declarations()
+
+ if xoutput is not None:
+ outputfilename = xoutput + ".c"
+ with open(outputfilename, "w") as fp:
+ fp.write(fheader)
+ print("Header data written to " + outputfilename)
+
+ else:
+ print(fheader)
+
+ exit(0)
+
+
def cfile_analyze_file(args: argparse.Namespace) -> NoReturn:
"""Analyzes a single c-file and saves the results in the .cch directory.
diff --git a/chc/cmdline/c_project/cprojectutil.py b/chc/cmdline/c_project/cprojectutil.py
index c474f17..d134fc2 100644
--- a/chc/cmdline/c_project/cprojectutil.py
+++ b/chc/cmdline/c_project/cprojectutil.py
@@ -161,6 +161,86 @@ def cproject_parse_project(args: argparse.Namespace) -> NoReturn:
exit(0)
+def cproject_mk_headerfile(args: argparse.Namespace) -> NoReturn:
+
+ # arguments
+ tgtpath: str = args.tgtpath
+ projectname: str = args.projectname
+ userdatafile: Optional[str] = args.userdata
+ includepaths: List[str] = args.includepaths
+ includedirs: List[str] = args.includedirs
+ excludepaths: List[str] = args.excludepaths
+ loglevel: str = args.loglevel
+ logfilename: Optional[str] = args.logfilename
+ logfilemode: str = args.logfilemode
+
+ if not os.path.isdir(tgtpath):
+ print_error(f"Target directory {tgtpath} not found")
+ exit(1)
+
+ if userdatafile and not os.path.isfile(userdatafile):
+ print_error(f"Userdata file {userdatafile} not found")
+ exit(1)
+
+ targetpath = os.path.abspath(tgtpath)
+ projectpath = targetpath
+ contractpath = os.path.join(projectpath, "cch_contracts")
+
+ if userdatafile:
+ with open(userdatafile, "r") as fp:
+ userdata = json.load(fp).get("userdata", {})
+ else:
+ userdata = {}
+
+ fnnames: Dict[str, str] = userdata.get("function-names", {})
+ gvarnames: Dict[str, str] = userdata.get("symbolic-addresses", {})
+
+ fnnames = {v: k for (k, v) in fnnames.items()}
+ gvarnames = {v: k for (k, v) in gvarnames.items()}
+
+ set_logging(
+ loglevel,
+ targetpath,
+ logfilename=logfilename,
+ mode=logfilemode,
+ msg="c-project mk-headerfile invoked")
+
+ capp = CApplication(
+ projectpath, projectname, targetpath, contractpath)
+
+ if not UF.has_analysisresults_path(targetpath, projectname):
+ print_error(
+ f"No analysis results found for {projectname} in {targetpath}")
+ exit(1)
+
+ capp = CApplication(
+ projectpath, projectname, targetpath, contractpath)
+
+ lines: List[str] = []
+
+ def is_included(path: Optional[str]) -> bool:
+ if path is None:
+ return True
+ if len(includepaths) > 0:
+ return any(path.startswith(p) for p in includepaths)
+ elif len(includedirs) > 0:
+ return path in includedirs
+ elif len(excludepaths) > 0:
+ return not (any(path.startswith(p) for p in excludepaths))
+ else:
+ return True
+
+ for cfile in capp.cfiles:
+ if not is_included(cfile.cfilepath):
+ continue
+ fheader = cfile.header_declarations(gvarnames, fnnames)
+ lines.append(fheader)
+
+ print("\n\n".join(lines))
+
+ exit(0)
+
+
def cproject_analyze_project(args: argparse.Namespace) -> NoReturn:
# arguments
@@ -637,3 +717,30 @@ def get_fi_missing_summaries(cfile: "CFile") -> None:
print("\n".join(lines))
exit(0)
+
+
+def cproject_create_header_file(args: argparse.Namespace) -> NoReturn:
+
+ # arguments
+ tgtpath: str = args.tgtpath
+ projectname: str = args.projectname
+
+ targetpath = os.path.abspath(tgtpath)
+ projectpath = targetpath
+ contractpath = os.path.join(targetpath, "chc_contracts")
+
+ if not UF.has_analysisresults_path(targetpath, projectname):
+ print_error(
+ f"No analysis results found for {projectname} in {targetpath}")
+ exit(1)
+
+ capp = CApplication(
+ projectpath, projectname, targetpath, contractpath)
+
+ for cfile in capp.cfiles:
+ for gfun in cfile.gfunctions.values():
+ if gfun.is_system_function:
+ continue
+ print(str(gfun.varinfo))
+
+ exit(0)
diff --git a/chc/cmdline/chkc b/chc/cmdline/chkc
index e7bdfa6..fa14016 100755
--- a/chc/cmdline/chkc
+++ b/chc/cmdline/chkc
@@ -650,6 +650,34 @@ def parse() -> argparse.Namespace:
cfileparse.set_defaults(func=C.cfile_parse_file)
+ # --- mk-headerfile
+ cfilemkheader = cfileparsers.add_parser("mk-headerfile")
+ cfilemkheader.add_argument(
+ "filename",
+ help="name of file to analyze (())")
+ cfilemkheader.add_argument(
+ "--tgtpath",
+ help="directory in which to store the analysis artifacts "
+ + "(default: )")
+ cfilemkheader.add_argument(
+ "--output", "-o",
+ help=("basename of file to save output "
+ + "(without extension): a file with extension .c will be created"))
+ cfilemkheader.add_argument(
+ "--loglevel", "-log",
+ choices=UL.LogLevel.options(),
+ default="NONE",
+ help="activate logging with given level (default to stderr)")
+ cfilemkheader.add_argument(
+ "--logfilename",
+ help="name of file to write log messages")
+ cfilemkheader.add_argument(
+ "--logfilemode",
+ choices=["a", "w"],
+ default="a",
+ help="file mode for log file: append (a, default), or write (w)")
+ cfilemkheader.set_defaults(func=C.cfile_mk_headerfile)
+
# --- analyze
cfileanalyze = cfileparsers.add_parser("analyze")
cfileanalyze.add_argument(
@@ -1326,6 +1354,44 @@ def parse() -> argparse.Namespace:
cprojectparse.set_defaults(func=P.cproject_parse_project)
+ # --- mk-headerfile
+ cprojectmkheader = cprojectparsers.add_parser("mk-headerfile")
+ cprojectmkheader.add_argument(
+ "tgtpath", help="directory that contains the analysis results")
+ cprojectmkheader.add_argument(
+ "projectname", help="name of the project")
+ cprojectmkheader.add_argument(
+ "--userdata", help="name of json file with userdata")
+ cprojectmkheader.add_argument(
+ "--includepaths",
+ nargs="*",
+ default=[],
+ help="names of (relative) paths from which to include source files")
+ cprojectmkheader.add_argument(
+ "--includedirs",
+ nargs="*",
+ default=[],
+ help="names of (relative) directories from which to include source files")
+ cprojectmkheader.add_argument(
+ "--excludepaths",
+ nargs="*",
+ default=[],
+ help="names of (relative) path from which not to include source files")
+ cprojectmkheader.add_argument(
+ "--loglevel", "-log",
+ choices=UL.LogLevel.options(),
+ default="NONE",
+ help="activate logging with given level (default to stderr)")
+ cprojectmkheader.add_argument(
+ "--logfilename",
+ help="name of file to write log messages")
+ cprojectmkheader.add_argument(
+ "--logfilemode",
+ choices=["a", "w"],
+ default="a",
+ help="file mode for log file: append (a, default), or write (w)")
+ cprojectmkheader.set_defaults(func=P.cproject_mk_headerfile)
+
# --- analyze
cprojectanalyze = cprojectparsers.add_parser("analyze")
cprojectanalyze.add_argument(
@@ -1427,6 +1493,14 @@ def parse() -> argparse.Namespace:
help="show for all files (including application files)")
cprojectmissingsummaries.set_defaults(func=P.cproject_missing_summaries)
+ # --- create_header_file
+ cprojectcreateheader = cprojectparsers.add_parser("create-header-file")
+ cprojectcreateheader.add_argument(
+ "tgtpath", help="directory that contains the analysis results")
+ cprojectcreateheader.add_argument(
+ "projectname", help="name of the project")
+ cprojectcreateheader.set_defaults(func=P.cproject_create_header_file)
+
args = parser.parse_args()
return args