mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 19:21:11 +08:00
fix: 字符串面 GetIterator 尊重用户 @@iterator 覆盖
String 臂此前对原始串与装箱串一律走码元步进、忽略用户覆盖的 @@iterator。改为按 GetMethod 语义以值自身为 receiver 读 @@iterator (原始串落 String.prototype、装箱串读自身→原型链),默认迭代器 自别名即 @@iterator 槽本身、以绑定层捕获的函数指针比较走快速 路径避免递归回本臂;覆盖则调用、receiver 为值。peek 对非字符串 原始值先装箱再读 @@iterator;Iterator.from 增 GetIteratorFlattenable 前置校验,非字符串原始值非对象 → TypeError(不装箱)。
This commit is contained in:
@@ -72,6 +72,18 @@ pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
oxide_builtins::string::string_symbol_iterator::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
);
|
||||
// 捕获默认迭代器函数对象指针写入 world:String 臂覆盖判定以此做指针比较。
|
||||
// 此时槽为初始数据属性(用户覆盖尚未可能),读回即默认函数本体。
|
||||
let iter_key = oxide_types::private_key::make_well_known_symbol_key(0);
|
||||
if let Some(pos) = core.shape_forge().lookup_position(proto.shape_id(), iter_key) {
|
||||
let v = proto.get_prop_at(pos);
|
||||
if v.is_object() {
|
||||
session
|
||||
.builtin_world()
|
||||
.string_default_iterator
|
||||
.set(v.as_js_object_ptr() as *const JsObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 全局 String 槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec),
|
||||
// 无槽时开新槽;描述符非枚举(规范 { writable:true, enumerable:false, configurable:true })。
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
//! `Iterator.from` / `Array.from` 的原始值迭代器面钉:String 臂经 GetMethod
|
||||
//! 以原始值 receiver 读 `@@iterator`(用户覆盖 getter 观测 `typeof this`),
|
||||
//! peek 对非字符串原始值装箱读 `@@iterator`,`Iterator.from` 对非字符串原始值
|
||||
//! 非对象按 GetIteratorFlattenable 抛 TypeError(区别于 `Array.from` 的装箱路径)。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn run(source: &str) -> Result<JsValue, String> {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&Arc::new(module))
|
||||
}
|
||||
|
||||
/// 断言 IIFE 求值为 `true`(把多断言收敛为布尔,规避 JsValue Display 只暴露
|
||||
/// number/bool 的限制)。
|
||||
fn assert_truthy(name: &str, source: &str) {
|
||||
let result = run(source).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
assert!(result.as_bool(), "{name}: expected truthy, got {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_from_string_getter_sees_raw_string() {
|
||||
// 原始字符串 receiver:getter 的 typeof this 为 'string'(不装箱);
|
||||
// 包装字符串对象 receiver 为 'object'。
|
||||
assert_truthy(
|
||||
"string getter receiver",
|
||||
"(function(){
|
||||
var orig = String.prototype[Symbol.iterator];
|
||||
var obs;
|
||||
Object.defineProperty(String.prototype, Symbol.iterator, {
|
||||
get(){ 'use strict'; obs = typeof this; return orig; }
|
||||
});
|
||||
Iterator.from('');
|
||||
var raw = obs;
|
||||
Iterator.from(new String(''));
|
||||
var boxed = obs;
|
||||
return raw === 'string' && boxed === 'object';
|
||||
})()",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_number_boxes_to_read_number_iterator() {
|
||||
// Array.from(5) 经 GetIterator 装箱后读 Number.prototype[Symbol.iterator]。
|
||||
assert_truthy(
|
||||
"Array.from(5)",
|
||||
"(function(){
|
||||
Number.prototype[Symbol.iterator] = function*(){
|
||||
var i = 0, t = this >>> 0;
|
||||
while (i < t) { yield i; ++i; }
|
||||
};
|
||||
var r = Array.from(5);
|
||||
return r.length === 5 && r[0] === 0 && r[1] === 1 && r[4] === 4;
|
||||
})()",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_from_number_throws_type_error() {
|
||||
// Iterator.from(5):非字符串原始值非对象,GetIteratorFlattenable 抛 TypeError
|
||||
// (不装箱),即便 Number.prototype 已覆盖。
|
||||
assert_truthy(
|
||||
"Iterator.from(5) throws",
|
||||
"(function(){
|
||||
Number.prototype[Symbol.iterator] = function*(){ yield 1; };
|
||||
try { Iterator.from(5); return false; }
|
||||
catch (e) { return e instanceof TypeError; }
|
||||
})()",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_iterator_from_boxed_number() {
|
||||
// new Number(5) 是对象:Iterator.from 经 GetIteratorFlattenable 走覆盖迭代器。
|
||||
assert_truthy(
|
||||
"Array.from(Iterator.from(new Number(5)))",
|
||||
"(function(){
|
||||
Number.prototype[Symbol.iterator] = function*(){
|
||||
var i = 0, t = this >>> 0;
|
||||
while (i < t) { yield i; ++i; }
|
||||
};
|
||||
var r = Array.from(Iterator.from(new Number(5)));
|
||||
return r.length === 5 && r[0] === 0 && r[4] === 4;
|
||||
})()",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_iterator_from_string_yields_chars() {
|
||||
// Iterator.from 原始字符串走 String 臂码元快速路径,产出字符序列。
|
||||
assert_truthy(
|
||||
"Array.from(Iterator.from('string'))",
|
||||
"(function(){
|
||||
var r = Array.from(Iterator.from('string'));
|
||||
return r.length === 6 && r[0] === 's' && r[5] === 'g';
|
||||
})()",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_from_string_default_no_override() {
|
||||
// 无覆盖回归:默认字符串迭代器仍走码元快速路径,逐个字符产出。
|
||||
assert_truthy(
|
||||
"Array.from('abc') default",
|
||||
"(function(){
|
||||
var r = Array.from('abc');
|
||||
return r.length === 3 && r[0] === 'a' && r[1] === 'b' && r[2] === 'c';
|
||||
})()",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_of_string_default_no_override() {
|
||||
// 无覆盖回归:for-of 字符串仍逐字符迭代(默认快速路径未被破坏)。
|
||||
assert_truthy(
|
||||
"for-of 'hix' default",
|
||||
"(function(){
|
||||
var n = 0;
|
||||
for (var c of 'hix') { n++; }
|
||||
return n === 3;
|
||||
})()",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user