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 
39 namespace ark::es2panda::checker {
40 
AnalyzeClassDefForFinalModifier(const ir::ClassDefinition *classDef)41 void 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 
AnalyzeClassMethodForFinalModifier(const ir::MethodDefinition *methodDef, const ir::ClassDefinition *classDef)76 void 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 
ETSWarningSuggestFinal(const ir::AstNode *node)120 void 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 
CheckTopLevelExpressions(const ir::Expression *expression)137 void 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 
CheckProhibitedTopLevelStatements(const ir::Statement *statement)152 void 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 
ETSWarningsProhibitTopLevelStatements(const ir::AstNode *node)172 void 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 
ETSWarningBoostEqualityStatement(const ir::AstNode *node)209 void 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 
ETSWarningRemoveAsync(const ir::AstNode *node)225 void 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 
ETSWarningRemoveLambda(const ir::AstNode *node)236 void 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 
CheckTypeOfBoxing(const ir::AstNode *node)246 void 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 
CheckTypeOfUnboxing(const ir::AstNode *node)282 void 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 
CheckTypeOfBoxingUnboxing(const ir::AstNode *node)318 void ETSWarningAnalyzer::CheckTypeOfBoxingUnboxing(const ir::AstNode *node)
319 {
320     ASSERT(node != nullptr);
321 
322     CheckTypeOfBoxing(node);
323     CheckTypeOfUnboxing(node);
324 }
325 
GetBoxingUnboxingType(const ir::AstNode *node)326 std::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 
ETSWarningImplicitBoxingUnboxing(const ir::AstNode *node)359 void 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 
ETSThrowWarning(const std::string &message, const lexer::SourcePosition &pos)385 void 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