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 type {IOptions} from '../../configs/IOptions'; 51import type { INameObfuscationOption } from '../../configs/INameObfuscationOption'; 52import type {TransformPlugin} from '../TransformPlugin'; 53import {TransformerOrder} from '../TransformPlugin'; 54import {NodeUtils} from '../../utils/NodeUtils'; 55import { ArkObfuscator, performancePrinter } from '../../ArkObfuscator'; 56import { EventList, endSingleFileEvent, startSingleFileEvent } from '../../utils/PrinterUtils'; 57import { 58 isInPropertyWhitelist, 59 isReservedProperty, 60 needToRecordProperty 61} from '../../utils/TransformUtil'; 62import { 63 classInfoInMemberMethodCache, 64 globalGenerator, 65 nameCache 66} from './RenameIdentifierTransformer'; 67import { UpdateMemberMethodName } from '../../utils/NameCacheUtil'; 68import { PropCollections, UnobfuscationCollections } from '../../utils/CommonCollections'; 69import { MemoryDottingDefine } from '../../utils/MemoryDottingDefine'; 70 71namespace secharmony { 72 /** 73 * Rename Properties Transformer 74 * 75 * @param option obfuscation options 76 */ 77 const createRenamePropertiesFactory = function (option: IOptions): TransformerFactory<Node> | null { 78 let profile: INameObfuscationOption | undefined = option?.mNameObfuscation; 79 let shouldPrintKeptNames: boolean = !!(option.mUnobfuscationOption?.mPrintKeptNames); 80 81 if (!profile || !profile.mEnable || !profile.mRenameProperties) { 82 return null; 83 } 84 85 return renamePropertiesFactory; 86 87 function renamePropertiesFactory(context: TransformationContext): Transformer<Node> { 88 89 return renamePropertiesTransformer; 90 91 function renamePropertiesTransformer(node: Node): Node { 92 if (isSourceFile(node) && ArkObfuscator.isKeptCurrentFile) { 93 return node; 94 } 95 96 const recordInfo = ArkObfuscator.recordStage(MemoryDottingDefine.PROPERTY_OBFUSCATION); 97 startSingleFileEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter); 98 let ret: Node = renameProperties(node); 99 UpdateMemberMethodName(nameCache, PropCollections.globalMangledTable, classInfoInMemberMethodCache); 100 let parentNodes = setParentRecursive(ret, true); 101 endSingleFileEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter); 102 ArkObfuscator.stopRecordStage(recordInfo); 103 return parentNodes; 104 } 105 106 function renameProperties(node: Node): Node { 107 if (!NodeUtils.isPropertyNode(node)) { 108 return visitEachChild(node, renameProperties, context); 109 } 110 111 if (isElementAccessExpression(node.parent)) { 112 return renameElementAccessProperty(node); 113 } 114 115 if (isIndexedAccessTypeNode(node.parent)) { 116 return renameIndexedAccessProperty(node); 117 } 118 119 if (isComputedPropertyName(node)) { 120 return renameComputedProperty(node); 121 } 122 123 return renameProperty(node, false); 124 } 125 126 function renameElementAccessProperty(node: Node): Node { 127 if (isStringLiteralLike(node)) { 128 return renameProperty(node, false); 129 } 130 return visitEachChild(node, renameProperties, context); 131 } 132 133 function renameIndexedAccessProperty(node: Node): Node { 134 if (NodeUtils.isStringLiteralTypeNode(node)) { 135 let prop = renameProperty((node as LiteralTypeNode).literal, false); 136 if (prop !== (node as LiteralTypeNode).literal) { 137 return factory.createLiteralTypeNode(prop as StringLiteral); 138 } 139 return visitEachChild(node, renameProperties, context); 140 } 141 142 if (!isUnionTypeNode(node)) { 143 return visitEachChild(node, renameProperties, context); 144 } 145 146 let isChanged: boolean = false; 147 const elemTypes = node.types.map((elemType) => { 148 if (!elemType || !NodeUtils.isStringLiteralTypeNode(elemType)) { 149 return elemType; 150 } 151 const prop = renameProperty((elemType as LiteralTypeNode).literal, false); 152 if (prop !== (elemType as LiteralTypeNode).literal) { 153 isChanged = true; 154 return factory.createLiteralTypeNode(prop as StringLiteral); 155 } 156 return elemType; 157 }); 158 if (isChanged) { 159 return factory.createUnionTypeNode(elemTypes); 160 } 161 return visitEachChild(node, renameProperties, context); 162 } 163 164 function renameComputedProperty(node: ComputedPropertyName): ComputedPropertyName { 165 if (isStringLiteralLike(node.expression) || isNumericLiteral(node.expression)) { 166 let prop: Node = renameProperty(node.expression, true); 167 if (prop !== node.expression) { 168 return factory.createComputedPropertyName(prop as Expression); 169 } 170 } 171 172 if (isIdentifier(node.expression)) { 173 return node; 174 } 175 176 return visitEachChild(node, renameProperties, context); 177 } 178 179 function renameProperty(node: Node, computeName: boolean): Node { 180 if (!NodeUtils.isPropertyNameType(node)) { 181 return visitEachChild(node, renameProperties, context); 182 } 183 184 if (isStringLiteralLike(node) && profile?.mKeepStringProperty) { 185 if (shouldPrintKeptNames) { 186 needToRecordProperty(node.text, UnobfuscationCollections.unobfuscatedPropMap); 187 } 188 return node; 189 } 190 191 let original: string = node.text; 192 if (isInPropertyWhitelist(original, UnobfuscationCollections.unobfuscatedPropMap, shouldPrintKeptNames)) { 193 return node; 194 } 195 196 let mangledName: string = getPropertyName(original); 197 198 if (isStringLiteralLike(node)) { 199 return factory.createStringLiteral(mangledName); 200 } 201 202 /** 203 * source demo: 204 * class A { 205 * 123 = 1; // it is NumericLiteral 206 * [456] = 2; // it is NumericLiteral within ComputedPropertyName 207 * } 208 * obfuscation result: 209 * class A { 210 * a = 1; 211 * ['b'] = 2; 212 * } 213 */ 214 if (isNumericLiteral(node)) { 215 return computeName ? factory.createStringLiteral(mangledName) : factory.createIdentifier(mangledName); 216 } 217 218 if (isIdentifier(node) || isNumericLiteral(node)) { 219 return factory.createIdentifier(mangledName); 220 } 221 222 return factory.createPrivateIdentifier('#' + mangledName); 223 } 224 225 function getPropertyName(original: string): string { 226 const historyName: string = PropCollections.historyMangledTable?.get(original); 227 let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(original); 228 while (!mangledName) { 229 let tmpName = globalGenerator.getName(); 230 if (isReservedProperty(tmpName) || 231 tmpName === original) { 232 continue; 233 } 234 235 // For incremental compilation, preventing generated names from conflicting with existing global name. 236 if (PropCollections.globalMangledNamesInCache.has(tmpName)) { 237 continue; 238 } 239 240 mangledName = tmpName; 241 } 242 PropCollections.globalMangledTable.set(original, mangledName); 243 return mangledName; 244 } 245 } 246 }; 247 248 export let transformerPlugin: TransformPlugin = { 249 'name': 'renamePropertiesPlugin', 250 'order': TransformerOrder.RENAME_PROPERTIES_TRANSFORMER, 251 'createTransformerFactory': createRenamePropertiesFactory 252 }; 253} 254 255export = secharmony; 256