mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
(For internal tracking: fixes STAB-1471, STAB-1472, STAB-1473, STAB-1474, STAB-1475, STAB-1476, STAB-1480, STAB-1481) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
211 lines
7.0 KiB
Zig
211 lines
7.0 KiB
Zig
//! Basic utilities for working with memory and objects.
|
|
|
|
/// Allocates memory for a value of type `T` using the provided allocator, and initializes the
|
|
/// memory with `value`.
|
|
///
|
|
/// If `allocator` is `bun.default_allocator`, this will internally use `bun.tryNew` to benefit from
|
|
/// the added assertions.
|
|
pub fn create(comptime T: type, allocator: std.mem.Allocator, value: T) bun.OOM!*T {
|
|
if ((comptime Environment.allow_assert) and isDefault(allocator)) {
|
|
return bun.tryNew(T, value);
|
|
}
|
|
const ptr = try allocator.create(T);
|
|
ptr.* = value;
|
|
return ptr;
|
|
}
|
|
|
|
/// Frees memory previously allocated by `create`.
|
|
///
|
|
/// The memory must have been allocated by the `create` function in this namespace, not
|
|
/// directly by `allocator.create`.
|
|
pub fn destroy(allocator: std.mem.Allocator, ptr: anytype) void {
|
|
if ((comptime Environment.allow_assert) and isDefault(allocator)) {
|
|
bun.destroy(ptr);
|
|
} else {
|
|
allocator.destroy(ptr);
|
|
}
|
|
}
|
|
|
|
/// Default-initializes a value of type `T`.
|
|
///
|
|
/// This method tries the following, in order:
|
|
///
|
|
/// * `.initDefault()`, if a method with that name exists
|
|
/// * `.init()`, if a method with that name exists
|
|
/// * `.{}`, otherwise
|
|
pub fn initDefault(comptime T: type) T {
|
|
return if (comptime std.meta.hasFn(T, "initDefault"))
|
|
.initDefault()
|
|
else if (comptime std.meta.hasFn(T, "init"))
|
|
.init()
|
|
else
|
|
.{};
|
|
}
|
|
|
|
/// Returns true if `T` should not be required to have a `deinit` method.
|
|
///
|
|
/// This method is primarily for external types where a `deinit` method can't be added.
|
|
/// For other types, prefer adding a `deinit` method or adding `pub const deinit = void;` if
|
|
/// possible.
|
|
fn exemptedFromDeinit(comptime T: type) bool {
|
|
return switch (T) {
|
|
std.mem.Allocator => true,
|
|
else => {
|
|
_ = T.deinit; // no deinit method? add one, set to void, or add an exemption
|
|
return false;
|
|
},
|
|
};
|
|
}
|
|
|
|
fn deinitIsVoid(comptime T: type) bool {
|
|
return switch (@TypeOf(T.deinit)) {
|
|
type => T.deinit == void,
|
|
void => true,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// Calls `deinit` on `ptr_or_slice`, or on every element of `ptr_or_slice`, if the pointer points
|
|
/// to a struct or tagged union.
|
|
///
|
|
/// This function first does the following:
|
|
///
|
|
/// * If `ptr_or_slice` is a single-item pointer of type `*T`:
|
|
/// - If `T` is a struct or tagged union, calls `ptr_or_slice.deinit()`
|
|
/// - If `T` is an optional, checks if `ptr_or_slice` points to a non-null value, and if so,
|
|
/// calls `bun.memory.deinit` with a pointer to the payload.
|
|
/// * If `ptr_or_slice` is a slice, for each element of the slice, calls `bun.memory.deinit` with
|
|
/// a pointer to the element.
|
|
///
|
|
/// Then, if `ptr_or_slice` is non-const, this function also sets all memory referenced by the
|
|
/// pointer to `undefined`.
|
|
///
|
|
/// This method does not free `ptr_or_slice` itself.
|
|
pub fn deinit(ptr_or_slice: anytype) void {
|
|
const PtrType = @TypeOf(ptr_or_slice);
|
|
const ptr_info = @typeInfo(PtrType);
|
|
switch (comptime ptr_info.pointer.size) {
|
|
.slice => {
|
|
for (ptr_or_slice) |*elem| {
|
|
deinit(elem);
|
|
}
|
|
return;
|
|
},
|
|
.one => {},
|
|
else => @compileError("unsupported pointer type: " ++ @typeName(PtrType)),
|
|
}
|
|
|
|
const Child = ptr_info.pointer.child;
|
|
const mutable = !ptr_info.pointer.is_const;
|
|
defer {
|
|
if (comptime mutable) {
|
|
ptr_or_slice.* = undefined;
|
|
}
|
|
}
|
|
|
|
switch (comptime @typeInfo(Child)) {
|
|
.void, .bool, .int, .float, .pointer, .comptime_float, .comptime_int => return,
|
|
.undefined, .null, .error_set, .@"enum", .vector => return,
|
|
.array => {
|
|
for (ptr_or_slice) |*elem| {
|
|
deinit(elem);
|
|
}
|
|
return;
|
|
},
|
|
.@"struct" => {},
|
|
.optional => {
|
|
if (ptr_or_slice.*) |*payload| {
|
|
deinit(payload);
|
|
}
|
|
return;
|
|
},
|
|
.error_union => {
|
|
if (ptr_or_slice.*) |*payload| {
|
|
deinit(payload);
|
|
} else |_| {}
|
|
return;
|
|
},
|
|
.@"union" => |u| {
|
|
if (comptime u.tag_type == null) {
|
|
@compileError("cannot deinit an untagged union: " ++ @typeName(Child));
|
|
}
|
|
},
|
|
.type, .noreturn, .@"fn", .@"opaque", .frame, .@"anyframe", .enum_literal => {
|
|
@compileError("unsupported type for deinit: " ++ @typeName(Child));
|
|
},
|
|
}
|
|
|
|
if (comptime !exemptedFromDeinit(Child) and !deinitIsVoid(Child)) {
|
|
ptr_or_slice.deinit();
|
|
}
|
|
}
|
|
|
|
/// Rebase a slice from one memory buffer to another buffer.
|
|
///
|
|
/// Given a slice which points into a memory buffer with base `old_base`, return
|
|
/// a slice which points to the same offset in a new memory buffer with base
|
|
/// `new_base`, preserving the length of the slice.
|
|
///
|
|
///
|
|
/// ```
|
|
/// const old_base = [6]u8{};
|
|
/// assert(@ptrToInt(&old_base) == 0x32);
|
|
///
|
|
/// 0x32 0x33 0x34 0x35 0x36 0x37
|
|
/// old_base |????|????|????|????|????|????|
|
|
/// ^
|
|
/// |<-- slice --->|
|
|
///
|
|
/// const new_base = [6]u8{};
|
|
/// assert(@ptrToInt(&new_base) == 0x74);
|
|
/// const output = rebaseSlice(slice, old_base, new_base)
|
|
///
|
|
/// 0x74 0x75 0x76 0x77 0x78 0x79
|
|
/// new_base |????|????|????|????|????|????|
|
|
/// ^
|
|
/// |<-- output -->|
|
|
/// ```
|
|
pub fn rebaseSlice(slice: []const u8, old_base: [*]const u8, new_base: [*]const u8) []const u8 {
|
|
const offset = @intFromPtr(slice.ptr) - @intFromPtr(old_base);
|
|
return new_base[offset..][0..slice.len];
|
|
}
|
|
|
|
/// Removes the sentinel from a sentinel-terminated slice or many-item pointer. The resulting
|
|
/// non-sentinel-terminated slice can be freed with `allocator.free`.
|
|
///
|
|
/// `ptr` must be `[:x]T` or `[*:x]T`, or their const equivalents, and it must have been allocated
|
|
/// by `allocator`.
|
|
///
|
|
/// Most allocators will perform this operation without allocating any memory, but unlike a simple
|
|
/// cast, this function will not cause issues with allocators that need to know the exact size of
|
|
/// the allocation to free it.
|
|
pub fn dropSentinel(ptr: anytype, allocator: std.mem.Allocator) blk: {
|
|
var info = @typeInfo(@TypeOf(ptr));
|
|
info.pointer.size = .slice;
|
|
info.pointer.sentinel_ptr = null;
|
|
break :blk bun.OOM!@Type(info);
|
|
} {
|
|
const info = @typeInfo(@TypeOf(ptr)).pointer;
|
|
const Child = info.child;
|
|
if (comptime info.sentinel_ptr == null) {
|
|
@compileError("pointer must have sentinel");
|
|
}
|
|
|
|
const slice = switch (comptime info.size) {
|
|
.many => std.mem.span(ptr),
|
|
.slice => ptr,
|
|
else => @compileError("only slices and many-item pointers are supported"),
|
|
};
|
|
|
|
if (allocator.remap(@constCast(slice), slice.len)) |new| return new;
|
|
defer allocator.free(slice);
|
|
return allocator.dupe(Child, slice);
|
|
}
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const isDefault = bun.allocators.isDefault;
|