修复可调用回调与视觉脚本可达性
合并条件表达式两端可调用身份并保留成员 bind 赋值结果 穿透回调调用括号并校正 Promise 回调参数槽 使用原始大小写脚本计算视觉资产语义可达性 补充回归测试与 Runtime 技术决策文档
This commit is contained in:
+240
-52
@@ -1358,6 +1358,11 @@ enum JavascriptCallableValue {
|
||||
source: JavascriptSymbolId,
|
||||
captured_at: usize,
|
||||
},
|
||||
Member {
|
||||
receiver: JavascriptSymbolId,
|
||||
name: String,
|
||||
captured_at: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
@@ -1445,40 +1450,64 @@ impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn callable_value(
|
||||
fn callable_values(
|
||||
&self,
|
||||
expression: &JavascriptExpression<'_>,
|
||||
position: usize,
|
||||
) -> Option<JavascriptCallableValue> {
|
||||
) -> Vec<JavascriptCallableValue> {
|
||||
match expression {
|
||||
JavascriptExpression::FunctionExpression(_)
|
||||
| JavascriptExpression::ArrowFunctionExpression(_) => {
|
||||
let span = expression.span();
|
||||
Some(JavascriptCallableValue::Direct((
|
||||
vec![JavascriptCallableValue::Direct((
|
||||
span.start as usize,
|
||||
span.end as usize,
|
||||
)))
|
||||
}
|
||||
JavascriptExpression::Identifier(_) => {
|
||||
self.referenced_symbol(expression)
|
||||
.map(|source| JavascriptCallableValue::Alias {
|
||||
source,
|
||||
captured_at: position,
|
||||
})
|
||||
))]
|
||||
}
|
||||
JavascriptExpression::Identifier(_) => self
|
||||
.referenced_symbol(expression)
|
||||
.map(|source| JavascriptCallableValue::Alias {
|
||||
source,
|
||||
captured_at: position,
|
||||
})
|
||||
.into_iter()
|
||||
.collect(),
|
||||
JavascriptExpression::ParenthesizedExpression(parenthesized) => {
|
||||
self.callable_value(&parenthesized.expression, position)
|
||||
self.callable_values(&parenthesized.expression, position)
|
||||
}
|
||||
JavascriptExpression::ConditionalExpression(conditional) => {
|
||||
let mut values = self.callable_values(&conditional.consequent, position);
|
||||
values.extend(self.callable_values(&conditional.alternate, position));
|
||||
values
|
||||
}
|
||||
JavascriptExpression::CallExpression(call) => call
|
||||
.callee
|
||||
.as_member_expression()
|
||||
.filter(|member| member.static_property_name() == Some("bind"))
|
||||
.and_then(|member| self.referenced_symbol(member.object()))
|
||||
.map(|source| JavascriptCallableValue::Alias {
|
||||
source,
|
||||
captured_at: position,
|
||||
}),
|
||||
_ => None,
|
||||
.into_iter()
|
||||
.flat_map(|bind| {
|
||||
if let Some(source) = self.referenced_symbol(bind.object()) {
|
||||
return vec![JavascriptCallableValue::Alias {
|
||||
source,
|
||||
captured_at: position,
|
||||
}];
|
||||
}
|
||||
bind.object()
|
||||
.as_member_expression()
|
||||
.and_then(|member| {
|
||||
self.referenced_symbol(member.object())
|
||||
.zip(member.static_property_name().map(str::to_string))
|
||||
})
|
||||
.map(|(receiver, name)| JavascriptCallableValue::Member {
|
||||
receiver,
|
||||
name,
|
||||
captured_at: position,
|
||||
})
|
||||
.into_iter()
|
||||
.collect()
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1495,19 +1524,29 @@ impl JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let value = self.callable_value(expression, position);
|
||||
self.callable_events
|
||||
.entry(symbol_id)
|
||||
.or_default()
|
||||
.push(JavascriptAliasEvent {
|
||||
let values = self.callable_values(expression, position);
|
||||
let events = self.callable_events.entry(symbol_id).or_default();
|
||||
if values.is_empty() {
|
||||
events.push(JavascriptAliasEvent {
|
||||
position,
|
||||
scope: javascript_alias_scope_at(self.ranges, position),
|
||||
value,
|
||||
value: None,
|
||||
conditional: javascript_position_is_conditionally_executed(
|
||||
self.conditional_ranges,
|
||||
position,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
events.extend(values.into_iter().map(|value| JavascriptAliasEvent {
|
||||
position,
|
||||
scope: javascript_alias_scope_at(self.ranges, position),
|
||||
value: Some(value),
|
||||
conditional: javascript_position_is_conditionally_executed(
|
||||
self.conditional_ranges,
|
||||
position,
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn receiver_values(
|
||||
@@ -1613,9 +1652,9 @@ impl<'a> VisitJavascript<'a> for JavascriptMethodReceiverOwnerCollector<'_> {
|
||||
initializer,
|
||||
JavascriptExpression::FunctionExpression(_)
|
||||
| JavascriptExpression::ArrowFunctionExpression(_)
|
||||
) && self
|
||||
.callable_value(initializer, declarator.span.start as usize)
|
||||
.is_some()
|
||||
) && !self
|
||||
.callable_values(initializer, declarator.span.start as usize)
|
||||
.is_empty()
|
||||
{
|
||||
self.record_callable_assignment(
|
||||
symbol_id,
|
||||
@@ -1690,6 +1729,9 @@ fn javascript_expression_is_known_event_target(
|
||||
scoping: &JavascriptScoping,
|
||||
) -> bool {
|
||||
match expression {
|
||||
JavascriptExpression::ParenthesizedExpression(parenthesized) => {
|
||||
javascript_expression_is_known_event_target(&parenthesized.expression, scoping)
|
||||
}
|
||||
JavascriptExpression::Identifier(identifier) => {
|
||||
javascript_identifier_is_unresolved_known_global(
|
||||
identifier,
|
||||
@@ -1722,6 +1764,7 @@ fn javascript_known_callback_argument_indices(
|
||||
) -> &'static [usize] {
|
||||
const NONE: &[usize] = &[];
|
||||
const FIRST: &[usize] = &[0];
|
||||
const FIRST_AND_SECOND: &[usize] = &[0, 1];
|
||||
const SECOND: &[usize] = &[1];
|
||||
let unresolved_global = |identifier: &oxc_ast::ast::IdentifierReference<'_>| {
|
||||
identifier
|
||||
@@ -1729,7 +1772,11 @@ fn javascript_known_callback_argument_indices(
|
||||
.get()
|
||||
.is_some_and(|reference_id| scoping.get_reference(reference_id).symbol_id().is_none())
|
||||
};
|
||||
if let JavascriptExpression::Identifier(identifier) = &call.callee {
|
||||
let mut callee = &call.callee;
|
||||
while let JavascriptExpression::ParenthesizedExpression(parenthesized) = callee {
|
||||
callee = &parenthesized.expression;
|
||||
}
|
||||
if let JavascriptExpression::Identifier(identifier) = callee {
|
||||
if !unresolved_global(identifier) {
|
||||
return NONE;
|
||||
}
|
||||
@@ -1739,15 +1786,23 @@ fn javascript_known_callback_argument_indices(
|
||||
_ => NONE,
|
||||
};
|
||||
}
|
||||
let Some(member) = call.callee.as_member_expression() else {
|
||||
let Some(member) = callee.as_member_expression() else {
|
||||
return NONE;
|
||||
};
|
||||
let Some(name) = member.static_property_name() else {
|
||||
return NONE;
|
||||
};
|
||||
let mut receiver = member.object();
|
||||
while let JavascriptExpression::ParenthesizedExpression(parenthesized) = receiver {
|
||||
receiver = &parenthesized.expression;
|
||||
}
|
||||
if matches!(name, "catch" | "finally" | "then") {
|
||||
return if matches!(member.object(), JavascriptExpression::ImportExpression(_)) {
|
||||
FIRST
|
||||
return if matches!(receiver, JavascriptExpression::ImportExpression(_)) {
|
||||
if name == "then" {
|
||||
FIRST_AND_SECOND
|
||||
} else {
|
||||
FIRST
|
||||
}
|
||||
} else {
|
||||
NONE
|
||||
};
|
||||
@@ -1756,7 +1811,7 @@ fn javascript_known_callback_argument_indices(
|
||||
name,
|
||||
"every" | "filter" | "find" | "forEach" | "map" | "reduce" | "some"
|
||||
) {
|
||||
return if matches!(member.object(), JavascriptExpression::ArrayExpression(_)) {
|
||||
return if matches!(receiver, JavascriptExpression::ArrayExpression(_)) {
|
||||
FIRST
|
||||
} else {
|
||||
NONE
|
||||
@@ -1771,10 +1826,10 @@ fn javascript_known_callback_argument_indices(
|
||||
| "setTimeout"
|
||||
) {
|
||||
let known_receiver = if name == "addEventListener" {
|
||||
javascript_expression_is_known_event_target(member.object(), scoping)
|
||||
javascript_expression_is_known_event_target(receiver, scoping)
|
||||
} else {
|
||||
matches!(
|
||||
member.object(),
|
||||
receiver,
|
||||
JavascriptExpression::Identifier(identifier)
|
||||
if javascript_identifier_is_unresolved_known_global(
|
||||
identifier,
|
||||
@@ -1854,6 +1909,23 @@ impl JavascriptFunctionInvocationCollector<'_, '_> {
|
||||
*captured_at,
|
||||
visiting,
|
||||
)),
|
||||
JavascriptCallableValue::Member {
|
||||
receiver,
|
||||
name,
|
||||
captured_at,
|
||||
} => {
|
||||
for owner in self.receiver_owners_for_symbol(
|
||||
*receiver,
|
||||
*captured_at,
|
||||
&mut BTreeSet::new(),
|
||||
) {
|
||||
indices.extend(self.method_indices_for_owner(
|
||||
owner,
|
||||
name,
|
||||
&mut BTreeSet::new(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2737,11 +2809,12 @@ fn game_index_visibly_uses_visual_asset(
|
||||
let Ok(html) = std::str::from_utf8(html) else {
|
||||
return false;
|
||||
};
|
||||
let content = strip_art_reference_comments(html).to_ascii_lowercase();
|
||||
if !content.contains(&asset_path) {
|
||||
let original_content = strip_art_reference_comments(html);
|
||||
let heuristic_content = original_content.to_ascii_lowercase();
|
||||
if !heuristic_content.contains(&asset_path) {
|
||||
return false;
|
||||
}
|
||||
let markup = strip_script_blocks(&content);
|
||||
let markup = strip_script_blocks(&heuristic_content);
|
||||
|
||||
let mut tag_cursor = 0;
|
||||
while let Some(start_offset) = markup[tag_cursor..].find('<') {
|
||||
@@ -2806,28 +2879,31 @@ fn game_index_visibly_uses_visual_asset(
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
let identifiers = canvas_visual_identifiers(&content, &markup, &asset_path);
|
||||
let identifiers = canvas_visual_identifiers(&heuristic_content, &markup, &asset_path);
|
||||
if identifiers.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let javascript = executable_javascript_from_html(&content);
|
||||
let original_javascript = executable_javascript_from_html(&original_content);
|
||||
let heuristic_javascript = original_javascript.to_ascii_lowercase();
|
||||
let mut significant_draws = 0usize;
|
||||
let function_ranges = named_javascript_function_ranges(&javascript);
|
||||
let function_ranges = named_javascript_function_ranges(&original_javascript);
|
||||
let mut draw_cursor = 0;
|
||||
while let Some(offset) = javascript[draw_cursor..].find("drawimage(") {
|
||||
while let Some(offset) = heuristic_javascript[draw_cursor..].find("drawimage(") {
|
||||
let call = draw_cursor + offset;
|
||||
let arguments_start = call + "drawimage(".len();
|
||||
draw_cursor = arguments_start;
|
||||
if position_is_inside_javascript_string(&javascript, call)
|
||||
|| !javascript_position_is_reachable(&javascript, &function_ranges, call)
|
||||
if position_is_inside_javascript_string(&heuristic_javascript, call)
|
||||
|| !javascript_position_is_reachable(&original_javascript, &function_ranges, call)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(arguments_end) = javascript_call_arguments_end(&javascript, arguments_start)
|
||||
let Some(arguments_end) =
|
||||
javascript_call_arguments_end(&heuristic_javascript, arguments_start)
|
||||
else {
|
||||
break;
|
||||
};
|
||||
let arguments = split_javascript_arguments(&javascript[arguments_start..arguments_end]);
|
||||
let arguments =
|
||||
split_javascript_arguments(&heuristic_javascript[arguments_start..arguments_end]);
|
||||
if arguments
|
||||
.first()
|
||||
.is_some_and(|identifier| identifiers.contains(*identifier))
|
||||
@@ -9961,6 +10037,33 @@ pub(in crate::agent) fn autonomous_game_build_completion_blocker_at_locked(
|
||||
mod visible_destination_tests {
|
||||
use super::*;
|
||||
|
||||
fn mixed_case_main_loop_html(invocation: &str) -> Vec<u8> {
|
||||
format!(
|
||||
"<!doctype html><html><body><canvas width=320 height=180></canvas><script>const context=document.querySelector('canvas').getContext('2d');const PlayerArt=new Image();PlayerArt.src='../assets/art-spec.png';function MainLoop(){{context.drawImage(PlayerArt,0,0,64,64);}}function mainloop(){{return 0;}}{invocation}();</script></body></html>"
|
||||
)
|
||||
.into_bytes()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visual_asset_reachability_keeps_original_identifier_case_for_called_main_loop() {
|
||||
assert!(game_index_visibly_uses_visual_asset(
|
||||
&mixed_case_main_loop_html("MainLoop"),
|
||||
"assets/art-spec.png",
|
||||
(64, 64),
|
||||
VisualAssetUsageRequirement::CanvasDraw,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visual_asset_reachability_does_not_merge_differently_cased_main_loops() {
|
||||
assert!(!game_index_visibly_uses_visual_asset(
|
||||
&mixed_case_main_loop_html("mainloop"),
|
||||
"assets/art-spec.png",
|
||||
(64, 64),
|
||||
VisualAssetUsageRequirement::CanvasDraw,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canvas_draw_destination_rejects_offscreen_and_unbounded_dynamic_coordinates() {
|
||||
let canvas = (320.0, 180.0);
|
||||
@@ -10183,6 +10286,29 @@ mod javascript_projection_reachability_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_expression_preserves_both_possible_callables() {
|
||||
for (content, sources) in [
|
||||
(
|
||||
"function first() { return import('./conditional-first.mjs'); } function second() { return import('./conditional-second.mjs'); } const start = flag ? first : second; start();",
|
||||
["./conditional-first.mjs", "./conditional-second.mjs"],
|
||||
),
|
||||
(
|
||||
"function first() { return import('./assigned-first.mjs'); } function second() { return import('./assigned-second.mjs'); } let start; start = flag ? first : second; start();",
|
||||
["./assigned-first.mjs", "./assigned-second.mjs"],
|
||||
),
|
||||
] {
|
||||
let ranges = named_javascript_function_ranges(content);
|
||||
for source in sources {
|
||||
let position = content.find(source).expect("find conditional callable import");
|
||||
assert!(
|
||||
javascript_position_is_reachable(content, &ranges, position),
|
||||
"both conditional callable branches must remain reachable: {source}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_function_alias_preserves_the_call_edge() {
|
||||
let content = "function start() { return import('./aliased-function.mjs'); } const alias = start; alias();";
|
||||
@@ -10246,6 +10372,26 @@ mod javascript_projection_reachability_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn member_bind_results_remain_callable_after_declaration_or_assignment() {
|
||||
for invocation in [
|
||||
"const bound = api.run.bind(api); bound();",
|
||||
"let bound; bound = api.run.bind(api); bound();",
|
||||
] {
|
||||
let content = format!(
|
||||
"const api = {{ run() {{ return import('./member-bind.mjs'); }} }}; {invocation}"
|
||||
);
|
||||
let ranges = named_javascript_function_ranges(&content);
|
||||
let position = content
|
||||
.find("./member-bind.mjs")
|
||||
.expect("find bound member import");
|
||||
assert!(
|
||||
javascript_position_is_reachable(&content, &ranges, position),
|
||||
"a bound local member must preserve its callable identity: {invocation}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_function_expression_uses_its_outer_binding_as_one_range() {
|
||||
let content =
|
||||
@@ -10353,11 +10499,13 @@ mod javascript_projection_reachability_tests {
|
||||
let content = format!(
|
||||
"function callback(){{return import('./promise-{api}-callback.mjs')}} function extra(){{return import('./promise-{api}-extra.mjs')}} import('./seed.mjs').{api}(callback, extra);"
|
||||
);
|
||||
assert_callback_reachability(
|
||||
&content,
|
||||
&[&format!("./promise-{api}-callback.mjs")],
|
||||
&[&format!("./promise-{api}-extra.mjs")],
|
||||
);
|
||||
let callback = format!("./promise-{api}-callback.mjs");
|
||||
let extra = format!("./promise-{api}-extra.mjs");
|
||||
if api == "then" {
|
||||
assert_callback_reachability(&content, &[&callback, &extra], &[]);
|
||||
} else {
|
||||
assert_callback_reachability(&content, &[&callback], &[&extra]);
|
||||
}
|
||||
}
|
||||
for api in [
|
||||
"map", "filter", "every", "find", "forEach", "some", "reduce",
|
||||
@@ -10373,6 +10521,47 @@ mod javascript_projection_reachability_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn controlled_callback_apis_unwrap_parenthesized_callees_and_receivers() {
|
||||
assert_callback_reachability(
|
||||
"function callback(){return import('./parenthesized-timer.mjs')} (setTimeout)(callback, 0);",
|
||||
&["./parenthesized-timer.mjs"],
|
||||
&[],
|
||||
);
|
||||
assert_callback_reachability(
|
||||
"function callback(){return import('./parenthesized-array.mjs')} ([1]).map(callback);",
|
||||
&["./parenthesized-array.mjs"],
|
||||
&[],
|
||||
);
|
||||
assert_callback_reachability(
|
||||
"function fulfilled(){return import('./parenthesized-import-fulfilled.mjs')} function rejected(){return import('./parenthesized-import-rejected.mjs')} (import('./seed.mjs')).then(fulfilled, rejected);",
|
||||
&[
|
||||
"./parenthesized-import-fulfilled.mjs",
|
||||
"./parenthesized-import-rejected.mjs",
|
||||
],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promise_then_executes_both_callbacks_but_catch_and_finally_only_the_first() {
|
||||
assert_callback_reachability(
|
||||
"function fulfilled(){return import('./then-fulfilled.mjs')} function rejected(){return import('./then-rejected.mjs')} import('./seed.mjs').then(fulfilled, rejected);",
|
||||
&["./then-fulfilled.mjs", "./then-rejected.mjs"],
|
||||
&[],
|
||||
);
|
||||
for api in ["catch", "finally"] {
|
||||
let content = format!(
|
||||
"function callback(){{return import('./{api}-callback.mjs')}} function extra(){{return import('./{api}-extra.mjs')}} import('./seed.mjs').{api}(callback, extra);"
|
||||
);
|
||||
assert_callback_reachability(
|
||||
&content,
|
||||
&[&format!("./{api}-callback.mjs")],
|
||||
&[&format!("./{api}-extra.mjs")],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_member_callback_demands_use_the_same_exact_slots() {
|
||||
let content = "import * as api from './api.mjs'; setTimeout(api.timer, api.delay); document.addEventListener(api.eventType, api.listener, api.options); import('./seed.mjs').then(api.fulfilled, api.rejected); [1].reduce(api.reducer, api.initialValue); setTIMEOUT(api.wrongCase); [1].foreach(api.wrongMethodCase);";
|
||||
@@ -10381,7 +10570,7 @@ mod javascript_projection_reachability_tests {
|
||||
.import_member_calls
|
||||
.get("api")
|
||||
.expect("record controlled callback member demands");
|
||||
for callback in ["timer", "listener", "fulfilled", "reducer"] {
|
||||
for callback in ["timer", "listener", "fulfilled", "rejected", "reducer"] {
|
||||
assert!(
|
||||
members
|
||||
.keys()
|
||||
@@ -10393,7 +10582,6 @@ mod javascript_projection_reachability_tests {
|
||||
"delay",
|
||||
"eventType",
|
||||
"options",
|
||||
"rejected",
|
||||
"initialValue",
|
||||
"wrongCase",
|
||||
"wrongMethodCase",
|
||||
|
||||
@@ -5986,6 +5986,7 @@
|
||||
- JavaScript / ESM 深层可达性补充:constructor、`.call/.apply` 与受控 inline callback 建立真实 invocation;具名 function expression 不再生成遮蔽外层 binding 的重叠节点,普通 inline function / arrow 未被执行时保持不可达。受控 callback API 名大小写敏感,并以精确参数索引建立执行边:timer、microtask、RAF、Promise 与数组迭代取第一个参数,`addEventListener` 取第二个参数,delay、initial value、event type、options 和额外参数保持普通值。条件/循环赋值合并执行与跳过状态,conditional expression 合并各 owner,未知确定赋值显式 invalidation;已调用函数对外层 alias 的副作用按调用位置传播,`super` owner 固定在 class 定义点。恒假扫描先屏蔽 parser 识别的注释和 literal;恒假分支区间随单次函数可达性 analysis 预计算、排序合并并以借用二分索引查询,不再使用 thread-local 完整源码 key 或命中时 clone ranges。投影 canonical 根名避让两侧全部非 import binding,dynamic shorthand 保留原键,循环模块按相同原始声明去重,同名 dynamic export 不得拉入无引用本地声明。
|
||||
- JavaScript / ESM 深层可达性补充:constructor、`.call/.apply` 与受控 inline callback 建立真实 invocation;具名 function expression 不再生成遮蔽外层 binding 的重叠节点,普通 inline function / arrow 未被执行时保持不可达。条件/循环赋值合并执行与跳过状态,conditional expression 合并各 owner,未知确定赋值显式 invalidation;已调用函数对外层 alias 的副作用按调用位置传播,`super` owner 固定在 class 定义点。恒假扫描先屏蔽 parser 识别的注释和 literal。投影 canonical 根名避让两侧全部非 import binding,dynamic shorthand 保留原键,循环模块按相同原始声明去重,同名 dynamic export 不得拉入无引用本地声明。
|
||||
- JavaScript / ESM 构造、继承与 callable 补充:`new` 沿冻结的 class owner 图执行本类显式 constructor、显式 `super()` 或隐式 derived constructor,并支持直接 class expression;instance / static 成员未 override 时继续沿 `extends` 链查找。普通嵌套 function 不继承 class `this`,arrow 保持词法 owner。`let binding; binding = function/arrow`、本地 function alias 和 Function.prototype `.bind()` 结果都建立 callable identity;`.call/.apply/.bind` 只有在 receiver 可解析为 callable 时采用 Function.prototype 语义,业务对象同名方法仍作为普通 receiver method 执行。
|
||||
- JavaScript / ESM callable、callback 与视觉可达性补充:callable conditional expression 合并两端全部身份;声明式与后续赋值式 member `.bind()` 都冻结成员 owner。受控 callback API 穿透 callee / receiver 外层括号,Promise `.then` 取 fulfilled 与 rejected 两个 callback 槽,`.catch/.finally` 仍仅取首槽。视觉资产路径与 `drawImage` 启发式可使用 ASCII 小写副本,但 AST、semantic binding 和函数可达性只解析原始大小写 JavaScript,大小写不同的 `MainLoop/mainloop` 不得合并。
|
||||
- 浏览器因果:状态证据仍只冻结 trusted input listener 及其点击派生微任务内的变化;完整手势身份改由宿主在成功完成 Chromium 元素鼠标输入后调用隔离世界 finish。更早注册的 `window` capture listener 即使调用 `stopImmediatePropagation()` 也不能阻断探针自身的完成身份,页面脚本不能伪造 host finish,RAF / timer 继续不计入动作结果。
|
||||
- 验证边界:Linux 定向回归覆盖目录相对读写与清理、祖先 symlink、CAS 安装后错误、九文件混合快照、Tetris AST 反例和七项真实 Chrome generic 试玩。Windows cfg 代码必须继续在真实 Windows CI / 发布构建验证;本地缺少 MinGW C compiler 时,安装了 Rust target 也不能把交叉 `cargo check` 失败误报为源码失败。
|
||||
- JavaScript / ESM 循环与体积补充:投影声明按 `(origin module, original root binding)` 保存身份,canonical 重命名不能改写原始身份;删除回流声明后仍把 import 引用改接到 importer 已有 canonical,并给固定点保留“模块数 + 1”轮的产出与稳定确认预算,未收敛时失败关闭。inline module 先按浏览器可执行标签提取原文并计入与外部脚本共享的累计 `2 MiB` 源码预算,再做语法和语义校验;无效超限模块不能被 helper 静默过滤,无效小模块也明确失败关闭。
|
||||
|
||||
@@ -836,6 +836,7 @@ game-project/
|
||||
- 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;调用事件按 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` 不能触发参数执行推断。
|
||||
- member alias、局部 receiver 与动态 namespace 的 assignment 必须保留赋值位置和 enclosing function scope;函数体内的使用必须按真实 invocation position 选择当时已生效的事件,多次调用跨过赋值边界时合并全部可能 owner,恒假分支、未调用函数或调用之后的赋值不能倒灌覆盖更早使用点。未知条件或循环内赋值保留执行与跳过两种状态,conditional expression 保留全部可解析 owner,无法解析的确定重赋值显式 invalidation;已调用函数对外层 alias 的最终副作用按调用位置回写,不能被 scope 隔离丢失。被导出的 function、class/object member 还必须把模块初始化完成视为潜在外部调用时点,使声明之后生效的顶层赋值进入 demand,同时保留此前本地调用的旧状态。动态依赖传播继续以原始 owner 模块 AST span 为权威,不得因投影重排声明或省略独立赋值语句而重算出相反 alias。class owner 必须继续区分 static / instance,class expression 与实例化 alias 进入相同 receiver 图,`super` 的 parent owner 在 class 定义位置冻结;`new` 必须覆盖显式 `super()`、隐式 derived constructor 和直接 class expression,未 override 的 instance / static 方法沿冻结的 `extends` 链查找。普通嵌套 function 拥有独立 `this`,只有 arrow 词法继承外层 owner。声明后通过 assignment 安装的 function / arrow、以及本地 function alias 和 `.bind()` 结果必须保留 callable identity。constructor、真正 Function.prototype 的 `.call()` / `.apply()` / `.bind()` 都是实际 invocation,但业务对象同名方法仍按 receiver method 解析;具名 function expression 只保留外层 binding 对应的单一 range,未被受控 API 执行的 inline function / arrow 保持不可达。恒假分支识别必须先用 parser trivia 屏蔽注释和非执行 literal。callback API 只接受 semantic 未解析的已知全局调度函数或 AST 可证明的 literal array / dynamic import 调用;用户定义、导入或遮蔽的同名 `setTimeout / map / then` 不能触发参数执行推断。
|
||||
- callable conditional expression 必须合并 consequent / alternate 的全部可调用身份;声明式和后续赋值式 `api.run.bind(api)` 都在绑定时冻结成员 owner 与方法名。受控 callback 识别穿透 callee 与 receiver 外层括号;Promise `.then` 的 fulfilled / rejected 两个参数都是 callback,`.catch` / `.finally` 仍仅取第一个。视觉资产启发式允许使用 ASCII 小写副本查找路径、`drawImage` 与图片变量,但函数调用图、semantic binding 和可达性必须始终解析原始大小写 JavaScript,禁止把 `MainLoop` 与 `mainloop` 合并。
|
||||
- 泥点不足是确定性业务中断,不是瞬态 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