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)
5356import subprocess
5457import sys
5558
56- from typing import List , Optional , NoReturn
59+ from typing import Callable , List , NoReturn , Optional , TYPE_CHECKING
5760
5861from chc .cmdline .AnalysisManager import AnalysisManager
5962from chc .cmdline .ParseManager import ParseManager
6770import chc .util .fileutil as UF
6871from chc .util .loggingutil import chklogger , LogLevel
6972
73+ if TYPE_CHECKING :
74+ from chc .proof .CFunctionPO import CFunctionPO
75+
7076
7177def print_error (m : str ) -> None :
7278 sys .stderr .write (("*" * 80 ) + "\n " )
@@ -90,6 +96,18 @@ def set_logging(
9096
9197
9298def 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
159177def 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
240274def 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