diff --git a/ets2panda/linter/src/lib/TypeScriptLinter.ts b/ets2panda/linter/src/lib/TypeScriptLinter.ts index 70ab7059c246a0cac14219687a5714b5ac12ab36..f55388cfac5a6c1ec855f56159b6be09c8f04bf4 100644 --- a/ets2panda/linter/src/lib/TypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/TypeScriptLinter.ts @@ -10214,7 +10214,8 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { if ( this.isInMaxLengthControlledLoop(accessExpr) || TypeScriptLinter.hasDefaultValueProtection(accessExpr) || - this.isInLengthCheckedBlock(accessExpr) + this.isInLengthCheckedBlock(accessExpr) || + this.isInStandardLengthControlledLoop(accessExpr) ) { return true; } @@ -11108,4 +11109,101 @@ export class TypeScriptLinter extends BaseTypeScriptLinter { return false; } + + private isInStandardLengthControlledLoop(accessExpr: ts.ElementAccessExpression): boolean { + const arrayAccessInfo = this.getArrayAccessInfo(accessExpr); + if (!arrayAccessInfo) { + return false; + } + let parent: ts.Node | undefined = accessExpr; + while (parent && !ts.isForStatement(parent)) { + parent = parent.parent; + } + if (!parent) { + return false + }; + + const forStmt = parent; + + if (!forStmt.condition || !ts.isBinaryExpression(forStmt.condition)) { + return false; + } + + const condition = forStmt.condition; + const isStandardLoop = ( + (condition.operatorToken.kind === ts.SyntaxKind.LessThanToken || + condition.operatorToken.kind === ts.SyntaxKind.LessThanEqualsToken) && + ts.isPropertyAccessExpression(condition.right) && + condition.right.name.text === LENGTH_IDENTIFIER + ); + + if (!isStandardLoop) { + return false; + } + + return !this.hasDangerousArrayOperationsInForLoop(forStmt, arrayAccessInfo.arrayIdent.text); + } + + private hasDangerousArrayOperationsInForLoop(forStmt: ts.ForStatement, arrayName: string): boolean { + if (this.checkArrayModifications(forStmt.statement, arrayName)) { + return true; + } + + if (forStmt.initializer && ts.isVariableDeclarationList(forStmt.initializer)) { + const indexVar = forStmt.initializer.declarations[0]?.name.getText(); + if (indexVar && this.checkIndexModifications(forStmt.statement, indexVar)) { + return true; + } + } + + if (this.checkOutOfBoundAccess(forStmt.statement, arrayName)) { + return true; + } + + return false; + } + private checkArrayModifications(node: ts.Node, arrayName: string): boolean { + let hasModification = false; + ts.forEachChild(node, child => { + if (TypeScriptLinter.isArrayModification(child, arrayName)) { + hasModification = true; + } + if (!hasModification) { + hasModification = this.checkArrayModifications(child, arrayName); + } + }); + return hasModification; + } + + private checkIndexModifications(node: ts.Node, indexVar: string): boolean { + let hasModification = false; + ts.forEachChild(node, child => { + if (ts.isBinaryExpression(child) && + child.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isIdentifier(child.left) && + child.left.text === indexVar) { + hasModification = true; + } + if (!hasModification) { + hasModification = this.checkIndexModifications(child, indexVar); + } + }); + return hasModification; + } + + private checkOutOfBoundAccess(node: ts.Node, arrayName: string): boolean { + let hasOutOfBound = false; + ts.forEachChild(node, child => { + if (ts.isElementAccessExpression(child) && + ts.isIdentifier(child.expression) && + child.expression.text === arrayName && + ts.isNumericLiteral(child.argumentExpression)) { + hasOutOfBound = true; + } + if (!hasOutOfBound) { + hasOutOfBound = this.checkOutOfBoundAccess(child, arrayName); + } + }); + return hasOutOfBound; + } } diff --git a/ets2panda/linter/test/main/runtime_array_bound.ets b/ets2panda/linter/test/main/runtime_array_bound.ets index 94234312eb3e09ab82c8b5ff2118e6c22c06f825..934e9df563e128fa24f570286ac41e09eeb61c92 100644 --- a/ets2panda/linter/test/main/runtime_array_bound.ets +++ b/ets2panda/linter/test/main/runtime_array_bound.ets @@ -280,4 +280,18 @@ function test7(config: Record, name: string) { function getValue(): string { return ''; -} \ No newline at end of file +} + +const arr: Record = {}; +let keys: string[] = Object.keys(arr); +let values: string[] = Object.values(arr); +for (let i = 0; i < keys.length; i++) { + values[i]; +} + +const arr: Map = new Map(); +let keys: string[] = Object.keys(arr); +let values: string[] = Object.values(arr); +for (let i = 0; i < keys.length; i++) { + values[i]; +} diff --git a/ets2panda/linter/test/main/runtime_array_bound.ets.arkts2.json b/ets2panda/linter/test/main/runtime_array_bound.ets.arkts2.json index 801d14b2604e3b3af5b0cdce8d99c706183a7cf5..1c6c7709e366e2c679ba93b6a358cdc0c3713b58 100644 --- a/ets2panda/linter/test/main/runtime_array_bound.ets.arkts2.json +++ b/ets2panda/linter/test/main/runtime_array_bound.ets.arkts2.json @@ -2433,6 +2433,76 @@ "suggest": "", "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)", "severity": "ERROR" + }, + { + "line": 287, + "column": 5, + "endLine": 287, + "endColumn": 42, + "problem": "ArrayTypeImmutable", + "suggest": "", + "rule": "Array type is immutable in ArkTS1.2 (arkts-array-type-immutable)", + "severity": "ERROR" + }, + { + "line": 288, + "column": 10, + "endLine": 288, + "endColumn": 15, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 288, + "column": 14, + "endLine": 288, + "endColumn": 15, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 292, + "column": 34, + "endLine": 292, + "endColumn": 43, + "problem": "GenericCallNoTypeArgs", + "suggest": "", + "rule": "Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)", + "severity": "ERROR" + }, + { + "line": 294, + "column": 5, + "endLine": 294, + "endColumn": 42, + "problem": "ArrayTypeImmutable", + "suggest": "", + "rule": "Array type is immutable in ArkTS1.2 (arkts-array-type-immutable)", + "severity": "ERROR" + }, + { + "line": 295, + "column": 10, + "endLine": 295, + "endColumn": 15, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" + }, + { + "line": 295, + "column": 14, + "endLine": 295, + "endColumn": 15, + "problem": "NumericSemantics", + "suggest": "", + "rule": "Numeric semantics is different for integer values (arkts-numeric-semantic)", + "severity": "ERROR" } ] } \ No newline at end of file diff --git a/ets2panda/linter/test/main/runtime_array_bound.ets.migrate.ets b/ets2panda/linter/test/main/runtime_array_bound.ets.migrate.ets index 62220b36c26b28d3a4ff0ffd93a86181e94d951e..2dbaad2e11b34b40371edd8484a4c1575d444a88 100644 --- a/ets2panda/linter/test/main/runtime_array_bound.ets.migrate.ets +++ b/ets2panda/linter/test/main/runtime_array_bound.ets.migrate.ets @@ -280,4 +280,18 @@ function test7(config: Record, name: string) { function getValue(): string { return ''; -} \ No newline at end of file +} + +const arr: Record = {}; +let keys: string[] = Object.keys(arr); +let values: string[] = Object.values(arr); +for (let i: number = 0.0; i < keys.length; i++) { + values[i as int]; +} + +const arr: Map = new Map(); +let keys: string[] = Object.keys(arr); +let values: string[] = Object.values(arr); +for (let i: number = 0.0; i < keys.length; i++) { + values[i as int]; +} diff --git a/ets2panda/linter/test/main/runtime_array_bound.ets.migrate.json b/ets2panda/linter/test/main/runtime_array_bound.ets.migrate.json index 7862ff0a076ea35016ba1529bd90eccef008a044..c9862ab6cde2ce1255353e728918648f41a9c37a 100644 --- a/ets2panda/linter/test/main/runtime_array_bound.ets.migrate.json +++ b/ets2panda/linter/test/main/runtime_array_bound.ets.migrate.json @@ -193,6 +193,26 @@ "suggest": "", "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)", "severity": "ERROR" + }, + { + "line": 287, + "column": 5, + "endLine": 287, + "endColumn": 42, + "problem": "ArrayTypeImmutable", + "suggest": "", + "rule": "Array type is immutable in ArkTS1.2 (arkts-array-type-immutable)", + "severity": "ERROR" + }, + { + "line": 294, + "column": 5, + "endLine": 294, + "endColumn": 42, + "problem": "ArrayTypeImmutable", + "suggest": "", + "rule": "Array type is immutable in ArkTS1.2 (arkts-array-type-immutable)", + "severity": "ERROR" } ] }