1 /**
2 * Copyright (c) 2021-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 "ETSchecker.h"
17
18 namespace ark::es2panda::checker {
19
CheckerContext(Checker *checker, CheckerStatus newStatus, ETSObjectType const *containingClass, Signature *containingSignature)20 CheckerContext::CheckerContext(Checker *checker, CheckerStatus newStatus, ETSObjectType const *containingClass,
21 Signature *containingSignature)
22 : parent_(checker),
23 status_(newStatus),
24 capturedVars_(parent_->Allocator()->Adapter()),
25 smartCasts_(parent_->Allocator()->Adapter()),
26 containingClass_(containingClass),
27 containingSignature_(containingSignature),
28 testSmartCasts_(parent_->Allocator()->Adapter()),
29 breakSmartCasts_(parent_->Allocator()->Adapter())
30 {
31 }
32
33 void CheckerContext::SetSmartCast(varbinder::Variable const *const variable, checker::Type *const smartType) noexcept
34 {
35 // Just block captured and modified variables here instead of finding all their usage occurrences.
36 if (!variable->HasFlag(varbinder::VariableFlags::CAPTURED_MODIFIED)) {
37 smartCasts_.insert_or_assign(variable, smartType);
38 }
39 }
40
41 SmartCastTypes CheckerContext::CloneTestSmartCasts(bool const clearData) noexcept
42 {
43 if (testSmartCasts_.empty()) {
44 return std::nullopt;
45 }
46
47 SmartCastTestArray smartCasts {};
48 smartCasts.reserve(testSmartCasts_.size());
49
50 for (auto [variable, types] : testSmartCasts_) {
51 if (types.first != nullptr || types.second != nullptr) {
52 smartCasts.emplace_back(variable, types.first, types.second);
53 }
54 }
55
56 if (clearData) {
57 ClearTestSmartCasts();
58 }
59
60 return std::make_optional(smartCasts);
61 }
62
63 SmartCastArray CheckerContext::CloneSmartCasts(bool const clearData) noexcept
64 {
65 SmartCastArray smartCasts {};
66
67 if (!smartCasts_.empty()) {
68 smartCasts.reserve(smartCasts_.size());
69
70 for (auto const [variable, type] : smartCasts_) {
71 smartCasts.emplace_back(variable, type);
72 }
73 }
74
75 if (clearData) {
76 ClearSmartCasts();
77 }
78
79 return smartCasts;
80 }
81
RestoreSmartCasts(SmartCastArray const &otherSmartCasts)82 void CheckerContext::RestoreSmartCasts(SmartCastArray const &otherSmartCasts)
83 {
84 smartCasts_.clear();
85 if (!otherSmartCasts.empty()) {
86 for (auto [variable, type] : otherSmartCasts) {
87 smartCasts_.emplace(variable, type);
88 }
89 }
90 }
91
92 void CheckerContext::RemoveSmartCasts(SmartCastArray const &otherSmartCasts) noexcept
93 {
94 if (!smartCasts_.empty()) {
95 auto it = smartCasts_.begin();
96 while (it != smartCasts_.end()) {
97 if (std::find_if(otherSmartCasts.begin(), otherSmartCasts.end(), [&it](auto const &item) -> bool {
98 return item.first == it->first;
99 }) == otherSmartCasts.end()) {
100 it = smartCasts_.erase(it);
101 } else {
102 ++it;
103 }
104 }
105 }
106 }
107
108 // Auxiliary private method returning combined type (if types differ) or 'nullptr' if types are identical
109 // and no smart cast change is required.
110 checker::Type *CheckerContext::CombineTypes(checker::Type *const typeOne, checker::Type *const typeTwo) const noexcept
111 {
112 ASSERT(typeOne != nullptr && typeTwo != nullptr);
113 auto *const checker = parent_->AsETSChecker();
114
115 if (checker->Relation()->IsIdenticalTo(typeOne, typeTwo)) {
116 return nullptr;
117 }
118
119 return checker->CreateETSUnionType({typeOne, typeTwo});
120 }
121
CombineSmartCasts(SmartCastArray const &otherSmartCasts)122 void CheckerContext::CombineSmartCasts(SmartCastArray const &otherSmartCasts)
123 {
124 auto *const checker = parent_->AsETSChecker();
125
126 for (auto [variable, type] : otherSmartCasts) {
127 auto const it = smartCasts_.find(variable);
128 if (it == smartCasts_.end()) {
129 continue;
130 }
131 // Smart cast presents in both sets
132 if (auto *const smartType = CombineTypes(it->second, type); smartType != nullptr) {
133 // Remove it or set to new combined value
134 if (checker->Relation()->IsIdenticalTo(it->first->TsType(), smartType)) {
135 smartCasts_.erase(it);
136 } else {
137 it->second = smartType;
138 }
139 }
140 }
141
142 // Remove smart casts that don't present in the other set.
143 RemoveSmartCasts(otherSmartCasts);
144 }
145
146 // Second return value shows if the 'IN_LOOP' flag should be cleared on exit from the loop (case of nested loops).
147 std::pair<SmartCastArray, bool> CheckerContext::EnterLoop(ir::LoopStatement const &loop) noexcept
148 {
149 bool const clearFlag = !IsInLoop();
150 if (clearFlag) {
151 status_ |= CheckerStatus::IN_LOOP;
152 }
153
154 auto smartCasts = CloneSmartCasts();
155
156 SmartVariables changedVariables {};
157 loop.Iterate([this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
158
159 if (!changedVariables.empty()) {
160 for (auto const *variable : changedVariables) {
161 smartCasts_.erase(variable);
162 }
163 }
164
165 return {std::move(smartCasts), clearFlag};
166 }
167
168 void CheckerContext::ExitLoop(SmartCastArray &prevSmartCasts, bool const clearFlag,
169 ir::LoopStatement *loopStatement) noexcept
170 {
171 if (clearFlag) {
172 status_ &= ~CheckerStatus::IN_LOOP;
173 }
174
175 if (!breakSmartCasts_.empty()) {
176 auto it = breakSmartCasts_.begin();
177
178 while (it != breakSmartCasts_.end()) {
179 if (it->first != loopStatement) {
180 ++it;
181 } else {
182 CombineSmartCasts(it->second);
183 it = breakSmartCasts_.erase(it);
184 }
185 }
186 }
187
188 // Now we don't process smart casts inside the loops correctly, thus just combine them on exit from the loop.
189 CombineSmartCasts(prevSmartCasts);
190 }
191
192 void CheckerContext::CheckAssignments(ir::AstNode const *node, SmartVariables &changedVariables) noexcept
193 {
194 if (node == nullptr) { // Just in case!
195 return;
196 }
197
198 if (!node->IsAssignmentExpression()) {
199 node->Iterate(
200 [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
201 return;
202 }
203
204 auto const *assignment = node->AsAssignmentExpression();
205 if (assignment->Left()->IsIdentifier()) {
206 auto const *const ident = assignment->Left()->AsIdentifier();
207
208 auto const *variable = ident->Variable();
209 if (variable == nullptr) {
210 // NOTE: we're interesting in the local variables ONLY!
211 variable = parent_->AsETSChecker()->FindVariableInFunctionScope(
212 ident->Name(), varbinder::ResolveBindingOptions::ALL_NON_TYPE);
213 }
214
215 if (variable != nullptr) {
216 changedVariables.insert(variable);
217 }
218 }
219
220 assignment->Right()->Iterate(
221 [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
222 }
223
224 SmartCastArray CheckerContext::CheckTryBlock(ir::BlockStatement const &tryBlock) noexcept
225 {
226 SmartVariables changedVariables {};
227 tryBlock.Iterate(
228 [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
229
230 SmartCastArray smartCasts {};
231 if (!smartCasts_.empty()) {
232 smartCasts.reserve(smartCasts_.size());
233
234 for (auto const [variable, type] : smartCasts_) {
235 if (changedVariables.find(variable) == changedVariables.end()) {
236 smartCasts.emplace_back(variable, type);
237 }
238 }
239 }
240
241 return smartCasts;
242 }
243
244 // Check that the expression is a part of logical OR/AND or unary negation operators chain
245 // (other cases are not interested)
246 bool CheckerContext::IsInValidChain(ir::AstNode const *parent) noexcept
247 {
248 while (parent != nullptr && !parent->IsIfStatement() && !parent->IsConditionalExpression()) {
249 if (parent->IsBinaryExpression()) {
250 auto const operation = parent->AsBinaryExpression()->OperatorType();
251 if (operation != lexer::TokenType::PUNCTUATOR_LOGICAL_OR &&
252 operation != lexer::TokenType::PUNCTUATOR_LOGICAL_AND) {
253 return false;
254 }
255 } else if (parent->IsUnaryExpression()) {
256 if (parent->AsUnaryExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK) {
257 return false;
258 }
259 } else {
260 return false;
261 }
262 parent = parent->Parent();
263 }
264 return parent != nullptr;
265 }
266
267 void CheckerContext::CheckIdentifierSmartCastCondition(ir::Identifier const *const identifier) noexcept
268 {
269 if (!IsInTestExpression()) {
270 return;
271 }
272
273 auto const *const variable = identifier->Variable();
274 ASSERT(variable != nullptr);
275
276 // Smart cast for extended conditional check can be applied only to the variables of reference types.
277 if (auto const *const variableType = variable->TsType(); !variableType->IsETSReferenceType()) {
278 return;
279 }
280
281 if (!IsInValidChain(identifier->Parent())) {
282 return;
283 }
284
285 ASSERT(testCondition_.variable == nullptr);
286 if (identifier->TsType()->PossiblyETSNullish()) {
287 testCondition_ = {variable, parent_->AsETSChecker()->GlobalETSNullType(), true, false};
288 }
289 }
290
291 void CheckerContext::CheckUnarySmartCastCondition(ir::UnaryExpression const *const unaryExpression) noexcept
292 {
293 if (!IsInTestExpression() || unaryExpression->OperatorType() != lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK) {
294 return;
295 }
296
297 auto const *const argument = unaryExpression->Argument();
298 if (argument == nullptr || (!argument->IsIdentifier() && !argument->IsBinaryExpression())) {
299 return;
300 }
301
302 if (!IsInValidChain(unaryExpression->Parent())) {
303 return;
304 }
305
306 if (testCondition_.variable != nullptr) {
307 testCondition_.negate = !testCondition_.negate;
308 }
309 }
310
311 void CheckerContext::CheckBinarySmartCastCondition(ir::BinaryExpression *const binaryExpression) noexcept
312 {
313 if (!IsInTestExpression() || !IsInValidChain(binaryExpression->Parent())) {
314 return;
315 }
316
317 if (auto const operatorType = binaryExpression->OperatorType(); operatorType == lexer::TokenType::KEYW_INSTANCEOF) {
318 ASSERT(testCondition_.variable == nullptr);
319 if (binaryExpression->Left()->IsIdentifier()) {
320 testCondition_ = {binaryExpression->Left()->AsIdentifier()->Variable(),
321 binaryExpression->Right()->TsType()};
322 }
323 } else if (operatorType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL ||
324 operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
325 operatorType == lexer::TokenType::PUNCTUATOR_EQUAL ||
326 operatorType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL) {
327 ASSERT(testCondition_.variable == nullptr);
328 CheckSmartCastEqualityCondition(binaryExpression);
329 }
330 }
331
332 // Extracted just to avoid large length and depth of method 'CheckBinarySmartCastCondition()'.
333 void CheckerContext::CheckSmartCastEqualityCondition(ir::BinaryExpression *const binaryExpression) noexcept
334 {
335 varbinder::Variable const *variable = nullptr;
336 checker::Type *testedType = nullptr;
337 auto const operatorType = binaryExpression->OperatorType();
338
339 bool strict = operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
340 operatorType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL;
341
342 // extracted just to avoid extra nested level
343 auto const getTestedType = [&variable, &testedType, &strict](ir::Identifier const *const identifier,
344 ir::Expression *const expression) -> void {
345 ASSERT(identifier != nullptr && expression != nullptr);
346 variable = identifier->Variable();
347 if (expression->IsLiteral()) {
348 testedType = expression->TsType();
349 if (!expression->IsNullLiteral() && !expression->IsUndefinedLiteral()) {
350 strict = false;
351 }
352 }
353 };
354
355 if (binaryExpression->Left()->IsIdentifier()) {
356 getTestedType(binaryExpression->Left()->AsIdentifier(), binaryExpression->Right());
357 }
358
359 if (testedType == nullptr && binaryExpression->Right()->IsIdentifier()) {
360 getTestedType(binaryExpression->Right()->AsIdentifier(), binaryExpression->Left());
361 }
362
363 if (testedType != nullptr) {
364 bool const negate = operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
365 operatorType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL;
366
367 if (testedType->DefinitelyETSNullish()) {
368 testCondition_ = {variable, testedType, negate, strict};
369 }
370 }
371 }
372
373 void CheckerContext::ClearTestSmartCasts() noexcept
374 {
375 testCondition_ = {};
376 testSmartCasts_.clear();
377 operatorType_ = lexer::TokenType::EOS;
378 }
379
380 checker::Type *CheckerContext::GetSmartCast(varbinder::Variable const *const variable) const noexcept
381 {
382 if (IsInTestExpression()) {
383 if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_AND) {
384 if (auto const it = testSmartCasts_.find(variable);
385 it != testSmartCasts_.end() && it->second.first != nullptr) {
386 return it->second.first;
387 }
388 } else if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_OR) {
389 if (auto const it = testSmartCasts_.find(variable);
390 it != testSmartCasts_.end() && it->second.second != nullptr) {
391 return it->second.second;
392 }
393 }
394 }
395
396 auto const it = smartCasts_.find(variable);
397 return it == smartCasts_.end() ? nullptr : it->second;
398 }
399
OnBreakStatement(ir::BreakStatement const *breakStatement)400 void CheckerContext::OnBreakStatement(ir::BreakStatement const *breakStatement)
401 {
402 ir::Statement const *targetStatement = breakStatement->Target()->AsStatement();
403 ASSERT(targetStatement != nullptr);
404 if (targetStatement->IsLabelledStatement()) {
405 targetStatement = targetStatement->AsLabelledStatement()->Body();
406 }
407 ASSERT(targetStatement != nullptr);
408
409 auto const inInnerScope = [targetStatement](varbinder::Scope const *scope, ir::AstNode const *parent) -> bool {
410 do {
411 parent = parent->Parent();
412 if (parent->IsScopeBearer() && parent->Scope() == scope) {
413 return true;
414 }
415 } while (parent != targetStatement);
416 return false;
417 };
418
419 status_ |= CheckerStatus::MEET_BREAK;
420
421 if (smartCasts_.empty()) {
422 return;
423 }
424
425 SmartCastArray smartCasts {};
426 smartCasts.reserve(smartCasts_.size());
427
428 for (auto const [variable, type] : smartCasts_) {
429 if (!inInnerScope(variable->AsLocalVariable()->GetScope(), breakStatement)) {
430 smartCasts.emplace_back(variable, type);
431 }
432 }
433
434 if (!smartCasts.empty()) {
435 AddBreakSmartCasts(targetStatement, std::move(smartCasts));
436 }
437
438 ClearSmartCasts();
439 }
440
AddBreakSmartCasts(ir::Statement const *targetStatement, SmartCastArray &&smartCasts)441 void CheckerContext::AddBreakSmartCasts(ir::Statement const *targetStatement, SmartCastArray &&smartCasts)
442 {
443 breakSmartCasts_.emplace(targetStatement, std::move(smartCasts));
444 }
445
CombineBreakSmartCasts(ir::Statement const *targetStatement)446 void CheckerContext::CombineBreakSmartCasts(ir::Statement const *targetStatement)
447 {
448 ASSERT(smartCasts_.empty());
449
450 if (!breakSmartCasts_.empty()) {
451 bool firstCase = true;
452 auto it = breakSmartCasts_.begin();
453
454 while (it != breakSmartCasts_.end()) {
455 if (it->first != targetStatement) {
456 ++it;
457 continue;
458 }
459
460 if (firstCase) {
461 firstCase = false;
462 RestoreSmartCasts(it->second);
463 } else {
464 CombineSmartCasts(it->second);
465 }
466
467 it = breakSmartCasts_.erase(it);
468 }
469 }
470 }
471 } // namespace ark::es2panda::checker
472