feat(ecmascript): improve may_have_side_effects for instanceof (#9365)

This commit is contained in:
sapphi-red committed 2025-02-26 08:16:30 +00:00
1 parent bff83c96a2
commit 35e5ca9628
2 files changed
+77 -7

No files matched your search

@@ -1,7 +1,8 @@
use oxc_ast::ast::*;
use crate::{
is_global_reference::IsGlobalReference, to_numeric::ToNumeric, to_primitive::ToPrimitive,
constant_evaluation::DetermineValueType, is_global_reference::IsGlobalReference,
to_numeric::ToNumeric, to_primitive::ToPrimitive,
};
/// Returns true if subtree changes application state.
@@ -121,8 +122,26 @@ impl MayHaveSideEffects for BinaryExpression<'_> {
self.left.may_have_side_effects(is_global_reference)
|| self.right.may_have_side_effects(is_global_reference)
}
BinaryOperator::In | BinaryOperator::Instanceof => {
// instanceof and in can throw `TypeError`
BinaryOperator::Instanceof => {
// When the following conditions are met, instanceof won't throw `TypeError`.
// - the right hand side is a known global reference which is a function
// - the left hand side is not a proxy
if let Expression::Identifier(right_ident) = &self.right {
let name = right_ident.name.as_str();
// Any known global non-constructor functions can be allowed here.
// But because non-constructor functions are not likely to be used, we ignore them.
if is_known_global_constructor(name)
&& is_global_reference.is_global_reference(right_ident) == Some(true)
&& !self.left.value_type(is_global_reference).is_undetermined()
{
return false;
}
}
// instanceof can throw `TypeError`
true
}
BinaryOperator::In => {
// in can throw `TypeError`
true
}
BinaryOperator::Addition => {
@@ -200,6 +219,56 @@ impl MayHaveSideEffects for BinaryExpression<'_> {
}
}
/// Whether the name matches any known global constructors.
///
/// <https://tc39.es/ecma262/multipage/global-object.html#sec-constructor-properties-of-the-global-object>
fn is_known_global_constructor(name: &str) -> bool {
// technically, we need to exclude the constructors that are not supported by the target
matches!(
name,
"AggregateError"
| "Array"
| "ArrayBuffer"
| "BigInt"
| "BigInt64Array"
| "BitUint64Array"
| "Boolean"
| "DataView"
| "Date"
| "Error"
| "EvalError"
| "FinalizationRegistry"
| "Float32Array"
| "Float64Array"
| "Function"
| "Int8Array"
| "Int16Array"
| "Int32Array"
| "Iterator"
| "Map"
| "Number"
| "Object"
| "Promise"
| "Proxy"
| "RangeError"
| "ReferenceError"
| "RegExp"
| "Set"
| "SharedArrayBuffer"
| "String"
| "Symbol"
| "SyntaxError"
| "TypeError"
| "Uint8Array"
| "Uint8ClampedArray"
| "Uint16Array"
| "Uint32Array"
| "URIError"
| "WeakMap"
| "WeakSet"
)
}
impl MayHaveSideEffects for LogicalExpression<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
self.left.may_have_side_effects(is_global_reference)
@@ -526,13 +526,14 @@ fn test_binary_expressions() {
test("1n % 0n", true); // `%` throws an error when the right operand is zero
test("0n >>> 1n", true); // `>>>` throws an error even when both operands are bigint
test("[] instanceof 1", true); // throws an error
test("[] instanceof { [Symbol.hasInstance]() { throw 'foo' } }", true);
test_with_global_variables("[] instanceof Object", vec!["Object".to_string()], false);
test_with_global_variables("a instanceof Object", vec!["Object".to_string()], true); // a maybe a proxy that has a side effectful "getPrototypeOf" trap
// b maybe not a object
// b maybe a proxy that has a side effectful "has" trap
test("a in b", true);
// b maybe not a function
// b[Symbol.hasInstance] may have a side effect
// a maybe a proxy that has a side effectful "getPrototypeOf" trap
test("a instanceof b", true);
}
#[test]