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