Skip to content

Commit c08fe8d

Browse files
authoredFeb 28, 2025··
fix(es/minifier): Skip inlining if the referential identity of a function matters (#10123)
**Description:** ```js function a() { if (a.isInit) return; a.isInit = true; console.log('run') } function b() { a(); console.log('after'); } b(); b(); ``` was minified as ```js function b() { (function a() { if (a.isInit) return; a.isInit = true, console.log('run'); })(), console.log('after'); } b(), b(); ``` Which is wrong because the referential identity of `a` is important. **Related issue:** - Closes #10095
1 parent 010ff2a commit c08fe8d

File tree

6 files changed

+208
-107
lines changed

6 files changed

+208
-107
lines changed
 

‎.changeset/cuddly-spiders-begin.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
swc_ecma_minifier: patch
3+
swc_core: patch
4+
---
5+
6+
fix(es/minifier): Skip inlining if a function has recursive access

‎crates/swc_ecma_minifier/src/program_data.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,11 @@ impl VarUsageInfo {
177177
}
178178

179179
pub(crate) fn can_inline_fn_once(&self) -> bool {
180-
self.callee_count > 0
181-
|| !self.executed_multiple_time && (self.is_fn_local || !self.used_in_non_child_fn)
180+
(self.callee_count > 0
181+
|| !self.executed_multiple_time && (self.is_fn_local || !self.used_in_non_child_fn))
182+
&& !(self.used_recursively
183+
&& self.has_property_access
184+
&& self.property_mutation_count != 0)
182185
}
183186

184187
fn initialized(&self) -> bool {

‎crates/swc_ecma_minifier/tests/benches-full/jquery.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -382,10 +382,11 @@
382382
* deleting the oldest entry
383383
*/ function createCache() {
384384
var keys = [];
385-
return function cache(key, value) {
385+
function cache(key, value) {
386386
return keys.push(key + " ") > Expr.cacheLength && // Only keep the most recent entries
387387
delete cache[keys.shift()], cache[key + " "] = value;
388-
};
388+
}
389+
return cache;
389390
}
390391
/**
391392
* Mark a function for special use by Sizzle

‎crates/swc_ecma_minifier/tests/benches-full/lodash.js

+111-96
Original file line numberDiff line numberDiff line change
@@ -2972,7 +2972,7 @@
29722972
* @returns {Function} Returns the new wrapped function.
29732973
*/ function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
29742974
var isAry = 128 & bitmask, isBind = 1 & bitmask, isBindKey = 2 & bitmask, isCurried = 24 & bitmask, isFlip = 512 & bitmask, Ctor = isBindKey ? undefined : createCtor(func);
2975-
return function wrapper() {
2975+
function wrapper() {
29762976
for(var length = arguments.length, args = Array1(length), index = length; index--;)args[index] = arguments[index];
29772977
if (isCurried) var placeholder = getHolder(wrapper), holdersCount = /**
29782978
* Gets the number of `placeholder` occurrences in `array`.
@@ -3006,7 +3006,8 @@
30063006
}
30073007
return array;
30083008
}(args, argPos) : isFlip && length > 1 && args.reverse(), isAry && ary < length && (args.length = ary), this && this !== root && this instanceof wrapper && (fn = Ctor || createCtor(fn)), fn.apply(thisBinding, args);
3009-
};
3009+
}
3010+
return wrapper;
30103011
}
30113012
/**
30123013
* Creates a function like `_.invertBy`.
@@ -3268,17 +3269,29 @@
32683269
(value = source[7]) && (data[7] = value), 128 & srcBitmask && (data[8] = null == data[8] ? source[8] : nativeMin(data[8], source[8])), null == data[9] && (data[9] = source[9]), // Use source `func` and merge bitmasks.
32693270
data[0] = source[0], data[1] = newBitmask;
32703271
}
3271-
}(newData, data), func = newData[0], bitmask = newData[1], thisArg = newData[2], partials = newData[3], holders = newData[4], (arity = newData[9] = newData[9] === undefined ? isBindKey ? 0 : func.length : nativeMax(newData[9] - length, 0)) || !(24 & bitmask) || (bitmask &= -25), bitmask && 1 != bitmask) 8 == bitmask || 16 == bitmask ? (func1 = func, bitmask1 = bitmask, arity1 = arity, Ctor = createCtor(func1), result = function wrapper() {
3272-
for(var length = arguments.length, args = Array1(length), index = length, placeholder = getHolder(wrapper); index--;)args[index] = arguments[index];
3273-
var holders = length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder ? [] : replaceHolders(args, placeholder);
3274-
return (length -= holders.length) < arity1 ? createRecurry(func1, bitmask1, createHybrid, wrapper.placeholder, undefined, args, holders, undefined, undefined, arity1 - length) : apply(this && this !== root && this instanceof wrapper ? Ctor : func1, this, args);
3275-
}) : 32 != bitmask && 33 != bitmask || holders.length ? result = createHybrid.apply(undefined, newData) : (func2 = func, bitmask2 = bitmask, thisArg1 = thisArg, partials1 = partials, isBind = 1 & bitmask2, Ctor1 = createCtor(func2), result = function wrapper() {
3276-
for(var argsIndex = -1, argsLength = arguments.length, leftIndex = -1, leftLength = partials1.length, args = Array1(leftLength + argsLength), fn = this && this !== root && this instanceof wrapper ? Ctor1 : func2; ++leftIndex < leftLength;)args[leftIndex] = partials1[leftIndex];
3272+
}(newData, data), func = newData[0], bitmask = newData[1], thisArg = newData[2], partials = newData[3], holders = newData[4], (arity = newData[9] = newData[9] === undefined ? isBindKey ? 0 : func.length : nativeMax(newData[9] - length, 0)) || !(24 & bitmask) || (bitmask &= -25), bitmask && 1 != bitmask) 8 == bitmask || 16 == bitmask ? result = /**
3273+
* Creates a function that wraps `func` to enable currying.
3274+
*
3275+
* @private
3276+
* @param {Function} func The function to wrap.
3277+
* @param {number} bitmask The bitmask flags. See `createWrap` for more details.
3278+
* @param {number} arity The arity of `func`.
3279+
* @returns {Function} Returns the new wrapped function.
3280+
*/ function(func, bitmask, arity) {
3281+
var Ctor = createCtor(func);
3282+
function wrapper() {
3283+
for(var length = arguments.length, args = Array1(length), index = length, placeholder = getHolder(wrapper); index--;)args[index] = arguments[index];
3284+
var holders = length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder ? [] : replaceHolders(args, placeholder);
3285+
return (length -= holders.length) < arity ? createRecurry(func, bitmask, createHybrid, wrapper.placeholder, undefined, args, holders, undefined, undefined, arity - length) : apply(this && this !== root && this instanceof wrapper ? Ctor : func, this, args);
3286+
}
3287+
return wrapper;
3288+
}(func, bitmask, arity) : 32 != bitmask && 33 != bitmask || holders.length ? result = createHybrid.apply(undefined, newData) : (func1 = func, bitmask1 = bitmask, thisArg1 = thisArg, partials1 = partials, isBind = 1 & bitmask1, Ctor = createCtor(func1), result = function wrapper() {
3289+
for(var argsIndex = -1, argsLength = arguments.length, leftIndex = -1, leftLength = partials1.length, args = Array1(leftLength + argsLength), fn = this && this !== root && this instanceof wrapper ? Ctor : func1; ++leftIndex < leftLength;)args[leftIndex] = partials1[leftIndex];
32773290
for(; argsLength--;)args[leftIndex++] = arguments[++argsIndex];
32783291
return apply(fn, isBind ? thisArg1 : this, args);
32793292
});
3280-
else var func1, bitmask1, arity1, Ctor, func2, bitmask2, thisArg1, partials1, isBind, Ctor1, func3, bitmask3, thisArg2, isBind1, Ctor2, result = (func3 = func, bitmask3 = bitmask, thisArg2 = thisArg, isBind1 = 1 & bitmask3, Ctor2 = createCtor(func3), function wrapper() {
3281-
return (this && this !== root && this instanceof wrapper ? Ctor2 : func3).apply(isBind1 ? thisArg2 : this, arguments);
3293+
else var func1, bitmask1, thisArg1, partials1, isBind, Ctor, func2, bitmask2, thisArg2, isBind1, Ctor1, result = (func2 = func, bitmask2 = bitmask, thisArg2 = thisArg, isBind1 = 1 & bitmask2, Ctor1 = createCtor(func2), function wrapper() {
3294+
return (this && this !== root && this instanceof wrapper ? Ctor1 : func2).apply(isBind1 ? thisArg2 : this, arguments);
32823295
});
32833296
return setWrapToString((data ? baseSetData : setData)(result, newData), func, bitmask);
32843297
}
@@ -4563,6 +4576,93 @@
45634576
return createWrap(key, bitmask, object, partials, holders);
45644577
});
45654578
/**
4579+
* Creates a function that accepts arguments of `func` and either invokes
4580+
* `func` returning its result, if at least `arity` number of arguments have
4581+
* been provided, or returns a function that accepts the remaining `func`
4582+
* arguments, and so on. The arity of `func` may be specified if `func.length`
4583+
* is not sufficient.
4584+
*
4585+
* The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
4586+
* may be used as a placeholder for provided arguments.
4587+
*
4588+
* **Note:** This method doesn't set the "length" property of curried functions.
4589+
*
4590+
* @static
4591+
* @memberOf _
4592+
* @since 2.0.0
4593+
* @category Function
4594+
* @param {Function} func The function to curry.
4595+
* @param {number} [arity=func.length] The arity of `func`.
4596+
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
4597+
* @returns {Function} Returns the new curried function.
4598+
* @example
4599+
*
4600+
* var abc = function(a, b, c) {
4601+
* return [a, b, c];
4602+
* };
4603+
*
4604+
* var curried = _.curry(abc);
4605+
*
4606+
* curried(1)(2)(3);
4607+
* // => [1, 2, 3]
4608+
*
4609+
* curried(1, 2)(3);
4610+
* // => [1, 2, 3]
4611+
*
4612+
* curried(1, 2, 3);
4613+
* // => [1, 2, 3]
4614+
*
4615+
* // Curried with placeholders.
4616+
* curried(1)(_, 3)(2);
4617+
* // => [1, 2, 3]
4618+
*/ function curry(func, arity, guard) {
4619+
arity = guard ? undefined : arity;
4620+
var result = createWrap(func, 8, undefined, undefined, undefined, undefined, undefined, arity);
4621+
return result.placeholder = curry.placeholder, result;
4622+
}
4623+
/**
4624+
* This method is like `_.curry` except that arguments are applied to `func`
4625+
* in the manner of `_.partialRight` instead of `_.partial`.
4626+
*
4627+
* The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
4628+
* builds, may be used as a placeholder for provided arguments.
4629+
*
4630+
* **Note:** This method doesn't set the "length" property of curried functions.
4631+
*
4632+
* @static
4633+
* @memberOf _
4634+
* @since 3.0.0
4635+
* @category Function
4636+
* @param {Function} func The function to curry.
4637+
* @param {number} [arity=func.length] The arity of `func`.
4638+
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
4639+
* @returns {Function} Returns the new curried function.
4640+
* @example
4641+
*
4642+
* var abc = function(a, b, c) {
4643+
* return [a, b, c];
4644+
* };
4645+
*
4646+
* var curried = _.curryRight(abc);
4647+
*
4648+
* curried(3)(2)(1);
4649+
* // => [1, 2, 3]
4650+
*
4651+
* curried(2, 3)(1);
4652+
* // => [1, 2, 3]
4653+
*
4654+
* curried(1, 2, 3);
4655+
* // => [1, 2, 3]
4656+
*
4657+
* // Curried with placeholders.
4658+
* curried(3)(1, _)(2);
4659+
* // => [1, 2, 3]
4660+
*/ function curryRight(func, arity, guard) {
4661+
arity = guard ? undefined : arity;
4662+
var result = createWrap(func, 16, undefined, undefined, undefined, undefined, undefined, arity);
4663+
return result.placeholder = curryRight.placeholder, result;
4664+
}
4665+
/**
45664666
* Creates a debounced function that delays invoking `func` until after `wait`
45674667
* milliseconds have elapsed since the last time the debounced function was
45684668
* invoked. The debounced function comes with a `cancel` method to cancel
@@ -6596,92 +6696,7 @@
65966696
*/ function(prototype, properties) {
65976697
var result = baseCreate(prototype);
65986698
return null == properties ? result : baseAssign(result, properties);
6599-
}, lodash.curry = /**
6600-
* Creates a function that accepts arguments of `func` and either invokes
6601-
* `func` returning its result, if at least `arity` number of arguments have
6602-
* been provided, or returns a function that accepts the remaining `func`
6603-
* arguments, and so on. The arity of `func` may be specified if `func.length`
6604-
* is not sufficient.
6605-
*
6606-
* The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
6607-
* may be used as a placeholder for provided arguments.
6608-
*
6609-
* **Note:** This method doesn't set the "length" property of curried functions.
6610-
*
6611-
* @static
6612-
* @memberOf _
6613-
* @since 2.0.0
6614-
* @category Function
6615-
* @param {Function} func The function to curry.
6616-
* @param {number} [arity=func.length] The arity of `func`.
6617-
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
6618-
* @returns {Function} Returns the new curried function.
6619-
* @example
6620-
*
6621-
* var abc = function(a, b, c) {
6622-
* return [a, b, c];
6623-
* };
6624-
*
6625-
* var curried = _.curry(abc);
6626-
*
6627-
* curried(1)(2)(3);
6628-
* // => [1, 2, 3]
6629-
*
6630-
* curried(1, 2)(3);
6631-
* // => [1, 2, 3]
6632-
*
6633-
* curried(1, 2, 3);
6634-
* // => [1, 2, 3]
6635-
*
6636-
* // Curried with placeholders.
6637-
* curried(1)(_, 3)(2);
6638-
* // => [1, 2, 3]
6639-
*/ function curry(func, arity, guard) {
6640-
arity = guard ? undefined : arity;
6641-
var result = createWrap(func, 8, undefined, undefined, undefined, undefined, undefined, arity);
6642-
return result.placeholder = curry.placeholder, result;
6643-
}, lodash.curryRight = /**
6644-
* This method is like `_.curry` except that arguments are applied to `func`
6645-
* in the manner of `_.partialRight` instead of `_.partial`.
6646-
*
6647-
* The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
6648-
* builds, may be used as a placeholder for provided arguments.
6649-
*
6650-
* **Note:** This method doesn't set the "length" property of curried functions.
6651-
*
6652-
* @static
6653-
* @memberOf _
6654-
* @since 3.0.0
6655-
* @category Function
6656-
* @param {Function} func The function to curry.
6657-
* @param {number} [arity=func.length] The arity of `func`.
6658-
* @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
6659-
* @returns {Function} Returns the new curried function.
6660-
* @example
6661-
*
6662-
* var abc = function(a, b, c) {
6663-
* return [a, b, c];
6664-
* };
6665-
*
6666-
* var curried = _.curryRight(abc);
6667-
*
6668-
* curried(3)(2)(1);
6669-
* // => [1, 2, 3]
6670-
*
6671-
* curried(2, 3)(1);
6672-
* // => [1, 2, 3]
6673-
*
6674-
* curried(1, 2, 3);
6675-
* // => [1, 2, 3]
6676-
*
6677-
* // Curried with placeholders.
6678-
* curried(3)(1, _)(2);
6679-
* // => [1, 2, 3]
6680-
*/ function curryRight(func, arity, guard) {
6681-
arity = guard ? undefined : arity;
6682-
var result = createWrap(func, 16, undefined, undefined, undefined, undefined, undefined, arity);
6683-
return result.placeholder = curryRight.placeholder, result;
6684-
}, lodash.debounce = debounce, lodash.defaults = defaults, lodash.defaultsDeep = defaultsDeep, lodash.defer = defer, lodash.delay = delay, lodash.difference = difference, lodash.differenceBy = differenceBy, lodash.differenceWith = differenceWith, lodash.drop = /**
6699+
}, lodash.curry = curry, lodash.curryRight = curryRight, lodash.debounce = debounce, lodash.defaults = defaults, lodash.defaultsDeep = defaultsDeep, lodash.defer = defer, lodash.delay = delay, lodash.difference = difference, lodash.differenceBy = differenceBy, lodash.differenceWith = differenceWith, lodash.drop = /**
66856700
* Creates a slice of `array` with `n` elements dropped from the beginning.
66866701
*
66876702
* @static

‎crates/swc_ecma_minifier/tests/exec.rs

+75
Original file line numberDiff line numberDiff line change
@@ -11375,3 +11375,78 @@ fn isssue_9498() {
1137511375
",
1137611376
)
1137711377
}
11378+
11379+
#[test]
11380+
fn issue_10095() {
11381+
run_exec_test(
11382+
"
11383+
function module() {
11384+
function a() {
11385+
if (a.isInit) return;
11386+
a.isInit = true;
11387+
11388+
console.log('run')
11389+
}
11390+
11391+
function b() {
11392+
a();
11393+
11394+
console.log('after');
11395+
}
11396+
11397+
b();
11398+
b();
11399+
}
11400+
11401+
module();
11402+
",
11403+
r#"{
11404+
"defaults": true,
11405+
"arguments": false,
11406+
"arrows": false,
11407+
"booleans": false,
11408+
"booleans_as_integers": false,
11409+
"collapse_vars": false,
11410+
"comparisons": false,
11411+
"computed_props": false,
11412+
"conditionals": false,
11413+
"dead_code": false,
11414+
"directives": false,
11415+
"drop_console": false,
11416+
"drop_debugger": false,
11417+
"evaluate": false,
11418+
"expression": false,
11419+
"hoist_funs": false,
11420+
"hoist_props": false,
11421+
"hoist_vars": false,
11422+
"if_return": false,
11423+
"join_vars": false,
11424+
"keep_classnames": false,
11425+
"keep_fargs": false,
11426+
"keep_fnames": false,
11427+
"keep_infinity": false,
11428+
"loops": false,
11429+
"negate_iife": false,
11430+
"properties": false,
11431+
"reduce_funcs": false,
11432+
"reduce_vars": false,
11433+
"side_effects": false,
11434+
"switches": false,
11435+
"typeofs": false,
11436+
"unsafe": false,
11437+
"unsafe_arrows": false,
11438+
"unsafe_comps": false,
11439+
"unsafe_Function": false,
11440+
"unsafe_math": false,
11441+
"unsafe_symbols": false,
11442+
"unsafe_methods": false,
11443+
"unsafe_proto": false,
11444+
"unsafe_regexp": false,
11445+
"unsafe_undefined": false,
11446+
"unused": false,
11447+
"const_to_let": false,
11448+
"pristine_globals": false
11449+
}"#,
11450+
false,
11451+
);
11452+
}

‎crates/swc_ecma_minifier/tests/fixture/next/react-pdf-renderer/output.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -16123,6 +16123,13 @@
1612316123
throw t;
1612416124
}
1612516125
}
16126+
function I() {
16127+
for(var e = arguments.length, t = Array(e), r = 0; r < e; r++)t[r] = arguments[r];
16128+
S.apply(void 0, [
16129+
I,
16130+
t.length
16131+
].concat(t));
16132+
}
1612616133
E.throws = function e(t) {
1612716134
for(var r = arguments.length, n = Array(r > 1 ? r - 1 : 0), i = 1; i < r; i++)n[i - 1] = arguments[i];
1612816135
F.apply(void 0, [
@@ -16176,13 +16183,7 @@
1617616183
}
1617716184
throw n;
1617816185
}
16179-
}, E.strict = b(function e() {
16180-
for(var t = arguments.length, r = Array(t), n = 0; n < t; n++)r[n] = arguments[n];
16181-
S.apply(void 0, [
16182-
e,
16183-
r.length
16184-
].concat(r));
16185-
}, E, {
16186+
}, E.strict = b(I, E, {
1618616187
equal: E.strictEqual,
1618716188
deepEqual: E.deepStrictEqual,
1618816189
notEqual: E.notStrictEqual,

0 commit comments

Comments
 (0)
Please sign in to comment.