vscode+zed: CWE-407 scan — 6 defects across 2 code editors

VS Code (4 defects):
- vscode-0001: extensionGalleryService uuid dedup O(N×M) MEDIUM 100x
- vscode-0002: abstractExtensionManagementService deps O(D²) MEDIUM 49x
- vscode-0003: userDataSync merge compare() O(N×M) across 8 files MEDIUM 250x
- vscode-0004: configurationModels override identifiers O(N×M) LOW-MEDIUM 18x

Zed (2 defects):
- zed-0001: lsp_store LSP edit dedup Vec::contains O(E²) MEDIUM 49x
- zed-0002: extension_builder manifest dedup Vec::contains O(N²) LOW 24x

6/6 unit tests PASS
This commit is contained in:
russell@unturf.com 2026-03-30 11:43:31 -04:00
parent d05d4bd6f0
commit 05e4902a9e
8 changed files with 608 additions and 0 deletions

View file

@ -0,0 +1,65 @@
# UNDF: (leave blank)
# zed-0001: lsp_store LSP edit dedup Vec::contains O(E²)
#
# File: crates/project/src/lsp_store.rs
# Functions: apply_code_actions_as_format (line ~2010) and
# apply_code_action (line ~3356)
#
# Defect: When accumulating unique edits from LSP code actions, each new
# edit is checked against the `lsp_edits`/`edits` Vec via
# Vec::contains() — O(E²) where E = number of edits returned by
# the language server. Both call sites use the same pattern:
#
# let mut lsp_edits = Vec::new();
# for edit in op.edits {
# if !lsp_edits.contains(&edit) { lsp_edits.push(edit); }
# }
#
# Fix: Use a HashSet as a parallel seen-set for O(1) dedup, keeping Vec
# for ordered output.
# Severity: MEDIUM (E = edits per code action; large refactors can produce
# hundreds of edits — e.g., rename across file)
# Speedup: 50x at E=100
#
--- a/crates/project/src/lsp_store.rs
+++ b/crates/project/src/lsp_store.rs
@@ -2010,10 +2010,12 @@ (apply_code_actions_as_format, first site)
let mut lsp_edits = Vec::new();
+ let mut seen_edits = HashSet::new();
for edit in op.edits {
match edit {
Edit::Plain(edit) => {
- if !lsp_edits.contains(&edit) {
+ if seen_edits.insert(edit.clone()) {
lsp_edits.push(edit);
}
}
Edit::Annotated(edit) => {
- if !lsp_edits.contains(&edit.text_edit) {
+ if seen_edits.insert(edit.text_edit.clone()) {
lsp_edits.push(edit.text_edit);
}
}
@@ -3356,10 +3358,12 @@ (apply_code_action, second site)
let (mut edits, mut snippet_edits) = (vec![], vec![]);
+ let mut seen_edits = HashSet::new();
for edit in op.edits {
match edit {
Edit::Plain(edit) => {
- if !edits.contains(&edit) {
+ if seen_edits.insert(edit.clone()) {
edits.push(edit)
}
}
Edit::Annotated(edit) => {
- if !edits.contains(&edit.text_edit) {
+ if seen_edits.insert(edit.text_edit.clone()) {
edits.push(edit.text_edit)
}
}
@@ -3383,1 +3385,1 @@
- if !edits.contains(&new_edit) {
+ if seen_edits.insert(new_edit.clone()) {
edits.push(new_edit);
}

View file

@ -0,0 +1,55 @@
# UNDF: (leave blank)
# zed-0002: extension_builder manifest entry dedup Vec::contains O(N²)
#
# File: crates/extension/src/extension_builder.rs
# Function: compile_extension_manifest (lines 585-632)
#
# Defect: Three while-loops accumulate entries into manifest.languages,
# manifest.themes, and manifest.icon_themes (all Vec), each
# checking Vec::contains before push — O(N²) per category where
# N = number of language dirs / theme files / icon theme files.
#
# Fix: Use a HashSet<PathBuf> as a seen-set for each category.
# Severity: LOW (N = extension assets, typically 1-20; only at build time)
# Speedup: 20x at N=20
#
--- a/crates/extension/src/extension_builder.rs
+++ b/crates/extension/src/extension_builder.rs
@@ -580,6 +580,7 @@
+ let existing_languages: HashSet<_> = manifest.languages.iter().cloned().collect();
while let Some(language_dir) = language_dir_entries.next().await {
let language_dir = language_dir?;
let config_path = language_dir.join(LanguageConfig::FILE_NAME);
if fs.is_file(config_path.as_path()).await {
let relative_language_dir =
language_dir.strip_prefix(extension_path)?.to_path_buf();
- if !manifest.languages.contains(&relative_language_dir) {
+ if !existing_languages.contains(&relative_language_dir) {
manifest.languages.push(relative_language_dir);
}
}
@@ -598,6 +600,7 @@
+ let existing_themes: HashSet<_> = manifest.themes.iter().cloned().collect();
while let Some(theme_path) = theme_dir_entries.next().await {
let theme_path = theme_path?;
if theme_path.extension() == Some("json".as_ref()) {
let relative_theme_path = theme_path.strip_prefix(extension_path)?.to_path_buf();
- if !manifest.themes.contains(&relative_theme_path) {
+ if !existing_themes.contains(&relative_theme_path) {
manifest.themes.push(relative_theme_path);
}
}
@@ -616,6 +620,7 @@
+ let existing_icon_themes: HashSet<_> = manifest.icon_themes.iter().cloned().collect();
while let Some(icon_theme_path) = icon_theme_dir_entries.next().await {
let icon_theme_path = icon_theme_path?;
if icon_theme_path.extension() == Some("json".as_ref()) {
let relative_icon_theme_path =
icon_theme_path.strip_prefix(extension_path)?.to_path_buf();
- if !manifest.icon_themes.contains(&relative_icon_theme_path) {
+ if !existing_icon_themes.contains(&relative_icon_theme_path) {
manifest.icon_themes.push(relative_icon_theme_path);
}
}

View file

@ -0,0 +1,115 @@
import java.util.*;
/**
* CWE-407 unit tests for Zed editor defects.
*
* zed-0001: lsp_store LSP edit dedup Vec::contains O(E²)
* zed-0002: extension_builder manifest entry dedup Vec::contains O(N²)
*/
public class ZedTest {
// ---------------------------------------------------------------
// zed-0001: LSP edit dedup Vec::contains O(E²)
// ---------------------------------------------------------------
/** BEFORE: Vec::contains for each new edit — O(E²) */
static int lspEditDedupBefore(List<String> edits) {
int ops = 0;
List<String> lspEdits = new ArrayList<>();
for (String edit : edits) {
// !lsp_edits.contains(&edit)
boolean found = false;
for (String existing : lspEdits) {
ops++;
if (existing.equals(edit)) { found = true; break; }
}
if (!found) lspEdits.add(edit);
}
return ops;
}
/** AFTER: HashSet seen-set for O(1) dedup — O(E) */
static int lspEditDedupAfter(List<String> edits) {
int ops = 0;
Set<String> seen = new HashSet<>();
List<String> lspEdits = new ArrayList<>();
for (String edit : edits) {
ops++; // set lookup
if (seen.add(edit)) {
lspEdits.add(edit);
}
}
return ops;
}
// ---------------------------------------------------------------
// zed-0002: extension manifest dedup Vec::contains O(N²)
// ---------------------------------------------------------------
/** BEFORE: manifest.languages.contains for each dir — O(N²) */
static int manifestDedupBefore(List<String> entries) {
int ops = 0;
List<String> manifest = new ArrayList<>();
for (String entry : entries) {
boolean found = false;
for (String existing : manifest) {
ops++;
if (existing.equals(entry)) { found = true; break; }
}
if (!found) manifest.add(entry);
}
return ops;
}
/** AFTER: HashSet seen-set — O(N) */
static int manifestDedupAfter(List<String> entries) {
int ops = 0;
Set<String> existing = new HashSet<>();
List<String> manifest = new ArrayList<>();
for (String entry : entries) {
ops++;
if (existing.add(entry)) {
manifest.add(entry);
}
}
return ops;
}
// ---------------------------------------------------------------
// Main: run all tests
// ---------------------------------------------------------------
public static void main(String[] args) {
int pass = 0, fail = 0;
// --- zed-0001: LSP edit dedup ---
{
int E = 100; // edits from large refactor
List<String> edits = new ArrayList<>();
for (int i = 0; i < E; i++) edits.add("edit-" + i); // all unique = worst case
int before = lspEditDedupBefore(edits);
int after = lspEditDedupAfter(edits);
double ratio = (double) before / after;
boolean ok = ratio > 10;
System.out.printf("zed-0001 lsp-edit-dedup E=%d before=%d after=%d ratio=%.1fx %s%n",
E, before, after, ratio, ok ? "PASS" : "FAIL");
if (ok) pass++; else fail++;
}
// --- zed-0002: manifest dedup ---
{
int N = 50; // language dirs in a large extension
List<String> entries = new ArrayList<>();
for (int i = 0; i < N; i++) entries.add("languages/lang-" + i);
int before = manifestDedupBefore(entries);
int after = manifestDedupAfter(entries);
double ratio = (double) before / after;
boolean ok = ratio > 10;
System.out.printf("zed-0002 manifest-dedup N=%d before=%d after=%d ratio=%.1fx %s%n",
N, before, after, ratio, ok ? "PASS" : "FAIL");
if (ok) pass++; else fail++;
}
System.out.printf("%n%d/%d PASS%n", pass, pass + fail);
if (fail > 0) System.exit(1);
}
}