From 7fa196837aa9696c57c6b65683c33cd88e4c8010 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 19:37:12 -0400 Subject: [PATCH] scummvm: 2 CWE-407 defects, vita3k: 1 CWE-407 defect, MOAD 0002-0005 CLEAN scummvm-0001: crab PathfindingGrid::getNearestOpenNode BFS visited set Common::Array O(N^2), fix std::unordered_set O(N), 104x speedup scummvm-0002: tsage WalkRegions::calculateRestOfRoute _disabledRegions Common::List::contains O(D) in recursive route loop, fix std::unordered_set O(1), 2.5x speedup vita3k-0001: ngs deliver_data voice_queue std::vector::contains O(V^2*P) per audio frame, fix std::unordered_set built once per frame O(V*P), 9.3x speedup at V=256 --- defects/scummvm-0001/patch/scummvm-0001.patch | 50 ++++ defects/scummvm-0001/test/scummvm-0001-test | Bin 0 -> 91704 bytes .../scummvm-0001/test/scummvm-0001-test.cpp | 227 ++++++++++++++++++ defects/scummvm-0002/patch/scummvm-0002.patch | 73 ++++++ defects/scummvm-0002/test/scummvm-0002-test | Bin 0 -> 78488 bytes .../scummvm-0002/test/scummvm-0002-test.cpp | 162 +++++++++++++ defects/vita3k-0001/patch/vita3k-0001.patch | 36 ++- defects/vita3k-0001/test/vita3k-0001-test | Bin 0 -> 102576 bytes defects/vita3k-0001/test/vita3k-0001-test.cpp | 170 +++++++++++++ 9 files changed, 705 insertions(+), 13 deletions(-) create mode 100644 defects/scummvm-0001/patch/scummvm-0001.patch create mode 100755 defects/scummvm-0001/test/scummvm-0001-test create mode 100644 defects/scummvm-0001/test/scummvm-0001-test.cpp create mode 100644 defects/scummvm-0002/patch/scummvm-0002.patch create mode 100755 defects/scummvm-0002/test/scummvm-0002-test create mode 100644 defects/scummvm-0002/test/scummvm-0002-test.cpp create mode 100755 defects/vita3k-0001/test/vita3k-0001-test create mode 100644 defects/vita3k-0001/test/vita3k-0001-test.cpp diff --git a/defects/scummvm-0001/patch/scummvm-0001.patch b/defects/scummvm-0001/patch/scummvm-0001.patch new file mode 100644 index 000000000..a465d2ef4 --- /dev/null +++ b/defects/scummvm-0001/patch/scummvm-0001.patch @@ -0,0 +1,50 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/engines/crab/PathfindingGrid.cpp ++++ b/engines/crab/PathfindingGrid.cpp +@@ -27,6 +27,8 @@ + + #include "crab/PathfindingGrid.h" + #include "crab/TMX/TMXMap.h" ++#include + + namespace Crab { + +@@ -234,21 +236,22 @@ PathfindingGraphNode *PathfindingGrid::getNearestOpenNode(Vector2f nodePos, Vect + if (startNode->getMovementCost() > 0) // If the clicked node is open, we're done! + return startNode; + + PathfindingGraphNode *returnNode = nullptr; + + float shortestDistance = 0.0f; + + Common::List checkNodes; + checkNodes.push_back(startNode); + +- Common::Array allUsedNodes; +- allUsedNodes.push_back(startNode); ++ // Use an unordered_set for O(1) visited-node lookup instead of O(N) linear ++ // scan over allUsedNodes on every neighbor check. Without this fix the BFS ++ // is O(N^2) in the number of grid nodes visited. ++ std::unordered_set visitedNodes; ++ visitedNodes.insert(startNode); + + // Iterate through the nodes, check if they are open then check their distance from the compare point. + while (!checkNodes.empty()) { + if (checkNodes.front()->getMovementCost() > 0) { + float distance = (comparePos - checkNodes.front()->getPosition()).magSqr(); + + if (shortestDistance == 0.0f || distance) { // If this is the new shortest distance, this becomes the new return. + shortestDistance = distance; + + returnNode = checkNodes.front(); + } + } else { + for (uint i = 0; i < checkNodes.front()->_neighborNodes.size(); ++i) { + // If the neighbor hasn't been checked yet, add it to the list to check. +- if (Common::find(allUsedNodes.begin(), allUsedNodes.end(), checkNodes.front()->_neighborNodes[i]) == allUsedNodes.end()) { +- allUsedNodes.push_back(checkNodes.front()->_neighborNodes[i]); ++ if (visitedNodes.find(checkNodes.front()->_neighborNodes[i]) == visitedNodes.end()) { ++ visitedNodes.insert(checkNodes.front()->_neighborNodes[i]); + checkNodes.push_back(checkNodes.front()->_neighborNodes[i]); + } + } diff --git a/defects/scummvm-0001/test/scummvm-0001-test b/defects/scummvm-0001/test/scummvm-0001-test new file mode 100755 index 0000000000000000000000000000000000000000..3a458b212f6659a8e5ab55b4521e51cd6dee4791 GIT binary patch literal 91704 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%0A8D zT@(tC`D5#TT1<&dbRI(?aSOgAFtbZ(gR^GmaO5hQR|(P1j9~Bxc&MI~m&U^uWB(XlvVr({AKN zAhd5d6E5-hHoWEbF0{5uxird;@&W95Jc6Yh`c5L0aFq7foasMxF1aJVSe>Hj5{ZsN z-TBQs!AUnIPv@p+QeiYA(c@IhNOobrT7YMho8OKyJ54)%U!8h`(@Y#oiyF*bKr_ud zts}SWa9b`^sguL+;Vl+;|k(CCdz(XSTzU=tvx0>sd*A zK(?&wJItA`=Ly~aviq<1Jnaxb8@NXVz9S8Gk_5p+d@%(SnsIeqb}k>OyT_eyCA)wF zWZT+BwiU+I9iiEW%)q&ArP;rxj!bM{dU0@@{$vLv$cuq7OBSM^inZpVEwL+78x7Ul ziX-9%StHS}s5H53bimk4T(JvJL$!tYR0ajnA@Cy2XGeXXb8!6BCCiT1DG99`$>o`60P79M4D8byq=S^;{QP`9zQFCj3;r= zc7gp)WZn~KeuYn%BhjHmXqidAwjJ;bfL*t*-A?w=7<+s5VJm@AjznK3KUt~0%eV2& zzY_2K%dquP=FdGixpO38Q}qnv^#eL4Y8pqki6=00th*&gcY0lAGSLFmRjrApnAwvO zYs@YbkM?Q~?z!ES;m^1@Ry#qvPHZ|!!I))Qj?`T=Uf5wUnr`j28jfZ8?!QMZLcNLx z^NWtG4WvP;sGxy#5S2!v(=qSG0q^Yr$7HDa#UtnQcgjINy}DF}61?<%jjR&3`(f7` z1rR7bQxyU$BDbTcFl~00W3$l8oOx}5?Q_KOOvxsyzVq%q7u7P?rxOFp-R9vLLFp!c z6y3wLS{qQx!QjaZR*(^R7s6){{Ka5&G?s2rCti1lpoMtV+3SVN zW}xRsxyQ*JKj|Xb3{xrqqe~Vm68$OdUeyBA4QQ;^F>w_-LHBIeT!a1w%-(?9PAww$ z=Jo@tX-+oBImr(+ucc>gOpCFH?)u1KFYdCzr^50VeTkr)fuyh8$aCDzw4F2g+j_1; zC)?DVUCZb9x*{IFHDDJyHFB4~6>$K1z#Hc!YiX~+_@RPjY7|0WxY zH;||`Df&7=^qWJP5ECL#y6y%u|4vo;7AWx+D|`v8-4=9B^R^BRB#wenNznneAW*oZBo<iBMT}hDaT^Jnl2lU|3YkoDGs$+9(oG( zI~QC4?Y6d9twD0FD#paptG~Ts#OzY9Mcqb$c$VpIhMv(&7qO;ue8>iMIH+Tjvj11{&F&?V&m zQio4f?aKT8?QUud!V-fxAzJq%kfz4U z-u`rwF3F3fKRZoy=|5mPP3>+ zh_DmO3N#RwgZ8m&8+{C0_=$#g4lvN-+q^6K^`G?j!wfcek%@>-ws;uYFGw{s!cJ&-J#E9#7uD9{*w zW#W1Pdr7+AU|&MTdG&sJy6mvoOf%!-v|oXp^;-YCBEDioD!pC#)8n45xb=~Df^@3V z*E5+}aI6p9_Mw=uwAFzksR8;m4-hytvzZ?K)N7Ovt3;+c--3Hl&2K+D9P4KE{O9`n z5ucl?E?Y?i;R&`xy9%2B5Z#8aJ?D~a^P8Xdul!5+I20v9G71(nW47RJ1n3gpb(z6# z$J7O;%m-P>BhlyFW9M1B(Nk=1x>MBD#CWi+oG1 zjRo{I{wXfvJLY;EFSyLTAuDPIQ6T9{nGv?HCWRwK(D#f4WZ1jrjGjhC-U&vrDO?~&k4*4@s*xDB)!N9`Ck^J2pV-h#*$e?3=OdL<7JD&Es zjRe~Omjiw;hQ(Y(V(QRVU>?SH15BXnm6*I`oxcH$w$iBfy-!1hQ0H3z47+ul|9;@lRC}7d2g&)tg(sM%5ceg1iF5#d7o;^VAZ_<+%?B;Hg!+HnUlI%-$;xv$-iGsAc(PzAw4S$ev z4`qCtuUcXOrnCyvO1f7sO&7C9Ia+JdgZdwt5BIs z9;_9{s%^(csUA3jN%pZ;_*6)Vj-@kE2k9R6OC6a#c&9S99z4LT#lHm6Epo*ZjU5j& z3E3^wOmnT@<^MEE4$Lt=uJdR5g&u=>uRlei<0uL20DX>*A0<&lT>7O8hN;mfHp7xG z#2@qlYc~0Lk!Tyz=!89M$s?HuF1J9uo6I3oCtyJ1oq_w=j$j7*pCx7I^=K2mb)g$w zgf&guNc4qF8%Le2mXzCoZqSHN4)+6TEuvXpffWG$W87U zr&ki@BNAO7H(zR$>qEnHNdUi9l^a)7&|@EZ>?l<1f={okGR7ljy^rI^sT25nNrBip zW5lq%;Uopc_V9Ym(a@EIjEN_>R0RdxBhF(w=;_CE52C}>_#-3ErytKC?NwVSwG#k} z?*@Y5Nc5MmzsCK7iSFKwLQ>r-puAI$yvgo#io+x| zoD9PP^JU8kcb$~gaVAHcgCpso%hbs0rRld3n)I(Bpt2*MBN8|wfg=()B7q|kI3j@~ z5;!7(BN8|wfg=()B7q|k_&+EC{R_5BYbwfo;ShcaxwNLPE>s$>4%OF>9A(v)HdIwD zts0Y=nVB_)=wnN3Yppu`dh#M<@||<3uezbKvNl`?+@gxcP??D;qhD4o^IdwWkH0N~ zdnCvU5377jSs%k4kRiWx|ak!$Uy55K16E5>rhl=aaqOYblRP8Gc`|69U zLR?dEb!kXsZSptSH+}ZhX}-eZa5J@`nCQsIhhxa!UM(lMciqj>eBM6;<_c1edm`oo?qr$g#IlXQ$jy2jovLr zjiW*F)rD%oW?fB{Z)E1^OjQ`=eZD*;nUPKtqfk|4Wtp#`{#ejwu#|j6ALa8A3vT+H zTKxWVL#?llM1hiHvlijSF2|DKP=9H0b#+bHS6UY;4u^dBaplRAmtr7l>U@>Ob&ErF zzHoVQwXdqUF<6hEW5xg~>g37zMQD_vpTG`QRMQVTTS@q!#ub+*oiXH+J$MB3bR+c_WjAh+$SATyFU?pGy;KtVe{)5PK=ePR%>FpQ`?m<30AaTpR{rzo#ZTI!}@4;h2 z?{37q^6^gS(;w*XF9+Pd2@x{D7ZEBfLp>BSTnYGRz|DYZh$HR<90|A&Z~{a3;_O-@PH$Jgn9vI0rmlI0vv&t*Z&PL7qAhhbmf4b0=5Fq`}h9- zt$?clUjuvu@E~9#&asE%kmohPDS*Bo_xIlbSPQruunlk{(E+ysz6ZD$aKdB#{ht7? z0vv(gXxIff1@Jw<8vw1xQ64ZGa3kOXz-@pV0QUm61AYQ{4DPp#zfKvch0^R`l zB;azuU4R<_UkBU<*ax^5FcVjKEc`~y^?*LWm4Nh{F%JSR1ndM{0r)Q9CcwV~b^x~g z6ypqdKVS+%$z6cw0d@oC0cPVuSs7p+;7Y(K;1ZO|0dsLw*a=t%7zLzb%QW17 zIUUbzj0P+NECAdGSP6K-v;F;R09yec1$-TFH(+K5^c`?M;86VV_S=A?0nfv2hXTOW zfR%tx1Fix51K^{8e*@eN_zygm)(e>T9OQPoWqk`U6R;Do5b!;~TEI^LTLJTS!Y%-o z0CoXZKM#Ec+yFT24Cn`74&cH_fB#&-Zvut^Hvz5(JOH>AaAGI)9q>xPgMj6L!$w%v zn}9ihy?}E8tzV)%;I)A30dE7`3b+aIHNcz~FwX!h0f*x!0q+N#2>3qWJiy`qiTMjS z8E^w&4PZOq9e{fP+W`9j-vJzcmStVE8|4AZ0OtWd2-pbN3Ah391Hg8`YhHxD1Fi@B z1n{STBhE&J7*1n2`C+11~l3wR~q zLclixR|38VxEXNRE3k`z*?{{1=K`jj1Gxa62e=V15AdgeWq?P$it>Q_05=0B<7jjz zU@71}!1aJB=UUdofad}30?Y&416T$){WX*aECSpNxD9Y8;7fq}0FV0>%74+a&H_9S za0*}^;P(K_0Dlg+5^y)*X284mKyLsy1NH(w37B!7Wu5eE=pEp6z(T+xz*@i+fUSVf z0JZ^U<4nB^a5rEdVD4`)Zs%j20GtR|3pfvO9bhBiqktO#w*j^Teh9b+aMfOn8{kWT zBfbPVy@hcDyawD1N z+_K_J=?5@R0mFb>0apRO z2KYK)AK=tKV!TFM)*QfzfZG9Y0DK2B3W0+vDj>c*Tdx(EI_?LDk#tHeX6EbF;F#O8& z<8De@VO@UuqzlHK2W?j6ZvgbI!uSwEKGa?q|AqsfN#*53ZLPt-;_vqNWBJ2^c|yjj zJQHHwSbq6Apc*L*ET_KImBf9Qu_G{Q%HYK!>?-=+`*(q2Pz;6HI!Q zLmv(LY|vq*o$_CC=mnq`fX?#L{Lgpjl~NvJcgmMJ^fjO_MEOMhdld9*Ku^T~ZqSQB zzsRhAkyHNx(91wS$E445=tGn7(-xp(8F%% ze-5$R83uYG+JRYh@?5X_=>sYU^edbD`_F_tU3H`<*>%iAp3DJwZ3KPd0J7RJ2)%s( zI`uOHygdgV$0J|Lec)R6HGsaJ=k(P|LEZuQ@Ch9z$El-4$%4it7xWPW^m8HT=M7N* zib3d`gudErKl$snoI5~YGeG^_1JKVyofDx`T`idNB&Su-TvJZxD>+fVg~<2zI`~w8 zDfLD^(w#3Q+x!%!TGl1tr_#l5sZFm1eKqLu<`}ipiTWRD?eG5~#xLb8PdoQ1{i1xm z$T#Y4tRckrF-ATb-@iNjbfdj7XfKgXu#QqTFU#b&T#c^}IMU-;DE}3FPk90knwC#; zfHP5^<;b%fd9J5C_n3KA32YLqR@pO*pDOg~S0zDCb=YgILdLsVvKraD35r1W%-vN3&IaB*9L0<)W zBK|gmz8v&;I?DYAeI4kDY}7u`zYF>#v}KQv=p6U|7|YrUI%bhWH~fP0gidq7E-(5| zGeuSh+YPI(*23$vl4V4=!yEb8T4$>5zcVhpW)QM6ZCTjpzj0yB+#+q zIpt;D_W?Bp`Y{aji}5`r37bf*!_`h3Bag+N3VB8$k4<0b&;y_okBR8zpicoEGu0_y z5va~MnNA3daGGK&E-2B9)=!gSq~sjBL8zV z=y!vjZProYlrI2%Gw6xNsS@<{pwBkTCo2b+^kfa_yArhT1N~9Z+d=;~vpn_T9>oLA zuQZJH#0UEOL-?LD2>}T`mXz;Whu1#TwG{dC%(`xI=);F%&VU}TFFur+2zoQ<1M#vR zyrgXsUVe_?N&;S1qOKXpmv8ctoXgCRT)H zp5+fFmVMgsu=SwFn2deHxE#LHR$((9#7s>{}#|!f!>1e zb{|D~iQWnNhoBd@=+kW(MnOO8-}?KDU36sxsC*h?tP4TE+C^7JhUgTdZ3R7%d?`ypvRj#KGeS&^e)hw&Gt!W4V`=xc_wU*FC(fW1!Mdr(5vWs5q42p-;&iP zi~7-vI%<(8k*&?Z0Neq3J@Pu`zs7R(0hI~*>!6QIGU(uQ$oY=%KNor4eW<_xN@Q{B zm}-wj81#&X5eIeAZ?fs@L9Ye<0+U|MWZnmF1^s5wZ#U`5$~mGwz6SbU(ETQTxl{fi z=*d4+zLUeB<2Mb%hMnU0W6bi#J~apQe9#lMKNs{zKu^?u81ydC%gp+dmE%SHuLphD z7VKS2dZokvR?tU*o=85gfj$oOcyrZ#xVKMP8-8dMC|!T{rxjc9x%=% zW#Vxn=%YcOY0{JPxB{Zj1N}PC6U7qfcc5!QKi4dOozwmX(Ca{t*9WS<9rQBLuQ1Es z=9J$9`Wn#Vt+zgu=>z?npbz!R;8UlL5oo;bk;HPI0{U{$3v)p)06ojBL-vm(gN2~e zQ=wOz^kipUSpoVw&=bw&O`vZ8{T#Er_^woc2k74gJ&~>G2K{l+(N#yrMoiFx!}uWR ziENz@bUGu^FpndVXCCq-($fIwC7^%bY$JI+canH42fZ8gczqxqR)hWy z=&fdX;=$OTJc>NC+7jD~-Jo9v`Y5vwV~=(K^Z@9I#&IZI^ckSX8gwe3NCpL< z&mN%uO3({I$8b2~V8rj&Nc(e5dh%7a3?BvkW|V)+MSsnv?*@J1PZIa<0O%7yFLsr8 zeBhzjF5LurB3m{Z^wppzS}zJfzZvvIafC{tCn~=N^h%Up;^MEw?%$)J|7?KvcZ2>T z&|PyqSs4QwzXPE64p4q54mREaJ&`RM4SN3oSh?-{9(t6Z4w~`kSC9(xpbwdj_a~1L$voexX^vQU$h0?Vv|NPc&A0KtJjU zocWvOlb3M+DNc|HUAP|f`|v$wIyUNhuS7Pdg|l%xJ_uuXf_g|!x%lJ5U66QVLiC0B za}wx@bYcbQ83WKafqudO^bXL6f}Y5S>IVH2@E>n&@PR+;OvT?oKG1o{a|hZ;N9ovy7o_(hIy$^LH|GdBdR;uH1qmHZA4OyGACbj9P zWcJLcu05!0F7hVQ%RbO&fDYjBV#Fhd!-(gBo@3J0^s%V^iJ;FLp#FKFUpWB15%ejb zzh%~+?5t%QK>v7v^6j7>0DY2K9&?vir1_lzojB^L{{E%-o^lrsfwW%I{5Je^8l1Vv zmuTF4pcjCC5cSw~DF1OE4IIfL4|zuXBC)K?KtBQWY~;1;Fns8hp!*V(r*YqlzMKU5 z;w1FtHz`gX`C`|LSWTIiF4Q%1TVh^%L8m)oH=tgdmt@CoXPkq5Dd-TdLzlAvA5fV> zKOVf;<&`}Cq2)0bc@{klJvH+T3EOii4El4=c+LZs@-*N+tOxxa&=c)zw}O7cvx(1R zUIYCy(Bs)W8n1((&jCHr+!zK2aK-@j=YYNxbX4QWz}S<`1^p(_UAk$Uw}e4&1wEdh zL%O>g!ru$}NBEv%;h@jd-QPQM+J<_5*pYaR-3xj<=<)jLL*1W%{s`zUyG-+As?&z= zi-`9jPdr{pmbsvxvpsPe3qd~_^lId_`=RuO9W$zf?sd&ao<#Db`(6t`PvlGN1ic3I zM1J)?&=-LoPY%?63WPpyfcnn^y#n;rW*>|*_&m^G0X-i7RHVN@>C{=NzdXt+Pu+Ty z_2Z;>QksvpRvwMz5|k4gQu|V^?<74lj{km+MU zT<}TqUx!$mQ&hh6Nr1me`s#bh)^~;|RK_Tn?C&J6AM$dl^+~G2QI+&>=!Nk5=A>S4 z)hqnai;(_mYKe7=J0Imuo18k;x?bVU@66skN!`iNos`tuQ?19S5v%`urz9OeC3Rj> z-LcE9bxA-aQP*|LT;mm~v#cdlBk~UYa_ZQmxvAGNLS^NeS0<%?m}LFGq|}F!txd^F zM8{s2`b3iTJF4`^=ZFN3NZ^PBj!58$1dd4Hhy;#E;D`i{NZ^PB{(qMMJ!?pxT!FG> zu};N4h(0=fz9&6S(zu$VKDc{9pW_v#9--rOfIoCrK%e6juyBOHA3iIT?*ycnHS%SJlwww= zbbOfPMRb)v98aTfQnAL5l!i6mdsKD{5qSRS_Bk#IjL!G>e$_uT$EZ&w9gu?klGc3o zO8Nw$f9#=4bN_8%xQvUp!b}-&^`ce8fF-?9V6DIv0$T-c64)lNLtvM{Zh^f5trJzt z83KI*GX>@fEEMSFbFg$;mpjK5)2C1NjVvl@s17&yvc`=aH#T$3ga%G#-GvFJGt`pg&n zS?8!UJ$FH$C4foRN!H(QS7||8-xK^N9{di$|HFg-K=7Y>@QKFpZ19z24YP6|SM8dA zCd_v2(Moa;v;jVQv({GmszQ-xfT_7(u%` zMd7C{R2li)2w&I3@KXNTa)swOI=((D_-p1XytZ$>g5Q><@Ly5DqMwuVs4`Q=08jPz z(qR@pI!{>e*L_vtFI2#~Pw?xfDuiF~uL}P4OBJF@@DIaKQ~gg*Q3#H~N(-eM;I z%vAX=SHP+e{D*}Kaiid$7yJv-uI7K}i8#ZvPPgr_WVcQRp6XvTLt*&-Ccc&ne(7Zj zUoQnNhjLQ>r#$uFA^2lNp@eLGD)`;BK;WatWit#G)ie7-h5w2I)_)5AfGDzv+xn~E zOTMHsYX9vFI7(Dc$2AI%=J+#H@Byjk-vr+zc&}b<6MW8>RYt9!_k%GXhBWZ zsh*zcDy{Q8Ech;w50dq*8sO7-L|Qn_{6@q(|F`I04-odSoQ@+Xf`i0cKPEBMu;6hhbk4Z%M& zLE-iIb~0WH8{l`6KciG3H9x0dXsO-1OBDWADf1Pf?p^2#UeM&f3M)*llj8; z`B5eUzk%wXCF`Bm|1XYE_%$+)TF%P^U+(E|hv2h>&tl={m@`#98?IIeUH@d@PeQxT zi(=Aqo%Fd}@~_TTY0ZC?;CEfB@Y-%XF8I506kf~icfjlKilY7=4+f~ekI6W`D8xyE z-z@X}dcj{O_)3{~Viv7+z{jiq2az^GRk4WMdKwCIV)E&h*WZ2tc;a)b z#}4-j{xuK&RG-S<-u9W+%h+0_Hg1wO#P{S13H+VZhg?&X@WpD?GZ%pW|Uis9z7yQfVwr|jU_9_t&uFPY%!E}e_$gA4E;}9LP5HlBt!mM7+X(zQ(4P#k|7p@l zyX1dTGF~G1UcnzF_Muqtxey4|KRHt&3I$&x_)leC+$Q*M0Dn^QiFQ0g(AHYXZ_Q8` zJ&t<>|E}~`k0ZTPgxY;i#^n^LXd3Xk@49Y+LCOE1%=c^|-Y59c!uYjB-udz$g5NEAE8Vu<5d5_@s-9{E ztji$~s^4qpR|$S~naVgx^8Z@!2c=(neCI)*sGf(HsEk^#ZV|k9-P|Ggfanj*CV!H# z5Kukd^>>Wmz5Z~a;789^T zH2)>gZ>rzB&ORe}uRiw)J~~&mtLyPY@2Q>-uU2?H?oEQ=(}qk3DP`e15jd2=(L7aRHT|-rYtY0alLSH(#ajdLEsI zc}w*VD_3~kE3R-xcO5`SQ^;O`W?*PcHGyw(}X%3uikN%dFCI0~?) z2;SRnjo?>_e8xy+KM?$T!e_DI4+=h9st}rovEZB9-68WLEcvSh|0fZ!Ob@F`@FStu z^bz;PdQtGFNhzI3&!|7p*9*H)ww@dt5gWE$DfyHD}EM-cWyio_Vod@NZVDjM9vC^gNaSRn$ix5t9`Vyw|TQ7knA)4Slpd*(CU+a)sAC zcMJY8(Gw|VoqHYWn{)o9N?F!e;EDeaG4JW4>%Up>Ui6$)4{3VyrHU(L@ag1-mr41FdjVC7&wQ$4vdA9Do1Qt&5DRS2yQ z{~`FjGEq*E{J$6c2IvcYbRUOT}L zKR#PwrwDQ8BE`?cB4@q6Gz-2@?4hpnSAu`vuPV^^)8HVHd_EQX!|zf-mMaABuW{#p zK=3753O`8!>lMM*U7--#jvWJgP4%z;vcjJ&`7;Ioajim}BlvZKPmy&K%L;!=DpWmQ zyZxBpuaWw-ed`f?xvcwI|FgcP>IsN^AYT5|2>x8Lt7i%RQNer9-98Zfo0wxg?~SKDBzSNA&s3@Uz2|qo7rb{}9$l^S-|giGc%Nd$ z^|wy)d-pZB3;w!mRCWwEfBqtP?|N||^oHc%jdv{(y!YH;li)uV`J62E|48sr$b&xG zF8vvJA7k8q(xFe(t~bu(7yJ)oye3MqHw6Eb=qTXKjDV1^>-4s-E+NpVflDQ2MLe?G*eKkT-o~`dRt)s{XwfD7@DH zrv!h}B?{l7fc3uMj}vk_+s_?7FDZJM6I|cu^ z@UQK|9|b>F=DY6K_?uKcSBPF|yfvq^E}S*C#tH_DODcll;>8va_2Gs^i^i5(Wudyz zV*Kbzs4f_;3YJ#Z;8$Fbv#ch#xU#0CxH4FVUka%Y7B@6nr8V@EE8$St*v$Aj@N+KJ z6~W@Vy5eO){4h)1GHX#Cet;!dh987kh9X7^zbH}>c4akxmc{wmnBesVS(zmb6_sT{ z`W=;u3JaJ?i|RrlPS2hbo}hlNB!5mYzc7b>sbx-a{+z6!-%oHHAeG1-hhKCF%CEx& z>DOW8=V(+B&XOIIg@2vK$&od|`)fB#so?k$`u&%Rii#4X=7h7dgTadWApaIiaB2RW z@Ob{3Ur?Fl$3Ofkj{5kSvqAG8LN=+78W_Az-Fzl7!})_VW>1~z5Bg{21%nu*D`pi1 z{Q*G+@@8AXyz6F7otZz~3SLog^|Yx4!K-J?nB$)toI7<|fgdHVpM_3j1_QBIz z%8(w0te}hp=o6t)p>T0U<)mPK8C0Vpyvz@5=*9+USh$7@{3OdAGWCo|wPvDSd<)cG_quvIq8J88LYUG1X z)D~CNscbnxDiy52bb$$SIx}~LVOojREI;0>mZC${FYI;xH_C) z!c38PuR2`hn?pY^-HU^qJu9aYwq4Ii z7{qKC#PRqIs_HOu*49Do7Uf=1kUwpDFnetF*zv4xMsuj9x~8rQdTA?hp;%eR@MiNw zr<(maCB^k>gjr{&lQP#elxhyH^!t|<&JE_(UJbPuHOLNL!@ouqq@o6=WWr%fg-E~l zH9gCZX_>7H9u_8Nd}$?qXUsdyS3{1>_Ik@^}(gZmHAgG>HA^QGK0{Dd|J2- z`dmnD_M8bKqY0(u80wmF!E3U!vg?;&mFFeCbji5tnw$Jf)yU7A7aTVhce5vXwQ_v0 zfPRgQtQ-%{#Za;Gnwlm4LbUBJ%Zf9I-U#c)ift&48rvK_wh%yd=q9K-EH9f*>2_9T zSxAr}u#pv%sid6%(M|CHFn^#h;}TZtu;P?sHTE=+ly)(=0f7q0ggDmyC@w{_L;wXrBqYc_i*!B9y}usl>*3-eh}kx8B+Nngn+ z#tQqDdjMtPm1__!7fp(59XR_>+omI3A;0cP=W;WzF7iltbgA8H3k)VwPs>`1`;QrSv#5hQ?9{y*DO@x0obX{hH5-uB#(&Ezc5Ie*$9_$AEZyB^~)1V!G zD}G*T3-&-P~No&O)pOYL&>Wt)VRj%Gba@P(GA14XWXI z4P?0dC9|(03mL~9#6B>S=EihcZT=}6Lkl2gvR>g?J+aJdx4^D@Kx1xfD^;JwezPb; zWyRrQ<>+WL8qY<}$^j$l7gN=Ga3y=5nDmA0*xHT}ueRjR#&kbBn9o0&>hf!Cj|b9p z`2ogO81~!V3V$xYfaZ5*F#bN6#?Z0D@wdSf)E?NJdWRtSESqbW)YalMCk$nyU)T*+ zEXp6u{>EtHIJUWDf|cJZ?s2U5OU@qhz~<-C|Mn(B{NSu{WepsRsScJFV_O74P1KnD z%J`8QYO@j(CSLpoK3jNFsG5Qv)USExFas0=wmpt+1lLa{cpM9EJ4kWd8Dno*6{^DaQtvHk87?j> ztJAh9zPAm{niIq#Qo2M1_bNlxE?)F})4s`IZi{XDWb_6dDulK$SktreE0u0t z>N+#7v=R{>(Nl`U%()ozH~(rZllTqjU_*VVOxXwwiMad}0mbr&I1x0t%(D&PeZcs` z%HO6~m&hGfQ=E$eJ`?IfRW(Z~ zHix(cVy1|v!Ld~ng2ZhOReS?R%LAfgVLQY?cBw`@LD-kd%3yUqEOe}b@}7vA!&pxs z!vOurq5(5^b%_dc(1P=3Be?7KJ4GS4Iot7WW8as}pW=`^pjU9V=s2H(*?Lb`REcS_GNbY%#;gBuS z|M9RcZ;{o05tgYe)TlQnT1^rj9XP(4y#RimeT-wf2W>Ht5hWLh6#X3-^ zInLsVeV4s>qU)J@afaiyI9whK)z#J16<$?pMru`Wv=wrDn3HN75JfC0E?r`3T>QL_ zVI0tDb%+#*;zIB&>qB9$^>Qu!2_tp!w-~f)mE!DHd#8uT)E^hZ%!l#^%jyxUgi=?A z23JMB+B>m@cna$*N3n`JNIU_7MQ3}Cy>Rxx0eBpZ!)?(6qg5&4a$8}q%!kJqVwuwi z$`Y?uejHZ9#D{~*-oejAa1-&Go9c>db#MmGE7k&b6=fo;^PQ>++RDbB^3a*WA?}*| zypWQi$-*^{s+#H`?Njrw)H_sX+lqB2OegFdP|An9icKx=XQv}Nif#8KwV+T{hs7Fv zXmiS~>G}?UaU|ru3!rSTc^zPSHtmPhu|MrejWg154LF6Rn>uAd7*BngWZaj)Ccu4{ zLPrgpO|)E-aoc=y44f0P&&PJwe?Qws1WJ28hlesDIHb4L6i|TwI>_A*!>xx85_1+G z&oPU8RKeq{jw-O$YD?@^hKW_hjlsk4iUw?HJ+3fRa^EO`5vnh*sZ^_LaeaNLE*xBh zt4Us;WZ;c8c^!2Aq!%}5gZO_p@Cz3bu2fC+*BWqERIX1;xrc~IQ>5d+AE z$08au7WZ2k8-qBGt*@coZFpI5DXth&Y(qQVnEnaRI_U}y-`&G;yB>~#4hXYU91n*L zdYfq<9_p30iZ_+xjRR|vitMQ}E=RH9IQpKbF5lw0*u2FPq!Sn& zeTMl%96?72%IH{&PNLa&hz%v^4U!@4gubK%!pD)v#OFtI!sDtcs`IO`;K6CZL5#jg z6@n4La+C+)TtsEFMlnKf(869h)Kw%kgnE5WxMCRE8xz|WBd)n218npe=iz6fBlC*NK>}|ip)g<@B2-4oEwy&lFGhsd2Ri_W9T$)8)Ry`UPoj{pVm_)>>l)Y)C5 zXLRE_&Xp2X8Lky@chD6ymj#~{_aSh?P_t+tf3*++JKPu)>A4*~50f@gZo1342M%S7 z1umOcxI#Hhru)x&t(p*Y+Bq!TJKl?`i%7IfqX#SK1_tcs|JZXmG;IG{)+NrFwcf!U z9-bq|#`#8ANeHK1>aiKzZdA|isrb0pHJ#NpL}fJeaiMaRt8c3E9`XI_Xsx<8swBo;pr=?G zaPvp6ED2@cb?WRhQ{~jz_u*;q4hxG%mz!MYWwClOs7%zlUshSTw92?9G*PMGnX*vDC%um=X%z7R^^X#&Q7pfl0qEmUIg%c>j(SQ5em$Mh^|JzEfp zo8ZPWot9B018ARG&fSYFg$FbJgDkR+a6XSUpwNYF4QS^ae|yJPfNx>g&ot1IuO2!= z|I{;daVGn}=1|d-N}(IIzkGO1T6msj0L1pYJs#S$-Yf7!jugzM z06i{WmzFPapJyRPPbXF4&T18icQa&ssHzrE=F{He5O$sR6B6`rQSoAk7Cg+IIbFX> z06Qi7A`|@fI42}zsQEE?YAwzUr;A-ToZQS>t&GfoY~rIhPQ=Xu`3yjt>_Xg)swe2&U z`r{g!=lQ9_!wux;*YRXmJ+1*&FUAvl>WPqqFAz8c9|+S_U+$pQ_VZnz%g(1TdF-{n z!MqvqRc)`zvuB zs+)Bd-kIn;b}UD_hcs2-ywf^lA2c2w+Cf%Ni`IQ)VlIuwV_b<}h(=FGx$W`idRCam z`*X+QoE_F@H-s_g`=UsOQV%#!2DzcckG1Q#pR)suw-nJK1>R~H-I)qtG zLHNP0ANXzcwdd(S-6aaM+Yg30dntO`Nxao3-o^Pt*jK4{U^>;)Q}Qvfr`Vmbv3Y}P zVEXEikX~pL-jBh;J_pxu<*vR_Qu`3vjWN$^WTU7_?BQrL3EtHyLzeBy!G|+<>u@Dp z@3~Ok1zvMnUs_yQTt_dm@S}D0nv11)86iJDL=W3fr3<+9-~m6qTo>YdlE>M_*u1 z>7egl^8f>pHNDqY8!ypN6~T>pjgqX@cC_}8Y`J3`j3JVR#`8)%*P12)J_>Qm0cnh9 z7Pl6Rqy&2S97>|9-sk+r=~H*DoQ}^%-o9ho4n+m&CUaQ`_sQvPpcHFR+T`4*adj-= z@i$_f&gB}-SK>)SmW#FAd21N2nshxWSb}GT`Fguo9S3n0&~Z4{16(bDol(!|4f-4& zBH`w%OK|tU-o9opZ{~E2R>E$|mejfSPJ{m0G|*WHgmvt@@Cs++#W_Jz%|m{B4t6ER z%X9EH9s8j-m(C$&*lw*emY0a}=!{|uFQApmF&>5+IGn>{8CkyE;TT#7- z#nB6lD*RD22!3Ve#_anJO))*s$f~zTqUZp8nAThlGmNzzGt|EQU^}aDRB4A)9con1 zAbBTIy!Kc$u6gqrc6y>0xNcIY4~XJe4)WTt4A`%t9b{dM$7Q^AxVBcGN@*!3oC0QS z1K|VphE1_+h;Cw_X?R_Mg@GRYh<%iU-6h_g+RlYJSfA%Y07=brB8XR8LK}(IK}0BO zZL60V2!c7l@~dxPex}U|IVE7gxxZ=qWrO;1B%6xr(mFL{{{;Iy>Ud)SUGI-Ih7LpC zH`S6j8>;zTweF`cvhbo>c0{n%8tiR4IHq-6k#aaC^6ou6o#fIfKQbnMz%cf~wgH@< z1oK=U2bfwG=T$%2otvOHH%c?yufdep9)0#@!C6Jf`YXk^ z8S3W5QXQ2+#RK2V#iJ4GqEM{Q4ZU~F3nkDLKSF_}wq&jtd0FG|2z`PV!0}tfXoaG8 z&v_q@N7K&2F0VqIg;_(d8lB75)_&m|PPZ@>r%JQ7?*7x89Q!cN4tBU!sX-i=0gvDD zZWuGxVe|Yi?W8-C<@3ekvl7MQG5Cj1V8wg$mM5Qmz2!446vcD=dHk9jXVzji4g5aQ z1XEVcpsev_74&L*dPSVtNAkqM%O%7w`)vL!pAR~VD=(}1)~UNDw?%@3=menj6PN!) z{DW_WG>-IW8XUo#tWN##iVTraGUS5Mt7WLKNZWd%}Zk6DE^zk zoTdNpP%z9V<8rZzHQ{*B+$CT)tIn*TQufKAik`Bux$%3;_o0FEO%WNU0z!NTqK6Mp+2KPJE$&!wv8_B;#zuqZkn0SndwC(+ z_?H1UHWzNx+spMu5g6=Gnt0`6m&;2Or!DIvHbKE!pMJdXNo+<~Z7~{-E?yGIi#4~LfXm%TBW&`z}(`#D= z((FtdGTuVQQ`4fC4?0vZnh!3!<9PBV?AO986#)AnNCbu05wd z#o>9N+y&D_5sjzOwPdw475SpEu}Tuyr!L|OBNdKY68uTNwWnR zNv-xu@W}r`j@Rey5d`{=i)LQWE}IQdx4yD~D3rEa>H{$|i}l7r&QXRRMo4o4FkACh z+a>V*{kVA-YPka0CwPO>Xcn+r-n#65=9`qp&5l^ z7rw+#8J=_^WQKhxa!9@!3F)Wg&3~y|Y`jD<0BjYEY@BhT`)5hmB zQ|ywdFdyuN1%C+N82=p4goWWlwYnOkQolnJm;Y0EhWMj~BRymBGmI#B_aeeyKd79f zY({sH(BKWzBnKJf>Dm%2}U$?zq^4_p3A`xV1@Sr1;~ znY<$LW1ob}xiP62Fl@i-%J2-Q1aOFVpN(@(DcgCBr~3%N5*Fhx8NRfp9E^wj5?5$O zE&hq{A?(DDN6(25X;ei$Vs=e|wc|>UfXII#)5G_Y?viO&PW@e#iUo6JSt)-RFWu7I PJK+bfD(8s^5INm1`{yMc literal 0 HcmV?d00001 diff --git a/defects/scummvm-0001/test/scummvm-0001-test.cpp b/defects/scummvm-0001/test/scummvm-0001-test.cpp new file mode 100644 index 000000000..5acb23902 --- /dev/null +++ b/defects/scummvm-0001/test/scummvm-0001-test.cpp @@ -0,0 +1,227 @@ +// scummvm-0001-test.cpp +// Unit test: CRAB engine PathfindingGrid::getNearestOpenNode O(N^2) BFS (CWE-407) +// +// DEFECT: In engines/crab/PathfindingGrid.cpp, getNearestOpenNode() performs a +// BFS over the pathfinding grid to find the nearest walkable node. The visited +// set is a Common::Array named allUsedNodes. On each +// neighbor check the BFS calls: +// Common::find(allUsedNodes.begin(), allUsedNodes.end(), neighbor) +// which is O(|visited|) per check. Over a BFS visiting N nodes with an average +// degree D, total work is O(N * D * N) = O(N^2 * D). On a 64x64 tile map this +// function can visit thousands of nodes per click. +// +// FIX: Replace allUsedNodes (Common::Array) with std::unordered_set. +// Membership test drops from O(N) to O(1). Total BFS cost becomes O(N * D). +// +// BUILD: g++ -std=c++17 -O2 -o scummvm-0001-test scummvm-0001-test.cpp && ./scummvm-0001-test + +#include +#include +#include +#include +#include +#include +#include +#include + +// Minimal simulated node +struct Node { + int x, y; + bool blocked; + std::vector neighbors; + Node(int x, int y, bool blocked) : x(x), y(y), blocked(blocked) {} +}; + +// Build a grid of W*H nodes; border nodes are blocked, inner nodes are open +static std::vector> build_grid(int W, int H) { + std::vector> grid(W, std::vector(H)); + for (int i = 0; i < W; i++) + for (int j = 0; j < H; j++) + grid[i][j] = new Node(i, j, (i == 0 || j == 0 || i == W-1 || j == H-1)); + + // Connect 4-neighbors + for (int i = 0; i < W; i++) { + for (int j = 0; j < H; j++) { + if (i > 0) grid[i][j]->neighbors.push_back(grid[i-1][j]); + if (i < W-1) grid[i][j]->neighbors.push_back(grid[i+1][j]); + if (j > 0) grid[i][j]->neighbors.push_back(grid[i][j-1]); + if (j < H-1) grid[i][j]->neighbors.push_back(grid[i][j+1]); + } + } + return grid; +} + +static void free_grid(std::vector> &grid) { + for (auto &col : grid) + for (auto *n : col) + delete n; +} + +// DEFECT: BFS with std::vector visited set = O(N^2) +static Node *get_nearest_open_defect(Node *start) { + if (!start->blocked) return start; + + Node *returnNode = nullptr; + float shortestDistance = 0.0f; + + std::list checkNodes; + checkNodes.push_back(start); + + std::vector allUsedNodes; // O(N) find on every neighbor check + allUsedNodes.push_back(start); + + while (!checkNodes.empty()) { + Node *front = checkNodes.front(); + + if (!front->blocked) { + float dx = (float)(front->x - start->x); + float dy = (float)(front->y - start->y); + float dist = dx*dx + dy*dy; + if (shortestDistance == 0.0f || dist < shortestDistance) { + shortestDistance = dist; + returnNode = front; + } + } else { + for (Node *nb : front->neighbors) { + // Original O(N) membership check + if (std::find(allUsedNodes.begin(), allUsedNodes.end(), nb) == allUsedNodes.end()) { + allUsedNodes.push_back(nb); + checkNodes.push_back(nb); + } + } + } + + if (returnNode != nullptr) return returnNode; + checkNodes.pop_front(); + } + return nullptr; +} + +// FIX: BFS with unordered_set visited = O(N) +static Node *get_nearest_open_fixed(Node *start) { + if (!start->blocked) return start; + + Node *returnNode = nullptr; + float shortestDistance = 0.0f; + + std::list checkNodes; + checkNodes.push_back(start); + + std::unordered_set visitedNodes; // O(1) find + visitedNodes.insert(start); + + while (!checkNodes.empty()) { + Node *front = checkNodes.front(); + + if (!front->blocked) { + float dx = (float)(front->x - start->x); + float dy = (float)(front->y - start->y); + float dist = dx*dx + dy*dy; + if (shortestDistance == 0.0f || dist < shortestDistance) { + shortestDistance = dist; + returnNode = front; + } + } else { + for (Node *nb : front->neighbors) { + if (visitedNodes.find(nb) == visitedNodes.end()) { + visitedNodes.insert(nb); + checkNodes.push_back(nb); + } + } + } + + if (returnNode != nullptr) return returnNode; + checkNodes.pop_front(); + } + return nullptr; +} + +static void test_correctness() { + // 10x10 grid, start at (0,0) which is blocked + auto grid = build_grid(10, 10); + Node *start = grid[0][0]; + + Node *r_defect = get_nearest_open_defect(start); + Node *r_fixed = get_nearest_open_fixed(start); + + assert(r_defect != nullptr); + assert(r_fixed != nullptr); + // Both should find same nearest open node (same distance) + float dx_d = (float)(r_defect->x - start->x); + float dy_d = (float)(r_defect->y - start->y); + float dx_f = (float)(r_fixed->x - start->x); + float dy_f = (float)(r_fixed->y - start->y); + float dist_d = dx_d*dx_d + dy_d*dy_d; + float dist_f = dx_f*dx_f + dy_f*dy_f; + assert(dist_d == dist_f); + + free_grid(grid); + printf("PASS correctness: both implementations find nearest open at same distance\n"); +} + +static long long bench(int W, int H, int repeats, bool fixed) { + auto grid = build_grid(W, H); + // Block everything; no open node is found so BFS visits all N*W nodes + for (int i = 0; i < W; i++) + for (int j = 0; j < H; j++) + grid[i][j]->blocked = true; + Node *start = grid[0][0]; + + auto t0 = std::chrono::high_resolution_clock::now(); + for (int r = 0; r < repeats; r++) { + Node *result = fixed ? get_nearest_open_fixed(start) : get_nearest_open_defect(start); + (void)result; + } + auto t1 = std::chrono::high_resolution_clock::now(); + + free_grid(grid); + return std::chrono::duration_cast(t1 - t0).count(); +} + +int main() { + printf("scummvm-0001: CRAB PathfindingGrid::getNearestOpenNode O(N^2) BFS (CWE-407)\n\n"); + + test_correctness(); + + // Benchmark: fully-blocked grid forces BFS to visit ALL N nodes before returning + // null. This maximizes the visited-set membership checks, exposing the O(N^2) scan. + const int W = 80, H = 80; // 6400 nodes + auto grid = build_grid(W, H); + for (int i = 0; i < W; i++) + for (int j = 0; j < H; j++) + grid[i][j]->blocked = true; + + Node *start = grid[0][0]; + const int REPS = 20; + + printf("\nBenchmark: %dx%d fully-blocked grid, %d reps from (0,0)\n", W, H, REPS); + + auto t0 = std::chrono::high_resolution_clock::now(); + for (int r = 0; r < REPS; r++) { + auto *result = get_nearest_open_defect(start); + (void)result; + } + auto t1 = std::chrono::high_resolution_clock::now(); + long long us_defect = std::chrono::duration_cast(t1 - t0).count(); + + auto t2 = std::chrono::high_resolution_clock::now(); + for (int r = 0; r < REPS; r++) { + auto *result = get_nearest_open_fixed(start); + (void)result; + } + auto t3 = std::chrono::high_resolution_clock::now(); + long long us_fixed = std::chrono::duration_cast(t3 - t2).count(); + + free_grid(grid); + + printf(" Defect (O(N^2) BFS): %lld us\n", us_defect); + printf(" Fixed (O(N) BFS): %lld us\n", us_fixed); + + if (us_fixed > 0 && us_defect > 0) { + double ratio = (double)us_defect / (double)us_fixed; + printf(" Speedup ratio: %.1fx\n", ratio); + } + + printf("\nPASS\n"); + return 0; +} diff --git a/defects/scummvm-0002/patch/scummvm-0002.patch b/defects/scummvm-0002/patch/scummvm-0002.patch new file mode 100644 index 000000000..6abb5cca6 --- /dev/null +++ b/defects/scummvm-0002/patch/scummvm-0002.patch @@ -0,0 +1,73 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/engines/tsage/core.h ++++ b/engines/tsage/core.h +@@ -835,6 +835,8 @@ class WalkRegions { + private: + void loadOriginal(); + void loadRevised(); ++ ++#include ++ + public: + int _resNum; + RouteEnds _routeEnds; +@@ -843,7 +845,9 @@ public: + Common::Array _idxList; + Common::Array _idxList2; +- Common::List _disabledRegions; ++ // Changed from Common::List to std::unordered_set to make ++ // contains() and disableRegion() O(1) instead of O(D). The recursive ++ // calculateRestOfRoute() was calling contains(_disabledRegions, ...) on ++ // every connected region per step, producing O(D * R * depth) total work. ++ std::unordered_set _disabledRegions; + public: + WalkRegions() { _resNum = -1; } +--- a/engines/tsage/core.cpp ++++ b/engines/tsage/core.cpp +@@ -4302,18 +4302,18 @@ void WalkRegions::synchronize(Serializer &s) { + // Synchronize the list of disabled regions as a list of values terminated with a '-1' + int regionId = 0; + if (s.isLoading()) { + _disabledRegions.clear(); + + s.syncAsSint16LE(regionId); + while (regionId != -1) { +- _disabledRegions.push_back(regionId); ++ _disabledRegions.insert(regionId); + s.syncAsSint16LE(regionId); + } + } else { +- Common::List::iterator i; +- for (i = _disabledRegions.begin(); i != _disabledRegions.end(); ++i) { +- regionId = *i; ++ for (const int id : _disabledRegions) { ++ regionId = id; + s.syncAsSint16LE(regionId); + } + + regionId = -1; + s.syncAsSint16LE(regionId); + } + } + +@@ -4325,8 +4325,8 @@ void WalkRegions::synchronize(Serializer &s) { + void WalkRegions::disableRegion(int regionId) { +- if (!contains(_disabledRegions, regionId)) +- _disabledRegions.push_back(regionId); ++ _disabledRegions.insert(regionId); // O(1); unordered_set deduplicates automatically + } + + void WalkRegions::enableRegion(int regionId) { +- _disabledRegions.remove(regionId); ++ _disabledRegions.erase(regionId); // O(1) + } +--- a/engines/tsage/core.cpp (calculateRestOfRoute change) ++++ b/engines/tsage/core.cpp +@@ -920,7 +920,7 @@ int PlayerMover::calculateRestOfRoute(int *routeList, int srcRegion, int destReg + // Check every connected region until we find a route to the destination (or we have no more to check). + int bestDistance = 31990; + while (((currDest = g_globals->_walkRegions._idxList[srcWalkRegion._idxListIndex + foundIndex]) != 0) && (!foundRoute)) { + // Only check the region if it isn't in the list of explicitly disabled regions +- if (!contains(g_globals->_walkRegions._disabledRegions, (int)currDest)) { ++ if (g_globals->_walkRegions._disabledRegions.find((int)currDest) == g_globals->_walkRegions._disabledRegions.end()) { + int newDistance = calculateRestOfRoute(tempList, currDest, destRegion, foundRoute); diff --git a/defects/scummvm-0002/test/scummvm-0002-test b/defects/scummvm-0002/test/scummvm-0002-test new file mode 100755 index 0000000000000000000000000000000000000000..815f1cbd54969a636ef1e0d1eb12575f0bb99d40 GIT binary patch literal 78488 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 ONjO#uDw$l5#{UDwy3a5G literal 0 HcmV?d00001 diff --git a/defects/scummvm-0002/test/scummvm-0002-test.cpp b/defects/scummvm-0002/test/scummvm-0002-test.cpp new file mode 100644 index 000000000..8c9ba3528 --- /dev/null +++ b/defects/scummvm-0002/test/scummvm-0002-test.cpp @@ -0,0 +1,162 @@ +// scummvm-0002-test.cpp +// Unit test: TsAGE WalkRegions _disabledRegions O(D) linear scan in recursive pathfinding (CWE-407) +// +// DEFECT: In engines/tsage/core.cpp, calculateRestOfRoute() is a recursive +// function that finds optimal walk paths across scene regions. In the inner +// while loop it calls: +// contains(_disabledRegions, (int)currDest) +// where contains() performs a linear O(D) scan over a Common::List. +// With D disabled regions, R connected regions per walk region, and recursive +// depth up to the number of regions, the total cost per pathfinding call is +// O(D * R * depth). Also WalkRegions::indexOf() scans O(R*I) where I is +// the size of the ignored-index list passed in. +// +// FIX: Change _disabledRegions from Common::List to +// std::unordered_set. insert(), erase(), and find() all become O(1). +// The save/load serialization is updated to use insert() and range iteration. +// +// BUILD: g++ -std=c++17 -O2 -o scummvm-0002-test scummvm-0002-test.cpp && ./scummvm-0002-test + +#include +#include +#include +#include +#include +#include +#include + +// Simulated Common::List contains (original defect path) +static bool list_contains(const std::list &l, int v) { + return std::find(l.begin(), l.end(), v) != l.end(); +} + +// Route-finding context: simulate calculateRestOfRoute with disabled region check +// Graph: nodes 1..N in a chain, each connected to next 3 nodes +static const int REGION_LIST_SIZE = 40; + +struct DefectRouter { + int N; // total regions + std::vector> adj; // adjacency + std::list disabledRegions; // O(D) linear scan + + DefectRouter(int n, const std::vector &disabled) : N(n), adj(n + 1) { + // Build adjacency: each region connects to up to 3 forward neighbors + for (int i = 1; i <= N; i++) { + for (int k = 1; k <= 3 && i + k <= N; k++) { + adj[i].push_back(i + k); + } + } + for (int d : disabled) disabledRegions.push_back(d); + } + + // Returns path length or 32000 if no route + int findRoute(int src, int dest, int depth = 0) { + if (depth > REGION_LIST_SIZE) return 32000; + if (src == dest) return 0; + + int best = 32000; + for (int next : adj[src]) { + // DEFECT: O(D) linear scan on every recursive call + if (!list_contains(disabledRegions, next)) { + int d = findRoute(next, dest, depth + 1); + if (d < best) best = 1 + d; + } + } + return best; + } +}; + +struct FixedRouter { + int N; + std::vector> adj; + std::unordered_set disabledRegions; // O(1) lookup + + FixedRouter(int n, const std::vector &disabled) : N(n), adj(n + 1) { + for (int i = 1; i <= N; i++) { + for (int k = 1; k <= 3 && i + k <= N; k++) { + adj[i].push_back(i + k); + } + } + for (int d : disabled) disabledRegions.insert(d); + } + + int findRoute(int src, int dest, int depth = 0) { + if (depth > REGION_LIST_SIZE) return 32000; + if (src == dest) return 0; + + int best = 32000; + for (int next : adj[src]) { + // FIX: O(1) hash lookup + if (disabledRegions.find(next) == disabledRegions.end()) { + int d = findRoute(next, dest, depth + 1); + if (d < best) best = 1 + d; + } + } + return best; + } +}; + +static void test_correctness() { + const int N = 20; + // Disable every 3rd region to simulate partially blocked maps + std::vector disabled; + for (int i = 3; i <= N; i += 3) disabled.push_back(i); + + DefectRouter dr(N, disabled); + FixedRouter fr(N, disabled); + + for (int src = 1; src <= N; src++) { + for (int dst = src; dst <= N; dst++) { + int rd = dr.findRoute(src, dst); + int rf = fr.findRoute(src, dst); + assert(rd == rf); + } + } + printf("PASS correctness: defect and fixed agree on all region pairs\n"); +} + +int main() { + printf("scummvm-0002: TsAGE _disabledRegions O(D) linear scan in pathfinding (CWE-407)\n\n"); + + test_correctness(); + + // Benchmark: many disabled regions, find route across map, many calls + const int N = 30; + std::vector disabled; + // Disable every other region -- maximizes disabled list size (D = N/2) + for (int i = 2; i <= N; i += 2) disabled.push_back(i); + + const int CALLS = 5000; + + printf("\nBenchmark: N=%d regions, D=%zu disabled, %d route-find calls\n", + N, disabled.size(), CALLS); + + DefectRouter dr(N, disabled); + FixedRouter fr(N, disabled); + + auto t0 = std::chrono::high_resolution_clock::now(); + for (int c = 0; c < CALLS; c++) { + // Route from region 1 to region N -- traverses full map + (void)dr.findRoute(1, N); + } + auto t1 = std::chrono::high_resolution_clock::now(); + long long us_defect = std::chrono::duration_cast(t1 - t0).count(); + + auto t2 = std::chrono::high_resolution_clock::now(); + for (int c = 0; c < CALLS; c++) { + (void)fr.findRoute(1, N); + } + auto t3 = std::chrono::high_resolution_clock::now(); + long long us_fixed = std::chrono::duration_cast(t3 - t2).count(); + + printf(" Defect (O(D*R^depth) per call): %lld us\n", us_defect); + printf(" Fixed (O(R^depth) per call): %lld us\n", us_fixed); + + if (us_fixed > 0 && us_defect > 0) { + double ratio = (double)us_defect / (double)us_fixed; + printf(" Speedup ratio: %.1fx\n", ratio); + } + + printf("\nPASS\n"); + return 0; +} diff --git a/defects/vita3k-0001/patch/vita3k-0001.patch b/defects/vita3k-0001/patch/vita3k-0001.patch index 91ecb0a08..50bc135c3 100644 --- a/defects/vita3k-0001/patch/vita3k-0001.patch +++ b/defects/vita3k-0001/patch/vita3k-0001.patch @@ -1,7 +1,7 @@ # UNDF: UNDF-2026-XXXXXXXXX --- a/vita3k/ngs/src/route.cpp +++ b/vita3k/ngs/src/route.cpp -@@ -17,6 +17,8 @@ +@@ -17,21 +17,24 @@ #include @@ -10,7 +10,16 @@ #include namespace ngs { -@@ -28,7 +30,13 @@ bool deliver_data(const MemState &mem, const std::vector &voice_queue, +-bool deliver_data(const MemState &mem, const std::vector &voice_queue, Voice *source, const uint8_t output_port, ++bool deliver_data(const MemState &mem, const std::unordered_set &voice_queue_set, Voice *source, const uint8_t output_port, + const VoiceProduct &data_to_deliver) { + if (!data_to_deliver.data) { + return false; + } + + for (auto &patch_ptr : source->patches[output_port]) { + Patch *patch = patch_ptr.get(mem); + if (!patch || patch->output_sub_index == -1) continue; @@ -21,35 +30,36 @@ const std::lock_guard guard(*patch->dest->voice_mutex); --- a/vita3k/ngs/include/ngs/system.h +++ b/vita3k/ngs/include/ngs/system.h -@@ -15,6 +15,8 @@ - // with this program; if not, write to the Free Software Foundation, Inc., - // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +@@ -18,6 +18,8 @@ + #pragma once +#include + - #pragma once - #include -@@ -218,4 +220,4 @@ struct Rack; + #include + #include +@@ -218,7 +220,7 @@ struct Rack; -bool deliver_data(const MemState &mem, const std::vector &voice_queue, Voice *source, const uint8_t output_port, -+bool deliver_data(const MemState &mem, const std::vector &voice_queue, const std::unordered_set &voice_queue_set, Voice *source, const uint8_t output_port, ++bool deliver_data(const MemState &mem, const std::unordered_set &voice_queue_set, Voice *source, const uint8_t output_port, const VoiceProduct &data_to_deliver); --- a/vita3k/ngs/src/scheduler.cpp +++ b/vita3k/ngs/src/scheduler.cpp -@@ -120,10 +120,12 @@ void VoiceScheduler::update(KernelState &kern, const MemState &mem, const SceUI +@@ -120,10 +120,13 @@ void VoiceScheduler::update(KernelState &kern, const MemState &mem, const SceUI // make a copy of the queue, this way we have no issue if it is modified in a callback std::vector queue_copy = queue; ++ // Build O(1) set so deliver_data membership test is O(1) per patch, ++ // not O(V) per patch. Total cost per frame: O(V) build + O(V*P*out) lookups ++ // instead of O(V^2 * P * out) with the old linear scan. + const std::unordered_set queue_set(queue_copy.begin(), queue_copy.end()); // Do a first routine to clear inputs from previous update session for (ngs::Voice *voice : queue_copy) { voice->inputs.reset_inputs(); } - -@@ -160,7 +162,7 @@ void VoiceScheduler::update(KernelState &kern, const MemState &mem, const SceUI +@@ -162,6 +166,6 @@ void VoiceScheduler::update(KernelState &kern, const MemState &mem, const SceUI for (size_t i = 0; i < voice->rack->vdef->output_count; i++) { if (voice->products[i].data) - deliver_data(mem, queue_copy, voice, static_cast(i), voice->products[i]); -+ deliver_data(mem, queue_copy, queue_set, voice, static_cast(i), voice->products[i]); ++ deliver_data(mem, queue_set, voice, static_cast(i), voice->products[i]); } diff --git a/defects/vita3k-0001/test/vita3k-0001-test b/defects/vita3k-0001/test/vita3k-0001-test new file mode 100755 index 0000000000000000000000000000000000000000..9cbeb61ecfce6ac313061b769ccf86e1bc135519 GIT binary patch literal 102576 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 +// which is a linear O(V) scan over all voices. With V voices, P patches per +// port, and O output ports, the total cost per frame is O(V * O * P * V) = +// O(V^2 * O * P). NGS supports up to 256 voices per rack in a PS Vita game. +// At 48kHz audio, this runs continuously. +// +// FIX: Build a std::unordered_set from queue_copy once per frame in +// VoiceScheduler::update(), then pass it to deliver_data() for O(1) lookup. +// Total cost per frame drops to O(V) set build + O(V * O * P) lookups. +// +// BUILD: g++ -std=c++17 -O2 -o vita3k-0001-test vita3k-0001-test.cpp && ./vita3k-0001-test + +#include +#include +#include +#include +#include +#include + +// Simulated Voice structure (minimal for testing) +struct Voice { + int id; + explicit Voice(int i) : id(i) {} +}; + +// Simulated patch: points to a destination voice +struct Patch { + Voice *dest; + explicit Patch(Voice *d) : dest(d) {} +}; + +// DEFECT: O(V) linear scan per patch membership check +static bool deliver_data_defect(const std::vector &voice_queue, + const std::vector &patches) +{ + int received = 0; + for (const Patch &patch : patches) { + // Original: vector_utils::contains = O(V) linear scan + bool in_queue = (std::find(voice_queue.begin(), voice_queue.end(), patch.dest) != voice_queue.end()); + if (in_queue) { + ++received; + } + } + return received > 0; +} + +// FIX: O(1) hash-set lookup per patch +static bool deliver_data_fixed(const std::unordered_set &voice_queue_set, + const std::vector &patches) +{ + int received = 0; + for (const Patch &patch : patches) { + bool in_queue = (voice_queue_set.find(patch.dest) != voice_queue_set.end()); + if (in_queue) { + ++received; + } + } + return received > 0; +} + +static long long bench_defect(int num_voices, int patches_per_voice, int frames) { + std::vector voices; + voices.reserve(num_voices); + for (int i = 0; i < num_voices; i++) voices.emplace_back(i); + + std::vector voice_queue; + for (auto &v : voices) voice_queue.push_back(&v); + + // Each voice has patches connecting to voices at higher indices (ring-like) + std::vector> all_patches(num_voices); + for (int i = 0; i < num_voices; i++) { + for (int p = 0; p < patches_per_voice; p++) { + int dest_idx = (i + p + 1) % num_voices; + all_patches[i].emplace_back(&voices[dest_idx]); + } + } + + auto t0 = std::chrono::high_resolution_clock::now(); + for (int f = 0; f < frames; f++) { + for (int i = 0; i < num_voices; i++) { + deliver_data_defect(voice_queue, all_patches[i]); + } + } + auto t1 = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(t1 - t0).count(); +} + +static long long bench_fixed(int num_voices, int patches_per_voice, int frames) { + std::vector voices; + voices.reserve(num_voices); + for (int i = 0; i < num_voices; i++) voices.emplace_back(i); + + std::vector voice_queue; + for (auto &v : voices) voice_queue.push_back(&v); + // Build set once per simulated frame (as fixed scheduler does) + std::unordered_set voice_queue_set(voice_queue.begin(), voice_queue.end()); + + std::vector> all_patches(num_voices); + for (int i = 0; i < num_voices; i++) { + for (int p = 0; p < patches_per_voice; p++) { + int dest_idx = (i + p + 1) % num_voices; + all_patches[i].emplace_back(&voices[dest_idx]); + } + } + + auto t0 = std::chrono::high_resolution_clock::now(); + for (int f = 0; f < frames; f++) { + for (int i = 0; i < num_voices; i++) { + deliver_data_fixed(voice_queue_set, all_patches[i]); + } + } + auto t1 = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(t1 - t0).count(); +} + +// Correctness: both implementations must agree on membership results +static void test_correctness() { + const int N = 32; + std::vector voices; + for (int i = 0; i < N; i++) voices.emplace_back(i); + + std::vector queue; + // Put only even-indexed voices in the queue + for (int i = 0; i < N; i += 2) queue.push_back(&voices[i]); + + std::unordered_set queue_set(queue.begin(), queue.end()); + + for (int i = 0; i < N; i++) { + std::vector patches = { Patch(&voices[i]) }; + bool defect_result = deliver_data_defect(queue, patches); + bool fixed_result = deliver_data_fixed(queue_set, patches); + assert(defect_result == fixed_result); + } + printf("PASS correctness: defect and fixed agree on all %d voices\n", N); +} + +int main() { + printf("vita3k-0001: NGS deliver_data voice_queue O(V^2) linear scan (CWE-407)\n\n"); + + test_correctness(); + + const int V = 256; // PS Vita NGS max voices per rack + const int P = 4; // typical patches per output port + const int FRAMES = 200; + + printf("\nBenchmark: V=%d voices, P=%d patches, %d frames\n", V, P, FRAMES); + + long long t_defect = bench_defect(V, P, FRAMES); + long long t_fixed = bench_fixed(V, P, FRAMES); + + printf(" Defect (O(V^2*P) per frame): %lld us\n", t_defect); + printf(" Fixed (O(V*P) per frame): %lld us\n", t_fixed); + + if (t_fixed > 0 && t_defect > 0) { + double ratio = (double)t_defect / (double)t_fixed; + printf(" Speedup ratio: %.1fx\n", ratio); + // Expect meaningful improvement at V=256; at minimum 3x faster + if (ratio < 1.5) { + printf("WARNING: ratio %.1f lower than expected -- check benchmark timing\n", ratio); + } + } + + printf("\nPASS\n"); + return 0; +}