Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions crates/socket-patch-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ layout: `npm`, `pnpm` (isolated `.pnpm` store with symlinks), `yarn-classic`,
`bun-isolated` (the same lockfile, Bun 1.3's isolated `.bun` store),
`vlt` (`.vlt` store), `pip` (hash-pinned `requirements.txt`), `uv`, `pylock`
(PEP 751), `poetry`, `pipenv`, `pdm`, `bundler`, `composer`, `cargo`,
`golang`, `nuget` and `maven`. Deno has no hosted rewrite and is not
benchmarked separately.
`golang`, `nuget`, `maven` and `gradle` (`gradle.lockfile`, Gradle's
`modules-2/files-2.1` cache; hosted mode wires the build through
`.socket/gradle/`). Deno has no hosted rewrite and is not benchmarked
separately.

Sizes are a large-but-ordinary project for the ecosystem (3000 npm-family
packages, 1500 for vlt, 400-1200 for the others, so every scan takes about
Expand Down Expand Up @@ -75,8 +77,9 @@ unexpected). A rescan's first, preparing scan is untimed.

The environment is rebuilt from nothing for every run (`env -i`): `HOME`,
`XDG_*` and `TMPDIR` point into the fixture, so per-user caches
(`~/.cargo`, `~/go/pkg/mod`, `~/.nuget/packages`, `~/.m2`) are the
fixture's own and the runner's are never read; telemetry, the update check
(`~/.cargo`, `~/go/pkg/mod`, `~/.nuget/packages`, `~/.m2`, and
`GRADLE_USER_HOME`, which the Gradle fixture sets) are the fixture's own
and the runner's are never read; telemetry, the update check
and the persisted Socket login are off; and every proxy variable points at a
closed port, so a request to anything but the mock fails the run instead of
timing the internet. Python fixtures carry a project `.venv` and Ruby ones
Expand Down
7 changes: 7 additions & 0 deletions crates/socket-patch-bench/src/fixtures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ pub static ALL: &[Pm] = &[
patched: 25,
build: other::build_maven,
},
Pm {
name: "gradle",
description: "Gradle (build.gradle + gradle.lockfile, ~/.gradle/caches)",
packages: 1000,
patched: 25,
build: other::build_gradle,
},
];

#[cfg(test)]
Expand Down
211 changes: 167 additions & 44 deletions crates/socket-patch-bench/src/fixtures/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,9 +712,11 @@ pub fn build_nuget(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {

// ── Maven ──────────────────────────────────────────────────────────────

pub fn build_maven(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
let arts = universe(
"maven",
/// The Maven-coordinate universe the Maven and Gradle fixtures share
/// (`group:artifact` names).
fn jvm_universe(seed: &str, size: Size) -> Vec<Pkg> {
universe(
seed,
size,
0.2,
|r, i| {
Expand All @@ -731,11 +733,27 @@ pub fn build_maven(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
format!("{g}:{a}")
},
gen::version,
);
let ga = |p: &Pkg| -> (String, String) {
let (g, a) = p.name.split_once(':').unwrap();
(g.to_string(), a.to_string())
};
)
}

fn ga(p: &Pkg) -> (String, String) {
let (g, a) = p.name.split_once(':').unwrap();
(g.to_string(), a.to_string())
}

/// A dependency's pom, listing its own dependencies.
fn jvm_pom(arts: &[Pkg], p: &Pkg) -> String {
let (g, a) = ga(p);
let mut deps = String::new();
for &j in &p.deps {
let (dg, da) = ga(&arts[j]);
let _ = write!(deps, " <dependency>\n <groupId>{dg}</groupId>\n <artifactId>{da}</artifactId>\n <version>{}</version>\n </dependency>\n", arts[j].version);
}
format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project>\n <modelVersion>4.0.0</modelVersion>\n <groupId>{g}</groupId>\n <artifactId>{a}</artifactId>\n <version>{}</version>\n <dependencies>\n{deps} </dependencies>\n</project>\n", p.version)
}

pub fn build_maven(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
let arts = jvm_universe("maven", size);
let mut pom = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n <modelVersion>4.0.0</modelVersion>\n <groupId>dev.socket.bench</groupId>\n <artifactId>bench-app</artifactId>\n <version>1.0.0</version>\n <packaging>jar</packaging>\n\n <properties>\n <maven.compiler.release>17</maven.compiler.release>\n </properties>\n\n <dependencies>\n");
for p in arts.iter().filter(|p| p.direct) {
let (g, a) = ga(p);
Expand All @@ -754,12 +772,7 @@ pub fn build_maven(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
g.replace('.', "/"),
p.version
);
let mut deps = String::new();
for &j in &p.deps {
let (dg, da) = ga(&arts[j]);
let _ = write!(deps, " <dependency>\n <groupId>{dg}</groupId>\n <artifactId>{da}</artifactId>\n <version>{}</version>\n </dependency>\n", arts[j].version);
}
let pom = format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project>\n <modelVersion>4.0.0</modelVersion>\n <groupId>{g}</groupId>\n <artifactId>{a}</artifactId>\n <version>{}</version>\n <dependencies>\n{deps} </dependencies>\n</project>\n", p.version);
let pom = jvm_pom(&arts, p);
t.write(
&format!("{dir}/{a}-{}.pom.sha1", p.version),
gen::sha1_hex(&pom),
Expand All @@ -778,36 +791,10 @@ pub fn build_maven(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
.iter()
.filter(|p| p.patched)
.map(|p| {
let (g, a) = ga(p);
let purl = format!("pkg:maven/{g}/{a}@{}", p.version);
let (uuid, token) = grant(&purl);
let suffixed = format!("{}-socket.{}", p.version, &uuid[..8]);
let url = format!("{PATCH_HOST}/patch/maven/{g}/{a}/{}/{token}/{uuid}/{a}-{suffixed}.jar", p.version);
spec(
purl.clone(),
uuid.clone(),
&format!("package/{a}.class"),
json!({
"status": "granted",
"url": url,
"purl": purl,
"artifacts": [{ "kind": "tarball", "url": url, "integrity": {
"sha256": gen::sha256_hex(&format!("patched-jar:{}", p.name)),
"sha1": gen::sha1_hex(&format!("patched-jar:{}", p.name)),
} }],
"registryOverride": {
"kind": "maven2",
"indexUrl": format!("{PATCH_HOST}/patch-registry/maven/{token}/{uuid}/maven2"),
"identifiers": {
"name": format!("{g}/{a}"),
"version": p.version,
"mavenGroupId": g,
"mavenArtifactId": a,
"mavenSuffixedVersion": suffixed,
"mavenPomSha256": gen::sha256_hex(&format!("patched-pom:{}", p.name)),
},
},
}),
jvm_patch(
p,
|token, uuid| format!("{PATCH_HOST}/patch-registry/maven/{token}/{uuid}/maven2"),
false,
)
})
.collect();
Expand All @@ -822,3 +809,139 @@ pub fn build_maven(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
&[],
))
}

/// The mock's patch for one Maven coordinate: a suffixed-version maven2
/// registry override. `index_url` builds the override's repository URL
/// from the grant token and patch uuid.
fn jvm_patch(p: &Pkg, index_url: impl Fn(&str, &str) -> String, module_sha: bool) -> PatchSpec {
let (g, a) = ga(p);
let purl = format!("pkg:maven/{g}/{a}@{}", p.version);
let (uuid, token) = grant(&purl);
let suffixed = format!("{}-socket.{}", p.version, &uuid[..8]);
let url = format!(
"{PATCH_HOST}/patch/maven/{g}/{a}/{}/{token}/{uuid}/{a}-{suffixed}.jar",
p.version
);
let mut identifiers = json!({
"name": format!("{g}/{a}"),
"version": p.version,
"mavenGroupId": g,
"mavenArtifactId": a,
"mavenSuffixedVersion": suffixed,
"mavenPomSha256": gen::sha256_hex(&format!("patched-pom:{}", p.name)),
});
if module_sha {
identifiers["mavenModuleSha256"] =
json!(gen::sha256_hex(&format!("patched-module:{}", p.name)));
}
spec(
purl.clone(),
uuid.clone(),
&format!("package/{a}.class"),
json!({
"status": "granted",
"url": url,
"purl": purl,
"artifacts": [{ "kind": "tarball", "url": url, "integrity": {
"sha256": gen::sha256_hex(&format!("patched-jar:{}", p.name)),
"sha1": gen::sha1_hex(&format!("patched-jar:{}", p.name)),
} }],
"registryOverride": {
"kind": "maven2",
"indexUrl": index_url(&token, &uuid),
"identifiers": identifiers,
},
}),
)
}

// ── Gradle ─────────────────────────────────────────────────────────────

/// A single-project Groovy-DSL build with dependency locking, its
/// dependencies in Gradle's own cache (`modules-2/files-2.1`, jar and pom
/// in separate sha1 dirs). Hosted mode wires the build through an owned
/// settings script and index under `.socket/gradle/` and pins the
/// suffixed versions in `gradle.lockfile`.
pub fn build_gradle(t: &mut Tree, size: Size) -> std::io::Result<Fixture> {
let arts = jvm_universe("gradle", size);
t.write(
"project/settings.gradle",
"rootProject.name = 'bench-app'\n",
)?;
let mut build = String::from(
"plugins {\n id 'java'\n}\n\nrepositories {\n mavenCentral()\n}\n\ndependencyLocking {\n lockAllConfigurations()\n}\n\ndependencies {\n",
);
for p in arts.iter().filter(|p| p.direct) {
let _ = writeln!(build, " implementation '{}:{}'", p.name, p.version);
}
build.push_str("}\n");
t.write("project/build.gradle", build)?;
let mut sorted: Vec<&Pkg> = arts.iter().collect();
sorted.sort_by(|a, b| a.name.cmp(&b.name));
let mut lock = String::from(
"# This is a Gradle generated file for dependency locking.\n# Manual edits can break the build and are not advised.\n# This file is expected to be part of source control.\n",
);
for p in &sorted {
let _ = writeln!(
lock,
"{}:{}=compileClasspath,runtimeClasspath",
p.name, p.version
);
}
lock.push_str("empty=annotationProcessor,testAnnotationProcessor\n");
t.write("project/gradle.lockfile", lock)?;
t.write(
"project/gradle/wrapper/gradle-wrapper.properties",
"distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.10.2-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n",
)?;
t.write(
"project/src/main/java/App.java",
"public class App { public static void main(String[] a) {} }\n",
)?;
for p in &arts {
let (g, a) = ga(p);
let dir = format!(
"home/.gradle/caches/modules-2/files-2.1/{g}/{a}/{}",
p.version
);
let pom = jvm_pom(&arts, p);
let jar = format!("PK synthetic {}", p.name);
t.write(
&format!("{dir}/{}/{a}-{}.pom", gen::sha1_hex(&pom), p.version),
pom,
)?;
t.write(
&format!("{dir}/{}/{a}-{}.jar", gen::sha1_hex(&jar), p.version),
jar,
)?;
}
// Gradle's planner only takes https repositories; the CLI never
// fetches the index during a scan, so it need not be the mock.
let patches = arts
.iter()
.filter(|p| p.patched)
.map(|p| {
jvm_patch(
p,
|token, uuid| {
format!("https://patch.socket.dev/patch-registry/maven/{token}/{uuid}/maven2")
},
true,
)
})
.collect();
let mut f = fixture(
arts.len(),
patches,
&[
".socket/gradle/.gitattributes",
".socket/gradle/hosted-index.tsv",
".socket/gradle/socket-patch.hosted.settings.gradle",
"gradle.lockfile",
"settings.gradle",
],
&["redirect_gradle_detached_configs_unguarded"],
);
f.env_paths = vec![("GRADLE_USER_HOME", "home/.gradle")];
Ok(f)
}
9 changes: 4 additions & 5 deletions crates/socket-patch-core/src/crawlers/gradle_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ pub fn hash_eq(dir_name: &str, sha1_hex: &str) -> bool {
/// Whether `bytes` are the pristine download Gradle stored in the hash
/// directory `dir_name` (their sha1 names it).
pub fn pristine(dir_name: &str, bytes: &[u8]) -> bool {
use sha1::{Digest, Sha1};
hash_eq(dir_name, &hex::encode(Sha1::digest(bytes)))
hash_eq(dir_name, &crate::utils::digest::sha1_hex_of(bytes))
}

/// Whether `path` is a version directory of a `files-2.1` tree
Expand Down Expand Up @@ -432,8 +431,6 @@ impl DerivedIndex {
/// The [`DerivedCopies`] of the jar `jar_leaf` whose pristine bytes
/// hash to `pristine_sha1`.
pub fn query(&self, jar_leaf: &str, pristine_sha1: &str) -> DerivedCopies {
use sha1::{Digest, Sha1};

let instrumented = format!("instrumented-{jar_leaf}");
let mut out = DerivedCopies {
incomplete: self.incomplete,
Expand All @@ -460,7 +457,9 @@ impl DerivedIndex {
out.stale.push(path.clone());
} else if name == jar_leaf || name == instrumented {
match crate::utils::fs::read_regular_to_bytes_sync(path) {
Ok(bytes) if hash_eq(&hex::encode(Sha1::digest(&bytes)), pristine_sha1) => {
Ok(bytes)
if hash_eq(&crate::utils::digest::sha1_hex_of(&bytes), pristine_sha1) =>
{
out.stale.push(path.clone())
}
Ok(_) => out.unknown.push(path.clone()),
Expand Down
7 changes: 2 additions & 5 deletions crates/socket-patch-core/src/patch/jvm_jar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};

use sha1::Digest as _;

use crate::crawlers::gradle_cache;
use crate::hash::git_sha256::compute_git_sha256_from_bytes;
use crate::manifest::schema::PatchFileInfo;
Expand Down Expand Up @@ -353,12 +351,11 @@ fn unpatched_members(
}

fn sha256_hex(bytes: &[u8]) -> String {
use sha2::Digest as _;
hex::encode(sha2::Sha256::digest(bytes))
crate::utils::digest::sha256_hex_of(bytes)
}

fn sha1_hex(bytes: &[u8]) -> String {
hex::encode(sha1::Sha1::digest(bytes))
crate::utils::digest::sha1_hex_of(bytes)
}

/// `<socket_dir>/jvm-originals/<sha256>.jar`.
Expand Down
4 changes: 1 addition & 3 deletions crates/socket-patch-core/src/patch/sidecars/maven.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

use std::path::{Path, PathBuf};

use sha1::Digest as _;

use super::{
SidecarAdvisory, SidecarAdvisoryCode, SidecarError, SidecarFile, SidecarFileAction,
SidecarPayload, SidecarSeverity,
Expand All @@ -44,7 +42,7 @@ impl Algo {

fn digest(self, bytes: &[u8]) -> String {
match self {
Algo::Sha1 => hex::encode(sha1::Sha1::digest(bytes)),
Algo::Sha1 => crate::utils::digest::sha1_hex_of(bytes),
Algo::Md5 => hex::encode(md5(bytes)),
}
}
Expand Down
Loading