diff options
author | 2023-08-22 17:20:12 -0700 | |
---|---|---|
committer | 2023-08-22 17:20:12 -0700 | |
commit | d3f55e5fb6e91fc47fa5a9922f3617d7f64c3f68 (patch) | |
tree | 04a9e07af9eca34efa2d78dc31e785540747d1e7 | |
parent | d86084dd8e69d41ae36303024e330e257df534b7 (diff) | |
download | bun-d3f55e5fb6e91fc47fa5a9922f3617d7f64c3f68.tar.gz bun-d3f55e5fb6e91fc47fa5a9922f3617d7f64c3f68.tar.zst bun-d3f55e5fb6e91fc47fa5a9922f3617d7f64c3f68.zip |
feat(install): include top 500 packages as defaults for postinstall
-rw-r--r-- | src/StaticHashMap.zig | 756 | ||||
-rw-r--r-- | src/install/default-trusted-dependencies.txt | 500 | ||||
-rw-r--r-- | src/install/install-scripts-allowlist.txt | 4 | ||||
-rw-r--r-- | src/install/install.zig | 2 | ||||
-rw-r--r-- | src/install/lockfile.zig | 43 |
5 files changed, 1299 insertions, 6 deletions
diff --git a/src/StaticHashMap.zig b/src/StaticHashMap.zig new file mode 100644 index 000000000..e0cbb7cc6 --- /dev/null +++ b/src/StaticHashMap.zig @@ -0,0 +1,756 @@ +// https://github.com/lithdew/rheia/blob/162293d0f0e8d6572a8954c0add83f13f76b3cc6/hash_map.zig +// Apache License 2.0 +const std = @import("std"); + +const mem = std.mem; +const math = std.math; +const testing = std.testing; + +const assert = std.debug.assert; + +pub fn AutoHashMap(comptime K: type, comptime V: type, comptime max_load_percentage: comptime_int) type { + return HashMap(K, V, std.hash_map.AutoContext(K), max_load_percentage); +} + +pub fn AutoStaticHashMap(comptime K: type, comptime V: type, comptime capacity: comptime_int) type { + return StaticHashMap(K, V, std.hash_map.AutoContext(K), capacity); +} + +pub fn StaticHashMap(comptime K: type, comptime V: type, comptime Context: type, comptime capacity: usize) type { + assert(math.isPowerOfTwo(capacity)); + + const shift = 63 - math.log2_int(u64, capacity) + 1; + const overflow = capacity / 10 + (63 - @as(u64, shift) + 1) << 1; + + return struct { + const empty_hash = math.maxInt(u64); + + pub const Entry = struct { + hash: u64 = empty_hash, + key: K = std.mem.zeroes(K), + value: V = std.mem.zeroes(V), + + pub fn isEmpty(self: Entry) bool { + return self.hash == empty_hash; + } + + pub fn format(self: Entry, comptime layout: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = layout; + _ = options; + try std.fmt.format(writer, "(hash: {}, key: {}, value: {})", .{ self.hash, self.key, self.value }); + } + }; + + pub const GetOrPutResult = struct { + value_ptr: *V, + found_existing: bool, + }; + + const Self = @This(); + + entries: [capacity + overflow]Entry = [_]Entry{.{}} ** (capacity + overflow), + len: usize = 0, + shift: u6 = shift, + + // put_probe_count: usize = 0, + // get_probe_count: usize = 0, + // del_probe_count: usize = 0, + + pub usingnamespace HashMapMixin(Self, K, V, Context); + }; +} + +pub fn HashMap(comptime K: type, comptime V: type, comptime Context: type, comptime max_load_percentage: comptime_int) type { + return struct { + const empty_hash = math.maxInt(u64); + + pub const Entry = struct { + hash: u64 = empty_hash, + key: K = undefined, + value: V = undefined, + + pub fn isEmpty(self: Entry) bool { + return self.hash == empty_hash; + } + + pub fn format(self: Entry, comptime layout: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = layout; + _ = options; + try std.fmt.format(writer, "(hash: {}, key: {}, value: {})", .{ self.hash, self.key, self.value }); + } + }; + + pub const GetOrPutResult = struct { + value_ptr: *V, + found_existing: bool, + }; + + const Self = @This(); + + entries: [*]Entry, + len: usize = 0, + shift: u6, + + // put_probe_count: usize = 0, + // get_probe_count: usize = 0, + // del_probe_count: usize = 0, + + pub usingnamespace HashMapMixin(Self, K, V, Context); + + pub fn initCapacity(gpa: mem.Allocator, capacity: u64) !Self { + assert(math.isPowerOfTwo(capacity)); + + const shift = 63 - math.log2_int(u64, capacity) + 1; + const overflow = capacity / 10 + (63 - @as(u64, shift) + 1) << 1; + + const entries = try gpa.alloc(Entry, @as(usize, @intCast(capacity + overflow))); + @memset(entries, .{}); + + return Self{ + .entries = entries.ptr, + .shift = shift, + }; + } + + pub fn deinit(self: *Self, gpa: mem.Allocator) void { + gpa.free(self.slice()); + } + + pub fn ensureUnusedCapacity(self: *Self, gpa: mem.Allocator, count: usize) !void { + try self.ensureTotalCapacity(gpa, self.len + count); + } + + pub fn ensureTotalCapacity(self: *Self, gpa: mem.Allocator, count: usize) !void { + while (true) { + const capacity = @as(u64, 1) << (63 - self.shift + 1); + if (count <= capacity * max_load_percentage / 100) { + break; + } + try self.grow(gpa); + } + } + + fn grow(self: *Self, gpa: mem.Allocator) !void { + const capacity = @as(u64, 1) << (63 - self.shift + 1); + const overflow = capacity / 10 + (63 - @as(usize, self.shift) + 1) << 1; + const end = self.entries + @as(usize, @intCast(capacity + overflow)); + + var map = try Self.initCapacity(gpa, @as(usize, @intCast(capacity * 2))); + var src = self.entries; + var dst = map.entries; + + while (src != end) { + const entry = src[0]; + + const i = if (!entry.isEmpty()) entry.hash >> map.shift else 0; + const p = map.entries + i; + + dst = if (@intFromPtr(p) >= @intFromPtr(dst)) p else dst; + dst[0] = entry; + + src += 1; + dst += 1; + } + + self.deinit(gpa); + self.entries = map.entries; + self.shift = map.shift; + } + + pub fn put(self: *Self, gpa: mem.Allocator, key: K, value: V) !void { + try self.putContext(gpa, key, value, undefined); + } + + pub fn putContext(self: *Self, gpa: mem.Allocator, key: K, value: V, ctx: Context) !void { + try self.ensureUnusedCapacity(gpa, 1); + self.putAssumeCapacityContext(key, value, ctx); + } + + pub fn getOrPut(self: *Self, gpa: mem.Allocator, key: K) !GetOrPutResult { + return try self.getOrPutContext(gpa, key, undefined); + } + + pub fn getOrPutContext(self: *Self, gpa: mem.Allocator, key: K, ctx: Context) !GetOrPutResult { + try self.ensureUnusedCapacity(gpa, 1); + return self.getOrPutAssumeCapacityContext(key, ctx); + } + }; +} + +fn HashMapMixin( + comptime Self: type, + comptime K: type, + comptime V: type, + comptime Context: type, +) type { + return struct { + pub fn clearRetainingCapacity(self: *Self) void { + @memset(self.slice(), .{}); + self.len = 0; + } + + pub fn slice(self: *Self) []Self.Entry { + const capacity = @as(u64, 1) << (63 - self.shift + 1); + const overflow = capacity / 10 + (63 - @as(usize, self.shift) + 1) << 1; + return self.entries[0..@as(usize, @intCast(capacity + overflow))]; + } + + pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { + self.putAssumeCapacityContext(key, value, undefined); + } + + pub fn putAssumeCapacityContext(self: *Self, key: K, value: V, ctx: Context) void { + const result = self.getOrPutAssumeCapacityContext(key, ctx); + if (!result.found_existing) result.value_ptr.* = value; + } + + pub fn getOrPutAssumeCapacity(self: *Self, key: K) Self.GetOrPutResult { + return self.getOrPutAssumeCapacityContext(key, undefined); + } + + pub fn getOrPutAssumeCapacityContext(self: *Self, key: K, ctx: Context) Self.GetOrPutResult { + var it: Self.Entry = .{ .hash = ctx.hash(key), .key = key, .value = undefined }; + var i = it.hash >> self.shift; + + assert(it.hash != Self.empty_hash); + + var inserted_at: ?usize = null; + while (true) : (i += 1) { + const entry = self.entries[i]; + if (entry.hash >= it.hash) { + if (ctx.eql(entry.key, key)) { + return .{ .found_existing = true, .value_ptr = &self.entries[i].value }; + } + self.entries[i] = it; + if (entry.isEmpty()) { + self.len += 1; + return .{ .found_existing = false, .value_ptr = &self.entries[inserted_at orelse i].value }; + } + if (inserted_at == null) { + inserted_at = i; + } + it = entry; + } + // self.put_probe_count += 1; + } + } + + pub fn get(self: *Self, key: K) ?V { + return self.getContext(key, undefined); + } + + pub fn getContext(self: *Self, key: K, ctx: Context) ?V { + const hash = ctx.hash(key); + assert(hash != Self.empty_hash); + + var i = hash >> self.shift; + while (true) : (i += 1) { + const entry = self.entries[i]; + if (entry.hash >= hash) { + if (!ctx.eql(entry.key, key)) { + return null; + } + return entry.value; + } + // self.get_probe_count += 1; + } + } + + pub fn has(self: *Self, key: K) bool { + return self.hasContext(key, undefined); + } + + pub fn hasContext(self: *Self, key: K, ctx: Context) bool { + const hash = ctx.hash(key); + assert(hash != Self.empty_hash); + + var i = hash >> self.shift; + while (true) : (i += 1) { + const entry = self.entries[i]; + if (entry.hash >= hash) { + if (!ctx.eql(entry.key, key)) { + return false; + } + return true; + } + // self.get_probe_count += 1; + } + } + + pub fn delete(self: *Self, key: K) ?V { + return self.deleteContext(key, undefined); + } + + pub fn deleteContext(self: *Self, key: K, ctx: Context) ?V { + const hash = ctx.hash(key); + assert(hash != Self.empty_hash); + + var i = hash >> self.shift; + while (true) : (i += 1) { + const entry = self.entries[i]; + if (entry.hash >= hash) { + if (!ctx.eql(entry.key, key)) { + return null; + } + break; + } + // self.del_probe_count += 1; + } + + const value = self.entries[i].value; + + while (true) : (i += 1) { + const j = self.entries[i + 1].hash >> self.shift; + if (i < j or self.entries[i + 1].isEmpty()) { + break; + } + self.entries[i] = self.entries[i + 1]; + // self.del_probe_count += 1; + } + self.entries[i] = .{}; + self.len -= 1; + + return value; + } + }; +} + +pub fn SortedHashMap(comptime V: type, comptime max_load_percentage: comptime_int) type { + return struct { + const empty_hash: [32]u8 = [_]u8{0xFF} ** 32; + + pub const Entry = struct { + hash: [32]u8 = empty_hash, + value: V = undefined, + + pub fn isEmpty(self: Entry) bool { + return cmp(self.hash, empty_hash) == .eq; + } + + pub fn format(self: Entry, comptime layout: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = layout; + _ = options; + try std.fmt.format(writer, "(hash: {}, value: {})", .{ std.fmt.fmtSliceHexLower(mem.asBytes(&self.hash)), self.value }); + } + }; + + const Self = @This(); + + entries: [*]Entry, + len: usize = 0, + shift: u6, + + // put_probe_count: usize = 0, + // get_probe_count: usize = 0, + // del_probe_count: usize = 0, + + pub fn init(gpa: mem.Allocator) !Self { + return Self.initCapacity(gpa, 16); + } + + pub fn initCapacity(gpa: mem.Allocator, capacity: u64) !Self { + assert(math.isPowerOfTwo(capacity)); + + const shift = 63 - math.log2_int(u64, capacity) + 1; + const overflow = capacity / 10 + (63 - @as(u64, shift) + 1) << 1; + + const entries = try gpa.alloc(Entry, @as(usize, @intCast(capacity + overflow))); + @memset(entries, Entry{}); + + return Self{ + .entries = entries.ptr, + .shift = shift, + }; + } + + pub fn deinit(self: *Self, gpa: mem.Allocator) void { + gpa.free(self.slice()); + } + + /// The following routine has its branches optimized against inputs that are cryptographic hashes by + /// assuming that if the first 64 bits of 'a' and 'b' are equivalent, then 'a' and 'b' are most likely + /// equivalent. + fn cmp(a: [32]u8, b: [32]u8) math.Order { + const msa = @as(u64, @bitCast(a[0..8].*)); + const msb = @as(u64, @bitCast(b[0..8].*)); + if (msa != msb) { + return if (mem.bigToNative(u64, msa) < mem.bigToNative(u64, msb)) .lt else .gt; + } else if (@reduce(.And, @as(@Vector(32, u8), a) == @as(@Vector(32, u8), b))) { + return .eq; + } else { + switch (math.order(mem.readIntBig(u64, a[8..16]), mem.readIntBig(u64, b[8..16]))) { + .eq => {}, + .lt => return .lt, + .gt => return .gt, + } + switch (math.order(mem.readIntBig(u64, a[16..24]), mem.readIntBig(u64, b[16..24]))) { + .eq => {}, + .lt => return .lt, + .gt => return .gt, + } + return math.order(mem.readIntBig(u64, a[24..32]), mem.readIntBig(u64, b[24..32])); + } + } + + /// In release-fast mode, LLVM will optimize this routine to utilize 109 cycles. This routine scatters + /// hash values across a table into buckets which are lexicographically ordered from one another in + /// ascending order. + fn idx(a: [32]u8, shift: u6) usize { + return @as(usize, @intCast(mem.readIntBig(u64, a[0..8]) >> shift)); + } + + pub fn clearRetainingCapacity(self: *Self) void { + @memset(self.slice(), Entry{}); + self.len = 0; + } + + pub fn slice(self: *Self) []Entry { + const capacity = @as(u64, 1) << (63 - self.shift + 1); + const overflow = capacity / 10 + (63 - @as(usize, self.shift) + 1) << 1; + return self.entries[0..@as(usize, @intCast(capacity + overflow))]; + } + + pub fn ensureUnusedCapacity(self: *Self, gpa: mem.Allocator, count: usize) !void { + try self.ensureTotalCapacity(gpa, self.len + count); + } + + pub fn ensureTotalCapacity(self: *Self, gpa: mem.Allocator, count: usize) !void { + while (true) { + const capacity = @as(u64, 1) << (63 - self.shift + 1); + if (count <= capacity * max_load_percentage / 100) { + break; + } + try self.grow(gpa); + } + } + + fn grow(self: *Self, gpa: mem.Allocator) !void { + const capacity = @as(u64, 1) << (63 - self.shift + 1); + const overflow = capacity / 10 + (63 - @as(usize, self.shift) + 1) << 1; + const end = self.entries + @as(usize, @intCast(capacity + overflow)); + + var map = try Self.initCapacity(gpa, @as(usize, @intCast(capacity * 2))); + var src = self.entries; + var dst = map.entries; + + while (src != end) { + const entry = src[0]; + + const i = if (!entry.isEmpty()) idx(entry.hash, map.shift) else 0; + const p = map.entries + i; + + dst = if (@intFromPtr(p) >= @intFromPtr(dst)) p else dst; + dst[0] = entry; + + src += 1; + dst += 1; + } + + self.deinit(gpa); + self.entries = map.entries; + self.shift = map.shift; + } + + pub fn put(self: *Self, gpa: mem.Allocator, key: [32]u8, value: V) !void { + try self.ensureUnusedCapacity(gpa, 1); + self.putAssumeCapacity(key, value); + } + + pub fn putAssumeCapacity(self: *Self, key: [32]u8, value: V) void { + const result = self.getOrPutAssumeCapacity(key); + if (!result.found_existing) result.value_ptr.* = value; + } + + pub const GetOrPutResult = struct { + value_ptr: *V, + found_existing: bool, + }; + + pub fn getOrPut(self: *Self, gpa: mem.Allocator, key: [32]u8) !GetOrPutResult { + try self.ensureUnusedCapacity(gpa, 1); + return self.getOrPutAssumeCapacity(key); + } + + pub fn getOrPutAssumeCapacity(self: *Self, key: [32]u8) GetOrPutResult { + assert(self.len < (@as(u64, 1) << (63 - self.shift + 1))); + assert(cmp(key, empty_hash) != .eq); + + var it: Entry = .{ .hash = key, .value = undefined }; + var i = idx(key, self.shift); + + var inserted_at: ?usize = null; + while (true) : (i += 1) { + const entry = self.entries[i]; + if (cmp(entry.hash, it.hash).compare(.gte)) { + if (cmp(entry.hash, key) == .eq) { + return .{ .found_existing = true, .value_ptr = &self.entries[i].value }; + } + self.entries[i] = it; + if (entry.isEmpty()) { + self.len += 1; + return .{ .found_existing = false, .value_ptr = &self.entries[inserted_at orelse i].value }; + } + if (inserted_at == null) { + inserted_at = i; + } + it = entry; + } + self.put_probe_count += 1; + } + } + + pub fn get(self: *Self, key: [32]u8) ?V { + assert(cmp(key, empty_hash) != .eq); + + var i = idx(key, self.shift); + while (true) : (i += 1) { + const entry = self.entries[i]; + if (cmp(entry.hash, key).compare(.gte)) { + if (cmp(entry.hash, key) != .eq) { + return null; + } + return entry.value; + } + // self.get_probe_count += 1; + } + } + + pub fn delete(self: *Self, key: [32]u8) ?V { + assert(cmp(key, empty_hash) != .eq); + + var i = idx(key, self.shift); + while (true) : (i += 1) { + const entry = self.entries[i]; + if (cmp(entry.hash, key).compare(.gte)) { + if (cmp(entry.hash, key) != .eq) { + return null; + } + break; + } + self.del_probe_count += 1; + } + + const value = self.entries[i].value; + + while (true) : (i += 1) { + const j = idx(self.entries[i + 1].hash, self.shift); + if (i < j or self.entries[i + 1].isEmpty()) { + break; + } + self.entries[i] = self.entries[i + 1]; + self.del_probe_count += 1; + } + self.entries[i] = .{}; + self.len -= 1; + + return value; + } + }; +} + +test "StaticHashMap: put, get, delete, grow" { + var map: AutoStaticHashMap(usize, usize, 512) = .{}; + + var seed: usize = 0; + while (seed < 128) : (seed += 1) { + var rng = std.rand.DefaultPrng.init(seed); + + const keys = try testing.allocator.alloc(usize, 512); + defer testing.allocator.free(keys); + + for (keys) |*key| key.* = @as(usize, rng.next()); + + try testing.expectEqual(@as(u6, 55), map.shift); + + for (keys, 0..) |key, i| map.putAssumeCapacity(key, i); + try testing.expectEqual(keys.len, map.len); + + var it: usize = 0; + for (map.slice()) |entry| { + if (!entry.isEmpty()) { + if (it > entry.hash) { + return error.Unsorted; + } + it = entry.hash; + } + } + + for (keys, 0..) |key, i| try testing.expectEqual(i, map.get(key).?); + for (keys, 0..) |key, i| try testing.expectEqual(i, map.delete(key).?); + } +} + +test "HashMap: put, get, delete, grow" { + var seed: usize = 0; + while (seed < 128) : (seed += 1) { + var rng = std.rand.DefaultPrng.init(seed); + + const keys = try testing.allocator.alloc(usize, 512); + defer testing.allocator.free(keys); + + for (keys) |*key| key.* = rng.next(); + + var map = try AutoHashMap(usize, usize, 50).initCapacity(testing.allocator, 16); + defer map.deinit(testing.allocator); + + try testing.expectEqual(@as(u6, 60), map.shift); + + for (keys, 0..) |key, i| try map.put(testing.allocator, key, i); + + try testing.expectEqual(@as(u6, 54), map.shift); + try testing.expectEqual(keys.len, map.len); + + var it: usize = 0; + for (map.slice()) |entry| { + if (!entry.isEmpty()) { + if (it > entry.hash) { + return error.Unsorted; + } + it = entry.hash; + } + } + + for (keys, 0..) |key, i| try testing.expectEqual(i, map.get(key).?); + for (keys, 0..) |key, i| try testing.expectEqual(i, map.delete(key).?); + } +} + +test "SortedHashMap: cmp" { + const prefix = [_]u8{'0'} ** 8 ++ [_]u8{'1'} ** 23; + const a = prefix ++ [_]u8{0}; + const b = prefix ++ [_]u8{1}; + + try testing.expect(SortedHashMap(void, 100).cmp(a, b) == .lt); + try testing.expect(SortedHashMap(void, 100).cmp(b, a) == .gt); + try testing.expect(SortedHashMap(void, 100).cmp(a, a) == .eq); + try testing.expect(SortedHashMap(void, 100).cmp(b, b) == .eq); + try testing.expect(SortedHashMap(void, 100).cmp([_]u8{'i'} ++ [_]u8{'0'} ** 31, [_]u8{'o'} ++ [_]u8{'0'} ** 31) == .lt); + try testing.expect(SortedHashMap(void, 100).cmp([_]u8{ 'h', 'i' } ++ [_]u8{'0'} ** 30, [_]u8{ 'h', 'o' } ++ [_]u8{'0'} ** 30) == .lt); +} + +test "SortedHashMap: put, get, delete, grow" { + var seed: usize = 0; + while (seed < 128) : (seed += 1) { + var rng = std.rand.DefaultPrng.init(seed); + + const keys = try testing.allocator.alloc([32]u8, 512); + defer testing.allocator.free(keys); + + for (keys) |*key| rng.fill(key); + + var map = try SortedHashMap(usize, 50).initCapacity(testing.allocator, 16); + defer map.deinit(testing.allocator); + + try testing.expectEqual(@as(u6, 60), map.shift); + + for (keys, 0..) |key, i| try map.put(testing.allocator, key, i); + + try testing.expectEqual(@as(u6, 54), map.shift); + try testing.expectEqual(keys.len, map.len); + + var it = [_]u8{0} ** 32; + for (map.slice()) |entry| { + if (!entry.isEmpty()) { + if (!mem.order(u8, &it, &entry.hash).compare(.lte)) { + return error.Unsorted; + } + it = entry.hash; + } + } + + for (keys, 0..) |key, i| try testing.expectEqual(i, map.get(key).?); + for (keys, 0..) |key, i| try testing.expectEqual(i, map.delete(key).?); + } +} + +test "SortedHashMap: collision test" { + const prefix = [_]u8{22} ** 8 ++ [_]u8{1} ** 23; + + var map = try SortedHashMap(usize, 100).initCapacity(testing.allocator, 4); + defer map.deinit(testing.allocator); + + try map.put(testing.allocator, prefix ++ [_]u8{0}, 0); + try map.put(testing.allocator, prefix ++ [_]u8{1}, 1); + try map.put(testing.allocator, prefix ++ [_]u8{2}, 2); + try map.put(testing.allocator, prefix ++ [_]u8{3}, 3); + + var it = [_]u8{0} ** 32; + for (map.slice()) |entry| { + if (!entry.isEmpty()) { + if (!mem.order(u8, &it, &entry.hash).compare(.lte)) { + return error.Unsorted; + } + it = entry.hash; + } + } + + try testing.expectEqual(@as(usize, 0), map.get(prefix ++ [_]u8{0}).?); + try testing.expectEqual(@as(usize, 1), map.get(prefix ++ [_]u8{1}).?); + try testing.expectEqual(@as(usize, 2), map.get(prefix ++ [_]u8{2}).?); + try testing.expectEqual(@as(usize, 3), map.get(prefix ++ [_]u8{3}).?); + + try testing.expectEqual(@as(usize, 2), map.delete(prefix ++ [_]u8{2}).?); + try testing.expectEqual(@as(usize, 0), map.delete(prefix ++ [_]u8{0}).?); + try testing.expectEqual(@as(usize, 1), map.delete(prefix ++ [_]u8{1}).?); + try testing.expectEqual(@as(usize, 3), map.delete(prefix ++ [_]u8{3}).?); + + try map.put(testing.allocator, prefix ++ [_]u8{0}, 0); + try map.put(testing.allocator, prefix ++ [_]u8{2}, 2); + try map.put(testing.allocator, prefix ++ [_]u8{3}, 3); + try map.put(testing.allocator, prefix ++ [_]u8{1}, 1); + + it = [_]u8{0} ** 32; + for (map.slice()) |entry| { + if (!entry.isEmpty()) { + if (!mem.order(u8, &it, &entry.hash).compare(.lte)) { + return error.Unsorted; + } + it = entry.hash; + } + } + + try testing.expectEqual(@as(usize, 0), map.delete(prefix ++ [_]u8{0}).?); + try testing.expectEqual(@as(usize, 1), map.delete(prefix ++ [_]u8{1}).?); + try testing.expectEqual(@as(usize, 2), map.delete(prefix ++ [_]u8{2}).?); + try testing.expectEqual(@as(usize, 3), map.delete(prefix ++ [_]u8{3}).?); + + try map.put(testing.allocator, prefix ++ [_]u8{0}, 0); + try map.put(testing.allocator, prefix ++ [_]u8{2}, 2); + try map.put(testing.allocator, prefix ++ [_]u8{1}, 1); + try map.put(testing.allocator, prefix ++ [_]u8{3}, 3); + + it = [_]u8{0} ** 32; + for (map.slice()) |entry| { + if (!entry.isEmpty()) { + if (!mem.order(u8, &it, &entry.hash).compare(.lte)) { + return error.Unsorted; + } + it = entry.hash; + } + } + + try testing.expectEqual(@as(usize, 3), map.delete(prefix ++ [_]u8{3}).?); + try testing.expectEqual(@as(usize, 2), map.delete(prefix ++ [_]u8{2}).?); + try testing.expectEqual(@as(usize, 1), map.delete(prefix ++ [_]u8{1}).?); + try testing.expectEqual(@as(usize, 0), map.delete(prefix ++ [_]u8{0}).?); + + try map.put(testing.allocator, prefix ++ [_]u8{3}, 3); + try map.put(testing.allocator, prefix ++ [_]u8{0}, 0); + try map.put(testing.allocator, prefix ++ [_]u8{1}, 1); + try map.put(testing.allocator, prefix ++ [_]u8{2}, 2); + + it = [_]u8{0} ** 32; + for (map.slice()) |entry| { + if (!entry.isEmpty()) { + if (!mem.order(u8, &it, &entry.hash).compare(.lte)) { + return error.Unsorted; + } + it = entry.hash; + } + } + + try testing.expectEqual(@as(usize, 3), map.delete(prefix ++ [_]u8{3}).?); + try testing.expectEqual(@as(usize, 0), map.delete(prefix ++ [_]u8{0}).?); + try testing.expectEqual(@as(usize, 1), map.delete(prefix ++ [_]u8{1}).?); + try testing.expectEqual(@as(usize, 2), map.delete(prefix ++ [_]u8{2}).?); +} diff --git a/src/install/default-trusted-dependencies.txt b/src/install/default-trusted-dependencies.txt new file mode 100644 index 000000000..efdb12bdc --- /dev/null +++ b/src/install/default-trusted-dependencies.txt @@ -0,0 +1,500 @@ +@airbnb/node-memwatch +@alaskaairux/icons +@antv/l7-react +@apollo/protobufjs +@apollo/rover +@applitools/eyes-storybook +@appsignal/nodejs +@arkweid/lefthook +@aws-amplify/cli +@azure/msal-node-extensions +@bahmutov/add-typescript-to-cypress +@bazel/concatjs +@bazel/cypress +@bazel/esbuild +@bazel/hide-bazel-files +@bazel/jasmine +@bazel/protractor +@bazel/rollup +@bazel/terser +@bazel/typescript +@bufbuild/buf +@carbon/charts +@carbon/charts-angular +@carbon/charts-react +@carbon/ibm-products +@carbon/icons-react +@carbon/pictograms-react +@carbon/react +@cdktf/node-pty-prebuilt-multiarch +@ckeditor/ckeditor5-react +@ckeditor/ckeditor5-vue +@cloudflare/wrangler +@compodoc/compodoc +@contrast/fn-inspect +@cubejs-backend/cubestore +@cubejs-backend/native +@cypress/snapshot +@danmarshall/deckgl-typings +@databricks/sql +@datadog/mobile-react-native +@datadog/native-appsec +@datadog/native-metrics +@datadog/pprof +@discordjs/opus +@eversdk/lib-node +@evilmartians/lefthook +@ffmpeg-installer/darwin-arm64 +@ffmpeg-installer/darwin-x64 +@ffmpeg-installer/linux-arm +@ffmpeg-installer/linux-arm64 +@ffmpeg-installer/linux-ia32 +@ffmpeg-installer/linux-x64 +@ffprobe-installer/darwin-arm64 +@ffprobe-installer/darwin-x64 +@ffprobe-installer/linux-arm +@ffprobe-installer/linux-arm64 +@ffprobe-installer/linux-ia32 +@ffprobe-installer/linux-x64 +@fingerprintjs/fingerprintjs-pro-react +@fortawesome/fontawesome-common-types +@fortawesome/fontawesome-free +@fortawesome/fontawesome-svg-core +@fortawesome/free-brands-svg-icons +@fortawesome/free-regular-svg-icons +@fortawesome/free-solid-svg-icons +@ghaiklor/x509 +@go-task/cli +@gql2ts/language-typescript +@injectivelabs/sdk-ts +@instana/autoprofile +@intlify/vue-i18n-bridge +@intlify/vue-router-bridge +@lightdash/cli +@matteodisabatino/gc_info +@memlab/cli +@microsoft.azure/autorest-core +@microsoft/teamsfx-cli +@microsoft/ts-command-line +@napi-rs/canvas-linux-x64-gnu +@napi-rs/canvas-linux-x64-musl +@napi-rs/pinyin +@napi-rs/simple-git-linux-arm64-gnu +@napi-rs/simple-git-linux-arm64-musl +@napi-rs/simple-git-linux-x64-gnu +@napi-rs/simple-git-linux-x64-musl +@nativescript/core +@nestjs/core +@netlify/esbuild +@newrelic/native-metrics +@notarize/qlc-cli +@nx-dotnet/core +@openapitools/openapi-generator-cli +@opensea/seaport-js +@opensearch-project/oui +@opentelemetry/instrumentation-grpc +@pact-foundation/pact-core +@pact-foundation/pact-node +@paloaltonetworks/postman-code-generators +@parcel/watcher +@pdftron/pdfnet-node +@percy/core +@pnpm/exe +@prisma/client +@prisma/engines +@progress/kendo-licensing +@pulumi/aws +@pulumi/aws-native +@pulumi/awsx +@pulumi/azure +@pulumi/azure-native +@pulumi/cloudflare +@pulumi/command +@pulumi/datadog +@pulumi/docker +@pulumi/gcp +@pulumi/github +@pulumi/kubernetes +@pulumi/postgresql +@pulumi/random +@pulumi/tls +@replayio/cypress +@replayio/playwright +@root/acme +@roots/bud-framework +@sanity/eslint-config-studio +@sap/hana-client +@sap/hana-performance-tools +@sap/hana-theme-vscode +@scarf/scarf +@sematext/gc-stats +@sentry/capacitor +@sentry/cli +@sentry/profiling-node +@serialport/bindings +@serialport/bindings-cpp +@shopify/ngrok +@shopify/plugin-cloudflare +@shopify/react-native-skia +@sitespeed.io/chromedriver +@sitespeed.io/edgedriver +@softvisio/core +@splunk/otel +@strapi/strapi +@substrate/connect +@sveltejs/kit +@swc/core +@syncfusion/ej2-angular-base +@taquito/taquito +@tds/core-colours +@temporalio/core-bridge +@tensorflow/tfjs-node +@trufflesuite/bigint-buffer +@trumbitta/nx-plugin-unused-deps +@typescript-tools/rust-implementation +@vaadin/vaadin-usage-statistics +@vscode/ripgrep +@vscode/sqlite3 +abstract-socket +admin-lte +appdynamics +appium-chromedriver +appium-windows-driver +applicationinsights-native-metrics +argon2 +autorest +avo +aws-crt +azure-arm-cdn +azure-arm-compute +azure-arm-network +azure-arm-storage +azure-functions-core-tools +azure-streamanalytics-cicd +babylonjs +backport +baseui +bcrypt +better-sqlite3 +bigint-buffer +bigscreen-player +blake-hash +bootstrap-fileinput +bootstrap-vue +browser-tabs-lock +bs-platform +bufferutil +bun +canvacord +canvas +carbon-addons-iot-react +carbon-components +carbon-components-angular +carbon-components-react +cbor-extract +ccxt +chromedriver +chromium +classic-level +cld +cldr-data +clevertap-react-native +clientjs +cmark-gfm +command-join +commitlint-config-jira +compresion +contentlayer +contextify +cordova.plugins.diagnostic +core-js-bundle +couchbase +cpu-features +cwebp-bin +cy2 +cypress +data-forge +dd-trace +deasync +detox +detox-recorder +discord-economy-super +diskusage +docsify +dooboolab-welcome +dotnet-2.0.0 +dprint +drivelist +dtrace-provider +duckdb +dugite +eccrypto +egg-bin +egg-ci +electron +electron-chromedriver +electron-prebuilt +electron-winstaller +elm +elm-format +es5-ext +esbuild +esoftplay +event-loop-stats +exifreader +external-svg-loader +farmhash +fast-folder-size +faunadb +ffi +ffi-napi +ffmpeg-static +fibers +fmerge +free-email-domains +fs-xattr +full-icu +gatsby +gatsby-cli +gatsby-telemetry +gc-stats +gcstats.js +geckodriver +gentype +ghooks +gif2webp-bin +gifsicle +git-commit-msg-linter +git-validate +git-win +gl +gmsmith +go-ios +grpc +grpc-tools +handbrake-js +hasura-cli +heapdump +highcharts-export-server +hiredis +hnswlib-node +hugo-bin +hummus +ibm_db +iconv +iedriver +iltorb +incremental-json-parser +inferno +install-peers +interruptor +iobroker.js-controller +iso-constants +isolated-vm +java +javascript-obfuscator +jest-preview +jpeg-recompress-bin +jpegtran-bin +keccak +kerberos +keytar +lefthook +leveldown +libpg-query +libpq +libxmljs +libxmljs2 +lint +lmdb +lmdb-store +local-cypress +lz4 +lzma-native +lzo +macos-alias +mbt +medusa-telemetry +memlab +metalsmith +microtime +minidump +mmmagic +modern-syslog +monaco-languageclient +mongodb-client-encryption +mongodb-crypt-library-dummy +mongodb-crypt-library-version +mongodb-memory-server +mozjpeg +ms-chromium-edge-driver +msgpackr-extract +msnodesqlv8 +msw +muhammara +neo4j-bloom +nestjs-pino +netlify-cli +next-plugin-preact +next-translate-plugin +ngrok +ngx-popperjs +nice-napi +node +node-expat +node-hid +node-jq +node-libcurl +node-pty +node-rdkafka +node-sass +node-webcrypto-ossl +node-zopfli +node-zopfli-es +nodegit +nodejieba +nodent-runtime +nuxt-edge +nx +odiff-bin +oniguruma +optipng-bin +oracledb +os-dns-native +parcel-bundler +parse-server +phantomjs +phantomjs-prebuilt +pkcs11js +playwright +playwright-chromium +playwright-firefox +playwright-webkit +pngout-bin +pngquant-bin +poolifier +posix +postinstall-postinstall +postinstall-prepare +pprof +pre-commit +pre-push +prisma +protobufjs +protoc +protoc-gen-grpc-web +puppeteer +quick-mongo-super +re2 +react-intl-universal +react-jsx-parser +react-native-calendar-picker +react-native-elements +react-native-inappbrowser-reborn +react-native-storage +react-native-stylex +react-native-unimodules +react-particles +react-ranger +react-tsparticles +react-vertical-timeline-component +realm +redis-memory-server +ref +ref-napi +registry-js +restana +rete +robotjs +rome +rovel.js +rxdb +sauce-connect-launcher +saucectl +scrollreveal +secp256k1 +segfault-handler +serverless +sfdx-cli +shared-git-hooks +sharp +simple-git-hooks +sleep +slice2js +snyk +sockopt +sodium-native +sonar-scanner +spectaql +spectron +spellchecker +sq-native +sqlite3 +sse4_crc32 +ssh2 +storage-engine +strapi +subrequests +subrequests-express +subrequests-json-merger +supabase +svelte-preprocess +svf-lib +swagger-ui +swiftlint +taco-cli +taiko +tesseract.js +tldjs +tree-sitter +tree-sitter-cli +tree-sitter-json +tree-sitter-kotlin +tree-sitter-typescript +tree-sitter-yaml +truffle +tsparticles-engine +ttag-cli +ttf2woff2 +turbo +typeit +typemoq +typeorm-fixtures-cli +typesense-instantsearch-adapter +unix-dgram +ursa-optional +usb +utf-8-validate +v8-profiler-next +vercel +vis-data +vis-network +vis-timeline +vue-demi +vue-echarts +vue-inbrowser-compiler-demi +vue-ls +vue-material +vue-popperjs +vue-test-utils +vuepress +vuex-module-decorators +wd +wdeasync +weak-napi +web3-bzz +web3-shh +webdev-toolkit +windows-build-tools +wix-style-react +wordpos +workerd +wrtc +xxhash +yarn +yo +yorkie +zapatos +zeromq +zlib-sync +zopflipng-bin
\ No newline at end of file diff --git a/src/install/install-scripts-allowlist.txt b/src/install/install-scripts-allowlist.txt deleted file mode 100644 index fdd5d1c06..000000000 --- a/src/install/install-scripts-allowlist.txt +++ /dev/null @@ -1,4 +0,0 @@ -static-ffmpeg -canvas -better-sqlite3 -node-sass
\ No newline at end of file diff --git a/src/install/install.zig b/src/install/install.zig index d444b62fc..8f7411535 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -6804,7 +6804,7 @@ pub const PackageManager = struct { } } - if (resolution.tag == .workspace or this.lockfile.trusted_dependencies.contains(@as(u32, @truncate(String.Builder.stringHash(name))))) { + if (resolution.tag == .workspace or this.lockfile.hasTrustedDependency(name)) { var scripts = this.lockfile.packages.items(.scripts)[package_id]; if (scripts.hasAny()) { var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 8eb411739..93c74e9e1 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -81,6 +81,7 @@ const PackageNameHash = Install.PackageNameHash; const Resolution = @import("./resolution.zig").Resolution; const Crypto = @import("../sha.zig").Hashers; const PackageJSON = @import("../resolver/package_json.zig").PackageJSON; +const StaticHashMap = @import("../StaticHashMap.zig").StaticHashMap; const MetaHash = [std.crypto.hash.sha2.Sha512256.digest_length]u8; const zero_hash = std.mem.zeroes(MetaHash); @@ -105,10 +106,12 @@ allocator: Allocator, scratch: Scratch = .{}, scripts: Scripts = .{}, -trusted_dependencies: NameHashSet = .{}, workspace_paths: NameHashMap = .{}, workspace_versions: VersionHashMap = .{}, +has_trusted_dependencies: bool = false, +trusted_dependencies: NameHashSet = .{}, + const Stream = std.io.FixedBufferStream([]u8); pub const default_filename = "bun.lockb"; @@ -3379,6 +3382,7 @@ pub const Package = extern struct { }; lockfile.trusted_dependencies.putAssumeCapacity(@as(u32, @truncate(String.Builder.stringHash(name))), {}); } + lockfile.has_trusted_dependencies = true; }, else => { log.addErrorFmt(&source, q.loc, allocator, @@ -4281,3 +4285,40 @@ pub fn resolve(this: *Lockfile, package_name: []const u8, version: Dependency.Ve return null; } + +/// The default list of trusted dependencies is a static hashmap +const default_trusted_dependencies = brk: { + const max_values = 512; + + var map: StaticHashMap([]const u8, u0, std.hash_map.StringContext, max_values) = .{}; + + // This file contains a list of dependencies that Bun runs `postinstall` on by default. + const data = @embedFile("./default-trusted-dependencies.txt"); + comptime var line_start = 0; + comptime var i = 0; + @setEvalBranchQuota(123456); + while (i < data.len) { + while (i < data.len) : (i += 1) { + if (data[i] == '\n' or data[i] == '\r') break; + } + const line_slice = data[line_start..i]; + i += 1; + if (line_slice.len == 0) break; + if (map.len == max_values) { + @compileError("default-trusted-dependencies.txt is too large, please increase 'max_values' in lockfile.zig"); + } + line_start = i; + map.putAssumeCapacity(line_slice, 0); + } + + break :brk ↦ +}; + +pub fn hasTrustedDependency(this: *Lockfile, name: []const u8) bool { + if (this.has_trusted_dependencies) { + const hash = @as(u32, @truncate(String.Builder.stringHash(name))); + return this.trusted_dependencies.contains(hash); + } else { + return default_trusted_dependencies.has(name); + } +} |