# ------------------------------------------------------------------------------
# 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.
# ------------------------------------------------------------------------------
"""Variant type for the CIL **typ** data type."""
import xml.etree.ElementTree as ET
from typing import Any, cast, Dict, List, Optional, Tuple, TYPE_CHECKING
from chc.app.CDictionaryRecord import CDictionaryRecord, cdregistry
import chc.util.fileutil as UF
import chc.util.IndexedTable as IT
from chc.util.loggingutil import chklogger
if TYPE_CHECKING:
from chc.app.CDictionary import CDictionary
from chc.app.CExp import CExp, CExpConst
from chc.app.CAttributes import CAttributes
from chc.app.CCompInfo import CCompInfo
from chc.app.CConst import CConst, CConstInt
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"}
attribute_index = {
"tvoid": 0,
"tint": 0,
"tfloat": 0,
"tptr": 1,
"tarray": 2,
"tfun": 3,
"tnamed": 0,
"tcomp": 1,
"tenum": 0,
"tbuiltin-va-list": 0,
}
[docs]class CTyp(CDictionaryRecord):
"""Base class of all variable types."""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CDictionaryRecord.__init__(self, cd, ixval)
[docs] def expand(self) -> "CTyp":
return self
[docs] def strip_attributes(self) -> "CTyp":
aindex = attribute_index[self.tags[0]]
if aindex >= len(self.args):
return self
elif self.args[aindex] == 1:
return self
else:
newargs = self.args[:-1]
newtypix = self.cd.mk_typ_index(self.tags, newargs)
if newtypix != self.index:
newtyp = self.cd.get_typ(newtypix)
chklogger.logger.info(
"Stripping attributes %s ; changing type from %s to %s",
self.attributes_string,
str(self),
str(newtyp))
return newtyp
[docs] def get_typ(self, ix: int) -> "CTyp":
return self.cd.get_typ(ix)
[docs] def get_exp(self, ix: int) -> "CExp":
return self.cd.get_exp(ix)
[docs] def get_exp_opt(self, ix: int) -> Optional["CExp"]:
return self.cd.get_exp_opt(ix)
@property
def size(self) -> int:
return -1000
@property
def attributes(self) -> "CAttributes":
aindex = attribute_index[self.tags[0]]
if len(self.args) > aindex:
return self.cd.get_attributes(int(self.args[aindex]))
else:
return self.cd.get_attributes(1)
@property
def attributes_string(self) -> str:
attrs = self.attributes
if attrs.length > 0:
return "[" + str(attrs) + "]"
else:
return ""
[docs] def get_opaque_type(self) -> "CTyp":
return self
[docs] def equal(self, other: "CTyp") -> bool:
return self.expand().index == other.expand().index
@property
def is_array(self) -> bool:
return False
@property
def is_builtin_vaargs(self) -> bool:
return False
@property
def is_comp(self) -> bool:
return False
@property
def is_enum(self) -> bool:
return False
@property
def is_float(self) -> bool:
return False
@property
def is_function(self) -> bool:
return False
@property
def is_int(self) -> bool:
return False
@property
def is_named_type(self) -> bool:
return False
@property
def is_pointer(self) -> bool:
return False
@property
def is_struct(self) -> bool:
return False
@property
def is_void(self) -> bool:
return False
@property
def is_default_function_prototype(self) -> bool:
return False
[docs] def writexml(self, cnode: ET.Element) -> None:
cnode.set("ix", str(self.index))
cnode.set("tags", ",".join(self.tags))
cnode.set("args", ",".join([str(int(x)) for x in self.args]))
def __str__(self) -> str:
return "typebase:" + self.tags[0]
[docs] def to_dict(self) -> Dict[str, object]:
return {"base": "type"}
[docs] def to_idict(self) -> Dict[str, object]:
return {"t": self.tags, "a": self.args}
[docs]@cdregistry.register_tag("tvoid", CTyp)
class CTypVoid(CTyp):
""" Void type.
* args[0]: attributes
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def is_void(self) -> bool:
return True
[docs] def get_opaque_type(self) -> CTyp:
return self
[docs] def to_dict(self) -> Dict[str, object]:
return {"base": "void"}
def __str__(self) -> str:
return "void" + "[" + str(self.attributes) + "]"
[docs]@cdregistry.register_tag("tint", CTyp)
class CTypInt(CTyp):
""" Integer type.
* tags[1]: ikind
* args[0]: index of attributes in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def is_int(self) -> bool:
return True
@property
def size(self) -> int:
k = self.ikind
if "char" in integernames[k]:
return 1
else:
return 4 # TBD: adjust for other kinds
@property
def ikind(self) -> str:
return self.tags[1]
[docs] def get_opaque_type(self) -> CTyp:
return self
[docs] def to_dict(self) -> Dict[str, object]:
return {"base": "int", "kind": self.ikind}
def __str__(self) -> str:
return integernames[self.ikind] + str(self.attributes_string)
[docs]@cdregistry.register_tag("tfloat", CTyp)
class CTypFloat(CTyp):
""" Float type.
* tags[1]: fkind
* args[0]: attributes
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def is_float(self) -> bool:
return True
@property
def fkind(self) -> str:
return self.tags[1]
@property
def size(self) -> int:
return 4 # TBD: adjust for kind
[docs] def get_opaque_type(self) -> CTyp:
return self
[docs] def to_dict(self) -> Dict[str, object]:
return {"base": "float", "kind": self.fkind}
def __str__(self) -> str:
return floatnames[self.fkind]
[docs]@cdregistry.register_tag("tnamed", CTyp)
class CTypNamed(CTyp):
"""Type definition
* tags[1]: tname
* args[0]: attributes
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def name(self) -> str:
return self.tags[1]
[docs] def expand(self) -> CTyp:
return self.cd.decls.expand(self.name)
@property
def size(self) -> int:
return self.expand().size
@property
def is_named_type(self) -> bool:
return True
[docs] def get_opaque_type(self) -> CTyp:
return self.expand().get_opaque_type()
[docs] def to_dict(self) -> Dict[str, object]:
return {
"base": "named",
"name": self.name,
"expand": self.expand().to_dict(),
}
def __str__(self) -> str:
return self.name + str(self.attributes_string)
[docs]@cdregistry.register_tag("tcomp", CTyp)
class CTypComp(CTyp):
"""Struct type (composite type; also includes union)
* tags[0]: struct name
* args[0]: ckey
* args[1]: index of attributes in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def ckey(self) -> int:
return self.args[0]
@property
def compinfo(self) -> "CCompInfo":
return self.decls.get_compinfo_by_ckey(self.ckey)
@property
def name(self) -> str:
return self.compinfo.name
@property
def is_struct(self) -> bool:
return self.compinfo.is_struct
@property
def size(self) -> int:
return self.compinfo.size
@property
def is_comp(self) -> bool:
return True
[docs] def get_opaque_type(self) -> CTyp:
tags = ["tvoid"]
args: List[int] = []
return self.cd.get_typ(self.cd.mk_typ_index(tags, args))
[docs] def to_dict(self) -> Dict[str, Any]:
return {
"base": "struct",
"kind": "struct" if self.is_struct else "union",
"name": self.name,
"key": self.ckey,
}
def __str__(self) -> str:
if self.is_struct:
return "struct " + self.name + "(" + str(self.ckey) + ")"
else:
return "union " + self.name + "(" + str(self.ckey) + ")"
[docs]@cdregistry.register_tag("tenum", CTyp)
class CTypEnum(CTyp):
"""Enum type.
* tags[1]: name of enum (ename)
* args[0]: index of attributes in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def name(self) -> str:
return self.tags[1]
@property
def size(self) -> int:
return 4
@property
def is_enum(self) -> bool:
return True
[docs] def get_opaque_type(self) -> CTyp:
tags = ["tint", "iint"]
args: List[int] = []
return self.cd.get_typ(self.cd.mk_typ_index(tags, args))
[docs] def to_dict(self) -> Dict[str, Any]:
return {"base": "enum", "name": self.name}
def __str__(self) -> str:
return "enum " + self.name
[docs]@cdregistry.register_tag("tbuiltinvaargs", CTyp)
@cdregistry.register_tag("tbuiltin-va-list", CTyp)
class CTypBuiltinVaargs(CTyp):
"""Builtin variable arguments
* args[0]: index of attributes in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def is_builtin_vaargs(self) -> bool:
return True
[docs] def get_opaque_type(self) -> CTyp:
return self
[docs] def to_dict(self) -> Dict[str, Any]:
return {"base": "builtin_vaargs"}
def __str__(self) -> str:
return "tbuiltin_va_args"
[docs]@cdregistry.register_tag("tptr", CTyp)
class CTypPtr(CTyp):
""" Pointer type
* args[0]: index of pointed-to type in cdictionary
* args[1]: index of attributes in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def pointedto_type(self) -> CTyp:
return self.get_typ(self.args[0])
@property
def size(self) -> int:
return 4
@property
def is_pointer(self) -> bool:
return True
[docs] def get_opaque_type(self) -> CTyp:
tgttype = self.pointedto_type.get_opaque_type()
tags = ["tptr"]
args = [self.cd.index_typ(tgttype)]
return self.cd.get_typ(self.cd.mk_typ_index(tags, args))
[docs] def to_dict(self) -> Dict[str, Any]:
return {"base": "ptr", "tgt": self.pointedto_type.to_dict()}
def __str__(self) -> str:
return "(" + str(self.pointedto_type) + " *)"
[docs]@cdregistry.register_tag("tarray", CTyp)
class CTypArray(CTyp):
""" Array type
* args[0]: index of base type in cdictionary
* args[1]: index of size expression in cdictionary (optional)
* args[2]: index of attributes in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def array_basetype(self) -> CTyp:
return self.get_typ(self.args[0])
@property
def array_size_expr(self) -> "CExp":
if self.args[1] >= 0:
return self.get_exp(self.args[1])
else:
raise UF.CHCError("Array type does not have a size")
[docs] def has_array_size_expr(self) -> bool:
return self.args[1] >= 0
@property
def size(self) -> int:
try:
if self.has_array_size_expr():
array_size_const = cast(
"CExpConst", self.array_size_expr).constant
array_size_int = cast(
"CConstInt", array_size_const).intvalue
return self.array_basetype.size * array_size_int
except BaseException:
return -1000
else:
return -1000
@property
def is_array(self) -> bool:
return True
[docs] def get_opaque_type(self) -> CTyp:
tags = ["tvoid"]
args: List[int] = []
return self.cd.get_typ(self.cd.mk_typ_index(tags, args))
[docs] def to_dict(self) -> Dict[str, Any]:
result = {"base": "array", "elem": self.array_basetype.to_dict()}
if self.has_array_size_expr() and self.array_size_expr.is_constant:
result["size"] = str(self.array_size_expr)
return result
def __str__(self) -> str:
size = self.array_size_expr
ssize = str(size) if size is not None else "?"
return str(self.array_basetype) + "[" + ssize + "]"
[docs]@cdregistry.register_tag("tfun", CTyp)
class CTypFun(CTyp):
"""Function type
* args[0]: index of return type in cdictionary
* args[1]: index of argument types list in cdictionary (optional
* args[2]: 1 = varargs
* args[3]: index of attributes in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CTyp.__init__(self, cd, ixval)
@property
def return_type(self) -> CTyp:
return self.get_typ(self.args[0])
@property
def funargs(self) -> Optional["CFunArgs"]:
return self.cd.get_funargs_opt(self.args[1])
@property
def size(self) -> int:
return 4
@property
def is_function(self) -> bool:
return True
[docs] def get_opaque_type(self) -> CTyp:
tags = ["tvoid"]
args: List[int] = []
return self.cd.get_typ(self.cd.mk_typ_index(tags, args))
@property
def is_default_function_prototype(self) -> bool:
funargs = self.funargs
if funargs is None:
return True
else:
args = funargs.arguments
return len(args) > 0 and all(
[x.name.startswith("$par$") for x in args]
)
@property
def is_vararg(self) -> bool:
return self.args[2] == 1
[docs] def strip_attributes(self) -> CTyp:
rtype = self.return_type.strip_attributes()
if rtype.index != self.return_type.index:
newargs = self.args[:]
newargs[0] = rtype.index
newtypix = self.cd.mk_typ_index(self.tags, newargs)
newtyp = self.cd.get_typ(newtypix)
chklogger.logger.info(
"Change function type from %s to %s", str(self), str(newtyp))
return newtyp
else:
return self
[docs] def to_dict(self) -> Dict[str, Any]:
result: Dict[str, Any] = {
"base": "fun", "rvtype": self.return_type.to_dict()}
if self.is_default_function_prototype:
result["default"] = "true"
elif self.funargs is not None:
result["args"] = self.funargs.to_dict()
return result
def __str__(self) -> str:
rtyp = self.return_type
args = self.funargs
return "(" + str(args) + "):" + str(rtyp)
[docs]class CFunArg(CDictionaryRecord):
"""Function argument
* tags[0]: argument name
* args[0]: index of argument type in cdictionary
* args[1]: index of attributes in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CDictionaryRecord.__init__(self, cd, ixval)
@property
def name(self) -> str:
if len(self.tags) > 0:
return self.tags[0]
else:
return "__"
@property
def typ(self) -> CTyp:
return self.cd.get_typ(self.args[0])
[docs] def to_dict(self) -> Dict[str, Any]:
return self.typ.to_dict()
def __str__(self) -> str:
return str(self.typ) + " " + self.name
[docs]class CFunArgs(CDictionaryRecord):
"""Function arguments
* args[0..]: indices of function arguments in cdictionary
"""
def __init__(self, cd: "CDictionary", ixval: IT.IndexedTableValue) -> None:
CDictionaryRecord.__init__(self, cd, ixval)
@property
def arguments(self) -> List[CFunArg]:
return [self.cd.get_funarg(i) for i in self.args]
[docs] def to_dict(self) -> List[Dict[str, Any]]:
return [a.to_dict() for a in self.arguments]
def __str__(self) -> str:
return "(" + ", ".join([str(x) for x in self.arguments]) + ")"