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 forEachChild, 19 isComputedPropertyName, 20 isConstructorDeclaration, 21 isElementAccessExpression, 22 isEnumMember, 23 isIdentifier, 24 isClassDeclaration, 25 isNumericLiteral, 26 isPrivateIdentifier, 27 isStringLiteralLike, 28 isTypeNode, 29 setParentRecursive, 30 visitEachChild, 31 isStringLiteral, 32 isSourceFile 33} from 'typescript'; 34 35import type { 36 ComputedPropertyName, 37 Expression, 38 Identifier, 39 Node, 40 TransformationContext, 41 Transformer, 42 TransformerFactory, 43 ClassDeclaration, 44 ClassExpression, 45 StructDeclaration, 46 PropertyName 47} from 'typescript'; 48 49import type {IOptions} from '../../configs/IOptions'; 50import type {INameObfuscationOption} from '../../configs/INameObfuscationOption'; 51import type {INameGenerator, NameGeneratorOptions} from '../../generator/INameGenerator'; 52import {getNameGenerator, NameGeneratorType} from '../../generator/NameFactory'; 53import type {TransformPlugin} from '../TransformPlugin'; 54import {TransformerOrder} from '../TransformPlugin'; 55import {NodeUtils} from '../../utils/NodeUtils'; 56import {collectPropertyNamesAndStrings, isViewPUBasedClass} from '../../utils/OhsUtil'; 57import { ArkObfuscator, performancePrinter } from '../../ArkObfuscator'; 58import { EventList } from '../../utils/PrinterUtils'; 59import { needToBeReserved } from '../../utils/TransformUtil'; 60import { 61 classInfoInMemberMethodCache, 62 nameCache 63} from './RenameIdentifierTransformer'; 64import { UpdateMemberMethodName } from '../../utils/NameCacheUtil'; 65import { PropCollections } from '../../utils/CommonCollections'; 66 67namespace secharmony { 68 /** 69 * Rename Properties Transformer 70 * 71 * @param option obfuscation options 72 */ 73 const createRenamePropertiesFactory = function (option: IOptions): TransformerFactory<Node> { 74 let profile: INameObfuscationOption | undefined = option?.mNameObfuscation; 75 76 if (!profile || !profile.mEnable || !profile.mRenameProperties) { 77 return null; 78 } 79 80 return renamePropertiesFactory; 81 82 function renamePropertiesFactory(context: TransformationContext): Transformer<Node> { 83 let options: NameGeneratorOptions = {}; 84 let generator: INameGenerator = getNameGenerator(profile.mNameGeneratorType, options); 85 let currentConstructorParams: Set<string> = new Set<string>(); 86 87 return renamePropertiesTransformer; 88 89 function renamePropertiesTransformer(node: Node): Node { 90 if (isSourceFile(node) && ArkObfuscator.isKeptCurrentFile) { 91 return node; 92 } 93 94 collectReservedNames(node); 95 96 performancePrinter?.singleFilePrinter?.startEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter); 97 let ret: Node = renameProperties(node); 98 UpdateMemberMethodName(nameCache, PropCollections.globalMangledTable, classInfoInMemberMethodCache); 99 let parentNodes = setParentRecursive(ret, true); 100 performancePrinter?.singleFilePrinter?.endEvent(EventList.PROPERTY_OBFUSCATION, performancePrinter.timeSumPrinter); 101 return parentNodes; 102 } 103 104 function renameProperties(node: Node): Node { 105 if (isConstructorDeclaration(node)) { 106 currentConstructorParams.clear(); 107 } 108 109 if (NodeUtils.isClassPropertyInConstructorParams(node)) { 110 currentConstructorParams.add((node as Identifier).escapedText.toString()); 111 return renameProperty(node, false); 112 } 113 114 if (NodeUtils.isClassPropertyInConstructorBody(node, currentConstructorParams)) { 115 if (currentConstructorParams.has((node as Identifier).escapedText.toString())) { 116 return renameProperty(node, false); 117 } 118 } 119 120 if (!NodeUtils.isPropertyNode(node)) { 121 return visitEachChild(node, renameProperties, context); 122 } 123 124 if (isElementAccessExpression(node.parent)) { 125 return renameElementAccessProperty(node); 126 } 127 128 if (isComputedPropertyName(node)) { 129 return renameComputedProperty(node); 130 } 131 132 return renameProperty(node, false); 133 } 134 135 function renameElementAccessProperty(node: Node): Node { 136 if (isStringLiteralLike(node)) { 137 return renameProperty(node, false); 138 } 139 return visitEachChild(node, renameProperties, context); 140 } 141 142 function renameComputedProperty(node: ComputedPropertyName): ComputedPropertyName { 143 if (isStringLiteralLike(node.expression) || isNumericLiteral(node.expression)) { 144 let prop: Node = renameProperty(node.expression, true); 145 if (prop !== node.expression) { 146 return factory.createComputedPropertyName(prop as Expression); 147 } 148 } 149 150 if (isIdentifier(node.expression)) { 151 return node; 152 } 153 154 return visitEachChild(node, renameProperties, context); 155 } 156 157 function renameProperty(node: Node, computeName: boolean): Node { 158 if (!isStringLiteralLike(node) && !isIdentifier(node) && !isPrivateIdentifier(node) && !isNumericLiteral(node)) { 159 return visitEachChild(node, renameProperties, context); 160 } 161 162 if (isStringLiteralLike(node) && profile?.mKeepStringProperty) { 163 return node; 164 } 165 166 let original: string = node.text; 167 if (needToBeReserved(PropCollections.reservedProperties, PropCollections.universalReservedProperties, original)) { 168 return node; 169 } 170 171 let mangledName: string = getPropertyName(original); 172 173 if (isStringLiteralLike(node)) { 174 return factory.createStringLiteral(mangledName); 175 } 176 177 /** 178 * source demo: 179 * class A { 180 * 123 = 1; // it is NumericLiteral 181 * [456] = 2; // it is NumericLiteral within ComputedPropertyName 182 * } 183 * obfuscation result: 184 * class A { 185 * a = 1; 186 * ['b'] = 2; 187 * } 188 */ 189 if (isNumericLiteral(node)) { 190 return computeName ? factory.createStringLiteral(mangledName) : factory.createIdentifier(mangledName); 191 } 192 193 if (isIdentifier(node) || isNumericLiteral(node)) { 194 return factory.createIdentifier(mangledName); 195 } 196 197 return factory.createPrivateIdentifier('#' + mangledName); 198 } 199 200 function getPropertyName(original: string): string { 201 const historyName: string = PropCollections.historyMangledTable?.get(original); 202 let mangledName: string = historyName ? historyName : PropCollections.globalMangledTable.get(original); 203 204 while (!mangledName) { 205 let tmpName = generator.getName(); 206 if (needToBeReserved(PropCollections.reservedProperties, PropCollections.universalReservedProperties, tmpName) || 207 tmpName === original) { 208 continue; 209 } 210 211 if (PropCollections.newlyOccupiedMangledProps.has(tmpName) || PropCollections.mangledPropsInNameCache.has(tmpName)) { 212 continue; 213 } 214 215 mangledName = tmpName; 216 } 217 PropCollections.globalMangledTable.set(original, mangledName); 218 PropCollections.newlyOccupiedMangledProps.add(mangledName); 219 return mangledName; 220 } 221 222 function visitEnumInitializer(childNode: Node): void { 223 if (!isIdentifier(childNode)) { 224 forEachChild(childNode, visitEnumInitializer); 225 return; 226 } 227 228 if (NodeUtils.isPropertyNode(childNode)) { 229 return; 230 } 231 232 if (isTypeNode(childNode)) { 233 return; 234 } 235 236 PropCollections.reservedProperties.add(childNode.text); 237 } 238 239 // enum syntax has special scenarios 240 function collectReservedNames(node: Node): void { 241 // collect ViewPU class properties 242 if (isClassDeclaration(node) && isViewPUBasedClass(node)) { 243 getViewPUClassProperties(node, PropCollections.reservedProperties); 244 return; 245 } 246 247 // collect reserved name of enum 248 // example: enum H {A, B = A + 1}, enum H = {A, B= 1 + (A + 1)}; A is reserved 249 if (isEnumMember(node) && node.initializer) { 250 // collect enum properties 251 node.initializer.forEachChild(visitEnumInitializer); 252 return; 253 } 254 255 forEachChild(node, collectReservedNames); 256 } 257 258 function getViewPUClassProperties(classNode: ClassDeclaration | ClassExpression | StructDeclaration, propertySet: Set<string>): void { 259 if (!classNode || !classNode.members) { 260 return; 261 } 262 263 classNode.members.forEach((member) => { 264 const memberName: PropertyName = member.name; 265 if (!member || !memberName) { 266 return; 267 } 268 collectPropertyNamesAndStrings(memberName, propertySet); 269 }); 270 } 271 } 272 }; 273 274 export let transformerPlugin: TransformPlugin = { 275 'name': 'renamePropertiesPlugin', 276 'order': TransformerOrder.RENAME_PROPERTIES_TRANSFORMER, 277 'createTransformerFactory': createRenamePropertiesFactory 278 }; 279} 280 281export = secharmony; 282