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 18namespace ark::es2panda::checker { 19 20CheckerContext::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 33void 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 41SmartCastTypes 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 63SmartCastArray 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 82void 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 92void 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. 110checker::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 122void 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). 147std::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 168void 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 192void 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 224SmartCastArray 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) 246bool 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 267void 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 291void 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 311void 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()'. 333void 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 373void CheckerContext::ClearTestSmartCasts() noexcept 374{ 375 testCondition_ = {}; 376 testSmartCasts_.clear(); 377 operatorType_ = lexer::TokenType::EOS; 378} 379 380checker::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 400void 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 441void CheckerContext::AddBreakSmartCasts(ir::Statement const *targetStatement, SmartCastArray &&smartCasts) 442{ 443 breakSmartCasts_.emplace(targetStatement, std::move(smartCasts)); 444} 445 446void 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