Skip to content

Commit c92168b

Browse files
committed
CMD: add query facility for invariants
1 parent 9ee0aac commit c92168b

File tree

9 files changed

+209
-14
lines changed

9 files changed

+209
-14
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
import subprocess
5757
import sys
5858

59-
from typing import Callable, List, NoReturn, Optional, TYPE_CHECKING
59+
from typing import Callable, cast, List, NoReturn, Optional, TYPE_CHECKING
6060

6161
from chc.cmdline.AnalysisManager import AnalysisManager
6262
from chc.cmdline.ParseManager import ParseManager
@@ -71,6 +71,7 @@
7171
from chc.util.loggingutil import chklogger, LogLevel
7272

7373
if TYPE_CHECKING:
74+
from chc.invariants.CInvariantFact import CInvariantNRVFact
7475
from chc.proof.CFunctionPO import CFunctionPO
7576

7677

@@ -665,6 +666,62 @@ def header(s: str) -> str:
665666
exit(0)
666667

667668

669+
def cfile_query_invariants(args: argparse.Namespace) -> NoReturn:
670+
671+
# arguments
672+
xcfilename: str = args.filename
673+
opttgtpath: Optional[str] = args.tgtpath
674+
xfunction: str = args.function
675+
xline: int = args.line
676+
677+
projectpath = os.path.dirname(os.path.abspath(xcfilename))
678+
targetpath = projectpath if opttgtpath is None else opttgtpath
679+
targetpath = os.path.abspath(targetpath)
680+
cfilename_c = os.path.basename(xcfilename)
681+
cfilename = cfilename_c[:-2]
682+
projectname = cfilename
683+
684+
cchpath = UF.get_cchpath(targetpath, projectname)
685+
contractpath = os.path.join(targetpath, "chc_contracts")
686+
687+
if not UF.check_analysis_results_files(targetpath, projectname, None, cfilename):
688+
print_error("No analysis results found for " + cfilename
689+
+ ". Please run analyzer first.")
690+
exit(1)
691+
692+
capp = CApplication(
693+
projectpath, projectname, targetpath, contractpath, singlefile=True)
694+
capp.initialize_single_file(cfilename)
695+
cfile = capp.get_cfile()
696+
697+
if not cfile.has_function_by_name(xfunction):
698+
print_error("No function found with name " + xfunction)
699+
exit(1)
700+
701+
cfun = cfile.get_function_by_name(xfunction)
702+
ppos = cfun.get_ppos()
703+
contexts = {ppo.context for ppo in ppos if ppo.line == xline}
704+
705+
print("Invariants for function " + xfunction + ", line " + str(xline))
706+
if len(contexts) == 0:
707+
print("\nNo ast positions found with invariants on line " + str(xline) + ".")
708+
exit(0)
709+
710+
for ctxt in contexts:
711+
print("\nAST position: " + str(ctxt))
712+
print("-" * (len(str(ctxt)) + 14))
713+
invs = cfun.invarianttable.get_sorted_invariants(ctxt)
714+
nrvfacts: List[str] = []
715+
for inv in invs:
716+
if inv.is_nrv_fact:
717+
inv = cast("CInvariantNRVFact", inv)
718+
if not inv.variable.is_check_variable:
719+
nrvfacts.append(str(inv))
720+
print("\n".join(nrvfacts))
721+
722+
exit(0)
723+
724+
668725
def cfile_testlibc_summary(args: argparse.Namespace) -> NoReturn:
669726
"""Runs one of the programs in tests/libcsummaries
670727

chc/cmdline/c_project/cprojectutil.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from chc.app.CFunction import CFunction
6060
from chc.app.CInstr import CInstr
6161
from chc.app.CStmt import CInstrsStmt, CStmt
62+
from chc.invariants.CInvariantFact import CInvariantNRVFact
6263
from chc.proof.CFunctionPO import CFunctionPO
6364

6465

@@ -410,6 +411,64 @@ def pofilter(po: "CFunctionPO") -> bool:
410411
exit(0)
411412

412413

414+
def cproject_query_invariants(args: argparse.Namespace) -> NoReturn:
415+
416+
# arguments
417+
tgtpath: str = args.tgtpath
418+
projectname: str = args.projectname
419+
filename: str = args.filename
420+
xfunction: str = args.function
421+
xline: int = args.line
422+
423+
targetpath = os.path.abspath(tgtpath)
424+
projectpath = targetpath
425+
cfilename_c = os.path.basename(filename)
426+
cfilename = cfilename_c[:-2]
427+
cfilepath = os.path.dirname(filename)
428+
429+
if not UF.has_analysisresults_path(targetpath, projectname):
430+
print_error(
431+
f"No analysis results found for {projectname} in {targetpath}")
432+
exit(1)
433+
434+
contractpath = os.path.join(targetpath, "chc_contracts")
435+
capp = CApplication(
436+
projectpath, projectname, targetpath, contractpath)
437+
438+
if capp.has_file(filename[:-2]):
439+
cfile = capp.get_file(filename[:-2])
440+
else:
441+
print_error(f"File {filename} not found")
442+
exit(1)
443+
444+
if not cfile.has_function_by_name(xfunction):
445+
print_error("No function found with name " + xfunction)
446+
exit(1)
447+
448+
cfun = cfile.get_function_by_name(xfunction)
449+
ppos = cfun.get_ppos()
450+
contexts = {ppo.context for ppo in ppos if ppo.line == xline}
451+
452+
print("Invariants for function " + xfunction + ", line " + str(xline))
453+
if len(contexts) == 0:
454+
print("\nNo ast positions found with invariants on line " + str(xline) + ".")
455+
exit(0)
456+
457+
for ctxt in contexts:
458+
print("\nAST position: " + str(ctxt))
459+
print("-" * (len(str(ctxt)) + 14))
460+
invs = cfun.invarianttable.get_sorted_invariants(ctxt)
461+
nrvfacts: List[str] = []
462+
for inv in invs:
463+
if inv.is_nrv_fact:
464+
inv = cast("CInvariantNRVFact", inv)
465+
if not inv.variable.is_check_variable:
466+
nrvfacts.append(str(inv))
467+
print("\n".join(nrvfacts))
468+
469+
exit(0)
470+
471+
413472
def cproject_count_stmts(args: argparse.Namespace) -> NoReturn:
414473
"""CLI command to output size statistics for a c project."""
415474

chc/cmdline/chkc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,28 @@ def parse() -> argparse.Namespace:
834834
help="file mode for log file: append (a, default), or write (w)")
835835
cfileinvestigate.set_defaults(func=C.cfile_investigate_file)
836836

837+
# --- query
838+
cfilequery = cfileparsers.add_parser("query")
839+
cfilequeryparsers = cfilequery.add_subparsers(title="show options")
840+
841+
# -- query invariants --
842+
cfilequeryinvs = cfilequeryparsers.add_parser("invariants")
843+
cfilequeryinvs.add_argument(
844+
"filename",
845+
help="name of file analyzed ((<cpath/>)<cfilename>)")
846+
cfilequeryinvs.add_argument(
847+
"function",
848+
help="name of function")
849+
cfilequeryinvs.add_argument(
850+
"line",
851+
type=int,
852+
help="line number in the code for which to show invariants")
853+
cfilequeryinvs.add_argument(
854+
"--tgtpath",
855+
help="directory that holds the analysis results")
856+
cfilequeryinvs.set_defaults(func=C.cfile_query_invariants)
857+
858+
837859
# --- test libc summary
838860
cfiletestlibc = cfileparsers.add_parser("test-libc-summary")
839861
cfiletestlibc.add_argument(
@@ -1451,6 +1473,27 @@ def parse() -> argparse.Namespace:
14511473
help="show location invariants on the code")
14521474
cprojectreportfile.set_defaults(func=P.cproject_report_file)
14531475

1476+
# --- query
1477+
cprojectquery = cprojectparsers.add_parser("query")
1478+
cprojectqueryparsers = cprojectquery.add_subparsers(title="show options")
1479+
1480+
# --- query invariants
1481+
cprojectqueryinvs = cprojectqueryparsers.add_parser("invariants")
1482+
cprojectqueryinvs.add_argument(
1483+
"tgtpath", help="directory that contains the analysis results")
1484+
cprojectqueryinvs.add_argument(
1485+
"projectname", help="name of the project")
1486+
cprojectqueryinvs.add_argument(
1487+
"filename", help="filename with relative path wrt the project")
1488+
cprojectqueryinvs.add_argument(
1489+
"function", help="name of function")
1490+
cprojectqueryinvs.add_argument(
1491+
"line",
1492+
type=int,
1493+
help="line number in the source code to show invariants")
1494+
cprojectqueryinvs.set_defaults(func=P.cproject_query_invariants)
1495+
1496+
14541497
# --- count-statements
14551498
cprojectcountstmts = cprojectparsers.add_parser("count-statements")
14561499
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)