1 /*
2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "capturedVariables.h"
17 #include "util/options.h"
18
19 namespace ark::es2panda::compiler {
20
OnLeftSideOfAssignment(ir::Identifier const * const ident)21 static bool OnLeftSideOfAssignment(ir::Identifier const *const ident) noexcept
22 {
23 return ident->Parent()->IsAssignmentExpression() && ident->Parent()->AsAssignmentExpression()->Left() == ident;
24 }
25
AddScopes(ir::AstNode * node,std::set<varbinder::Scope * > & scopes)26 static void AddScopes(ir::AstNode *node, std::set<varbinder::Scope *> &scopes) noexcept
27 {
28 if (node->Scope()->IsFunctionScope()) {
29 scopes.emplace(node->Scope()->AsFunctionScope()->ParamScope());
30 }
31 if (node->Scope()->IsCatchScope()) {
32 scopes.emplace(node->Scope()->AsCatchScope()->ParamScope());
33 }
34 if (node->Scope()->IsLoopScope()) {
35 scopes.emplace(node->Scope()->AsLoopScope()->DeclScope());
36 }
37 scopes.emplace(node->Scope());
38 }
39
FindVariable(ir::Identifier * ident,std::set<varbinder::Scope * > const & scopes)40 static varbinder::Variable *FindVariable(ir::Identifier *ident, std::set<varbinder::Scope *> const &scopes) noexcept
41 {
42 auto *var = ident->Variable();
43 // NOTE! For some unknown reasons :) variables exist in scope collections but are not set to identifiers after
44 // 'varbinder->IdentifierAnalysis()' pass. Probably need to be investigated and fixed sometimes...
45 if (var == nullptr) {
46 // We start from the innermost scope!
47 for (auto it = scopes.crbegin(); it != scopes.crend(); ++it) {
48 auto res = (*it)->Find(ident->Name(), varbinder::ResolveBindingOptions::VARIABLES);
49 if (res.variable != nullptr) {
50 var = res.variable;
51 break;
52 }
53 }
54 }
55
56 if (var != nullptr) {
57 auto *scope = var->GetScope();
58 ES2PANDA_ASSERT(scope != nullptr);
59 // We are not interested in variables defined inside arrow function!
60 if (scopes.find(scope) != scopes.cend()) {
61 return nullptr;
62 }
63 }
64
65 return var;
66 }
67
FindModifiedCaptured(ir::ScriptFunction const * const scriptFunction,std::set<varbinder::Variable * > & variables)68 static void FindModifiedCaptured(ir::ScriptFunction const *const scriptFunction,
69 std::set<varbinder::Variable *> &variables) noexcept
70 {
71 auto scopes = std::set<varbinder::Scope *> {};
72 bool inLambda = false;
73
74 std::function<void(ir::AstNode *)> walker = [&](ir::AstNode *node) -> void {
75 if (node->IsArrowFunctionExpression() || node->IsClassDeclaration()) {
76 auto savedWL = inLambda;
77 auto savedScopes = std::set<varbinder::Scope *> {};
78 std::swap(scopes, savedScopes);
79
80 inLambda = true;
81 node->Iterate(walker);
82
83 inLambda = savedWL;
84 std::swap(scopes, savedScopes);
85 savedScopes.clear();
86
87 return;
88 }
89
90 if (inLambda && node->IsScopeBearer()) {
91 AddScopes(node, scopes);
92 } else if (inLambda && node->IsIdentifier() && OnLeftSideOfAssignment(node->AsIdentifier())) {
93 if (auto *var = FindVariable(node->AsIdentifier(), scopes); var != nullptr) {
94 variables.insert(var);
95 }
96 }
97
98 node->Iterate(walker);
99 };
100
101 scriptFunction->Iterate(walker);
102 }
103
HandleScriptFunction(ir::ScriptFunction const * const scriptFunction)104 static void HandleScriptFunction(ir::ScriptFunction const *const scriptFunction) noexcept
105 {
106 auto variables = std::set<varbinder::Variable *> {};
107 FindModifiedCaptured(scriptFunction, variables);
108
109 for (auto *variable : variables) {
110 variable->AddFlag(varbinder::VariableFlags::CAPTURED_MODIFIED);
111 }
112
113 variables.clear();
114 }
115
PerformForModule(public_lib::Context * ctx,parser::Program * program)116 bool CapturedVariables::PerformForModule([[maybe_unused]] public_lib::Context *ctx, parser::Program *program)
117 {
118 std::function<void(ir::AstNode *)> searchForFunctions = [&](ir::AstNode *ast) {
119 if (ast->IsScriptFunction()) {
120 HandleScriptFunction(ast->AsScriptFunction()); // no recursion
121 } else {
122 ast->Iterate(searchForFunctions);
123 }
124 };
125
126 program->Ast()->Iterate(searchForFunctions);
127 return true;
128 }
129 } // namespace ark::es2panda::compiler
130