123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263(* Yoann Padioleau
*
* Copyright (C) 2013 Facebook
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library 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 file
* license.txt for more details.
*)openCommonmoduleMV=Metavars_fuzzymodulePI=Parse_info(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*
* This module makes it possible to match and transform one tree
* against another tree providing a kind of patch but at a
* syntactical level.
*
* To understand the logic behind this code it may help to first read
* this: http://coccinelle.lip6.fr/papers/eurosys08.pdf
*)(*****************************************************************************)(* The functor argument *)(*****************************************************************************)moduleXMATCH=struct(* ------------------------------------------------------------------------*)(* Combinators history *)(* ------------------------------------------------------------------------*)(*
* version0:
* type ('a, 'b) matcher = 'a -> 'b -> bool
*
* This just lets you know if you matched something.
*
* version1:
* type ('a, 'b) matcher = 'a -> 'b -> unit -> ('a, 'b) option
*
* The Maybe monad.
*
* version2:
* type ('a, 'b) matcher = 'a -> 'b -> binding -> binding list
*
* Why not returning a binding option ? because I may need at some
* point to return multiple possible bindings for one matching code.
* For instance with the pattern do 'f(..., X, ...)', X could be binded
* to different parts of the code.
* Note that the empty list means a match failure.
*)typetin=MV.fuzzy_bindingtype'xtout=('x*MV.fuzzy_binding)listtype('a,'b)matcher='a->'b->tin->('a*'b)toutlet((>>=):(tin->('a*'b)tout)->(('a*'b)->(tin->('c*'d)tout))->(tin->('c*'d)tout))=funm1m2->funtin->(* old:
match m1 tin with
| None -> None
| Some (a,b) ->
m2 (a, b) tin
*)(* let's get a list of possible environment match (could be
* the empty list when it didn't match, playing the role None
* had before)
*)letxs=m1tinin(* try m2 on each possible returned bindings *)letxxs=xs|>List.map(fun((a,b),binding)->m2(a,b)binding)inList.flattenxxslet(>||>)m1m2=funtin->(* CHOICE
let xs = m1 tin in
if null xs
then m2 tin
else xs
*)(* opti? use set instead of list *)m1tin@m2tinletreturn(a,b)=funtin->(* old: Some (a,b) *)[(a,b),tin]letfail=fun_tin->(* old: None *)[](* ------------------------------------------------------------------------*)(* Environment *)(* ------------------------------------------------------------------------*)letsubst_metavars_envx=(* TODO *)x(* when a transformation contains a '+' part, as in
* - 2
* + bar(X)
*
* then before applying the transformation we need first to
* substitute all metavariables by their actual binded value
* in the environment.
*)letadjust_transfo_with_envenvtransfo=matchtransfowith|PI.NoTransfo|PI.Remove->transfo|PI.AddBeforeadd->PI.AddBefore(subst_metavarsenvadd)|PI.AddAfteradd->PI.AddAfter(subst_metavarsenvadd)|PI.Replaceadd->PI.Replace(subst_metavarsenvadd)|PI.AddArgsBefore_->raiseTodo(* propagate the transformation info *)lettokenfab=funtin->leta1=Parse_info.str_of_infoainletb1=Parse_info.str_of_infobinifa1=$=b1thenbeginlettransfo=a.PI.transfoinb.PI.transfo<-adjust_transfo_with_envtintransfo;return(a,b)tinendelsefailtin(* ------------------------------------------------------------------------*)(* Environment *)(* ------------------------------------------------------------------------*)(* pre: both 'a' and 'b' contains only regular PHP code. There is no
* metavariables in them.
* coupling: don't forget to also modify the one in matching_fuzzy.ml
* todo: factorize code
*)letequal_ast_binded_codeab=(* Note that because we want to retain the position information
* of the matched code in the environment (e.g. for the -pvar
* sgrep command line argument), we can not just use the
* generic '=' OCaml operator as 'a' and 'b' may represent
* the same code but they will contain leaves in their AST
* with different position information. So before doing
* the comparison we just need to remove/abstract-away
* the line number information in each ASTs.
*
* less: optimize by caching the abstract_lined ?
*)leta=Lib_ast_fuzzy.abstract_position_treesainletb=Lib_ast_fuzzy.abstract_position_treesbina=*=b(* This is quite similar to the code in matching_fuzzy.ml
*
* Note that in spatch we actually first calls match_x_x to get the
* environment and then we redo another pass by calling transform_x_x.
* So tin will be already populated with all metavariables so
* equal_ast_binded_code will be called even when we don't use
* two times the same metavariable in the pattern.
*)letcheck_and_add_metavar_binding((mvar:string),valu)=funtin->matchCommon2.assoc_optmvartinwith|Somevalu'->(* Should we use fuzzy_vs_fuzzy itself for comparing the binded code ?
* Hmmm, we can't because it leads to a circular dependencies.
* Moreover here we know both valu and valu' are regular code,
* not patterns, so we can just use the generic '=' of OCaml.
*)ifequal_ast_binded_codevaluvalu'thenSometinelseNone|None->(* first time the metavar is binded, just add it to the environment *)Some(Common2.insert_assoc(mvar,valu)tin)(*
* Sometimes a metavariable like X will match an expression made of
* multiple tokens like '1*2'.
* This metavariable may have a transformation associated with it,
* like '- X', in which case we want to propagate the removal
* transformation to all the tokens in the matched expression.
*
* In some cases the transformation may also contains a +, as in
* - X
* + 3
* in which case we can not just propagate the transformation
* to all the tokens. Indeed doing so would duplicate the '+ 3'
* on all the matched tokens. We need instead to distribute
* the removal transformation and associate the '+' transformation
* part only to the very last matched token by X (here '2').
*)letdistribute_transfotransfoanyenv=letii=Lib_ast_fuzzy.toks_of_treesanyin(matchtransfowith|PI.NoTransfo->()|PI.Remove->ii|>List.iter(funtok->tok.PI.transfo<-PI.Remove)|PI.Replace_add->ii|>List.iter(funtok->tok.PI.transfo<-PI.Remove);(matchiiwith|[ii]->ii.PI.transfo<-adjust_transfo_with_envenvtransfo;|_->failwith"metavar matching multi tokens not supported yet")|PI.AddBefore_add->raiseTodo|PI.AddAfter_add->(matchiiwith|[ii]->ii.PI.transfo<-adjust_transfo_with_envenvtransfo;|_->failwith"metavar matching multi tokens not supported yet")|PI.AddArgsBefore_->raiseTodo)let(envf:(Metavars_fuzzy.mvar*Parse_info.t,Ast_fuzzy.trees)matcher)=fun(mvar,tok)any->funtin->matchcheck_and_add_metavar_binding(mvar,any)tinwith|None->failtin|Somenew_binding->distribute_transfotok.PI.transfoanytin;return((mvar,tok),any)new_bindingend(*****************************************************************************)(* Entry point *)(*****************************************************************************)moduleMATCH=Fuzzy_vs_fuzzy.X_VS_X(XMATCH)type('a,'b)transformer='a->'b->Metavars_fuzzy.fuzzy_bindinglistlettransform_trees_treespatterneenv=ignore(MATCH.m_treespatterneenv)