1 2/** 3 * Copyright (c) 2024 Huawei Device Co., Ltd. 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "etsWarningAnalyzer.h" 18 19#include "parser/program/program.h" 20#include "util/options.h" 21#include "ir/expressions/binaryExpression.h" 22#include "ir/base/methodDefinition.h" 23#include "ir/base/scriptFunction.h" 24#include "ir/statements/classDeclaration.h" 25#include "ir/statements/expressionStatement.h" 26#include "ir/statements/blockStatement.h" 27#include "ir/expressions/assignmentExpression.h" 28#include "ir/expressions/callExpression.h" 29#include "ir/expressions/identifier.h" 30#include "ir/expressions/memberExpression.h" 31#include "ir/ets/etsTypeReferencePart.h" 32#include "ir/ets/etsTypeReference.h" 33#include "ir/base/classDefinition.h" 34#include "ir/statements/forOfStatement.h" 35#include "ir/statements/variableDeclarator.h" 36#include "ir/statements/variableDeclaration.h" 37#include "ir/expressions/updateExpression.h" 38 39namespace ark::es2panda::checker { 40 41void ETSWarningAnalyzer::AnalyzeClassDefForFinalModifier(const ir::ClassDefinition *classDef) 42{ 43 ASSERT(classDef != nullptr); 44 45 if (program_ == nullptr || classDef->IsFinal() || classDef->IsAbstract() || classDef->IsStatic() || 46 classDef->IsGlobal() || classDef->IsExported()) { 47 return; 48 } 49 50 const auto statements = program_->Ast()->Statements(); 51 for (const auto *it : statements) { 52 if (!it->IsClassDeclaration() || 53 classDef->Ident()->Name() == it->AsClassDeclaration()->Definition()->Ident()->Name()) { 54 continue; 55 } 56 57 const auto *itAsClassDef = it->AsClassDeclaration()->Definition(); 58 59 if (!itAsClassDef->IsGlobal()) { 60 const auto *superClass = itAsClassDef->Super(); 61 62 if (superClass == nullptr) { 63 continue; 64 } 65 66 if (superClass->IsETSTypeReference() && superClass->AsETSTypeReference()->Part()->Name()->IsIdentifier() && 67 superClass->AsETSTypeReference()->Part()->Name()->AsIdentifier()->Name() == classDef->Ident()->Name()) { 68 return; 69 } 70 } 71 } 72 73 ETSThrowWarning("Suggest 'final' modifier for class", classDef->Ident()->Start()); 74} 75 76void ETSWarningAnalyzer::AnalyzeClassMethodForFinalModifier(const ir::MethodDefinition *methodDef, 77 const ir::ClassDefinition *classDef) 78{ 79 ASSERT(methodDef != nullptr && classDef != nullptr); 80 81 if (methodDef->IsAbstract() || methodDef->IsStatic() || classDef->IsFinal() || program_ == nullptr || 82 methodDef->IsFinal() || methodDef->IsConstructor() || classDef->IsGlobal()) { 83 return; 84 } 85 86 bool suggestFinal = true; 87 88 const auto statements = program_->Ast()->Statements(); 89 for (const auto *it : statements) { 90 if (!it->IsClassDeclaration() || it->AsClassDeclaration()->Definition()->IsGlobal() || 91 classDef->Ident()->Name() == it->AsClassDeclaration()->Definition()->Ident()->Name()) { 92 continue; 93 } 94 95 const auto *statementDef = it->AsClassDeclaration()->Definition(); 96 for (const auto *bodyPart : statementDef->Body()) { 97 if (!bodyPart->IsMethodDefinition()) { 98 continue; 99 } 100 static auto classAsETSObject = classDef->TsType()->AsETSObjectType(); 101 static auto potentialDescendant = statementDef->TsType()->AsETSObjectType(); 102 if (!potentialDescendant->IsDescendantOf(classAsETSObject)) { 103 continue; 104 } 105 const util::StringView bodyMethodName = 106 ETSChecker::GetSignatureFromMethodDefinition(bodyPart->AsMethodDefinition())->Function()->Id()->Name(); 107 if (bodyPart->IsOverride() && bodyMethodName != compiler::Signatures::CTOR && 108 bodyMethodName == methodDef->Function()->Id()->Name()) { 109 suggestFinal = false; 110 break; 111 } 112 } 113 } 114 115 if (suggestFinal) { 116 ETSThrowWarning("Suggest 'final' modifier for method", methodDef->Function()->Start()); 117 } 118} 119 120void ETSWarningAnalyzer::ETSWarningSuggestFinal(const ir::AstNode *node) 121{ 122 if (node->IsClassDeclaration() && !program_->NodeContainsETSNolint(node, ETSWarnings::SUGGEST_FINAL)) { 123 if (node->AsClassDeclaration()->Definition()->IsClassDefinition()) { 124 AnalyzeClassDefForFinalModifier(node->AsClassDeclaration()->Definition()); 125 } 126 127 const auto classBody = node->AsClassDeclaration()->Definition()->Body(); 128 for (const auto *it : classBody) { 129 if (it->IsMethodDefinition()) { 130 AnalyzeClassMethodForFinalModifier(it->AsMethodDefinition(), node->AsClassDeclaration()->Definition()); 131 } 132 } 133 } 134 node->Iterate([&](auto *childNode) { ETSWarningSuggestFinal(childNode); }); 135} 136 137void ETSWarningAnalyzer::CheckTopLevelExpressions(const ir::Expression *expression) 138{ 139 if (expression->IsCallExpression()) { 140 const auto exprCallee = expression->AsCallExpression()->Callee(); 141 lexer::SourcePosition pos = exprCallee->Start(); 142 if (exprCallee->IsMemberExpression()) { 143 pos = exprCallee->AsMemberExpression()->Object()->Start(); 144 ETSThrowWarning("Prohibit top-level statements", pos); 145 } 146 } else if (expression->IsAssignmentExpression()) { 147 const auto assignmentExpr = expression->AsAssignmentExpression(); 148 ETSThrowWarning("Prohibit top-level statements", assignmentExpr->Left()->Start()); 149 } 150} 151 152void ETSWarningAnalyzer::CheckProhibitedTopLevelStatements(const ir::Statement *statement) 153{ 154 switch (statement->Type()) { 155 case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION: 156 case ir::AstNodeType::FUNCTION_DECLARATION: 157 case ir::AstNodeType::SCRIPT_FUNCTION: 158 case ir::AstNodeType::ETS_FUNCTION_TYPE: 159 case ir::AstNodeType::IMPORT_NAMESPACE_SPECIFIER: 160 case ir::AstNodeType::CLASS_DECLARATION: 161 case ir::AstNodeType::CLASS_EXPRESSION: 162 case ir::AstNodeType::VARIABLE_DECLARATION: 163 case ir::AstNodeType::CLASS_DEFINITION: 164 case ir::AstNodeType::CLASS_PROPERTY: 165 break; 166 default: 167 ETSThrowWarning("Prohibit top-level statements", statement->Start()); 168 break; 169 } 170} 171 172void ETSWarningAnalyzer::ETSWarningsProhibitTopLevelStatements(const ir::AstNode *node) 173{ 174 if (!node->IsClassDeclaration() || 175 program_->NodeContainsETSNolint(node, ETSWarnings::PROHIBIT_TOP_LEVEL_STATEMENTS)) { 176 node->Iterate([&](auto *childNode) { ETSWarningsProhibitTopLevelStatements(childNode); }); 177 return; 178 } 179 180 const auto *classDef = node->AsClassDeclaration()->Definition(); 181 if (!classDef->IsGlobal()) { 182 node->Iterate([&](auto *childNode) { ETSWarningsProhibitTopLevelStatements(childNode); }); 183 return; 184 } 185 186 for (const auto *itBody : classDef->Body()) { 187 if (!itBody->IsMethodDefinition() || 188 itBody->AsMethodDefinition()->Id()->Name() != compiler::Signatures::INIT_METHOD) { 189 continue; 190 } 191 192 for (const auto *statement : 193 itBody->AsMethodDefinition()->Function()->Body()->AsBlockStatement()->Statements()) { 194 if (program_->NodeContainsETSNolint(statement, ETSWarnings::PROHIBIT_TOP_LEVEL_STATEMENTS)) { 195 continue; 196 } 197 198 if (!statement->IsExpressionStatement()) { 199 CheckProhibitedTopLevelStatements(statement); 200 continue; 201 } 202 203 // Rewrite this part after fixing AST issue about tiop-level 204 CheckTopLevelExpressions(statement->AsExpressionStatement()->GetExpression()); 205 } 206 } 207} 208 209void ETSWarningAnalyzer::ETSWarningBoostEqualityStatement(const ir::AstNode *node) 210{ 211 ASSERT(node != nullptr); 212 213 if (node->IsBinaryExpression() && !program_->NodeContainsETSNolint(node, ETSWarnings::BOOST_EQUALITY_STATEMENT)) { 214 const auto binExpr = node->AsBinaryExpression(); 215 if (binExpr->OperatorType() == lexer::TokenType::PUNCTUATOR_EQUAL || 216 binExpr->OperatorType() == lexer::TokenType::PUNCTUATOR_NOT_EQUAL) { 217 if (binExpr->Right()->IsNullLiteral() && !binExpr->Left()->IsNullLiteral()) { 218 ETSThrowWarning("Boost Equality Statement. Change sides of binary expression", node->Start()); 219 } 220 } 221 } 222 node->Iterate([&](auto *childNode) { ETSWarningBoostEqualityStatement(childNode); }); 223} 224 225void ETSWarningAnalyzer::ETSWarningRemoveAsync(const ir::AstNode *node) 226{ 227 if (node->IsMethodDefinition() && !program_->NodeContainsETSNolint(node, ETSWarnings::REMOVE_ASYNC_FUNCTIONS)) { 228 const auto methodDefinition = node->AsMethodDefinition(); 229 if (methodDefinition->IsAsync()) { 230 ETSThrowWarning("Replace asynchronous function with coroutine", methodDefinition->Start()); 231 } 232 } 233 node->Iterate([&](auto *childNode) { ETSWarningRemoveAsync(childNode); }); 234} 235 236void ETSWarningAnalyzer::ETSWarningRemoveLambda(const ir::AstNode *node) 237{ 238 ASSERT(node != nullptr); 239 240 if (node->IsArrowFunctionExpression() && !program_->NodeContainsETSNolint(node, ETSWarnings::REMOVE_LAMBDA)) { 241 ETSThrowWarning("Replace the lambda function with a regular function", node->Start()); 242 } 243 node->Iterate([&](auto *childNode) { ETSWarningRemoveLambda(childNode); }); 244} 245 246void ETSWarningAnalyzer::CheckTypeOfBoxing(const ir::AstNode *node) 247{ 248 ASSERT(node != nullptr); 249 const auto flags = node->GetBoxingUnboxingFlags(); 250 if ((flags & ir::BoxingUnboxingFlags::BOXING_FLAG) != 0) { 251 switch (static_cast<ir::BoxingUnboxingFlags>(flags & ir::BoxingUnboxingFlags::BOXING_FLAG)) { 252 case ir::BoxingUnboxingFlags::BOX_TO_INT: 253 ETSThrowWarning("Implicit Boxing to Int" + GetBoxingUnboxingType(node), node->Start()); 254 break; 255 case ir::BoxingUnboxingFlags::BOX_TO_BOOLEAN: 256 ETSThrowWarning("Implicit Boxing to Boolean" + GetBoxingUnboxingType(node), node->Start()); 257 break; 258 case ir::BoxingUnboxingFlags::BOX_TO_BYTE: 259 ETSThrowWarning("Implicit Boxing to Byte" + GetBoxingUnboxingType(node), node->Start()); 260 break; 261 case ir::BoxingUnboxingFlags::BOX_TO_CHAR: 262 ETSThrowWarning("Implicit Boxing to Char" + GetBoxingUnboxingType(node), node->Start()); 263 break; 264 case ir::BoxingUnboxingFlags::BOX_TO_DOUBLE: 265 ETSThrowWarning("Implicit Boxing to Double" + GetBoxingUnboxingType(node), node->Start()); 266 break; 267 case ir::BoxingUnboxingFlags::BOX_TO_FLOAT: 268 ETSThrowWarning("Implicit Boxing to Float" + GetBoxingUnboxingType(node), node->Start()); 269 break; 270 case ir::BoxingUnboxingFlags::BOX_TO_LONG: 271 ETSThrowWarning("Implicit Boxing to Long" + GetBoxingUnboxingType(node), node->Start()); 272 break; 273 case ir::BoxingUnboxingFlags::BOX_TO_SHORT: 274 ETSThrowWarning("Implicit Boxing to Short" + GetBoxingUnboxingType(node), node->Start()); 275 break; 276 default: 277 break; 278 } 279 } 280} 281 282void ETSWarningAnalyzer::CheckTypeOfUnboxing(const ir::AstNode *node) 283{ 284 ASSERT(node != nullptr); 285 const auto flags = node->GetBoxingUnboxingFlags(); 286 if ((flags & ir::BoxingUnboxingFlags::UNBOXING_FLAG) != 0) { 287 switch (static_cast<ir::BoxingUnboxingFlags>(flags & ir::BoxingUnboxingFlags::UNBOXING_FLAG)) { 288 case ir::BoxingUnboxingFlags::UNBOX_TO_INT: 289 ETSThrowWarning("Implicit Unboxing to int" + GetBoxingUnboxingType(node), node->Start()); 290 break; 291 case ir::BoxingUnboxingFlags::UNBOX_TO_BOOLEAN: 292 ETSThrowWarning("Implicit Unboxing to boolean" + GetBoxingUnboxingType(node), node->Start()); 293 break; 294 case ir::BoxingUnboxingFlags::UNBOX_TO_BYTE: 295 ETSThrowWarning("Implicit Unboxing to byte" + GetBoxingUnboxingType(node), node->Start()); 296 break; 297 case ir::BoxingUnboxingFlags::UNBOX_TO_CHAR: 298 ETSThrowWarning("Implicit Unboxing to char" + GetBoxingUnboxingType(node), node->Start()); 299 break; 300 case ir::BoxingUnboxingFlags::UNBOX_TO_DOUBLE: 301 ETSThrowWarning("Implicit Unboxing to double" + GetBoxingUnboxingType(node), node->Start()); 302 break; 303 case ir::BoxingUnboxingFlags::UNBOX_TO_FLOAT: 304 ETSThrowWarning("Implicit Unboxing to float" + GetBoxingUnboxingType(node), node->Start()); 305 break; 306 case ir::BoxingUnboxingFlags::UNBOX_TO_LONG: 307 ETSThrowWarning("Implicit Unboxing to long" + GetBoxingUnboxingType(node), node->Start()); 308 break; 309 case ir::BoxingUnboxingFlags::UNBOX_TO_SHORT: 310 ETSThrowWarning("Implicit Unboxing to short" + GetBoxingUnboxingType(node), node->Start()); 311 break; 312 default: 313 break; 314 } 315 } 316} 317 318void ETSWarningAnalyzer::CheckTypeOfBoxingUnboxing(const ir::AstNode *node) 319{ 320 ASSERT(node != nullptr); 321 322 CheckTypeOfBoxing(node); 323 CheckTypeOfUnboxing(node); 324} 325 326std::string ETSWarningAnalyzer::GetBoxingUnboxingType(const ir::AstNode *node) 327{ 328 ASSERT(node->Parent() != nullptr); 329 switch (node->Parent()->Type()) { 330 case ir::AstNodeType::VARIABLE_DECLARATOR: { 331 return " in Variable Declaration"; 332 } 333 case ir::AstNodeType::CALL_EXPRESSION: { 334 return " in Call Method/Function"; 335 } 336 case ir::AstNodeType::SWITCH_STATEMENT: { 337 return " in Switch-case Statement"; 338 } 339 case ir::AstNodeType::ASSIGNMENT_EXPRESSION: { 340 return " in Assignment Expression"; 341 } 342 case ir::AstNodeType::BINARY_EXPRESSION: { 343 return " in Binary Expression"; 344 } 345 case ir::AstNodeType::UNARY_EXPRESSION: { 346 return " in Unary Expression"; 347 } 348 case ir::AstNodeType::UPDATE_EXPRESSION: { 349 return " in Update Expression"; 350 } 351 case ir::AstNodeType::MEMBER_EXPRESSION: { 352 return " in Member Expression"; 353 } 354 default: 355 return ""; 356 } 357} 358 359void ETSWarningAnalyzer::ETSWarningImplicitBoxingUnboxing(const ir::AstNode *node) 360{ 361 ASSERT(node != nullptr); 362 363 switch (node->Type()) { 364 case ir::AstNodeType::VARIABLE_DECLARATOR: 365 case ir::AstNodeType::SWITCH_STATEMENT: 366 case ir::AstNodeType::CALL_EXPRESSION: 367 case ir::AstNodeType::BINARY_EXPRESSION: 368 case ir::AstNodeType::ASSIGNMENT_EXPRESSION: 369 case ir::AstNodeType::UNARY_EXPRESSION: 370 case ir::AstNodeType::UPDATE_EXPRESSION: 371 case ir::AstNodeType::MEMBER_EXPRESSION: { 372 if (!program_->NodeContainsETSNolint(node, ETSWarnings::IMPLICIT_BOXING_UNBOXING)) { 373 node->Iterate([this](auto *childNode) { CheckTypeOfBoxingUnboxing(childNode); }); 374 } 375 break; 376 } 377 default: { 378 break; 379 } 380 } 381 382 node->Iterate([&](auto *childNode) { ETSWarningImplicitBoxingUnboxing(childNode); }); 383} 384 385void ETSWarningAnalyzer::ETSThrowWarning(const std::string &message, const lexer::SourcePosition &pos) 386{ 387 lexer::LineIndex index(program_->SourceCode()); 388 lexer::SourceLocation location = index.GetLocation(pos); 389 390 if (etsWerror_) { 391 throw Error(ErrorType::ETS_WARNING, ark::es2panda::util::BaseName(program_->SourceFilePath().Utf8()), message, 392 location.line, location.col); 393 } 394 395 std::cout << "ETS Warning: " << message << "." 396 << " [" << ark::es2panda::util::BaseName(program_->SourceFilePath().Utf8()) << ":" << location.line << ":" 397 << location.col << "]" << std::endl; 398} 399 400} // namespace ark::es2panda::checker 401