aboutsummaryrefslogtreecommitdiff
path: root/src/renamer.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/renamer.zig')
-rw-r--r--src/renamer.zig520
1 files changed, 509 insertions, 11 deletions
diff --git a/src/renamer.zig b/src/renamer.zig
index bce2d3030..88dcb769c 100644
--- a/src/renamer.zig
+++ b/src/renamer.zig
@@ -12,19 +12,19 @@ const C = bun.C;
const std = @import("std");
const Ref = @import("./ast/base.zig").Ref;
const logger = @import("bun").logger;
+const JSLexer = @import("./js_lexer.zig");
-// This is...poorly named
-// It does not rename
-// It merely names
-pub const Renamer = struct {
+pub const NoOpRenamer = struct {
symbols: js_ast.Symbol.Map,
source: *const logger.Source,
- pub fn init(symbols: js_ast.Symbol.Map, source: *const logger.Source) Renamer {
- return Renamer{ .symbols = symbols, .source = source };
+ pub fn init(symbols: js_ast.Symbol.Map, source: *const logger.Source) NoOpRenamer {
+ return NoOpRenamer{ .symbols = symbols, .source = source };
}
- pub fn nameForSymbol(renamer: *Renamer, ref: Ref) string {
+ pub const originalName = nameForSymbol;
+
+ pub fn nameForSymbol(renamer: *NoOpRenamer, ref: Ref) string {
if (ref.isSourceContentsSlice()) {
return renamer.source.contents[ref.sourceIndex() .. ref.sourceIndex() + ref.innerIndex()];
}
@@ -37,11 +37,509 @@ pub const Renamer = struct {
Global.panic("Invalid symbol {s} in {s}", .{ ref, renamer.source.path.text });
}
}
+
+ pub fn toRenamer(this: *NoOpRenamer) Renamer {
+ return .{
+ .NoOpRenamer = this,
+ };
+ }
+};
+
+pub const Renamer = union(enum) {
+ NumberRenamer: *NumberRenamer,
+ NoOpRenamer: *NoOpRenamer,
+
+ pub fn symbols(this: Renamer) js_ast.Symbol.Map {
+ return switch (this) {
+ inline else => |r| r.symbols,
+ };
+ }
+
+ pub fn nameForSymbol(renamer: Renamer, ref: Ref) string {
+ return switch (renamer) {
+ inline else => |r| r.nameForSymbol(ref),
+ };
+ }
+
+ pub fn originalName(renamer: Renamer, ref: Ref) ?string {
+ return switch (renamer) {
+ inline else => |r| r.originalName(ref),
+ };
+ }
+
+ pub fn deinit(renamer: Renamer) void {
+ switch (renamer) {
+ .NumberRenamer => |r| r.deinit(),
+ else => {},
+ }
+ }
+};
+
+pub const NumberRenamer = struct {
+ symbols: js_ast.Symbol.Map,
+ names: []bun.BabyList(string) = &.{},
+ allocator: std.mem.Allocator,
+ temp_allocator: std.mem.Allocator,
+ number_scope_pool: bun.HiveArray(NumberScope, 128).Fallback,
+ arena: std.heap.ArenaAllocator,
+ root: NumberScope = .{},
+ name_stack_fallback: std.heap.StackFallbackAllocator(512) = undefined,
+ name_temp_allocator: std.mem.Allocator = undefined,
+
+ pub fn deinit(self: *NumberRenamer) void {
+ self.allocator.free(self.names);
+ self.root.deinit(self.temp_allocator);
+ self.arena.deinit();
+ }
+
+ pub fn toRenamer(this: *NumberRenamer) Renamer {
+ return .{
+ .NumberRenamer = this,
+ };
+ }
+
+ pub fn originalName(r: *NumberRenamer, ref: Ref) string {
+ if (ref.isSourceContentsSlice()) {
+ unreachable;
+ }
+
+ const resolved = r.symbols.follow(ref);
+ return r.symbols.getConst(resolved).?.original_name;
+ }
+
+ pub fn assignName(r: *NumberRenamer, scope: *NumberScope, input_ref: Ref) void {
+ const ref = r.symbols.follow(input_ref);
+
+ // Don't rename the same symbol more than once
+ var inner: *bun.BabyList(string) = &r.names[ref.sourceIndex()];
+ if (inner.len > ref.innerIndex() and inner.at(ref.innerIndex()).len > 0) return;
+
+ // Don't rename unbound symbols, symbols marked as reserved names, labels, or private names
+ const symbol = r.symbols.get(ref).?;
+ if (symbol.slotNamespace() != .default) {
+ return;
+ }
+
+ r.name_stack_fallback.fixed_buffer_allocator.end_index = 0;
+ switch (scope.findUnusedName(r.allocator, r.name_temp_allocator, symbol.original_name)) {
+ .renamed => |name| {
+ const new_len = @max(inner.len, ref.innerIndex() + 1);
+ if (inner.cap <= new_len) {
+ const prev_cap = inner.len;
+ inner.ensureUnusedCapacity(r.allocator, new_len - prev_cap) catch unreachable;
+ const to_write = inner.ptr[prev_cap..inner.cap];
+ @memset(std.mem.sliceAsBytes(to_write).ptr, 0, std.mem.sliceAsBytes(to_write).len);
+ }
+ inner.len = new_len;
+ inner.mut(ref.innerIndex()).* = name;
+ },
+ .no_collision => {},
+ }
+ }
+
+ pub fn init(
+ allocator: std.mem.Allocator,
+ temp_allocator: std.mem.Allocator,
+ symbols: js_ast.Symbol.Map,
+ root_names: bun.StringHashMapUnmanaged(u32),
+ ) !*NumberRenamer {
+ var renamer = try allocator.create(NumberRenamer);
+ renamer.* = NumberRenamer{
+ .symbols = symbols,
+ .allocator = allocator,
+ .temp_allocator = temp_allocator,
+ .names = try allocator.alloc(bun.BabyList(string), symbols.symbols_for_source.len),
+ .number_scope_pool = undefined,
+ .arena = std.heap.ArenaAllocator.init(temp_allocator),
+ };
+ renamer.name_stack_fallback = .{
+ .buffer = undefined,
+ .fallback_allocator = renamer.arena.allocator(),
+ .fixed_buffer_allocator = undefined,
+ };
+ renamer.name_temp_allocator = renamer.name_stack_fallback.get();
+ renamer.number_scope_pool = bun.HiveArray(NumberScope, 128).Fallback.init(renamer.arena.allocator());
+ renamer.root.name_counts = root_names;
+ if (comptime Environment.allow_assert) {
+ if (std.os.getenv("BUN_DUMP_SYMBOLS") != null)
+ symbols.dump();
+ }
+
+ @memset(std.mem.sliceAsBytes(renamer.names).ptr, 0, std.mem.sliceAsBytes(renamer.names).len);
+
+ return renamer;
+ }
+
+ pub fn assignNamesRecursive(r: *NumberRenamer, scope: *js_ast.Scope, source_index: u32, parent: ?*NumberScope, sorted: *std.ArrayList(u32)) void {
+ var s = r.number_scope_pool.get();
+ s.* = NumberScope{
+ .parent = parent,
+ .name_counts = .{},
+ };
+ defer {
+ s.deinit(r.temp_allocator);
+ r.number_scope_pool.put(s);
+ }
+
+ assignNamesRecursiveWithNumberScope(r, s, scope, source_index, sorted);
+ }
+
+ fn assignNamesInScope(
+ r: *NumberRenamer,
+ s: *NumberScope,
+ scope: *js_ast.Scope,
+ source_index: u32,
+ sorted: *std.ArrayList(u32),
+ ) void {
+ {
+ sorted.clearRetainingCapacity();
+ sorted.ensureUnusedCapacity(scope.members.count()) catch unreachable;
+ sorted.items.len = scope.members.count();
+ var remaining = sorted.items;
+ var value_iter = scope.members.valueIterator();
+ while (value_iter.next()) |value_ref| {
+ if (comptime Environment.allow_assert)
+ std.debug.assert(!value_ref.ref.isSourceContentsSlice());
+
+ remaining[0] = value_ref.ref.innerIndex();
+ remaining = remaining[1..];
+ }
+ std.debug.assert(remaining.len == 0);
+ std.sort.sort(u32, sorted.items, void{}, std.sort.asc(u32));
+
+ for (sorted.items) |inner_index| {
+ r.assignName(s, Ref.init(@intCast(Ref.Int, inner_index), source_index, false));
+ }
+ }
+
+ for (scope.generated.slice()) |ref| {
+ r.assignName(s, ref);
+ }
+ }
+
+ pub fn assignNamesRecursiveWithNumberScope(r: *NumberRenamer, initial_scope: *NumberScope, scope_: *js_ast.Scope, source_index: u32, sorted: *std.ArrayList(u32)) void {
+ var s = initial_scope;
+ var scope = scope_;
+ defer if (s != initial_scope) {
+ s.deinit(r.temp_allocator);
+ r.number_scope_pool.put(s);
+ };
+
+ // Ignore function argument scopes
+ if (scope.kind == .function_args and scope.children.len == 1) {
+ scope = scope.children.ptr[0];
+ std.debug.assert(scope.kind == .function_body);
+ }
+
+ while (true) {
+ if (scope.members.count() > 0 or scope.generated.len > 0) {
+ var new_child_scope = r.number_scope_pool.get();
+ new_child_scope.* = .{
+ .parent = s,
+ .name_counts = .{},
+ };
+ s = new_child_scope;
+
+ r.assignNamesInScope(s, scope, source_index, sorted);
+ }
+
+ if (scope.children.len == 1) {
+ scope = scope.children.ptr[0];
+ if (scope.kind == .function_args and scope.children.len == 1) {
+ scope = scope.children.ptr[0];
+ std.debug.assert(scope.kind == .function_body);
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Symbols in child scopes may also have to be renamed to avoid conflicts
+ for (scope.children.slice()) |child| {
+ r.assignNamesRecursiveWithNumberScope(s, child, source_index, sorted);
+ }
+ }
+
+ pub fn addTopLevelSymbol(r: *NumberRenamer, ref: Ref) void {
+ r.assignName(&r.root, ref);
+ }
+
+ pub fn addTopLevelDeclaredSymbols(r: *NumberRenamer, declared_symbols: js_ast.DeclaredSymbol.List) void {
+ var decls = declared_symbols;
+ js_ast.DeclaredSymbol.forEachTopLevelSymbol(&decls, r, addTopLevelSymbol);
+ }
+
+ pub fn nameForSymbol(renamer: *NumberRenamer, ref: Ref) string {
+ if (ref.isSourceContentsSlice()) {
+ bun.unreachablePanic("Unexpected unbound symbol!\n{any}", .{ref});
+ }
+
+ const resolved = renamer.symbols.follow(ref);
+
+ const source_index = resolved.sourceIndex();
+ const inner_index = resolved.innerIndex();
+
+ const renamed_list = renamer.names[source_index];
+
+ if (renamed_list.len > inner_index) {
+ const renamed = renamed_list.at(inner_index).*;
+ if (renamed.len > 0) {
+ return renamed;
+ }
+ }
+
+ return renamer.symbols.symbols_for_source.at(source_index).at(inner_index).original_name;
+ }
+
+ pub const NumberScope = struct {
+ parent: ?*NumberScope = null,
+ name_counts: bun.StringHashMapUnmanaged(u32) = .{},
+
+ pub fn deinit(this: *NumberScope, allocator: std.mem.Allocator) void {
+ this.name_counts.deinit(allocator);
+ this.* = undefined;
+ }
+
+ pub const NameUse = union(enum) {
+ unused: void,
+ same_scope: u32,
+ used: void,
+
+ pub fn find(this: *NumberScope, name: []const u8) NameUse {
+ // This version doesn't allocate
+ if (comptime Environment.allow_assert)
+ std.debug.assert(JSLexer.isIdentifier(name));
+
+ // avoid rehashing the same string over for each scope
+ const ctx = bun.StringHashMapContext.pre(name);
+
+ if (this.name_counts.getAdapted(name, ctx)) |count| {
+ return .{ .same_scope = count };
+ }
+
+ var s: ?*NumberScope = this.parent;
+
+ while (s) |scope| : (s = scope.parent) {
+ if (scope.name_counts.containsAdapted(name, ctx)) {
+ return .{ .used = void{} };
+ }
+ }
+
+ return .{ .unused = void{} };
+ }
+ };
+
+ const UnusedName = union(enum) {
+ no_collision: void,
+ renamed: string,
+ };
+
+ /// Caller must use an arena allocator
+ pub fn findUnusedName(this: *NumberScope, allocator: std.mem.Allocator, temp_allocator: std.mem.Allocator, input_name: []const u8) UnusedName {
+ var name = bun.MutableString.ensureValidIdentifier(input_name, temp_allocator) catch unreachable;
+
+ switch (NameUse.find(this, name)) {
+ .unused => {},
+ else => |use| {
+ var tries: u32 = if (use == .used)
+ 1
+ else
+ // To avoid O(n^2) behavior, the number must start off being the number
+ // that we used last time there was a collision with this name. Otherwise
+ // if there are many collisions with the same name, each name collision
+ // would have to increment the counter past all previous name collisions
+ // which is a O(n^2) time algorithm. Only do this if this symbol comes
+ // from the same scope as the previous one since sibling scopes can reuse
+ // the same name without problems.
+ use.same_scope;
+
+ const prefix = name;
+
+ tries += 1;
+
+ var mutable_name = MutableString.initEmpty(temp_allocator);
+ mutable_name.growIfNeeded(prefix.len + 4) catch unreachable;
+ mutable_name.appendSlice(prefix) catch unreachable;
+ mutable_name.appendInt(tries) catch unreachable;
+
+ switch (NameUse.find(this, mutable_name.toOwnedSliceLeaky())) {
+ .unused => {
+ name = mutable_name.toOwnedSliceLeaky();
+
+ if (use == .same_scope) {
+ var existing = this.name_counts.getOrPut(allocator, prefix) catch unreachable;
+ if (!existing.found_existing) {
+ if (strings.eqlLong(input_name, prefix, true)) {
+ existing.key_ptr.* = input_name;
+ } else {
+ existing.key_ptr.* = allocator.dupe(u8, prefix) catch unreachable;
+ }
+ }
+
+ existing.value_ptr.* = tries;
+ }
+ },
+ else => |cur_use| {
+ while (true) {
+ mutable_name.resetTo(prefix.len);
+ mutable_name.appendInt(tries) catch unreachable;
+
+ tries += 1;
+
+ switch (NameUse.find(this, mutable_name.toOwnedSliceLeaky())) {
+ .unused => {
+ if (cur_use == .same_scope) {
+ var existing = this.name_counts.getOrPut(allocator, prefix) catch unreachable;
+ if (!existing.found_existing) {
+ if (strings.eqlLong(input_name, prefix, true)) {
+ existing.key_ptr.* = input_name;
+ } else {
+ existing.key_ptr.* = allocator.dupe(u8, prefix) catch unreachable;
+ }
+ }
+
+ existing.value_ptr.* = tries;
+ }
+
+ name = mutable_name.toOwnedSliceLeaky();
+ break;
+ },
+ else => {},
+ }
+ }
+ },
+ }
+ },
+ }
+
+ // Each name starts off with a count of 1 so that the first collision with
+ // "name" is called "name2"
+ if (strings.eqlLong(name, input_name, true)) {
+ this.name_counts.putNoClobber(allocator, input_name, 1) catch unreachable;
+ return .{ .no_collision = {} };
+ }
+
+ name = allocator.dupe(u8, name) catch unreachable;
+
+ this.name_counts.putNoClobber(allocator, name, 1) catch unreachable;
+ return .{ .renamed = name };
+ }
+ };
};
-pub const DisabledRenamer = struct {
- pub fn init(_: js_ast.Symbol.Map) DisabledRenamer {}
- pub inline fn nameForSymbol(_: *Renamer, _: js_ast.Ref) string {
- @compileError("DisabledRunner called");
+pub const ExportRenamer = struct {
+ string_buffer: bun.MutableString,
+ used: bun.StringHashMap(u32),
+
+ pub fn init(allocator: std.mem.Allocator) ExportRenamer {
+ return ExportRenamer{
+ .string_buffer = MutableString.initEmpty(allocator),
+ .used = bun.StringHashMap(u32).init(allocator),
+ };
+ }
+
+ pub fn clearRetainingCapacity(this: *ExportRenamer) void {
+ this.used.clearRetainingCapacity();
+ this.string_buffer.reset();
+ }
+
+ pub fn deinit(this: *ExportRenamer) void {
+ this.used.deinit();
+ this.string_buffer.deinit();
+ }
+
+ pub fn nextRenamedName(this: *ExportRenamer, input: []const u8) string {
+ var entry = this.used.getOrPut(input) catch unreachable;
+ var tries: u32 = 1;
+ if (entry.found_existing) {
+ while (true) {
+ this.string_buffer.reset();
+ var writer = this.string_buffer.writer();
+ writer.print("{s}{d}", .{ input, tries }) catch unreachable;
+ tries += 1;
+ var attempt = this.string_buffer.toOwnedSliceLeaky();
+ entry = this.used.getOrPut(attempt) catch unreachable;
+ if (!entry.found_existing) {
+ const to_use = this.string_buffer.allocator.dupe(u8, attempt) catch unreachable;
+ entry.key_ptr.* = to_use;
+ entry.value_ptr.* = tries;
+
+ entry = this.used.getOrPut(input) catch unreachable;
+ entry.value_ptr.* = tries;
+ return to_use;
+ }
+ }
+ } else {
+ entry.value_ptr.* = tries;
+ }
+
+ return entry.key_ptr.*;
}
};
+
+pub fn computeInitialReservedNames(
+ allocator: std.mem.Allocator,
+) !bun.StringHashMapUnmanaged(u32) {
+ var names = bun.StringHashMapUnmanaged(u32){};
+
+ const extras = .{
+ "Promise",
+ "Require",
+ };
+
+ try names.ensureTotalCapacityContext(
+ allocator,
+ @truncate(u32, JSLexer.Keywords.keys().len + JSLexer.StrictModeReservedWords.keys().len + 1 + extras.len),
+ bun.StringHashMapContext{},
+ );
+
+ for (JSLexer.Keywords.keys()) |keyword| {
+ names.putAssumeCapacity(keyword, 1);
+ }
+
+ for (JSLexer.StrictModeReservedWords.keys()) |keyword| {
+ names.putAssumeCapacity(keyword, 1);
+ }
+
+ inline for (comptime extras) |extra| {
+ names.putAssumeCapacity(extra, 1);
+ }
+
+ return names;
+}
+
+pub fn computeReservedNamesForScope(
+ scope: *js_ast.Scope,
+ symbols: *const js_ast.Symbol.Map,
+ names_: *bun.StringHashMapUnmanaged(u32),
+ allocator: std.mem.Allocator,
+) void {
+ var names = names_.*;
+ defer names_.* = names;
+
+ var member_iter = scope.members.valueIterator();
+ while (member_iter.next()) |member| {
+ const symbol = symbols.get(member.ref).?;
+ if (symbol.kind == .unbound or symbol.must_not_be_renamed) {
+ names.put(allocator, symbol.original_name, 1) catch unreachable;
+ }
+ }
+
+ for (scope.generated.slice()) |ref| {
+ const symbol = symbols.get(ref).?;
+ if (symbol.kind == .unbound or symbol.must_not_be_renamed) {
+ names.put(allocator, symbol.original_name, 1) catch unreachable;
+ }
+ }
+
+ // If there's a direct "eval" somewhere inside the current scope, continue
+ // traversing down the scope tree until we find it to get all reserved names
+ if (scope.contains_direct_eval) {
+ for (scope.children.slice()) |child| {
+ if (child.contains_direct_eval) {
+ names_.* = names;
+ computeReservedNamesForScope(child, symbols, &names, allocator);
+ }
+ }
+ }
+}