From dd4fddda9689343f34b9d21677510598b23bf5c7 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 7 May 2026 15:57:58 -0400 Subject: [PATCH] GP-6790 - Function Signature Editor - Added support for changing the namespace --- .../help/topics/FunctionPlugin/Variables.htm | 4 + .../FunctionPlugin/images/FunctionEditor.png | Bin 38281 -> 28405 bytes .../core/function/editor/FunctionData.java | 24 +- .../function/editor/FunctionDataView.java | 15 +- .../function/editor/FunctionEditorDialog.java | 236 ++++++++++++++++-- .../function/editor/FunctionEditorModel.java | 63 ++++- .../java/ghidra/app/util/AddEditDialog.java | 34 ++- .../java/ghidra/app/util/NamespaceCache.java | 3 + .../app/util/NamespaceChooserDialog.java | 12 +- .../util/parser/FunctionSignatureParser.java | 36 ++- .../editor/FunctionEditorDialogTest.java | 198 ++++++++++++++- .../parser/FunctionSignatureParserTest.java | 1 + .../editor/FunctionEditorModelTest.java | 7 +- .../actions/SpecifyCPrototypeAction.java | 14 +- .../java/functioncalls/graph/FcgLevel.java | 4 +- .../graph/job/BowTieExpandVerticesJob.java | 2 +- .../job/FcgExpandingVertexCollection.java | 6 +- .../widgets/combobox/GhidraComboBox.java | 6 + .../model/address/GlobalNamespace.java | 9 +- .../screenshot/LabelMgrPluginScreenShots.java | 2 +- 20 files changed, 601 insertions(+), 75 deletions(-) diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/Variables.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/Variables.htm index 5b3c50ee17..0ed73247cc 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/Variables.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/Variables.htm @@ -327,6 +327,10 @@

This text field can be used to change the name of the function.

+

Namespace

+ +

This combo box and browse button can be use to change the function namespace.

+

Calling Convention

This field is a combobox that allows you to choose a calling convention from the list of diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/images/FunctionEditor.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionPlugin/images/FunctionEditor.png index 9913c311e2f2addbb9c0d833c6a8575b6deabb46..b2edbda95986f99e8d7feeb66aa40345d9e4bac0 100644 GIT binary patch literal 28405 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 MrzTq}ZSwlR0JjXWH2?qr literal 38281 zcmeFYbx>Siw=D`GAvnP;1a}P(G`PDvf#8ib?iSqLgQjtJ8Yj3zaCdiidrf}d`R+OQ zp1SqkegD0BR8f2NmbLeuYpyxR7<26q1vznK1bhT2C@5q}2@xeIs5i}!e@eJFkUe}9 zBcGw5en3fz2&%Z~9;Uz7m{KF?t)%Vrp{EMYn$j|@rB$N~t=l)ndcWS`15;yOs@?7b zA_c84JF3`1hYl4v`jR@b==>n|wG`>%J#n_P7YEvjLzUaDy|^z2QE|DB_Vr6Hd&kCw zsf~z3$LWbP__u#&#zlf9qa;In`-Y)k$=b9W8u5)^8NbiJ4v6_f$Go>36Nud%UXai2 z_Qd9o3QGDF3NF7ienbmSU?+51s4NN;}a-;GBTiCXyWXOPj4UGZ%x zHOc2f!m}Jw`e=_9vY z;x@B&gFEED80M3Wo`6k6J(_83fKiS#F~~>aofO+6xA|H|^K&c{2g3-$oJbbV%7j`ug;7aGL9^qeDg3HP0ROP(9Jd=E6uWib|z z{Msk=!%(`=&{o%Q+8z!KGsMD>VilG8e4lJZn1e++d!mCv52F%-79tA&WSQmCC=Nfo zXqv4H7dt%_GiidRyfr8$$^tLg1MRARf6txhDS@M~jW?!yI<8s_HgGxR%mnPmDWV1g ziN`otPq;L%c->9sx8c@To41krcX!WF!J(k(E6wnQPr%f+AGWC^Gdedq1eI)n-;ddT zIJfdz?oP4-q?xip(w`WPr?_~sRkqLh&b|)>UI=5qkCt^Rd(KV@qMvn7UuJXcd^i1z zTMy43Im8lLvRw_U25;rf>dOvn|_i|mG(h<-FZd3MeRys-)r zPR*F7HCu^t$#+c{@dg@=!28n!wTTR8vPK{JAyn3vI%9DlHf{+fe#pA5B-f?RC(1bW zAlKVveK+p<_mZN19B9bU<50~si>||MIKaHzCC;lbcHGELS7b`Rd6>WZ3pYf-9@D%X>~UZ7?V>Q?*JXH!gr{Y!+0Jng zeYMpGYcxnugS}8^7K9C`z{}NE^UkO1i zJya*)HmN5vHGo+{4~xbU0I~CUY^a0|`7rVN z4`A$q9uD?l6PMFgzdgsNahgrJ8(TRN+tg-m8KAq7XlM7>C;dw+ju3k@uF>5VI+rt( zX7P$AQsacG`{>2671@5-VK1jE;C;jcp7pXPaRzx+?EWUZ-yDN9O3%owO(>(s+_KF~ z3r8S50&xUMagw2Of+hZet9kN*t)0BQ!F69QH2Ro;QDUr)9}Z!g5WkrVmFM^e$DPWJ z&_9v~BV!;RwgfmdQjAeuZ%&}`X;r7Q1dpWi*l9s|uW$V17vL0)nt1#7 z+<`=*T)yP^Aw8Xn8}2874_v@*4iz}I>y3fr52IyN27C!INfH*2zM}x?1SP`-iIxvG zLL}JBVdQArp{>`sZ7N?B=E=Y}II#cyW#J4)lZVFNiILsqlLp_Q{;By-roXOR!lD~W z+6j;8McXAU8uEz6D6?p1JTi{tkNB@aRN!iqLEk0R-m{Xh#mRBdT6tEg2pf_zF+R(A z2CC*Re_!`aKkL3?!^>UMh{ed`+7s$Gl!gYX^-ixd+Z|PsISi0b&@Vs6>3Rsdf#+ z-?!7j4?lZX&>UDzte>6Fo_7@og*RfW`e_w$rP${fPN|-R6O)b`06ZtbB5cvZ+2k{D zH(6^y=o(;_>+}9m-NuY$*-8LtZ6aU3L0)eh6&23Nb#ojer)XKu+{9{@^bJXsm)t%R()^q@qmQ< z9UtjST6ljT-)BFZPuUyPXvi39FLVt!RM59KjywHUx#ZljeeI40^xL^yX6uJVH30b^ z1e0yMTZ7HC85@owNI6s?Z$;I_ltL@$=F(FqF+R!V{Upv!%k@Mf*&LU)QE}Ndbfg|X zDf$+{*&7n8jcWkWB_s2;0R?aG@nD{seE@K=eSJb9uqN;j_ii}hO_V)DIIRM>87WP) z9RQvb#KWiq&9t#&F6G)H^0tI#gt@Pak;O-f`Ie8Yfljz3H7T34nQ3S7UU9PF{)s6( zWfCGV@K|A<29jN}gLxTWF~*p6)!1aToV`k9qEXh=atathqOgtktu7QlCzy>5Z?oR@ zo+IcYaDsfw`r3JKkA-P#GFzT)y2igtt~y^MX}YXp7aGUNAC7l|As((d3D6u9aI)!& z{q0T@&+(@td3DVQAK87LcGExc<1Q-=l-y zea8DbEOOA3w|%~*|D{OaHgWB0;pQ?7tK$Kci0JExdTf}^2Yz@eLi#1on2+o2VUZKo zx;zg962u;eL$2O-9i$MUT7TZE*ekAr^w;mGw*ryqX@iL3f=*n9OfpYrNZ+c7NKs!Ck zp!Necd#~SJjypZB>3mOmu>vk(T;HF^(2m<7a}A`QyZ|qGA1w-fIe1k;7c%)?ZXH)f z#P32YB?K-9gcg3w>n57Kc_jicdUNGwFm?t{=ZAgNCMW^}<{bj{l=2OG@P7U?m3imp zivl7fmqL`>l0csz+AP3{g*z?;Z_2s>BwTB0PY!Y)59;4J^ zvvEyR=KRyGuE^Lw)VtIUmQW62IM2^K`^ZhWQ$fGEJI8~e2D{L3kD7KO-DX5m!h^q6 zv#mD@leB`cWB#$tru?OuEJwpYgo4`av1S;pBt4*ADxs7+Q6MZZH|XR;Tnb#nX1!nP zkch;8!Z9PJdATl*E-fXE-$SO0x-X8@37#umNhBW&QLn<+z($B5TuY9F7kr@1W!Hl})(A{@+=LakDRUe{|I`7{leDPgndmt~rSxj2# zBhC?l!*=h>(&tpA*@(InRIkHX#jpf4Si0Kn)iMP(nFTx7#n9CPo~V`97lHM5pSa3c zdB)z-SV$IJ-od`>ioAlO7`aTR3z2- z+(5L;h;5JLwVY*ec1zyUca$M9EV+8}mPn8w;qWC!gd_>0k1=i<`>Qu$dh}M~2K^Xb z9Ea{B-R*Sx=10=hK1x!YU?}|Rud`PtyMl~fv6-gdtNh)1(Q*qIkAVCn($!=>PB^Z#vUN$o&$9>8GX<5o3!!K$}pz*O_~m(KV4mT(}-- zSo&B!KL?zq62lG#XjF+5Z@h?AoY#~@?j_K>N_i~I+ znO8RNb4uGuo{qdzZWX(ua@T4=uY`Dn4QfX78tGC{nfHp)BvM&S1;gAsCBzm)S`NwZ zc$onzA3bBnD(2+_y{f^ST0fm9!o+}~dDn#H%ZI?Q&LzM14fip!*I{pZ%DKq;Da_kS zhCiU7Q#eAP+gvkD9AIqq-Wf?g``;!bK%>|`TwA&Ho(fUeb?f!l*wOGCyF1!v18_Uf zV3o1{RZ1$x4G(-{12ToWMpp=iCw9FxL|LQ|V$hJoJl5ki{7x1Y8q~o{CGszJ5D)^9 zI7l0P4j_UBM2IQ5Ox~&Ns_h6u#%LxpIcd!u4<>*}E&l?E?LHd+l48ccKCY|3S70S@ zc~xSJ5cES~XPzowWgoxxfud4mnfZ^`!TD&WYEF1#tLM&0e;@xg@ua-vi<;Iw*zboQ=tf}+4-6_Fz{kT%k$8z-q^!Lulv#LKRTY_vKII#E@ zIkn2dmk5#gC`u~F3;DXhKU#j>7JN0QB?PDtsj2?K8uQ5hHkD=aWRmVh!0Ua`|9*6? z3}JOn#WAbOJOPD`;-m+4?&T;0xbVx&Gn-Zw>A-vCSJZ^m%7Ny|EKm3)EtoCAa0Sz z1o)9bz2_K(hAAqFd6EV9ViYF=D)XqK$d?A+sK9dB^yv!6(wU3edVfaLb%9Tt!-R;) z>^{QshNc)il-2C6dLjC7WK|*T$gnXcSUyJp747ra+5;5;7c{W5<;6S$)m>@TRModF zjzZ(3ZNr==K_xmtDY0s1vDRjr=ynrj%uZ+ds=f28 z!-(o?GqA?)jL5EXdLZ9Qxajq@0M2COwmsigow@>6BiN1N7KRfzGIQJRZ@0Gw5ls!b zhmE&GcfC0&IQK-a528-^J^Du7oz6RW7ep!O>~%K_e)sN7=C7K-8rNZZwnxwy{d`n= zPzd-ofl^G1sdBmA)=)|ths==J6{4iObn%U?WWw1*Ox5A);EUOe>3QOXnf2D!f>lh4 zB$la&b`w?_Bd(+6V(FbmRUx5?4S2#%Muf-vkROp ztPPt>kmvG$Bqqj0+*pkzC|(yO;joR%Y};n^X9-TrymxJuKI&)YI~rh_TZ$# zklMl}jLpo5G~~n^}s7Liy*aq2RQL6t&1Bo^b8fNdHUn!prhjceD>7bC~Rt9#r#O~G}&zk8-b*ue#Vwdv_G5PaU)%HB~B~c+Yz-Ty_kA& zLbkT9nicI!neWr**vI4L?TjyU!36YTntpiH+|+OQDz8(;E`ZSynd2xt8cOF1*h||J zOkd*)!(qT08N-Rc+gA7{B1MMTC&tI|Rxgy4zCwNc$JJ#)f~UuaC$;?*RtGWUvhKTc zkvcO!nJ{1rUDiSq+oP1PMZWfVK7ZX2>^hdT>Pt^l>qgOq7xLcby4c%a6q~Grg<=0D zG2oq%>EP8;%H^4$;?UUI+_VCj!HF1q?<-m{v?qakH7{k0_JP^x8e&04?ZuauX3I(5 z8V<$=n~3PZl@_lT751%(&aTV=P3{L?SK01EDgxBS-B$AEfP>uQ)RTL-DS?5z1KBXu zZKn3of=6q;w{LqM>%M}nKz)1*$4^+9+%bytL615&R*Fa1BA2rnF4r_zo)V5L!(1y4 zo>B;!u8Dl+tuFc7ON;Wnf-Wtl7w`1d7&*2zVvmoi0?s6qt6x1o6jpFVjdmTMJ3Uuz zM24A|?u)lJK}Ooky{!Iy`YFtX+b~KVI}M{Cjd39XYt-xv=Cp`VtZg=tHy*cRuDnWK zxD(d9W`w*JwZP>O7n1QV)Mt-M-m- zksKBCZFEsywf@;D>8P%6)yvWz*|GL|&8}DET;rxv5Bucy8?Ng7Rrb{i8i?{PH>qi~ zq19w5soZf_f%et|@o#@1{WBRD$B1t+dCNcJ!T4DIMSOZ3Tofps2N$* zu3Qg%13Uw7mg%>f*uFE!otfB2p1%8W=-Y_0|BU$ZE;fWUL8 zD-zvuC?6Akvv8BQEw@PC)@06BywCAP#y z$U1twa$e(^=X^ti@Z(mFs&&^ZMRz zZFIJ|De{6QfSNVTZ26^iq{VsOVtsrcWVEId$vK=tVkEan+Y&*)t5;@KG3~{dZ!al| zReK-FHvVm8_A!BDapAy3B@YwXEL-|J)V` zL$V#z_ybx4!&Hrye%Y-k5_%R*A$3RmlG@pKmuACoGUO{)ORLl>ROG<}uwdhb5wL|X zq847F-tN4({OQz&p6^tZm55iDK2sR8&NLI(JvtFqE9M%HB8QIt2S^ksvccIoUHzFJ zuS#}|@Xr@o5uECauD!rmLL5u#1}jS zpQwI+oAU=+7i~xrE>vnYuL~AildVe}J_qCj_bv+4j(qvkvg_G{Y~h}WfKl!vWtuUK zIS#@7)mziD#O;r2s(nq=h^}k=;f?tx^;5r25;@=;JH)cJmX>N~`~}NO_h^y&!3`v0ELKWpt9weaSng6zMQzbS73ZHY zbAz!2T8Xom49HH}bD%B$sCx{im@%lHi@*F1`V5&Ni(ysKI4{Fyk}X!&4e@TNU6h7~ zR1A`vd?xa;n*=ugT#gA$#(4x>Io7u!lO?>Q5;ctAOd|RhAIOwwk;dJX4jvNJSG}Qe z^!7HS?Kb*DvX7jZj5+E=Ub)s>+|A`e!E=cK8D5G7G&j=C-+t@8xKMrnK(+&h`uq^=wRLz-we}GDarGD4Y~0C zs(1?w1(0s0?mR<}c_xb2Xt~^ZFB~7mvZ9?!HN8Vy0#h+l356o0J*@P|j3;ICdE`)v zMq&3?(vU;#|w;}hSf2d?5RQCh>d z#Kb@B4_{msYrmf&(RLIMR7f3>&na6+rpg@hC^cXIn8Y#`;nM~_=J!lr>E7)M_vNt8 z+@Tcxlwrt#WHpQ(MrGNDh3xy@!W)#Md8KB^k=c52yI0&1-h^{Kyw`0{niK0ss=Tzo zDNnaH;XV;VmC+Y3AfiD;+R<8Q$m*^>I~+9ku6((LQ9?dElC#xz`!{=>YD)(7*e@or zwgemT(|(Fon7<&sK%EVaZz)Z<%!HeJa$+J6aU(f(xy!{d=0;JI<75Fuz&K}te3Q+> zGewkeCh{={D<#y1k!dSdxy$~Y{g@e10)>sz_6zuH%)`8&rk2m)`!tBqkvsjYP*$Ou zX&?k54;i9oF|s_#D^;~h=6|sGiVQJdmNx18eH@Z)>GY3o?~xwvV@nHmi`FiQW~$$1=;Om5n-Avrs{*L$-jQ%L{yC3PQA3PXvU-_ZWU)V_ql`Vfy*r=KbxA`mv?t|W~$ zyW;M1t<9)iAv@5iPqP-R6y;A8#nFuG5gF-mE>pfA0b6}bx+hE)o0e-baC!7VmQU7K z%_v0JIag+MT8OD&x;8Ute^4jlYjL}yygQqy2n&g8X8IC$GD>FbwOJp1863_ry6o+7 zwSKkaG7`nZP+zy(<~ylQw9@J+{Zv08HS!RnDtQyKagfHJ!_VrNF+iKCP9=tKw#W0< z-V0rc41y$*agGrZt}bPVT3;X{UK0v)iwX;Qg3-CgOS;9Gasc~FMj!6<_FqNF1cEf@9BK4zDRnc;F?+8mo_g(Iu- zK1WRv?#xS>6_|vT<89BT%@!9TS5PY)X6=ep9htQs;pG|YxWJcxz=YFS7Fq>a@iv~g zE_H>ImpmW*+d>>5oHw12QOWZ6bDFWHY&q4Y&LJ8n0lX*pk(aLj zOGOdcvp$vr{9kVsU{kR1E6&4X8vEmMHl-S}DYv~9_=RP!WI1~4>FV8ZZK)d00phb= zpWhZg?o-_Z3F9aQl;^Fhq^6Z>L-(6U3(G8gr|_fzryqc<4`MnE1#q3YJu0EF^FHfa zE$;mNz=T7qIiWF(Z>1o98OG80Q6T-DZc_7Gs8+VWF9S6upeQHO*?V&md%}+o4sS`Q z9W{QZQ_GLIRZSq>Izzz({eubl-;XL}dB@`93?cs1-W7(;^sf@Zt9GCw;sMbB{%RGp zqA#YBJSC4L2687jM$mgZE#k(|NU7_F(I=U3 z?CrWn#iFc?w8&6Z(ps(Pnj`=_%P%-0%q_G^?E;{~DPsaM5S?F^TSQ^Vc)!7fjckBL zjl+r*C#e<*$v1lmQ=YVo-KHEa!x`q0bZ~JqnFcP%=S|&K*taQF9gC-di|s%kJsp&6 z5e4>F>%~jf`$Pw(jvy6lamxmkAhw4q#kqW4Z|E&(A-pvUjZ@GRizfQU#r>Q;mL!wDQF8_| z-(9jQHg-sL7i)=4hY6jSd`*~>%2h8}ahBDvG&eD~EKnz6Rqn;mJeT^u6+0Ji?`A%M zO$N@0^o&nW$0w|>Z!$~mR(v;^s0}Q&xH;#q>MjMy0CwHi;x+W!FgvD%E+U@hIQo~7tINrG~uh@Y}r z^u-LIB}J441flEWEH+fJ+_Qgq%O12 zH&`6$c*GA!h9dSolrQ>j(*&PBbBx)*u(#kJ0{%D}7C8v(@As6!Mz)g@iPrW-d)i1W z%%R18$!s?fMx8jpz*MExd|Cl&nfF;zATF_gHbymzU7P)YM+85lGbF3O5kcpn9CM2N z>y1xvWvCyDQfgJFN!=d~;GF5A&M4tC8vKB+EOQi&0^MO5)92T?8zH{j9YX+T#<}5aPQgTiXvwPiCKr=VxNL9 zcd2Y_8IH)1IW@lki*oF=!)JVt)yg5U&`v+Pt0lTL9%7Mo`IZb^Hx1nN0Hcri2IQW+ zzY4Hn`w5!%Zjm8!nGbD@Q76d*nOVY8?=vJuvfdus-{OOU+VqKZ9iKoIZF! zZ>Hl=yo03Jth2r*PFfTd8q=?VnwmdV#wN-aa-N`)vZdY6J|G7KxQ_9$KEUyRDY;4? z-sGLxyL;#Ck;cu=JXtIuUt@G{>R(PU(Lb(`9~=40*mO>wF=0{&qopFZ-{q`X9A5rS z?-b-6{48^&<`t;v@ZA`*_!`(aCMOEVvLO@Ap^ z8Sk-GJzdhF<-Gy^p%qWvzA8FU`^;`_!Set}yVaI)iu;Wt^gqUcn=uj!z`f52DQ65I zu0a7P4}a04I_i9c{^Kxdt1G}3kJHz!4)r4sdJIaSQnA;SZbRzx#kJfZ+px%9<_M zX#P+j$fm$gLK?>1(EzfJkw%&FN+(KMTs_-3#eXXT^G5{YBGl}V=X$%3hO9^2T2zj$ zR5ct;g3W%A!kIhR4ISs9e1^FJMX`ij@W(OjRkq zJ^%b&O;~+2#}+KvRBl>LWO8Zx@?qirb2|+u%(oF3^6^et?4N2(=~6r zYz{U83-FWe-iu$#uZ*%*vtlnq`4Ep_;i#15?9$Yfy@S0eb70e>%=%RN$aF;b<)wMD z^G_Rr`whUIt+>ex<>NOl!!ULj9M~S|x`H@RFf2b$sqO_h5^Sc}7O0yr&+xu9a;Ul5 zJ5<)tke3%GjIr&3stw1zRUXCR=*7>>F`bqYd0~!;C!KM=qs`x0(g*SV69AZ{oxAPK zn@8AuBNg8=TIJ_`ZE0*)l5a8l$8CI>llE!(T=K#FUlAB<3W`qMoh zKbgXNpQo$-bog?V)e>T6J-V7Kju&O%tA5RH{Jh!Jp@kb98hW+AsPH&=7=Df#0sL$v z+gBQY__){$j^qTh5nvEMdb$+Z_{mj2mFF87FC%7*sWk=qh??#d37#C)@bz=?)|!RI zXv_EV4UEV|py(_v)eNswpQU~P;&`%$QsdVkHPr_$54xXyJvwZ@o`)s3kyb~3I3C2f zZ)s(#)-0}cXQbeDy`$wnTfyQTo-Yl4(1=Gr7w$=!I;y1SWZamm4xjy_>cseK{Sb=) zY1V;+=Ec=({0_Np+e9v3H(IfwyxjJrt*Yw?bMt7=ko}n`FBHl++#n1V{ce3@gWn4? zQrtCKw#%o!k|oiq)f`-Kb`QJR!OJ8SDZmTz2@t`9EQ*RS5oe}E`=2u2#Y@9DH9GPn ze-eS6MA6%E)OiA_mxeTuR&sEd8~)aKV}DI`hn}jbIu)g7Sj66@{Uo~Wt=F2Pts5j2 zX$>2xS%kxb5lkz?{g)~+7?B!#OilM`_T%-+4>0a79H{cyI}d}F5$mz8mMLmhpud6nFK*ZWCFqcgZ^7)qdRb%X|-Ho8r=b}kVY3wU9Up>tKeCH!DcipmhAQ?=+}2W{z8C44wX<3zSEM%+p!O9Jy{EskR|Bt z^EQ^j;Zbja2y~x2y>pC{zSxF$JyWVMO!W8&tk*XjHFL2;5|VTCOMu_d`@FfacQS1wD{MujX0-!Yu!lPbVtt{r%#GO% zt#Zb^8)Dz6PBbT#J;q$nk62rn8dwX9v}>I(ZL3j2rZJB8^7}Er-dkHr&6ZmY&u3UB| zJN;|1ckx-_=eeT7YjGHnj`QX944Q2RbCn~r0&6b?YV4W)eQt`oOGs<)Lh`ls12csQ zFT5-|)|FV5H#(*-i}e&ADQ^UvL*L3o&L1uX%dzH(XgZK33R*qY$)fiq7fF`#dFZ8 zi9!SWN#+g{D8On_gT-h5(bw>*f$g$neyN;hC7lQ6@c-XFYBW{`1r^Iskzo%vJ$N7e6J zE(Qt1tJs7ZQ~`zJUD1M9SPs_A?$%r5`ntd!*)#Jmfj<<+u8mFqx74z znF$wN+Glu4-yx4Y>mAW5aG1hw;_6htk$?H6ti<-%S|DiRrK`{Ui1$RY&OZGI z{^XU-e4f*1g)lkrAuF(K2jwN$fAq@S7xa(ArQ%UCDN2&)Sxu{__`l|8AAAuQ>Ih6x zHV;P)Qa@S2(`1wEHv#8w;%!?40a%pe`|!UI zf{M0XYR2v(Y?EfwLTF&|$uUwwy5>8}@FsOG zCf_O^zNd}*8!=yjBSdy~y4+C4!xTGU3ML&B7O=!IY&j!skq@3Y2Tgfia zN9Y^T!~c(4vx)ANFe$M8%|j3vi?JCB3nj%xfri|Dk41(y+F_~%&>V_Sf75TxhoJ}V z=okvV$}ZhkpD8ObU931b(caOpSU}7+JYLFDjbtA_P@O3?uw0&Yx>nqa`jZsD?u1zG z!j#$v;y2vJ<3MB;?f)+~BgX$hsbo2Js_M3Vm>W$J{8#brydAR;qy9}&?3TI z5Rnc*+Kr9;^E5LhvH|Ie8x1QnCf#XgapW_@rMHfg-_d!)m_HCY6ZzOx(eCXBY_1zijhY@%b-=LF98zUY^(er6kl3N`W3)yh;&{ zilC~m|Gx8IwxHAK03Qksz&7fNwH^F4aK5Vh@yze7ogg@#?%$)+ake2R00Yl-e8RSx zkGkaxq*4F3)4Z~coLLY_*6oXtp!7~J1&5KBX~;txdOX?FC?b}_<$*@s9*5zm>fTGW zijX25EepjiHn4M(5<4j^xf3za*-R_Hu}rR(J7WIQL5skxy#D3GV>NrYG_a(6AviPj zC*I!PUMl+$=jv!_{zibTs(p)S(0`!f#TR3xeU#rxLJU05??nATFN*y^v77IVqho_% zoixv*aR{vfkt!=RM;}jyM0qK4pGVKfF+hl_@G}e&t2)f1eXg` zo~5H-DOfvZqV_D!3Y0Zx`p!*zBhXUNnP?!3hnI_m8FrM%uO92ks=X~#%YQ~@REnnh zAi`{1AOyOK4R0So|BT)9r|gUb(p}V)3s}rbn%6en-Ww~9@#6D)ec{DaY<6T=$T^n{czTV<;ww$X3CAPY0Xrb{`WD> zM)N=JJmPMmk8{5b2HNHhjY2_FgPa~gVqn}V%1t#XC$HlTZSgd1h;){vs{Y?V9(r+? zD~4A<_r@_8Hn&PKCdU9`rgs#iS|K0eKsAy9Qde2cMRDv-U+_JWxh$(t=kS~s;&lo% zo@l70INgk{XzVw!D)mF}K7!W1DXn%0^8X~*RV%#v7G?JSqy;^QmelFK5le^qy)NCwu8puFR`Y%0zjC*q-CTi$(r002`$Ry_uV2qERU-8uIjFg4Uk^;+^w0<-*sky5 zFmtD3>!Ch|%w&Raj&1`A7OOtw{nHg^Jy@-U61Ra0J^~+UQMjoGs9%(ROOhbIEHHSs zNU1#>ZG3jE-drMO`pjc#g6O%C=-D%TwYWwUL+thCm!*6PM1r6$N=gvzS0u}*^Oh!q zaZYHO!IW1j%b2Y+h`fl_)0!zA%gXQNx(Aj?T(O|)>9-TMan)H3$rh=A-phzK39GQs z2wtngQCRHK{)hWQfbSNW0CwR&{wwZ! z^7@KtzbG{s2bx_TLnur^`kitr9C2-;^aGVt=nf+m%dh9c#y1khQP#qTi!d zyhmkt)69(<#VHq|MzJq%t9S_Awb%sWkb$PaM*fw3Y3cM;KOf=kPFjT9p@G;7L~#)H zd4UjbpfwVgoRyLazKC$ck3#f#YS?ylY={!t@BYAkyK+n}Nt(M6^l2p8a+H&;OJeT; z{->|TD2f;iO7MBNyVK-BUF4l(p#MMmv*#iq%XGnn)*e}Chi|YZvZ@YSe2xMpSo@gX zO4;5rYf~+Ql;?Z6q`kURTbx)AgGDw!ceIvDRX&T3UdiTFWvpDZk0MhsLBhu`TT9e! zsB;obgWY8}x52c$bs;OfJ5Q{Y834M6GR{F~|J$-;^?#)dKY#)w0hS~!@a-0(M0Adg zh@VREXT@dmXSe8-b2flS`ZN&SH$L25dLRRbje-TSj-+C!nE4q#WhQzUEGbIkNj%2( zC^ue}NGA$`;wma$;eweVtYI9c=xdzA0hHS8Pts6?3Z71h!h|ILi|)zE@Bs8CN}|*O z|1QCJVUQrQ-k>m7Xl6>3=lr+CuS5Gfcn=#}4myMj^x*T~vC*>wbx|B-3L4Drqr{G& z2iR#K+xM%G;xjWYF0(Ur`I4$yw*A_OR;%;$@ouwHZ;`{!!^NRn#O1m?tHd5S4(erP zO*>NTy7C3L%lY5Ms8t!8e67w;i)iJQ=~Rfk2@bv>H_fGj%>tSeu?9~U2ZN?xh)(bH zB4tMghP_Er>A^qomk`PN5M9^<(@SAv+@90a#&Kc zK-KcV5#8#&GXeff5GRT8;|Ik=V?zl98xYaSX{7v>a2{yb!u;~1x< zTH}bk53aG*!SYDTuyg%~k@BMB@1iS>xFA(mu@!>y^}v!3(2U|qcE?qK`Y(3M)DZgX ze@MW00b65jV4jt4`h7m9^T%BfFl5JfXBk;*3oy~F>$pVQvu$N%N|fMLSYRVRXS8x| zi#OjOd1TH#KV`0**;NkTLg%(uUV(u*J$Nh@ATI05F>ut`eCrJ!{&+dze0Aw~g|yFZ z&TliFQM?{W4QvU}L|gWIUEN)(+*z_zF*VU74K@)gA+l0!YjlE$KY`!8SGaq%?xi5> zPXB>PK>!8XSD%-pqScHhmQ^I}GE$g+_4ovIt^@6A8ak>}Po9TRmuiE5S=tR70%)l# z0Uw&b@=tZtv@LkcsaBQjQiNvHGH!G2dJ zz0I%nXmyUE!{NlnuQP(Fo|Q-wk{MQSVj;MH3~=6`ZXVb0QW=N!kEF@~`lXO!sX|;> zkM7e%?2`f#3GN5gOFmu^({1&D)aDavwHX@QZ4`2zK@^{U@Y2iIn((v!t==I-i{5#*(r>ETGZlgddg;@d zo|j^pW)L_X2vI#BO(`3)wAmsI8HHQqM7m9l@|U>4_A-c1L`k1JBM|c0BvDq8X zmCwZGAAu7n3<}p^0nQ0)e%i0+e3P~JT5NeGzy<~eO3KQ=2!7=KWong`mH(yG0|zMp z!Qb88iExOBi1@=~!ADn0VRlaBkL&R&cgRBh_&S>}6#}W=y#DkYqg@rUIE9^xhn=g7o9zZV%A^_Fz{a@_8WmFyA zmNuG%07-Cn3+}<)3GVJB!QD3wA-D(FKyY^p?h-Wk#@*fB?G}08(|ykA?)&xk_m0aL zHQ2Rx)vBsx^O?_l)~sms3P}G$et#R{Qo`<@YKhrrw(&KYkj?|i{iymHfgcl)2^O*GB7Am$dys2F44w6x9IMpJvoy9qt$cQ_m-f{DqkD6(% zE>xElAD+$}Z1eXyx$z&^LOV?q?U-NE-?G7-n5@#;cn9T|+J}e9Um|1>Pi6e}%b{xx zT6GEW+9pc;qreZG-JRW}n%&ytagkYH)p*oeLY8K6Z4Q>Psj;GodC*NlXAw_I2_E1R zPkcCHT@l2ujuCb_YpSjIzQ zu4Gwdw63l#=9;F|-3IS+^i|2#`nM>)-Pw7oe!p-xd>Y@|PgD5ezeJEq*zT!xqbszq zfv0XQ^i&OeaiaC#dtDd`+UV@TF~ZfIf4JSSyj4{rghtMerW1TA7TbPsNPtL|HoPX9 zJuNwpz|~0!=@9*3_v<$zj71Da)-Wl?clz?ZeN9-rStTF@K(#Td>bp2+hSTAG73TXF1+;Gqw>bs^9a)r;B(fDNk1)1^Y4jwB85c{6 ze&K5SlWDDhY=fj<<{Qty-IlLOf4eQuK@OR0a)JJSiEKUj@lxx?t#Z@7jtlm1V;v6) zC(6%_D+B%G(U}x>%D>xd&)z5O^x=Z+#5%ei7~WzfSV>2=dN}G$B^s|mZ8W7yEPXNH zzQM2VER@E?sXfBd!^+FE-Y=YxF|O|ZL2MrUbVz$;$iDM8532^W511Q}!fO1AewLPG zK3p<=qcDhh`oOn`5cwm?6#e7&Cvo3WJmnm`{I4d<*mS3gDsZ%laavGFWRf)j;T)yM zvLD+y{NBA@Rj`8yDT>w0uI7IC2gKwCxwK}}IXDP-Ll`jH#&a~L>Sb0lHBST0Q(e&+ z%MnZXbI&>#nh`>b1H|Bp4|__h#&@->oet_dm&Z$<8>s^nMuCHt88-73!*%tkJolui z$L7E=mLkXzWJTZKqeRFIS+)4B(NT&QN{suh8{2V?j1-q$11|z0>LjX7HcHatUodu$ zV{1l(S`k|IB0M^qWLW#dh6}Uudt>boYlzX<%)I_s*L7Y(An8$_(Ut)1z|4fSmoD!D zddcaM=VPiLrDW8jcHAH?M|zCEkZw{e3VakGs9WAU+xRH*TO&)(BByrW9~z|+w=Ys- z0G!RH8wNv!+=~C*-o-Yj#ofQG(_i8qNLC?&WFo=-pK1xCVCeExf-o3-WDK068O5@> zhQ_c1R1MkH*4BO81ZtE@JdR|XA=xck57Ah{xlRZEv&RGt`Tw{t@>HqI%%8;8ns^T* zKKq8x6HE*+!HjysOj(+?vhk9zP}1HQ|Gv$Tb#jq$rW!zWVlSIo7<_7l#ld;2_;?6q z3`Z#}CR6*<_MOB43g64`w|N9rWoDB-w5;rGENtxKMUgmw;I~~1OksRT@aa<}f0Ul& z|D=idbJz2@8t6<^bN_pS=u>H$!u_Gpw`)FzLVnB^uLqa zS*@BBcz^jBWNmy8ZKFNLE^Uyl4Rl8j&z7eDDFVRxj|f1#dv!}?LX-G8>Qh9dat};^ z8%8zf5{L1Bl}%*QF&+73Nm|rujA#jc`Ozb>M^Rm9x`#V%W42aDb8TH}>uVWveZ*KR zW+%1YWP^84Jppgen`8LV1<6{swy;JHp%T=gCV#}x%8+U;rRI9nA}p`AQt#}m#x=`Y zrmkBXQtqlfHzS^t<_z>sDvW%2)~~~2>rF?v_pYfa&neJ6G>LJg1g17UO<)Fr-Q^;v zzjT*?gaX)o@fGVYZRIjFV6}limy?8#BAm~kLf-Hs#;NhQnj)JrCDK zdHZhnY9zg2nGDvWjq~N7?W-x|lWjEApI@Jk5M(v|z}fqf=!4vgKN~SFD|YhwS0rr8 zzcyq9TzEPAHrOzX43KrZOjL8?9Y+nS+U+DdUUqqV0(f z7L%^W$$asnOGLmZ@;E-sG$st|wf4DVyAJ_*4|XF3&Cy`G zOaW$Mn+1ST}weyE0}wFPt!+Hd~+l%^N&dG|h+B$jS-<2*T02yK|BLJBh%Z zyLG`Sz#6Je6?_@}netFEwsQpiEq)5Kis_(o&*xfhXRU+pVPLLa*PEO{o+;5Ix}V2= ziPGj~BEO`EZ>6uN{kn}DBrwS_I#%-evZKuirT*1zP4r7@pGfU97(ji&T>aZ&Nr=Mp zQh>zTGmhZC(u7vpfKRt0U^3>io5@Xg@z+@|vfy&Lwj)DbuyRIB35%*TdiO3!3 zM9=7cG=yy0_a=!oI=J%mFkv>igYu(A(0|-Tc%S zz1}-F)?;}uVB^rKa9=!2~7PiF5EdF52zb3-KYidzYukk&%vi9E?W zMEZY*T5LBrVGWNB4Ox^~35sSpPW(LLzhy|LLx73}$J*H&L>P78#BVG*l{50lM@HtG zO*${kz{XXDs7GoiC>OTmD zr=sHeYs&FDD4?^mv#zeL*2bUFKjc-fFmv?&K5A%a`gl-4cPR&sxPZGf)>`mZLC!$< zB%3&FaG7p4(@VsFOr82UZ!_K_ToHuqlFD0<7b_4#D@rh1@{C7BG@o8f85V&d0Xkvj zupSEI(_`9Vz5=LF@YwRI%y=v-pBx&;485+o@xw$_fkjyU5eoNUYLk<@)z?? zgCrFLn1C)^B&H`=suT8BBw0Htx3Ua^1V)EPO;{Q6zigCtKZltO zUPZ~cThgtq)0iSxU`AfE;s!&8>e(^&1e*pbaVI9#tGG(M{{Cn#bmQ#s0^ot3?r z*u9vV*-+F5BJqg^-#$9y`fUkig}dk+v*Ge*lF-oX4&q_hBw`5tQbeQEA$veIN=TGI~oq)8FUzU=W40^$F)$^#LZLL--O>(7Ccm=E6;>H5k#Z@A-GJ#udiOHuK6DxF2o3;kJ zWGCkR78d;mIo+4=DML!qos~;M-JTWSd_<*|bwv7Zmw^Gb-aQEhgZpMYB+l#gyF5aF>DO%fK{1 zx(1l-83m88t_?28cmaXb&6BbCBZ0SiMAx!jTW5}FynL1>*Jrn^KE&4Lu@57!KI5vh z?*BvyhS`9X?)Wo7Z~8F66U^n$Odx_u#^zQhza&r}3+L zo&F%0UW~6>P5}N5KEwcmALq zop0(wrS!IV;;%m06MTO2a<4)VP^G$l3!!CQfA@(F$UlUS8UllQchMQT%(Oa%_WTSg ztS%O22PlagqQ15}uG22cz;N|D`{L)YmyJe}mXa_F$v;^4jE_0fYx(VJOKQ1?o`fUx z>Q_%TLF=SByFfIqD`RP0P2t>GnW5g85$g?E{N;3ybJ;1;QuwhxN4dj-+o(JPntBz} z;ejXp-Nsqi-W@^f6pHiOU_An!9Dl-%*0>1o0u`Z~Gc%CLk@3*E^R3sosKm)HDgM@x z$);38hLlX-oM6*aPQ|x(hB7mgStHvcts$rw2uL4ke44f50(_+U`Z(X-c9>`1`BaWb zaxpG84anC0bah#~HQ2rh-|uf9TO&13n6u_gBWuxNp%i8g^vm=iqk;z&TbR|d-LH&c z$rACj`8z#|g8C8F3rEM~>FmsOl@-^7{wysq@pVK;8Y7 zQdQ||S^nOFuk_i%8Q_Yi|u^A)!gyfz>7Ivf4du^XB8-^?Y3mKkO~- zO{7Wr?5*D^yEG`G6JD2SMe2^0QAyz4)+wjyXkqW{U~7Qv7704Gom^q18&qTd^y;DB z>ys97_Z>FfU&jjhsGLRrFoU%_@mE7cr)O8WfV3ZekL}R($o@Lwx^q(p^Ie%u5+o-o zN{*Z$OKh?tO{Xu678)w}_`dt{Bn5Xnl;PSPbJ{c#nbx<0%O!*iSE&=(4q#KrcO`w} zRan2PMoX|u&Ts3`^lwEj7li~UiEDE8wjx9SGDr-6XXugG-ogM@{R@5p3$A`AY|3IS z8dJto10_{CRRuFcFomgM+G)y7HOQ0UsOPm3#2T4Xq>XOwQ3P2({E{3>L)z&f&^n)u z>6Y;|eIfX+TeAF(HnV+4LEU+Kyu5PerM#&skUEu*wrD_!Y(#M96_)w!s|&SuX)5#@ zQ!^{HS!%Bf#VQSSH(oy`ae!p4e0tuHf1Mpl47UCp9MHmn+$a zYX1T+45-Rn-EJ5Cz7;kSSzqc3Qw{3mO9=tpX=*k2DoNC91&nBxt5{2 z-~QP|qCP_D$%6_18H=JzvVMARxbx~?4igo@xnF1g19_ViamF_mzsE-rIn%h1e*Ke) zrF%>T#;HD&B**eTjJ1~MacR->E1TVu_lsJm7Yv3PMul~hU#e-W74V%Hi`}(L4u(}} z&7VpqOP0k-Dofx>GFMG?;ZZ)~yab0=m=M2wtupWw{e9g&UYg-0di2%a4KIKEk!Pzj ze*OAleIxNZf8h)Ts$3;k1$9i^!aE>6&688Q0u}l@|1UJLuuk2Obm%8G_J=8-=fluL z3RDQkpxOA#Br|h#?6^6;^eM)#_Cc(}~Wi z{%uHSc;VFc*}ZJTYI%Mi)oQF9<<%_Gl37`q3BV zAB(nWytFurZ$mecp%MT?7>R|4hlh;3oJH{;mq*6sHyVi%s+h|bs$6IyJ^NUBs%%qF zzt3~CO}i@y2BGh`=$w;;CZXZ1K3$W?OQ-c~ob#V!+Gq8#1q1}ZHc8N_v0|#?eDp?3 zS2>?$u<{1!FU()o5}O(5K^Pb40tjV)(_7s9=HhPO-;-_Oy|tA*`Q~-Fc;UU6r&@xj zO5P`~YMRfDj0;v9Er0A*9@>c3vM-C^0L`b#$ea-oB-_{+6sv15O&hlrcL1phJb&A~ zvw4W5-(ARz(zAk!Q9}$JzTXt}etD*BO`vmGVi2FpHo(mtq3Z#mjAmSVscJ=Ao4!1_ zH@)1p-C2P-d%`Keb!=B_RIRou zu2QO|E)6xZHdFEK+mOvIlM0aac>&4h!Xykj%Gi!P`;3;y9%0tHYj11$bj{S!=NTIH zz(QPqNIF}rv!UO}?=jcWxK3pQyl`G=e5gg8WOH*S`?-{9zvAR&9``^^)&zTh%3V_T z*xdOaHj9?~Lr<%ECJrKxPI%TA1v7|RK>&>U0P_fn;gYfCIbe65erxSktZmQ(+`dU8{B{K>lXDww!9 zJ5)K`tx@b4e3 z{GhzZ2f|htjD*fX@B$w(!zhW4eR*48OcZI7bRP42i1zPrnklM8Z53pk>wP0o!74On zi>#E?8_$!@tCO?u`Rg3>&g-N&QiDov@*|;_=-!qRJS?5|EhhY~mgU0%gkmrh zd^LWCO7Yb02EE~Y8{=lXH|Wrn3aT~8xK;Tsddf2U&Yi)zYnPq|%zG8b_)!)T6^wjMVHu{~HuOzd!x8ZEB}|IQJbE)ox5rc&IDR_R-b#jYTgsBDf)EKQ^hi>mh)SEFa2H+Jmwf_u7}~fd zDJytqD``KGp^5Pp46@0MTJx%h*jA;TwBxm#?G%CEV1j{NBE2>(?Ntc!K(MT=8YXUE zL{GiVl8JF%Am5oguC`!Wzs@;77yc5&MH3NORcIIPC_d8yd4KAfglTo>Z0ci(nxa|- z1UU$jZb!`(ayBoHs~{(Y9rj48IZE=lJHKfaHy>0lQ0vq`!(ngQk<*`Npha3(2=0Tl zt(~BvqH^NPeNPM`=@j&EUyZ?>a`*q3tE3)8+qz}sdfGt zJgMJ{3&-r6=0}bW2|&jrrzE!(ji>yU6^&-+nyJ%;j~g9%!GZ7296BEDYF)Ro43!Co zF#mfE5wp3biHanbCC%o@n(JNxJRdWpFi<3-ngdMOK~Ua?hGg;mIYSEm*uwB+0m>$( z?=pO3{Yy=)stvc8LnkdB$ir34zwHTPtthN?H5SJsxup9DU3@mPj{cXhuKpl?!&kM@ETJ{GqGRM^7a=EP;sV zf}c$Gh)LeuTES-A^41geardL9ftj(fo1sfk>yp9z8%?_|7j$6uw>e*6yJ<~OJfA{0 zSRu0f?Or}R=Gr*KrL>vMW!iYE z1Ns$p1~jnOM)Yvy)4A)MMWF%op?y zG8x5D%(WkO>mvGpd-vtGmwiT|Gz7TM8RFU!8!~(t`w-l(4i_ke zv;7|4i+;6raL6KqX~|r{MI(OZ0X@WU{a{+iIFPA-O-!iX9|82scCgRz2OXAIen1+z zufq~+5W#Fe-A2#>G6@Cp^P1eMWS9)0WNgsUVza~6kna1=*Cz^}t<#aFe!R%Y$aIy- zXZqzkOO4@%6nR5q0fzPm`M0%emWoN zWkS_^SHmH61z%kaA5CP($?a>)FzWi`R;J%Zw-kA4&ZU1|GfugS&^*7pAwCp07r@;| znl0iy?4Hzw;?_tQXZujK4>4ICrhHJ6@?+_{$!0;wt8I;JZn8JM>xOZHZ!s5eoDi@( z+Sp{iMc$Zf^{pm%GCSW;W`ci><0R4%MJhf&Ki|290Xm$=0cH_=d>I*JJ0DXrW7WJe z9VIWU_fzo**N@8`%~_6~c^Rz@(UIIpS}qWtixktzMwKI9I9NHZM7k;1LdF>uJuRgl zG1!NmW>-ymvI7B!aE!U<+380#=>#rQ#u_#DpvEWWaP@O`9-p)2vcWO8$8G9l)gj6h zGtTPfX>V29u@<~0J)=#dmeX0tRMlYO3q@1KQchFP$kL(*`LRi1U4D!;!S9M3test5 z^}+kI)u9sbt77jtkddwq=ESSe2!1~l>6#)dSKFux;q>6ks;WCG99qNEpJ1evnik^22}Zk$ z12p4F4C3qWf&?7Dswq?rw?+!EcGk^Js;`P4eJL#_oNIPnyS=?gsyP5SK4Xul{rlHL z59UN=I4}`C4h1FIF-oJ9&0wO9db{>1wQiW(fH>VAtCPWKkQdfgz1RKJWcc|WyY=4n z*ixCOjiNn|_F+s3R3bZ`H)r`JwU8g0ma^!oKrcq?YJS?7Uy$&%l*-F!7(LqKj#7FLNf z-x>y(=8=s06aE{m6E-Jn0WF2J^U*1esqmx4Cc9>>RcTDN zl(Kh8&Boa9+=(K)>H*mNmNx-51N$6J$SvhDbJQ81uWEi0-OXSpAkE;Zpm2j!!mL1@W(STN+{LVyicIZQULgLUh2ZvxNynwULgHQN zkL~Isb642sL!8CTN=8N|Dkf%Mud@rU`V85=a5@D%yQnm=w`D)g8yRHUH0LJWRKH$# zQ8n>0JtWc%pOwk6jI5u&Hs$zucOU3SDk9rp6PeA5Cbi}a;z+&%2>MyDmI32$IUV22 z=#=bi-kn;L2|PEc(?`YChpdk?Enqv_^j>g(1zrhRABf#+Il{-7-Xu9P-Xkiy0!_)r zPsZ_pvKzlp#>qtv`5Vh4=A@%&n(t%9C0m1;)fV`&{2;|PU(>zS2cAs+L`< z)m{_V+3~kJ!Q?d@qb@qH=3LjnPNd82qquIO2b^aqI9fBEn^lG2n>;Emujvv+q49g~ zJ*_-YN@;790KsV*QNmJLPPraE-&))QPK&)J_G!|BMPmlW@m(JQ+n|p{2rc0l4+RTFl=i%s&G?CT$p&7nyF zh6-n4BGX6jd2JUXPac2@kI)$8EGEW3-&J}*i|;uz5z;2lChks6a#(FuorY{j@?h}m zj&;>NEyhQ)5D@97;TTLzY!s}U#H2GKWMsU0hERg4#ImIrUIV4i@a7paz4!#3@lr5P zfuSvUWew}a&ua$}%>LqV6-!Uoi&hol22$f2*R} zT`kX(=nmjbS{Tl_{*Ls9URUtepH&4ARk%q;p~J~4gJtSOUURISu5SP)fm`^~4Wt7s zI|l~@&gm3$ii^M zZdqrFr*|C@KvJ}n1A{Hi)%Ate4LWZS)*Bu@)1c=V1>mHpC0rjYm$Kt9E3I=B?zSaH z^mTi!V_*hBXAuKfgMISN8#OoT()wxW@YRf38Uk{SM2;D4r}9^y?kE0 z@&^Hq!Dy4jx%(4(-5yNiQ}Nz%F=5yuRj$uEVj z=AJFJM7Y_zSYxdTiN8;Xm#5c*3@i1@NUF~*}{03@P9`P%^d#^#87y@MTpPZqoZ4+U{xCY z)5h<4YfJ=lZzjbm^(MIbCNC8Z3z3J0Tr2!mfd`rt?$}Y#7n1SykqjimVnP2f{arlAtWzxYm|9N*py}dJKK~b(L}*0$5xG9e;{Wm ztR{Dy(%eCpC{y2#b`jOgu4FyHVZ#pE^0q6htS5PDK&VB1oSGhwM-bS!%-A3$;0YD( zs3Q*qIFSOq`|)|>-mKaxkk2*oXWr%7B&K_O#s0k_hruN7(mP5z<`}G-d5ba5clzbpG6XZ-a&Qep6;RiMMuW#G#dM3 zve8X6|~f(oZoJCm~RFPVfG)s%}W4boE!@a)GD&4Y^J(V zH?In67H@UVc0_!)q3h@3s~Z}J%iLMNqn%5L%DO(Z2*r@U!IrDEz745`X;(Fx#Lj)k02!Vl)WZ~Von=;x?+9<&d6wUpZbX?vY?L+ zF;QJvb(Li*UqYcwjF`I$?=9|cXL)&Lg2S~hP3k1gGr45?%W0+Ry!zx*4bwpt;aWdT z8C6M1@pzE8+`eA5%j_o}hmK8BuPcl#iB%jTCmoPluSLi&;#!qm4X_&K83V}`MO%1F zy}6~c#h6=lXW(AqkT={M@U&m9f0FODn$24HI%DRfpyCEJ0tiSs!fmWGgQVTFd+C9M^hb#&9{SkZ5+h^CvKN_*DtpbEu3k99s7H*zKZ@M`HT-D$tejOo2H z#^pMUheH^3XUi@_c(0)#+%e+iGPCX29Ll*ipWP9V*KXG5PKClma7oRHnqJii-bd|i zyiK~iy_sFRp0D~A>FVl@80(3vSXcBWu<>#4%T(Q1yG21J99jiv^a~E7d=Md~`_pWe zUJ1+s2aI&f+Sw9(hu++pIvXGaP8!kZng;o_OPl&3D~TdD>W)SN*lPsPX`3;BgB_t=C!+Auip zs{Z=wuJb*~cFplCNR@ZIRua#{QuXmRo!2^Cne5uss+fco9eJZD3Q{OTGh~9#qGHKb6gxLVt~o-+ECiHkwQ$0he;Q)ZX;u8 zH|u8x>uz^Dy_+D;9c%rqji1h3dBK>^@6o2!b_=*fHk^tLsi5ss(VxeseiQBPW);xU z@$$M@1ppUihKh?$0mJ0>OtMT!RdMWgG3*Em_An|IbNp+x#L;&an% z6?S31gEMn(a@}W?k#_e@ZxH?&aGC8KKP1q z7U1E6$zYhItn>B$;?>fMcxetI`P82Q59=EQC2%qE$A6*6ghB{$^Z7)71X#QP&Y#oe)Xx7S2M=IzmS7|#VDv*^-(wGHQnz2fI(Zk|w-9s}wAL}@g+h>F9O zm3rHecj}b6ysYa28s>A(;?wRJ3xZCj%O@_KK4~2*uM2|zBI)Dvsee%-Amntp1~U#l zjW4*Z`djWc(a`=6tdBpVZzN#!rxE!u8Qm1tYcoaSxhNl2?cXig8$8}E^j6#*eU6?Y zOKFD|uD)E|!es9!idi%;Lg!!DnDI{6`qV0|lQLyA>jOJ7W3z;{_0gT7x;oWc{M_zi zTQk?x=zv{+_)ir=#@MxLgKweREI%Bt13QbBoGbRv_I$%4?GL`l$7|16heq*Xc3M_e z>z#y6-z?s9^;N9V5UnU?EC+{{43b5nue`!mqZHHQbc!EpVsBnMUbFG*Pak^qE;b`$ z7ull_Lfk)cdw{t#m@!n^e)!DnsQ#Kjhi3HUJyKn8FFxN0CNio=vVM?G-!`I+Tc53* zNJO47cn1yVx681xn~9i<*-ZKH({71c{>fz#b_X9?-udQ|D4%eAmfXyAJ6UNB<)M1p=l+)K2kxUntqT(t6CqWbNz6 ztn}?+oBmCH4by#FnO}-JobwME(pP2N^cPa(DH)k`aVMvK5+|Xue7jTTkh9uPy$SDf zCxV%pEnFdo;{`t2)nGwj<<{q^DT+`+CGDcX(d(22gslh4WU^+h)3p21=C%7Iob}XK zLLnAu4H4po9QXzHR#GM9ScY3PLU?wOT-fw=jLC(^53q%lGYmqmuyuSzixB1L;a&1Y z0WI*s2OL;fz_*Zjnsu8Y>_VDnf)sCrot;EC8dLW;?S_93F7@=|5Dv+$v>CJ+vAn%V zcQ6>#rfZ^?dnZ$bX7COHovv|*!4IYaj~15kX|479b#eRZeX+)qL;nGuv&PvvLA7?Q z(|^yvfOh(cSFRzGB8bhIOVdM*wd37&bJ1gwYn5<$9A+Jkn$~EmN zOWxErm__ahTqd{N4BwI1B|vxiYmP)BdQHumPdbgr!eIdK%BoH`j8AWZ>b$o$VYamS zB=*^B_pGvVZckF60E=H|cD~)LNZo}yEe&_G+F(`d29E^#BMRWlX-3!~F~-?o(f%(f zo0Z7+pWIBR?FF>&6`A4u017MR_EPjD>Fy4bYetKWIp4x^W;3Z=;0fisB)7BL21aZB zR?R0{lhA#9oxgrd?@(?? zm@3Pf`rWV<_E3~!$Rj2>5X+YJajDB{)a$sy0RSvF{41+fYO(}10{!Z?ZC+SwBHTTCpFjD$;m|mlbWt>>N{_C{~|OI(|l~e&$oU`0f?Dv z(d>e%FGzE-RT>0pE#wc%Fr{=7 zfzmg#PX8~#IDQf9bV?Zh4%@*uf5L72q=UjxX>oV}0^~olMfk^;;UZ9JV7wndC}YRg z2?%8X7vRqVph(MlKl->D>PE-Jd%@?mao1r$VLp-+##$P&}lnm;Z&(NpMB%7J_*oAksZFt4@+qft^2@GmSf%G`QGy}^e zmRW!RZz1B}NBaE>#r1!zng4)Rz+L=XrTu^JCvJl0wh^Qs&oWb0{q=kvI^~WoCuoJvX9q zekbJki*L!uZvoW&!7r`_LRdQ8j@7Kp5F67Gqw(8DqX$+vgiL^KOS2kGLG;^iv-cMkCa5OaiA{N0F9OoGb*4v1WIghj$5SAxeESfak`&2<*QLxF(iDfw zRw8!T(j_S)z3y^=|JjV`PK&z)4|qP6FQ&(~lSDrSqGk&<`Z9ztr3v5U0lggKiZy%j zraa~5!PFOdPz~3eP{-#5cI(v#VHiZK1U?w8+p+-L9k+yTt~IBcowrJ!bKo zy82L=Qr~l?Y3U}Xc3QNB#IT<{W)i$j8&C3NV!`W@i#FhUww4P)?QrF<|55hT2=!(voH8r@W(m&X5~|F6y6k+wWI~oy!qfe?Coi@ z2TrAz*~4XJwHPxf?(dS#E+UcJ^V6@dVrPc1SQ-m#eHYlFjsM5h@o@{eJD=Cz0&=TA zv!jNP>7NPl)5ji>Kcmn8|8CNf+6aJQh{wal-QC;UgPOoOfw$*Op4aL1-UB*3cY(bK zR1`KU8k&IDJ=NFUFc+BMt*GA!+u? z;JKpSaqvdgelgz8)TE7}6^C0_fdXNDUi3{Ol*L~~D6k24ESrs4!Zfx<*?{q9 z;U1=@6F~_M*FuhJijsqkC}P$*eK;gmh$vs-fNpO%XY=2F~k~ZEX_+&SX3FMhBm*yf3OKzec@1ITr^+ zJNvnX9h1}F`NR06Z=ZyK7TCo-h&dL2 z9sFQU^SoDsOR!qA`@R|nKxkK-4{HhjENfOogbtFw+;B0JRcm91deZb8uU!e5Fv6vu6v1498 zjdqQL4x<8@X1b0-9KI0N4`+F7DD~Rz>Uad8uW{0N#?SU?#jKH;_J#NR~& z3`&EjLP3U4{|0LPuc;|O|_pm1sQ>Ms=pw4K^rBs zJnM^XRw3wsfFkuW8cFysqOh5GPGlr3=l2aqx78N2g8F4%V{srkxJ#GjzFbhD$_5u9 z*o01#E0FeLnX(^b@v5LiRf^n-KdGx?r=zTn68A;A7OIM+X}aiZe{{b9x>f|f!A6-S z&jU?IEs*hOe;X_oXrM;lBWm_&46^K^px6^_kEp)+OCHGf>ndzjqD5nNHbWODOT_GS z2lc`fN(dmL7}=+B!|#1>WThlL6v zEmu=w+`wY44_QecA7=H#Fa|dQbTuxlu+DDXU?>I;x=XIYP#@o%tB%Es>7lqLbarGU zF*bzV<(7OD@pTJNx^i4}J7RN9AC@1~YJ438%UvQ+KAoY=VgU6ZHh4F{gZf=@QoMIE zYAPoiFKLM-?P8PV0^K?HUaFTl6+hKtX~8D zeYItFO0^l^gPW|DH_PV~aSh{{1^^j_r@!<22Xg4;=p%dJ>mC0xeTkQ0qqO9=IL$t= zT6Dya8?^amX%BCvtl|FN$!)d>ufK7Ei45^uMPPV9eP~3|exqU}EbEprD6#3z_;?TA zTFw60-#>-kL>GgA1RRO+_@ev4`@$_Dk=8Bw=4cd@l1ua$e?~z{%G7!Ggip8s$VgmP zOzFz$X||3L6;JJvKV@C97mihKhEt%p7)V)d%6r0iQYQJ^z20dr%|AhCyfSy<_d+da zuvsvESqdo~y|KPDmXlJ2)4CJ&Uia$NfY({<75FYTI!8Cf=rjF78v!X@D>y(P2ac>v ztCw9Vb$c11MmE5@87=NdLM-mZ{?N%rgn(`|J?sm?qBn(tf5RQc7Fh~=ZTYO;EyKW8 za4RLw#VjFtGTCC?T$wjRLR6i%U^t>Z5`u}YdRbC5RO@@{Kp0IrSK?$P{y0<1i_q5d z&)??KbK57}u6;qs_DcBDzmf|_JjKliJ)WOk%!%oDn=)$2uzFZNq$J?&E_ zlDXk}IxE^&1Psgbam-{N9ct_tP}i4`mPx1TaIm+UTqcz@QI18TR~AK58$sd4(yQIP z+L2H8tDwA{J-_9vX?=eM^8%vvjfM!y!cLuV4U@yTl(5^O?qzjkXcwliD(eo1AqADJi08i4S}n!R73dWVWnru(=|=ESU3I3iNX!uFX6d5Wd4&xt1CUvS9Zkgtl74^Md7&8&LnuMU zMs~QT5Bjd`6_nH3sys`Lw1egt)p(b1u4lI>z)bDizh**6Bt?*EOP3S%Mv~9@V?SZc zhX+ek0OV2Yp_EY8cR8zuRt%4=g8GBoE#=|cSc{IQbr>LX0Yl~ zvVmnQr5roYuyaoMD#pkZt;LD%#H_JDQb7vHHFLNWdnLQC1kGS3e23(2P<=F}rCCXe zL7qyJ*l2>LR`CSsJ626s>57cJIm?T$amXtEkhbA--KCBJ&?e!qN%;=&)`YTDfx4d# za%tqNfc*A>ub_M*C`E4+@9H;)FbyxAA|Z3NGNBZuM?9NN%d)vl?);2b`S+(vHo~Ss z9tKkS*{$iS8nvPg?Vdq>*3OmWO@{80OI=Ml?*#nc<=%7mvNq)3`t zxiBM+?!aOWeM$M-%f4*g1T%&i!4X8#MwDn*AFyiCyx2}>4$&?Bw8Kn%%=f5R%nQ>a zQ@BRij8!ARmnRVF3V<1Ws#6v_)@K;1l z6QjTV?Y*IN@>%kcM44wJqnK~C47xcH2WPxk2+&IlEI1#DYFD~Box{^N%Fk2}_;9PO z{0dwAf#qPacm8PAQm8H78l}1=ROvkLl0cp)qC5RWQ7%e~2A(;sobp0EI{xH( zd4F;8j~=HS=jZjh+o(E20;Ys+b)gkl&ak{)4w=RSiwQ?*1VS zSpR>X;7@2NWEUcsj`-}HEM&cBeFOY|K?e4RG4Fo8^W0KBJ7#aWldmBL4 zRH2mXc21(qNi0~71oU)w!@PcdZA$v3F{nhjZsi3raEWyG)gMxPqvix(@CuR!w|Y7| z{rvr7ftA7!ikb()mRRLZo$N{+crVBRG&@eg-}5~oK{6!+Bk15AYKl$z(DGUE@M>3A z*SK#9CP_`iwAijO)YsR-P+w;|)4B>uwEWDVJUu+}12s!TEJ>XzUkU>7R?teON2TOf zl@9<_xe@)JyVti&`1S0Fy;o=&ju7WRSZ+ZQ(+VZ?~S!WVW7RdgXd{!nIw$J5<}{ z)M^h&YXbY(TEI4N!>t*8E1w?i-C6$X!AfbyiJmj>#N?#~r5xxDX7{)6(B@_>TB!K= zY5nrM=kHa|)XQAq;Um4GEJK6uURM6a(sKuQ3$FaB>bkR2pV{WF*q7h;OSAT$YQDe_ zFcB0=$4Y#3mz(qZzuo`E#(ACYZ*_TjU)853+t>EjZ!5X7+v5D!$NRVC zEs5V=7=3*Hzq+s6*8HvA^Zb@!tgh6HtEO9D+GU>Julatdw{r%=3Ik|RPV;|scyjxD zm*D@K1oU5>dpMbYa?9jL7Y_7(-(T_S(Bc`VYI==w7tC6ym=&DfE&5M><=NDA=UyAV zJ<$B(&Gz@*pBLFqzZG?Uf3Ql9I^Sp0FIGO*MfOIv|8?zEKRmfIExxwARbA>Z<_y&I z6?;$QySD2U`~5olRAk@NV}HKyo}Bvq{$a1v(|~C(AQ2X9^FL(gJ)d_ezB1DP{LV@@ zj^!N}3VzP}9{HV{ab@L|IMMD*=Bqrs=F4AO%RCN>7JWQdB59|NgN3Wiv*`7_Lb4x^ z8u5KKj{Fi}F?G-K?Hf0{ugeLWsq_8d+ImivrIS^z&s%$~g=0C;GDI41ZfX0x%IWab z0~Yi5^ULoSoaJS!9Jp|X*@h%%=a;W%zdlx;e{VbJS*P0mlP08bSGW zS#p-;-pY!ghrJV)%k35PJ-yF%;_M%14&VLz>GF}7qWfKkw=`z8u^n7}|Lu(YmObEk zlb@;lUVG}d1jtGm@$mt709yLzEen85y$G#5>-1&mN?_M^0XW}+GKT8kQ>jI1d2zr7 z^Oei9d*XMV@#frD_2mQW|E~Yayfx2wL(GBqyT;|bOwBWYe__AiOV;;YpC`q!R=kV8 zQ9lznvShgb_Dr7J(lY{Q%>VW0&6{skrXlTYj+l#2MelQ*#6E$tXzPXP%b1W$0fv+E zSN0?%$i78mq+t%u2FS9Qlja=ghgcA2=*pRVrm7`mb<# zGr#cB`a3HXUVNC6_w4KI7p9xuStkF_QWsn3sG!lgb5q)u7ptfG-r|od^ZPaRG}Z}? zU-P%V@D64KmQ$R-Wu03MWrMe}U1rM?l3FDVF)u%IJ3D=kf} zv@J|wTlWnsVBu^0<4;3F!=t2&e_Nxvz#_AG8NfxLw}*#7z1a06P(eH!bpAOfH@CLt z&zApNzpA^SmC(l?f9yzJ^`oZwn namespaceChoices; private JCheckBox varArgsCheckBox; private DataTypeManagerService service; private JCheckBox inLineCheckBox; @@ -167,7 +171,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod protected void okCallback() { if (model.isInParsingMode()) { try { - model.parseSignatureFieldText(); + doParse(); } catch (Exception e) { handleParseException(e); @@ -212,6 +216,32 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod super.close(); } + FunctionSignatureTextField getSignatureField() { + return signatureTextField; + } + + void triggerSignatureParsing() { + try { + doParse(); + } + catch (Exception ex) { + if (!handleParseException(ex)) { + return; + } + } + } + + private void doParse() throws CancelledException, ParseException { + model.parseSignatureFieldText(); + + Namespace ns = model.getNamespace(); + rebuildNamespaces(ns); + } + + Namespace getSelectedNamesapce() { + return namespaceChoices.getSelectedItem().getNamespace(); + } + private JComponent buildMainPanel(boolean hasOptionalSignatureCommit) { JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); @@ -224,7 +254,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod private JComponent buildCenterPanel(boolean hasOptionalSignatureCommit) { centerPanel = new JPanel(new BorderLayout()); centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); - centerPanel.add(buildAttributePanel(), BorderLayout.NORTH); + centerPanel.add(createAttributePanel(), BorderLayout.NORTH); centerPanel.add(buildTable(), BorderLayout.CENTER); centerPanel.add(buildBottomPanel(hasOptionalSignatureCommit), BorderLayout.SOUTH); centerPanel.getAccessibleContext().setAccessibleName("Function Attributes"); @@ -342,7 +372,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod signatureTextField.setActionListener(e -> { try { if (model.isInParsingMode()) { - model.parseSignatureFieldText(); + doParse(); return; } } @@ -361,7 +391,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod ActionListener tabListener = e -> { try { - model.parseSignatureFieldText(); + doParse(); } catch (Exception ex) { if (!handleParseException(ex)) { @@ -411,16 +441,21 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod return result == OptionDialog.OPTION_TWO; // Option 2 is to abort } - private Component buildAttributePanel() { + private Component createAttributePanel() { JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 15, 15)); JPanel leftPanel = new JPanel(new PairLayout(4, 8)); leftPanel.add(new GLabel("Function Name:")); leftPanel.add(createNameField()); - leftPanel.add(new GLabel("Calling Convention")); + + leftPanel.add(new GLabel("Namespace:")); + leftPanel.add(createNamespacePanel()); + + leftPanel.add(new GLabel("Calling Convention:")); leftPanel.add(createCallingConventionCombo()); - leftPanel.setBorder(BorderFactory.createEmptyBorder(14, 0, 0, 10)); + + leftPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 10)); leftPanel.getAccessibleContext().setAccessibleName("Function"); panel.add(leftPanel, BorderLayout.CENTER); panel.add(buildTogglePanel(), BorderLayout.EAST); @@ -428,6 +463,133 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod return panel; } + private Component createNamespacePanel() { + + namespaceChoices = new GhidraComboBox<>(); + namespaceChoices.setName("NamespaceComboBox"); + namespaceChoices.addItemListener(e -> { + NamespaceWrapper wrapper = (NamespaceWrapper) e.getItem(); + Namespace ns = wrapper.getNamespace(); + model.setNamespace(ns); + }); + + initNamespaces(); + selectNamespace(); + + JPanel nsPanel = new JPanel(); + nsPanel.setLayout(new BoxLayout(nsPanel, BoxLayout.LINE_AXIS)); + Component browsePanel = createBrowseButton(); + nsPanel.add(namespaceChoices); + nsPanel.add(Box.createHorizontalStrut(5)); + nsPanel.add(browsePanel); + return nsPanel; + } + + private void selectNamespace() { + Function function = model.getFunction(); + Symbol symbol = function.getSymbol(); + Namespace ns = symbol.getParentNamespace(); + namespaceChoices.setSelectedItem(new NamespaceWrapper(ns)); + } + + private Component createBrowseButton() { + JButton browseButton = new BrowseButton(); + browseButton.setToolTipText("Choose Namespace"); + browseButton.addActionListener(e -> showNamespaceChooser()); + return browseButton; + } + + private void showNamespaceChooser() { + Function function = model.getFunction(); + Program program = function.getProgram(); + NamespaceChooserDialog dialog = new NamespaceChooserDialog(); + Namespace namespace = dialog.getNamespace(program); + if (namespace != null) { + rebuildNamespaces(namespace); + return; + } + + String nsText = dialog.getNamespaceText(); + if (StringUtils.isBlank(nsText)) { + return; + } + + Namespace newNamespace = createNamespace(nsText); + rebuildNamespaces(newNamespace); + } + + private void rebuildNamespaces(Namespace namespace) { + if (namespace == null) { + return; + } + + Function function = model.getFunction(); + Program program = function.getProgram(); + NamespaceCache.add(program, namespace); + initNamespaces(); + namespaceChoices.setSelectedItem(new NamespaceWrapper(namespace)); + } + + private Namespace createNamespace(String nsText) { + + Program p = model.getProgram(); + return p.withTransaction("Create Namespace", () -> { + Namespace globalNs = p.getGlobalNamespace(); + try { + return NamespaceUtils.createNamespaceHierarchy(nsText, globalNs, p, + SourceType.USER_DEFINED); + + } + catch (InvalidInputException e) { + setStatusText("Invalid Namespace name: " + nsText); + return null; + } + }); + } + + private void initNamespaces() { + namespaceChoices.removeAllItems(); + + for (Namespace namespace : getSelectableNamespaces()) { + namespaceChoices.addItem(new NamespaceWrapper(namespace)); + } + } + + private Collection getSelectableNamespaces() { + SequencedSet namespaces = new LinkedHashSet<>(); + addGlobalNamespace(namespaces); + addCurrentNamespace(namespaces); + addRecentNamespaces(namespaces); + return namespaces; + } + + private void addRecentNamespaces(SequencedSet namespaces) { + Program program = model.getProgram(); + List recentNamespaces = NamespaceCache.get(program); + if (recentNamespaces == null) { + return; + } + for (Namespace namespace : recentNamespaces) { + if (!namespaces.contains(namespace)) { + namespaces.add(namespace); + } + } + } + + private void addGlobalNamespace(SequencedSet namespaces) { + Program program = model.getProgram(); + Namespace globalNamespace = program.getGlobalNamespace(); + if (!namespaces.contains(globalNamespace)) { + namespaces.add(globalNamespace); + } + } + + private void addCurrentNamespace(SequencedSet namespaces) { + Function function = model.getFunction(); + Namespace ns = function.getParentNamespace(); + namespaces.add(ns); + } + private Component buildTogglePanel() { JPanel panel = new JPanel(new PairLayout()); varArgsCheckBox = new GCheckBox("Varargs"); @@ -753,8 +915,9 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod } if (!model.hasValidName()) { - signatureTextField.setError(model.getFunctionNameStartPosition(), - model.getNameString().length()); + int pos = model.getFunctionNameStartPosition(); + int len = model.getNameString().length(); + signatureTextField.setError(pos, len); } if (caretPosition < preview.length()) { signatureTextField.setCaretPosition(caretPosition); @@ -771,6 +934,47 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod } } +//================================================================================================= +// Inner Classes +//================================================================================================= + + private class NamespaceWrapper { + private Namespace namespace; + + NamespaceWrapper(Namespace namespace) { + this.namespace = namespace; + } + + Namespace getNamespace() { + return namespace; + } + + @Override + public String toString() { + return namespace.getName(true); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object == null) { + return false; + } + if (object.getClass() == getClass()) { + NamespaceWrapper w = (NamespaceWrapper) object; + return namespace.equals(w.namespace); + } + return false; + } + + @Override + public int hashCode() { + return namespace.hashCode(); + } + } + private class ParameterDataTypeCellRenderer extends GTableCellRenderer { @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { @@ -1133,7 +1337,7 @@ public class FunctionEditorDialog extends DialogComponentProvider implements Mod if (!processEvent(e)) { try { - model.parseSignatureFieldText(); + doParse(); } catch (Exception ex) { handleParseException(ex); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java index 6f8045b6b9..97426a2184 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/editor/FunctionEditorModel.java @@ -18,8 +18,11 @@ package ghidra.app.plugin.core.function.editor; import java.util.*; import ghidra.app.services.DataTypeManagerService; +import ghidra.app.util.NamespaceUtils; +import ghidra.app.util.SymbolPath; import ghidra.app.util.cparser.C.ParseException; import ghidra.app.util.parser.FunctionSignatureParser; +import ghidra.app.util.parser.FunctionSignatureParser.FsParseResult; import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.program.model.lang.Register; @@ -61,7 +64,7 @@ public class FunctionEditorModel { this.dataTypeManagerService = service; this.function = function; this.program = function.getProgram(); - functionData = new FunctionData(function); + this.functionData = new FunctionData(function); this.originalFunctionData = new FunctionDataView(functionData); validate(); } @@ -118,6 +121,7 @@ public class FunctionEditorModel { "Signature transformed due to auto-params and/or forced-indirect storage change"; isSignatureTransformed = false; // one-shot message } + isValid = hasValidName() && hasValidReturnType() && hasValidReturnStorage() && hasValidParams(); hasSignificantParameterChanges = false; @@ -256,7 +260,7 @@ public class FunctionEditorModel { * {@link VariableUtilities#checkVariableConflict(List, Variable, VariableStorage, VariableConflictHandler)} * @param conflicts parameters whose storage conflicts * @return return false to indicate conflicts have not been resolved and additional checks - * should be disconctinued. + * should be discontinued. * @see VariableConflictHandler */ private boolean handleConflicts(List conflicts) { @@ -405,6 +409,18 @@ public class FunctionEditorModel { notifyDataChanged(); } + Namespace getNamespace() { + return functionData.getNamespace(); + } + + void setNamespace(Namespace ns) { + if (getNamespace().equals(ns)) { + return; + } + functionData.setNamespace(ns); + notifyDataChanged(); + } + String getCallingConventionName() { return functionData.getCallingConventionName(); } @@ -742,9 +758,12 @@ public class FunctionEditorModel { if (b == canUseCustomStorage()) { return; } + functionData.setUseCustomStorage(b); - isSignatureTransformed = !functionData.getFunctionSignatureText() - .equals(originalFunctionData.getFunctionSignatureText()); + + String signatureText = functionData.getFunctionSignatureText(); + String originalSignatureText = originalFunctionData.getFunctionSignatureText(); + isSignatureTransformed = !signatureText.equals(originalSignatureText); notifyDataChanged(); } @@ -779,6 +798,11 @@ public class FunctionEditorModel { function.setName(name, SourceType.USER_DEFINED); } + Namespace namespace = functionData.getNamespace(); + if (!namespace.equals(function.getParentNamespace())) { + function.setParentNamespace(namespace); + } + boolean isInline = functionData.isInline(); if (function.isInline() != isInline) { function.setInline(isInline); @@ -905,7 +929,7 @@ public class FunctionEditorModel { return dt1.getLength() == dt2.getLength(); } - public void setFunctionData(FunctionDefinitionDataType functionDefinition) { + public void setFunctionData(Namespace ns, FunctionDefinitionDataType functionDefinition) { setName(functionDefinition.getName()); @@ -941,6 +965,8 @@ public class FunctionEditorModel { functionData.updateParameterAndReturnStorage(); + functionData.setNamespace(ns); + notifyDataChanged(); } @@ -1010,7 +1036,9 @@ public class FunctionEditorModel { void parseSignatureFieldText() throws ParseException, CancelledException { FunctionSignatureParser parser = new FunctionSignatureParser(program.getDataTypeManager(), dataTypeManagerService); - FunctionDefinitionDataType f = parser.parse(getFunctionSignature(), signatureFieldText); + FsParseResult result = + parser.parseWithNamespace(getFunctionSignature(), signatureFieldText); + FunctionDefinitionDataType f = result.functionDefinition(); // Preserve calling convention and noreturn flag from current model f.setNoReturn(functionData.hasNoReturn()); @@ -1021,10 +1049,31 @@ public class FunctionEditorModel { // ignore } - setFunctionData(f); + Namespace ns = createNamespace(result.namespace()); + setFunctionData(ns, f); isInParsingMode = false; } + private Namespace createNamespace(SymbolPath path) throws ParseException { + + if (path == null) { + return null; + } + + String nsText = path.getPath(); + return program.withTransaction("Create Namespace", () -> { + Namespace globalNs = program.getGlobalNamespace(); + try { + return NamespaceUtils.createNamespaceHierarchy(nsText, globalNs, program, + SourceType.USER_DEFINED); + + } + catch (InvalidInputException e) { + throw new ParseException("Invalid Namespace name: " + nsText); + } + }); + } + int getFunctionNameStartPosition() { return getFormalReturnType().getName().length() + 1; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddEditDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddEditDialog.java index 96d4edf86a..d691ecf807 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddEditDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddEditDialog.java @@ -40,6 +40,7 @@ import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.util.HelpLocation; import ghidra.util.Swing; +import ghidra.util.exception.InvalidInputException; import ghidra.util.layout.VerticalLayout; /** @@ -575,8 +576,7 @@ public class AddEditDialog extends ReusableDialogComponentProvider { // the number of columns determines the default width of the add/edit label dialog labelNameChoices.setColumns(20); labelNameChoices.setName("label.name.choices"); - GhidraComboBox comboBox = new GhidraComboBox<>(); - namespaceChoices = comboBox; + namespaceChoices = new GhidraComboBox<>(); primaryCheckBox = new GCheckBox("Primary"); primaryCheckBox.setMnemonic('P'); @@ -636,12 +636,40 @@ public class AddEditDialog extends ReusableDialogComponentProvider { private void showNamespaceChooser() { NamespaceChooserDialog dialog = new NamespaceChooserDialog(); - Namespace namespace = dialog.getNameSpace(program); + Namespace namespace = dialog.getNamespace(program); if (namespace != null) { NamespaceCache.add(program, namespace); initNamespaces(); namespaceChoices.setSelectedItem(new NamespaceWrapper(namespace)); } + + String nsText = dialog.getNamespaceText(); + if (StringUtils.isBlank(nsText)) { + return; + } + + Namespace newNamespace = createNamespace(nsText); + if (newNamespace != null) { + NamespaceCache.add(program, newNamespace); + initNamespaces(); + namespaceChoices.setSelectedItem(new NamespaceWrapper(newNamespace)); + } + } + + private Namespace createNamespace(String nsText) { + + return program.withTransaction("Create Namespace", () -> { + Namespace globalNs = program.getGlobalNamespace(); + try { + return NamespaceUtils.createNamespaceHierarchy(nsText, globalNs, program, + SourceType.USER_DEFINED); + + } + catch (InvalidInputException e) { + setStatusText("Invalid Namespace name: " + nsText); + return null; + } + }); } private void addListeners() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceCache.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceCache.java index 3fc485eb75..7808e4993a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceCache.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceCache.java @@ -23,6 +23,9 @@ import ghidra.util.datastruct.LRUSet; /** * Static class for remember the last few namespaces used for a program. + * + *

Note: This class is not currently multi-threaded. Accesses are expected to be on the Swing + * thread. */ public class NamespaceCache { public static final int MAX_RECENTS = 10; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceChooserDialog.java index 43ddafac52..6b3a535f76 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceChooserDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/NamespaceChooserDialog.java @@ -48,7 +48,11 @@ public class NamespaceChooserDialog extends DialogComponentProvider { addCancelButton(); } - public Namespace getNameSpace(Program program) { + public void setText(String text) { + dropDownField.setText(text); + } + + public Namespace getNamespace(Program program) { List namespaces = gatherNamespaces(program); if (namespaces == null) { // user cancelled while gathering namespaces @@ -59,6 +63,10 @@ public class NamespaceChooserDialog extends DialogComponentProvider { return chosenNamespace; } + public String getNamespaceText() { + return dropDownField.getText(); + } + @Override protected void okCallback() { chosenNamespace = dropDownField.getSelectedValue(); @@ -68,6 +76,7 @@ public class NamespaceChooserDialog extends DialogComponentProvider { @Override protected void cancelCallback() { chosenNamespace = null; + dropDownField.setText(""); close(); } @@ -82,6 +91,7 @@ public class NamespaceChooserDialog extends DialogComponentProvider { panel.add(new JLabel("Namespace: ")); dropDownField = new DropDownSelectionTextField<>(namespaceModel); + dropDownField.setShowMatchingListOnEmptyText(true); panel.add(dropDownField); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); return panel; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java index d90cb4429b..a0485ffd63 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/parser/FunctionSignatureParser.java @@ -21,6 +21,7 @@ import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import ghidra.app.services.DataTypeQueryService; +import ghidra.app.util.SymbolPath; import ghidra.app.util.cparser.C.ParseException; import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.model.data.*; @@ -56,7 +57,8 @@ public class FunctionSignatureParser { private DataTypeParser dataTypeParser; private Map dtMap = new HashMap<>(); - private Map nameMap = new HashMap<>(); + /** Stores parameter names that required name fixup for parsing to work correctly */ + private Map replacedNameMap = new HashMap<>(); private DataTypeManager destDataTypeManager; private ParserDataTypeManagerService dtmService; @@ -96,8 +98,16 @@ public class FunctionSignatureParser { */ public FunctionDefinitionDataType parse(FunctionSignature originalSignature, String signatureText) throws ParseException, CancelledException { + + FsParseResult result = parseWithNamespace(originalSignature, signatureText); + return result.functionDefinition(); + } + + public FsParseResult parseWithNamespace(FunctionSignature originalSignature, + String signatureText) throws ParseException, CancelledException { + dtMap.clear(); - nameMap.clear(); + replacedNameMap.clear(); if (dtmService != null) { dtmService.clearCache(); // clear datatype selection cache } @@ -108,14 +118,18 @@ public class FunctionSignatureParser { } String functionName = extractFunctionName(signatureText); + SymbolPath path = new SymbolPath(functionName); + SymbolPath nsPath = path.getParent(); + String name = path.getName(); + FunctionDefinitionDataType function = - new FunctionDefinitionDataType(functionName, destDataTypeManager); + new FunctionDefinitionDataType(name, destDataTypeManager); function.setReturnType(extractReturnType(signatureText)); function.setArguments(extractArguments(signatureText)); function.setVarArgs(hasVarArgs(signatureText)); - return function; + return new FsParseResult(nsPath, function); } private void initDataTypeMap(FunctionSignature signature) { @@ -247,7 +261,7 @@ public class FunctionSignatureParser { if (canParseName(name)) { return text; } - nameMap.put(replacementName, name); + replacedNameMap.put(replacementName, name); return substitute(text, name, replacementName); } @@ -299,8 +313,8 @@ public class FunctionSignatureParser { } private String resolveName(String name) throws ParseException { - if (nameMap.containsKey(name)) { - return nameMap.get(name); + if (replacedNameMap.containsKey(name)) { + return replacedNameMap.get(name); } if (!canParseName(name)) { throw new ParseException("Can't parse name: " + name); @@ -320,6 +334,14 @@ public class FunctionSignatureParser { return !StringUtils.containsAny(text, "()<>,"); } + /** + * A simple object to hold data for the results of parsing the function signature text + * @param namespace the namespace; may be null + * @param functionDefinition the function definition; will not be null + */ + public record FsParseResult(SymbolPath namespace, + FunctionDefinitionDataType functionDefinition) {} + /** * Provides a simple caching datatype manager service wrapper.
* Implementation intended for use with {@link FunctionSignatureParser} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialogTest.java index f73c8f717b..af4a4ca16c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/editor/FunctionEditorDialogTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,34 +15,36 @@ */ package ghidra.app.plugin.core.function.editor; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import javax.swing.AbstractButton; +import javax.swing.ComboBoxModel; import javax.swing.table.TableCellEditor; import org.junit.*; import docking.action.DockingActionIf; import docking.widgets.DropDownSelectionTextField; +import docking.widgets.button.BrowseButton; +import docking.widgets.combobox.GhidraComboBox; import docking.widgets.table.GTable; import ghidra.app.cmd.function.DeleteFunctionCmd; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.function.FunctionPlugin; import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin; import ghidra.app.services.ProgramManager; +import ghidra.app.util.*; import ghidra.app.util.datatype.DataTypeSelectionEditor; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressFactory; import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.Namespace; +import ghidra.program.model.symbol.SourceType; import ghidra.test.*; public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTest { - public FunctionEditorDialogTest() { - super(); - } - private TestEnv env; private PluginTool tool; private AddressFactory addrFactory; @@ -64,10 +66,11 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes @After public void tearDown() { + closeAllWindows(); env.dispose(); } - /** + /* * Tests that an invalid parameter type entry will generate the proper error message * shown in the status box, and NOT present the user with a stack trace. */ @@ -93,10 +96,180 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes assertTrue(dialog.getStatusText().contains("Invalid data type")); } + @Test + public void testSetNamespace() throws Exception { + + createFunctionAtEntry(); + + String newNamespaceName = "NewNamespace"; + Namespace newNs = createNamespace(newNamespaceName); + + FunctionEditorDialog dialog = editFunction(); + + pickNamespaceFromComboBox(dialog, newNs); + + pressButtonByText(dialog, "OK"); + waitForBusyTool(tool); + + Function f = getFunction("0x1006420"); + Namespace actualNamespace = f.getParentNamespace(); + assertEquals(newNs, actualNamespace); + } + + @Test + public void testSetNamespace_Browse_CreateNew() throws Exception { + + createFunctionAtEntry(); + + String newNamespace = "NonExistingNamespace"; + + FunctionEditorDialog dialog = editFunction(); + + setNamespaceUsingNsChooserDilaog(dialog, newNamespace); + + pressButtonByText(dialog, "OK"); + waitForBusyTool(tool); + + Function f = getFunction("0x1006420"); + Namespace actualNamespace = f.getParentNamespace(); + assertEquals(newNamespace, actualNamespace.toString()); + } + + @Test + public void testSetNamespace_Browse_CreateNew_NamespacePath() throws Exception { + + createFunctionAtEntry(); + + String newNamespacePath = "Foo::Bar::NonExistingNamespace"; + + FunctionEditorDialog dialog = editFunction(); + + setNamespaceUsingNsChooserDilaog(dialog, newNamespacePath); + + pressButtonByText(dialog, "OK"); + waitForBusyTool(tool); + + Function f = getFunction("0x1006420"); + Namespace actualNamespace = f.getParentNamespace(); + SymbolPath expectedPath = new SymbolPath(newNamespacePath); + SymbolPath actualPath = new SymbolPath(actualNamespace.getPathList(true)); + assertEquals(expectedPath, actualPath); + } + + @Test + public void testSetNamespace_ViaTextEditor() throws Exception { + + createFunctionAtEntry(); + + String newNamespacePath = "Foo::Bar"; + + FunctionEditorDialog dialog = editFunction(); + + setNamespaceUsingTextEditor(dialog, newNamespacePath); + assertNamespaceNotVisibleInEditorAfterParsing(dialog); + assertNamespaceComboBoxIsShowingNamespace(dialog, newNamespacePath); + + pressButtonByText(dialog, "OK"); + waitForBusyTool(tool); + + Function f = getFunction("0x1006420"); + Namespace actualNamespace = f.getParentNamespace(); + SymbolPath expectedPath = new SymbolPath(newNamespacePath); + SymbolPath actualPath = new SymbolPath(actualNamespace.getPathList(true)); + assertEquals(expectedPath, actualPath); + } + //================================================================================================== // Private Methods //================================================================================================== + private void assertNamespaceComboBoxIsShowingNamespace(FunctionEditorDialog dialog, + String expectedNsPath) { + + Namespace actualNs = runSwing(() -> dialog.getSelectedNamesapce()); + assertEquals(expectedNsPath, actualNs.toString()); + } + + private void assertNamespaceNotVisibleInEditorAfterParsing(FunctionEditorDialog dialog) { + FunctionSignatureTextField field = dialog.getSignatureField(); + String signature = runSwing(() -> field.getText()); + assertFalse("Namespace should not be visible in the editor after parsing", + signature.contains("::")); + } + + private void setNamespaceUsingTextEditor(FunctionEditorDialog dialog, String ns) { + + FunctionSignatureTextField field = dialog.getSignatureField(); + String signature = runSwing(() -> field.getText()); + + // Insert namespace in front of name. Format: + // undefined entry (void) + int paren = signature.indexOf('('); + int spaceBeforeParen = paren - 1; + int space = signature.lastIndexOf(' ', spaceBeforeParen - 1); + String beginning = signature.substring(0, space + 1); + String end = signature.substring(space + 1); + String updated = beginning + ns + "::" + end; + + setText(field, updated); + + runSwing(() -> dialog.triggerSignatureParsing()); + } + + private void pickNamespaceFromComboBox(FunctionEditorDialog dialog, Namespace ns) { + + GhidraComboBox combo = + (GhidraComboBox) findComponentByName(dialog, "NamespaceComboBox"); + + int index = indexOf(combo, ns); + if (index < 0) { + fail("Could not find namespace in combo box: " + ns); + } + runSwing(() -> combo.setSelectedIndex(index)); + } + + private int indexOf(GhidraComboBox combo, Namespace ns) { + return runSwing(() -> { + + String nsName = ns.getName(); + ComboBoxModel model = combo.getModel(); + int n = model.getSize(); + for (int i = 0; i < n; i++) { + Object element = model.getElementAt(i); + String elementText = element.toString(); + if (elementText.equals(nsName)) { + return i; + } + } + return -1; + }); + } + + private void setNamespaceUsingNsChooserDilaog(FunctionEditorDialog dialog, + String newNamespace) { + + AbstractButton button = findButtonByName(dialog, BrowseButton.NAME); + pressButton(button, false); + + NamespaceChooserDialog nsDialog = waitForDialogComponent(NamespaceChooserDialog.class); + runSwing(() -> nsDialog.setText(newNamespace)); + + pressButtonByText(nsDialog, "OK"); + waitForBusyTool(tool); + } + + private Namespace createNamespace(String newNamespace) { + + Namespace newNs = tx(program, () -> { + return NamespaceUtils.createNamespaceHierarchy(newNamespace, null, program, + SourceType.USER_DEFINED); + }); + + runSwing(() -> NamespaceCache.add(program, newNs)); + + return newNs; + } + private void setEditorText(TableCellEditor cellEditor, String text) { DropDownSelectionTextField textField = getDataTypeEditor(cellEditor); setText(textField, text); @@ -106,7 +279,7 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes private FunctionEditorDialog editFunction() { performAction(editFunction, cb.getProvider(), false); - return waitForDialogComponent(null, FunctionEditorDialog.class, DEFAULT_WINDOW_TIMEOUT); + return waitForDialogComponent(FunctionEditorDialog.class); } private void finishEditing(final TableCellEditor cellEditor) { @@ -150,6 +323,11 @@ public class FunctionEditorDialogTest extends AbstractGhidraHeadedIntegrationTes addrFactory = program.getAddressFactory(); } + private Function getFunction(String addr) { + FunctionManager fm = program.getFunctionManager(); + return fm.getFunctionAt(addr(addr)); + } + private void createFunctionAtEntry() { FunctionManager fm = program.getFunctionManager(); Function f = fm.getFunctionAt(addr("0x1006420")); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/parser/FunctionSignatureParserTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/parser/FunctionSignatureParserTest.java index 3bcf031939..2cc7388504 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/parser/FunctionSignatureParserTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/parser/FunctionSignatureParserTest.java @@ -132,6 +132,7 @@ public class FunctionSignatureParserTest extends AbstractGhidraHeadedIntegration public void testExtractFunctionName() throws Exception { assertEquals("bob", parser.extractFunctionName("void bob(int a)")); assertEquals("bob", parser.extractFunctionName("void bob (int a)")); + assertEquals("Foo::Bar::bob", parser.extractFunctionName("void Foo::Bar::bob (int a)")); } @Test diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/function/editor/FunctionEditorModelTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/function/editor/FunctionEditorModelTest.java index bd295ad46e..a93915548a 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/function/editor/FunctionEditorModelTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/function/editor/FunctionEditorModelTest.java @@ -22,7 +22,6 @@ import java.util.List; import org.junit.*; import generic.test.AbstractGuiTest; -import ghidra.app.services.DataTypeManagerService; import ghidra.app.util.cparser.C.ParseException; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; @@ -40,7 +39,6 @@ public class FunctionEditorModelTest extends AbstractGuiTest { private volatile boolean dataChangeCalled; private Structure bigStruct; private ProgramDB program; - private DataTypeManagerService service; private volatile boolean tableRowsChanged; class MyModelChangeListener implements ModelChangeListener { @@ -1612,9 +1610,9 @@ public class FunctionEditorModelTest extends AbstractGuiTest { assertEquals("R9D:4", storage.toString()); model.setUseCustomizeStorage(false); - // no change to 'this', return ptr consumed and unfortunately + // no change to 'this', return pointer consumed and unfortunately // injected before custom 'this' param - // TODO: should we be removing 'this' param if not __thiscall ? + // Note: should we be removing 'this' param if not __thiscall ? assertTrue(model.getReturnType().isEquivalent(new PointerDataType(bigStruct))); assertTrue(model.getFormalReturnType().isEquivalent(bigStruct)); @@ -1843,7 +1841,6 @@ public class FunctionEditorModelTest extends AbstractGuiTest { model.setUseCustomizeStorage(true); VariableStorage paramStorage1 = model.getParameters().get(0).getStorage(); - VariableStorage paramStorage2 = model.getParameters().get(1).getStorage(); VariableStorage paramStorage3 = model.getParameters().get(2).getStorage(); model.setSignatureFieldText("int joe(int e, int c, int f, int g)"); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SpecifyCPrototypeAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SpecifyCPrototypeAction.java index aeffece704..4b6ce3fe58 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SpecifyCPrototypeAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SpecifyCPrototypeAction.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,6 +27,7 @@ import ghidra.program.model.data.*; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.VariableStorage; import ghidra.program.model.pcode.*; +import ghidra.program.model.symbol.Namespace; import ghidra.program.model.symbol.SourceType; import ghidra.util.HelpLocation; import ghidra.util.UndefinedFunction; @@ -99,7 +100,10 @@ public class SpecifyCPrototypeAction extends AbstractDecompilerAction { if (useCustom) { // Force custom storage model.setUseCustomizeStorage(true); - model.setFunctionData(buildSignature(hf)); + Function function = hf.getFunction(); + Namespace ns = function.getParentNamespace(); + FunctionDefinitionDataType signature = buildSignature(hf); + model.setFunctionData(ns, signature); model.setReturnStorage(functionPrototype.getReturnStorage()); parameters = model.getParameters(); for (int i = 0; i < decompParamCnt; i++) { @@ -169,7 +173,9 @@ public class SpecifyCPrototypeAction extends AbstractDecompilerAction { if (function.getEntryPoint().equals(hf.getFunction().getEntryPoint())) { if (function.getSignatureSource() == SourceType.DEFAULT) { - model.setFunctionData(buildSignature(hf)); + Namespace ns = function.getParentNamespace(); + FunctionDefinitionDataType signature = buildSignature(hf); + model.setFunctionData(ns, signature); verifyDynamicEditorModel(hf, model); } else if (function.getReturnType() == DataType.DEFAULT) { diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgLevel.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgLevel.java index 775b9396e0..821ab24c2f 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgLevel.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/FcgLevel.java @@ -145,8 +145,8 @@ public class FcgLevel implements Comparable { /** * Returns the child level of this level. The child of a level has the same direction * as this level, with a distance of one more than this level. - * - * @param the direction of the child + * + * @param newDirection the direction of the child * @return returns the child level of this level * @throws IllegalArgumentException if this is the source level, which is row 1 */ diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java index 2f8397f6ca..035d9f571a 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java @@ -38,7 +38,7 @@ import ghidra.util.Msg; * *

This class is handed a group of edges to processes. In this group there are vertices that * do not need to be arranged, referred to as the {@code existing} vertices. This - * classes uses {@link VertexCollection} to find and store the new vertices that need + * classes uses {@link FcgExpandingVertexCollection} to find and store the new vertices that need * to be arranged. */ public class BowTieExpandVerticesJob extends AbstractGraphTransitionJob { diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java index c4cfa04467..42b0a2fa00 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -31,7 +31,7 @@ import util.CollectionUtils; * A container to house all newly added vertices (those being arranged) and the sources, or * 'from' vertices, of the new vertices. * - *

This offers exiting vertices and new vertices pre-sorted by position in the graph in + *

This offers existing vertices and new vertices pre-sorted by position in the graph in * order to minimize edge crossings. Specifically, the new vertices will be sorted * by the level of the parent and then the x-value of the parent so that the * immediate parent level will be preferred, with the x-value dictating where to place diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java index b1489076ff..af7831455c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/combobox/GhidraComboBox.java @@ -131,6 +131,12 @@ public class GhidraComboBox extends JComboBox implements GComponent { } } + @SuppressWarnings("unchecked") + @Override + public E getSelectedItem() { + return (E) super.getSelectedItem(); + } + /** * Returns the text in combobox's editor text component * @return the text in combobox's editor text component diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/GlobalNamespace.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/GlobalNamespace.java index 6d94560f4d..dc25dabfa7 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/GlobalNamespace.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/GlobalNamespace.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -129,4 +129,9 @@ public class GlobalNamespace implements Namespace { return false; } + @Override + public int hashCode() { + return getClass().hashCode(); + } + } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java index d5fc118b32..4aa549d449 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/LabelMgrPluginScreenShots.java @@ -79,7 +79,7 @@ public class LabelMgrPluginScreenShots extends GhidraScreenShotGenerator { public void testChooseNamespace() { runSwingLater(() -> { NamespaceChooserDialog dialog = new NamespaceChooserDialog(); - dialog.getNameSpace(program); + dialog.getNamespace(program); }); waitForDialogComponent(NamespaceChooserDialog.class); captureDialog();