Source code for chc.app.IndexManager

# ------------------------------------------------------------------------------
# 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.
# ------------------------------------------------------------------------------
"""Global variable and struct definition relationships between files."""

from dataclasses import dataclass

import xml.etree.ElementTree as ET

from typing import Dict, List, Optional, Tuple, TYPE_CHECKING

import chc.util.fileutil as UF
from chc.util.loggingutil import chklogger
import chc.util.xmlutil as UX

if TYPE_CHECKING:
    from chc.app.CFileDeclarations import CFileDeclarations
    from chc.app.CFile import CFile

fidvidmax_initial_value = 1000000

"""
TODO:
  - save gxrefs file if new vid's were added to a file
"""


[docs]@dataclass class FileVarReference: fid: int # file index vid: int # variable index in file with fid @property def tuple(self) -> Tuple[int, int]: return (self.fid, self.vid) def __str__(self) -> str: return f"(vid: {self.vid}, fid: {self.fid})"
[docs]@dataclass class FileKeyReference: fid: int # file index ckey: int # struct key in file with fid def __str__(self) -> str: return f"(ckey: {self.ckey}, fid: {self.fid})"
[docs]@dataclass class VarReference: fid: Optional[int] vid: int @property def is_global(self) -> bool: return self.fid is None
[docs]@dataclass class CKeyReference: fid: Optional[int] ckey: int @property def is_global(self) -> bool: return self.fid is None
[docs]class IndexManager: def __init__(self, issinglefile: bool) -> None: self._issinglefile = issinglefile # application consists of a single file self.vid2gvid: Dict[int, Dict[int, int]] = {} # fid -> vid -> gvid self.gvid2vid: Dict[int, Dict[int, int]] = {} # gvid -> fid -> vid # fid -> maximum vid in file with index fid self.fidvidmax: Dict[int, int] = {} self.ckey2gckey: Dict[int, Dict[int, int]] = {} # fid -> ckey -> gckey self.gckey2ckey: Dict[int, Dict[int, int]] = {} # gckey -> fid -> ckey # gvid -> fid (file in which gvid is defined) self.gviddefs: Dict[int, int] = {} @property def is_single_file(self) -> bool: return self._issinglefile
[docs] def get_vid_gvid_subst(self, fid: int) -> Dict[int, int]: return self.vid2gvid[fid]
[docs] def get_fid_gvid_subset(self, fileindex: int) -> Dict[int, int]: result: Dict[int, int] = {} for gvid in self.gvid2vid: for fid in self.gvid2vid[gvid]: if fid == fileindex: result[gvid] = self.gvid2vid[gvid][fid] return result
[docs] def resolve_vid( self, filevar: FileVarReference) -> Optional[FileVarReference]: """Returns the local reference of the definition of (fid, vid). An object (variable or function) may be declared in one file (fid) and referenced by vid, but defined in another file, with file index def-fid and variable reference def-vid. If the definition is found this method returns (def-fid, def-vid). """ if self.is_single_file: # there is only one file, so all objects must be defined there. return filevar fid = filevar.fid vid = filevar.vid if fid in self.vid2gvid: if vid in self.vid2gvid[fid]: gvid = self.vid2gvid[fid][vid] # global vid for (fid, vid) if gvid in self.gviddefs: deffid = self.gviddefs[gvid] # file that defines gvid if gvid in self.gvid2vid: if deffid in self.gvid2vid[gvid]: defvid = self.gvid2vid[gvid][deffid] return FileVarReference(deffid, defvid) chklogger.logger.debug( "target fid: %s not found in gvid2vid[%s] for " + "(%s, %s)", str(deffid), str(gvid), str(fid), str(vid)) return None chklogger.logger.debug( "global vid %s not found in gvid2vid for (%s, %s)", str(gvid), str(fid), str(vid)) return None chklogger.logger.debug( "global vid %s not found gviddefs for (%s, %s)", str(gvid), str(fid), str(vid)) return None chklogger.logger.debug( "local vid %s not found in vid2gvid[%s] for (%s, %s)", str(vid), str(fid), str(fid), str(vid)) return None chklogger.logger.debug("file id %s not found in vid2gvid", str(fid)) return None
"""return a list of (fid,vid) pairs that refer to the same global variable."""
[docs] def get_gvid_references(self, gvid: int) -> List[FileVarReference]: """Returns a list all file variables that refer to the same global var.""" result: List[FileVarReference] = [] for fid in self.gvid2vid[gvid]: vid = self.gvid2vid[gvid][fid] result.append(FileVarReference(fid, vid)) return result
[docs] def has_gvid_reference(self, gvid: int, fid: int) -> bool: if gvid in self.gvid2vid: return fid in self.gvid2vid[gvid] else: return False
[docs] def get_gvid_reference(self, gvid: int, fid: int) -> Optional[int]: """Returns the vid that corresponds to gvid in the file with index fid.""" if gvid in self.gvid2vid: if fid in self.gvid2vid[gvid]: return self.gvid2vid[gvid][fid] return None
"""return a list of (fid,vid) pairs that refer to the same variable."""
[docs] def get_vid_references( self, filevar: FileVarReference) -> List[FileVarReference]: """Returns a list of file vars that refer to the same variable as filevar. Note: does not include filevar itself. """ result: List[FileVarReference] = [] if self.is_single_file: return result if filevar.fid in self.vid2gvid: if filevar.vid in self.vid2gvid[filevar.fid]: gvid = self.vid2gvid[filevar.fid][filevar.vid] for fid in self.gvid2vid[gvid]: if fid == filevar.fid: continue vid = self.gvid2vid[gvid][fid] result.append(FileVarReference(fid, vid)) return result
"""return the vid in the file with index fidtgt for vid in fidsrc. If the target file does not map the gvid then create a new vid in this file to map the gvid. """
[docs] def convert_vid( self, varref: FileVarReference, tgtfid: int) -> Optional[int]: """Returns the vid of the var reference in (another) file tgtfid.""" if varref.fid == tgtfid: # same file return varref.vid gvid = self.get_gvid(varref) if gvid is not None: if gvid in self.gvid2vid: if tgtfid in self.gvid2vid[gvid]: return self.gvid2vid[gvid][tgtfid] else: chklogger.logger.warning( "failed to convert %s for file %d (found gvid: %d)", str(varref), tgtfid, gvid) return None return None
[docs] def get_gvid(self, varref: FileVarReference) -> Optional[int]: """Returns the global vid that corresponds to the file var reference.""" if self.is_single_file: # for a single file the global vid is the same as the file vid return varref.vid if varref.fid in self.vid2gvid: if varref.vid in self.vid2gvid[varref.fid]: return self.vid2gvid[varref.fid][varref.vid] return None
[docs] def get_vid(self, fid: int, gvid: int) -> Optional[int]: """Returns the vid of the gvid in the file with index fid.""" if self.is_single_file: return gvid if gvid in self.gvid2vid: if fid in self.gvid2vid[gvid]: return self.gvid2vid[gvid][fid] return None
[docs] def get_gckey(self, filekey: FileKeyReference) -> Optional[int]: """Returns the global ckey index for a file ckey reference.""" if self.is_single_file: # for a single file the global ckey is the same the file ckey return filekey.ckey if filekey.fid in self.ckey2gckey: if filekey.ckey in self.ckey2gckey[filekey.fid]: return self.ckey2gckey[filekey.fid][filekey.ckey] chklogger.logger.warning( "No global key found for file key %s", str(filekey)) return None
[docs] def convert_ckey( self, filekey: FileKeyReference, tgtfid: int) -> Optional[int]: """Returns the ckey of filekey of the same struct in the file tgtfid.""" if filekey.fid == tgtfid: # same file return filekey.ckey gckey = self.get_gckey(filekey) if gckey is not None: if gckey in self.gckey2ckey: if tgtfid in self.gckey2ckey[gckey]: return self.gckey2ckey[gckey][tgtfid] else: chklogger.logger.warning( "Target fid %d not found for global key %d", tgtfid, gckey) return None else: chklogger.logger.warning( "Global key %d not found in converter", gckey) return None else: chklogger.logger.warning( "No global key found for file key %s", str(filekey)) return None
[docs] def add_ckey2gckey(self, filekey: FileKeyReference, gckey: int) -> None: """Registers a local file ckey with a global ckey.""" # add forward conversion to global ckey self.ckey2gckey.setdefault(filekey.fid, {}) self.ckey2gckey[filekey.fid][filekey.ckey] = gckey # add reverse conversion from global ckey self.gckey2ckey.setdefault(gckey, {}) self.gckey2ckey[gckey][filekey.fid] = filekey.ckey
[docs] def add_vid2gvid(self, filevar: FileVarReference, gvid: int) -> None: """Registers a local file vid with a global vid.""" # add forward conversion to global vid self.vid2gvid.setdefault(filevar.fid, {}) self.vid2gvid[filevar.fid][filevar.vid] = gvid # add reverse conversion from global vid self.gvid2vid.setdefault(gvid, {}) self.gvid2vid[gvid][filevar.fid] = filevar.vid
[docs] def add_file(self, cfile: "CFile") -> None: fid = cfile.index if not self.is_single_file: xxreffile = UF.get_cxreffile_xnode( cfile.targetpath, cfile.projectname, cfile.cfilepath, cfile.cfilename) if xxreffile is not None: self._add_xrefs(xxreffile, fid) self._add_globaldefinitions(cfile, fid) self.fidvidmax[fid] = fidvidmax_initial_value
[docs] def save_xrefs( self, targetpath: str, projectname: str, cfilepath: Optional[str], cfilename: str, fid: int) -> None: xrefroot = UX.get_xml_header("global-xrefs", "global-xrefs") xrefsnode = ET.Element("global-xrefs") xrefroot.append(xrefsnode) cxrefsnode = ET.Element("compinfo-xrefs") vxrefsnode = ET.Element("varinfo-xrefs") xrefsnode.extend([cxrefsnode, vxrefsnode]) if fid in self.ckey2gckey: for ckey in sorted(self.ckey2gckey[fid]): xref = ET.Element("cxref") xref.set("ckey", str(ckey)) xref.set("gckey", str(self.ckey2gckey[fid][ckey])) cxrefsnode.append(xref) if fid in self.vid2gvid: for vid in sorted(self.vid2gvid[fid]): xref = ET.Element("vxref") xref.set("vid", str(vid)) xref.set("gvid", str(self.vid2gvid[fid][vid])) vxrefsnode.append(xref) xreffilename = UF.get_cxreffile_filename( targetpath, projectname, cfilepath, cfilename) xreffile = open(xreffilename, "w") xreffile.write(UX.doc_to_pretty(ET.ElementTree(xrefroot)))
def _add_xrefs(self, xnode: ET.Element, fid: int) -> None: if fid not in self.ckey2gckey: self.ckey2gckey[fid] = {} xcompinfoxrefs = xnode.find("compinfo-xrefs") if xcompinfoxrefs is not None: for cxref in xcompinfoxrefs.findall("cxref"): xckey = cxref.get("ckey") if xckey is not None: ckey = int(xckey) xgckey = cxref.get("gckey") if xgckey is not None: gckey = int(xgckey) self.ckey2gckey[fid][ckey] = gckey self.gckey2ckey.setdefault(gckey, {}) # if gckey not in self.gckey2ckey: # self.gckey2ckey[gckey] = {} self.gckey2ckey[gckey][fid] = ckey else: raise UF.CHCError( "Compinfo xref without gckey attribute") else: raise UF.CHCError("Compinfo xref without ckey attribute") if fid not in self.vid2gvid: self.vid2gvid[fid] = {} xvarinfoxrefs = xnode.find("varinfo-xrefs") if xvarinfoxrefs is not None: for vxref in xvarinfoxrefs.findall("vxref"): xvid = vxref.get("vid") if xvid is not None: vid = int(xvid) xgvid = vxref.get("gvid") if xgvid is not None: gvid = int(xgvid) self.vid2gvid[fid][vid] = gvid self.gvid2vid.setdefault(gvid, {}) # if gvid not in self.gvid2vid: # self.gvid2vid[gvid] = {} self.gvid2vid[gvid][fid] = vid else: raise UF.CHCError( "Varinfo xref without gvid attribute") else: raise UF.CHCError("Varinfo xref without vid attribute") def _add_globaldefinitions(self, cfile: "CFile", fid: int) -> None: for gvar in cfile.gvardefs.values(): filevar = FileVarReference(fid, gvar.varinfo.vid) gvid = self.get_gvid(filevar) if gvid is not None: self.gviddefs[gvid] = fid for gfun in cfile.gfunctions.values(): filevar = FileVarReference(fid, gfun.varinfo.vid) gvid = self.get_gvid(filevar) if gvid is not None: chklogger.logger.info( "set function %s (%s) to file %s", gfun.varinfo.vname, str(gvid), str(fid)) self.gviddefs[gvid] = fid