• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-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 "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 
SetSmartCast(varbinder::Variable const * const variable,checker::Type * const smartType)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 
CloneTestSmartCasts(bool const clearData)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 
CloneSmartCasts(bool const clearData)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 
RemoveSmartCasts(SmartCastArray const & otherSmartCasts)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.
CombineTypes(checker::Type * const typeOne,checker::Type * const typeTwo) const110 checker::Type *CheckerContext::CombineTypes(checker::Type *const typeOne, checker::Type *const typeTwo) const noexcept
111 {
112     ES2PANDA_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).
EnterLoop(const ir::LoopStatement & loop,const SmartCastTypes loopConditionSmartCasts)147 std::pair<SmartCastArray, bool> CheckerContext::EnterLoop(const ir::LoopStatement &loop,
148                                                           const SmartCastTypes loopConditionSmartCasts) noexcept
149 {
150     bool const clearFlag = !IsInLoop();
151     if (clearFlag) {
152         status_ |= CheckerStatus::IN_LOOP;
153     }
154 
155     auto smartCasts = CloneSmartCasts();
156 
157     ReassignedVariableMap changedVariables {};
158     if (loop.IsWhileStatement()) {
159         // In 'while' loops, we only invalidate smart casts for reassigned variables in the body. If a variable is
160         // being reassigned in the test condition, it'll have that type until it's reassigned in the body
161         loop.AsWhileStatement()->Body()->Iterate(
162             [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
163     } else {
164         // Handling 'for' and 'do-while' loops is a bit different, as the above statement on 'while' loops doesn't hold
165         // here. Later we'll need to implement these checks too.
166         loop.Iterate(
167             [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
168     }
169 
170     const auto variableIsConstrainedInPrecondition = [&loopConditionSmartCasts](const varbinder::Variable *var) {
171         if (!loopConditionSmartCasts.has_value()) {
172             return false;
173         }
174 
175         return std::find_if(loopConditionSmartCasts->begin(), loopConditionSmartCasts->end(),
176                             [&var](const SmartCastTuple &smartCast) { return std::get<0>(smartCast) == var; }) !=
177                loopConditionSmartCasts->end();
178     };
179 
180     if (!changedVariables.empty()) {
181         for (const auto &[variable, isAccessedAfterReassign] : changedVariables) {
182             // Two cases to invalidate a smart cast:
183             //   - when a variable is used in the body after reassignment
184             //   - when a variable is reassigned, and the precondition of the loop does not restrict its type
185             // Currently it allows us to keep the smart types in some cases. It's good enough for now, as a
186             // complete solution would require CFG/DFG, and smart casts will be rewritten with those in the future.
187             if (isAccessedAfterReassign || !(variableIsConstrainedInPrecondition(variable))) {
188                 smartCasts_.erase(variable);
189             }
190         }
191     }
192 
193     return {std::move(smartCasts), clearFlag};
194 }
195 
ExitLoop(SmartCastArray & prevSmartCasts,bool const clearFlag,ir::LoopStatement * loopStatement)196 void CheckerContext::ExitLoop(SmartCastArray &prevSmartCasts, bool const clearFlag,
197                               ir::LoopStatement *loopStatement) noexcept
198 {
199     if (clearFlag) {
200         status_ &= ~CheckerStatus::IN_LOOP;
201     }
202 
203     if (!breakSmartCasts_.empty()) {
204         auto it = breakSmartCasts_.begin();
205 
206         while (it != breakSmartCasts_.end()) {
207             if (it->first != loopStatement) {
208                 ++it;
209             } else {
210                 CombineSmartCasts(it->second);
211                 it = breakSmartCasts_.erase(it);
212             }
213         }
214     }
215 
216     //  Now we don't process smart casts inside the loops correctly, thus just combine them on exit from the loop.
217     CombineSmartCasts(prevSmartCasts);
218 }
219 
CheckAssignments(ir::AstNode const * node,ReassignedVariableMap & changedVariables) const220 void CheckerContext::CheckAssignments(ir::AstNode const *node, ReassignedVariableMap &changedVariables) const noexcept
221 {
222     if (node == nullptr) {  //  Just in case!
223         return;
224     }
225 
226     if (!node->IsAssignmentExpression()) {
227         // If the node is an identifier, check if it was reassigned before
228         if (node->IsIdentifier() && changedVariables.count(node->AsIdentifier()->Variable()) != 0) {
229             changedVariables[node->AsIdentifier()->Variable()] = true;
230         }
231 
232         node->Iterate(
233             [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
234         return;
235     }
236 
237     auto const *assignment = node->AsAssignmentExpression();
238     if (assignment->Left()->IsIdentifier()) {
239         auto const *const ident = assignment->Left()->AsIdentifier();
240 
241         auto const *variable = ident->Variable();
242         if (variable == nullptr) {
243             //  NOTE: we're interesting in the local variables ONLY!
244             variable = parent_->AsETSChecker()->FindVariableInFunctionScope(ident->Name());
245         }
246 
247         if (variable != nullptr) {
248             changedVariables.insert({variable, false});
249         }
250     }
251 
252     assignment->Right()->Iterate(
253         [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
254 }
255 
CheckTryBlock(ir::BlockStatement const & tryBlock)256 SmartCastArray CheckerContext::CheckTryBlock(ir::BlockStatement const &tryBlock) noexcept
257 {
258     ReassignedVariableMap changedVariables {};
259     tryBlock.Iterate(
260         [this, &changedVariables](ir::AstNode *childNode) { CheckAssignments(childNode, changedVariables); });
261 
262     SmartCastArray smartCasts {};
263     if (!smartCasts_.empty()) {
264         smartCasts.reserve(smartCasts_.size());
265 
266         for (const auto &[variable, type] : smartCasts_) {
267             if (changedVariables.count(variable) == 0) {
268                 smartCasts.emplace_back(variable, type);
269             }
270         }
271     }
272 
273     return smartCasts;
274 }
275 
276 //  Check that the expression is a part of logical OR/AND or unary negation operators chain
277 //  (other cases are not interested)
IsInValidChain(ir::AstNode const * parent)278 bool CheckerContext::IsInValidChain(ir::AstNode const *parent) noexcept
279 {
280     while (parent != nullptr) {
281         if (parent->IsBinaryExpression()) {
282             auto const operation = parent->AsBinaryExpression()->OperatorType();
283             if (operation != lexer::TokenType::PUNCTUATOR_LOGICAL_OR &&
284                 operation != lexer::TokenType::PUNCTUATOR_LOGICAL_AND) {
285                 return false;
286             }
287         } else if (parent->IsUnaryExpression()) {
288             if (parent->AsUnaryExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK) {
289                 return false;
290             }
291         } else if (parent->IsExpression()) {
292             return parent->IsConditionalExpression();
293         } else {
294             return true;
295         }
296         parent = parent->Parent();
297     }
298     return parent != nullptr;
299 }
300 
CheckIdentifierSmartCastCondition(ir::Identifier const * const identifier)301 void CheckerContext::CheckIdentifierSmartCastCondition(ir::Identifier const *const identifier) noexcept
302 {
303     if (!IsInTestExpression()) {
304         return;
305     }
306 
307     auto const *const variable = identifier->Variable();
308     ES2PANDA_ASSERT(variable != nullptr);
309 
310     //  Smart cast for extended conditional check can be applied only to the variables of reference types.
311     if (auto const *const variableType = variable->TsType(); !variableType->IsETSReferenceType()) {
312         return;
313     }
314 
315     if (!IsInValidChain(identifier->Parent())) {
316         return;
317     }
318 
319     if (identifier->TsType()->PossiblyETSNullish()) {
320         ES2PANDA_ASSERT(testCondition_.variable == nullptr);
321         testCondition_ = {variable, parent_->AsETSChecker()->GlobalETSNullType(), true, false};
322     }
323 }
324 
CheckUnarySmartCastCondition(ir::UnaryExpression const * const unaryExpression)325 void CheckerContext::CheckUnarySmartCastCondition(ir::UnaryExpression const *const unaryExpression) noexcept
326 {
327     if (!IsInTestExpression() || unaryExpression->OperatorType() != lexer::TokenType::PUNCTUATOR_EXCLAMATION_MARK) {
328         return;
329     }
330 
331     auto const *const argument = unaryExpression->Argument();
332     if (argument == nullptr || (!argument->IsIdentifier() && !argument->IsBinaryExpression())) {
333         return;
334     }
335 
336     if (!IsInValidChain(unaryExpression->Parent())) {
337         return;
338     }
339 
340     if (testCondition_.variable != nullptr) {
341         testCondition_.negate = !testCondition_.negate;
342     }
343 }
344 
CheckBinarySmartCastCondition(ir::BinaryExpression * const binaryExpression)345 void CheckerContext::CheckBinarySmartCastCondition(ir::BinaryExpression *const binaryExpression) noexcept
346 {
347     if (!IsInTestExpression() || !IsInValidChain(binaryExpression->Parent())) {
348         return;
349     }
350 
351     if (auto const operatorType = binaryExpression->OperatorType(); operatorType == lexer::TokenType::KEYW_INSTANCEOF) {
352         if (binaryExpression->Left()->IsIdentifier()) {
353             if (binaryExpression->Right()->TsType() == nullptr) {
354                 return;
355             }
356             // NOTE(pantos) Issue with generics
357             // eg
358             // class C <T> { ... }
359             // let x = new C<...>
360             // if (x instanceof C && x.fld instanceof ...
361             const auto variable = binaryExpression->Left()->AsIdentifier()->Variable();
362             auto type = binaryExpression->Right()->TsType();
363             auto smartIt = smartCasts_.find(variable);
364             // NOTE(pantos) Handle union types e.g. C<A>|C<B>|...
365             if (type->HasTypeFlag(TypeFlag::GENERIC) && smartIt != smartCasts_.end() && type->IsETSObjectType() &&
366                 type->AsETSObjectType()->IsSameBasedGeneric(type->AsETSObjectType()->GetRelation(), smartIt->second)) {
367                 // Replace generic type with instantiated one, e.g. C<T> with C<A>
368                 type = smartIt->second;
369             }
370             ES2PANDA_ASSERT(testCondition_.variable == nullptr);
371             testCondition_ = {variable, type};
372         }
373     } else if (operatorType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL ||
374                operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
375                operatorType == lexer::TokenType::PUNCTUATOR_EQUAL ||
376                operatorType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL) {
377         CheckSmartCastEqualityCondition(binaryExpression);
378     }
379 }
380 
381 //  Extracted just to avoid large length and depth of method 'CheckBinarySmartCastCondition()'.
CheckSmartCastEqualityCondition(ir::BinaryExpression * const binaryExpression)382 void CheckerContext::CheckSmartCastEqualityCondition(ir::BinaryExpression *const binaryExpression) noexcept
383 {
384     varbinder::Variable const *variable = nullptr;
385     checker::Type *testedType = nullptr;
386     auto const operatorType = binaryExpression->OperatorType();
387 
388     bool strict = operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
389                   operatorType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL;
390 
391     // extracted just to avoid extra nested level
392     auto const getTestedType = [&variable, &testedType, &strict](ir::Identifier const *const identifier,
393                                                                  ir::Expression *const expression) -> void {
394         ES2PANDA_ASSERT(identifier != nullptr && expression != nullptr);
395         variable = identifier->Variable();
396         if (expression->IsLiteral()) {
397             testedType = expression->TsType();
398             if (!expression->IsNullLiteral() && !expression->IsUndefinedLiteral()) {
399                 strict = false;
400             }
401         }
402     };
403 
404     if (binaryExpression->Left()->IsIdentifier()) {
405         getTestedType(binaryExpression->Left()->AsIdentifier(), binaryExpression->Right());
406     }
407 
408     if (testedType == nullptr && binaryExpression->Right()->IsIdentifier()) {
409         getTestedType(binaryExpression->Right()->AsIdentifier(), binaryExpression->Left());
410     }
411 
412     if (testedType != nullptr) {
413         bool const negate = operatorType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
414                             operatorType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL;
415 
416         if (testedType->DefinitelyETSNullish()) {
417             ES2PANDA_ASSERT(testCondition_.variable == nullptr);
418             testCondition_ = {variable, testedType, negate, strict};
419         }
420     }
421 }
422 
ClearTestSmartCasts()423 void CheckerContext::ClearTestSmartCasts() noexcept
424 {
425     testCondition_ = {};
426     testSmartCasts_.clear();
427     operatorType_ = lexer::TokenType::EOS;
428 }
429 
GetSmartCast(varbinder::Variable const * const variable) const430 checker::Type *CheckerContext::GetSmartCast(varbinder::Variable const *const variable) const noexcept
431 {
432     if (IsInTestExpression()) {
433         if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_AND) {
434             if (auto const it = testSmartCasts_.find(variable);
435                 it != testSmartCasts_.end() && it->second.first != nullptr) {
436                 return it->second.first;
437             }
438         } else if (operatorType_ == lexer::TokenType::PUNCTUATOR_LOGICAL_OR) {
439             if (auto const it = testSmartCasts_.find(variable);
440                 it != testSmartCasts_.end() && it->second.second != nullptr) {
441                 return it->second.second;
442             }
443         }
444     }
445 
446     auto const it = smartCasts_.find(variable);
447     return it == smartCasts_.end() ? nullptr : it->second;
448 }
449 
OnBreakStatement(ir::BreakStatement const * breakStatement)450 void CheckerContext::OnBreakStatement(ir::BreakStatement const *breakStatement)
451 {
452     if (breakStatement->Target() == nullptr) {
453         ES2PANDA_ASSERT(parent_->IsAnyError());
454         return;
455     }
456 
457     ir::Statement const *targetStatement = breakStatement->Target()->AsStatement();
458     ES2PANDA_ASSERT(targetStatement != nullptr);
459     if (targetStatement->IsLabelledStatement()) {
460         targetStatement = targetStatement->AsLabelledStatement()->Body();
461     }
462     ES2PANDA_ASSERT(targetStatement != nullptr);
463 
464     auto const inInnerScope = [targetStatement](varbinder::Scope const *scope, ir::AstNode const *parent) -> bool {
465         do {
466             parent = parent->Parent();
467             if (parent->IsScopeBearer() && parent->Scope() == scope) {
468                 return true;
469             }
470         } while (parent != targetStatement);
471         return false;
472     };
473 
474     status_ |= CheckerStatus::MEET_BREAK;
475 
476     if (smartCasts_.empty()) {
477         return;
478     }
479 
480     SmartCastArray smartCasts {};
481     smartCasts.reserve(smartCasts_.size());
482 
483     for (auto const [variable, type] : smartCasts_) {
484         if (!inInnerScope(variable->AsLocalVariable()->GetScope(), breakStatement)) {
485             smartCasts.emplace_back(variable, type);
486         }
487     }
488 
489     if (!smartCasts.empty()) {
490         AddBreakSmartCasts(targetStatement, std::move(smartCasts));
491     }
492 
493     ClearSmartCasts();
494 }
495 
AddBreakSmartCasts(ir::Statement const * targetStatement,SmartCastArray && smartCasts)496 void CheckerContext::AddBreakSmartCasts(ir::Statement const *targetStatement, SmartCastArray &&smartCasts)
497 {
498     breakSmartCasts_.emplace(targetStatement, std::move(smartCasts));
499 }
500 
CombineBreakSmartCasts(ir::Statement const * targetStatement)501 void CheckerContext::CombineBreakSmartCasts(ir::Statement const *targetStatement)
502 {
503     ES2PANDA_ASSERT(smartCasts_.empty());
504 
505     if (!breakSmartCasts_.empty()) {
506         bool firstCase = true;
507         auto it = breakSmartCasts_.begin();
508 
509         while (it != breakSmartCasts_.end()) {
510             if (it->first != targetStatement) {
511                 ++it;
512                 continue;
513             }
514 
515             if (firstCase) {
516                 firstCase = false;
517                 RestoreSmartCasts(it->second);
518             } else {
519                 CombineSmartCasts(it->second);
520             }
521 
522             it = breakSmartCasts_.erase(it);
523         }
524     }
525 }
526 }  // namespace ark::es2panda::checker
527