Skip to content

Commit d5c66d1

Browse files
committed
CFILE: add more commands
1 parent 7975c59 commit d5c66d1

File tree

3 files changed

+320
-5
lines changed

3 files changed

+320
-5
lines changed

chc/cmdline/c_file/cfileutil.py

Lines changed: 254 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
# ------------------------------------------------------------------------------
2727
"""Command-line interface to the CodeHawk C Analyzer.
2828
29-
Naming conventions:
29+
The functions in this module support the commands available in the single-file
30+
mode of the CodeHawk-C analyzer.
31+
32+
Naming conventions used in this module:
3033
3134
- pcfilename_c full-path name of cfile analyzed
3235
- cfilename base name of cfile analyzed (without extension)
@@ -53,7 +56,7 @@
5356
import subprocess
5457
import sys
5558

56-
from typing import List, Optional, NoReturn
59+
from typing import Callable, List, NoReturn, Optional, TYPE_CHECKING
5760

5861
from chc.cmdline.AnalysisManager import AnalysisManager
5962
from chc.cmdline.ParseManager import ParseManager
@@ -67,6 +70,9 @@
6770
import chc.util.fileutil as UF
6871
from chc.util.loggingutil import chklogger, LogLevel
6972

73+
if TYPE_CHECKING:
74+
from chc.proof.CFunctionPO import CFunctionPO
75+
7076

7177
def print_error(m: str) -> None:
7278
sys.stderr.write(("*" * 80) + "\n")
@@ -90,6 +96,18 @@ def set_logging(
9096

9197

9298
def cfile_parse_file(args: argparse.Namespace) -> NoReturn:
99+
"""Parses a single file and saves the results in the .cch directory.
100+
101+
This command runs the gcc preprocessor on the single c-file presented and
102+
then calls the ocaml C analyzer to parse (using the goblint cil parser)
103+
the resulting .i file into ocaml data structures. These data structures
104+
are saved in the <projectname>.cch/a directory. The original c-file and
105+
the preprocessed i-file are saved in the <projectname>.cch/s directory.
106+
107+
Note: When run on a MacOSX platform, the CIL parser may not be able to
108+
handle the syntax of the MacOS standard header files, if these are included
109+
in the c-file.
110+
"""
93111

94112
# arguments
95113
pcfilename: str = os.path.abspath(args.filename)
@@ -157,6 +175,22 @@ def cfile_parse_file(args: argparse.Namespace) -> NoReturn:
157175

158176

159177
def cfile_analyze_file(args: argparse.Namespace) -> NoReturn:
178+
"""Analyzes a single c-file and saves the results in the .cch directory.
179+
180+
This command runs the ocaml C analyzer to
181+
182+
1. generate proof obligations for the declarations and functions represented
183+
by the data structures saved by the parse command,
184+
2. generate invariants for these functions (targeted for the proof obligations),
185+
3. attempts to discharge the generated proof obligations with the generated
186+
invariants
187+
4. generates supporting proof obligations for proof obligations that were
188+
discharged against an api assumption (on the python side)
189+
5. repeats steps 2-4 until convergence (or some preset maximum is reached)
190+
191+
All results are saved in the <projectname>.cch/a/ directory
192+
193+
"""
160194

161195
# arguments
162196
xcfilename: str = args.filename
@@ -238,11 +272,20 @@ def cfile_analyze_file(args: argparse.Namespace) -> NoReturn:
238272

239273

240274
def cfile_report_file(args: argparse.Namespace) -> NoReturn:
275+
"""Reports the analysis results for a single file.
276+
277+
This command loads the analysis results saved by the analysis command and
278+
prints the result statistics (in terms of proof obligations discharged),
279+
and optionally prints the indivual proof obligations embedded in the
280+
original source code (if available from the <projectname>.cch/s/ directory).
281+
282+
"""
241283

242284
# arguments
243285
xcfilename: str = args.filename
244286
opttgtpath: Optional[str] = args.tgtpath
245287
cshowcode: bool = args.showcode
288+
cfunctions: Optional[List[str]] = args.functions
246289
copen: bool = args.open
247290
jsonoutput: bool = args.json
248291
outputfile: Optional[str] = args.output
@@ -283,9 +326,215 @@ def cfile_report_file(args: argparse.Namespace) -> NoReturn:
283326
print(json.dumps(jsonokresult, indent=2))
284327
exit(0)
285328

329+
if cfunctions is None:
330+
if cshowcode:
331+
if args.open:
332+
pofilter = lambda po: (not po.is_closed)
333+
else:
334+
pofilter = lambda po: True
335+
336+
print(RP.file_code_tostring(cfile, pofilter=pofilter))
337+
338+
print(RP.file_proofobligation_stats_tostring(cfile))
339+
exit(0)
340+
341+
for fnname in cfunctions:
342+
if cfile.has_function_by_name(fnname):
343+
cfun = cfile.get_function_by_name(fnname)
344+
345+
if args.open:
346+
pofilter = lambda po: (not po.is_closed)
347+
else:
348+
pofilter = lambda po: True
349+
350+
print(RP.function_code_tostring(cfun, pofilter=pofilter))
351+
352+
print(RP.file_proofobligation_stats_tostring(cfile))
353+
exit(0)
354+
355+
356+
def cfile_investigate_file(args: argparse.Namespace) -> NoReturn:
357+
"""Shows a list of open, delegated, and violated proof obligations.
358+
359+
The proof obligations are presented grouped by proof obliigation type and
360+
function. The proof obligation types can be optionally restricted by listing
361+
the proof obliigations of interest (e.g., not-null) explicitly.
362+
"""
363+
364+
# arguments
365+
xcfilename: str = args.filename
366+
opttgtpath: Optional[str] = args.tgtpath
367+
predicates: Optional[List[str]] = args.predicates
368+
referrals: bool = args.referrals
369+
loglevel: str = args.loglevel
370+
logfilename: Optional[str] = args.logfilename
371+
logfilemode: str = args.logfilemode
372+
373+
projectpath = os.path.dirname(os.path.abspath(xcfilename))
374+
targetpath = projectpath if opttgtpath is None else opttgtpath
375+
targetpath = os.path.abspath(targetpath)
376+
cfilename_c = os.path.basename(xcfilename)
377+
cfilename = cfilename_c[:-2]
378+
projectname = cfilename
379+
380+
set_logging(
381+
loglevel,
382+
targetpath,
383+
logfilename=logfilename,
384+
mode=logfilemode,
385+
msg="Command cfile report was invoked")
386+
387+
cchpath = UF.get_cchpath(targetpath, projectname)
388+
contractpath = os.path.join(targetpath, "chc_contracts")
389+
390+
capp = CApplication(
391+
projectpath, projectname, targetpath, contractpath, singlefile=True)
392+
capp.initialize_single_file(cfilename)
393+
cfile = capp.get_cfile()
394+
395+
pofilter: Callable[["CFunctionPO"], bool] = lambda po: True
396+
397+
if predicates is not None:
398+
399+
# need to reassign predicates to satisfy mypy
400+
xpredicates: List[str] = predicates
401+
pofilter = lambda po: po.predicate_name in xpredicates
402+
403+
openppos = cfile.get_open_ppos()
404+
violations = cfile.get_ppos_violated()
405+
delegated = cfile.get_ppos_delegated()
406+
407+
# TODO: add spos
408+
409+
lines: List[str] = []
410+
411+
def header(s: str) -> str:
412+
return (s + ":\n" + ("=" * 80))
413+
414+
if len(openppos) > 0:
415+
lines.append(header("Open primary proof obligations"))
416+
lines.append(RP.tag_file_function_pos_tostring(openppos, pofilter=pofilter))
417+
418+
if len(violations) > 0:
419+
lines.append(header("Primary proof obligations violated"))
420+
lines.append(RP.tag_file_function_pos_tostring(
421+
violations, pofilter=pofilter))
422+
423+
if len(delegated) > 0:
424+
lines.append(header("Primary proof obligations delegated"))
425+
lines.append(RP.tag_file_function_pos_tostring(delegated, pofilter=pofilter))
426+
427+
if referrals:
428+
429+
# TODO: associate with proof obligation
430+
431+
lines.append(header("Referrals"))
432+
result: List["CFunctionPO"] = []
433+
for ppo in openppos:
434+
if ppo.has_referral_diagnostic():
435+
result.append(ppo)
436+
437+
for ppo in result:
438+
referral = ppo.get_referral_diagnostics()
439+
for k in referral:
440+
lines.append(f" {k}: {referral[k]}")
441+
442+
print("\n".join(lines))
443+
444+
exit(0)
445+
446+
447+
def cfile_testlibc_summary(args: argparse.Namespace) -> NoReturn:
448+
"""Runs one of the programs in tests/libcsummaries
449+
450+
A combination of a header file and a function name from that header may
451+
have a test program associated with it (see tests/testfiles.json).
452+
"""
453+
454+
# arguments
455+
cheader: str = args.header
456+
cfnname: str = args.function
457+
cshowcode: bool = args.showcode
458+
copen: bool = args.open
459+
loglevel: str = args.loglevel
460+
logfilename: Optional[str] = args.logfilename
461+
logfilemode: str = args.logfilemode
462+
463+
try:
464+
(projectpath, cfilename_c) = UF.get_libc_summary_test(cheader, cfnname)
465+
except UF.CHCError as e:
466+
print(str(e.wrap()))
467+
exit(1)
468+
469+
targetpath = projectpath
470+
cfilename = cfilename_c[:-2]
471+
projectname = cfilename
472+
473+
if not os.path.isdir(targetpath):
474+
print_error(f"Target directory: {targetpath} does not exist")
475+
exit(1)
476+
477+
set_logging(
478+
loglevel,
479+
targetpath,
480+
logfilename=logfilename,
481+
mode=logfilemode,
482+
msg="cfile test-libc-summary invoked")
483+
484+
parsemanager = ParseManager(projectpath, projectname, targetpath)
485+
parsemanager.remove_semantics()
486+
parsemanager.initialize_paths()
487+
488+
try:
489+
cfilename_i = parsemanager.preprocess_file_with_gcc(cfilename_c)
490+
result = parsemanager.parse_ifile(cfilename_i)
491+
if result != 0:
492+
print("*" * 80)
493+
print("Error in parsing " + cfilename_c)
494+
if Config().platform == "macOS":
495+
print(" (Problem may be related to standard header files on macOS)")
496+
print("*" * 80)
497+
exit(1)
498+
except OSError as e:
499+
print_error("Error when parsing file: " + str(e))
500+
exit(1)
501+
502+
chklogger.logger.info("cfile parse completed")
503+
504+
cchpath = UF.get_cchpath(targetpath, projectname)
505+
506+
contractpath = os.path.join(targetpath, "chc_contracts")
507+
508+
capp = CApplication(
509+
projectpath, projectname, targetpath, contractpath, singlefile=True)
510+
511+
capp.initialize_single_file(cfilename)
512+
cfile = capp.get_cfile()
513+
514+
am = AnalysisManager(capp, verbose=False, wordsize=32)
515+
516+
am.create_file_primary_proofobligations(cfilename)
517+
am.reset_tables(cfile)
518+
capp.collect_post_assumes()
519+
520+
am.generate_and_check_file(cfilename, "llrvisp")
521+
am.reset_tables(cfile)
522+
capp.collect_post_assumes()
523+
524+
for k in range(5):
525+
capp.update_spos()
526+
am.generate_and_check_file(cfilename, "llrvisp")
527+
am.reset_tables(cfile)
528+
529+
chklogger.logger.info("cfile analyze completed")
530+
531+
capp = CApplication(
532+
projectpath, projectname, targetpath, contractpath, singlefile=True)
533+
capp.initialize_single_file(cfilename)
534+
cfile = capp.get_cfile()
286535

287536
if cshowcode:
288-
if args.open:
537+
if copen:
289538
pofilter = lambda po: (not po.is_closed)
290539
else:
291540
pofilter = lambda po: True
@@ -294,4 +543,6 @@ def cfile_report_file(args: argparse.Namespace) -> NoReturn:
294543

295544
print(RP.file_proofobligation_stats_tostring(cfile))
296545

546+
# TODO: add investigation output
547+
297548
exit(0)

0 commit comments

Comments
 (0)