1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { 17 ArkInstanceInvokeExpr, 18 ArkMethod, 19 ArkStaticInvokeExpr, 20 CallGraph, 21 CallGraphBuilder, 22 Stmt, 23 Value, 24} from 'arkanalyzer/lib'; 25import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger'; 26import { BaseChecker, BaseMetaData } from '../BaseChecker'; 27import { Rule, Defects, ClassMatcher, MethodMatcher, MatcherTypes, MatcherCallback } from '../../Index'; 28import { IssueReport } from '../../model/Defects'; 29import { CALL_DEPTH_LIMIT, CALLBACK_METHOD_NAME, CallGraphHelper } from './Utils'; 30import { WarnInfo } from '../../utils/common/Utils'; 31 32const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'AppStorageGetCheck'); 33const gMetaData: BaseMetaData = { 34 severity: 1, 35 ruleDocPath: '', 36 description: 37 'Get State of AppStorage in component build function, it will update UI interface when the state of AppStorage is changed', 38}; 39 40const APP_STORAGE_STR = 'AppStorage'; 41const API_SET: Set<string> = new Set<string>(['has', 'get', 'keys', 'size']); 42 43export class AppStorageGetCheck implements BaseChecker { 44 readonly metaData: BaseMetaData = gMetaData; 45 public rule: Rule; 46 public defects: Defects[] = []; 47 public issues: IssueReport[] = []; 48 49 private classMatcher: ClassMatcher = { 50 matcherType: MatcherTypes.CLASS, 51 hasViewTree: true, 52 }; 53 54 private buildMatcher: MethodMatcher = { 55 matcherType: MatcherTypes.METHOD, 56 class: [this.classMatcher], 57 name: ['build'], 58 }; 59 60 public registerMatchers(): MatcherCallback[] { 61 const matchBuildCb: MatcherCallback = { 62 matcher: this.buildMatcher, 63 callback: this.check, 64 }; 65 return [matchBuildCb]; 66 } 67 68 public check = (targetMtd: ArkMethod): void => { 69 const scene = targetMtd.getDeclaringArkFile().getScene(); 70 let callGraph = CallGraphHelper.getCGInstance(scene); 71 let callGraphBuilder = new CallGraphBuilder(callGraph, scene); 72 callGraphBuilder.buildClassHierarchyCallGraph([targetMtd.getSignature()]); 73 74 this.checkMethod(targetMtd, callGraph); 75 }; 76 77 private checkMethod(targetMtd: ArkMethod, cg: CallGraph, depth: number = 0): void { 78 if (depth > CALL_DEPTH_LIMIT) { 79 return; 80 } 81 const stmts = targetMtd.getBody()?.getCfg().getStmts() ?? []; 82 for (const stmt of stmts) { 83 this.checkAppStorageGet(stmt); 84 const invokeExpr = stmt.getInvokeExpr(); 85 if (invokeExpr && invokeExpr instanceof ArkInstanceInvokeExpr) { 86 if ( 87 CALLBACK_METHOD_NAME.includes( 88 invokeExpr.getMethodSignature().getMethodSubSignature().getMethodName() 89 ) 90 ) { 91 continue; 92 } 93 } 94 let callsite = cg.getCallSiteByStmt(stmt); 95 callsite.forEach(cs => { 96 let callee = cg.getArkMethodByFuncID(cs.calleeFuncID); 97 if (callee) { 98 this.checkMethod(callee, cg, depth + 1); 99 } 100 }); 101 } 102 } 103 104 private checkAppStorageGet(stmt: Stmt): void { 105 let invokeExpr = stmt.getInvokeExpr(); 106 if (!(invokeExpr instanceof ArkStaticInvokeExpr)) { 107 return; 108 } 109 const methodSig = invokeExpr.getMethodSignature(); 110 if (methodSig.getDeclaringClassSignature().getClassName() !== APP_STORAGE_STR) { 111 return; 112 } 113 if (!API_SET.has(methodSig.getMethodSubSignature().getMethodName())) { 114 return; 115 } 116 this.addIssueReport(stmt, invokeExpr); 117 } 118 119 private addIssueReport(stmt: Stmt, operand: Value): void { 120 const severity = this.rule.alert ?? this.metaData.severity; 121 const warnInfo = this.getLineAndColumn(stmt, operand); 122 const problem = 'AppStorageSpecChanged'; 123 const desc = `${this.metaData.description} (${this.rule.ruleId.replace('@migration/', '')})`; 124 let defects = new Defects( 125 warnInfo.line, 126 warnInfo.startCol, 127 warnInfo.endCol, 128 problem, 129 desc, 130 severity, 131 this.rule.ruleId, 132 warnInfo.filePath, 133 this.metaData.ruleDocPath, 134 true, 135 false, 136 false 137 ); 138 this.issues.push(new IssueReport(defects, undefined)); 139 } 140 141 private getLineAndColumn(stmt: Stmt, operand: Value): WarnInfo { 142 const arkFile = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile(); 143 const originPosition = stmt.getOperandOriginalPosition(operand); 144 if (arkFile && originPosition) { 145 const originPath = arkFile.getFilePath(); 146 const line = originPosition.getFirstLine(); 147 const startCol = originPosition.getFirstCol(); 148 const endCol = startCol; 149 return { line, startCol, endCol, filePath: originPath }; 150 } else { 151 logger.debug('ArkFile is null.'); 152 } 153 return { line: -1, startCol: -1, endCol: -1, filePath: '' }; 154 } 155} 156