From f797e6f0ff76c780aba94572ace9e232e842f38a Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 19:43:49 -0400 Subject: [PATCH] =?UTF-8?q?dosbox-x:=202=20CWE-407=20defects=20(jtbs/dbox?= =?UTF-8?q?=20O(N=C2=B2)=20text=20select,=20bdlist=20O(N=C2=B2)=20DBCS=20c?= =?UTF-8?q?onvert);=20higan:=20all=205=20MOADs=20CLEAN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...jtbs-dbox-vector-find-in-nested-loop.patch | 45 +++++++ .../test/DosboxXJtbsDboxTest.class | Bin 0 -> 4050 bytes .../test/DosboxXJtbsDboxTest.java | 115 ++++++++++++++++++ ...box-x-0004-bdlist-list-find-per-char.patch | 43 +++++++ .../test/DosboxXBdlistTest.class | Bin 0 -> 3610 bytes .../dosbox-x-0004/test/DosboxXBdlistTest.java | 101 +++++++++++++++ defects/higan/scan | 29 +++++ 7 files changed, 333 insertions(+) create mode 100644 defects/dosbox-x-0003/patch/dosbox-x-0003-jtbs-dbox-vector-find-in-nested-loop.patch create mode 100644 defects/dosbox-x-0003/test/DosboxXJtbsDboxTest.class create mode 100644 defects/dosbox-x-0003/test/DosboxXJtbsDboxTest.java create mode 100644 defects/dosbox-x-0004/patch/dosbox-x-0004-bdlist-list-find-per-char.patch create mode 100644 defects/dosbox-x-0004/test/DosboxXBdlistTest.class create mode 100644 defects/dosbox-x-0004/test/DosboxXBdlistTest.java create mode 100644 defects/higan/scan diff --git a/defects/dosbox-x-0003/patch/dosbox-x-0003-jtbs-dbox-vector-find-in-nested-loop.patch b/defects/dosbox-x-0003/patch/dosbox-x-0003-jtbs-dbox-vector-find-in-nested-loop.patch new file mode 100644 index 000000000..2b7186393 --- /dev/null +++ b/defects/dosbox-x-0003/patch/dosbox-x-0003-jtbs-dbox-vector-find-in-nested-loop.patch @@ -0,0 +1,45 @@ +--- a/src/hardware/vga_draw.cpp ++++ b/src/hardware/vga_draw.cpp +@@ -2585 +2585 @@ +-std::vector> jtbs = {}, dbox = {}; ++std::unordered_set jtbs_set, dbox_set; ++static inline uint32_t encode_pos(int row, int col) { return ((uint32_t)(uint16_t)row << 16) | (uint32_t)(uint16_t)col; } +@@ -2622,2 +2622,2 @@ +- if (!jtbs.empty()) jtbs.erase(std::remove_if(jtbs.begin(), jtbs.end(), first_equal(row)), jtbs.end()); +- if (!dbox.empty()) dbox.erase(std::remove_if(dbox.begin(), dbox.end(), first_equal(row)), dbox.end()); ++ for (auto it = jtbs_set.begin(); it != jtbs_set.end(); ) { if ((int)(*it >> 16) == row) it = jtbs_set.erase(it); else ++it; } ++ for (auto it = dbox_set.begin(); it != dbox_set.end(); ) { if ((int)(*it >> 16) == row) it = dbox_set.erase(it); else ++it; } +@@ -2686 +2686 @@ +- if (line == 1) dbox.push_back(std::make_pair(row, col)); ++ if (line == 1) dbox_set.insert(encode_pos(row, col)); +@@ -2722 +2722 @@ +- if (line == 1) jtbs.push_back(std::make_pair(row, col)); ++ if (line == 1) jtbs_set.insert(encode_pos(row, col)); +--- a/src/ints/mouse.cpp ++++ b/src/ints/mouse.cpp +@@ -995 +995 @@ +-extern std::vector> jtbs, dbox; ++extern std::unordered_set jtbs_set, dbox_set; ++static inline bool jtbs_has(int row, int col) { return jtbs_set.count(((uint32_t)(uint16_t)row << 16) | (uint32_t)(uint16_t)col) != 0; } ++static inline bool dbox_has(int row, int col) { return dbox_set.count(((uint32_t)(uint16_t)row << 16) | (uint32_t)(uint16_t)col) != 0; } +@@ -1116 +1116 @@ +- && showdbcs) ? std::find(jtbs.begin(), jtbs.end(), std::make_pair(i,j)) != jtbs.end():false; ++ && showdbcs) ? jtbs_has(i,j) : false; +@@ -1130 +1130 @@ +- && showdbcs && std::find(dbox.begin(), dbox.end(), std::make_pair(i,j)) != dbox.end()) bdlist.push_back(len); ++ && showdbcs && dbox_has(i,j)) bdlist.push_back(len); +@@ -1138 +1138 @@ +- && showdbcs))&&j==c2&&c2>) with +# std::find inside nested loops over screen rows (i) and columns (j). +# For a full 80x25 DBCS screen, jtbs can hold ~2000 entries. Each of the +# ~2000 (row, col) positions calls std::find → O(rows * cols * N_entries) +# = O(N²) where N = number of DBCS characters on screen. +# Fix: replace vector> with unordered_set using +# (row<<16)|col encoding. Membership test drops from O(N) to O(1). +# The row-based erase loop replaces erase(remove_if) — same O(N) cost, +# called only on scanline invalidation, not per text-selection character. +# Severity: MEDIUM — triggered during mouse text selection/copy in DBCS +# (Japanese/Chinese/Korean) codepage mode; scales with screen DBCS content. diff --git a/defects/dosbox-x-0003/test/DosboxXJtbsDboxTest.class b/defects/dosbox-x-0003/test/DosboxXJtbsDboxTest.class new file mode 100644 index 0000000000000000000000000000000000000000..adbbe913593d1e159517c5c5f6cac022bca36e9c GIT binary patch literal 4050 zcmaJ^>sJ)#8UMZPWtL@RVcA*11sqcmR#CjJhD8)XBZxq=L;~9Ausg!&GDBu&DJE@9 zQ%qWWZ__5)rnZ{YHZ@IR&r#IgKKArO&*?cm>B*O#p7flye}l)AKJV;pK<$2*dFOqf z>+iX|&;IGZcYg<<2OkDe0gnK$2p{|mHD{DLr6a58Qys&TXVkRK;P29O&F*IKw8f7J z2r$$+L^)f_cJ!F0a&Azw>>vbqs!#<{z$y{d2r*Qb={N~VP%;??scmqjmSlW_^VW(8 zqmCh<*{Z47hRG0Zi|45wb^V3?WECVt1*{fPj|PTtSyPdOL6}yoA$6YXZR7GK#6&cr ziNUKo+mWJdm%m8?YZ)qz4+OCePYC#oi1lb8pZg4J(wKjCz@D`FXfdjChpOiYBE#%8 zbX(DMtL&R&Eq6NE-yfgw082{(+8Aofh8|X|>6A*qwZg-dZ4%Lr%?y>Qo;EV-h(Y3f zZN%OmLY0gq@M5Z z5%DCCS0G*RgPW5S(S^MXp~;+<%^Y+5%ttZ8jdBIWag(#`6R{sX45GtQ@QR-a}mlUbEAl1&I0W~eWxWlxdnplKRrm}=#-BA!NyY@1Qesub|F zVvlL|G_5RK(QOJAAz#GBfj(uMnm$FY4&fPIUdPJm;ZC^tu}VCPaRCz|j^o6#8cdzD zY;}eb*vL_~NqNvUqho|5+azCAW|CnrJjabY$${e@yUMF%gMvu`X%QKy#R6U~hjM6^ zOU>#l%q)nA9}AM1QzE9JQL5;QZj5R(B#eq{Ab_(J$EQ^*m$l^?&6-i{^mG7Z`G!4P z=eO^Y`wsM`WSgo^w$v=;65%hmwPa*doweefEqV|N3=y+eCyHOO(IPP*T z-=UHPf0w(H41hwiUY7F}+BK_aX1AQxbX75BE3N3Aa!Y$g)-A3(hdBYC=jAnD>{?EQ z1`U0R=kI_UQoc#jUtnmF<(zJq8P!xXCoR>MhugNr<*Z?x&CQm@2k-@IP_k^zs%pkL z*s|^PJXiY?&I@>%tNqflsP`E;%1a)GKDXlhcGAB;K@jD&iW%Bg z2&D?|a%DrI7bdxqO>RMtrOUSma&d;5H?LoJa+T6vz-7vTM;uWVbfqbyoXatoGIC~G zJ*07H>K}1gY~heom=73+ZP}(WdsMZjjf@q*PZ-wr8m38Qp{tf9pH@gnrjuvxUPkp| z?_~j8Wq86(G<>8}X7V&OOtn!JEIQXr9!METBQ4V8M&<&^NHd zlkmk7e#zf>4XsTHK@ueIHLTt!`6Xcy@+J5ed~87=F#faPr{~9#udF_gNehYEKq^}a zjW%+!6&vY$D>gZ%gyH*?MqWZBz#@c48Isq@02;+8a6DLC3Xl7$HQ@~})zr+#==CyJ z<&N)GVxifm;KO-h;qgt}Lo3BMUWjj7F}@A$O_FyJ8yB(V4t5qp`)?<-A3qe@og{k~ zg*HK<-A#@pu!X*NqH|ejcNIb#EBo<%Vx(EAVAPijz$RV5c9ei!TL3n1)9wn2c?sZu zH~|0UVSu|yiJ_y<%vZ{W>5z!iU{1gV6B z1@^`o7tuMypWU6_&7MYY#uM{q7I7fqqr@wH9!mIcV&F1jl27tqLF68W#`*FtMu6KG z{rydhNxoR)U3?C>g;O;n38AJT5uh{*kb=(2{Gz)v=t)#bRsO4pH&lA00I8D%N8eaO z(EWXAY@v$M>~FRBW1?yir+15d1}?&5y!=dR4vtA!7A=z_zd?g8oC&bTr{4Hl9S=A_qV7R z_ef;ameeiiV|>%hZ$?S0UD`ZX*o?S0U(9dTM;GzM0wssFJ{rL*Z@h&t*IFOFOFgRy zYxp(7lQ+10|9OSV8(fRvsW?8 z-llo+223a2UPI*)DWv9di7{$*0(LQKy-QT?^vB1wS2_jmW785h``vbM%b+_=<%MiTR;r09)17-Y*7X&XEA1cKCG*aV7 j4!m#S$MlZS_d6u#iu3%b^Sp*%(n*kZZ{Sz>01f{G+o8f3 literal 0 HcmV?d00001 diff --git a/defects/dosbox-x-0003/test/DosboxXJtbsDboxTest.java b/defects/dosbox-x-0003/test/DosboxXJtbsDboxTest.java new file mode 100644 index 000000000..0937b4a05 --- /dev/null +++ b/defects/dosbox-x-0003/test/DosboxXJtbsDboxTest.java @@ -0,0 +1,115 @@ +import java.util.*; + +/** + * Unit test for dosbox-x-0003: jtbs/dbox vector> O(N^2) membership + * in Mouse_GetSelected nested row/col loops. + * + * Models the DOSBox-X defect where std::find is called on a vector of (row,col) + * pairs inside nested loops over all screen positions. For a full DBCS screen + * (80 cols x 25 rows), jtbs can hold ~2000 entries; each position calls find + * making the total O(rows * cols * N_entries) = O(N^2). + * + * The fix encodes (row,col) as a single int key and stores in a HashSet, + * making each membership test O(1). + */ +public class DosboxXJtbsDboxTest { + + // --- Defect: vector> with linear find --- + static List buildVectorList(int screenCols, int screenRows) { + List list = new ArrayList<>(); + // Simulate: every other column is a double-byte char (DBCS), producing (row,col) pairs + for (int row = 0; row < screenRows; row++) { + for (int col = 0; col < screenCols; col += 2) { + list.add(new long[]{row, col}); + } + } + return list; + } + + static boolean vectorContains(List list, int row, int col) { + for (long[] pair : list) { + if (pair[0] == row && pair[1] == col) return true; + } + return false; + } + + static int countHitsVector(List jtbs, int r1, int r2, int c1, int c2) { + int hits = 0; + for (int i = r1; i <= r2; i++) { + for (int j = c1; j <= c2; j++) { + if (vectorContains(jtbs, i, j)) hits++; + } + } + return hits; + } + + // --- Fix: unordered_set encoded as (row<<16)|col --- + static int encodePos(int row, int col) { + return ((row & 0xFFFF) << 16) | (col & 0xFFFF); + } + + static Set buildHashSet(int screenCols, int screenRows) { + Set set = new HashSet<>(); + for (int row = 0; row < screenRows; row++) { + for (int col = 0; col < screenCols; col += 2) { + set.add(encodePos(row, col)); + } + } + return set; + } + + static int countHitsHashSet(Set jtbs, int r1, int r2, int c1, int c2) { + int hits = 0; + for (int i = r1; i <= r2; i++) { + for (int j = c1; j <= c2; j++) { + if (jtbs.contains(encodePos(i, j))) hits++; + } + } + return hits; + } + + public static void main(String[] args) { + // Small correctness test + int smallCols = 10, smallRows = 5; + List vec = buildVectorList(smallCols, smallRows); + Set set = buildHashSet(smallCols, smallRows); + + int vecHits = countHitsVector(vec, 0, smallRows - 1, 0, smallCols - 1); + int setHits = countHitsHashSet(set, 0, smallRows - 1, 0, smallCols - 1); + assert vecHits == setHits : "Correctness failed: vec=" + vecHits + " set=" + setHits; + System.out.println("Correctness OK: " + vecHits + " hits in " + smallCols + "x" + smallRows + " grid"); + + // Performance test: 80x25 full DBCS screen + int cols = 80, rows = 25; + List bigVec = buildVectorList(cols, rows); + Set bigSet = buildHashSet(cols, rows); + + // Warmup + for (int i = 0; i < 3; i++) { + countHitsVector(bigVec, 0, rows - 1, 0, cols - 1); + countHitsHashSet(bigSet, 0, rows - 1, 0, cols - 1); + } + + int reps = 200; + long t0 = System.nanoTime(); + int totalVec = 0; + for (int i = 0; i < reps; i++) totalVec += countHitsVector(bigVec, 0, rows - 1, 0, cols - 1); + long vecNs = System.nanoTime() - t0; + + long t1 = System.nanoTime(); + int totalSet = 0; + for (int i = 0; i < reps; i++) totalSet += countHitsHashSet(bigSet, 0, rows - 1, 0, cols - 1); + long setNs = System.nanoTime() - t1; + + assert totalVec == totalSet : "Result mismatch"; + + double ratio = (double) vecNs / setNs; + System.out.printf("80x25 DBCS text selection (%d reps):%n", reps); + System.out.printf(" vector linear scan: %,d ns%n", vecNs); + System.out.printf(" unordered_set O(1) lookup: %,d ns%n", setNs); + System.out.printf(" speedup: %.1fx%n", ratio); + + assert ratio > 3.0 : "Expected >3x speedup, got " + ratio; + System.out.println("PASS"); + } +} diff --git a/defects/dosbox-x-0004/patch/dosbox-x-0004-bdlist-list-find-per-char.patch b/defects/dosbox-x-0004/patch/dosbox-x-0004-bdlist-list-find-per-char.patch new file mode 100644 index 000000000..cdb92265e --- /dev/null +++ b/defects/dosbox-x-0004/patch/dosbox-x-0004-bdlist-list-find-per-char.patch @@ -0,0 +1,43 @@ +--- a/src/dos/drive_local.cpp ++++ b/src/dos/drive_local.cpp +@@ -278 +278 @@ +-std::list bdlist = {}; ++std::unordered_set bdlist; +@@ -290 +290 @@ +- ) && (std::find(bdlist.begin(), bdlist.end(), (uint16_t)(baselen + s - ss)) != bdlist.end() || (isKanji1(*s) && (!(*(s+1)) || !isKanji2(*(s+1)))))) { ++ ) && (bdlist.count((uint16_t)(baselen + s - ss)) || (isKanji1(*s) && (!(*(s+1)) || !isKanji2(*(s+1)))))) { +@@ -342 +342 @@ +- ) && (std::find(bdlist.begin(), bdlist.end(), (uint16_t)(baselen + s - ss)) != bdlist.end() || (isKanji1(*s) && (!(*(s+1)) || !isKanji2(*(s+1))))) && utf8_encode(&d,df,(uint32_t)cp437_to_unicode[(uint8_t)*s]) >= 0) { ++ ) && (bdlist.count((uint16_t)(baselen + s - ss)) || (isKanji1(*s) && (!(*(s+1)) || !isKanji2(*(s+1))))) && utf8_encode(&d,df,(uint32_t)cp437_to_unicode[(uint8_t)*s]) >= 0) { +--- a/src/ints/mouse.cpp ++++ b/src/ints/mouse.cpp +@@ -993 +993 @@ +-extern std::list bdlist; ++extern std::unordered_set bdlist; +@@ -1044 +1044 @@ +- if (curAC[rtl?ttf.cols-x-1:x].boxdraw||(!x&&curAC[rtl?ttf.cols-x:x+1].boxdraw)) bdlist.push_back(len); ++ if (curAC[rtl?ttf.cols-x-1:x].boxdraw||(!x&&curAC[rtl?ttf.cols-x:x+1].boxdraw)) bdlist.insert(len); +@@ -1123 +1123 @@ +- if (curAC[rtl?ttfcols-j-1:j].boxdraw||(!j&&curAC[rtl?ttf.cols-j:j+1].boxdraw)) bdlist.push_back(len); ++ if (curAC[rtl?ttfcols-j-1:j].boxdraw||(!j&&curAC[rtl?ttf.cols-j:j+1].boxdraw)) bdlist.insert(len); +@@ -1146 +1146 @@ +- while (len>0&&text[len-1]==32) {text[--len]=0;bdlist.remove(len);} ++ while (len>0&&text[len-1]==32) {text[--len]=0;bdlist.erase(len);} +--- a/src/misc/clipboard.cpp ++++ b/src/misc/clipboard.cpp +@@ -721 +721 @@ +-extern std::list bdlist; ++extern std::unordered_set bdlist; +# +# CWE-407: String_DBCS_TO_HOST_UTF16 and String_DBCS_TO_HOST_UTF8 in +# drive_local.cpp call std::find(bdlist.begin(), bdlist.end(), pos) for +# every character in the input string while converting DBCS filenames to +# host encoding. bdlist is a std::list tracking box-draw character +# byte positions; std::find is O(N) per character, making each conversion +# O(string_length * bdlist_size) = O(N²) when both grow together. +# CROSS_LEN = 512 so worst case: 512 * 512 = 262,144 comparisons per filename. +# This path is hot during file open, FindFirst, GetFileAttr, etc. in DBCS TTF mode. +# Fix: replace std::list with std::unordered_set; membership +# test drops from O(N) to O(1). push_back → insert; remove(val) → erase(val). +# Severity: MEDIUM — triggered on every file I/O in DBCS TTF mode; O(N²) cost +# per file access scales with number of box-draw characters in filenames/paths. diff --git a/defects/dosbox-x-0004/test/DosboxXBdlistTest.class b/defects/dosbox-x-0004/test/DosboxXBdlistTest.class new file mode 100644 index 0000000000000000000000000000000000000000..318381dc79b7cb7d85337fa6d2acc503b47691da GIT binary patch literal 3610 zcmaJ^TW}j!8UBv6)=Kg^ie$@nlQ`K>yR~e`CN#B)Wt;dGqQsXpa_u@!Yu3_Pu(Im@zI4QpB*|&}}WFTMSi=B{m6@3k-erMNXtB@(f58Ali@H(4B%&^(C8MC3b)K67N8(5yMq zBZzi*MXWm&#D$t(Eu9vccM6yFDEJJHF@#9hMQ+*?8GBAhWQY>ygAR&0Zthdij{(Y< zl0A;WRa>{vTs)Qt5X*4|pT!B%yd zP&k@MC5Go#7m^w8i)cSE{GopFkUhAP*|Yur*BX>9VG z73*x!MebZsFfWQuvNF%|AkJYyfewQ*)6tt0=qmNCtM=3m?%vUfete#xSye4N-PtLs zv@1{EINUyM_o&&7&NY*)(9@l2LrYrKtp+^^3$~2BsMN(`?{Z=`WazWx8I8aJon2!67RZ%9>FG7v_Ya(Zsw?SRc`SEpf_UYbaGKd%PO&OOIyd-*T zXtPNAvs6r!_i1ir$eN9sN!ps7r-i^qkg3EH)SimP;&e?fND1QTH`1hVXo$Q#l3$qN z=9s8TLX()({IHgDzXQq4tWJVWT8ll{IlD>%U2Kj$qMb4{ZS4u7x9p6Lk&3acU}_-$ z8!IhP8y96?nq?dHBI7D$!469@e#GFJA~JthigL?24ueS}Z>IQhsuYsA?dWI~PNzWk z8HR1yrj{G#_MDNn{8(X7`wi2i(^cn|rJmPF1m&s7;cliVo zoSHL@bUwvZ=GLao(`sr?GfDD$40{KcawM4MdLCaYW?_puYuG9a`S5-aKgG{v{9I(x zHHP}l)X33~k?IyarX=9Y|KF*IzmX(v@>e88~(|3j%& z73{)6I*>e6g&x%6WqgZ@;M>mEE4ypODMe!+&CwN#p5LLbI(jpDmo?o4`+x=v-*rJi zh?*l^I7pa)PSabWcdY3aWQO6ETc}{Tf;}{-WcWR5Zs4Jwy1j3sOuDwZ{&(*+0(%^B z5ocqR*s4)amykzEyH{ydf%hI{8N2q~CFJ)U{AD7gQ`;rwF6qPGZf{Rr(?=`V(_FWP zx+@4Qd)O*`^j!9`)%Abu+oanMnMCcQt#uIhfCvr{;X%4u)zP!wrIzkPVyx10m)udH z{ytID;qOvkaH$(Ns1FG0hi~Cg51IV{|KGOp$7p{O@i&vfEyUakmA)UL>rlb;0)K4@ z{|^wrYk0j78qX?O5v72vhCTRAaHu(qch+#|4j!AliN}ZUpq<{I9`UwIQHc;MIM@=E z9QBTRFmcEochPuR@~p1^wYg;t-R)BIp*8fjmtDu;RqTyQ(XzKtbq9$_F}#gaz)g() z?mEsyrQ3K4xPhr~<%{h;Z@V0ocL!Gxb>vJqVkVYl^87#7pmoTBj)1qlJX#+0t)Qwp z;Gka6Is(h(1o;<1s%73$E}#{Gj*z!K91RzsAx9KJL(AbJw1Rhp9Sx(^f$GrZE2t1E zM!3+ey8Kdg_>yt7l9Y_h`hE+~q?ha`%afB5rDBE^#${2Tn7e`Ogh1vb2U$%jm>ZK{ za3{m1lNSqx#y@lmvNG+^%O2+2v=$RHb(I-PUC$% zfj{63{zT|M<4OF5?sEUe1pb3bwhL3N2Ip7^`a(yNlY75tJkv)T~qCRBQn=CW)W3spcLI zNcZrHmx|_%jec^(=_Wupvi}CYaTT((3d#E+1~=U!T94>0q6*63QI63a6mL4-dy8xa ds_1)_K<_xuA3M)?@e5iB((EtsYh1@e{{?J)gHr$i literal 0 HcmV?d00001 diff --git a/defects/dosbox-x-0004/test/DosboxXBdlistTest.java b/defects/dosbox-x-0004/test/DosboxXBdlistTest.java new file mode 100644 index 000000000..6393b3bbb --- /dev/null +++ b/defects/dosbox-x-0004/test/DosboxXBdlistTest.java @@ -0,0 +1,101 @@ +import java.util.*; + +/** + * Unit test for dosbox-x-0004: bdlist std::list O(N^2) membership + * in String_DBCS_TO_HOST_UTF16/UTF8 per-character conversion loop. + * + * Models DOSBox-X's DBCS filename conversion where std::find(bdlist) is called + * for every character in the input string. bdlist tracks byte positions of + * box-draw characters; with CROSS_LEN=512, worst case is 512*512=262,144 + * comparisons per filename conversion. + * + * The fix replaces std::list with std::unordered_set, + * making each membership test O(1). + */ +public class DosboxXBdlistTest { + + static final int CROSS_LEN = 512; + + // --- Defect: std::list with std::find per character --- + static boolean listContains(List bdlist, int pos) { + for (int v : bdlist) { + if (v == pos) return true; + } + return false; + } + + static int convertWithList(int[] stringBytes, List bdlist) { + int converted = 0; + for (int i = 0; i < stringBytes.length; i++) { + if (listContains(bdlist, i)) { + converted++; // box-draw path + } else { + converted++; // normal DBCS path + } + } + return converted; + } + + // --- Fix: std::unordered_set with O(1) count --- + static int convertWithSet(int[] stringBytes, Set bdset) { + int converted = 0; + for (int i = 0; i < stringBytes.length; i++) { + if (bdset.contains(i)) { + converted++; // box-draw path + } else { + converted++; // normal DBCS path + } + } + return converted; + } + + public static void main(String[] args) { + // Build a worst-case string: CROSS_LEN chars, half are box-draw positions + int[] str = new int[CROSS_LEN]; + Arrays.fill(str, 0xA1); // Kanji-range bytes + + List bdlist = new ArrayList<>(); + Set bdset = new HashSet<>(); + for (int i = 0; i < CROSS_LEN; i += 2) { + bdlist.add(i); + bdset.add(i); + } + + // Correctness check + int listResult = convertWithList(str, bdlist); + int setResult = convertWithSet(str, bdset); + assert listResult == setResult : "Correctness failed: list=" + listResult + " set=" + setResult; + System.out.println("Correctness OK: both produce " + listResult + " converted chars"); + + // Performance benchmark + int reps = 5000; + + // Warmup + for (int i = 0; i < 10; i++) { + convertWithList(str, bdlist); + convertWithSet(str, bdset); + } + + long t0 = System.nanoTime(); + long totalList = 0; + for (int i = 0; i < reps; i++) totalList += convertWithList(str, bdlist); + long listNs = System.nanoTime() - t0; + + long t1 = System.nanoTime(); + long totalSet = 0; + for (int i = 0; i < reps; i++) totalSet += convertWithSet(str, bdset); + long setNs = System.nanoTime() - t1; + + assert totalList == totalSet : "Result mismatch"; + + double ratio = (double) listNs / setNs; + System.out.printf("DBCS filename conversion CROSS_LEN=%d, bdlist=%d entries (%d reps):%n", + CROSS_LEN, bdlist.size(), reps); + System.out.printf(" std::list linear find: %,d ns%n", listNs); + System.out.printf(" unordered_set O(1): %,d ns%n", setNs); + System.out.printf(" speedup: %.1fx%n", ratio); + + assert ratio > 5.0 : "Expected >5x speedup, got " + ratio; + System.out.println("PASS"); + } +} diff --git a/defects/higan/scan b/defects/higan/scan new file mode 100644 index 000000000..171089402 --- /dev/null +++ b/defects/higan/scan @@ -0,0 +1,29 @@ +CLEAN + +MOAD-0001 (CWE-407): CLEAN + - Emulation cores (SFC/SNES, FC/NES, GBA, WonderSwan, NGP, MD, PCE, MS) + use fixed-size arrays indexed by sprite/object number for per-scanline + and per-pixel paths. No list membership checks in hot render loops. + - nall vector::find (linear O(N)) is called only in setup/attach paths + (Screen::attach, Scheduler::append, node::remove) — bounded by small + constant counts (sprites ~8, scheduler threads ~4). Not O(N^2). + - icarus ROM database lookup: O(N_games) linear scan per ROM import — not + O(N^2), just a single pass through 1162 SNES entries or ~500 GBA entries. + - GBA heuristic scan: 7 identifiers x ROM_bytes x list.find (max 7 entries) + = O(7 * D * 7). list stays bounded to num identifiers. Not O(N^2). + +MOAD-0002 (Intertangle): CLEAN + - higan uses global CPU/PPU/APU/Bus objects by design for emulation accuracy. + This is intentional coupling, not an accidental god-object defect. + +MOAD-0003 (Leaked Context): CLEAN + - No thread_local or TLS usage found. higan uses cooperative coroutines + (libco) for emulator threads — no OS thread-local context leakage. + +MOAD-0004 (CWE-312): CLEAN + - No credentials, tokens, passwords, or secrets are logged or persisted. + higan handles only ROM data and save states — no network authentication. + +MOAD-0005 (Thundering Herd): CLEAN + - Single-threaded cooperative scheduler. No concurrent cache access patterns. + All state mutation is deterministic and synchronous within scheduler turns.