123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639(****************************************************************************)(* *)(* This file is part of MOPSA, a Modular Open Platform for Static Analysis. *)(* *)(* Copyright (C) 2017-2023 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/>. *)(* *)(****************************************************************************)(** Engine supporting parts of the debug adapter protocol *)openMopsa_utilsopenCore.AllopenToplevelopenLocationopenCallstackopenFormatopenYojson.BasicopenYojson.Basic.UtilopenBreakpointopenInterfaceopenQuerymoduleIntMap=MapExt.IntMapmoduleStringMap=MapExt.StringMapopenToplevelopenActionopenEnvdbmoduleMake(Toplevel:TOPLEVEL)=struct(*************************)(** Variables references *)(*************************)typevalue=|Leafofstring|Compoundofintletvref_counter=ref0letcompute_vrefspobj=letvrefs=refIntMap.emptyinletreciter=function|Map(m,_)->incrvref_counter;letvref=!vref_counterinletchildren=MapExtPoly.fold(funkvacc->letname=Format.asprintf"%a"pp_print_objectkinletvv=matchkwith|Var_->Leaf(Format.asprintf"%a"pp_print_objectv)|_->itervin(name,vv)::acc)m[]invrefs:=IntMap.addvref(List.revchildren)!vrefs;Compoundvref|List(l,_)->incrvref_counter;letvref=!vref_counterinletchildren=List.mapi(funiv->string_of_inti,iterv)linvrefs:=IntMap.addvrefchildren!vrefs;Compoundvref|Set(s,_)->incrvref_counter;letvref=!vref_counterinletchildren=SetExtPoly.elementss|>List.mapi(funiv->string_of_inti,iterv)invrefs:=IntMap.addvrefchildren!vrefs;Compoundvref|pobj->lets=Format.asprintf"%a"pp_print_objectpobjinLeafsinlet_=iterpobjin!vrefsletcompute_scopes_vrefspobj=letinitial_vref=!vref_counterinletvrefs=ref(compute_vrefspobj)inifIntMap.is_empty!vrefsthenIntMap.empty,[]elseletscopes=IntMap.find(initial_vref+1)!vrefsinletscopes=scopes|>List.map(fun(name,value)->letvref=matchvaluewith|Compoundvref->vref|Leaf_->incrvref_counter;letvref=!vref_counterinvrefs:=IntMap.addvref["",value]!vrefs;vrefin(name,vref))in!vrefs,scopesletvrefs=refIntMap.empty(********************)(** JSON processing *)(********************)(** Reading the request from the standard input *)letread_json_DAP()=lets=input_linestdininassert(Str.string_match(Str.regexp"Content-Length: \\([0-9]+\\)")s0);letcontent_length=int_of_string(Str.matched_group1s)inlets=input_linestdininlets=ifStr.string_match(Str.regexp"Content-Type:")s0theninput_linestdinelsesinassert(s="\r");letobj_bytes=Bytes.createcontent_lengthinletread_length=inputstdinobj_bytes0content_lengthinassert(read_length=content_length);from_string@@Bytes.to_stringobj_bytes(** Answering on stdin *)letwrite_json_DAPobj=letobj_str=pretty_to_stringobjinletobj_str_len=String.lengthobj_strinletresponse="Content-Length: "^(string_of_intobj_str_len)^"\r\n\r\n"^obj_strinoutput_stringstdoutresponse;flushstdout(** Extraction functions for some JSON fields *)letextract_commandrequest=request|>member"command"|>to_stringletextract_seqrequest=request|>member"seq"|>to_intletextract_varrefrequest=request|>member"arguments"|>member"variablesReference"|>to_intletextract_pathrequest=request|>member"arguments"|>member"source"|>member"path"|>to_stringletextract_breakpointsrequest=request|>member"arguments"|>member"breakpoints"|>to_listletextract_line_breakpointbp=bp|>member"line"|>to_intletextract_namex=x|>member"name"|>to_stringletextract_expressionrequest=request|>member"arguments"|>member"expression"|>to_stringletextract_filerequest=request|>member"arguments"|>member"file"|>to_stringletextract_linerequest=request|>member"arguments"|>member"line"|>to_intletcreate_responserequestbody:Yojson.Basic.t=letcommand=extract_commandrequestandreq_seq=extract_seqrequestinmatchbodywith|`Null->`Assoc[("seq",`Int0);("type",`String"response");("request_seq",`Intreq_seq);("success",`Booltrue);("command",`Stringcommand)]|_->`Assoc[("seq",`Int0);("type",`String"response");("request_seq",`Intreq_seq);("success",`Booltrue);("command",`Stringcommand);("body",body)]letcreate_eventnamebody:Yojson.Basic.t=matchbodywith|`Null->`Assoc[("seq",`Int0);("type",`String"event");("event",`Stringname);]|_->`Assoc[("seq",`Int0);("type",`String"event");("event",`Stringname);("body",body)]letcreate_body_stoppedreason:Yojson.Basic.t=`Assoc[("reason",`Stringreason);("threadId",`Int1)]letcreateFrameidnamelinecolumnfilepath:Yojson.Basic.t=`Assoc[("id",`Intid);("line",`Intline);("column",`Intcolumn);("name",`Stringname);("source",`Assoc[("name",`Stringfile);("path",`Stringpath);("sourceReference",`Int0)])]letcreateScopenamevref:Yojson.Basic.t=`Assoc[("name",`Stringname);("variablesReference",`Intvref);("expensive",`Booltrue);]letcreateVariablename=function|Leafvalue->`Assoc[("name",`Stringname);("value",`Stringvalue);("variablesReference",`Int0)]|Compoundvref->`Assoc[("name",`Stringname);("value",`String"");("variablesReference",`Intvref)]letbody_initilize:Yojson.Basic.t=`Assoc[("supportsFunctionBreakpoints",`Booltrue)]letbody_threads:Yojson.Basic.t=`Assoc[("threads",`List[`Assoc[("id",`Int1);("name",`String"thread 1")]])]letbody_stackTracestack_trace_json:Yojson.Basic.t=`Assoc[("totalFrames",`Int(List.lengthstack_trace_json));("stackFrames",`Liststack_trace_json)]letbody_scopesscopes:Yojson.Basic.t=`Assoc[("scopes",`List(List.map(fun(name,vref)->createScopenamevref)scopes))]letbody_variablesvars:Yojson.Basic.t=`Assoc[("variables",`List(List.map(fun(name,value)->createVariablenamevalue)vars))]letbody_breakpointsnb_bps:Yojson.Basic.t=`Assoc[("breakpoints",`List(List.initnb_bps(fun_->`Assoc[("verified",`Booltrue)])))]letbody_empty_evaluate:Yojson.Basic.t=`Assoc[("result",`Null);("variablesReference",`Int0)]letbody_evaluatevref:Yojson.Basic.t=`Assoc[("result",`Null);("variablesReference",`Intvref)]letreport_of_alarmsalarms=alarms|>List.fold_left(funreportalarm->add_alarmalarmreport)empty_reportletcreate_body_alarms_outputalarms:Yojson.Basic.t=`Assoc[("category",`String"important");("output",`String(Format.asprintf"%d new alarm%a"(List.lengthalarms)Debug.plurial_listalarms));("data",`Assoc[("kind",`String"alarms");("alarms",`List(let_,_,r=Output.Json.render_alarms(report_of_alarmsalarms)inr));])]letcreate_body_environment_outputfilelineenv:Yojson.Basic.t=`Assoc[("category",`String"important");("output",`Null);("data",`Assoc[("kind",`String"environment");("file",`Stringfile);("line",`Intline);("envrionment",env);])]letlast_request=ref`Null(** Extract breakpoints from request *)letbreakpoints_from_requestrequest=letpath=extract_pathrequestandbreakpoints_json=extract_breakpointsrequestin(path,List.map(functionbp_json->B_line(path,(extract_line_breakpointbp_json)))breakpoints_json)(** Extract functional breakpoints from request *)letfunction_breakpoints_from_requestrequest=letbreakpoints_json=extract_breakpointsrequestinList.map(functionbp_json->B_function(extract_namebp_json))breakpoints_json(** Commands *)typedap_command=|Initialize(** Initialize DA *)|Launch(** Launch DA *)|Threads(** Send list of current threads *)|StackTrace(** Send current stack trace *)|Scopes(** Send available scopes *)|Varsofint(** variables reference *)(** Send list of variables associated to the given variables reference*)|Breaksof(string(** file *)*breakpointlist(** breakpoints *))(** Set breakpoints for file *)|FuncBreaksofbreakpointlist(** Set functional breakpoints *)|ExceptBreaks(** Set exceptional breakpoints *)|Continue(** Stop at next breakpoint *)|Next(** Stop at next statement and skip function calls *)|Step(** Step into function calls *)|Finish(** Finish current function *)|Evaluateofstring(* Evaluate given expression and send to the client's REPL *)|Environmentofstring*int|Disconnect(** The last entered command *)letlast_command=refNone(** Read a command from input *)letrecread_dap_command()=letreq=read_json_DAP()inletcmd=extract_commandreqinletc=matchcmdwith|"initialize"->Initialize|"launch"->Launch|"setExceptionBreakpoints"->ExceptBreaks|"setFunctionBreakpoints"->FuncBreaks(function_breakpoints_from_requestreq)|"setBreakpoints"->Breaks(breakpoints_from_requestreq)|"threads"->Threads|"stackTrace"->StackTrace|"scopes"->Scopes|"variables"->Vars(extract_varrefreq)|"continue"->Continue|"next"->Next|"stepIn"->Step|"stepOut"->Finish|"evaluate"->Evaluate(extract_expressionreq)|"disconnect"->Disconnect|"environment"->Environment(extract_filereq,extract_linereq)|_->read_dap_command()inlast_request:=req;last_command:=Somec;cletinit()=letcmd=tryread_dap_command()withExit->exit0inmatchcmdwith|Initialize->write_json_DAP(create_response!last_requestbody_initilize);write_json_DAP(create_event"initialized"`Null)|_->assertfalseletreachactionmanflow=letrange=action_rangeactioninifis_orig_rangerangethen(vrefs:=IntMap.empty;vref_counter:=0;write_json_DAP(create_event"stopped"(create_body_stopped"step"));)letalarmalarmsactionmanflow=write_json_DAP(create_event"output"(create_body_alarms_outputalarms))letdummy_range=mk_fresh_range()letrecread_commandactionenvdbmanflow=ifnot(is_orig_range(action_rangeaction))thenInterface.Stepelseletcmd=tryread_dap_command()withExit->exit0inmatchcmdwith|Initialize->assertfalse|Launch->write_json_DAP(create_response!last_request`Null);read_commandactionenvdbmanflow|ExceptBreaks->write_json_DAP(create_response!last_request`Null);read_commandactionenvdbmanflow|Breaksbreakpoints_info->letpath=fstbreakpoints_infoinletbreakpoints_tmp=BreakpointSet.filter(funb->matchbwith|B_function_->true|B_line(path2,_)->not(path=path2)|_->false)!breakpointsinbreakpoints:=List.fold_left(funsetbp->BreakpointSet.addbpset)breakpoints_tmp(sndbreakpoints_info);write_json_DAP(create_response!last_request(body_breakpoints(List.length(sndbreakpoints_info))));read_commandactionenvdbmanflow|FuncBreaksbreakpoints_info->letbreakpoints_tmp=BreakpointSet.filter(functionb->matchbwith|B_function_->false|B_line_->true|_->false)!breakpointsinbreakpoints:=List.fold_right(funbpset->BreakpointSet.addbpset)breakpoints_infobreakpoints_tmp;write_json_DAP(create_response!last_request(body_breakpoints(List.lengthbreakpoints_info)));read_commandactionenvdbmanflow|Threads->write_json_DAP(create_response!last_requestbody_threads);read_commandactionenvdbmanflow|StackTrace->letfile_path_line_column_from_rangerange=letpos=Location.get_range_startrangeinletl=String.split_on_char'/'(Location.get_range_filerange)inletpath=String.concat"/"linletpath=ifFilename.is_relativepaththenSys.getcwd()^"/"^pathelsepathinletfile=List.nthl((List.lengthl)-1)in(file,path,pos.pos_line,pos.pos_column)inletcs=Flow.get_callstackflowandi=ref0inletcall_stack_json=List.map(functioncallsite->i:=!i+1;let(file,path,line,column)=file_path_line_column_from_rangecallsite.call_rangeincreateFrame!i(callsite.call_fun_orig_name)linecolumnfilepath)csinlet(file,path,line,column)=file_path_line_column_from_range(action_rangeaction)inletstack_trace_info=(createFrame0"Current pointer"linecolumnfilepath)::call_stack_jsoninwrite_json_DAP(create_response!last_request(body_stackTracestack_trace_info));read_commandactionenvdbmanflow|Scopes->ifis_orig_range(action_rangeaction)thenletprinter=Print.empty_printer()inletvars=action_varsactioninList.iter(funv->tryman.print_exprflowprinter(mk_varvdummy_range)withNot_found->())vars;letpobj=get_printed_objectprinterinletmap,scopes=compute_scopes_vrefspobjinvrefs:=IntMap.fold(funvrefvacc->IntMap.addvrefvacc)map!vrefs;write_json_DAP(create_response!last_request(body_scopesscopes))elsewrite_json_DAP(create_response!last_request(body_scopes[]));read_commandactionenvdbmanflow|Varsvref->letchildren=IntMap.findvref!vrefsinwrite_json_DAP(create_response!last_request(body_variableschildren));read_commandactionenvdbmanflow|Continue->write_json_DAP(create_response!last_request`Null);Continue|Next->write_json_DAP(create_response!last_request`Null);Next|Step->write_json_DAP(create_response!last_request`Null);Step|Finish->write_json_DAP(create_response!last_request`Null);Finish|Evaluatevars->letvars=String.split_on_char' 'vars|>List.mapString.trim|>List.filter(function""->false|_->true)inletvars=List.fold_left(funaccs->letparts=String.split_on_char','s|>List.filter(function""->false|_->true)inSetExt.StringSet.unionacc(SetExt.StringSet.of_listparts))SetExt.StringSet.emptyvarsinletvars=SetExt.StringSet.elementsvars|>ListExt.map_filter(funname->trySome(find_var_by_namenamemanflow)withNot_found->None)inletprinter=Print.empty_printer()inList.iter(funv->tryman.print_exprflowprinter(mk_varvdummy_range)withNot_found->())vars;letpobj=get_printed_objectprinterinletinitial_vref=!vref_counterinletmap=compute_vrefspobjinvrefs:=IntMap.fold(funvrefvacc->IntMap.addvrefvacc)map!vrefs;ifIntMap.is_emptymapthenwrite_json_DAP(create_response!last_requestbody_empty_evaluate)elsewrite_json_DAP(create_response!last_request(body_evaluate(initial_vref+1)));read_commandactionenvdbmanflow|Environment(file,line)->letenvs,vars=matchfind_envdb_optfilelineenvdbwith|None->CallstackMap.empty,[]|Some(action,envs)->letvars=action_line_varsactioninenvs,varsinletctx=Flow.get_ctxflowinletenv=CallstackMap.fold(fun_->man.lattice.joinctx)envsman.lattice.bottominletflow'=Flow.singletonctxT_curenvinletprinter=Print.empty_printer()invars|>List.iter(funv->tryman.print_exprflow'printer(mk_varvdummy_range)withNot_found->());letbody=print_object_to_json(get_printed_objectprinter)inwrite_json_DAP(create_response!last_requestbody);read_commandactionenvdbmanflow|Disconnect->write_json_DAP(create_response!last_request`Null);raiseExitletrecwait_disconnect()=letcmd=tryread_dap_command()withExit->exit0inmatchcmdwith|Disconnect->write_json_DAP(create_response!last_request`Null);exit0|_->wait_disconnect()letfinishmanflow=write_json_DAP(create_event"terminated"`Null);wait_disconnect()leterrore=write_json_DAP(create_event"output"(`Assoc[("category",`String"stderr");("output",`String(Format.asprintf"Exception: %s"(Printexc.to_stringe)))]));wait_disconnect()end