66import subprocess
77import signal
88import functools
9+ import copy
10+ import multiprocessing
11+ import yaml
912from enum import Flag , auto
1013from .svcomp .utils import verify_bpl_svcomp
1114from .utils import temporary_file , try_command , remove_temp_files ,\
12- llvm_exact_bin
15+ llvm_exact_bin , smack_portfolio_path
1316from .replay import replay_error_trace
1417from .frontend import link_bc_files , frontends , languages , extra_libs
1518from .errtrace import error_trace , json_output_str
@@ -536,6 +539,7 @@ def arguments():
536539 choices = [
537540 'boogie' ,
538541 'corral' ,
542+ 'portfolio' ,
539543 'symbooglix' ,
540544 'svcomp' ],
541545 default = 'corral' ,
@@ -545,6 +549,14 @@ def arguments():
545549 choices = ['z3' , 'cvc4' , "yices2" ], default = 'z3' ,
546550 help = 'back-end SMT solver' )
547551
552+ verifier_group .add_argument (
553+ '--portfolio-config' ,
554+ metavar = 'FILE' ,
555+ default = smack_portfolio_path (),
556+ action = FileAction ,
557+ type = str ,
558+ help = 'read portfolio configuration in YAML format from FILE' )
559+
548560 verifier_group .add_argument (
549561 '--unroll' ,
550562 metavar = 'N' ,
@@ -882,8 +894,8 @@ def verification_result(verifier_output, verifier):
882894 line_no = int (boogie_af_msg .group (2 ))
883895 with open (boogie_af_msg .group (1 ), 'r' ) as f :
884896 assert_line = re .search (
885- attr_pat ,
886- f .read ().splitlines (True )[line_no - 1 ])
897+ attr_pat ,
898+ f .read ().splitlines (True )[line_no - 1 ])
887899 if assert_line :
888900 attr = assert_line .group (1 )
889901 else :
@@ -901,6 +913,73 @@ def verification_result(verifier_output, verifier):
901913 return VResult .UNKNOWN
902914
903915
916+ def invoke_boogie (args ):
917+ command = ["boogie" ]
918+ command += [args .bpl_file ]
919+ command += ["/doModSetAnalysis" ]
920+ command += ["/useArrayTheory" ]
921+ command += ["/timeLimit:%s" % args .time_limit ]
922+ command += ["/errorLimit:%s" % args .max_violations ]
923+ if not args .modular :
924+ command += ["/loopUnroll:%d" % args .unroll ]
925+ if args .solver == 'cvc4' :
926+ command += ["/proverOpt:SOLVER=cvc4" ]
927+ elif args .solver == 'yices2' :
928+ command += ["/proverOpt:SOLVER=Yices2" ]
929+ return command
930+
931+
932+ def invoke_corral (args ):
933+ command = ["corral" ]
934+ command += [args .bpl_file ]
935+ command += ["/tryCTrace" , "/noTraceOnDisk" , "/printDataValues:1" ]
936+ command += ["/k:%d" % args .context_bound ]
937+ command += ["/useProverEvaluate" ]
938+ command += ["/timeLimit:%s" % args .time_limit ]
939+ command += ["/cex:%s" % args .max_violations ]
940+ command += ["/maxStaticLoopBound:%d" % args .loop_limit ]
941+ command += ["/recursionBound:%d" % args .unroll ]
942+ if args .solver == 'cvc4' :
943+ command += ["/bopt:proverOpt:SOLVER=cvc4" ]
944+ elif args .solver == 'yices2' :
945+ command += ["/bopt:proverOpt:SOLVER=Yices2" ]
946+ return command
947+
948+
949+ def invoke_symbooglix (args ):
950+ command = ['symbooglix' ]
951+ command += [args .bpl_file ]
952+ command += ["--file-logging=0" ]
953+ command += ["--entry-points=%s" % "," .join (args .entry_points )]
954+ command += ["--timeout=%d" % args .time_limit ]
955+ command += ["--max-loop-depth=%d" % args .unroll ]
956+ return command
957+
958+
959+ def process_verifier_output (args , verifier_output ):
960+ verifier_output = transform_out (args , verifier_output )
961+ result = verification_result (verifier_output , args .verifier )
962+
963+ if args .json_file :
964+ with open (args .json_file , 'w' ) as f :
965+ f .write (json_output_str (result , verifier_output , args .verifier ))
966+
967+ if result in VResult .ERROR :
968+ error = error_trace (verifier_output , args .verifier )
969+
970+ if args .error_file :
971+ with open (args .error_file , 'w' ) as f :
972+ f .write (error )
973+
974+ if not args .quiet :
975+ print (error )
976+
977+ if args .replay :
978+ replay_error_trace (verifier_output , args )
979+ print (result .message (args ))
980+ return result .return_code ()
981+
982+
904983def verify_bpl (args ):
905984 """Verify the Boogie source file with a back-end verifier."""
906985
@@ -909,85 +988,140 @@ def verify_bpl(args):
909988 return
910989
911990 elif args .verifier == 'boogie' or args .modular :
912- command = ["boogie" ]
913- command += [args .bpl_file ]
914- command += ["/doModSetAnalysis" ]
915- command += ["/useArrayTheory" ]
916- command += ["/timeLimit:%s" % args .time_limit ]
917- command += ["/errorLimit:%s" % args .max_violations ]
991+ command = invoke_boogie (args )
918992 command += ["/proverOpt:O:smt.array.extensional=false" ]
919993 command += ["/proverOpt:O:smt.qi.eager_threshold=100" ]
920994 command += ["/proverOpt:O:smt.arith.solver=2" ]
921- if not args .modular :
922- command += ["/loopUnroll:%d" % args .unroll ]
923- if args .solver == 'cvc4' :
924- command += ["/proverOpt:SOLVER=cvc4" ]
925- elif args .solver == 'yices2' :
926- command += ["/proverOpt:SOLVER=Yices2" ]
927995
928996 elif args .verifier == 'corral' :
929- command = ["corral" ]
930- command += [args .bpl_file ]
931- command += ["/tryCTrace" , "/noTraceOnDisk" , "/printDataValues:1" ]
932- command += ["/k:%d" % args .context_bound ]
933- command += ["/useProverEvaluate" ]
934- command += ["/timeLimit:%s" % args .time_limit ]
935- command += ["/cex:%s" % args .max_violations ]
936- command += ["/maxStaticLoopBound:%d" % args .loop_limit ]
937- command += ["/recursionBound:%d" % args .unroll ]
938- command += ["/bopt:proverOpt:O:smt.qi.eager_threshold=100" ]
939- command += ["/bopt:proverOpt:O:smt.arith.solver=2" ]
940- if args .solver == 'cvc4' :
941- command += ["/bopt:proverOpt:SOLVER=cvc4" ]
942- elif args .solver == 'yices2' :
943- command += ["/bopt:proverOpt:SOLVER=Yices2" ]
997+ command = invoke_corral (args )
998+ args .verifier_options += (
999+ " /bopt:proverOpt:O:smt.qi.eager_threshold=100"
1000+ " /bopt:proverOpt:O:smt.arith.solver=2" )
9441001
9451002 elif args .verifier == 'symbooglix' :
946- command = ['symbooglix' ]
947- command += [args .bpl_file ]
948- command += ["--file-logging=0" ]
949- command += ["--entry-points=%s" % "," .join (args .entry_points )]
950- command += ["--timeout=%d" % args .time_limit ]
951- command += ["--max-loop-depth=%d" % args .unroll ]
1003+ command = invoke_symbooglix (args )
9521004
9531005 if args .verifier_options :
9541006 command += args .verifier_options .split ()
9551007
9561008 verifier_output = try_command (command , timeout = args .time_limit )
957- verifier_output = transform_out (args , verifier_output )
958- result = verification_result (verifier_output , args .verifier )
1009+ return process_verifier_output (args , verifier_output )
9591010
960- if args .json_file :
961- with open (args .json_file , 'w' ) as f :
962- f .write (json_output_str (result , verifier_output , args .verifier ))
9631011
964- if result in VResult .ERROR :
965- error = error_trace (verifier_output , args .verifier )
1012+ def thread_verify_bpl (args , args_to_add ):
9661013
967- if args .error_file :
968- with open (args .error_file , 'w' ) as f :
969- f .write (error )
1014+ if 'verifier' in args_to_add :
1015+ if args_to_add ['verifier' ] == 'portfolio' :
1016+ raise RuntimeError (
1017+ "portfolio is not a valid verifier specification"
1018+ " within the portfolio configuration" )
1019+ else :
1020+ print (
1021+ "Warning: SMACK is using argument verifier from"
1022+ " the chosen portfolio configuration" )
1023+ args .verifier = args_to_add ['verifier' ]
1024+ else :
1025+ raise RuntimeError (
1026+ "verifier is a required argument"
1027+ " in the portfolio configuration file" )
1028+
1029+ if 'modular' in args_to_add :
1030+ if args .modular :
1031+ raise RuntimeError (
1032+ "argument modular specified in both"
1033+ " command line and portfolio configuration" )
1034+ else :
1035+ print (
1036+ "Warning: SMACK is using argument modular from"
1037+ " the chosen portfolio configuration" )
1038+ args .modular = args_to_add ['modular' ]
1039+
1040+ if (args .verifier != 'boogie' ) and args .modular :
1041+ raise RuntimeError (
1042+ "Incompatible arguments modular"
1043+ " and non-boogie verifier were specified" )
1044+
1045+ if 'verifier-options' in args_to_add :
1046+ if args .verifier_options :
1047+ raise RuntimeError (
1048+ "argument verifier-options specified in both"
1049+ " command line and portfolio configuration" )
1050+ else :
1051+ print (
1052+ "Warning: SMACK is using argument verifier-"
1053+ "options from the chosen portfolio configuration" )
1054+ args .verifier_options = args_to_add ['verifier-options' ]
1055+
1056+ if 'solver' in args_to_add :
1057+ if args .solver != 'z3' : # better check than not default?
1058+ raise RuntimeError (
1059+ "argument solver specified in both command"
1060+ " line and portfolio configuration" )
1061+ else :
1062+ print (
1063+ "Warning: SMACK is using argument solver from"
1064+ " the chosen portfolio configuration" )
1065+ args .solver = args_to_add ['solver' ]
9701066
971- if not args .quiet :
972- print ( error )
1067+ if args . verifier == 'boogie' or args .modular :
1068+ command = invoke_boogie ( args )
9731069
974- if args .replay :
975- replay_error_trace (verifier_output , args )
976- print (result .message (args ))
977- return result .return_code ()
1070+ elif args .verifier == 'corral' :
1071+ command = invoke_corral (args )
1072+
1073+ elif args .verifier == 'symbooglix' :
1074+ command = invoke_symbooglix (args )
1075+
1076+ if args .verifier_options :
1077+ command += args .verifier_options .split ()
1078+
1079+ verifier_output = try_command (command , timeout = args .time_limit )
1080+ return args , verifier_output
1081+
1082+
1083+ def verify_bpl_portfolio (args ):
1084+
1085+ portfolio_config = yaml .load (open (args .portfolio_config , 'r' ))
1086+ p = multiprocessing .Pool ()
1087+ results = {} # map of process -> thread name
1088+
1089+ for thread in list (portfolio_config .keys ()):
1090+ async_result = p .apply_async (thread_verify_bpl ,
1091+ args = (copy .deepcopy (args ),
1092+ portfolio_config [thread ]))
1093+ results [async_result ] = thread
1094+
1095+ # TODO: revisit this loop to improve efficiency
1096+ while True :
1097+ for result in list (results .keys ()):
1098+ if result .ready ():
1099+ p .terminate ()
1100+ args , verifier_output = result .get ()
1101+ verifier_output = process_verifier_output (
1102+ args , verifier_output )
1103+ thread_name = results [result ]
1104+ print (f'SMACK portfolio { thread_name } terminated' )
1105+ return verifier_output
9781106
9791107
9801108def clean_up_upon_sigterm (main ):
9811109 def handler (signum , frame ):
1110+ remove_temp_files_lock .acquire ()
9821111 remove_temp_files ()
1112+ remove_temp_files_lock .release ()
9831113 sys .exit (0 )
1114+
9841115 signal .signal (signal .SIGTERM , handler )
9851116 return main
9861117
9871118
9881119@clean_up_upon_sigterm
9891120def main ():
9901121 try :
1122+ global remove_temp_files_lock
1123+ remove_temp_files_lock = multiprocessing .Lock ()
1124+
9911125 global args
9921126 args = arguments ()
9931127
@@ -1002,7 +1136,10 @@ def main():
10021136 if not args .quiet :
10031137 print ("SMACK generated %s" % args .bpl_file )
10041138 else :
1005- return_code = verify_bpl (args )
1139+ if args .verifier == 'portfolio' :
1140+ return_code = verify_bpl_portfolio (args )
1141+ else :
1142+ return_code = verify_bpl (args )
10061143 sys .exit (return_code )
10071144
10081145 except KeyboardInterrupt :
0 commit comments