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