From d596a64db650c36a9701477f917714e2efb29606 Mon Sep 17 00:00:00 2001 From: xudan16 Date: Wed, 13 Aug 2025 16:48:26 +0800 Subject: [PATCH] homecheck check sdk field and return type Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICSSGZ Signed-off-by: xudan16 --- .../arkanalyzer/src/core/common/Const.ts | 2 + .../src/core/common/TypeInference.ts | 7 + .../linter/arkanalyzer/src/core/graph/Cfg.ts | 4 + .../arkanalyzer/src/core/model/ArkMethod.ts | 19 +- .../core/model/builder/ArkMethodBuilder.ts | 15 +- ets2panda/linter/homecheck/ruleSet.json | 196 ----- .../checker/migration/NumericSemanticCheck.ts | 750 ++++++++++++++---- .../homecheck/src/utils/common/CheckEntry.ts | 1 + .../homecheck/src/utils/common/SDKUtils.ts | 40 +- 9 files changed, 687 insertions(+), 347 deletions(-) diff --git a/ets2panda/linter/arkanalyzer/src/core/common/Const.ts b/ets2panda/linter/arkanalyzer/src/core/common/Const.ts index 1fb1a81e79..34a8c9afc0 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/Const.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/Const.ts @@ -31,6 +31,8 @@ export const STATIC_INIT_METHOD_NAME = NAME_PREFIX + 'statInit'; export const STATIC_BLOCK_METHOD_NAME_PREFIX = NAME_PREFIX + 'statBlock'; export const ANONYMOUS_METHOD_PREFIX = NAME_PREFIX + 'AM'; export const CALL_SIGNATURE_NAME = 'create'; +export const GETTER_METHOD_PREFIX = 'Get-'; +export const SETTER_METHOD_PREFIX = 'Set-'; // ArkSignature const export const UNKNOWN_PROJECT_NAME = NAME_PREFIX + UNKNOWN_NAME; diff --git a/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts b/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts index b2caf8ad56..691a0d854b 100644 --- a/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts +++ b/ets2panda/linter/arkanalyzer/src/core/common/TypeInference.ts @@ -208,6 +208,13 @@ export class TypeInference { } } signatures.forEach(s => this.inferSignatureReturnType(s, arkMethod)); + if (arkMethod.isGetter()) { + // should also update the corresponding field type with the getter method return type + const field = arkMethod.getGeneratedFieldOfGetter(); + if (field) { + field.getSignature().setType(arkMethod.getReturnType()); + } + } } private static resolveStmt(stmt: Stmt, arkMethod: ArkMethod): void { diff --git a/ets2panda/linter/arkanalyzer/src/core/graph/Cfg.ts b/ets2panda/linter/arkanalyzer/src/core/graph/Cfg.ts index 7b1c4a6e5a..840ceee435 100644 --- a/ets2panda/linter/arkanalyzer/src/core/graph/Cfg.ts +++ b/ets2panda/linter/arkanalyzer/src/core/graph/Cfg.ts @@ -133,6 +133,10 @@ export class Cfg { } } + /** + * Get all basic blocks with topological order. + * @returns The set of all basic blocks. + */ public getBlocks(): Set { return this.blocks; } diff --git a/ets2panda/linter/arkanalyzer/src/core/model/ArkMethod.ts b/ets2panda/linter/arkanalyzer/src/core/model/ArkMethod.ts index dd275f7324..9365a9762e 100644 --- a/ets2panda/linter/arkanalyzer/src/core/model/ArkMethod.ts +++ b/ets2panda/linter/arkanalyzer/src/core/model/ArkMethod.ts @@ -33,7 +33,7 @@ import { ArkClass, ClassCategory } from './ArkClass'; import { MethodSignature, MethodSubSignature } from './ArkSignature'; import { BodyBuilder } from './builder/BodyBuilder'; import { ArkExport, ExportType } from './ArkExport'; -import { ANONYMOUS_METHOD_PREFIX, DEFAULT_ARK_METHOD_NAME, LEXICAL_ENV_NAME_PREFIX } from '../common/Const'; +import { ANONYMOUS_METHOD_PREFIX, DEFAULT_ARK_METHOD_NAME, GETTER_METHOD_PREFIX, LEXICAL_ENV_NAME_PREFIX, SETTER_METHOD_PREFIX } from '../common/Const'; import { getColNo, getLineNo, LineCol, setCol, setLine } from '../base/Position'; import { ArkBaseModel, ModifierType } from './ArkBaseModel'; import { ArkError, ArkErrorCode } from '../common/ArkError'; @@ -44,6 +44,7 @@ import { ArkFile, Language } from './ArkFile'; import { CONSTRUCTOR_NAME } from '../common/TSConst'; import { MethodParameter } from './builder/ArkMethodBuilder'; import { TypeInference } from '../common/TypeInference'; +import { ArkField } from './ArkField'; export const arkMethodNodeKind = [ 'MethodDeclaration', @@ -575,6 +576,22 @@ export class ArkMethod extends ArkBaseModel implements ArkExport { return this.isGeneratedFlag; } + public isGetter(): boolean { + return this.getName().startsWith(GETTER_METHOD_PREFIX); + } + + public getGeneratedFieldOfGetter(): ArkField | null { + if (!this.isGetter()) { + return null; + } + const fieldName = this.getName().slice(4); + return this.getDeclaringArkClass().getFieldWithName(fieldName); + } + + public isSetter(): boolean { + return this.getName().startsWith(SETTER_METHOD_PREFIX); + } + public setIsGeneratedFlag(isGeneratedFlag: boolean): void { this.isGeneratedFlag = isGeneratedFlag; } diff --git a/ets2panda/linter/arkanalyzer/src/core/model/builder/ArkMethodBuilder.ts b/ets2panda/linter/arkanalyzer/src/core/model/builder/ArkMethodBuilder.ts index 4dcc8b5216..72eadc8773 100644 --- a/ets2panda/linter/arkanalyzer/src/core/model/builder/ArkMethodBuilder.ts +++ b/ets2panda/linter/arkanalyzer/src/core/model/builder/ArkMethodBuilder.ts @@ -39,7 +39,16 @@ import { BasicBlock } from '../../graph/BasicBlock'; import { Local } from '../../base/Local'; import { Value } from '../../base/Value'; import { CONSTRUCTOR_NAME, SUPER_NAME, THIS_NAME } from '../../common/TSConst'; -import { ANONYMOUS_METHOD_PREFIX, CALL_SIGNATURE_NAME, DEFAULT_ARK_CLASS_NAME, DEFAULT_ARK_METHOD_NAME, NAME_DELIMITER, NAME_PREFIX } from '../../common/Const'; +import { + ANONYMOUS_METHOD_PREFIX, + CALL_SIGNATURE_NAME, + DEFAULT_ARK_CLASS_NAME, + DEFAULT_ARK_METHOD_NAME, + GETTER_METHOD_PREFIX, + NAME_DELIMITER, + NAME_PREFIX, + SETTER_METHOD_PREFIX, +} from '../../common/Const'; import { ArkSignatureBuilder } from './ArkSignatureBuilder'; import { IRUtils } from '../../common/IRUtils'; import { ArkErrorCode } from '../../common/ArkError'; @@ -163,9 +172,9 @@ function buildMethodName(node: MethodLikeNode, declaringClass: ArkClass, sourceF } else if (ts.isCallSignatureDeclaration(node)) { name = CALL_SIGNATURE_NAME; } else if (ts.isGetAccessor(node) && ts.isIdentifier(node.name)) { - name = 'Get-' + node.name.text; + name = GETTER_METHOD_PREFIX + node.name.text; } else if (ts.isSetAccessor(node) && ts.isIdentifier(node.name)) { - name = 'Set-' + node.name.text; + name = SETTER_METHOD_PREFIX + node.name.text; } else if (ts.isArrowFunction(node)) { name = buildAnonymousMethodName(node, declaringClass); } diff --git a/ets2panda/linter/homecheck/ruleSet.json b/ets2panda/linter/homecheck/ruleSet.json index ebfa295c23..0bd8f48742 100644 --- a/ets2panda/linter/homecheck/ruleSet.json +++ b/ets2panda/linter/homecheck/ruleSet.json @@ -1,200 +1,4 @@ { - "plugin:@ArkTS-eslint/all": { - "@ArkTS-eslint/require-await-check": 2, - "@ArkTS-eslint/triple-slash-reference-check": 2, - "@ArkTS-eslint/restrict-template-expressions-check": 2, - "@ArkTS-eslint/restrict-plus-operands-check": 2, - "@ArkTS-eslint/switch-exhaustiveness-check": 2, - "@ArkTS-eslint/unified-signatures-check": 2, - "@ArkTS-eslint/no-regex-spaces-check": 2, - "@ArkTS-eslint/valid-typeof-check": 2, - "@ArkTS-eslint/array-type-check": 2, - "@ArkTS-eslint/no-useless-backreference-check": 2, - "@ArkTS-eslint/ban-tslint-comment-check": 2, - "@ArkTS-eslint/prefer-arrow-callback-check":2, - "@ArkTS-eslint/no-unnecessary-boolean-literal-compare-check":2, - "@ArkTS-eslint/ban-types-check": 2, - "@ArkTS-eslint/brace-style-check": 2, - "@ArkTS-eslint/no-unsafe-optional-chaining-check": 2, - "@ArkTS-eslint/no-useless-escape-check": 2, - "@ArkTS-eslint/no-useless-catch-check": 2, - "@ArkTS-eslint/no-this-alias-check": 2, - "@ArkTS-eslint/no-non-null-assertion-check": 2, - "@ArkTS-eslint/no-misused-new-check": 2, - "@ArkTS-eslint/no-require-imports-check": 2, - "@ArkTS-eslint/no-parameter-properties-check": 2, - "@ArkTS-eslint/no-redeclare-check": 2, - "@ArkTS-eslint/no-shadow-check": 2, - "@ArkTS-eslint/no-non-null-asserted-optional-chain-check": 2, - "@ArkTS-eslint/consistent-type-assertions-check": 2, - "@ArkTS-eslint/consistent-type-definitions-check": 2, - "@ArkTS-eslint/consistent-type-imports-check": 2, - "@ArkTS-eslint/consistent-indexed-object-style-check": 2, - "@ArkTS-eslint/no-new-wrappers-check": 2, - "@ArkTS-eslint/max-classes-per-file-check": 2, - "@ArkTS-eslint/max-nested-callbacks-check": 2, - "@ArkTS-eslint/no-async-promise-executor-check": 2, - "@ArkTS-eslint/no-array-constructor-check": 2, - "@ArkTS-eslint/max-depth-check": 2, - "@ArkTS-eslint/eqeqeq-check": 2, - "@ArkTS-eslint/no-array-constructor-ts-check": 2, - "@ArkTS-eslint/no-extra-semi-check": 2, - "@ArkTS-eslint/no-extra-boolean-cast-check":2, - "@ArkTS-eslint/no-confusing-void-expression-check":2, - "@ArkTS-eslint/prefer-const-check": 2, - "@ArkTS-eslint/await-thenable-check": 2, - "@ArkTS-eslint/init-declarations-check": 2, - "@ArkTS-eslint/default-param-last-check": 2, - "@ArkTS-eslint/explicit-function-return-type-check": 2, - "@ArkTS-eslint/explicit-module-boundary-types-check": 2, - "@ArkTS-eslint/no-dupe-class-members-check": 2, - "@ArkTS-eslint/ban-ts-comment-check": 2, - "@ArkTS-eslint/member-ordering-check": 2, - "@ArkTS-eslint/no-unnecessary-condition-check": 2, - "@ArkTS-eslint/no-unnecessary-qualifier-check": 2, - "@ArkTS-eslint/no-unnecessary-type-arguments-check": 2, - "@ArkTS-eslint/no-unnecessary-type-assertion-check": 2, - "@ArkTS-eslint/prefer-string-starts-ends-with-check": 2, - "@ArkTS-eslint/prefer-regexp-exec-check": 2, - "@ArkTS-eslint/max-lines-per-function-check": 2, - "@ArkTS-eslint/no-cond-assign-check": 2, - "@ArkTS-eslint/no-for-in-array-check": 2, - "@ArkTS-eslint/no-loss-of-precision-check": 2, - "@ArkTS-eslint/no-loop-func-check": 2, - "@ArkTS-eslint/no-extraneous-class-check": 2, - "@ArkTS-eslint/no-duplicate-imports-check": 2, - "@ArkTS-eslint/no-case-declarations-check": 2, - "@ArkTS-eslint/default-case-check": 2, - "@ArkTS-eslint/default-case-last-check": 2, - "@ArkTS-eslint/use-isnan-check": 2, - "@ArkTS-eslint/no-invalid-void-type-check": 2, - "@ArkTS-eslint/no-namespace-check": 2, - "@ArkTS-eslint/typedef-check": 2, - "@ArkTS-eslint/return-await-check":2, - "@ArkTS-eslint/prefer-reduce-type-parameter-check":2, - "@ArkTS-eslint/prefer-nullish-coalescing-check": 2, - "@ArkTS-eslint/max-lines-check": 2, - "@ArkTS-eslint/no-unnecessary-type-constraint-check": 2, - "@ArkTS-eslint/no-unsafe-argument-check": 2, - "@ArkTS-eslint/no-unsafe-call-check": 2, - "@ArkTS-eslint/no-control-regex-check": 2, - "@ArkTS-eslint/no-empty-character-class-check": 2, - "@ArkTS-eslint/no-ex-assign-check": 2, - "@ArkTS-eslint/no-invalid-regexp-check": 2, - "@ArkTS-eslint/no-octal-check": 2, - "@ArkTS-eslint/no-unexpected-multiline-check": 2, - "@ArkTS-eslint/no-unreachable-check": 2, - "@ArkTS-eslint/no-inferrable-types-check": 2, - "@ArkTS-eslint/space-before-function-paren-check": 2, - "@ArkTS-eslint/space-infix-ops-check": 2, - "@ArkTS-eslint/no-restricted-syntax-check": 2, - "@ArkTS-eslint/adjacent-overload-signatures-check": 2, - "@ArkTS-eslint/class-literal-property-style-check": 2, - "@ArkTS-eslint/no-confusing-non-null-assertion-check": 2, - "@ArkTS-eslint/no-empty-function-check": 2, - "@ArkTS-eslint/no-magic-numbers-check": 2, - "@ArkTS-eslint/prefer-enum-initializers-check": 2, - "@ArkTS-eslint/prefer-literal-enum-member-check": 2, - "@ArkTS-eslint/prefer-readonly-parameter-types-check": 2, - "@ArkTS-eslint/require-array-sort-compare-check": 2, - "@ArkTS-eslint/no-invalid-this-check": 2, - "@ArkTS-eslint/no-fallthrough-check": 2, - "@ArkTS-eslint/no-explicit-any-check": 2, - "@ArkTS-eslint/no-unused-expressions-check": 2, - "@ArkTS-eslint/no-throw-literal-check": 2, - "@ArkTS-eslint/comma-dangle-check": 2, - "@ArkTS-eslint/prefer-ts-expect-error-check": 2, - "@ArkTS-eslint/no-extra-parens-check": 2, - "@ArkTS-eslint/no-dynamic-delete-check": 2, - "@ArkTS-eslint/no-implicit-any-catch-check": 2, - "@ArkTS-eslint/no-empty-interface-check": 2, - "@ArkTS-eslint/no-unsafe-finally-check": 3, - "@ArkTS-eslint/prefer-function-type-check":3, - "@ArkTS-eslint/prefer-namespace-keyword-check": 3, - "@ArkTS-eslint/prefer-readonly-check": 2, - "@ArkTS-eslint/comma-spacing-check": 2, - "@ArkTS-eslint/naming-convention-check": 2, - "@ArkTS-eslint/no-extra-non-null-assertion-check": 2, - "@ArkTS-eslint/no-type-alias-check": 2, - "@ArkTS-eslint/type-annotation-spacing-check": 2, - "@ArkTS-eslint/func-call-spacing-check": 1, - "@ArkTS-eslint/unbound-method-check": 2, - "@ArkTS-eslint/method-signature-style-check": 2, - "@ArkTS-eslint/lines-between-class-members-check": 2, - "@ArkTS-eslint/member-delimiter-style-check": 2, - "@ArkTS-eslint/no-unsafe-return-check": 2, - "@ArkTS-eslint/no-use-before-define-check": 1, - "@ArkTS-eslint/quotes-check": 2, - "@ArkTS-eslint/prefer-as-const-check": 2, - "@ArkTS-eslint/prefer-optional-chain-check": 2, - "@ArkTS-eslint/no-trailing-spaces-check": 2, - "@ArkTS-eslint/no-unsafe-assignment-check": 2, - "@ArkTS-eslint/prefer-for-of-check": 2, - "@ArkTS-eslint/strict-boolean-expressions-check": 2, - "@ArkTS-eslint/no-implied-eval-check": 2, - "@ArkTS-eslint/semi-check": 2, - "@ArkTS-eslint/no-base-to-string-check": 2, - "@ArkTS-eslint/promise-function-async-check": 2, - "@ArkTS-eslint/prefer-includes-check": 2, - "@ArkTS-eslint/no-unsafe-member-access-check": 2, - "@ArkTS-eslint/no-unused-vars-check": 2, - "@ArkTS-eslint/no-useless-constructor-check": 2, - "@ArkTS-eslint/dot-notation-check": 2, - "@ArkTS-eslint/explicit-member-accessibility-check": 2, - "@ArkTS-eslint/keyword-spacing-check": 2, - "@ArkTS-eslint/no-floating-promises-check": 2, - "@ArkTS-eslint/no-misused-promises-check": 2 - }, - "plugin:@performance/all": { - "@performance/array-definition-check": 3, - "@performance/avoid-empty-callback-check": 3, - "@performance/avoid-update-auto-state-var-in-aboutToReuse-check": 3, - "@performance/constant-property-referencing-check-in-loops": 3, - "@performance/effectkit-blur-check": 3, - "@performance/foreach-args-check": 3, - "@performance/foreach-index-check": 1, - "@performance/image-sync-load-check": 3, - "@performance/list-in-scroll-check": 3, - "@performance/lottie-animation-destroy-check": 3, - "@performance/multiple-associations-state-var-check": 3, - "@performance/remove-redundant-state-var-check": 3, - "@performance/set-cached-count-for-lazyforeach-check": 3, - "@performance/start-window-icon-check": 3, - "@performance/timezone-interface-check": 3, - "@performance/typed-array-check": 3, - "@performance/use-object-link-to-replace-prop-check": 3, - "@performance/web-cache-mode-check": 3, - "@performance/number-init-check": 3, - "@performance/sparse-array-check": 3, - "@performance/high-frequency-log-check": 1, - "@performance/waterflow-data-preload-check": 3, - "@performance/union-type-array-check": 3, - "@performance/layout-properties-scale-check": 3, - "@performance/optional-parameters-check": 3, - "@performance/use-grid-layout-options-check": 3, - "@performance/remove-unchanged-state-var-check": 3, - "@performance/js-code-cache-by-precompile-check": 3, - "@performance/js-code-cache-by-interception-check": 3, - "@performance/web-on-active-check": 3, - "@performance/gif-hardware-decoding-check": 1 - }, - "plugin:@performance/recommended": { - "@performance/foreach-args-check": 1, - "@performance/start-window-icon-check": 3, - "@performance/waterflow-data-preload-check": 3, - "@performance/high-frequency-log-check": 1 - }, - "plugin:@security/all": { - "@security/specified-interface-call-chain-check": 3 - }, - "plugin:@correctness/all": { - "@correctness/audio-interrupt-check": 2, - "@correctness/audio-pause-or-mute-check": 1, - "@correctness/avsession-buttons-check": 1, - "@correctness/avsession-metadata-check": 1, - "@correctness/image-interpolation-check": 1, - "@correctness/image-pixel-format-check": 1 - }, "plugin:@migration/all": { "@migration/arkts-obj-literal-generate-class-instance": 1, "@migration/arkts-instance-method-bind-this": 1, diff --git a/ets2panda/linter/homecheck/src/checker/migration/NumericSemanticCheck.ts b/ets2panda/linter/homecheck/src/checker/migration/NumericSemanticCheck.ts index 90636ec1d0..b6250b7da0 100644 --- a/ets2panda/linter/homecheck/src/checker/migration/NumericSemanticCheck.ts +++ b/ets2panda/linter/homecheck/src/checker/migration/NumericSemanticCheck.ts @@ -140,6 +140,8 @@ export class NumericSemanticCheck implements BaseChecker { private visited: Set = new Set(); private callDepth = 0; private classFieldRes: Map = new Map(); + private issuesMap: Map = new Map(); + private sourceFiles: Map = new Map(); public registerMatchers(): MatcherCallback[] { const matchBuildCb: MatcherCallback = { @@ -169,6 +171,8 @@ export class NumericSemanticCheck implements BaseChecker { if (arkFile.getLanguage() !== Language.ARKTS1_2) { continue; } + // 用于记录与issue相关的文件的tsc信息,避免每次新增issue时重复创建,提升性能。每次遍历新文件时清空map,节省内存。 + this.sourceFiles = new Map(); const defaultMethod = arkFile.getDefaultClass().getDefaultArkMethod(); if (defaultMethod) { this.dvfgBuilder.buildForSingleMethod(defaultMethod); @@ -182,14 +186,16 @@ export class NumericSemanticCheck implements BaseChecker { } } } + + this.issues = Array.from(this.issuesMap.values()); }; public processClass(arkClass: ArkClass): void { - if (arkClass.getCategory() === ClassCategory.ENUM) { + if (arkClass.getCategory() === ClassCategory.ENUM || arkClass.getCategory() === ClassCategory.TYPE_LITERAL) { // Enum类型的class不需要处理,仅有statint函数,一定不涉及SDK调用,整型字面量不能进行浮点字面量的修改,也不涉及类型注解修改 + // TYPE_LITERAL类型的class不需要处理,仅作为type使用,该class内无方法,仅有field的定义,且field无初始化语句,仅设定类型 return; } - // TODO: type literal类型的class需要处理吗 this.classFieldRes = new Map(); // 查找全部method,包含constructor、%instInit,%statInit等 for (let mtd of arkClass.getMethods(true)) { @@ -203,16 +209,18 @@ export class NumericSemanticCheck implements BaseChecker { for (const stmt of stmts) { try { this.checkSdkArgsInStmt(stmt); + this.checkSdkReturnValueInStmt(stmt); + this.checkSdkFieldValueInStmt(stmt); } catch (e) { logger.error(`Error checking sdk called in stmt: ${stmt.toString()}, method: ${target.getSignature().toString()}, error: ${e}`); } } - // 场景2:需要检查整型字面量出现的stmt,该stmt为sink点。场景2在场景1之后执行,优先让SDK调用来决定变量的类型为int、long、number,剩余的场景2处理,避免issue之间的冲突 + // 场景2:需要检查整型字面量或除法出现的stmt,该stmt为sink点。场景2在场景1之后执行,优先让SDK调用来决定变量的类型为int、long、number,剩余的场景2处理,避免issue之间的冲突 if (target.isGenerated()) { // statInit、instInit等方法不进行检查,不主动对类属性的类型进行检查,因为类属性的使用范围很广,很难找全,仅对涉及的1/2这种进行告警,自动修复为1.0/2.0 try { - this.checkFieldInitializerWithDivision(target); + this.checkFieldInitializerWithIntLiteral(target); } catch (e) { logger.error(`Error checking init method with numeric literal, method: ${target.getSignature().toString()}, error: ${e}`); } @@ -239,18 +247,18 @@ export class NumericSemanticCheck implements BaseChecker { } private checkSdkArgsInStmt(stmt: Stmt): void { - // res用于存放检查过程中所有找到的Local变量,记录这些变量是否均仅当做int使用,若是则可以设置成int类型,跨函数场景下可能包含其他method中的Local变量 - const res = new Map(); - this.callDepth = 0; const intArgs = this.getSDKIntLongArgs(stmt); if (intArgs === null || intArgs.size === 0) { return; } + // res用于存放检查过程中所有找到的Local变量,记录这些变量是否均仅当做int使用,若是则可以设置成int类型,跨函数场景下可能包含其他method中的Local变量 + const res = new Map(); + this.callDepth = 0; for (const [arg, category] of intArgs) { const issueReason = this.checkValueOnlyUsedAsIntLong(stmt, arg, res, category); if (issueReason !== IssueReason.OnlyUsedAsIntLong) { - this.addIssueReport(RuleCategory.SDKIntType, category, issueReason, true, stmt, arg); + this.addIssueReportForSDKArg(RuleCategory.SDKIntType, category, issueReason, true, stmt, arg); } } res.forEach((issueInfo, local) => { @@ -259,19 +267,130 @@ export class NumericSemanticCheck implements BaseChecker { } const declaringStmt = local.getDeclaringStmt(); if (declaringStmt !== null && issueInfo.issueReason === IssueReason.OnlyUsedAsIntLong) { - this.addIssueReport(RuleCategory.SDKIntType, issueInfo.numberCategory, issueInfo.issueReason, true, declaringStmt, local, undefined, stmt); + this.addIssueReportForSDKArg( + RuleCategory.SDKIntType, + issueInfo.numberCategory, + issueInfo.issueReason, + true, + declaringStmt, + local, + undefined, + stmt + ); } }); this.classFieldRes.forEach((fieldInfo, field) => { if (fieldInfo.issueReason === IssueReason.OnlyUsedAsIntLong || fieldInfo.issueReason === IssueReason.UsedWithOtherType) { // 如果能明确判断出field是int或非int,则添加类型注解int或number,其他找不全的场景不变 - this.addIssueReport(RuleCategory.NumericLiteral, fieldInfo.numberCategory, fieldInfo.issueReason, true, undefined, undefined, field); + this.addIssueReportForSDKArg(RuleCategory.SDKIntType, fieldInfo.numberCategory, fieldInfo.issueReason, true, undefined, undefined, field, stmt); } }); } - private checkFieldInitializerWithDivision(method: ArkMethod): void { - // 仅对类属性的初始化语句进行检查,判断其中是否有涉及整型字面量参与的除法运算 + private checkSdkReturnValueInStmt(stmt: Stmt): void { + if (!(stmt instanceof ArkAssignStmt)) { + return; + } + const rightOp = stmt.getRightOp(); + if (!(rightOp instanceof AbstractInvokeExpr)) { + return; + } + const numberCategory = this.checkSDKReturnType(rightOp); + if (!numberCategory) { + return; + } + + const res = new Map(); + this.callDepth = 0; + const leftOp = stmt.getLeftOp(); + this.checkValueOnlyUsedAsIntLong(stmt, leftOp, res, numberCategory); + res.forEach((issueInfo, local) => { + if (this.shouldIgnoreLocal(local)) { + return; + } + const declaringStmt = local.getDeclaringStmt(); + if (declaringStmt !== null) { + this.addIssueReportForSDKReturnOrField( + RuleCategory.SDKIntType, + issueInfo.numberCategory, + issueInfo.issueReason, + declaringStmt, + local, + undefined, + stmt + ); + } + }); + this.classFieldRes.forEach((fieldInfo, field) => { + if (fieldInfo.issueReason === IssueReason.OnlyUsedAsIntLong || fieldInfo.issueReason === IssueReason.UsedWithOtherType) { + // 如果能明确判断出field是int或非int,则添加类型注解int或number,其他找不全的场景不变 + this.addIssueReportForSDKReturnOrField( + RuleCategory.SDKIntType, + fieldInfo.numberCategory, + fieldInfo.issueReason, + undefined, + undefined, + field, + stmt + ); + } + }); + } + + private checkSdkFieldValueInStmt(stmt: Stmt): void { + if (!(stmt instanceof ArkAssignStmt)) { + return; + } + const rightOp = stmt.getRightOp(); + if (!(rightOp instanceof AbstractFieldRef)) { + return; + } + const numberCategory = this.checkSDKFieldType(rightOp); + if (!numberCategory) { + return; + } + const res = new Map(); + this.callDepth = 0; + const leftOp = stmt.getLeftOp(); + if (!Utils.isNearlyNumberType(leftOp.getType())) { + return; + } + this.checkValueOnlyUsedAsIntLong(stmt, leftOp, res, numberCategory); + res.forEach((issueInfo, local) => { + if (this.shouldIgnoreLocal(local)) { + return; + } + const declaringStmt = local.getDeclaringStmt(); + if (declaringStmt !== null) { + this.addIssueReportForSDKReturnOrField( + RuleCategory.SDKIntType, + issueInfo.numberCategory, + issueInfo.issueReason, + declaringStmt, + local, + undefined, + stmt + ); + } + }); + this.classFieldRes.forEach((fieldInfo, field) => { + if (fieldInfo.issueReason === IssueReason.OnlyUsedAsIntLong || fieldInfo.issueReason === IssueReason.UsedWithOtherType) { + // 如果能明确判断出field是int或非int,则添加类型注解int或number,其他找不全的场景不变 + this.addIssueReportForSDKReturnOrField( + RuleCategory.SDKIntType, + fieldInfo.numberCategory, + fieldInfo.issueReason, + undefined, + undefined, + field, + stmt + ); + } + }); + } + + private checkFieldInitializerWithIntLiteral(method: ArkMethod): void { + // 仅对类属性的初始化语句进行检查,判断其中是否有涉及整型字面量的赋值或涉及除法运算 if (method.getName() !== STATIC_INIT_METHOD_NAME && method.getName() !== INSTANCE_INIT_METHOD_NAME) { return; } @@ -288,16 +407,73 @@ export class NumericSemanticCheck implements BaseChecker { continue; } const rightOp = stmt.getRightOp(); - if (rightOp instanceof Local && !rightOp.getName().startsWith(TEMP_LOCAL_PREFIX)) { - // 类属性的初始化语句使用Local赋值,且Local非临时变量,则一定不涉及除法运算,无需继续本轮检查 - continue; + if (rightOp instanceof Local && rightOp.getName().startsWith(TEMP_LOCAL_PREFIX)) { + // 类属性的初始化语句使用Local赋值,且Local为临时变量,则可能涉及除法运算 + // 整型字面量参与除法运算的告警和自动修复信息在检查过程中就已生成,无需在此处额外生成 + this.checkValueOnlyUsedAsIntLong(stmt, rightOp, new Map(), NumberCategory.int); + this.checkFieldRef( + leftOp, + stmt.getCfg().getDeclaringMethod().getDeclaringArkClass().getSignature(), + NumberCategory.int, + new Map() + ); + } + if (rightOp instanceof NumberConstant && !this.isNumberConstantActuallyFloat(rightOp)) { + this.checkFieldRef( + leftOp, + stmt.getCfg().getDeclaringMethod().getDeclaringArkClass().getSignature(), + NumberCategory.int, + new Map() + ); } - // 整型字面量参与除法运算的告警和自动修复信息在检查过程中就已生成,无需在此处额外生成 - this.checkValueOnlyUsedAsIntLong(stmt, stmt.getRightOp(), new Map(), NumberCategory.int); } + this.classFieldRes.forEach((fieldInfo, field) => { + this.addIssueReport(RuleCategory.NumericLiteral, fieldInfo.numberCategory, fieldInfo.issueReason, true, undefined, undefined, field); + }); } private checkStmtContainsNumericLiteral(stmt: Stmt): void { + const res = new Map(); + this.callDepth = 0; + + // 场景1:先判断是否涉及除法运算 + if (stmt instanceof ArkAssignStmt) { + const leftOp = stmt.getLeftOp(); + const rightOp = stmt.getRightOp(); + if (leftOp instanceof Local && rightOp instanceof ArkNormalBinopExpr && rightOp.getOperator() === NormalBinaryOperator.Division) { + if (this.isLocalAssigned2Array(leftOp)) { + // local为临时变量,用于给数组元素赋值的场景,不在本规则的实现范围内,归另一处的规则开发实现 + return; + } + if (!Utils.isNearlyNumberType(leftOp.getType())) { + // 对左值进行检查决定是否对其添加类型注解int或number,如果不是number相关类型则无需继续进行检查 + return; + } + this.checkValueOnlyUsedAsIntLong(stmt, stmt.getLeftOp(), res, NumberCategory.number); + // 因为如果let a10 = a1/2; a10 = a2/3;第1句能判断a10为number,则不会继续后面的检查,所以需要额外对除法表达式的op1和op2进行number类型注解的补充 + this.isAbstractExprOnlyUsedAsIntLong(stmt, rightOp, res, NumberCategory.number); + res.forEach((issueInfo, local) => { + if (this.shouldIgnoreLocal(local)) { + return; + } + const declaringStmt = local.getDeclaringStmt(); + if (declaringStmt === null) { + return; + } + // 无论local的判定结果是什么,均需要进行自动修复类型注解为int或者number + this.addIssueReport(RuleCategory.NumericLiteral, issueInfo.numberCategory, issueInfo.issueReason, true, declaringStmt, local); + }); + this.classFieldRes.forEach((fieldInfo, field) => { + if (fieldInfo.issueReason === IssueReason.OnlyUsedAsIntLong || fieldInfo.issueReason === IssueReason.UsedWithOtherType) { + // 如果能明确判断出field是int或非int,则添加类型注解int或number,其他找不全的场景不变 + this.addIssueReport(RuleCategory.NumericLiteral, fieldInfo.numberCategory, fieldInfo.issueReason, true, undefined, undefined, field); + } + }); + return; + } + } + + // 场景2:非除法运算场景,处理其余涉及整型字面量的场景 if (!this.isStmtContainsIntLiteral(stmt)) { return; } @@ -334,9 +510,7 @@ export class NumericSemanticCheck implements BaseChecker { return; } - const res = new Map(); - this.callDepth = 0; - if (rightOp instanceof NumberConstant && !this.isNumberConstantWithDecimalPoint(rightOp)) { + if (rightOp instanceof NumberConstant && !this.isNumberConstantActuallyFloat(rightOp)) { // 整型字面量直接赋值给左值,判断左值在生命周期内是否仅作为int使用,并且判断左值是否继续赋值给其他变量,其他变量是否也可以定义为int this.checkAllLocalsAroundLocal(stmt, leftOp, res, NumberCategory.int); } else if (rightOp instanceof AbstractExpr) { @@ -358,7 +532,7 @@ export class NumericSemanticCheck implements BaseChecker { return; } // 无论local的判定结果是什么,均需要进行自动修复类型注解为int或者number - this.addIssueReport(RuleCategory.NumericLiteral, issueInfo.numberCategory, issueInfo.issueReason, true, declaringStmt, local, undefined); + this.addIssueReport(RuleCategory.NumericLiteral, issueInfo.numberCategory, issueInfo.issueReason, true, declaringStmt, local); }); this.classFieldRes.forEach((fieldInfo, field) => { if (fieldInfo.issueReason === IssueReason.OnlyUsedAsIntLong || fieldInfo.issueReason === IssueReason.UsedWithOtherType) { @@ -511,13 +685,40 @@ export class NumericSemanticCheck implements BaseChecker { if (declaringStmt instanceof ArkAssignStmt && declaringStmt.getRightOp() instanceof ClosureFieldRef) { return true; } + + // 对于for (const i of arr)这样的写法,不能为i添加类型注解 + if (declaringStmt instanceof ArkAssignStmt) { + const rightOp = declaringStmt.getRightOp(); + if (!(rightOp instanceof ArkCastExpr)) { + return false; + } + const castOp = rightOp.getOp(); + if (!(castOp instanceof Local)) { + return false; + } + const castOpDeclaring = castOp.getDeclaringStmt(); + if (!(castOpDeclaring instanceof ArkAssignStmt)) { + return false; + } + const castOpRight = castOpDeclaring.getRightOp(); + if (!(castOpRight instanceof ArkInstanceFieldRef)) { + return false; + } + const fieldSig = castOpRight.getFieldSignature(); + if (fieldSig.getFieldName() === 'value') { + const declaringSig = fieldSig.getDeclaringSignature(); + if (declaringSig instanceof ClassSignature && declaringSig.getClassName() === 'IteratorYieldResult') { + return true; + } + } + } return false; } private isStmtContainsIntLiteral(stmt: Stmt): boolean { const uses = stmt.getUses(); for (const use of uses) { - if (use instanceof NumberConstant && !this.isNumberConstantWithDecimalPoint(use)) { + if (use instanceof NumberConstant && !this.isNumberConstantActuallyFloat(use)) { return true; } } @@ -638,7 +839,7 @@ export class NumericSemanticCheck implements BaseChecker { const args = invokeExpr.getArgs(); // 根据找到的对应arkts1.1中的SDK接口匹配到对应在arkts1.2中的SDK接口 - const ets2SdkSignature = this.getEts2SdkSignatureWithEts1Method(callMethod, args); + const ets2SdkSignature = this.getEts2SdkSignatureWithEts1Method(callMethod, args, true); if (ets2SdkSignature === null) { return null; } @@ -660,6 +861,82 @@ export class NumericSemanticCheck implements BaseChecker { return res; } + private checkSDKReturnType(invokeExpr: AbstractInvokeExpr): NumberCategory | null { + const callMethod = this.scene.getMethod(invokeExpr.getMethodSignature()); + if (callMethod === null || !SdkUtils.isMethodFromSdk(callMethod)) { + return null; + } + const args = invokeExpr.getArgs(); + + // 根据找到的对应arkts1.1中的SDK接口匹配到对应在arkts1.2中的SDK接口 + const ets2SdkSignature = this.getEts2SdkSignatureWithEts1Method(callMethod, args, false); + if (ets2SdkSignature === null) { + return null; + } + const returnType = ets2SdkSignature.getType(); + if (this.isLongType(returnType)) { + return NumberCategory.long; + } + if (this.isIntType(returnType)) { + return NumberCategory.int; + } + return null; + } + + private checkSDKFieldType(fieldRef: AbstractFieldRef): NumberCategory | null { + if (!SdkUtils.isFieldFromSdk(fieldRef) || !Utils.isNearlyNumberType(fieldRef.getType())) { + return null; + } + const ets1SdkFileSig = fieldRef.getFieldSignature().getDeclaringSignature().getDeclaringFileSignature(); + const ets2SdkFileSig = new FileSignature(ets1SdkFileSig.getProjectName(), ets1SdkFileSig.getFileName().replace('.d.ts', '.d.ets')); + const ets2SdkFileSigBak = new FileSignature(ets1SdkFileSig.getProjectName(), ets1SdkFileSig.getFileName()); + const ets2SdkFile = this.ets2SdkScene?.getFile(ets2SdkFileSig) ?? this.ets2SdkScene?.getFile(ets2SdkFileSigBak); + if (!ets2SdkFile) { + return null; + } + let ets2Field = SdkUtils.getSdkField(ets2SdkFile, fieldRef); + if (!ets2Field) { + return null; + } + if (this.isIntType(ets2Field.getType())) { + return NumberCategory.int; + } + if (this.isLongType(ets2Field.getType())) { + return NumberCategory.long; + } + return null; + } + + private matchEts1NumberEts2IntLongReturnSig(ets2Sigs: MethodSignature[], ets1Sig: MethodSignature): MethodSignature | null { + const ets1Params = ets1Sig.getMethodSubSignature().getParameters(); + for (const ets2Sig of ets2Sigs) { + let allParamMatched = true; + const ets2Params = ets2Sig.getMethodSubSignature().getParameters(); + if (ets2Params.length !== ets1Params.length) { + continue; + } + for (let i = 0; i < ets1Params.length; i++) { + const ets2ParamType = ets2Params[i].getType(); + const ets1ParamType = ets1Params[i].getType(); + if ( + ets2ParamType === ets1ParamType || + (ets1ParamType instanceof NumberType && (this.isIntType(ets2ParamType) || this.isLongType(ets2ParamType))) + ) { + continue; + } + allParamMatched = false; + break; + } + if (allParamMatched) { + const returnType = ets2Sig.getType(); + if (this.isLongType(returnType) || this.isIntType(returnType)) { + return ets2Sig; + } + } + } + return null; + } + private matchEts1NumberEts2IntLongMethodSig(ets2Sigs: MethodSignature[], ets1Sig: MethodSignature): MethodSignature | null { let intSDKMatched: MethodSignature | null = null; const ets1Params = ets1Sig.getMethodSubSignature().getParameters(); @@ -697,7 +974,8 @@ export class NumericSemanticCheck implements BaseChecker { return intSDKMatched; } - private getEts2SdkSignatureWithEts1Method(ets1SDK: ArkMethod, args: Value[], exactMatch: boolean = true): MethodSignature | null { + // checkArg = true is for checking SDK arg with int or long; otherwise is for checking SDK return with int or long + private getEts2SdkSignatureWithEts1Method(ets1SDK: ArkMethod, args: Value[], checkArg: boolean, exactMatch: boolean = true): MethodSignature | null { const ets2Sdks = this.ets2Sdks; if (ets2Sdks === undefined || ets2Sdks.length === 0) { return null; @@ -726,7 +1004,10 @@ export class NumericSemanticCheck implements BaseChecker { if (!exactMatch && declareSigs.length === 1) { return declareSigs[0]; } - return this.matchEts1NumberEts2IntLongMethodSig(declareSigs, ets1SigMatched); + if (checkArg) { + return this.matchEts1NumberEts2IntLongMethodSig(declareSigs, ets1SigMatched); + } + return this.matchEts1NumberEts2IntLongReturnSig(declareSigs, ets1SigMatched); } private getEts2SdkWithEts1SdkInfo(ets2File: ArkFile, ets1SDK: ArkMethod): ArkMethod | null { @@ -804,7 +1085,7 @@ export class NumericSemanticCheck implements BaseChecker { return IssueReason.RelatedWithNonETS2; } if (value instanceof NumberConstant) { - if (this.isNumberConstantWithDecimalPoint(value)) { + if (this.isNumberConstantActuallyFloat(value)) { return IssueReason.UsedWithOtherType; } return IssueReason.OnlyUsedAsIntLong; @@ -830,8 +1111,18 @@ export class NumericSemanticCheck implements BaseChecker { return IssueReason.Other; } - private isNumberConstantWithDecimalPoint(constant: NumberConstant): boolean { - return constant.getValue().includes('.'); + private isNumberConstantActuallyFloat(constant: NumberConstant): boolean { + const valueStr = constant.getValue(); + if (valueStr.includes('.') && !valueStr.includes('e')) { + // 数字字面量非科学计数的写法,并且有小数点,则一定是浮点数,1.0也认为是float + return true; + } + const num = Number(constant.getValue()); + if (isNaN(num)) { + // 超大数字字面量转换后是NaN,按照number处理 + return true; + } + return !Number.isInteger(num); } // 判断number constant是否为1.0、2.0这种可以转成1、2的整型形式 @@ -899,16 +1190,20 @@ export class NumericSemanticCheck implements BaseChecker { } if (stmt.getCfg().getDeclaringMethod().getLanguage() !== Language.ARKTS1_2) { - hasChecked.set(local, { issueReason: IssueReason.RelatedWithNonETS2, numberCategory: numberCategory }); + hasChecked.set(local, { issueReason: IssueReason.RelatedWithNonETS2, numberCategory: NumberCategory.number }); return IssueReason.RelatedWithNonETS2; } // 先将value加入map中,默认设置成false,避免后续递归查找阶段出现死循环,最后再根据查找结果绝对是否重新设置成true - hasChecked.set(local, { issueReason: IssueReason.Other, numberCategory: numberCategory }); + hasChecked.set(local, { issueReason: IssueReason.Other, numberCategory: NumberCategory.number }); const resWithLocalType = this.checkResWithLocalType(local, stmt); if (resWithLocalType) { - hasChecked.set(local, { issueReason: resWithLocalType, numberCategory: numberCategory }); + if (resWithLocalType === IssueReason.OnlyUsedAsIntLong) { + hasChecked.set(local, { issueReason: resWithLocalType, numberCategory: numberCategory }); + } else { + hasChecked.set(local, { issueReason: resWithLocalType, numberCategory: NumberCategory.number }); + } return resWithLocalType; } @@ -931,7 +1226,7 @@ export class NumericSemanticCheck implements BaseChecker { if (declaringStmt === null) { // local变量未找到定义语句,直接返回false,因为就算是能确认local仅当做int使用,也找不到定义语句去修改类型注解为int,所以后续检查都没有意义 logger.error(`Missing declaring stmt, local: ${local.getName()}`); - hasChecked.set(local, { issueReason: IssueReason.CannotFindAll, numberCategory: numberCategory }); + hasChecked.set(local, { issueReason: IssueReason.CannotFindAll, numberCategory: NumberCategory.number }); return IssueReason.CannotFindAll; } hasChecked.delete(local); @@ -981,13 +1276,17 @@ export class NumericSemanticCheck implements BaseChecker { private checkRelatedStmtForLocal(stmt: Stmt, local: Local, hasChecked: Map, numberCategory: NumberCategory): IssueInfo { if (stmt instanceof ArkAssignStmt && stmt.getLeftOp() === local) { const issueReason = this.checkValueOnlyUsedAsIntLong(stmt, stmt.getRightOp(), hasChecked, numberCategory); - return { issueReason, numberCategory }; + if (issueReason === IssueReason.OnlyUsedAsIntLong) { + return { issueReason, numberCategory }; + } else { + return { issueReason, numberCategory: NumberCategory.number }; + } } // 当前检查的local位于赋值语句的右边,若参与除法运算则看做double类型使用,若作为SDK入参依据SDK定义,其余运算、赋值等处理不会影响其自身从int -> number,所以不处理 if (stmt instanceof ArkAssignStmt && stmt.getLeftOp() !== local) { const rightOp = stmt.getRightOp(); if (rightOp instanceof ArkNormalBinopExpr && rightOp.getOperator() === NormalBinaryOperator.Division) { - return { issueReason: IssueReason.UsedWithOtherType, numberCategory }; + return { issueReason: IssueReason.UsedWithOtherType, numberCategory: NumberCategory.number }; } if (rightOp instanceof AbstractInvokeExpr) { const res = this.checkLocalUsedAsSDKArg(rightOp, local, hasChecked); @@ -1011,7 +1310,7 @@ export class NumericSemanticCheck implements BaseChecker { return { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory }; } logger.error(`Need to check new type of stmt: ${stmt.toString()}, method: ${stmt.getCfg().getDeclaringMethod().getSignature().toString()}`); - return { issueReason: IssueReason.Other, numberCategory }; + return { issueReason: IssueReason.Other, numberCategory: NumberCategory.number }; } // 判断local是否是SDK invoke expr的入参,且其类型是int或long,否则返回null @@ -1153,11 +1452,15 @@ export class NumericSemanticCheck implements BaseChecker { if (expr.getOperator() === NormalBinaryOperator.Division) { const op1 = expr.getOp1(); const op2 = expr.getOp2(); - if (op1 instanceof NumberConstant && !this.isNumberConstantWithDecimalPoint(op1)) { + if (op1 instanceof NumberConstant && !this.isNumberConstantActuallyFloat(op1)) { this.addIssueReport(RuleCategory.NumericLiteral, NumberCategory.number, IssueReason.UsedWithOtherType, true, stmt, op1); + } else if (op1 instanceof Local) { + hasChecked.set(op1, { issueReason: IssueReason.UsedWithOtherType, numberCategory: NumberCategory.number }); } - if (op2 instanceof NumberConstant && !this.isNumberConstantWithDecimalPoint(op2)) { + if (op2 instanceof NumberConstant && !this.isNumberConstantActuallyFloat(op2)) { this.addIssueReport(RuleCategory.NumericLiteral, NumberCategory.number, IssueReason.UsedWithOtherType, true, stmt, op2); + } else if (op2 instanceof Local) { + hasChecked.set(op2, { issueReason: IssueReason.UsedWithOtherType, numberCategory: NumberCategory.number }); } return IssueReason.UsedWithOtherType; } @@ -1186,7 +1489,7 @@ export class NumericSemanticCheck implements BaseChecker { if (SdkUtils.isMethodFromSdk(method)) { const ets2SDKSig = this.getEts2SdkSignatureWithEts1Method(method, expr.getArgs(), false); if (ets2SDKSig === null) { - return IssueReason.RelatedWithNonETS2; + return IssueReason.UsedWithOtherType; } if (this.isIntType(ets2SDKSig.getType()) || this.isLongType(ets2SDKSig.getType())) { return IssueReason.OnlyUsedAsIntLong; @@ -1259,19 +1562,22 @@ export class NumericSemanticCheck implements BaseChecker { return IssueReason.Other; } - private checkFieldRef(ref: AbstractRef, currentClassSig: ClassSignature, numberCategory: NumberCategory, hasChecked: Map): IssueReason { - const refType = ref.getType(); - if (!(ref instanceof AbstractFieldRef)) { - if (!Utils.isNearlyNumberType(refType)) { - if (refType instanceof UnknownType) { - return IssueReason.CannotFindAll; - } - return IssueReason.UsedWithOtherType; + private checkFieldRef( + fieldRef: AbstractFieldRef, + currentClassSig: ClassSignature, + numberCategory: NumberCategory, + hasChecked: Map + ): IssueReason { + if (SdkUtils.isFieldFromSdk(fieldRef)) { + const ets2FieldType = this.checkSDKFieldType(fieldRef); + if (ets2FieldType && (ets2FieldType === NumberCategory.int || ets2FieldType === NumberCategory.long)) { + return IssueReason.OnlyUsedAsIntLong; } - // 此处若想充分解析,需要在整个项目中找到该field的所有使用到的地方,效率很低,且很容易找漏,当前不做检查,直接返回false - return IssueReason.CannotFindAll; + return IssueReason.UsedWithOtherType; } - const fieldBase = ref.getFieldSignature().getDeclaringSignature(); + + const refType = fieldRef.getType(); + const fieldBase = fieldRef.getFieldSignature().getDeclaringSignature(); if (fieldBase instanceof NamespaceSignature) { return IssueReason.CannotFindAll; } @@ -1282,7 +1588,6 @@ export class NumericSemanticCheck implements BaseChecker { if (baseClass.getLanguage() !== Language.ARKTS1_2) { return IssueReason.RelatedWithNonETS2; } - // TODO: typeliteral是什么类型? if ( baseClass.getCategory() === ClassCategory.ENUM || baseClass.getCategory() === ClassCategory.OBJECT || @@ -1295,7 +1600,7 @@ export class NumericSemanticCheck implements BaseChecker { if (baseClass.getSignature().toString() !== currentClassSig.toString()) { return IssueReason.CannotFindAll; } - const field = baseClass.getField(ref.getFieldSignature()); + const field = baseClass.getField(fieldRef.getFieldSignature()); if (field === null) { return IssueReason.CannotFindAll; } @@ -1306,29 +1611,44 @@ export class NumericSemanticCheck implements BaseChecker { if (!Utils.isNearlyNumberType(refType)) { if (refType instanceof UnknownType) { const res = IssueReason.CannotFindAll; - this.classFieldRes.set(field, { issueReason: res, numberCategory: numberCategory }); + this.classFieldRes.set(field, { issueReason: res, numberCategory: NumberCategory.number }); return res; } const res = IssueReason.UsedWithOtherType; - this.classFieldRes.set(field, { issueReason: res, numberCategory: numberCategory }); - return res; - } - if (field.containsModifier(ModifierType.READONLY)) { - // 先写入默认值,避免后续查找时出现死循环,得到结果后再进行替换 - this.classFieldRes.set(field, { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory: numberCategory }); - const res = this.checkReadonlyFieldInitializer(field, baseClass, numberCategory, hasChecked); - this.classFieldRes.set(field, { issueReason: res, numberCategory: numberCategory }); + this.classFieldRes.set(field, { issueReason: res, numberCategory: NumberCategory.number }); return res; } if (field.containsModifier(ModifierType.PRIVATE)) { + // 如果属性有setter方法,则无法找全其赋值的地方,无法判断是否为int,保守方式判定为number + // 如果属性有getter方法,则无法找全其使用的地方,如果有用作除法运算,则应该是number,保守方式判定为number + if (this.fieldWithSetter(field, baseClass) || this.fieldWithGetter(field, baseClass)) { + const res = IssueReason.CannotFindAll; + this.classFieldRes.set(field, { issueReason: res, numberCategory: NumberCategory.number }); + return res; + } + if (field.containsModifier(ModifierType.READONLY)) { + // 先写入默认值,避免后续查找时出现死循环,得到结果后再进行替换 + this.classFieldRes.set(field, { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory: numberCategory }); + const res = this.checkReadonlyFieldInitializer(field, baseClass, numberCategory, hasChecked); + if (res === IssueReason.OnlyUsedAsIntLong) { + this.classFieldRes.set(field, { issueReason: res, numberCategory: numberCategory }); + } else { + this.classFieldRes.set(field, { issueReason: res, numberCategory: NumberCategory.number }); + } + return res; + } this.classFieldRes.set(field, { issueReason: IssueReason.OnlyUsedAsIntLong, numberCategory: numberCategory }); const res = this.checkPrivateField(field, baseClass, numberCategory, hasChecked); - this.classFieldRes.set(field, { issueReason: res, numberCategory: numberCategory }); + if (res === IssueReason.OnlyUsedAsIntLong) { + this.classFieldRes.set(field, { issueReason: res, numberCategory: numberCategory }); + } else { + this.classFieldRes.set(field, { issueReason: res, numberCategory: NumberCategory.number }); + } return res; } // 此处若想充分解析,需要在整个项目中找到该field的所有使用到的地方,效率很低,且很容易找漏,当前不做检查,直接返回false const res = IssueReason.CannotFindAll; - this.classFieldRes.set(field, { issueReason: res, numberCategory: numberCategory }); + this.classFieldRes.set(field, { issueReason: res, numberCategory: NumberCategory.number }); return res; } @@ -1380,9 +1700,6 @@ export class NumericSemanticCheck implements BaseChecker { } private checkPrivateField(field: ArkField, baseClass: ArkClass, numberCategory: NumberCategory, hasChecked: Map): IssueReason { - if (this.fieldWithSetter(field, baseClass)) { - return IssueReason.CannotFindAll; - } const methods = baseClass.getMethods(true); for (const method of methods) { if (method.getName().startsWith('Set-') || method.getName().startsWith('Get-')) { @@ -1419,7 +1736,6 @@ export class NumericSemanticCheck implements BaseChecker { if (rightOp instanceof AbstractFieldRef) { if (this.isFieldRefMatchArkField(rightOp, field)) { if (leftOp instanceof Local && leftOp.getName().startsWith(TEMP_LOCAL_PREFIX)) { - // return this.checkTempLocalAssignByFieldRef(leftOp); return this.isLocalOnlyUsedAsIntLong(stmt, leftOp, hasChecked, numberCategory); } return IssueReason.OnlyUsedAsIntLong; @@ -1474,6 +1790,39 @@ export class NumericSemanticCheck implements BaseChecker { return false; } + private fieldWithGetter(field: ArkField, baseClass: ArkClass): boolean { + const methods = baseClass.getMethods(); + for (const method of methods) { + if (!method.getName().startsWith('Get-')) { + continue; + } + const stmts = method.getCfg()?.getStmts(); + if (stmts === undefined) { + continue; + } + for (const stmt of stmts) { + if (!(stmt instanceof ArkReturnStmt)) { + continue; + } + const op = stmt.getOp(); + if (op instanceof Local) { + const opDeclaringStmt = op.getDeclaringStmt(); + if (!(opDeclaringStmt instanceof ArkAssignStmt)) { + continue; + } + const rightOp = opDeclaringStmt.getRightOp(); + if (!(rightOp instanceof ArkInstanceFieldRef)) { + continue; + } + if (field.getName() === rightOp.getFieldName()) { + return true; + } + } + } + } + return false; + } + private checkAllArgsOfParameter(stmt: Stmt, hasChecked: Map, numberCategory: NumberCategory): IssueReason { let checkAll = { value: true }; let visited: Set = new Set(); @@ -1623,30 +1972,22 @@ export class NumericSemanticCheck implements BaseChecker { } private getFieldIssueFromIssueList(field: ArkField): IssueReport | null { + const filePath = field.getDeclaringArkClass().getDeclaringArkFile().getFilePath(); const position: WarnInfo = { line: field.getOriginPosition().getLineNo(), startCol: field.getOriginPosition().getColNo(), endCol: field.getOriginPosition().getColNo(), filePath: field.getDeclaringArkClass().getDeclaringArkFile().getFilePath(), }; - const fixKeyPrefix = position.line + '%' + position.startCol + '%' + position.endCol + '%'; - for (const issue of this.issues) { - if (issue.defect.fixKey.startsWith(fixKeyPrefix)) { - return issue; - } - } - return null; + const mapKey = `${filePath}%${position.line}%${position.startCol}%${position.endCol}%${this.rule.ruleId}`; + return this.issuesMap.get(mapKey) ?? null; } private getLocalIssueFromIssueList(local: Local, stmt: Stmt): IssueReport | null { + const filePath = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath(); const position = getLineAndColumn(stmt, local, true); - const fixKeyPrefix = position.line + '%' + position.startCol + '%' + position.endCol + '%'; - for (const issue of this.issues) { - if (issue.defect.fixKey.startsWith(fixKeyPrefix)) { - return issue; - } - } - return null; + const mapKey = `${filePath}%${position.line}%${position.startCol}%${position.endCol}%${this.rule.ruleId}`; + return this.issuesMap.get(mapKey) ?? null; } private getWarnInfo(field?: ArkField, issueStmt?: Stmt, value?: Value): WarnInfo | null { @@ -1707,24 +2048,7 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private getDesc( - ruleCategory: RuleCategory, - reason: IssueReason, - numberCategory: NumberCategory, - couldAutofix: boolean, - issueStmt?: Stmt, - usedStmt?: Stmt - ): string | null { - if (ruleCategory === RuleCategory.SDKIntType) { - if (reason === IssueReason.OnlyUsedAsIntLong) { - if (usedStmt) { - return `It has relationship with the arg of SDK API in ${this.getUsedStmtDesc(usedStmt, issueStmt)} and only used as ${numberCategory}, should be defined as ${numberCategory} (${ruleCategory})`; - } - logger.error('Missing used stmt when getting issue description'); - return null; - } - return `The arg of SDK API should be ${numberCategory} here (${ruleCategory})`; - } + private getDesc(ruleCategory: RuleCategory, reason: IssueReason, couldAutofix: boolean): string | null { if (ruleCategory === RuleCategory.NumericLiteral) { if (reason === IssueReason.OnlyUsedAsIntLong) { return `It is used as ${NumberCategory.int} (${ruleCategory})`; @@ -1747,24 +2071,7 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private addIssueReport( - ruleCategory: RuleCategory, - numberCategory: NumberCategory, - reason: IssueReason, - couldAutofix: boolean, - issueStmt?: Stmt, - value?: Value, - field?: ArkField, - usedStmt?: Stmt - ): void { - const severity = this.rule.alert ?? this.metaData.severity; - let warnInfo = this.getWarnInfo(field, issueStmt, value); - let problem = this.getProblem(ruleCategory, reason); - let desc = this.getDesc(ruleCategory, reason, numberCategory, couldAutofix, issueStmt, usedStmt); - if (!warnInfo || !problem || !desc) { - return; - } - + private shouldSkipDuplicatedIssue(numberCategory: NumberCategory, field?: ArkField, value?: Value, issueStmt?: Stmt): boolean { // 添加新的issue之前需要检查一下已有issue,避免重复issue,或2个issue之间冲突,一个issue要改为int,一个issue要改为long let currentIssue: IssueReport | null = null; let issueCategory: NumberCategory | null = null; @@ -1783,23 +2090,60 @@ export class NumericSemanticCheck implements BaseChecker { } if (currentIssue && issueCategory) { const issueReason = this.getIssueReasonFromDefectInfo(currentIssue.defect); - if (issueReason !== null) { - if (issueReason === IssueReason.OnlyUsedAsIntLong) { - if (issueCategory !== NumberCategory.long && numberCategory === NumberCategory.long) { - // 删除掉之前的修复为int的,用本次即将add的新的issue替代 - const index = this.issues.indexOf(currentIssue); - if (index > -1) { - this.issues.splice(index, 1); - } - } else { - // 已有的issue已经足够进行自动修复处理,无需重复添加 - return; - } - } else { - // 已有的issue对非int进行修改,无需重复添加 - return; - } + if (issueReason === null) { + return false; + } + if (issueReason !== IssueReason.OnlyUsedAsIntLong) { + return true; + } + if (issueCategory !== NumberCategory.long && numberCategory === NumberCategory.long) { + // 删除掉之前的修复为int的,用本次即将add的新的issue替代 + this.issuesMap.delete(this.getIssuesMapKey(currentIssue.defect.mergeKey)); + return false; + } else { + // 已有的issue已经足够进行自动修复处理,无需重复添加 + return true; + } + } + return false; + } + + private getIssuesMapKey(mergeKey: string): string { + const lastIndex = mergeKey.lastIndexOf('%'); + return mergeKey.substring(0, lastIndex); + } + + private addIssueReportForSDKArg( + ruleCategory: RuleCategory, + numberCategory: NumberCategory, + reason: IssueReason, + couldAutofix: boolean, + issueStmt?: Stmt, + value?: Value, + field?: ArkField, + usedStmt?: Stmt + ): void { + const severity = this.rule.alert ?? this.metaData.severity; + let warnInfo = this.getWarnInfo(field, issueStmt, value); + let problem = this.getProblem(ruleCategory, reason); + if (!warnInfo || !problem) { + return; + } + let desc: string; + if (reason === IssueReason.OnlyUsedAsIntLong) { + if (usedStmt) { + desc = `It has relationship with the arg of SDK API in ${this.getUsedStmtDesc(usedStmt, issueStmt)} and only used as ${numberCategory}, should be defined as ${numberCategory} (${ruleCategory})`; + } else { + logger.error('Missing used stmt when getting issue description'); + return; } + } else { + desc = `The arg of SDK API should be ${numberCategory} here (${ruleCategory})`; + } + + const shouldSkip = this.shouldSkipDuplicatedIssue(numberCategory, field, value, issueStmt); + if (shouldSkip) { + return; } let defects = new Defects( @@ -1818,17 +2162,115 @@ export class NumericSemanticCheck implements BaseChecker { ); if (!couldAutofix) { - this.issues.push(new IssueReport(defects, undefined)); + this.issuesMap.set(this.getIssuesMapKey(defects.mergeKey), new IssueReport(defects, undefined)); return; } - if (ruleCategory === RuleCategory.SDKIntType) { - const autofix = this.generateSDKRuleFix(warnInfo, reason, numberCategory, issueStmt, value, field); - if (autofix === null) { - // 此规则必须修复,若autofix为null,则表示无需修复,不添加issue - return; + const autofix = this.generateSDKArgRuleFix(warnInfo, reason, numberCategory, issueStmt, value, field); + if (autofix === null) { + // 此规则必须修复,若autofix为null,则表示无需修复,不添加issue + return; + } else { + this.issuesMap.set(this.getIssuesMapKey(defects.mergeKey), new IssueReport(defects, autofix)); + } + return; + } + + private addIssueReportForSDKReturnOrField( + ruleCategory: RuleCategory, + numberCategory: NumberCategory, + reason: IssueReason, + issueStmt?: Stmt, + value?: Value, + field?: ArkField, + usedStmt?: Stmt + ): void { + const severity = this.rule.alert ?? this.metaData.severity; + let warnInfo = this.getWarnInfo(field, issueStmt, value); + let problem = this.getProblem(ruleCategory, reason); + if (!warnInfo || !problem) { + return; + } + const shouldSkip = this.shouldSkipDuplicatedIssue(numberCategory, field, value, issueStmt); + if (shouldSkip) { + return; + } + + let desc: string; + if (reason === IssueReason.OnlyUsedAsIntLong) { + if (usedStmt) { + desc = `It has relationship with the SDK API in ${this.getUsedStmtDesc(usedStmt, issueStmt)} and only used as ${numberCategory}, should be defined as ${numberCategory} (${ruleCategory})`; } else { - this.issues.push(new IssueReport(defects, autofix)); + logger.error('Missing used stmt when getting issue description'); + return; } + } else { + desc = `It is used as number (${ruleCategory})`; + } + + let defects = new Defects( + warnInfo.line, + warnInfo.startCol, + warnInfo.endCol, + problem, + desc, + severity, + this.rule.ruleId, + warnInfo.filePath, + this.metaData.ruleDocPath, + true, + false, + true + ); + + const autofix = this.generateSDKReturnOrFieldRuleFix(warnInfo, numberCategory, issueStmt, field); + if (autofix === null) { + // 此规则必须修复,若autofix为null,则表示无需修复,不添加issue + return; + } else { + this.issuesMap.set(this.getIssuesMapKey(defects.mergeKey), new IssueReport(defects, autofix)); + } + return; + } + + private addIssueReport( + ruleCategory: RuleCategory, + numberCategory: NumberCategory, + reason: IssueReason, + couldAutofix: boolean, + issueStmt?: Stmt, + value?: Value, + field?: ArkField + ): void { + const severity = this.rule.alert ?? this.metaData.severity; + let warnInfo = this.getWarnInfo(field, issueStmt, value); + let problem = this.getProblem(ruleCategory, reason); + let desc = this.getDesc(ruleCategory, reason, couldAutofix); + if (!warnInfo || !problem || !desc) { + return; + } + + const shouldSkip = this.shouldSkipDuplicatedIssue(numberCategory, field, value, issueStmt); + if (shouldSkip) { + return; + } + + let defects = new Defects( + warnInfo.line, + warnInfo.startCol, + warnInfo.endCol, + problem, + desc, + severity, + this.rule.ruleId, + warnInfo.filePath, + this.metaData.ruleDocPath, + true, + false, + couldAutofix + ); + + if (!couldAutofix) { + this.issuesMap.set(this.getIssuesMapKey(defects.mergeKey), new IssueReport(defects, undefined)); return; } if (ruleCategory === RuleCategory.NumericLiteral) { @@ -1837,7 +2279,7 @@ export class NumericSemanticCheck implements BaseChecker { // 此规则必须修复,若autofix为null,则表示无需修复,不添加issue return; } - this.issues.push(new IssueReport(defects, autofix)); + this.issuesMap.set(this.getIssuesMapKey(defects.mergeKey), new IssueReport(defects, autofix)); return; } if (ruleCategory === RuleCategory.ArrayIndex) { @@ -1845,9 +2287,9 @@ export class NumericSemanticCheck implements BaseChecker { const autofix = this.generateIntConstantIndexRuleFix(warnInfo, issueStmt, value); if (autofix === null) { defects.fixable = false; - this.issues.push(new IssueReport(defects, undefined)); + this.issuesMap.set(this.getIssuesMapKey(defects.mergeKey), new IssueReport(defects, undefined)); } else { - this.issues.push(new IssueReport(defects, autofix)); + this.issuesMap.set(this.getIssuesMapKey(defects.mergeKey), new IssueReport(defects, autofix)); } } else { const autofix = this.generateNumericLiteralRuleFix(warnInfo, reason, issueStmt, value, field); @@ -1855,7 +2297,7 @@ export class NumericSemanticCheck implements BaseChecker { // 此规则必须修复,若autofix为null,则表示无需修复,不添加issue return; } - this.issues.push(new IssueReport(defects, autofix)); + this.issuesMap.set(this.getIssuesMapKey(defects.mergeKey), new IssueReport(defects, autofix)); } return; } @@ -1881,7 +2323,12 @@ export class NumericSemanticCheck implements BaseChecker { logger.error('Missing both issue stmt and field when generating auto fix info.'); return null; } - return AstTreeUtils.getASTNode(arkFile.getName(), arkFile.getCode()); + let sourceFile = this.sourceFiles.get(arkFile.getFileSignature()); + if (!sourceFile) { + sourceFile = AstTreeUtils.getASTNode(arkFile.getName(), arkFile.getCode()); + this.sourceFiles.set(arkFile.getFileSignature(), sourceFile); + } + return sourceFile; } private generateRuleFixForLocalDefine(sourceFile: ts.SourceFile, warnInfo: WarnInfo, numberCategory: NumberCategory): RuleFix | null { @@ -1995,7 +2442,7 @@ export class NumericSemanticCheck implements BaseChecker { return null; } - private generateSDKRuleFix( + private generateSDKArgRuleFix( warnInfo: WarnInfo, issueReason: IssueReason, numberCategory: NumberCategory, @@ -2077,6 +2524,17 @@ export class NumericSemanticCheck implements BaseChecker { } } + private generateSDKReturnOrFieldRuleFix(warnInfo: WarnInfo, numberCategory: NumberCategory, issueStmt?: Stmt, field?: ArkField): RuleFix | null { + const sourceFile = this.getSourceFile(field, issueStmt); + if (!sourceFile) { + return null; + } + if (field) { + return this.generateRuleFixForFieldDefine(sourceFile, warnInfo, numberCategory); + } + return this.generateRuleFixForLocalDefine(sourceFile, warnInfo, numberCategory); + } + private generateIntConstantIndexRuleFix(warnInfo: WarnInfo, issueStmt: Stmt, constant: NumberConstant): RuleFix | null { if (!this.isFloatActuallyInt(constant)) { return null; @@ -2121,7 +2579,7 @@ export class NumericSemanticCheck implements BaseChecker { if (value instanceof NumberConstant) { // 对整型字面量进行自动修复,转成浮点字面量,例如1->1.0 - if (this.isNumberConstantWithDecimalPoint(value)) { + if (this.isNumberConstantActuallyFloat(value)) { // 无需修复 return null; } diff --git a/ets2panda/linter/homecheck/src/utils/common/CheckEntry.ts b/ets2panda/linter/homecheck/src/utils/common/CheckEntry.ts index cb365aed60..728844baea 100644 --- a/ets2panda/linter/homecheck/src/utils/common/CheckEntry.ts +++ b/ets2panda/linter/homecheck/src/utils/common/CheckEntry.ts @@ -269,6 +269,7 @@ function buildScene(fileList: string[], checkEntry: CheckEntry): boolean { checkEntry.scene.buildSceneFromFiles(sceneConfig); logger.info('Build scene completed.'); checkEntry.scene.inferTypes(); + checkEntry.scene.inferTypes(); logger.info('Infer types completed.'); } catch (error) { logger.error('Build scene or infer types error: ', error); diff --git a/ets2panda/linter/homecheck/src/utils/common/SDKUtils.ts b/ets2panda/linter/homecheck/src/utils/common/SDKUtils.ts index 22343b644c..f094b9e132 100644 --- a/ets2panda/linter/homecheck/src/utils/common/SDKUtils.ts +++ b/ets2panda/linter/homecheck/src/utils/common/SDKUtils.ts @@ -15,8 +15,9 @@ import { Sdk } from 'arkanalyzer/lib/Config'; import fs from 'fs'; -import { AnyType, ArkMethod, EnumValueType, MethodSignature, UnionType, Value } from 'arkanalyzer'; +import { AnyType, ArkField, ArkFile, ArkMethod, ClassSignature, EnumValueType, Local, MethodSignature, UnionType, Value } from 'arkanalyzer'; import { Utils } from './Utils'; +import { AbstractFieldRef, ArkNamespace, NamespaceSignature } from 'arkanalyzer/lib'; export class SdkUtils { static OhosSdkName = 'ohosSdk'; @@ -57,6 +58,11 @@ export class SdkUtils { return projectName === this.OhosSdkName || projectName === this.HmsSdkName; } + static isFieldFromSdk(fieldRef: AbstractFieldRef): boolean { + const projectName = fieldRef.getFieldSignature().getDeclaringSignature().getDeclaringFileSignature().getProjectName(); + return projectName === this.OhosSdkName || projectName === this.HmsSdkName; + } + static getSdkMatchedSignature(ets1SDK: ArkMethod, args: Value[]): MethodSignature | null { const declareSigs = ets1SDK.getDeclareSignatures(); if (declareSigs === null) { @@ -94,4 +100,36 @@ export class SdkUtils { } return ets1SigMatched; } + + static getSdkField(etsFile: ArkFile, fieldRef: AbstractFieldRef): ArkField | Local | null { + const declaringSig = fieldRef.getFieldSignature().getDeclaringSignature(); + if (declaringSig instanceof ClassSignature) { + const declaringNS = declaringSig.getDeclaringNamespaceSignature(); + if (!declaringNS) { + return etsFile?.getClassWithName(declaringSig.getClassName())?.getFieldWithName(fieldRef.getFieldName()) ?? null; + } + const namespace = this.getSdkNamespace(etsFile, declaringNS); + if (!namespace) { + return null; + } + return namespace.getClassWithName(declaringSig.getClassName())?.getFieldWithName(fieldRef.getFieldName()) ?? null; + } + const namespace = this.getSdkNamespace(etsFile, declaringSig); + if (!namespace) { + return null; + } + return namespace.getDefaultClass().getDefaultArkMethod()?.getBody()?.getLocals().get(fieldRef.getFieldName()) ?? null; + } + + static getSdkNamespace(etsFile: ArkFile, namespaceSig: NamespaceSignature): ArkNamespace | null { + const declaringNsSig = namespaceSig.getDeclaringNamespaceSignature(); + if (!declaringNsSig) { + return etsFile.getNamespace(namespaceSig); + } + const declaringNS = this.getSdkNamespace(etsFile, declaringNsSig); + if (!declaringNS) { + return null; + } + return declaringNS.getNamespace(namespaceSig); + } } -- Gitee