From 237e8cb487eaff2da3b2bbe11f3126c5b315d503 Mon Sep 17 00:00:00 2001 From: wanggengliang Date: Fri, 5 Sep 2025 10:42:21 +0800 Subject: [PATCH] Fix: closure capture issue at for loop variables Mark variables declared in the initializer of C-style for loops with the PER_ITERATION flag during scope initialization. Update boxingForLocals to skip boxing for variables carrying PER_ITERATION, so they remain plain values and can later be handled by passes that implement per-iteration semantics. Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICWH6B?from=project-issue Signed-off-by: wanggengliang --- .../compiler/lowering/ets/boxingForLocals.cpp | 19 +++++- .../lowering/scopesInit/scopesInitPhase.cpp | 11 ++++ .../lambda_n_for_let_per_iteration.ets | 58 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 ets2panda/test/runtime/ets/lambda_n/lambda_n_for_let_per_iteration.ets diff --git a/ets2panda/compiler/lowering/ets/boxingForLocals.cpp b/ets2panda/compiler/lowering/ets/boxingForLocals.cpp index 3c5f9f7852..9ef91f8d7a 100644 --- a/ets2panda/compiler/lowering/ets/boxingForLocals.cpp +++ b/ets2panda/compiler/lowering/ets/boxingForLocals.cpp @@ -110,8 +110,15 @@ static ArenaSet FindVariablesToBox(public_lib::Context *c auto modified = FindModified(ctx, func); auto varsToBox = ArenaSet(allocator->Adapter()); - std::set_intersection(captured.cbegin(), captured.cend(), modified.cbegin(), modified.cend(), - std::inserter(varsToBox, varsToBox.begin())); + for (auto *v : captured) { + if (modified.find(v) == modified.end()) { + continue; + } + if (v->HasFlag(varbinder::VariableFlags::PER_ITERATION)) { + continue; // binding pre iteration: not participate in outer boxing to avoid generating single reference + } + varsToBox.insert(v); + } return varsToBox; } @@ -192,6 +199,11 @@ static ir::AstNode *HandleVariableDeclarator(public_lib::Context *ctx, ir::Varia auto *scope = oldVar->GetScope(); auto *type = oldVar->TsType(); auto *boxedType = checker->GlobalBuiltinBoxType(type); + bool inForInit = declarator->Parent() && declarator->Parent()->Parent() && + declarator->Parent()->Parent()->IsForUpdateStatement(); + if (inForInit && oldVar->HasFlag(varbinder::VariableFlags::PER_ITERATION)) { + return declarator; + } auto initArgs = ArenaVector(allocator->Adapter()); if (declarator->Init() != nullptr) { @@ -263,6 +275,9 @@ static bool OnLeftSideOfAssignment(ir::AstNode *ast) static ir::AstNode *HandleReference(public_lib::Context *ctx, ir::Identifier *id, varbinder::Variable *var) { + if (var->HasFlag(varbinder::VariableFlags::PER_ITERATION)) { + return id; + } auto *parser = ctx->parser->AsETSParser(); auto *checker = ctx->GetChecker()->AsETSChecker(); diff --git a/ets2panda/compiler/lowering/scopesInit/scopesInitPhase.cpp b/ets2panda/compiler/lowering/scopesInit/scopesInitPhase.cpp index 5897ab6d64..29e91fdc6d 100644 --- a/ets2panda/compiler/lowering/scopesInit/scopesInitPhase.cpp +++ b/ets2panda/compiler/lowering/scopesInit/scopesInitPhase.cpp @@ -160,6 +160,17 @@ void ScopesInitPhase::VisitForUpdateStatement(ir::ForUpdateStatement *forUpdateS // CC-OFFNXT(G.FMT.06-CPP) project code style VarBinder(), forUpdateStmt->Scope()->DeclScope()); CallNode(forUpdateStmt->Init()); + if (auto *init = forUpdateStmt->Init(); init && init->IsVariableDeclaration()) { + auto *vd = init->AsVariableDeclaration(); + for (auto *decl : vd->Declarators()) { + if (!decl->Id()->IsIdentifier()) + continue; + auto *id = decl->Id()->AsIdentifier(); + if (auto *var = id->Variable()) { + var->AddFlag(varbinder::VariableFlags::PER_ITERATION); + } + } + } auto lexicalScope = LexicalScopeCreateOrEnter(VarBinder(), forUpdateStmt); AttachLabelToScope(forUpdateStmt); diff --git a/ets2panda/test/runtime/ets/lambda_n/lambda_n_for_let_per_iteration.ets b/ets2panda/test/runtime/ets/lambda_n/lambda_n_for_let_per_iteration.ets new file mode 100644 index 0000000000..b4c71f08ba --- /dev/null +++ b/ets2panda/test/runtime/ets/lambda_n/lambda_n_for_let_per_iteration.ets @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function case_classic_for_let_closures(): void { + // case 1: classic for(let i...) + closures + let out1: String = ""; + const fns1: Array<() => void> = []; + for (let i = 0; i < 3; i++) { + fns1.push(() => { out1 += ("" + i); }); + } + for (const fn of fns1) fn(); + arktest.assertEQ(out1, "012"); +} + +function case_for_of_per_iteration(): void { + // case 2: for-of must also be per-iteration + let out2: String = ""; + const fns2: Array<() => void> = []; + const arr: number[] = [10, 20, 30]; + for (let x of arr) { + fns2.push(() => { out2 += ("" + x); }); + } + for (const fn of fns2) fn(); + arktest.assertEQ(out2, "102030"); +} + +function case_continue_keeps_binding(): void { + // case 3: continue should not break binding of this iteration + let out3: String = ""; + const fns3: Array<() => void> = []; + for (let i = 0; i < 3; i++) { + if (i === 1) { + fns3.push(() => { out3 += "M" + i; }); + continue; + } + fns3.push(() => { out3 += "N" + i; }); + } + for (const fn of fns3) fn(); + arktest.assertEQ(out3, "N0M1N2"); +} + +function main(): void { + case_classic_for_let_closures(); + case_for_of_per_iteration(); + case_continue_keeps_binding(); +} \ No newline at end of file -- Gitee