123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206(* 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.
*)openCommonmoduleG=Graph_codemoduleE=Entity_codemoduleSet=Set_(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*****************************************************************************)(* Types *)(*****************************************************************************)(* parent -> children *)typeclass_hierarchy=Graph_code.nodeGraphe.graph(*****************************************************************************)(* Helpers *)(*****************************************************************************)letclass_method_of_stringstr=ifstr=~"^\\(.*\\)\\.\\([^\\.]+\\)$"thenCommon.matched2strelsefailwith(spf"not a method: %s"str)(* alt: Graph_code.shortname_of_node *)letstring_of_class_method(c,m)=c^"."^m(*****************************************************************************)(* One off analysis *)(*****************************************************************************)(* Finding protected fields that could be private.
* This was done for Oravec to possibliy optimize code as protected field
* have an overhead in HPHP.
*
* It can be difficult to trace the use of a field in languages like
* PHP because one can do $o->fld and you don't know the type of $o
* and so its class. But for the protected_to_private analysis,
* it means the field is protected and so it can be used only
* via a $this->xxx expression, which is easy to statically
* analyze.
*)letprotected_to_private_candidatesg=g|>G.iter_nodes(funnode->matchnodewith|(_s,E.Field)->letprivacy=tryG.privacy_of_nodenodegwithNot_found->pr2(spf"No nodeinfo for %s"(G.string_of_nodenode));E.Privatein(matchprivacywith|E.Private->letusers=G.prednodeG.Useginifnullusersthenpr2(spf"DEAD private field: %s"(G.string_of_nodenode))|E.Protected->letparents=G.parentsnodeginifList.lengthparents>1thenbeginpr2_gennode;pr2_genparentsend;letclass_=G.parentnodeginifclass_=*=G.dupethenpr2(spf"Redefined field: %s"(G.string_of_nodenode))elsebeginletclassname=fstclass_inletusers=G.prednodeG.Useginifnullusersthenpr2(spf"DEAD protected field: %s"(G.string_of_nodenode))elseifusers|>List.for_all(fun(s,_kind)->s=~(spf"^%s\\."classname))thenpr2(spf"Protected to private candidate: %s"(G.string_of_nodenode))else()end|_->())|_->())(*****************************************************************************)(* Main entry point *)(*****************************************************************************)(* if B extends A then will have a node from A to B (children relation) *)letclass_hierarchyg=letdag=Graphe.create()ing|>G.iter_nodes(funn1->(matchsndn1with|E.Class->dag|>Graphe.add_vertex_if_not_presentn1;(* explore if its extends/implements/uses another class/interf/trait *)letsucc=g|>G.succn1G.Useinsucc|>List.iter(funn2->(matchsndn2with|E.Class->dag|>Graphe.add_vertex_if_not_presentn2;(* from parent to children *)dag|>Graphe.add_edgen2n1|_->failwith(spf"class_hierarchy: impossible edge %s --> %s"(G.string_of_noden1)(G.string_of_noden2))))|_->()));daglettoplevel_methodsgdag=(* in a clean language we could just start from the Object class *)letstart=Graphe.entry_nodesdaginletenv=Set.emptyinlethtoplevels=Hashtbl.create101inletrecauxenvn=letmethods_here=G.childrenng|>Common.map_filter(funn2->matchsndn2with|E.Method->let(_,method_str)=class_method_of_string(fstn2)inletprivacy=G.privacy_of_noden2ginSome(method_str,privacy,n2)|_->None)inmethods_here|>List.iter(fun(s,priv,n2)->ifSet.memsenvthen()else(* We care only about toplevel public methods. Private or protected
* methods can be used only via $this-> and so should be resolved.
* Only calls like $o->foo() are unresolved and those methods
* must be public methods.
*)ifpriv=E.PublicthenHashtbl.addhtoplevelssn2else());letchildren_classes=Graphe.succndagin(* todo? what if public overriding a private? *)letenv=methods_here|>List.fold_left(funacc(s,_p,_)->Set.addsacc)envinchildren_classes|>List.iter(auxenv)instart|>List.iter(auxenv);htoplevels(* the inverse of lookup, go down in the children instead of up in the parent *)letdispatched_methods2gdagnode=let(str,kind)=nodeinassert(kind=*=E.Method);let(c,m)=class_method_of_stringstrinletres=ref[]inletrecaux(current_class,class_kind)=letnode=(string_of_class_method(current_class,m),kind)in(* todo? need get public and protected there too *)(ifG.has_nodenodegthenCommon.pushnoderes);letchildren=Graphe.succ(current_class,class_kind)daginchildren|>List.iterauxinletnode=(c,E.Class)in(ifG.has_nodenodegthenGraphe.succnodedag|>List.iterauxelsefailwith(spf"could not find class %s"c));!resletdispatched_methodsabc=Common.profile_code"GCCA.dispatched_methods"(fun()->dispatched_methods2abc)