// Exact, callback-free admission for the #616 ordinary getter recursion
// slice. The normalized plan is semantic data; runtime provenance is sealed
// separately after the admitted root setup declarations have executed.
///|
#warnings("-unused_field")
priv struct GetterRecursionExpressionRecipe {
base_condition_loc : @token.Loc
decrement_loc : @token.Loc
recursive_add_loc : @token.Loc
recursive_member_loc : @token.Loc
root_member_loc : @token.Loc
}
///|
fn GetterRecursionExpressionRecipe::GetterRecursionExpressionRecipe(
base_condition_loc~ : @token.Loc,
decrement_loc~ : @token.Loc,
recursive_add_loc~ : @token.Loc,
recursive_member_loc~ : @token.Loc,
root_member_loc~ : @token.Loc,
) -> GetterRecursionExpressionRecipe {
{
base_condition_loc,
decrement_loc,
recursive_add_loc,
recursive_member_loc,
root_member_loc,
}
}
///|
#warnings("-unused_field")
priv enum GetterRecursionAccessorPlacement {
GetterOwnAccessor
GetterDirectPrototypeAccessor(String)
}
///|
#warnings("-unused_field")
priv struct GetterRecursionPlan {
counter_name : String
object_name : String
property_name : String
initial_count : Double
placement : GetterRecursionAccessorPlacement
recipe : GetterRecursionExpressionRecipe
}
///|
fn GetterRecursionPlan::GetterRecursionPlan(
counter_name~ : String,
object_name~ : String,
property_name~ : String,
initial_count~ : Double,
placement~ : GetterRecursionAccessorPlacement,
recipe~ : GetterRecursionExpressionRecipe,
) -> GetterRecursionPlan {
{ counter_name, object_name, property_name, initial_count, placement, recipe }
}
///|
fn getter_recursion_accessor_holder_name(plan : GetterRecursionPlan) -> String {
match plan.placement {
GetterOwnAccessor => plan.object_name
GetterDirectPrototypeAccessor(holder_name) => holder_name
}
}
///|
fn getter_recursion_seal_statement_index(plan : GetterRecursionPlan) -> Int {
match plan.placement {
GetterOwnAccessor => 1
GetterDirectPrototypeAccessor(_) => 2
}
}
///|
fn getter_recursion_root_statement_index(plan : GetterRecursionPlan) -> Int {
getter_recursion_seal_statement_index(plan) + 1
}
///|
fn getter_recursion_static_property_name(expr : @ast.Expr) -> String? {
match expr {
@ast.StringLit(name, false, @token.LexForm::LexNormal, _) => Some(name)
_ => None
}
}
///|
fn classify_getter_recursion_body(
body : Array[@ast.Stmt],
counter_name : String,
object_name : String,
property_name : String,
root_member_loc : @token.Loc,
) -> GetterRecursionExpressionRecipe? {
guard body.length() == 3 else { return None }
let base_condition_loc = match body[0] {
@ast.IfStmt(
@ast.Binary(
@ast.EqEqEq,
@ast.Ident(condition_counter, _),
base_condition,
condition_loc
),
@ast.ReturnStmt(Some(base_result), _),
None,
_
) if condition_counter == counter_name &&
exact_numeric_recursion_number(base_condition, NUMERIC_RECURSION_BASE) &&
exact_numeric_recursion_number(base_result, NUMERIC_RECURSION_BASE) =>
condition_loc
_ => return None
}
let decrement_loc = match body[1] {
@ast.ExprStmt(
@ast.Assign(
assignment_counter,
@ast.Binary(
@ast.Sub,
@ast.Ident(decrement_counter, _),
decrement,
subtraction_loc
),
_
),
_
) if assignment_counter == counter_name &&
decrement_counter == counter_name &&
exact_numeric_recursion_number(decrement, NUMERIC_RECURSION_STEP) =>
subtraction_loc
_ => return None
}
let (recursive_add_loc, recursive_member_loc) = match body[2] {
@ast.ReturnStmt(
Some(
@ast.Binary(
@ast.Add,
increment,
@ast.Member(
@ast.Ident(recursive_object, _),
recursive_property,
member_loc
),
add_loc
)
),
_
) if recursive_object == object_name &&
recursive_property == property_name &&
exact_numeric_recursion_number(increment, NUMERIC_RECURSION_STEP) =>
(add_loc, member_loc)
_ => return None
}
Some(
GetterRecursionExpressionRecipe(
base_condition_loc~,
decrement_loc~,
recursive_add_loc~,
recursive_member_loc~,
root_member_loc~,
),
)
}
///|
fn classify_getter_recursion_accessor_object(
stmt : @ast.Stmt,
counter_name : String,
recursive_object_name : String,
root_property_name : String,
root_member_loc : @token.Loc,
) -> (String, String, GetterRecursionExpressionRecipe)? {
match stmt {
@ast.VarDecl(
@ast.LetKind,
object_name,
Some(@ast.ObjectLit(properties, _)),
_
) => {
guard numeric_recursion_identifier_is_safe(object_name) &&
properties.length() == 1 else {
return None
}
let property = properties[0]
guard property.kind == @ast.Get &&
!property.computed &&
property.is_method else {
return None
}
let property_name = match
getter_recursion_static_property_name(property.key) {
Some(found) => found
None => return None
}
guard property_name == root_property_name else { return None }
match property.value {
@ast.FuncExpr(Some(getter_name), params, body, _, _) if getter_name ==
property_name &&
params.is_empty() =>
match
classify_getter_recursion_body(
body, counter_name, recursive_object_name, property_name, root_member_loc,
) {
Some(recipe) => Some((object_name, property_name, recipe))
None => None
}
_ => None
}
}
_ => None
}
}
///|
fn getter_recursion_direct_prototype_target_matches(
stmt : @ast.Stmt,
target_name : String,
holder_name : String,
) -> Bool {
match stmt {
@ast.VarDecl(
@ast.LetKind,
object_name,
Some(@ast.ObjectLit(properties, _)),
_
) => {
guard object_name == target_name &&
numeric_recursion_identifier_is_safe(object_name) &&
properties.length() == 1 else {
return false
}
let property = properties[0]
property.kind == @ast.Init &&
!property.computed &&
!property.is_method &&
(match (property.key, property.value) {
(
@ast.StringLit("__proto__", false, @token.LexForm::LexNormal, key_loc),
@ast.Ident(prototype_name, value_loc),
) =>
// The parser assigns a shorthand value (`{ __proto__ }`) the
// object literal's opening location. An explicit initializer's
// value follows its key, so this keeps the special colon form exact.
prototype_name == holder_name && value_loc.offset > key_loc.offset
_ => false
})
}
_ => false
}
}
///|
#warnings("-unused_value")
fn classify_getter_recursion_program(
stmts : Array[@ast.Stmt],
) -> GetterRecursionPlan? {
guard stmts.length() == 3 || stmts.length() == 4 else { return None }
let (counter_name, initial_count) = match stmts[0] {
@ast.VarDecl(@ast.LetKind, name, Some(initial), _) if numeric_recursion_identifier_is_safe(
name,
) &&
exact_numeric_recursion_number(
initial,
NUMERIC_RECURSION_INITIAL_ARGUMENT,
) => (name, NUMERIC_RECURSION_INITIAL_ARGUMENT)
_ => return None
}
let root_statement_index = stmts.length() - 1
let (root_object_name, root_property_name, root_member_loc) = match
stmts[root_statement_index] {
@ast.ExprStmt(
@ast.Member(@ast.Ident(object_name, _), property_name, member_loc),
_
) => (object_name, property_name, member_loc)
_ => return None
}
guard counter_name != root_object_name else { return None }
match
classify_getter_recursion_accessor_object(
stmts[1],
counter_name,
root_object_name,
root_property_name,
root_member_loc,
) {
Some((holder_name, property_name, recipe)) => {
let placement = if stmts.length() == 3 {
guard holder_name == root_object_name else { return None }
GetterOwnAccessor
} else {
guard holder_name != root_object_name &&
holder_name != counter_name &&
getter_recursion_direct_prototype_target_matches(
stmts[2],
root_object_name,
holder_name,
) else {
return None
}
GetterDirectPrototypeAccessor(holder_name)
}
Some(
GetterRecursionPlan(
counter_name~,
object_name=root_object_name,
property_name~,
initial_count~,
placement~,
recipe~,
),
)
}
None => None
}
}
///|
#warnings("-unused_value")
fn getter_recursion_plan_is_dispatchable(plan : GetterRecursionPlan) -> Bool {
match plan.placement {
GetterOwnAccessor => true
GetterDirectPrototypeAccessor(_) => true
}
}
///|
#warnings("-unused_value")
fn getter_recursion_root_statement_matches(
plan : GetterRecursionPlan,
index : Int,
stmt : @ast.Stmt,
) -> Bool {
match index {
0 =>
match stmt {
@ast.VarDecl(@ast.LetKind, name, Some(initial), _) =>
name == plan.counter_name &&
exact_numeric_recursion_number(initial, plan.initial_count)
_ => false
}
1 =>
match
classify_getter_recursion_accessor_object(
stmt,
plan.counter_name,
plan.object_name,
plan.property_name,
plan.recipe.root_member_loc,
) {
Some((holder_name, property_name, _)) =>
holder_name == getter_recursion_accessor_holder_name(plan) &&
property_name == plan.property_name
None => false
}
2 if getter_recursion_seal_statement_index(plan) == 2 =>
getter_recursion_direct_prototype_target_matches(
stmt,
plan.object_name,
getter_recursion_accessor_holder_name(plan),
)
root_index if root_index == getter_recursion_root_statement_index(plan) =>
match stmt {
@ast.ExprStmt(
@ast.Member(@ast.Ident(object_name, _), property_name, _),
_
) =>
object_name == plan.object_name && property_name == plan.property_name
_ => false
}
_ => false
}
}
///|
#warnings("-unused_value")
fn getter_recursion_body_statement_matches(
plan : GetterRecursionPlan,
index : Int,
stmt : @ast.Stmt,
) -> Bool {
match index {
0 =>
match stmt {
@ast.IfStmt(
@ast.Binary(
@ast.EqEqEq,
@ast.Ident(counter_name, _),
base_condition,
_
),
@ast.ReturnStmt(Some(base_result), _),
None,
_
) =>
counter_name == plan.counter_name &&
exact_numeric_recursion_number(base_condition, NUMERIC_RECURSION_BASE) &&
exact_numeric_recursion_number(base_result, NUMERIC_RECURSION_BASE)
_ => false
}
1 =>
match stmt {
@ast.ExprStmt(
@ast.Assign(
assignment_counter,
@ast.Binary(
@ast.Sub,
@ast.Ident(decrement_counter, _),
decrement,
_
),
_
),
_
) =>
assignment_counter == plan.counter_name &&
decrement_counter == plan.counter_name &&
exact_numeric_recursion_number(decrement, NUMERIC_RECURSION_STEP)
_ => false
}
2 =>
match stmt {
@ast.ReturnStmt(
Some(
@ast.Binary(
@ast.Add,
increment,
@ast.Member(@ast.Ident(object_name, _), property_name, _),
_
)
),
_
) =>
object_name == plan.object_name &&
property_name == plan.property_name &&
exact_numeric_recursion_number(increment, NUMERIC_RECURSION_STEP)
_ => false
}
_ => false
}
}