1/* 2 * Copyright (c) 2023-2024 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 factory, 18 isComputedPropertyName, 19 isConstructorDeclaration, 20 isElementAccessExpression, 21 isIdentifier, 22 isNumericLiteral, 23 isPrivateIdentifier, 24 isStringLiteralLike, 25 setParentRecursive, 26 visitEachChild, 27 isSourceFile, 28 isIndexedAccessTypeNode, 29 isLiteralTypeNode, 30 isUnionTypeNode, 31} from 'typescript'; 32 33import type { 34 ComputedPropertyName, 35 Expression, 36 Identifier, 37 Node, 38 TransformationContext, 39 Transformer, 40 TransformerFactory, 41 ClassDeclaration, 42 ClassExpression, 43 StructDeclaration, 44 PropertyName, 45 StringLiteral, 46 LiteralTypeNode, 47 TypeNode 48} from 'typescript'; 49 50import { annotationPrefix } from '../../common/type'; 51import type {IOptions} from '../../configs/IOptions'; 52import type { INameObfuscationOption } from '../../configs/INameObfuscationOption'; 53import type {TransformPlugin} from '../TransformPlugin'; 54import {TransformerOrder} from '../TransformPlugin'; 55import {NodeUtils} from '../../utils/NodeUtils'; 56import { ArkObfuscator, performancePrinter } from '../../ArkObfuscator'; 57import { endSingleFileEvent, startSingleFileEvent } from '../../utils/PrinterUtils'; 58import { EventList } from '../../utils/PrinterTimeAndMemUtils'; 59import { 60 isInPropertyWhitelist, 61 isReservedProperty, 62 needToRecordProperty 63} from '../../utils/TransformUtil'; 64import { 65 classInfoInMemberMethodCache, 66 globalGenerator, 67 nameCache 68} from './RenameIdentifierTransformer'; 69import { FileUtils } from '../../ArkObfuscator'; 70import { UpdateMemberMethodName } from '../../utils/NameCacheUtil'; 71import { PropCollections, UnobfuscationCollections } from '../../utils/CommonCollections'; 72import { MemoryDottingDefine } from '../../utils/MemoryDottingDefine'; 73 74namespace secharmony { 75 /** 76 * Rename Properties Transformer 77 * 78 * @param option obfuscation options 79 */ 80 const createRenamePropertiesFactory = function (option: IOptions): TransformerFactory<Node> | null { 81 let profile: INameObfuscationOption | undefined = option?.mNameObfuscation; 82 let shouldPrintKeptNames: boolean = !!(option.mUnobfuscationOption?.mPrintKeptNames); 83 84 if (!profile || !profile.mEnable || !profile.mRenameProperties) { 85 return null; 86 } 87 88 return renamePropertiesFactory; 89 90 function renamePropertiesFactory(context: TransformationContext): Transformer<Node> { 91 let currentFileType: string | undefined = undefined; 92 return renamePropertiesTransformer; 93 94 function renamePropertiesTransformer(node: Node): Node { 95 if (isSourceFile(node) && ArkObfuscator.isKeptCurrentFile) { 96 return node; 97 } 98 currentFileType = isSourceFile(node) ? FileUtils.getFileSuffix(node.fileName).ext : undefined; 99 const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.PROPERTY_OBFUSCATION); 100 startSingleFileEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter); 101 102 startSingleFileEvent(EventList.RENAME_PROPERTIES); 103 let ret: Node = renameProperties(node); 104 endSingleFileEvent(EventList.RENAME_PROPERTIES); 105 106 startSingleFileEvent(EventList.UPDATE_MEMBER_METHOD_NAME); 107 UpdateMemberMethodName(nameCache, PropCollections.globalMangledTable, classInfoInMemberMethodCache); 108 endSingleFileEvent(EventList.UPDATE_MEMBER_METHOD_NAME); 109 110 startSingleFileEvent(EventList.SET_PARENT_RECURSIVE); 111 let parentNodes = setParentRecursive(ret, true); 112 endSingleFileEvent(EventList.SET_PARENT_RECURSIVE); 113 114 endSingleFileEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter); 115 ArkObfuscator.stopRecordStage(recordInfo); 116 return parentNodes; 117 } 118 119 function renameProperties(node: Node): Node { 120 if (NodeUtils.isObjectLiteralInAnnotation(node, currentFileType)) { 121 return node; 122 } 123 124 if (!NodeUtils.isPropertyNode(node)) { 125 return visitEachChild(node, renameProperties, context); 126 } 127 128 if (isElementAccessExpression(node.parent)) { 129 return renameElementAccessProperty(node); 130 } 131 132 if (isIndexedAccessTypeNode(node.parent)) { 133 return renameIndexedAccessProperty(node); 134 } 135 136 if (isComputedPropertyName(node)) { 137 return renameComputedProperty(node); 138 } 139 140 return renameProperty(node, false); 141 } 142 143 function renameElementAccessProperty(node: Node): Node { 144 if (isStringLiteralLike(node)) { 145 return renameProperty(node, false); 146 } 147 return visitEachChild(node, renameProperties, context); 148 } 149 150 function renameIndexedAccessProperty(node: Node): Node { 151 if (NodeUtils.isStringLiteralTypeNode(node)) { 152 let prop = renameProperty((node as LiteralTypeNode).literal, false); 153 if (prop !== (node as LiteralTypeNode).literal) { 154 return factory.createLiteralTypeNode(prop as StringLiteral); 155 } 156 return visitEachChild(node, renameProperties, context); 157 } 158 159 if (!isUnionTypeNode(node)) { 160 return visitEachChild(node, renameProperties, context); 161 } 162 163 let isChanged: boolean = false; 164 const elemTypes = node.types.map((elemType) => { 165 if (!elemType || !NodeUtils.isStringLiteralTypeNode(elemType)) { 166 return elemType; 167 } 168 const prop = renameProperty((elemType as LiteralTypeNode).literal, false); 169 if (prop !== (elemType as LiteralTypeNode).literal) { 170 isChanged = true; 171 return factory.createLiteralTypeNode(prop as StringLiteral); 172 } 173 return elemType; 174 }); 175 if (isChanged) { 176 return factory.createUnionTypeNode(elemTypes); 177 } 178 return visitEachChild(node, renameProperties, context); 179 } 180 181 function renameComputedProperty(node: ComputedPropertyName): ComputedPropertyName { 182 if (isStringLiteralLike(node.expression) || isNumericLiteral(node.expression)) { 183 let prop: Node = renameProperty(node.expression, true); 184 if (prop !== node.expression) { 185 return factory.createComputedPropertyName(prop as Expression); 186 } 187 } 188 189 if (isIdentifier(node.expression)) { 190 return node; 191 } 192 193 return visitEachChild(node, renameProperties, context); 194 } 195 196 function renameProperty(node: Node, computeName: boolean): Node { 197 if (!NodeUtils.isPropertyNameType(node)) { 198 return visitEachChild(node, renameProperties, context); 199 } 200 201 if (isStringLiteralLike(node) && profile?.mKeepStringProperty) { 202 if (shouldPrintKeptNames) { 203 needToRecordProperty(node.text, UnobfuscationCollections.unobfuscatedPropMap); 204 } 205 return node; 206 } 207 208 let original: string = node.text; 209 if (isInPropertyWhitelist(original, UnobfuscationCollections.unobfuscatedPropMap, shouldPrintKeptNames)) { 210 return node; 211 } 212 213 let mangledName: string = original; 214 let name: string | undefined = original.startsWith(annotationPrefix) ? original.substring(annotationPrefix.length) : undefined; 215 if (name) { 216 mangledName = getAnnotationMangledNameWithPrefix(name); 217 } else { 218 mangledName = getPropertyMangledName(original); 219 } 220 221 if (isStringLiteralLike(node)) { 222 return factory.createStringLiteral(mangledName); 223 } 224 225 /** 226 * source demo: 227 * class A { 228 * 123 = 1; // it is NumericLiteral 229 * [456] = 2; // it is NumericLiteral within ComputedPropertyName 230 * } 231 * obfuscation result: 232 * class A { 233 * a = 1; 234 * ['b'] = 2; 235 * } 236 */ 237 if (isNumericLiteral(node)) { 238 return computeName ? factory.createStringLiteral(mangledName) : factory.createIdentifier(mangledName); 239 } 240 241 if (isIdentifier(node) || isNumericLiteral(node)) { 242 return factory.createIdentifier(mangledName); 243 } 244 245 return factory.createPrivateIdentifier('#' + mangledName); 246 } 247 248 function getPropertyMangledName(original: string): string { 249 let mangledName: string = getMangledName(original); 250 return mangledName; 251 } 252 253 function getAnnotationMangledNameWithPrefix(original: string): string { 254 let mangledName: string = `${annotationPrefix}${getMangledName(original)}`; 255 return mangledName; 256 } 257 258 function getMangledName(original: string): string { 259 const historyName: string = PropCollections.historyMangledTable?.get(original); 260 let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(original); 261 while (!mangledName) { 262 let tmpName = globalGenerator.getName(); 263 if (isReservedProperty(tmpName) || 264 tmpName === original) { 265 continue; 266 } 267 268 // For incremental compilation, preventing generated names from conflicting with existing global name. 269 if (PropCollections.globalMangledNamesInCache.has(tmpName)) { 270 continue; 271 } 272 273 mangledName = tmpName; 274 } 275 PropCollections.globalMangledTable.set(original, mangledName); 276 return mangledName; 277 } 278 } 279 }; 280 281 export let transformerPlugin: TransformPlugin = { 282 'name': 'renamePropertiesPlugin', 283 'order': TransformerOrder.RENAME_PROPERTIES_TRANSFORMER, 284 'createTransformerFactory': createRenamePropertiesFactory 285 }; 286} 287 288export = secharmony; 289