修复异步回调状态与函数别名覆盖
区分异步回调的可达注册点与闭包外层状态执行点 按函数对象身份共享 call、apply、bind 自有成员覆盖 补充异步时序、函数别名和独立 bind 对象回归 同步技术方案与项目决策记录
This commit is contained in:
+251
-18
@@ -1083,6 +1083,8 @@ struct NamedJavascriptFunctionRange {
|
||||
member_static: bool,
|
||||
externally_callable: bool,
|
||||
invocations: Vec<usize>,
|
||||
// (registration/call site used for reachability, execution point used for outer state reads)
|
||||
invocation_state_positions: Vec<(usize, usize)>,
|
||||
synchronous_invocations: Vec<usize>,
|
||||
}
|
||||
|
||||
@@ -1150,6 +1152,7 @@ impl JavascriptFunctionDefinitionCollector {
|
||||
member_static,
|
||||
externally_callable: false,
|
||||
invocations: Vec::new(),
|
||||
invocation_state_positions: Vec::new(),
|
||||
synchronous_invocations: Vec::new(),
|
||||
});
|
||||
}
|
||||
@@ -1343,7 +1346,7 @@ struct JavascriptMethodReceiverOwnerCollector<'a> {
|
||||
callable_events:
|
||||
BTreeMap<JavascriptSymbolId, Vec<JavascriptAliasEvent<JavascriptCallableValue>>>,
|
||||
member_callable_events:
|
||||
BTreeMap<(JavascriptSymbolId, String), Vec<JavascriptAliasEvent<JavascriptCallableValue>>>,
|
||||
BTreeMap<(JavascriptSymbolId, String), Vec<JavascriptMemberCallableEvent>>,
|
||||
super_aliases: Vec<((usize, usize), JavascriptSymbolId, usize)>,
|
||||
}
|
||||
|
||||
@@ -1372,9 +1375,17 @@ enum JavascriptCallableValue {
|
||||
Bound {
|
||||
source: JavascriptSymbolId,
|
||||
captured_at: usize,
|
||||
object_span: (usize, usize),
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct JavascriptMemberCallableEvent {
|
||||
// Member receiver evaluation precedes the assignment RHS and must keep that object identity.
|
||||
receiver_captured_at: usize,
|
||||
event: JavascriptAliasEvent<JavascriptCallableValue>,
|
||||
}
|
||||
|
||||
impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
fn referenced_symbol(
|
||||
&self,
|
||||
@@ -1500,6 +1511,7 @@ impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
return vec![JavascriptCallableValue::Bound {
|
||||
source,
|
||||
captured_at,
|
||||
object_span: (call.span.start as usize, call.span.end as usize),
|
||||
}];
|
||||
}
|
||||
bind.object()
|
||||
@@ -1562,6 +1574,7 @@ impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
receiver: JavascriptSymbolId,
|
||||
name: String,
|
||||
expression: &JavascriptExpression<'_>,
|
||||
receiver_captured_at: usize,
|
||||
captured_at: usize,
|
||||
position: usize,
|
||||
) {
|
||||
@@ -1580,19 +1593,29 @@ impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
.entry((receiver, name))
|
||||
.or_default();
|
||||
if values.is_empty() {
|
||||
events.push(JavascriptAliasEvent {
|
||||
position,
|
||||
scope: javascript_alias_scope_at(self.ranges, position),
|
||||
value: None,
|
||||
conditional,
|
||||
events.push(JavascriptMemberCallableEvent {
|
||||
receiver_captured_at,
|
||||
event: JavascriptAliasEvent {
|
||||
position,
|
||||
scope: javascript_alias_scope_at(self.ranges, position),
|
||||
value: None,
|
||||
conditional,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
events.extend(values.into_iter().map(|value| JavascriptAliasEvent {
|
||||
position,
|
||||
scope: javascript_alias_scope_at(self.ranges, position),
|
||||
value: Some(value),
|
||||
conditional,
|
||||
}));
|
||||
events.extend(
|
||||
values
|
||||
.into_iter()
|
||||
.map(|value| JavascriptMemberCallableEvent {
|
||||
receiver_captured_at,
|
||||
event: JavascriptAliasEvent {
|
||||
position,
|
||||
scope: javascript_alias_scope_at(self.ranges, position),
|
||||
value: Some(value),
|
||||
conditional,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1684,7 +1707,7 @@ impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
) -> (
|
||||
BTreeMap<JavascriptSymbolId, Vec<JavascriptAliasEvent<JavascriptReceiverOwnerValue>>>,
|
||||
BTreeMap<JavascriptSymbolId, Vec<JavascriptAliasEvent<JavascriptCallableValue>>>,
|
||||
BTreeMap<(JavascriptSymbolId, String), Vec<JavascriptAliasEvent<JavascriptCallableValue>>>,
|
||||
BTreeMap<(JavascriptSymbolId, String), Vec<JavascriptMemberCallableEvent>>,
|
||||
Vec<((usize, usize), JavascriptSymbolId, usize)>,
|
||||
) {
|
||||
let super_owners = self.super_aliases.into_iter().collect();
|
||||
@@ -1764,6 +1787,7 @@ impl<'a> VisitJavascript<'a> for JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
receiver,
|
||||
name,
|
||||
&assignment.right,
|
||||
assignment.span.start as usize,
|
||||
assignment.right.span().start as usize,
|
||||
assignment.span.end as usize,
|
||||
);
|
||||
@@ -1809,7 +1833,7 @@ struct JavascriptFunctionInvocationCollector<'a, 'b> {
|
||||
callable_events:
|
||||
BTreeMap<JavascriptSymbolId, Vec<JavascriptAliasEvent<JavascriptCallableValue>>>,
|
||||
member_callable_events:
|
||||
BTreeMap<(JavascriptSymbolId, String), Vec<JavascriptAliasEvent<JavascriptCallableValue>>>,
|
||||
BTreeMap<(JavascriptSymbolId, String), Vec<JavascriptMemberCallableEvent>>,
|
||||
super_aliases: Vec<((usize, usize), JavascriptSymbolId, usize)>,
|
||||
marker: std::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
@@ -1982,9 +2006,28 @@ fn javascript_invocation_effect_position(span: oxc_span::Span) -> usize {
|
||||
}
|
||||
|
||||
impl JavascriptFunctionInvocationCollector<'_, '_> {
|
||||
fn asynchronous_state_position(&self, registration_at: usize) -> usize {
|
||||
// Browser callbacks run after the current synchronous job/scope completes. Keeping this
|
||||
// separate from the registration call site prevents both registration-time closure
|
||||
// freezing and synchronous propagation of callback writes.
|
||||
self.context_ranges
|
||||
.iter()
|
||||
.filter(|range| range.start <= registration_at && registration_at < range.end)
|
||||
.min_by_key(|range| range.end - range.start)
|
||||
.map_or(self.content.len(), |range| range.end.saturating_sub(1))
|
||||
}
|
||||
|
||||
fn push_invocations(&mut self, indices: Vec<usize>, at: usize, synchronous: bool) {
|
||||
let state_at = if synchronous {
|
||||
at
|
||||
} else {
|
||||
self.asynchronous_state_position(at)
|
||||
};
|
||||
for index in indices {
|
||||
self.ranges[index].invocations.push(at);
|
||||
self.ranges[index]
|
||||
.invocation_state_positions
|
||||
.push((at, state_at));
|
||||
if synchronous {
|
||||
self.ranges[index].synchronous_invocations.push(at);
|
||||
}
|
||||
@@ -2020,6 +2063,7 @@ impl JavascriptFunctionInvocationCollector<'_, '_> {
|
||||
JavascriptCallableValue::Bound {
|
||||
source,
|
||||
captured_at,
|
||||
..
|
||||
} => {
|
||||
let override_is_conditional_or_absent = self
|
||||
.member_callable_override_indices_for_key(
|
||||
@@ -2326,6 +2370,76 @@ impl JavascriptFunctionInvocationCollector<'_, '_> {
|
||||
Some((receiver, member.static_property_name()?.to_string()))
|
||||
}
|
||||
|
||||
fn callable_object_identities_for_value(
|
||||
&self,
|
||||
value: &JavascriptCallableValue,
|
||||
visiting: &mut BTreeSet<JavascriptSymbolId>,
|
||||
) -> BTreeSet<(usize, usize)> {
|
||||
match value {
|
||||
JavascriptCallableValue::Direct(span) => [*span].into_iter().collect(),
|
||||
JavascriptCallableValue::Alias {
|
||||
source,
|
||||
captured_at,
|
||||
} => self.callable_object_identities_for_symbol(*source, *captured_at, visiting),
|
||||
JavascriptCallableValue::Member {
|
||||
receiver,
|
||||
name,
|
||||
captured_at,
|
||||
} => self
|
||||
.receiver_owners_for_symbol(*receiver, *captured_at, &mut BTreeSet::new())
|
||||
.into_iter()
|
||||
.flat_map(|owner| self.method_indices_for_owner(owner, name, &mut BTreeSet::new()))
|
||||
.map(|index| (self.ranges[index].start, self.ranges[index].end))
|
||||
.collect(),
|
||||
JavascriptCallableValue::Bound { object_span, .. } => {
|
||||
[*object_span].into_iter().collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn callable_object_identities_for_symbol(
|
||||
&self,
|
||||
symbol_id: JavascriptSymbolId,
|
||||
at: usize,
|
||||
visiting: &mut BTreeSet<JavascriptSymbolId>,
|
||||
) -> BTreeSet<(usize, usize)> {
|
||||
if !visiting.insert(symbol_id) {
|
||||
return BTreeSet::new();
|
||||
}
|
||||
let binding_start = self.scoping.symbol_span(symbol_id).start as usize;
|
||||
let mut identities = self
|
||||
.binding_ranges
|
||||
.get(&binding_start)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|index| (self.ranges[*index].start, self.ranges[*index].end))
|
||||
.collect::<BTreeSet<_>>();
|
||||
if let Some(events) = self.callable_events.get(&symbol_id) {
|
||||
let selection = javascript_alias_event_indices_at(
|
||||
events,
|
||||
self.context_ranges,
|
||||
self.content,
|
||||
self.literal_false_ranges,
|
||||
self.conditional_ranges,
|
||||
at,
|
||||
&mut BTreeSet::new(),
|
||||
);
|
||||
if selection.resolved {
|
||||
if !selection.conditional {
|
||||
identities.clear();
|
||||
}
|
||||
for index in selection.indices {
|
||||
if let Some(value) = events[index].value.as_ref() {
|
||||
identities
|
||||
.extend(self.callable_object_identities_for_value(value, visiting));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visiting.remove(&symbol_id);
|
||||
identities
|
||||
}
|
||||
|
||||
fn member_callable_override_indices(
|
||||
&self,
|
||||
member: &oxc_ast::ast::MemberExpression<'_>,
|
||||
@@ -2340,9 +2454,34 @@ impl JavascriptFunctionInvocationCollector<'_, '_> {
|
||||
key: &(JavascriptSymbolId, String),
|
||||
at: usize,
|
||||
) -> Option<(Vec<usize>, bool)> {
|
||||
let events = self.member_callable_events.get(key)?;
|
||||
let receiver_identities =
|
||||
self.callable_object_identities_for_symbol(key.0, at, &mut BTreeSet::new());
|
||||
let events = self
|
||||
.member_callable_events
|
||||
.iter()
|
||||
.filter(|((_, name), _)| name == &key.1)
|
||||
.flat_map(|((receiver, _), events)| {
|
||||
events.iter().filter_map(|member_event| {
|
||||
let matches_receiver = if receiver_identities.is_empty() {
|
||||
*receiver == key.0
|
||||
} else {
|
||||
self.callable_object_identities_for_symbol(
|
||||
*receiver,
|
||||
member_event.receiver_captured_at,
|
||||
&mut BTreeSet::new(),
|
||||
)
|
||||
.iter()
|
||||
.any(|identity| receiver_identities.contains(identity))
|
||||
};
|
||||
matches_receiver.then(|| member_event.event.clone())
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if events.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let selection = javascript_alias_event_indices_at(
|
||||
events,
|
||||
&events,
|
||||
self.context_ranges,
|
||||
self.content,
|
||||
self.literal_false_ranges,
|
||||
@@ -2671,6 +2810,7 @@ fn named_javascript_function_ranges(content: &str) -> NamedJavascriptFunctionRan
|
||||
for _ in 0..16 {
|
||||
for range in &mut definitions.ranges {
|
||||
range.invocations.clear();
|
||||
range.invocation_state_positions.clear();
|
||||
range.synchronous_invocations.clear();
|
||||
}
|
||||
let mut invocations = JavascriptFunctionInvocationCollector {
|
||||
@@ -2693,6 +2833,8 @@ fn named_javascript_function_ranges(content: &str) -> NamedJavascriptFunctionRan
|
||||
for range in &mut definitions.ranges {
|
||||
range.invocations.sort_unstable();
|
||||
range.invocations.dedup();
|
||||
range.invocation_state_positions.sort_unstable();
|
||||
range.invocation_state_positions.dedup();
|
||||
range.synchronous_invocations.sort_unstable();
|
||||
range.synchronous_invocations.dedup();
|
||||
}
|
||||
@@ -2702,6 +2844,7 @@ fn named_javascript_function_ranges(content: &str) -> NamedJavascriptFunctionRan
|
||||
.zip(&context_ranges)
|
||||
.all(|(current, previous)| {
|
||||
current.invocations == previous.invocations
|
||||
&& current.invocation_state_positions == previous.invocation_state_positions
|
||||
&& current.synchronous_invocations == previous.synchronous_invocations
|
||||
});
|
||||
context_ranges = definitions.ranges.clone();
|
||||
@@ -5478,7 +5621,7 @@ fn javascript_alias_event_indices_at<T>(
|
||||
.iter()
|
||||
.find(|range| range.start == scope.0 && range.end == scope.1)
|
||||
{
|
||||
for invocation in &range.invocations {
|
||||
for (invocation, state_at) in &range.invocation_state_positions {
|
||||
if (range.start..range.end).contains(invocation)
|
||||
|| javascript_position_is_in_literal_false_block(
|
||||
content,
|
||||
@@ -5494,7 +5637,7 @@ fn javascript_alias_event_indices_at<T>(
|
||||
content,
|
||||
literal_false_ranges,
|
||||
conditional_ranges,
|
||||
*invocation,
|
||||
*state_at,
|
||||
visiting,
|
||||
);
|
||||
state.indices.extend(outer.indices);
|
||||
@@ -11139,6 +11282,31 @@ mod javascript_projection_reachability_tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asynchronous_callbacks_read_outer_aliases_after_registration() {
|
||||
for content in [
|
||||
"function oldRun() { return import('./async-before-registration.mjs'); } function nextRun() { return import('./async-after-registration.mjs'); } let run = oldRun; function callback() { run(); } setTimeout(callback, 0); run = nextRun;",
|
||||
"function oldRun() { return import('./nested-async-before-registration.mjs'); } function nextRun() { return import('./nested-async-after-registration.mjs'); } function setup() { let run = oldRun; function callback() { run(); } setTimeout(callback, 0); run = nextRun; } setup();",
|
||||
] {
|
||||
let ranges = named_javascript_function_ranges(content);
|
||||
let before = content
|
||||
.find("before-registration.mjs")
|
||||
.expect("find pre-registration callable");
|
||||
let after = content
|
||||
.find("after-registration.mjs")
|
||||
.expect("find post-registration callable");
|
||||
|
||||
assert!(
|
||||
!javascript_position_is_reachable(content, &ranges, before),
|
||||
"an asynchronous callback must not freeze an outer alias at registration",
|
||||
);
|
||||
assert!(
|
||||
javascript_position_is_reachable(content, &ranges, after),
|
||||
"an asynchronous callback must observe the outer alias after synchronous setup finishes",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_object_overrides_disable_call_apply_and_bind_intrinsics() {
|
||||
for member in ["call", "apply", "bind"] {
|
||||
@@ -11192,6 +11360,71 @@ mod javascript_projection_reachability_tests {
|
||||
"a conditional own override must preserve both possible call targets: {source}",
|
||||
);
|
||||
}
|
||||
|
||||
for member in ["call", "apply", "bind"] {
|
||||
for (installation, invocation) in [
|
||||
(
|
||||
format!("alias.{member} = overrideMethod;"),
|
||||
format!("target.{member}(null);"),
|
||||
),
|
||||
(
|
||||
format!("target.{member} = overrideMethod;"),
|
||||
format!("alias.{member}(null);"),
|
||||
),
|
||||
] {
|
||||
let content = format!(
|
||||
"function target() {{ return import('./aliased-intrinsic-decoy-{member}.mjs'); }} function overrideMethod() {{ return import('./aliased-override-{member}.mjs'); }} const alias = target; {installation} {invocation}"
|
||||
);
|
||||
let ranges = named_javascript_function_ranges(&content);
|
||||
let decoy = content
|
||||
.find(&format!("./aliased-intrinsic-decoy-{member}.mjs"))
|
||||
.expect("find aliased intrinsic decoy import");
|
||||
let override_import = content
|
||||
.find(&format!("./aliased-override-{member}.mjs"))
|
||||
.expect("find aliased function-object override import");
|
||||
|
||||
assert!(
|
||||
!javascript_position_is_reachable(&content, &ranges, decoy),
|
||||
"an own {member} override installed through an alias must disable the intrinsic",
|
||||
);
|
||||
assert!(
|
||||
javascript_position_is_reachable(&content, &ranges, override_import),
|
||||
"function aliases must share the same own {member} override",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let bound_identity = "function target() { return import('./bound-object-target.mjs'); } function overrideMethod() { return import('./bound-object-override.mjs'); } const bound = target.bind(null); bound.call = overrideMethod; target.call(null);";
|
||||
let ranges = named_javascript_function_ranges(bound_identity);
|
||||
assert!(javascript_position_is_reachable(
|
||||
bound_identity,
|
||||
&ranges,
|
||||
bound_identity.find("./bound-object-target.mjs").unwrap(),
|
||||
));
|
||||
assert!(
|
||||
!javascript_position_is_reachable(
|
||||
bound_identity,
|
||||
&ranges,
|
||||
bound_identity.find("./bound-object-override.mjs").unwrap(),
|
||||
),
|
||||
"a bound function must retain an object identity independent from its source",
|
||||
);
|
||||
|
||||
let late_override = "function target() { return import('./before-alias-override.mjs'); } function overrideMethod() { return import('./late-alias-override.mjs'); } const alias = target; target.call(null); alias.call = overrideMethod;";
|
||||
let ranges = named_javascript_function_ranges(late_override);
|
||||
assert!(javascript_position_is_reachable(
|
||||
late_override,
|
||||
&ranges,
|
||||
late_override.find("./before-alias-override.mjs").unwrap(),
|
||||
));
|
||||
assert!(
|
||||
!javascript_position_is_reachable(
|
||||
late_override,
|
||||
&ranges,
|
||||
late_override.find("./late-alias-override.mjs").unwrap(),
|
||||
),
|
||||
"a later override through an alias must not rewrite an earlier intrinsic call",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -5993,4 +5993,4 @@
|
||||
- JavaScript / ESM callee 与终止顺序补充:identifier callee 和 `new C(args)` 的 constructor / instance owner 在实参前冻结,invocation effect 保留在实参之后;callable assignment 到 RHS 完成后才生效,`start = start()` 继续调用旧值。`return / throw` 表达式中的 assignment / call 先执行,截断点取表达式之后的 AST statement end;函数体使用 Oxc body span,不从默认参数或解构参数中的首个 `{` 猜测。未知 guard clause 后续与 `catch` 体一律按 conditional effect 合并旧状态。
|
||||
- JavaScript callable 分支与参数快照补充:conditional expression 必须在 test 求值完成后,分别于 consequent / alternate 自身起点冻结 callable identity;受控 callback 参数按该参数自身起点解析,前置参数产生的 alias 副作用先于后续 callback identity 生效,callback 的执行边仍保留在注册调用完成位置。
|
||||
- JavaScript / ESM live binding 投影补充:被选 export root 的直接顶层 assignment 及其 RHS 依赖必须与原声明共同投影,覆盖 `export let x; x = impl`、导出对象成员安装和 class prototype 安装;assignment target 以 semantic root symbol 归属,函数体写入、嵌套控制流和无关 root 写入不得因同名文本进入投影。共享 declaration 的写入按原始源码位置合并,继续参与 canonical 重命名、循环去重和既有 `2 MiB / 32 MiB` 门禁;全部依赖声明必须先于延后的初始化写入输出,不能因 projection traversal 产生 TDZ。
|
||||
- JavaScript callback 时序、内建覆盖与 class expression owner 补充:受控异步 callback 的注册位置只作为可达根和闭包状态捕获点,不再作为函数体副作用的同步提交点;数组字面量上的已知迭代 callback 继续按同步执行传播外层 alias 变化。函数对象自有 `.call / .apply / .bind` assignment 形成成员 callable 状态,存在覆盖时禁止回退到 Function.prototype 语义;`new (class { ... })` 赋给局部变量时冻结 class expression 的 instance owner,使后续实例方法调用保持可达。
|
||||
- JavaScript callback 时序、内建覆盖与 class expression owner 补充:受控异步 callback 的注册位置只建立可达调用边,闭包读取的外层 alias 状态选取注册所在同步作用域收尾点,不能冻结在注册点;函数体写副作用仍不得同步提交到注册调用末尾。数组字面量上的已知迭代 callback 继续按同步执行传播外层 alias 变化。函数对象自有 `.call / .apply / .bind` assignment 按函数对象身份形成成员 callable 状态,普通函数别名共享同一对象覆盖,`.bind()` 结果保持独立对象身份;存在覆盖时禁止回退到 Function.prototype 语义。`new (class { ... })` 赋给局部变量时冻结 class expression 的 instance owner,使后续实例方法调用保持可达。
|
||||
|
||||
@@ -836,7 +836,7 @@ game-project/
|
||||
- dynamic import demand 必须以 source 与 import occurrence position 联合作为身份,同 source 的可达裸 import 不得继承不可达 occurrence 的 export。动态 namespace 在首段 export 之后继续保留完整 member path,声明 initializer 与后续赋值式 `await import` 都必须绑定 semantic symbol。named / namespace 成员作为回调参数、对象解构 alias、实例 alias 与下游 wrapper 时继续按真实调用 span 传播 demand。constructor、`new Game().method()` 和实例 alias 分别形成精确成员根,`this.method()` 只匹配相同 class / object owner;顶层声明位置只使用 Oxc span,禁止以源码文本 `find` 反查。受控 callback API 名严格大小写敏感,调用图只读取真实 callback 参数位:timer、microtask、RAF、Promise 与数组迭代 API 取第一个参数,`addEventListener` 取第二个参数,delay、initial value、event type、options 和其它额外参数都不得建立执行边。恒假分支区间由单次函数可达性 analysis 预计算、排序合并并借用二分索引,不得使用 thread-local 完整源码 key 或在每个位置查询时复制区间、重扫整份源码。
|
||||
- imported member 只有作为受控 callback API 的参数时才建立执行 demand;日志、注册元数据等普通传参不能被当作调用。普通 member alias 必须同时支持声明 initializer 与后续赋值,`new ns.Game()` 的完整 constructor path 继续传播到实例 alias。局部对象、class、实例、`this` 和 `super` 的方法调用统一按 semantic receiver owner 匹配,禁止按末级方法名跨 owner 选中同名 decoy。
|
||||
- member alias、局部 receiver 与动态 namespace 的 assignment 必须保留赋值位置和 enclosing function scope;函数体内的使用必须按真实 invocation position 选择当时已生效的事件,多次调用跨过赋值边界时合并全部可能 owner,恒假分支、未调用函数或调用之后的赋值不能倒灌覆盖更早使用点。`receiver = source` 必须冻结赋值完成时的 source owner,source 后续重赋值不能反向改写 receiver;identifier callee 与 `new C(args)` 的 constructor / instance owner 在参数求值前冻结,真实 invocation effect 仍在参数求值后生效,callable assignment 只在 RHS 完成后替换旧值,因此 `start = start()` 必须调用旧 callable。`return / throw` 的表达式先完整执行,随后才截断后续事件;函数体和截断位置统一使用 Oxc AST body / statement span,不能被默认参数或解构参数中的 `{}` 误导。未知 guard clause 后的语句与 `catch` 体赋值都保留执行和跳过状态;调用事件继续按 JavaScript 的 callee、参数 / RHS、内层调用、外层调用 / 赋值顺序生效,`outer(inner())` 与 `receiver = mutate()` 不得按 AST 先序颠倒副作用。未知条件、循环以及 `switch case/default` 内赋值保留执行与跳过状态,conditional expression 保留全部可解析 owner,无法解析的确定重赋值显式 invalidation;已调用函数对外层 alias 的最终副作用按调用位置回写,但普通函数体内无条件 `return / throw` 之后的事件必须截断。被导出的 function、class/object member 还必须把模块初始化完成视为潜在外部调用时点,使声明之后生效的顶层赋值进入 demand,同时保留此前本地调用的旧状态。动态依赖传播继续以原始 owner 模块 AST span 为权威,不得因投影重排声明或省略独立赋值语句而重算出相反 alias。class owner 必须继续区分 static / instance,class expression 与实例化 alias 进入相同 receiver 图,`super` 的 parent owner 在 class 定义位置冻结。constructor、`.call()` 与 `.apply()` 都是实际 invocation;具名 function expression 只保留外层 binding 对应的单一 range,未被受控 API 执行的 inline function / arrow 保持不可达。恒真 / 恒假关键字只按 JavaScript 大小写识别,局部或参数遮蔽的 `undefined` 不得当作恒假;恒假分支识别必须先用 parser trivia 屏蔽注释和非执行 literal。callback API 只接受 semantic 未解析的已知全局调度函数或 AST 可证明的 literal array / dynamic import 调用;用户定义、导入或遮蔽的同名 `setTimeout / map / then` 不能触发参数执行推断。
|
||||
- `new` 必须覆盖显式 `super()`、隐式 derived constructor 和直接 class expression;class expression 经 `new` 赋给局部变量时必须冻结 instance owner,未 override 的 instance / static 方法沿冻结的 `extends` 链查找。普通嵌套 function 拥有独立 `this`,只有 arrow 词法继承外层 owner。声明后通过 assignment 安装的 function / arrow、本地 function alias 和 `.bind()` 结果都必须保留 callable identity;只有未被函数对象自有成员覆盖的 Function.prototype `.call()` / `.apply()` / `.bind()` 采用内建 callable 语义,自有覆盖和业务对象同名方法都按实际成员 callable 解析。callable conditional expression 必须合并 consequent / alternate 的全部可调用身份;声明式和后续赋值式 `api.run.bind(api)` 都在绑定时冻结成员 owner 与方法名。受控 callback 识别穿透 callee 与 receiver 外层括号;Promise `.then` 的 fulfilled / rejected 两个参数都是 callback,`.catch` / `.finally` 仍仅取第一个。timer、RAF、microtask、Promise 与事件监听 callback 只建立异步可达根,不得把函数体对外层 alias 的副作用同步提交到注册调用末尾;数组字面量的已知迭代 API 才按同步 callback 传播函数体副作用。视觉资产启发式允许使用 ASCII 小写副本查找路径、`drawImage` 与图片变量,但函数调用图、semantic binding 和可达性必须始终解析原始大小写 JavaScript,禁止把 `MainLoop` 与 `mainloop` 合并。Canvas 视觉门必须复用同一组受 256 文件、累计源码 `2 MiB` 与投影处理 `32 MiB` 限制的 inline / external module 加载、链接和投影结果,并逐个原始大小写 unit 独立关联资产路径、图片变量与可达 `drawImage`;`<script type="module">`、本地 `<script type="module" src>` 及其真实可达依赖可以形成证据,未链接文件、恒假动态依赖、未调用函数、跨 unit 拼接和仅出现在 HTML 文本中的路径不得借用其它 unit 的绘制调用过门。
|
||||
- `new` 必须覆盖显式 `super()`、隐式 derived constructor 和直接 class expression;class expression 经 `new` 赋给局部变量时必须冻结 instance owner,未 override 的 instance / static 方法沿冻结的 `extends` 链查找。普通嵌套 function 拥有独立 `this`,只有 arrow 词法继承外层 owner。声明后通过 assignment 安装的 function / arrow、本地 function alias 和 `.bind()` 结果都必须保留 callable identity;只有未被函数对象自有成员覆盖的 Function.prototype `.call()` / `.apply()` / `.bind()` 采用内建 callable 语义,自有覆盖和业务对象同名方法都按实际成员 callable 解析。函数对象自有 `.call / .apply / .bind` 覆盖必须按对象身份传播到全部普通别名,`.bind()` 生成的新函数则保持独立对象身份。callable conditional expression 必须合并 consequent / alternate 的全部可调用身份;声明式和后续赋值式 `api.run.bind(api)` 都在绑定时冻结成员 owner 与方法名。受控 callback 识别穿透 callee 与 receiver 外层括号;Promise `.then` 的 fulfilled / rejected 两个参数都是 callback,`.catch` / `.finally` 仍仅取第一个。timer、RAF、microtask、Promise 与事件监听 callback 的注册点只建立异步可达调用边,闭包读取外层 alias 时必须选取当前同步作用域收尾后的状态,不能冻结在注册点;callback 函数体对外层 alias 的写副作用仍不得同步提交到注册调用末尾。数组字面量的已知迭代 API 才按同步 callback 传播函数体副作用。视觉资产启发式允许使用 ASCII 小写副本查找路径、`drawImage` 与图片变量,但函数调用图、semantic binding 和可达性必须始终解析原始大小写 JavaScript,禁止把 `MainLoop` 与 `mainloop` 合并。Canvas 视觉门必须复用同一组受 256 文件、累计源码 `2 MiB` 与投影处理 `32 MiB` 限制的 inline / external module 加载、链接和投影结果,并逐个原始大小写 unit 独立关联资产路径、图片变量与可达 `drawImage`;`<script type="module">`、本地 `<script type="module" src>` 及其真实可达依赖可以形成证据,未链接文件、恒假动态依赖、未调用函数、跨 unit 拼接和仅出现在 HTML 文本中的路径不得借用其它 unit 的绘制调用过门。
|
||||
- conditional callable 的 consequent / alternate 必须在 test 求值之后分别按自身表达式起点冻结身份;受控 callback 参数也按该参数自身起点解析,使更早参数的 alias 副作用先行生效,同时保持真实 callback 执行边位于注册调用之后。
|
||||
- 泥点不足是确定性业务中断,不是瞬态 Provider 故障或未知副作用。钱包的 `泥点余额不足` 与 `可消费泥点不足:...` 两种领域文案统一映射为稳定原因 `mud-points-insufficient`,不得自动重试;即使 External Generation durable ledger 已存在,也必须落为 `failed`,不能误入 `needs-reconciliation`。game-chat 顶部状态、持久失败对话与 `【Supervisor 阶段记录】` 统一显示“泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。”,并禁止透传 operationId、URL、路径、密钥或任意上游正文。
|
||||
- tool-plan 成功响应落账前,对内置 Runtime 原生函数与 legacy wrapper 的合法、无重复 key JSON arguments 按工具 schema 的精确位置做项目路径 canonicalization:`file.*.path`、`project.patchset.changes[*].path`、`project.git_commit.paths[*]`、`command.*.cwd`、`image.inspect.paths[*]` 与 `canvas.asset_generate.outputPath` 若是当前项目根目录内的完整绝对路径,转换为 `/` 分隔的项目相对路径后再校验、持久化并执行;源码/叙述字段、任务产物描述、动态 MCP arguments 和项目外绝对路径不得改写,后两者继续由绝对路径门禁失败关闭。项目根只允许搜索/列举范围与命令 cwd 规范化为 `.`,不能成为文件目标。当前进程与重启恢复都必须从同一份规范化 handoff 重放,禁止分别执行原响应和持久响应。
|
||||
|
||||
Reference in New Issue
Block a user