Skip to content

Commit 46e378c

Browse files
authored
Merge pull request #62 from sipma/invariantquery
Invariantquery
2 parents 82de696 + f3bc3ed commit 46e378c

File tree

9 files changed

+207
-13
lines changed

9 files changed

+207
-13
lines changed

chc/app/CContext.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
3232
"""
3333

34-
from typing import List, TYPE_CHECKING
34+
from typing import Any, cast, List, TYPE_CHECKING
3535

3636
import chc.util.fileutil as UF
3737
from chc.util.IndexedTable import IndexedTableValue
@@ -163,5 +163,13 @@ def cfg_context(self) -> CfgContext:
163163
def exp_context(self) -> ExpContext:
164164
return self.cxd.get_exp_context(self.args[1])
165165

166+
def __eq__(self, other: Any) -> bool:
167+
if not isinstance(other, ProgramContext):
168+
return NotImplemented
169+
return self.index == cast("ProgramContext", other).index
170+
171+
def __hash__(self) -> int:
172+
return self.index
173+
166174
def __str__(self) -> str:
167175
return "(" + str(self.cfg_context) + "," + str(self.exp_context) + ")"

chc/app/CFile.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -270,17 +270,20 @@ def functions(self) -> Dict[int, CFunction]:
270270
if self._functions is None:
271271
self._functions = {}
272272
for (vid, gf) in self.gfunctions.items():
273-
fnname = gf.vname
274-
xnode = UF.get_cfun_xnode(
275-
self.targetpath,
276-
self.projectname,
277-
self.cfilepath,
278-
self.cfilename,
279-
fnname)
280-
if xnode is not None:
281-
cfunction = CFunction(self, xnode, fnname)
282-
self._functions[vid] = cfunction
283-
else:
273+
try:
274+
fnname = gf.vname
275+
xnode = UF.get_cfun_xnode(
276+
self.targetpath,
277+
self.projectname,
278+
self.cfilepath,
279+
self.cfilename,
280+
fnname)
281+
if xnode is not None:
282+
cfunction = CFunction(self, xnode, fnname)
283+
self._functions[vid] = cfunction
284+
else:
285+
chklogger.logger.warning("Function {fnname} not found")
286+
except Exception as e:
284287
chklogger.logger.warning("Function {fnname} not found")
285288
return self._functions
286289

chc/app/CHVersion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
chcversion: str = "0.2.0-2024-11-04"
1+
chcversion: str = "0.2.0-2024-12-04"

chc/cmdline/c_file/cfileutil.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import subprocess
5757
import sys
5858

59+
5960
from typing import (
6061
Any, Callable, cast, Dict, List, NoReturn, Optional, TYPE_CHECKING)
6162

@@ -73,6 +74,7 @@
7374
from chc.util.loggingutil import chklogger, LogLevel
7475

7576
if TYPE_CHECKING:
77+
from chc.invariants.CInvariantFact import CInvariantNRVFact
7678
from chc.app.CAttributes import CAttributes
7779
from chc.app.CTyp import (
7880
CTypComp, CTypFloat, CTypFun, CTypInt, CTypPtr)
@@ -813,6 +815,62 @@ def header(s: str) -> str:
813815
exit(0)
814816

815817

818+
def cfile_query_invariants(args: argparse.Namespace) -> NoReturn:
819+
820+
# arguments
821+
xcfilename: str = args.filename
822+
opttgtpath: Optional[str] = args.tgtpath
823+
xfunction: str = args.function
824+
xline: int = args.line
825+
826+
projectpath = os.path.dirname(os.path.abspath(xcfilename))
827+
targetpath = projectpath if opttgtpath is None else opttgtpath
828+
targetpath = os.path.abspath(targetpath)
829+
cfilename_c = os.path.basename(xcfilename)
830+
cfilename = cfilename_c[:-2]
831+
projectname = cfilename
832+
833+
cchpath = UF.get_cchpath(targetpath, projectname)
834+
contractpath = os.path.join(targetpath, "chc_contracts")
835+
836+
if not UF.check_analysis_results_files(targetpath, projectname, None, cfilename):
837+
print_error("No analysis results found for " + cfilename
838+
+ ". Please run analyzer first.")
839+
exit(1)
840+
841+
capp = CApplication(
842+
projectpath, projectname, targetpath, contractpath, singlefile=True)
843+
capp.initialize_single_file(cfilename)
844+
cfile = capp.get_cfile()
845+
846+
if not cfile.has_function_by_name(xfunction):
847+
print_error("No function found with name " + xfunction)
848+
exit(1)
849+
850+
cfun = cfile.get_function_by_name(xfunction)
851+
ppos = cfun.get_ppos()
852+
contexts = {ppo.context for ppo in ppos if ppo.line == xline}
853+
854+
print("Invariants for function " + xfunction + ", line " + str(xline))
855+
if len(contexts) == 0:
856+
print("\nNo ast positions found with invariants on line " + str(xline) + ".")
857+
exit(0)
858+
859+
for ctxt in contexts:
860+
print("\nAST position: " + str(ctxt))
861+
print("-" * (len(str(ctxt)) + 14))
862+
invs = cfun.invarianttable.get_sorted_invariants(ctxt)
863+
nrvfacts: List[str] = []
864+
for inv in invs:
865+
if inv.is_nrv_fact:
866+
inv = cast("CInvariantNRVFact", inv)
867+
if not inv.variable.is_check_variable:
868+
nrvfacts.append(str(inv))
869+
print("\n".join(nrvfacts))
870+
871+
exit(0)
872+
873+
816874
def cfile_testlibc_summary(args: argparse.Namespace) -> NoReturn:
817875
"""Runs one of the programs in tests/libcsummaries
818876

chc/cmdline/c_project/cprojectutil.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from chc.app.CFunction import CFunction
6262
from chc.app.CInstr import CInstr
6363
from chc.app.CStmt import CInstrsStmt, CStmt
64+
from chc.invariants.CInvariantFact import CInvariantNRVFact
6465
from chc.app.CTyp import (
6566
CTypComp, CTypFloat, CTypFun, CTypInt, CTypPtr)
6667
from chc.proof.CFunctionPO import CFunctionPO
@@ -598,6 +599,64 @@ def pofilter(po: "CFunctionPO") -> bool:
598599
exit(0)
599600

600601

602+
def cproject_query_invariants(args: argparse.Namespace) -> NoReturn:
603+
604+
# arguments
605+
tgtpath: str = args.tgtpath
606+
projectname: str = args.projectname
607+
filename: str = args.filename
608+
xfunction: str = args.function
609+
xline: int = args.line
610+
611+
targetpath = os.path.abspath(tgtpath)
612+
projectpath = targetpath
613+
cfilename_c = os.path.basename(filename)
614+
cfilename = cfilename_c[:-2]
615+
cfilepath = os.path.dirname(filename)
616+
617+
if not UF.has_analysisresults_path(targetpath, projectname):
618+
print_error(
619+
f"No analysis results found for {projectname} in {targetpath}")
620+
exit(1)
621+
622+
contractpath = os.path.join(targetpath, "chc_contracts")
623+
capp = CApplication(
624+
projectpath, projectname, targetpath, contractpath)
625+
626+
if capp.has_file(filename[:-2]):
627+
cfile = capp.get_file(filename[:-2])
628+
else:
629+
print_error(f"File {filename} not found")
630+
exit(1)
631+
632+
if not cfile.has_function_by_name(xfunction):
633+
print_error("No function found with name " + xfunction)
634+
exit(1)
635+
636+
cfun = cfile.get_function_by_name(xfunction)
637+
ppos = cfun.get_ppos()
638+
contexts = {ppo.context for ppo in ppos if ppo.line == xline}
639+
640+
print("Invariants for function " + xfunction + ", line " + str(xline))
641+
if len(contexts) == 0:
642+
print("\nNo ast positions found with invariants on line " + str(xline) + ".")
643+
exit(0)
644+
645+
for ctxt in contexts:
646+
print("\nAST position: " + str(ctxt))
647+
print("-" * (len(str(ctxt)) + 14))
648+
invs = cfun.invarianttable.get_sorted_invariants(ctxt)
649+
nrvfacts: List[str] = []
650+
for inv in invs:
651+
if inv.is_nrv_fact:
652+
inv = cast("CInvariantNRVFact", inv)
653+
if not inv.variable.is_check_variable:
654+
nrvfacts.append(str(inv))
655+
print("\n".join(nrvfacts))
656+
657+
exit(0)
658+
659+
601660
def cproject_count_stmts(args: argparse.Namespace) -> NoReturn:
602661
"""CLI command to output size statistics for a c project."""
603662

chc/cmdline/chkc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,27 @@ def parse() -> argparse.Namespace:
862862
help="file mode for log file: append (a, default), or write (w)")
863863
cfileinvestigate.set_defaults(func=C.cfile_investigate_file)
864864

865+
# --- query
866+
cfilequery = cfileparsers.add_parser("query")
867+
cfilequeryparsers = cfilequery.add_subparsers(title="show options")
868+
869+
# -- query invariants --
870+
cfilequeryinvs = cfilequeryparsers.add_parser("invariants")
871+
cfilequeryinvs.add_argument(
872+
"filename",
873+
help="name of file analyzed ((<cpath/>)<cfilename>)")
874+
cfilequeryinvs.add_argument(
875+
"function",
876+
help="name of function")
877+
cfilequeryinvs.add_argument(
878+
"line",
879+
type=int,
880+
help="line number in the code for which to show invariants")
881+
cfilequeryinvs.add_argument(
882+
"--tgtpath",
883+
help="directory that holds the analysis results")
884+
cfilequeryinvs.set_defaults(func=C.cfile_query_invariants)
885+
865886
# --- test libc summary
866887
cfiletestlibc = cfileparsers.add_parser("test-libc-summary")
867888
cfiletestlibc.add_argument(
@@ -1510,6 +1531,26 @@ def parse() -> argparse.Namespace:
15101531
help="show location invariants on the code")
15111532
cprojectreportfile.set_defaults(func=P.cproject_report_file)
15121533

1534+
# --- query
1535+
cprojectquery = cprojectparsers.add_parser("query")
1536+
cprojectqueryparsers = cprojectquery.add_subparsers(title="show options")
1537+
1538+
# --- query invariants
1539+
cprojectqueryinvs = cprojectqueryparsers.add_parser("invariants")
1540+
cprojectqueryinvs.add_argument(
1541+
"tgtpath", help="directory that contains the analysis results")
1542+
cprojectqueryinvs.add_argument(
1543+
"projectname", help="name of the project")
1544+
cprojectqueryinvs.add_argument(
1545+
"filename", help="filename with relative path wrt the project")
1546+
cprojectqueryinvs.add_argument(
1547+
"function", help="name of function")
1548+
cprojectqueryinvs.add_argument(
1549+
"line",
1550+
type=int,
1551+
help="line number in the source code to show invariants")
1552+
cprojectqueryinvs.set_defaults(func=P.cproject_query_invariants)
1553+
15131554
# --- count-statements
15141555
cprojectcountstmts = cprojectparsers.add_parser("count-statements")
15151556
cprojectcountstmts.add_argument(

chc/invariants/CInvariantFact.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ def is_nrv_fact(self) -> bool:
5858
def is_unreachable_fact(self) -> bool:
5959
return False
6060

61+
@property
62+
def is_parameter_constraint(self) -> bool:
63+
return False
64+
6165
def __str__(self) -> str:
6266
return "invariant-fact:" + self.tags[0]
6367

@@ -100,6 +104,10 @@ def __init__(
100104
def xpr(self) -> "CXXpr":
101105
return self.xd.get_xpr(self.args[0])
102106

107+
@property
108+
def is_parameter_constraint(self) -> bool:
109+
return True
110+
103111
def __str__(self) -> str:
104112
return str(self.xpr)
105113

chc/invariants/CXVariable.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ def denotation(self) -> "CVariableDenotation":
7575
else:
7676
raise UF.CHCError("Variable does not have a denotation")
7777

78+
@property
79+
def is_check_variable(self) -> bool:
80+
if self.has_denotation():
81+
return self.vd.get_c_variable_denotation(self.seqnr).is_check_variable
82+
else:
83+
return False
84+
7885
def has_denotation(self) -> bool:
7986
return self.seqnr > 0
8087

chc/util/fileutil.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,16 @@ def get_cfile_predicate_dictionaryname(
939939
return os.path.join(filepath, cfilename + "_prd.xml")
940940

941941

942+
def check_analysis_results_files(
943+
targetpath: str,
944+
projectname: str,
945+
cfilepath: Optional[str],
946+
cfilename: str) -> bool:
947+
filename = get_cfile_predicate_dictionaryname(
948+
targetpath, projectname, cfilepath, cfilename)
949+
return os.path.isfile(filename)
950+
951+
942952
def get_cfile_predicate_dictionary_xnode(
943953
targetpath: str,
944954
projectname: str,

0 commit comments

Comments
 (0)