123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236(****************************************************************************)(* *)(* This file is part of MOPSA, a Modular Open Platform for Static Analysis. *)(* *)(* Copyright (C) 2019 The MOPSA Project. *)(* *)(* This program is free software: 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, either version 3 of the License, or *)(* (at your option) any later version. *)(* *)(* This program 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. *)(* *)(* You should have received a copy of the GNU Lesser General Public License *)(* along with this program. If not, see <http://www.gnu.org/licenses/>. *)(* *)(****************************************************************************)openMopsaopenAstletdebugfmt=Debug.debug~channel:"python.utils"fmtletrecpartition_list_by_lengthnl=ifn=0then[],lelsematchlwith|hd::tl->letlhd,ltl=partition_list_by_length(n-1)tlinhd::lhd,ltl|_->assertfalseletmk_builtin_raiseexnrange=mk_stmt(S_py_raise(Some(mk_py_object(Addr.find_builtinexn)range)))rangeletmk_builtin_raise_argsexnargsrange=mk_stmt(S_py_raise(Some(mk_py_call(mk_py_object(Addr.find_builtinexn)range)argsrange)))rangeletmk_builtin_raise_msgexnmsgrange=letopenUniversal.Astinmk_builtin_raise_argsexn[mk_constant~etyp:(T_py(SomeStr))(C_stringmsg)range]range(** Creates an AST block, tagged by `range`, representing the call of a builtin defined as `f` (a string), to which a list of parameters is passed *)letmk_builtin_callfparamsrange=mk_py_call(mk_py_object(Addr.find_builtinf)range)paramsrangeletmk_hasattrobjattrrange=mk_builtin_call"hasattr"[obj;Universal.Ast.mk_string~etyp:(T_pyNone)attrrange]rangeletmk_object_hasattrobjattrrange=mk_hasattr(mk_py_objectobjrange)attrrangeletmk_addr_hasattrobjattrrange=mk_hasattr(mk_addrobjrange)attrrange(** Creates a block
try: `body`
except StopIteration: `except`
This statement will be created with the provided `range` *)letmk_try_stopiterationbodyexceptrange=mk_trybody[mk_except(Some(mk_py_object(Addr.find_builtin"StopIteration")range))Noneexcept](Universal.Ast.mk_block[]range)(Universal.Ast.mk_block[]range)rangeletcheck_instances?(arguments_after_check=0)funnamemanflowrangeexprsinstancesprocessing=letopenMopsainletrecauxposiexprslexprslinstancesflow=matchlexprs,linstanceswith|_,[]->ifarguments_after_check=List.lengthlexprsthenprocessingiexprs(Flow.add_safe_checkAlarms.CHK_PY_TYPEERRORrangeflow)elseletmsg=Format.asprintf"%s: too many arguments: %d given, %d expected"funname(List.lengthexprs)(arguments_after_check+List.lengthinstances)inman.exec(mk_builtin_raise_msg"TypeError"msgrange)flow>>%Eval.empty|e::es,i::is->assume(Addr.mk_py_isinstance_builtineirange)manflow~fthen:(funflow->Flow.add_safe_checkAlarms.CHK_PY_TYPEERRORe.erangeflow|>aux(pos+1)iexprsesis)~felse:(funflow->letmsg=Format.asprintf"%s: expected instance of '%s', but found %a at argument #%d"funnameipp_expreposinman.exec(mk_builtin_raise_msg"TypeError"msgrange)flow>>%Eval.empty)|[],_->letmsg=Format.asprintf"%s: too few arguments: %d given, %d expected"funname(List.lengthexprs)(arguments_after_check+List.lengthinstances)inman.exec(mk_builtin_raise_msg"TypeError"msgrange)flow>>%Eval.emptyinCases.bind_listexprsman.evalflow|>Cases.bind_result(funexprsflow->aux1exprsexprsinstancesflow)letcheck_instances_disj?(arguments_after_check=0)funnamemanflowrangeexprsinstancesprocessing=(* FIXME: error messages *)letopenMopsainletrecauxposiexprslexprslinstancesflow=matchlexprs,linstanceswith|_,[]->ifarguments_after_check=List.lengthlexprsthenFlow.add_safe_checkAlarms.CHK_PY_TYPEERRORrangeflow|>processingiexprselseletmsg=Format.asprintf"%s: too many arguments: %d given, %d expected"funname(List.lengthexprs)(arguments_after_check+List.lengthinstances)inman.exec(mk_builtin_raise_msg"TypeError"msgrange)flow>>%Eval.empty|e::es,i::is->letmk_onecond=funi->Addr.mk_py_isinstance_builtineirangeinletcond=List.fold_left(funaccel->mk_binop~etyp:(T_pyNone)accO_py_or(mk_onecondel)range)(mk_onecond@@List.hdi)(List.tli)inassumecondmanflow~fthen:(funflow->Flow.add_safe_checkAlarms.CHK_PY_TYPEERRORe.erangeflow|>aux(pos+1)iexprsesis)~felse:(funflow->letmsg=Format.asprintf"%s: expected instance ∈ {%a}, but found %a at argument #%d"funname(Format.pp_print_list~pp_sep:(funfmt()->Format.fprintffmt", ")Format.pp_print_string)ipp_expreposinman.exec(mk_builtin_raise_msg"TypeError"msgrange)flow>>%Eval.empty)|_->letmsg=Format.asprintf"%s: too few arguments: %d given, %d expected"funname(List.lengthexprs)(arguments_after_check+List.lengthinstances)inman.exec(mk_builtin_raise_msg"TypeError"msgrange)flow>>%Eval.emptyinCases.bind_listexprsman.evalflow|>Cases.bind_result(funexprsflow->aux1exprsexprsinstancesflow)letstrip_object(e:expr)=letekind=matchekindewith|E_py_object(addr,oe)->letaddr={addrwithaddr_partitioning=G_all}inE_py_object(addr,oe)|_->assertfalsein{ewithekind}letnew_wrappermanrangeflownewclsargcls~fthennew=man.evalargclsflow>>$(funeclsflow->assume(Addr.mk_py_issubclass_builtin_rargclsnewclsrange)manflow~fthen:(funflow->Flow.add_safe_checkAlarms.CHK_PY_TYPEERRORargcls.erangeflow|>fthennew)~felse:(funflow->letmsg=Format.asprintf"%s.__new__(%a): %a is not a subtype of int"newclspp_exprargclspp_expreclsinman.exec(mk_builtin_raise_msg"TypeError"msgrange)flow>>%Eval.empty))|>OptionExt.returnletbind_list_args?(cleaners=true)manargsflowrange(f:varlist->'bflow->('b,'c)Cases.cases)=letcs=Flow.get_callstackflowinletmoduleRangeSet=SetExt.Make(structtypet=rangeletcompare=compare_rangeend)inletstmt,vars,_=List.fold_left(fun(stmts,vars,argranges)arg->assert(not@@RangeSet.memarg.erangeargranges);lettmp=mk_range_attr_vararg.erange(Format.asprintf"%xd(bla)"(Hashtbl.hash_param30100cs))(T_pyNone)in(mk_assign(mk_vartmparg.erange)argarg.erange)::stmts,tmp::vars,RangeSet.addarg.erangeargranges)([],[],RangeSet.empty)(List.revargs)inletstmt=Universal.Ast.mk_blockstmtrangeinletcases=fvars(man.execstmtflow|>post_to_flowman)inifcleanersthenCases.add_cleaners(List.map(funx->mk_remove_varxrange)vars)caseselsecasesletchange_var_typetv=mkv(Format.asprintf"%s:%a"v.vnamepp_typt)~mode:v.vmodev.vkindtletchange_evar_typetevar=matchekindevarwith|E_var(v,mode)->{evarwithekind=E_var(change_var_typetv,mode);etyp=t}|_->assertfalseletextract_oobjecte=matchekindewith|E_py_object(_,Somea)->a|_->assertfalseletget_eobj_itvmanflowe=letopenUniversal.Astinletmk_itv_from_zz=Bot.Nb(ItvUtils.IntItv.cstz)inmatchekind@@extract_oobjectewith|E_constant(C_intz)->mk_itv_from_zz|E_unop(O_minus,{ekind=E_constant(C_intz)})->mk_itv_from_z(Z.negz)|_->lete_num=extract_oobjecteindebug"need to ask the numerical domain for the value of %a"pp_expre_num;ask_and_reduceman.ask(Universal.Numeric.Common.mk_int_interval_querye_num)flow(* tries to evaluate python expr.
If the evaluation fails and raises an exception, the exception flow is caught, put back to cur and `on_empty` is then run *)lettry_eval_expr?(on_empty=funexc_expexc_strexc_msgflow->assertfalse)~(on_result:expr->'aflow->('a,expr)cases)(man:('a,'b)man)?(route=Core.Route.toplevel)expr(flow:'aflow):('a,expr)casesoption=man.eval~route:routeexprflow>>=?funcaseflow'->Some(matchcasewith|Result(expr',_,_)->on_resultexpr'flow'|Empty->(* find the T_py_exception tokens in flow' and not in flow *)letcaught_flow,flow_ok=Flow.fold(fun(caught,acc_ok)tkenv->matchtkwith|Alarms.T_py_exception(expr,name,msg,exc_kind)whennot@@Flow.memtkflow->(* FIXME: tk could be renamed by the evaluation of expr... It would be better to split erroneous flows and merge them back I guess *)(expr,name,msg,env)::caught,acc_ok|_->caught,Flow.addtkenvman.latticeacc_ok)([],Flow.bottom_fromflow')flow'inletresults=List.fold_left(funacc(expr,name,msg,env)->matchon_emptyexprnamemsg(Flow.addT_curenvman.latticeflow_ok)with|None->acc|Somes->s::acc)[]caught_flowinEval.join_list~empty:(fun()->Eval.emptyflow_ok)results|NotHandled->assertfalse)letcheckmancondrangeflow=man.exec(Universal.Ast.mk_assertcondrange)flow>>%man.eval(mk_py_nonerange)