123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701(****************************************************************************)(* *)(* This file is part of MOPSA, a Modular Open Platform for Static Analysis. *)(* *)(* Copyright (C) 2017-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/>. *)(* *)(****************************************************************************)openMopsa_utilsopenLatticeopenLocationopenCallstackopenFormat(** {1 Checks} *)(** ********** *)typecheck=..letcheck_print_chain:checkTypeExt.print_chain=TypeExt.mk_print_chain(fun__->Exceptions.panic"Print of unregistered check")letpp_checkfmtc=TypeExt.printcheck_print_chainfmtcletcompare_check=compareletregister_checkf=TypeExt.register_printfcheck_print_chain(** {1 Alarms} *)(** ********** *)typealarm_kind=..typealarm_kind+=A_genericofchecktypealarm={alarm_kind:alarm_kind;alarm_check:check;alarm_range:range;alarm_callstack:callstack;}letalarm_compare_chain:alarm_kindTypeExt.compare_chain=TypeExt.mk_compare_chain(funa1a2->matcha1,a2with|A_genericchk1,A_genericchk2->comparechk1chk2|_->comparea1a2)letalarm_print_chain:alarm_kindTypeExt.print_chain=TypeExt.mk_print_chain(funfmt->function|A_genericchk->pp_checkfmtchk|_->Exceptions.panic"Print of unregistered alarm")letalarm_check_chain:(alarm_kind->check)ref=ref(function|A_genericchk->chk|_->Exceptions.panic"Check of unregistered alarm")letalarm_join_chain:(alarm_kind->alarm_kind->alarm_kindoption)ref=ref(funa1a2->None)letcompare_alarm_kinda1a2=TypeExt.comparealarm_compare_chaina1a2letpp_alarm_kindfmta=TypeExt.printalarm_print_chainfmtaletjoin_alarm_kinda1a2=!alarm_join_chaina1a2letcompare_alarma1a2=Compare.quadruplecompare_alarm_kindcompare_callstackcompare_rangecompare_check(a1.alarm_kind,a1.alarm_callstack,a1.alarm_range,a1.alarm_check)(a2.alarm_kind,a2.alarm_callstack,a2.alarm_range,a2.alarm_check)letpp_alarmfmta=pp_alarm_kindfmta.alarm_kindletregister_alarm_comparef=TypeExt.register_comparefalarm_compare_chainletregister_alarm_ppf=TypeExt.register_printfalarm_print_chainletregister_alarm_checkf=alarm_check_chain:=f!alarm_check_chainletregister_alarm_joinf=alarm_join_chain:=f!alarm_join_chaintypealarm_info={check:(alarm_kind->check)->alarm_kind->check;compare:(alarm_kind->alarm_kind->int)->alarm_kind->alarm_kind->int;print:(formatter->alarm_kind->unit)->formatter->alarm_kind->unit;join:(alarm_kind->alarm_kind->alarm_kindoption)->alarm_kind->alarm_kind->alarm_kindoption;}letregister_alarminfo=register_alarm_checkinfo.check;register_alarm_ppinfo.print;register_alarm_compareinfo.compare;register_alarm_joininfo.joinletcheck_of_alarma=a.alarm_checkletrange_of_alarma=a.alarm_rangeletcallstack_of_alarma=a.alarm_callstackletmk_alarmkindcallstackrange={alarm_kind=kind;alarm_check=!alarm_check_chainkind;alarm_range=range;alarm_callstack=callstack}(** {1 Diagnostic} *)(** ************** *)moduleAlarmSet=SetExt.Make(structtypet=alarmletcompare=compare_alarmend)letpp_alarm_setfmta=matchAlarmSet.elementsawith|[]->pp_print_stringfmt"∅"|[a]->pp_alarmfmta|l->fprintffmt"@[<v>{ %a }@]"(pp_print_list~pp_sep:(funfmt()->fprintffmt"@,")pp_alarm)ltypediagnostic_kind=|Warning|Safe|Error|Unreachabletype'adiagnostic_={diag_range:range;diag_check:check;diag_kind:diagnostic_kind;diag_alarms:AlarmSet.t;diag_callstack:'a;}typediagnostic=callstackdiagnostic_typediagnosticWoCs=unitdiagnostic_letmk_safe_diagnosticcheckcallstackrange={diag_range=range;diag_check=check;diag_kind=Safe;diag_alarms=AlarmSet.empty;diag_callstack=callstack}letmk_unreachable_diagnosticcheckcallstackrange={diag_range=range;diag_check=check;diag_kind=Unreachable;diag_alarms=AlarmSet.empty;diag_callstack=callstack}letmk_error_diagnosticalarm={diag_range=alarm.alarm_range;diag_check=alarm.alarm_check;diag_kind=Error;diag_alarms=AlarmSet.singletonalarm;diag_callstack=alarm.alarm_callstack}letmk_warning_diagnosticcheckcallstackrange={diag_range=range;diag_check=check;diag_kind=Warning;diag_alarms=AlarmSet.empty;diag_callstack=callstack}letpp_diagnostic_kindfmt=function|Warning->pp_print_stringfmt"warning"|Safe->pp_print_stringfmt"safe"|Error->pp_print_stringfmt"error"|Unreachable->pp_print_stringfmt"unreachable"letpp_diagnosticfmtd=matchd.diag_kindwith|Safe|Unreachable->pp_diagnostic_kindfmtd.diag_kind|Error|Warning->fprintffmt"%a: %a"pp_diagnostic_kindd.diag_kindpp_alarm_setd.diag_alarmsletsubset_diagnostic_kinds1s2=ifs1==s2thentrueelsematchs1,s2with|Unreachable,_->true|_,Unreachable->false|_,Warning->true|Warning,_->false|Error,Error->true|Safe,Safe->true|Error,Safe->false|Safe,Error->falseletsubset_diagnosticd1d2=ifd1==d2thentrueelsesubset_diagnostic_kindd1.diag_kindd2.diag_kind&&AlarmSet.subsetd1.diag_alarmsd2.diag_alarms&&compare_callstackd1.diag_callstackd2.diag_callstack=0letjoin_diagnostic_kinds1s2=ifs1==s2thens1elsematchs1,s2with|Unreachable,x|x,Unreachable->x|Warning,x|x,Warning->Warning|Error,Error->Error|Safe,Safe->Safe|Error,Safe|Safe,Error->Warningletjoin_diagnosticd1d2=ifd1==d2thend1elseifsubset_diagnosticd1d2thend2elseifsubset_diagnosticd2d1thend1else(assert(compare_ranged1.diag_ranged2.diag_range=0);assert(compare_checkd1.diag_checkd2.diag_check=0);assert(compare_callstackd1.diag_callstackd2.diag_callstack=0);{diag_range=d1.diag_range;diag_check=d1.diag_check;diag_kind=join_diagnostic_kindd1.diag_kindd2.diag_kind;diag_alarms=AlarmSet.uniond1.diag_alarmsd2.diag_alarms;diag_callstack=d1.diag_callstack;})letmeet_diagnostic_kinds1s2=ifs1==s2thens1elsematchs1,s2with|Unreachable,_|_,Unreachable->Unreachable|Warning,x|x,Warning->x|Error,Error->Error|Safe,Safe->Safe|Error,Safe|Safe,Error->Exceptions.panic"unsound intersection of diagnostics"letmeet_diagnosticd1d2=ifd1==d2thend1elseifsubset_diagnosticd1d2thend1elseifsubset_diagnosticd2d1thend2else(assert(compare_ranged1.diag_ranged2.diag_range=0);assert(compare_checkd1.diag_checkd2.diag_check=0);assert(compare_callstackd1.diag_callstackd2.diag_callstack=0);(* It is currently unsound for one domain to remove alarms raised by
another domain during reduction, because a "safe" check in a domain
may correspond to a previous instance of the check at the same range
and there is actually no information for the current instance in
the domain.
The current fix is to perform a union instead of an intersection of
alarms during reduction. It is sound but imprecise.
See MR!51 for more details.
FIXME: implement a better fix, performing intersections when sound
*){diag_range=d1.diag_range;diag_check=d1.diag_check;diag_kind=meet_diagnostic_kindd1.diag_kindd2.diag_kind;diag_alarms=AlarmSet.uniond1.diag_alarmsd2.diag_alarms;diag_callstack=d1.diag_callstack})letadd_alarm_to_diagnosticad=assert(compare_callstackd.diag_callstacka.alarm_callstack=0);matchd.diag_kindwith|Error->{dwithdiag_alarms=AlarmSet.addad.diag_alarms;}|Warning->{dwithdiag_alarms=AlarmSet.addad.diag_alarms;}|Safe->{dwithdiag_kind=Error;diag_alarms=AlarmSet.singletona;}|Unreachable->{dwithdiag_kind=Error;diag_alarms=AlarmSet.singletona;}letcompare_diagnosticd1d2=Compare.quadruplecompare_rangecompare_checkcompare(Compare.pairAlarmSet.comparecompare_callstack)(d1.diag_range,d1.diag_check,d1.diag_kind,(d1.diag_alarms,d1.diag_callstack))(d2.diag_range,d2.diag_check,d2.diag_kind,(d2.diag_alarms,d2.diag_callstack))(** {1 Soundness assumption} *)(** ************************ *)typeassumption_scope=|A_localofrange|A_globaltypeassumption_kind=..typeassumption_kind+=A_ignore_unsupported_stmtofAst.Stmt.stmttypeassumption_kind+=A_ignore_unsupported_exprofAst.Expr.exprtypeassumption={assumption_scope:assumption_scope;assumption_kind:assumption_kind;}letassumption_compare_chain:assumption_kindTypeExt.compare_chain=TypeExt.mk_compare_chain(funa1a2->matcha1,a2with|A_ignore_unsupported_stmts1,A_ignore_unsupported_stmts2->Ast.Stmt.compare_stmts1s2|A_ignore_unsupported_expre1,A_ignore_unsupported_expre2->Ast.Expr.compare_expre1e2|_->comparea1a2)letassumption_print_chain:assumption_kindTypeExt.print_chain=TypeExt.mk_print_chain(funfmt->function|A_ignore_unsupported_stmts->Format.fprintffmt"ignoring unsupported statement '%a'"(Debug.boldAst.Stmt.pp_stmt)s|A_ignore_unsupported_expre->Format.fprintffmt"ignoring unsupported expression '%a'"(Debug.boldAst.Expr.pp_expr)e|_->Exceptions.panic"Print of unregistered assumption")letregister_assumptioninfo=TypeExt.registerinfoassumption_compare_chainassumption_print_chainletpp_assumption_kindfmth=TypeExt.printassumption_print_chainfmthletpp_assumptionfmth=matchh.assumption_scopewith|A_global->pp_assumption_kindfmth.assumption_kind|A_localrange->fprintffmt"%a: %a"pp_relative_rangerangepp_assumption_kindh.assumption_kindletcompare_assumption_kindh1h2=TypeExt.compareassumption_compare_chainh1h2letcompare_assumptionh1h2=Compare.pair(funscope1scope2->matchscope1,scope2with|A_global,A_global->0|A_localr1,A_localr2->compare_ranger1r2|_->comparescope1scope2)compare_assumption_kind(h1.assumption_scope,h1.assumption_kind)(h2.assumption_scope,h2.assumption_kind)letmk_global_assumptionkind={assumption_scope=A_global;assumption_kind=kind}letmk_local_assumptionkindrange={assumption_scope=A_localrange;assumption_kind=kind}(** {1 Alarms report} *)(** ***************** *)moduleRangeMap=MapExt.Make(structtypet=rangeletcompare=compare_rangeend)moduleRangeCallStackMap=MapExt.Make(structtypet=range*callstackletcompare=Compare.paircompare_rangecompare_callstackend)moduleCheckMap=MapExt.Make(structtypet=checkletcompare=compareend)moduleAssumptionSet=SetExt.Make(structtypet=assumptionletcompare=compare_assumptionend)typereport={report_diagnostics:diagnosticCheckMap.tRangeCallStackMap.t;report_assumptions:AssumptionSet.t;}letempty_report={report_diagnostics=RangeCallStackMap.empty;report_assumptions=AssumptionSet.empty;}letis_empty_reportr=RangeCallStackMap.is_emptyr.report_diagnosticsletis_safe_reportr=RangeCallStackMap.for_all(funrangechecks->CheckMap.for_all(funcheckdiag->matchdiag.diag_kindwith|Safe|Unreachable->true|Error|Warning->false)checks)r.report_diagnosticsletis_sound_reportr=AssumptionSet.is_emptyr.report_assumptionsletsingleton_reportalarm=letdiag=mk_error_diagnosticalarminletchecks=CheckMap.singletonalarm.alarm_checkdiagin{report_diagnostics=RangeCallStackMap.singleton(alarm.alarm_range,alarm.alarm_callstack)checks;report_assumptions=AssumptionSet.empty;}letpp_reportfmtr=fprintffmt"@[<v>assumptions: @[<v>%a@]@,%a@]"(AssumptionSet.fprintSetExtSig.{print_empty="∅";print_begin="";print_sep="@,";print_end="";}pp_assumption)r.report_assumptions(pp_print_list~pp_sep:(funfmt()->fprintffmt"@,")(funfmt((range,cs),checks)->pp_print_list~pp_sep:(funfmt()->fprintffmt"@,")(funfmt(check,diag)->fprintffmt"@[<hov2>%a(%a): %a:@ @[%a@]@]"pp_relative_rangerangepp_callstack_shortcspp_checkcheckpp_diagnosticdiag)fmt(CheckMap.bindingschecks)))(RangeCallStackMap.bindingsr.report_diagnostics)letsubset_reportr1r2=(r1==r2)||(AssumptionSet.equalr1.report_assumptionsr2.report_assumptions&&(r1.report_diagnostics==r2.report_diagnostics||(RangeCallStackMap.for_all2zo(fun_checks1->false)(fun_checks2->true)(fun_checks1checks2->CheckMap.for_all2zo(fun_diag1->diag1.diag_kind==Unreachable)(fun_diag2->true)(fun_diag1diag2->subset_diagnosticdiag1diag2)checks1checks2)r1.report_diagnosticsr2.report_diagnostics)))letjoin_reportr1r2=ifr1==r2thenr1elseifsubset_reportr1r2thenr2elseifsubset_reportr2r1thenr1else{report_assumptions=AssumptionSet.unionr1.report_assumptionsr2.report_assumptions;report_diagnostics=RangeCallStackMap.map2zo(funrangechecks1->checks1)(funrangechecks2->checks2)(funrangechecks1checks2->CheckMap.map2zo(funcheckdiag1->diag1)(funcheckdiag2->diag2)(fun_diag1diag2->join_diagnosticdiag1diag2)checks1checks2)r1.report_diagnosticsr2.report_diagnostics;}letmeet_reportr1r2=ifr1==r2thenr1elseifsubset_reportr1r2thenr1elseifsubset_reportr2r1thenr2else{report_assumptions=AssumptionSet.unionr1.report_assumptionsr2.report_assumptions;report_diagnostics=RangeCallStackMap.fold2zo(fun(range,_)_acc->Exceptions.panic_atrange"alarms: unsound meet")(fun(range,_)_acc->Exceptions.panic_atrange"alarms: unsound meet")(fun(range,cs)checks1checks2acc->letchecks=CheckMap.fold2zo(funcheckdiag1acc->Exceptions.panic_atrange"alarms: unsound meet for check '%a'"pp_checkcheck)(funcheckdiag2acc->Exceptions.panic_atrange"alarms: unsound meet for check '%a'"pp_checkcheck)(funcheckdiag1diag2acc->CheckMap.addcheck(meet_diagnosticdiag1diag2)acc)checks1checks2checks1inRangeCallStackMap.add(range,cs)checksacc)r1.report_diagnosticsr2.report_diagnosticsr1.report_diagnostics}letcount_alarmsr=RangeCallStackMap.fold(funrangechecksacc->CheckMap.fold(funcheckdiag(errors,warnings)->matchdiag.diag_kindwith|Error->errors+1,warnings|Warning->errors,warnings+1|Safe|Unreachable->errors,warnings)checksacc)r.report_diagnostics(0,0)letadd_alarm?(warning=false)alarmr=letcheck=alarm.alarm_checkinletrange=alarm.alarm_rangeinletcallstack=alarm.alarm_callstackinletchecks=tryRangeCallStackMap.find(range,alarm.alarm_callstack)r.report_diagnosticswithNot_found->CheckMap.emptyinletdiag=tryCheckMap.findcheckcheckswithNot_found->mk_unreachable_diagnosticcheckcallstackrangeinletdiag'=add_alarm_to_diagnosticalarmdiaginletdiag''=ifwarning&&diag'.diag_kind=Errorthen{diag'withdiag_kind=Warning}elsediag'in{rwithreport_diagnostics=RangeCallStackMap.add(alarm.alarm_range,alarm.alarm_callstack)(CheckMap.addcheckdiag''checks)r.report_diagnostics}letmap2zo_reportf1f2fr1r2={report_assumptions=AssumptionSet.unionr1.report_assumptionsr2.report_assumptions;report_diagnostics=RangeCallStackMap.map2zo(funrange->CheckMap.mapf1)(funrange->CheckMap.mapf2)(funrange->CheckMap.map2zo(fun_->f1)(fun_->f2)(fun_->f))r1.report_diagnosticsr2.report_diagnostics}letfold2zo_reportf1f2fr1r2init=RangeCallStackMap.fold2zo(funrangechecks1acc->CheckMap.fold(funcheck->f1)checks1acc)(funrangechecks2acc->CheckMap.fold(funcheck->f2)checks2acc)(funrangechecks1checks2acc->CheckMap.fold2zo(funcheckdiag1acc->f1diag1acc)(funcheckdiag2acc->f2diag2acc)(funcheckdiag1diag2acc->fdiag1diag2acc)checks1checks2acc)r1.report_diagnosticsr2.report_diagnosticsinitletexists2zo_reportf1f2fr1r2=RangeCallStackMap.exists2zo(funrangechecks1->CheckMap.exists(funcheck->f1)checks1)(funrangechecks2->CheckMap.exists(funcheck->f2)checks2)(funrangechecks1checks2->CheckMap.exists2zo(funcheckdiag1->f1diag1)(funcheckdiag2->f2diag2)(funcheckdiag1diag2->fdiag1diag2)checks1checks2)r1.report_diagnosticsr2.report_diagnosticsletfold_reportfrinit=RangeCallStackMap.fold(funrangechecksacc->CheckMap.fold(funcheck->f)checksacc)r.report_diagnosticsinitmoduleCallstackSet=SetExt.Make(structtypet=callstackletcompare=compare_callstackend)moduleRangeDiagnosticWoCsMap=MapExt.Make(structtypet=range*diagnosticWoCsletcompare(r1,d1)(r2,d2)=Compare.paircompare_range(Compare.quadruplecompare_rangecompare_checkcompareAlarmSet.compare)(r1,(d1.diag_range,d1.diag_check,d1.diag_kind,d1.diag_alarms))(r2,(d2.diag_range,d2.diag_check,d2.diag_kind,d2.diag_alarms))end)letgroup_diagnostics(rangecs:diagnosticCheckMap.tRangeCallStackMap.t):CallstackSet.tRangeDiagnosticWoCsMap.t=RangeCallStackMap.fold(fun(range,cs)cmacc->CheckMap.fold(funcheckdiagnosticacc->letdWoCs={diagnosticwithdiag_callstack=()}inletcss=tryRangeDiagnosticWoCsMap.find(range,dWoCs)accwithNot_found->CallstackSet.emptyinRangeDiagnosticWoCsMap.add(range,dWoCs)(CallstackSet.adddiagnostic.diag_callstackcss)acc)cmacc)rangecsRangeDiagnosticWoCsMap.emptyletfind_diagnosticrangecscheckreport=RangeCallStackMap.findrangecsreport.report_diagnostics|>CheckMap.findcheckletset_diagnosticdiagreport=letchecks=tryRangeCallStackMap.find(diag.diag_range,diag.diag_callstack)report.report_diagnosticswithNot_found->CheckMap.emptyinletchecks'=CheckMap.adddiag.diag_checkdiagchecksinifchecks==checks'thenreportelse{reportwithreport_diagnostics=RangeCallStackMap.add(diag.diag_range,diag.diag_callstack)checks'report.report_diagnostics}letadd_diagnosticdiagreport=letchecks=tryRangeCallStackMap.find(diag.diag_range,diag.diag_callstack)report.report_diagnosticswithNot_found->CheckMap.emptyintryletold_diag=CheckMap.finddiag.diag_checkchecksinletdiag'=join_diagnosticdiagold_diaginifold_diag==diag'thenreportelseletchecks'=CheckMap.adddiag.diag_checkdiag'checksin{reportwithreport_diagnostics=RangeCallStackMap.add(diag.diag_range,diag.diag_callstack)checks'report.report_diagnostics}withNot_found->letchecks'=CheckMap.adddiag.diag_checkdiagchecksin{reportwithreport_diagnostics=RangeCallStackMap.add(diag.diag_range,diag.diag_callstack)checks'report.report_diagnostics}letremove_diagnosticdiagreport=tryletchecks=RangeCallStackMap.find(diag.diag_range,diag.diag_callstack)report.report_diagnosticsinletchecks'=CheckMap.removediag.diag_checkchecksinifchecks==checks'thenreportelse{reportwithreport_diagnostics=RangeCallStackMap.add(diag.diag_range,diag.diag_callstack)checks'report.report_diagnostics}withNot_found->reportletexists_reportfreport=RangeCallStackMap.exists(funrange->CheckMap.exists(fun_diag->fdiag))report.report_diagnosticsletforall_reportfreport=RangeCallStackMap.exists(funrange->CheckMap.exists(fun_diag->fdiag))report.report_diagnosticsletfilter_reportfreport=letdiags=RangeCallStackMap.fold(funrangechecksacc->letchecks'=CheckMap.filter(fun_diag->fdiag)checksinifchecks==checks'thenaccelseifCheckMap.is_emptychecks'thenRangeCallStackMap.removerangeaccelseRangeCallStackMap.addrangechecks'acc)report.report_diagnosticsreport.report_diagnosticsinifdiags=report.report_diagnosticsthenreportelse{reportwithreport_diagnostics=diags}letalarms_to_reportalarms=List.fold_left(funaccalarm->join_reportacc(singleton_reportalarm))empty_reportalarmsletreport_to_alarmsreport=RangeCallStackMap.fold(funrangechecksacc->CheckMap.fold(funcheckdiagacc->matchdiag.diag_kindwith|Error|Warning->AlarmSet.elementsdiag.diag_alarms@acc|Safe|Unreachable->acc)checksacc)report.report_diagnostics[]letadd_assumptionareport=letassumptions=AssumptionSet.addareport.report_assumptionsinifassumptions==report.report_assumptionsthenreportelse{reportwithreport_assumptions=assumptions}letadd_global_assumptionkindreport=leta=mk_global_assumptionkindinadd_assumptionareportletadd_local_assumptionkindrangereport=leta=mk_local_assumptionkindrangeinadd_assumptionareport