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