1 /*
2 * Copyright (c) 2024 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 "helpers.h"
17 #include "identifierHasVariable.h"
18 #include "variableHasScope.h"
19 #include "ir/base/scriptFunction.h"
20 #include "ir/ts/tsEnumDeclaration.h"
21 #include "ir/typeNode.h"
22
23 namespace ark::es2panda::compiler::ast_verifier {
24
operator ()(CheckContext & ctx,const ir::AstNode * ast)25 CheckResult VariableHasScope::operator()(CheckContext &ctx, const ir::AstNode *ast)
26 {
27 if (!ast->IsIdentifier()) {
28 // Check invariant of Identifier only
29 return {CheckDecision::CORRECT, CheckAction::CONTINUE};
30 }
31
32 // we will check invariant for only local variables of identifiers
33 if (const auto maybeVar = GetLocalScopeVariable(allocator_, ctx, ast); maybeVar.has_value()) {
34 const auto var = *maybeVar;
35 const auto scope = var->GetScope();
36 if (scope == nullptr) {
37 ctx.AddCheckMessage("NULL_SCOPE_LOCAL_VAR", *ast, ast->Start());
38 return {CheckDecision::INCORRECT, CheckAction::CONTINUE};
39 }
40
41 auto result = std::make_tuple(CheckDecision::CORRECT, CheckAction::CONTINUE);
42 if (!ScopeEncloseVariable(ctx, var)) {
43 result = {CheckDecision::INCORRECT, CheckAction::CONTINUE};
44 }
45
46 return result;
47 }
48
49 return {CheckDecision::CORRECT, CheckAction::CONTINUE};
50 }
51
GetLocalScopeVariable(ArenaAllocator & allocator,CheckContext & ctx,const ir::AstNode * ast)52 std::optional<varbinder::LocalVariable *> VariableHasScope::GetLocalScopeVariable(ArenaAllocator &allocator,
53 CheckContext &ctx,
54 const ir::AstNode *ast)
55 {
56 if (!ast->IsIdentifier()) {
57 return std::nullopt;
58 }
59
60 auto invariantHasVariable = IdentifierHasVariable {allocator};
61 const auto variable = ast->AsIdentifier()->Variable();
62 const auto [decision, action] = invariantHasVariable(ctx, ast);
63
64 if (variable == nullptr) {
65 // NOTE(kkonkuznetsov): variable should not be null
66 // but currently some identifiers do not have variables,
67 // see exceptions in IdentifierHasVariable check
68 return std::nullopt;
69 }
70
71 if (decision == CheckDecision::CORRECT && variable->IsLocalVariable()) {
72 const auto localVar = variable->AsLocalVariable();
73 if (localVar->HasFlag(varbinder::VariableFlags::LOCAL)) {
74 return localVar;
75 }
76 }
77
78 return std::nullopt;
79 }
80
ScopeEncloseVariable(CheckContext & ctx,const varbinder::LocalVariable * var)81 bool VariableHasScope::ScopeEncloseVariable(CheckContext &ctx, const varbinder::LocalVariable *var)
82 {
83 ASSERT(var);
84
85 const auto scope = var->GetScope();
86 if (scope == nullptr || var->Declaration() == nullptr) {
87 return true;
88 }
89
90 const auto node = var->Declaration()->Node();
91 if (node == nullptr) {
92 return true;
93 }
94
95 const auto varStart = node->Start();
96 bool isOk = true;
97 if (scope->Bindings().count(var->Name()) == 0) {
98 ctx.AddCheckMessage("SCOPE_DO_NOT_ENCLOSE_LOCAL_VAR", *node, varStart);
99 isOk = false;
100 }
101
102 const auto scopeNode = scope->Node();
103 const auto varNode = node;
104 bool skip = CheckAstExceptions(varNode);
105
106 if (!IsContainedIn(varNode, scopeNode) || scopeNode == nullptr) {
107 if (!skip) {
108 ctx.AddCheckMessage("SCOPE_NODE_DONT_DOMINATE_VAR_NODE", *node, varStart);
109 isOk = false;
110 }
111 }
112
113 const auto &decls = scope->Decls();
114 const auto declDominate = std::count(decls.begin(), decls.end(), var->Declaration());
115 if (declDominate == 0) {
116 if (!skip) {
117 ctx.AddCheckMessage("SCOPE_DECL_DONT_DOMINATE_VAR_DECL", *node, varStart);
118 isOk = false;
119 }
120 }
121
122 return isOk;
123 }
124
CheckAstExceptions(const ir::AstNode * ast)125 bool VariableHasScope::CheckAstExceptions(const ir::AstNode *ast)
126 {
127 // Labels are attached to loop scopes,
128 // however label identifier is outside of loop.
129 // Example:
130 //
131 // ```
132 // loop: for (let i = 0; i < 10; i++) {
133 // }
134 // ```
135 return ast->IsLabelledStatement();
136 }
137
138 } // namespace ark::es2panda::compiler::ast_verifier
139