Compare commits

...

4 Commits

Author SHA1 Message Date
Don Isaac
9676f8b922 Merge branch 'main' into don/fix/large-patch-versions 2025-04-14 11:45:51 -07:00
Don Isaac
d1da677eff pr comments 2025-04-09 11:47:48 -07:00
Don Isaac
8455414042 Merge branch 'main' of github.com:oven-sh/bun into don/fix/large-patch-versions 2025-04-09 11:40:01 -07:00
Don Isaac
3ed8518a91 fix(install): some patch versions may overflow u32 2025-04-08 18:35:46 -07:00
7 changed files with 182 additions and 69 deletions

View File

@@ -3343,7 +3343,7 @@ noinline fn assertionFailureAtLocation(src: std.builtin.SourceLocation) noreturn
@compileError(std.fmt.comptimePrint("assertion failure"));
} else {
@branchHint(.cold);
Output.panic(assertion_failure_msg ++ "at {s}:{d}:{d}", .{ src.file, src.line, src.column });
Output.panic(assertion_failure_msg ++ " at {s}:{d}:{d}", .{ src.file, src.line, src.column });
}
}

View File

@@ -970,7 +970,8 @@ pub const PackageManifest = struct {
// - v0.0.3: added serialization of registry url. it's used to invalidate when it changes
// - v0.0.4: fixed bug with cpu & os tag not being added correctly
// - v0.0.5: added bundled dependencies
pub const version = "bun-npm-manifest-cache-v0.0.5\n";
// - v0.0.6: change patch from u32 to u64 to support date-formatted patch versions
pub const version = "bun-npm-manifest-cache-v0.0.6\n";
const header_bytes: string = "#!/usr/bin/env bun\n" ++ version;
pub const sizes = blk: {
@@ -1580,7 +1581,7 @@ pub const PackageManifest = struct {
}
}
var result: PackageManifest = bun.serializable(PackageManifest{});
var manifest: PackageManifest = bun.serializable(PackageManifest{});
var string_pool = String.Builder.StringPool.init(default_allocator);
defer string_pool.deinit();
@@ -1831,7 +1832,7 @@ pub const PackageManifest = struct {
// Using `expected_name` instead of the name from the manifest. Custom registries might
// have a different name than the dependency name in package.json.
result.pkg.name = string_builder.append(ExternalString, expected_name);
manifest.pkg.name = string_builder.append(ExternalString, expected_name);
get_versions: {
if (json.asProperty("versions")) |versions_q| {
@@ -2291,14 +2292,14 @@ pub const PackageManifest = struct {
}
}
result.pkg.dist_tags = DistTagMap{
manifest.pkg.dist_tags = DistTagMap{
.tags = ExternalStringList.init(all_extern_strings, extern_strings_slice[0..dist_tag_i]),
.versions = VersionSlice.init(all_semver_versions, dist_tag_versions[0..dist_tag_i]),
};
if (comptime Environment.allow_assert) {
bun.assertWithLocation(std.meta.eql(result.pkg.dist_tags.versions.get(all_semver_versions), dist_tag_versions[0..dist_tag_i]), @src());
bun.assertWithLocation(std.meta.eql(result.pkg.dist_tags.tags.get(all_extern_strings), extern_strings_slice[0..dist_tag_i]), @src());
bun.assertWithLocation(std.meta.eql(manifest.pkg.dist_tags.versions.get(all_semver_versions), dist_tag_versions[0..dist_tag_i]), @src());
bun.assertWithLocation(std.meta.eql(manifest.pkg.dist_tags.tags.get(all_extern_strings), extern_strings_slice[0..dist_tag_i]), @src());
}
extern_strings = extern_strings[dist_tag_i..];
@@ -2306,24 +2307,24 @@ pub const PackageManifest = struct {
}
if (last_modified.len > 0) {
result.pkg.last_modified = string_builder.append(String, last_modified);
manifest.pkg.last_modified = string_builder.append(String, last_modified);
}
if (etag.len > 0) {
result.pkg.etag = string_builder.append(String, etag);
manifest.pkg.etag = string_builder.append(String, etag);
}
if (json.asProperty("modified")) |name_q| {
const field = name_q.expr.asString(allocator) orelse return null;
result.pkg.modified = string_builder.append(String, field);
manifest.pkg.modified = string_builder.append(String, field);
}
result.pkg.releases.keys = VersionSlice.init(all_semver_versions, all_release_versions);
result.pkg.releases.values = PackageVersionList.init(versioned_packages, all_versioned_package_releases);
manifest.pkg.releases.keys = VersionSlice.init(all_semver_versions, all_release_versions);
manifest.pkg.releases.values = PackageVersionList.init(versioned_packages, all_versioned_package_releases);
result.pkg.prereleases.keys = VersionSlice.init(all_semver_versions, all_prerelease_versions);
result.pkg.prereleases.values = PackageVersionList.init(versioned_packages, all_versioned_package_prereleases);
manifest.pkg.prereleases.keys = VersionSlice.init(all_semver_versions, all_prerelease_versions);
manifest.pkg.prereleases.values = PackageVersionList.init(versioned_packages, all_versioned_package_prereleases);
const max_versions_count = @max(all_release_versions.len, all_prerelease_versions.len);
@@ -2364,7 +2365,7 @@ pub const PackageManifest = struct {
var all_indices = try bun.default_allocator.alloc(Int, max_versions_count);
defer bun.default_allocator.free(all_indices);
const releases_list = .{ &result.pkg.releases, &result.pkg.prereleases };
const releases_list = .{ &manifest.pkg.releases, &manifest.pkg.prereleases };
var all_cloned_versions = try bun.default_allocator.alloc(Semver.Version, max_versions_count);
defer bun.default_allocator.free(all_cloned_versions);
@@ -2373,7 +2374,7 @@ pub const PackageManifest = struct {
defer bun.default_allocator.free(all_cloned_packages);
inline for (0..2) |release_i| {
var release = releases_list[release_i];
var release: *ExternVersionMap = releases_list[release_i];
const indices = all_indices[0..release.keys.len];
const cloned_packages = all_cloned_packages[0..release.keys.len];
const cloned_versions = all_cloned_versions[0..release.keys.len];
@@ -2428,25 +2429,25 @@ pub const PackageManifest = struct {
all_extern_strings = all_extern_strings[0 .. all_extern_strings.len - extern_strings.len];
}
result.pkg.string_lists_buf.off = 0;
result.pkg.string_lists_buf.len = @as(u32, @truncate(all_extern_strings.len));
manifest.pkg.string_lists_buf.off = 0;
manifest.pkg.string_lists_buf.len = @as(u32, @truncate(all_extern_strings.len));
result.pkg.versions_buf.off = 0;
result.pkg.versions_buf.len = @as(u32, @truncate(all_semver_versions.len));
manifest.pkg.versions_buf.off = 0;
manifest.pkg.versions_buf.len = @as(u32, @truncate(all_semver_versions.len));
result.versions = all_semver_versions;
result.external_strings = all_extern_strings;
result.external_strings_for_versions = version_extern_strings;
result.package_versions = versioned_packages;
result.extern_strings_bin_entries = all_extern_strings_bin_entries[0 .. all_extern_strings_bin_entries.len - extern_strings_bin_entries.len];
result.bundled_deps_buf = bundled_deps_buf;
result.pkg.public_max_age = public_max_age;
manifest.versions = all_semver_versions;
manifest.external_strings = all_extern_strings;
manifest.external_strings_for_versions = version_extern_strings;
manifest.package_versions = versioned_packages;
manifest.extern_strings_bin_entries = all_extern_strings_bin_entries[0 .. all_extern_strings_bin_entries.len - extern_strings_bin_entries.len];
manifest.bundled_deps_buf = bundled_deps_buf;
manifest.pkg.public_max_age = public_max_age;
if (string_builder.ptr) |ptr| {
result.string_buf = ptr[0..string_builder.len];
manifest.string_buf = ptr[0..string_builder.len];
}
return result;
return manifest;
}
};

View File

@@ -91,6 +91,7 @@ fn runTests() u8 {
const tests: []const TestFn = builtin.test_functions;
for (tests) |t| {
std.testing.allocator_instance = .{};
defer Output.flush();
var did_lock = true;
stderr.lock(.exclusive) catch {

View File

@@ -12,11 +12,11 @@ pub const ExternalString = extern struct {
return lhs.value.order(&rhs.value, lhs_buf, rhs_buf);
}
/// ExternalString but without the hash
/// Create an `ExternalString`, calculating its hash.
pub inline fn from(in: string) ExternalString {
return ExternalString{
.value = String.init(in, in),
.hash = bun.Wyhash.hash(0, in),
.hash = String.Builder.stringHash(in),
};
}

View File

@@ -10,6 +10,10 @@ pub inline fn init(buf: string, slice: string) SlicedString {
return SlicedString{ .buf = buf, .slice = slice };
}
pub inline fn from(slice: string) SlicedString {
return .{ .buf = slice, .slice = slice };
}
pub inline fn external(this: SlicedString) ExternalString {
if (comptime Environment.allow_assert) {
assert(@intFromPtr(this.buf.ptr) <= @intFromPtr(this.slice.ptr) and ((@intFromPtr(this.slice.ptr) + this.slice.len) <= (@intFromPtr(this.buf.ptr) + this.buf.len)));

View File

@@ -1,8 +1,33 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const JSC = bun.JSC;
const IdentityContext = @import("../identity_context.zig").IdentityContext;
const OOM = bun.OOM;
const TruncatedPackageNameHash = bun.install.TruncatedPackageNameHash;
const Lockfile = bun.install.Lockfile;
const ExternalString = bun.Semver.ExternalString;
const SlicedString = bun.Semver.SlicedString;
const String = bun.Semver.String;
const Query = bun.Semver.Query;
const assert = bun.assert;
pub const Version = extern struct {
major: u32 = 0,
minor: u32 = 0,
patch: u32 = 0,
_tag_padding: [4]u8 = .{0} ** 4, // [see padding_checker.zig]
/// Some packages use the current date, e.g. `20241225` to make the patch number unique. Some
/// dates are large enough to overflow a u32.
patch: u64 = 0,
tag: Tag = .{},
/// Assumes that there is only one buffer for all the strings
@@ -221,7 +246,7 @@ pub const Version = extern struct {
pub const Partial = struct {
major: ?u32 = null,
minor: ?u32 = null,
patch: ?u32 = null,
patch: ?u64 = null,
tag: Tag = .{},
pub fn min(this: Partial) Version {
@@ -241,6 +266,27 @@ pub const Version = extern struct {
.tag = this.tag,
};
}
pub const PartialFormatter = struct {
version: Partial,
input: string,
pub fn format(formatter: PartialFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
const self = formatter.version;
try std.fmt.format(writer, "{?d}.{?d}.{?d}", .{ self.major orelse 0, self.minor orelse 0, self.patch orelse 0 });
if (self.tag.hasPre()) {
const pre = self.tag.pre.slice(formatter.input);
try writer.writeAll("-");
try writer.writeAll(pre);
}
if (self.tag.hasBuild()) {
const build = self.tag.build.slice(formatter.input);
try writer.writeAll("+");
try writer.writeAll(build);
}
}
};
};
const Hashable = extern struct {
@@ -815,15 +861,15 @@ pub const Version = extern struct {
switch (part_i) {
0 => {
result.version.major = parseVersionNumber(input[part_start_i..last_char_i]);
result.version.major = parseVersionNumber(u32, input[part_start_i..last_char_i]);
part_i = 1;
},
1 => {
result.version.minor = parseVersionNumber(input[part_start_i..last_char_i]);
result.version.minor = parseVersionNumber(u32, input[part_start_i..last_char_i]);
part_i = 2;
},
2 => {
result.version.patch = parseVersionNumber(input[part_start_i..last_char_i]);
result.version.patch = parseVersionNumber(u64, input[part_start_i..last_char_i]);
part_i = 3;
},
else => {},
@@ -945,9 +991,14 @@ pub const Version = extern struct {
return result;
}
fn parseVersionNumber(input: string) ?u32 {
// max decimal u32 is 4294967295
var bytes: [10]u8 = undefined;
fn parseVersionNumber(T: type, input: string) ?T {
// max decimal u32 is 4294967295. Max decimal u64 is 18446744073709551615
const buflen = switch (T) {
u32 => 10,
u64 => 20,
else => @compileError("parseVersionNumber only supports u32 and u64"),
};
var bytes: [buflen]u8 = undefined;
var byte_i: u8 = 0;
assert(input[0] != '.');
@@ -970,41 +1021,96 @@ pub const Version = extern struct {
// If there are no numbers
if (byte_i == 0) return null;
if (comptime Environment.isDebug) {
return std.fmt.parseInt(u32, bytes[0..byte_i], 10) catch |err| {
return std.fmt.parseInt(T, bytes[0..byte_i], 10) catch |err| {
if (comptime Environment.isDebug) {
Output.prettyErrorln("ERROR {s} parsing version: \"{s}\", bytes: {s}", .{
@errorName(err),
input,
bytes[0..byte_i],
});
return 0;
};
}
return std.fmt.parseInt(u32, bytes[0..byte_i], 10) catch 0;
}
return 0;
};
}
};
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const JSC = bun.JSC;
const IdentityContext = @import("../identity_context.zig").IdentityContext;
const OOM = bun.OOM;
const TruncatedPackageNameHash = bun.install.TruncatedPackageNameHash;
const Lockfile = bun.install.Lockfile;
const ExternalString = bun.Semver.ExternalString;
const SlicedString = bun.Semver.SlicedString;
const String = bun.Semver.String;
const t = std.testing;
const expect = t.expect;
const expectEqual = t.expectEqual;
const expectEqualStrings = t.expectEqualStrings;
const Query = bun.Semver.Query;
const assert = bun.assert;
test "Version.parse simple cases" {
const TestCase = struct { []const u8, Version.Partial };
const cases = &[_]TestCase{
.{ "1.0.0", .{ .major = 1, .minor = 0, .patch = 0 } },
.{ "2", .{ .major = 2 } },
.{ "0.1", .{ .major = 0, .minor = 1 } },
.{
"0.0.202410031711", // date-formatted patches could cause u32 overflows
.{ .major = 0, .minor = 0, .patch = 202410031711 },
},
.{
"1.0.0-alpha",
.{ .major = 1, .minor = 0, .patch = 0, .tag = Version.Tag{ .pre = .from("alpha") } },
},
.{
"1.0.0+build",
.{ .major = 1, .minor = 0, .patch = 0, .tag = Version.Tag{ .build = .from("build") } },
},
.{
"0.5.0-foo+bar",
.{ .major = 0, .minor = 5, .patch = 0, .tag = Version.Tag{
.pre = .from("foo"),
.build = .from("bar"),
} },
},
};
for (cases) |test_case| {
const src, const expected = test_case;
const parsed = Version.parseUTF8(src);
expect(parsed.valid) catch |e| {
bun.Output.printErrorln("'{s}' produced an invalid Version.", .{src});
return e;
};
expectEqual(expected, parsed.version) catch |err| {
bun.Output.printErrorln(
\\Parsed '{s}' into unexpected version.
\\Expected: {any}
\\Received: {}
\\ Raw: {any}
,
.{
src,
expected,
parsed.version.fmt(src),
parsed.version,
},
);
return err;
};
}
}
test "Version.parse with large pre/build parts" {
{
const src = "0.0.0-202410031711";
const version = Version.parseUTF8(src).version;
try expectEqual(version.major, 0);
try expectEqual(version.minor, 0);
try expectEqual(version.patch, 0);
try expect(version.tag.hasPre());
try expectEqualStrings(version.tag.pre.slice(src), "202410031711");
try expect(!version.tag.hasBuild());
}
{
const src = "0.0.0-some.pre.tag.thing+some.build.ID.thatislong";
const version = Version.parseUTF8(src).version;
try expect(version.tag.hasPre());
try expectEqualStrings(version.tag.pre.slice(src), "some.pre.tag.thing");
try expect(version.tag.hasBuild());
try expectEqualStrings(version.tag.build.slice(src), "some.build.ID.thatislong");
}
}

View File

@@ -5,6 +5,7 @@ const t = std.testing;
test {
_ = @import("shell/braces.zig");
_ = @import("bun.js/node/assert/myers_diff.zig");
_ = @import("semver/Version.zig");
}
test "basic string usage" {