proguard.obfuscate.kotlin.KotlinSyntheticToStringObfuscator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of proguard-base Show documentation
Show all versions of proguard-base Show documentation
ProGuard is a free shrinker, optimizer, obfuscator, and preverifier for Java bytecode
The newest version!
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2022 Guardsquare NV
*/
package proguard.obfuscate.kotlin;
import proguard.classfile.Clazz;
import proguard.classfile.Method;
import proguard.classfile.ProgramClass;
import proguard.classfile.attribute.Attribute;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.constant.Constant;
import proguard.classfile.constant.StringConstant;
import proguard.classfile.constant.Utf8Constant;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.editor.CodeAttributeEditor;
import proguard.classfile.editor.ConstantPoolEditor;
import proguard.classfile.instruction.ConstantInstruction;
import proguard.classfile.instruction.Instruction;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.kotlin.KotlinClassKindMetadata;
import proguard.classfile.kotlin.KotlinDeclarationContainerMetadata;
import proguard.classfile.kotlin.KotlinMetadata;
import proguard.classfile.kotlin.KotlinPropertyMetadata;
import proguard.classfile.kotlin.visitor.KotlinFunctionToMethodVisitor;
import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor;
import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor;
import proguard.classfile.kotlin.visitor.filter.KotlinFunctionFilter;
import proguard.obfuscate.ClassObfuscator;
import proguard.strip.KotlinAnnotationStripper;
import proguard.util.kotlin.asserter.KotlinMetadataAsserter;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import static proguard.classfile.ClassConstants.METHOD_NAME_TOSTRING;
import static proguard.classfile.ClassConstants.METHOD_NAME_TOSTRING_IMPL;
import static proguard.classfile.ClassConstants.METHOD_TYPE_TOSTRING;
import static proguard.classfile.ClassConstants.METHOD_TYPE_TOSTRING_IMPL;
import static proguard.classfile.util.ClassUtil.internalSimpleClassName;
import static proguard.obfuscate.ClassObfuscator.hasOriginalClassName;
import static proguard.obfuscate.ClassObfuscator.newClassName;
/**
* Some types of Kotlin classes (i.e. data, value, inline) simply hold data.
* The compiler uses this fact to automatically generate functions that a programmer would normally implement manually.
*
* One of these functions is the default toString() and its potential implementation counterpart toString-impl().
* The output of these functions is of the form "User(name=John, age=42)".
*
* This exposes unobfuscated classNames (in the example above "User") so we must update these className strings
* with their obfuscated versions.
*
* This class relies on the {@link KotlinPropertyNameObfuscator} storing the obfuscated
* name in the processingInfo field. And that the {@link ClassObfuscator} gives us the
* new class name (internally this also relies on the new class name being in the
* processingInfo field).
*
* One limitation of this class is that it requires that the Kotlin metadata is still attached to the
* classes that are processed. If the Kotlin metadata is stripped {@link KotlinAnnotationStripper} or
* removed by the {@link KotlinMetadataAsserter} the `toString` method's output cannot be updated with the
* obfuscated class name.
*/
public class KotlinSyntheticToStringObfuscator
implements KotlinMetadataVisitor
{
private static final Comparator REVERSE_LENGTH_STRING_ORDER =
Comparator
.comparingInt(String::length)
.reversed()
.thenComparing(Function.identity());
// Implementations for KotlinMetadataVisitor.
@Override
public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {}
@Override
public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata)
{
// Retrieve all the property names and their obfuscated versions.
// Start with the longest strings, in-case the smaller strings appear within longer ones.
Map nameMap = new TreeMap<>(REVERSE_LENGTH_STRING_ORDER);
kotlinClassKindMetadata.propertiesAccept(clazz, new PropertyNameCollector(nameMap));
// Add the original class name/obfuscated class name to the map as well
// but add "(" to class name to differentiate in-case the class is named the same as a property.
if (!hasOriginalClassName(clazz))
{
nameMap.put(internalSimpleClassName(kotlinClassKindMetadata.className) + "(",
internalSimpleClassName(newClassName(clazz)) + "(");
}
// We visit all the ldc instructions in the automatically declared toString function
// and use the nameMap to replace string values.
kotlinClassKindMetadata.functionsAccept(clazz,
new KotlinFunctionFilter(
fun ->
!fun.flags.isDeclaration &&
(fun.name.equals(METHOD_NAME_TOSTRING) ||
fun.name.equals(METHOD_NAME_TOSTRING_IMPL)) &&
(fun.jvmSignature.descriptor.toString().equals(METHOD_TYPE_TOSTRING) ||
fun.jvmSignature.descriptor.toString().equals(METHOD_TYPE_TOSTRING_IMPL)),
new KotlinFunctionToMethodVisitor(
new AllAttributeVisitor(
new MyObfuscatedToStringFixer(nameMap, new ConstantPoolEditor((ProgramClass)clazz))))));
}
private static final class MyObfuscatedToStringFixer
implements AttributeVisitor,
InstructionVisitor,
ConstantVisitor
{
private final Map originalToNewName;
private final ConstantPoolEditor constantPoolEditor;
private final CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
private String replacement = null;
private MyObfuscatedToStringFixer(Map originalToNewName, ConstantPoolEditor constantPoolEditor)
{
this.originalToNewName = originalToNewName;
this.constantPoolEditor = constantPoolEditor;
}
// Implementations for AttributeVisitor.
@Override
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
@Override
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
codeAttributeEditor.reset(codeAttribute.u4codeLength);
codeAttribute.instructionsAccept(clazz, method, this);
codeAttribute.accept(clazz, method, codeAttributeEditor);
}
// Implementations for InstructionVisitor.
@Override
public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {}
@Override
public void visitConstantInstruction(Clazz clazz,
Method method,
CodeAttribute codeAttribute,
int offset,
ConstantInstruction constantInstruction)
{
if (constantInstruction.opcode == Instruction.OP_LDC ||
constantInstruction.opcode == Instruction.OP_LDC_W)
{
replacement = null;
clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this);
if (replacement != null)
{
constantInstruction.constantIndex = constantPoolEditor.addStringConstant(replacement);
codeAttributeEditor.replaceInstruction(offset, constantInstruction);
}
}
}
// Implementations for ConstantVisitor.
@Override
public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
{
clazz.constantPoolEntryAccept(stringConstant.u2stringIndex, this);
}
@Override
public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant)
{
Map copy = new TreeMap<>(originalToNewName);
boolean foundAny = false;
replacement = utf8Constant.getString();
for (String originalName : copy.keySet())
{
if (replacement.contains(originalName))
{
replacement = replacement.replace(originalName, copy.get(originalName));
foundAny = true;
originalToNewName.remove(originalName);
}
}
if (!foundAny)
{
replacement = null;
}
}
@Override
public void visitAnyConstant(Clazz clazz, Constant constant) {}
}
// Collect the original name/new name map - assumes the old name is in the processingInfo.
private static final class PropertyNameCollector
implements KotlinPropertyVisitor
{
private final Map nameMap;
private PropertyNameCollector(Map nameMap) {
this.nameMap = nameMap;
}
// Implementations for KotlinPropertyVisitor.
@Override
public void visitAnyProperty(Clazz clazz,
KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata,
KotlinPropertyMetadata kotlinPropertyMetadata)
{
if (kotlinPropertyMetadata.getProcessingInfo() != null)
{
this.nameMap.put(kotlinPropertyMetadata.name + "=", kotlinPropertyMetadata.getProcessingInfo() + "=");
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy