123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351(**************************************************************************)(* *)(* This file is part of Frama-C. *)(* *)(* Copyright (C) 2007-2023 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file licenses/LGPLv2.1). *)(* *)(**************************************************************************)openCil_typesopenCil_datatypemoduleSelf=Plugin.Register(structletname="nonterm"letshortname="nonterm"lethelp="Warns when definitively non-terminating functions/loops are \
detected (e.g. reachable functions with unreachable returns)."end)moduleEnabled=Self.WithOutput(structletoption_name="-nonterm"lethelp="when on (off by default), \
warns about non-terminating functions/loops"letoutput_by_default=falseend)let()=Parameter_customize.argument_may_be_fundecl()moduleIgnore=Self.Filled_string_set(structletoption_name="-nonterm-ignore"letarg_name="f1,..,fn"lethelp="ignore functions f1,..,fn and direct calls to them. \
Functions prefixed with '-' are removed from the ignore \
list. Calls via function pointers are never ignored. \
By default, the following functions are ignored: \
abort, exit"letdefault=Datatype.String.Set.of_list["abort";"exit"]end)moduleDeadCode=Self.False(structletoption_name="-nonterm-dead-code"lethelp="warns about syntactically unreachable code. \
Note that this may emit a substantial amount of warnings."end)letpretty_stmt_kindfmtstmt=matchstmt.skindwith|Break_->letimplicit=tryletkf=Kernel_function.find_englobing_kfstmtinletloop=Kernel_function.find_enclosing_loopkfstmtin(* heuristic: if both statements have the same location, then
the break was implicitly generated *)Location.equal(Stmt.locstmt)(Stmt.locloop)withNot_found->falseinFormat.fprintffmt"%sbreak"(ifimplicitthen"implicit "else"")|Return_->(* heuristic: if the return statement has no predecessors, then
it is implicitly generated (and dead code) *)letimplicit=stmt.preds=[]inFormat.fprintffmt"%sreturn"(ifimplicitthen"implicit "else"")|Loop_->Format.fprintffmt"loop"|Switch_->Format.fprintffmt"switch"|Instr(Call_)->Format.fprintffmt"function call"|Instr(Local_init(_,ConsInit_,_))->Format.fprintffmt"function call (initializer)"|_->Format.fprintffmt"statement"letpp_numbered_stacksfmtcallstacks=ifList.lengthcallstacks<2thenFormat.fprintffmt"stack: %a"(Pretty_utils.pp_list~sep:": "Eva.Callstack.pretty)callstackselse(* number callstacks *)letnumbered_callstacks=List.mapi(funics->(i+1,cs))callstacksinFormat.fprintffmt"%a"(Pretty_utils.pp_list~sep:"@\n"(Pretty_utils.pp_pair~pre:"stack "~sep:": "Format.pp_print_intEva.Callstack.pretty))numbered_callstacksletwkey_stmt=Self.register_warn_category"stmt"letwarn_nonterminating_statementstmtcallstacks=Self.warning~wkey:wkey_stmt~source:(fst(Stmt.locstmt))"non-terminating %a@\n%a"pretty_stmt_kindstmtpp_numbered_stackscallstacksletwkey_dead=Self.register_warn_category"dead-code"letwarn_dead_codestmt=Self.warning~wkey:wkey_dead~source:(fst(Stmt.locstmt))"%a is syntactically unreachable"pretty_stmt_kindstmtclassdead_cc_collectorkf=objectinheritVisitor.frama_c_inplacevalreachable=letfirst=Kernel_function.find_first_stmtkfinletinitial_reachable=Stmt.Hptset.addfirst(Stmts_graph.reachable_stmtskffirst)inrefinitial_reachablevaldead_ccs=ref[]valcur_cc=ref[]methodget=(* the last cc may not have been finalized *)if!cur_cc<>[]thenbegindead_ccs:=!cur_cc::!dead_ccs;cur_cc:=[]end;!dead_ccsmethod!vstmtstmt=letnew_succsccs=List.filter(funs'->not(List.mems'cc))s.succsinifnot(Stmt.Hptset.memstmt!reachable)thenbegin(* add [stmt] and its successors to a connected component; if there is
already one with [stmt], remain there, otherwise create a new one *)beginif!cur_cc=[]thenbeginletcc=stmt::new_succs[]stmtincur_cc:=ccendelseifList.memstmt!cur_ccthenbegin(* part of same cc: update cc in previous list *)cur_cc:=!cur_cc@new_succs!cur_ccstmt;endelse(* new cc *)begindead_ccs:=!cur_cc::!dead_ccs;cur_cc:=stmt::new_succs[]stmt;endend;reachable:=Stmt.Hptset.addstmt!reachableend;Cil.DoChildrenendletwkey_unreachable=Self.register_warn_category"unreachable"letwarn_unreachable_statementstmt=Self.warning~wkey:wkey_unreachable~source:(fst(Stmt.locstmt))"unreachable %a"pretty_stmt_kindstmtclassunreachable_stmt_visitorkfto_ignore=objectinheritVisitor.frama_c_inplacevalsemantically_unreachable:stmtlistref=ref[]methodget:stmtlist=!semantically_unreachablevalsyntactically_reachable=letfirst=Kernel_function.find_first_stmtkfinletinitial_reachable=Stmt.Hptset.addfirst(Stmts_graph.reachable_stmtskffirst)ininitial_reachablevalsemantically_considered=refto_ignoremethod!vstmtstmt=ifStmt.Hptset.memstmtsyntactically_reachable&¬(Eva.Results.is_reachablestmt)&¬(Stmt.Hptset.memstmt!semantically_considered)thenbegin(* add node and its reachable successors to the considered statements *)letcc=Stmt.Hptset.addstmt(Stmts_graph.reachable_stmtskfstmt)insemantically_considered:=Stmt.Hptset.union!semantically_consideredcc;semantically_unreachable:=stmt::!semantically_unreachableend;Cil.DoChildrenend(* Unreachable returns only need to be checked if:
1. SyntacticallyUnreachable is disabled (otherwise it already checks them);
2. No warnings were emitted for the function (otherwise it may be redundant). *)letcheck_unreachable_returnskf=tryletret_stmt=Kernel_function.find_returnkfinifnot(Eva.Results.is_reachableret_stmt)thenwarn_unreachable_statementret_stmtwith|Kernel_function.No_Statement->(* should never happen *)Self.error"function %a has no return statement, skipping"Kernel_function.prettykf(* Checks [kf] for unreachable statements (ignoring those in [to_ignore])
and emits warnings. [warned_kfs] indicates functions which already had
warnings emitted, to minimize the amount of redundant ones. *)letcheck_unreachable_statementskf~to_ignore~dead_code~warned_kfs=matchEva.Analysis.statuskfwith|Unreachable|AnalyzedNoResults->()|SpecUsed|Builtin_->(* TODO: consider as non-terminating if spec has
\terminates(false) or \ensures(false) *)Self.debug"not analyzing function %a@ \
(using specification instead of definition),@ \
considered as always terminating"Kernel_function.prettykf|Analyzed_->tryletvis=newunreachable_stmt_visitorkfto_ignoreinignore(Visitor.visitFramacKf(vis:>Visitor.frama_c_visitor)kf);ifdead_codethenbegin(* compute syntactically unreachable statements *)letvis=newdead_cc_collectorkfinignore(Visitor.visitFramacKf(vis:>Visitor.frama_c_visitor)kf);letcc_heads=List.mapList.hdvis#getinStmt.Hptset.iter(funh->warn_dead_codeh)(Stmt.Hptset.of_listcc_heads)endelseifnot(Kernel_function.Set.memkfwarned_kfs)thencheck_unreachable_returnskfwith|Kernel_function.No_Statement->(* should never happen *)Self.error"function %a has no return statement, skipping"Kernel_function.prettykf(* To avoid redundant warnings, calls to possibly non-terminating functions
are ignored if:
1. the function is in the list of functions to be ignored;
2. or Eva results are available for the function.
In case 2, the call is ignored because non-terminating statements inside
it will already be reported. *)letignore_kfname=tryletkf=Globals.Functions.find_by_namenameinIgnore.memname||Eva.Results.are_availablekfwithNot_found->false(* simple statement collector: accumulates a list of all
statements, except calls to functions in [to_ignore]. *)classstmt_collector=objectinheritVisitor.frama_c_inplacevalinstr_stmts=ref[]method!vstmtstmt=beginmatchstmt.skindwith|(Instr(Call(_,{enode=Lval(Varvi,_)},_,_))|Instr(Local_init(_,ConsInit(vi,_,_),_)))when(ignore_kfvi.vname)->()|_->instr_stmts:=stmt::!instr_stmtsend;Cil.DoChildrenmethodget_instr_stmts=List.rev!instr_stmtsend(* collects the list of non-terminating instructions *)letcollect_nonterminating_statementsfdnonterm_stacks=letvis=newstmt_collectorinignore(Visitor.visitFramacFunction(vis:>Visitor.frama_c_visitor)fd);letnew_nonterm_stmts=refStmt.Hptset.emptyinletadd_stackstmtcs=new_nonterm_stmts:=Stmt.Hptset.addstmt!new_nonterm_stmts;letprev_stack_list=tryHashtbl.findnonterm_stacksstmtwithNot_found->[]inHashtbl.replacenonterm_stacksstmt(cs::prev_stack_list)inList.iter(funstmt->matchstmt.skindwith|Block_->(* do not compute; already done for the block stmts *)()|_->letsource=fst(Stmt.locstmt)inSelf.debug~source"processing stmt:@ %a"Printer.pp_stmtstmt;letprocess_callstackcs=ifEva.Results.(afterstmt|>in_callstackcs|>is_empty)thenadd_stackstmtcselseifmatchstmt.skindwithLoop_->true|_->falsethenbegin(* special treatment for loops: even if their after state
is reachable, we must check that at least one outgoing
edge is reachable *)letout_edges=Stmts_graph.get_all_stmt_out_edgesstmtinletall_out_edges_unreachable=List.for_all(fun(_,out_stmt)->Eva.Results.(beforeout_stmt|>in_callstackcs|>is_empty))out_edgesinifall_out_edges_unreachablethenadd_stackstmtcsendinList.iterprocess_callstackEva.Results.(beforestmt|>callstacks))vis#get_instr_stmts;!new_nonterm_stmtsletrun()=ifnot(Ast.is_computed())thenSelf.abort"nonterm requires a computed AST";ifnot(Eva.Analysis.is_computed())thenSelf.abort"nonterm requires a computed value analysis";Self.debug"Starting analysis...";letfile=Ast.get()inletglobals=file.globalsinletnonterm_stacks=Hashtbl.create13inList.iter(funglob->matchglobwith|GFun(fd,_loc)->letfname=fd.svar.vorig_nameinifIgnore.memfnamethenSelf.debug"ignoring function: %s"fnameelsebeginSelf.debug"considering function: %s"fname;letnew_nonterm_stmts=collect_nonterminating_statementsfdnonterm_stacksinletwarned_kfs=Stmt.Hptset.fold(funstmtacc->letcs=Hashtbl.findnonterm_stacksstmtinletcs=List.sortEva.Callstack.compare_lexcsinwarn_nonterminating_statementstmtcs;Kernel_function.Set.add(Kernel_function.find_englobing_kfstmt)acc)new_nonterm_stmtsKernel_function.Set.emptyinletkf=Globals.Functions.getfd.svarincheck_unreachable_statementskf~to_ignore:new_nonterm_stmts~warned_kfs~dead_code:(DeadCode.get());end|_->())globals;Self.feedback~level:2"Analysis done.";;letrun_once,_=State_builder.apply_once"Nonterm.run"[Eva.Analysis.self]runletmain()=ifEnabled.get()thenrun_once()let()=Db.Main.extendmain