# ------------------------------------------------------------------------------
# CodeHawk C Analyzer
# Author: Henny Sipma
# ------------------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2017-2020 Kestrel Technology LLC
# Copyright (c) 2020-2022 Henny B. Sipma
# Copyright (c) 2023-2024 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.
# ------------------------------------------------------------------------------
"""Control flow element in a function."""
import xml.etree.ElementTree as ET
from typing import Callable, cast, Dict, List, Optional, TYPE_CHECKING
from chc.app.CInstr import (CInstr, CCallInstr, CAssignInstr, CAsmInstr)
import chc.util.fileutil as UF
if TYPE_CHECKING:
from chc.app.CExp import CExp
from chc.app.CFile import CFile
from chc.app.CFileDictionary import CFileDictionary
from chc.app.CFunction import CFunction
stmt_constructors: Dict[str, Callable[["CStmt", ET.Element], "CStmt"]] = {
"instr": lambda x, y: CInstrsStmt(x, y),
"if": lambda x, y: CIfStmt(x, y),
"loop": lambda x, y: CLoopStmt(x, y),
"break": lambda x, y: CBreakStmt(x, y),
"return": lambda x, y: CReturnStmt(x, y),
"goto": lambda x, y: CGotoStmt(x, y),
"switch": lambda x, y: CSwitchStmt(x, y),
"continue": lambda x, y: CContinueStmt(x, y),
}
[docs]def get_statement(parent: "CStmt", xnode: ET.Element) -> "CStmt":
"""Return the appropriate kind of CStmt dependent on the stmt kind."""
knode = xnode.find("skind")
if knode is None:
raise UF.CHCError("missing element `skind`")
tag = knode.get("stag")
if tag is None:
raise UF.CHCError("missing attribute `stag`")
if tag in stmt_constructors:
return stmt_constructors[tag](parent, xnode)
raise UF.CHCError("Unknown statement tag found: " + tag)
[docs]class CStmt:
"""Superclass of all control flow components in a function."""
def __init__(self, parent: Optional["CStmt"], xnode: ET.Element) -> None:
self._parent = parent
self.xnode = xnode
self._succs: Optional[List[int]] = None
self._preds: Optional[List[int]] = None
@property
def parent(self) -> Optional["CStmt"]:
return self._parent
@property
def cfun(self) -> "CFunction":
if self.parent is not None:
return self.parent.cfun
else:
raise UF.CHCError("Unable to chain back to function body")
@property
def cfile(self) -> "CFile":
return self.cfun.cfile
@property
def cdictionary(self) -> "CFileDictionary":
return self.cfile.dictionary
@property
def sid(self) -> int:
xsid = self.xnode.get("sid")
if xsid is not None:
return int(xsid)
else:
raise UF.CHCError("sid missing from stmt")
@property
def xskind(self) -> ET.Element:
xskind = self.xnode.find("skind")
if xskind is not None:
return xskind
else:
raise UF.CHCError("skind missing from stmt")
@property
def kind(self) -> str:
xkind = self.xskind.get("stag")
if xkind is not None:
return xkind
else:
raise UF.CHCError("stag missing from stmt")
@property
def preds(self) -> List[int]:
if self._preds is None:
self._preds = []
xpreds = self.xnode.find("preds")
if xpreds is not None:
xpreds_r = xpreds.get("r")
if xpreds_r is not None:
self._preds = [int(x) for x in str(xpreds_r).split(",")]
return self._preds
@property
def succs(self) -> List[int]:
if self._succs is None:
self._succs = []
xsuccs = self.xnode.find("succs")
if xsuccs is not None:
xsuccs_r = xsuccs.get("r")
if xsuccs_r is not None:
self._succs = [int(x) for x in str(xsuccs_r).split(",")]
return self._succs
@property
def stmts(self) -> Dict[int, "CStmt"]:
return {}
[docs] def iter_stmts(self, f: Callable[["CStmt"], None]) -> None:
for s in self.stmts.values():
f(s)
@property
def is_instrs_stmt(self) -> bool:
return False
@property
def is_if_stmt(self) -> bool:
return False
@property
def is_block_stmt(self) -> bool:
return False
@property
def is_function_body(self) -> bool:
return False
@property
def block_count(self) -> int:
return sum(s.block_count for s in self.stmts.values())
@property
def stmt_count(self) -> int:
return sum(s.stmt_count for s in self.stmts.values()) + 1
@property
def instr_count(self) -> int:
return sum(s.instr_count for s in self.stmts.values())
@property
def call_instrs(self) -> List["CCallInstr"]:
return sum([s.call_instrs for s in self.stmts.values()], [])
@property
def strings(self) -> List[str]:
return sum([s.strings for s in self.stmts.values()], [])
[docs] def get_variable_uses(self, vid: int) -> int:
return sum(s.get_variable_uses(vid) for s in self.stmts.values())
def __str__(self) -> str:
lines: List[str] = []
predecessors = ",".join(str(p) for p in self.preds)
successors = ",".join(str(s) for s in self.succs)
lines.append(
str(self.sid).rjust(4)
+ ": ["
+ predecessors
+ "] "
+ self.kind
+ " ["
+ successors
+ "]")
for s in self.stmts.values():
lines.append(" " + str(s))
return "\n".join(lines)
[docs]class CBlock(CStmt):
def __init__(self, parent: Optional["CStmt"], xnode: ET.Element) -> None:
CStmt.__init__(self, parent, xnode)
self._stmts: Optional[Dict[int, "CStmt"]] = None
@property
def stmts(self) -> Dict[int, "CStmt"]:
if self._stmts is None:
self._stmts = {}
bstmts = self.xnode.find("bstmts")
if bstmts is not None:
for s in bstmts.findall("stmt"):
stmt = get_statement(self, s)
self._stmts[stmt.sid] = stmt
else:
raise UF.CHCError("stmts element is missing from block element")
return self._stmts
@property
def is_block_stmt(self) -> bool:
return True
@property
def block_count(self) -> int:
return sum(s.block_count for s in self.stmts.values()) + 1
[docs]class CFunctionBody(CBlock):
def __init__(self, cfun: "CFunction", xnode: ET.Element) -> None:
CBlock.__init__(self, None, xnode)
self._cfun = cfun
@property
def cfun(self) -> "CFunction":
return self._cfun
@property
def is_function_body(self) -> bool:
return self.parent is None
[docs]class CIfStmt(CStmt):
def __init__(self, parent: "CStmt", xnode: ET.Element) -> None:
CStmt.__init__(self, parent, xnode)
self._stmts: Optional[Dict[int, "CStmt"]] = None
@property
def stmts(self) -> Dict[int, "CStmt"]:
if self._stmts is None:
self._stmts = {}
xthen = self.xskind.find("thenblock")
if xthen is not None:
thenblock = CBlock(self, xthen)
for s in thenblock.stmts.values():
self._stmts[s.sid] = s
xelse = self.xskind.find("elseblock")
if xelse is not None:
elseblock = CBlock(self, xelse)
for s in elseblock.stmts.values():
self._stmts[s.sid] = s
return self._stmts
@property
def condition(self) -> "CExp":
xiexp = self.xskind.get("iexp")
if xiexp is not None:
return self.cdictionary.get_exp(int(xiexp))
else:
raise UF.CHCError("iexp attribute is missing from if stmt")
@property
def strings(self) -> List[str]:
result: List[str] = sum([s.strings for s in self.stmts.values()], [])
return result + self.condition.get_strings()
@property
def is_if_stmt(self) -> bool:
return True
[docs] def get_variable_uses(self, vid: int) -> int:
result: int = sum(s.get_variable_uses(vid) for s in self.stmts.values())
return result + self.condition.get_variable_uses(vid)
def __str__(self) -> str:
return CStmt.__str__(self) + ": " + str(self.condition)
[docs]class CLoopStmt(CStmt):
def __init__(self, parent: "CStmt", xnode: ET.Element) -> None:
CStmt.__init__(self, parent, xnode)
self._stmts: Optional[Dict[int, "CStmt"]] = None
@property
def stmts(self) -> Dict[int, "CStmt"]:
if self._stmts is None:
self._stmts = {}
xblock = self.xskind.find("block")
if xblock is not None:
loopblock = CBlock(self, xblock)
for s in loopblock.stmts.values():
self._stmts[s.sid] = s
else:
raise UF.CHCError("Loop stmt without nested block")
return self._stmts
[docs]class CSwitchStmt(CStmt):
def __init__(self, parent: "CStmt", xnode: ET.Element) -> None:
CStmt.__init__(self, parent, xnode)
self._stmts: Optional[Dict[int, "CStmt"]] = None
@property
def stmts(self) -> Dict[int, "CStmt"]:
if self._stmts is None:
self._stmts = {}
sblock = self.xskind.find("block")
if sblock is not None:
switchblock = CBlock(self, sblock)
for s in switchblock.stmts.values():
self._stmts[s.sid] = s
else:
raise UF.CHCError("Switch stmt without nested block")
return self._stmts
[docs]class CBreakStmt(CStmt):
def __init__(self, parent: "CStmt", xnode: ET.Element) -> None:
CStmt.__init__(self, parent, xnode)
[docs]class CContinueStmt(CStmt):
def __init__(self, parent: "CStmt", xnode: ET.Element) -> None:
CStmt.__init__(self, parent, xnode)
[docs]class CGotoStmt(CStmt):
def __init__(self, parent: "CStmt", xnode: ET.Element) -> None:
CStmt.__init__(self, parent, xnode)
[docs]class CReturnStmt(CStmt):
"""Function return."""
def __init__(self, parent: "CStmt", xnode: ET.Element) -> None:
CStmt.__init__(self, parent, xnode)