All Downloads are FREE. Search and download functionalities are using the official Maven repository.

proguard.classfile.kotlin.reflect.util.KotlinCallableReferenceInitializer Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.7
Show newest version
/*
 * ProGuardCORE -- library to process Java bytecode.
 *
 * Copyright (c) 2002-2021 Guardsquare NV
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package proguard.classfile.kotlin.reflect.util;

import proguard.classfile.*;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
import proguard.classfile.constant.ClassConstant;
import proguard.classfile.constant.Constant;
import proguard.classfile.constant.StringConstant;
import proguard.classfile.constant.visitor.ConstantVisitor;
import proguard.classfile.editor.InstructionSequenceBuilder;
import proguard.classfile.instruction.Instruction;
import proguard.classfile.instruction.visitor.AllInstructionVisitor;
import proguard.classfile.instruction.visitor.InstructionConstantVisitor;
import proguard.classfile.instruction.visitor.InstructionVisitor;
import proguard.classfile.kotlin.*;
import proguard.classfile.kotlin.reflect.*;
import proguard.classfile.kotlin.visitor.*;
import proguard.classfile.kotlin.visitor.filter.KotlinPropertyFilter;
import proguard.classfile.util.InstructionSequenceMatcher;
import proguard.classfile.util.MemberFinder;
import proguard.classfile.visitor.ClassVisitor;
import proguard.classfile.visitor.MemberNameFilter;
import proguard.classfile.visitor.MemberVisitor;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import static proguard.classfile.ClassConstants.METHOD_NAME_INIT;
import static proguard.classfile.kotlin.KotlinConstants.REFLECTION;
import static proguard.classfile.util.InstructionSequenceMatcher.*;

/**
 * Initialize callable reference class information, by visiting synthetic classes that implement (Function|Property|LocalVariable)Reference,
 * then finding Function/Property that they refer to and use this information to initialize a {@link CallableReferenceInfo}
 * inside the synthetic class.
 *
 * FunctionReferences are lambda synthetic classes
 * PropertyReferences are regular synthetic classes
 * LocalVariableReferences extend PropertyReferences
 *
 * @author James Hamilton
 */
public class KotlinCallableReferenceInitializer
implements   KotlinMetadataVisitor
{
    private static final MemberFinder memberFinder = new MemberFinder();

    private final ClassPool programClassPool;
    private final ClassPool libraryClassPool;


    public KotlinCallableReferenceInitializer(ClassPool programClassPool, ClassPool libraryClassPool)
    {
        this.programClassPool = programClassPool;
        this.libraryClassPool = libraryClassPool;
    }

    @Override
    public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {}

    @Override
    public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata syntheticClassKindMetadata)
    {
        if (clazz.extendsOrImplements(REFLECTION.CALLABLE_REFERENCE_CLASS_NAME))
        {
            Consumer loader = result -> {
                 // First try to initialize a Kotlin reference (requires the owner Kotlin metadata)
                // otherwise try to initialize a Java reference.

                if (clazz.extendsOrImplements(REFLECTION.FUNCTION_REFERENCE_CLASS_NAME))
                {
                    int descriptorStart = result.callableSignature.indexOf('(');
                    Method method = descriptorStart == -1 ?
                                        null :
                                        memberFinder.findMethod(result.callableOwnerClass,
                                                                result.callableName,
                                                                result.callableSignature.substring(descriptorStart));

                    if (method != null)
                    {
                        Clazz methodReferencedClass = memberFinder.correspondingClass();

                        if (result.callableOwnerMetadata != null)
                        {
                            method.accept(methodReferencedClass,
                                new MethodToKotlinFunctionVisitor(
                                new FunctionReferenceInfoInitializer(result.callableOwnerClass,
                                                                     result.callableOwnerMetadata,
                                                                     syntheticClassKindMetadata)));
                        }

                        if (syntheticClassKindMetadata.callableReferenceInfo == null)
                        {
                            // The class didn't have any Kotlin metadata attached
                            // so let's instead initialize a Java reference.
                            syntheticClassKindMetadata.callableReferenceInfo =
                                new JavaMethodReferenceInfo(result.callableOwnerClass, methodReferencedClass, method);
                        }
                    }
                }
                else if (clazz.extendsOrImplements(REFLECTION.LOCALVAR_REFERENCE_CLASS_NAME))
                {
                    // Note: LocalVariableReference extends PropertyReference.
                    if (result.callableOwnerMetadata != null)
                    {
                        result.callableOwnerClass.kotlinMetadataAccept(
                            new LocalVariableReferenceInfoInitializer(
                                result.callableOwnerClass,
                                result.callableOwnerMetadata,
                                syntheticClassKindMetadata,
                                result.callableName,
                                result.callableSignature));
                    }
                }
                else if (clazz.extendsOrImplements(REFLECTION.PROPERTY_REFERENCE_CLASS_NAME))
                {
                    // Search the Kotlin hierarchy for the property, by name.

                    if (result.callableOwnerMetadata != null)
                    {
                        try
                        {
                            result.callableOwnerClass.hierarchyAccept(
                                true, true, false, false,
                                new ReferencedKotlinMetadataVisitor(
                                new AllPropertyVisitor(
                                new KotlinPropertyFilter(
                                    prop -> prop.name.equals(result.callableName),
                                    (_clazz, declarationContainerMetadata, propertyMetadata) -> {
                                        if (syntheticClassKindMetadata.callableReferenceInfo == null)
                                        {
                                            syntheticClassKindMetadata.callableReferenceInfo
                                                = new PropertyReferenceInfo(result.callableOwnerClass,
                                                                            result.callableOwnerMetadata,
                                                                            propertyMetadata);
                                            // Exit the search.
                                            throw new PropertyFoundException();
                                        }
                                    }))));
                        }
                        catch (PropertyFoundException ignored)
                        {
                            // Found.
                        }
                    }

                    if (syntheticClassKindMetadata.callableReferenceInfo == null)
                    {
                        // If we couldn't find any in the Kotlin metadata, we can search for the field by name/descriptor (assuming there is a backing field).

                        int descriptorEnd = result.callableSignature.lastIndexOf(')');
                        Field field = descriptorEnd == -1 ?
                                        null :
                                        memberFinder.findField(result.callableOwnerClass,
                                                               result.callableName,
                                                               result.callableSignature.substring(descriptorEnd + 1));

                        if (field != null)
                        {
                            syntheticClassKindMetadata.callableReferenceInfo =
                                new JavaFieldReferenceInfo(result.callableOwnerClass, memberFinder.correspondingClass(), field);
                        }
                    }
                }
            };

            clazz.accept(syntheticClassKindMetadata.mv[0] == 1 && syntheticClassKindMetadata.mv[1] <= 3 ?
                            new CallableReferenceInfoLoader1dot3(loader) :
                            new CallableReferenceInfoLoader1dot4(loader));
        }
    }

    private static class InfoLoaderResult
    {
        // the "owner" (for the getOwner() method) is not necessarily
        // where the function/property is declared (e.g. could be in a superclass)
        Clazz                              callableOwnerClass;
        KotlinDeclarationContainerMetadata callableOwnerMetadata;

        String callableName;

        // For functions it's the JVM signature
        // For properties it's the getter signature (even if a getter doesn't exist)
        // For local variables it's like 
        String callableSignature;
    }

    /**
     * Helper class to load the callable reference information from the existing
     * implementations in a class generated by kotlin 1.4+ compiler
     */
    private class CallableReferenceInfoLoader1dot4
    implements    ClassVisitor,
                  InstructionVisitor
    {
        private static final int OWNER_INDEX     = A;
        private static final int NAME_INDEX      = B;
        private static final int SIGNATURE_INDEX = C;
        private static final int FLAGS_INDEX     = D;

        private final List matchers = new ArrayList<>();
        private final Consumer       consumer;

        public CallableReferenceInfoLoader1dot4(Consumer consumer)
        {
            this.consumer = consumer;
            InstructionSequenceBuilder builder = new InstructionSequenceBuilder(programClassPool, libraryClassPool)
                .ldc_(OWNER_INDEX)
                .ldc_(NAME_INDEX)
                .ldc_(SIGNATURE_INDEX)
                .ldc_(FLAGS_INDEX)
                .invokespecial(X);

            this.matchers.add(new InstructionSequenceMatcher(builder.constants(), builder.instructions()));

            builder
                .ldc_(OWNER_INDEX)
                .ldc_(NAME_INDEX)
                .ldc_(SIGNATURE_INDEX)
                .iconst(I)
                .invokespecial(X);

            this.matchers.add(new InstructionSequenceMatcher(builder.constants(), builder.instructions()));
        }


        @Override
        public void visitAnyClass(Clazz clazz)
        {
            for (InstructionSequenceMatcher matcher : matchers)
            {
                matcher.reset();
            }

            clazz.methodsAccept(
                    new MemberNameFilter(METHOD_NAME_INIT,
                    new AllAttributeVisitor(
                    new AllInstructionVisitor(this))));
        }


        @Override
        public void visitAnyInstruction(Clazz         clazz,
                                        Method        method,
                                        CodeAttribute codeAttribute,
                                        int           offset,
                                        Instruction instruction)
        {
            for (InstructionSequenceMatcher matcher : matchers)
            {
                instruction.accept(clazz, method, codeAttribute, offset, matcher);

                if (matcher.isMatching())
                {
                    InfoLoaderResult result = new InfoLoaderResult();
                    clazz.constantPoolEntryAccept(matcher.matchedConstantIndex(OWNER_INDEX), new ConstantVisitor() {
                        @Override
                        public void visitAnyConstant(Clazz clazz, Constant constant) { }

                        @Override
                        public void visitClassConstant(Clazz clazz, ClassConstant classConstant) {
                            result.callableOwnerClass = classConstant.referencedClass;
                        }
                    });

                    clazz.constantPoolEntryAccept(matcher.matchedConstantIndex(NAME_INDEX), new ConstantVisitor() {
                        @Override
                        public void visitAnyConstant(Clazz clazz, Constant constant) { }

                        @Override
                        public void visitStringConstant(Clazz clazz, StringConstant stringConstant) {
                            result.callableName = stringConstant.getString(clazz);
                        }
                    });

                    clazz.constantPoolEntryAccept(matcher.matchedConstantIndex(SIGNATURE_INDEX), new ConstantVisitor() {
                        @Override
                        public void visitAnyConstant(Clazz clazz, Constant constant) { }

                        @Override
                        public void visitStringConstant(Clazz clazz, StringConstant stringConstant) {
                            result.callableSignature = stringConstant.getString(clazz);
                        }
                    });

                    if (result.callableOwnerClass != null) {
                        result.callableOwnerClass.kotlinMetadataAccept(new KotlinMetadataVisitor() {
                            @Override
                            public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) { }

                            @Override
                            public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata declarationContainer) {
                                result.callableOwnerMetadata = declarationContainer;
                            }
                        });

                        if (result.callableName      != null &&
                            result.callableSignature != null)
                        {
                            this.consumer.accept(result);
                        }
                    }

                    break;
                }
            }
        }
    }

    /**
     * Helper class to load the callable reference information from the existing
     * implementations in a class generated by Kotlin <= 1.3.
     */
    private static class CallableReferenceInfoLoader1dot3
    implements           ClassVisitor,
                         MemberVisitor,
                         ConstantVisitor,
                         KotlinMetadataVisitor
    {
        private final Consumer infoLoaderResultConsumer;
        private final InfoLoaderResult result = new InfoLoaderResult();
        private String currentMethod;

        public CallableReferenceInfoLoader1dot3(Consumer infoLoaderResultConsumer)
        {
            this.infoLoaderResultConsumer = infoLoaderResultConsumer;
        }

        // Implementations for ClassVisitor.

        @Override
        public void visitAnyClass(Clazz clazz)
        {
            clazz.methodsAccept(this);

            if (result.callableOwnerClass != null)
            {
                result.callableOwnerClass.kotlinMetadataAccept(this);

                if (result.callableName      != null &&
                    result.callableSignature != null)
                {
                    this.infoLoaderResultConsumer.accept(result);
                }
            }
        }

        // Implementations for MemberVisitor.

        @Override
        public void visitAnyMember(Clazz clazz, Member member) {}

        @Override
        public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
        {
            this.currentMethod = programMethod.getName(programClass) + programMethod.getDescriptor(programClass);
            programMethod.accept(programClass,
                                 new AllAttributeVisitor(
                                 new AllInstructionVisitor(
                                 new InstructionConstantVisitor(this))));
        }

        // Implementations for ConstantVisitor.

        @Override
        public void visitAnyConstant(Clazz clazz, Constant constant) {}

        @Override
        public void visitStringConstant(Clazz clazz, StringConstant stringConstant)
        {
            if (currentMethod.equals(REFLECTION.GETSIGNATURE_METHOD_NAME + REFLECTION.GETSIGNATURE_METHOD_DESC))
            {
                result.callableSignature = stringConstant.getString(clazz);
            }
            else if (currentMethod.equals(REFLECTION.GETNAME_METHOD_NAME + REFLECTION.GETNAME_METHOD_DESC))
            {
                result.callableName = stringConstant.getString(clazz);
            }
        }

        @Override
        public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
        {
            if (currentMethod.equals(REFLECTION.GETOWNER_METHOD_NAME + REFLECTION.GETOWNER_METHOD_DESC))
            {
                // There is a class ref (+ possibly a string for the module name but that's not required
                // because we know the module from the Kotlin class).
                result.callableOwnerClass = classConstant.referencedClass;
            }
        }

        // Implementations for KotlinMetadataVisitor.

        @Override
        public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {}

        @Override
        public void visitKotlinDeclarationContainerMetadata(Clazz                              clazz,
                                                            KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata)
        {
            result.callableOwnerMetadata = kotlinDeclarationContainerMetadata;
        }
    }

    // Helper class to initialize the FunctionCallableReferenceInfo

    private static class FunctionReferenceInfoInitializer
    implements           KotlinFunctionVisitor
    {
        private final Clazz                              ownerClass;
        private final KotlinDeclarationContainerMetadata ownerMetadata;
        private final KotlinSyntheticClassKindMetadata   syntheticClassKindMetadata;

        private FunctionReferenceInfoInitializer(Clazz                              ownerClass,
                                                 KotlinDeclarationContainerMetadata ownerMetadata,
                                                 KotlinSyntheticClassKindMetadata   syntheticClassKindMetadata)
        {
            this.ownerClass                 = ownerClass;
            this.ownerMetadata              = ownerMetadata;
            this.syntheticClassKindMetadata = syntheticClassKindMetadata;
        }

        @Override
        public void visitAnyFunction(Clazz                  clazz,
                                     KotlinMetadata         kotlinMetadata,
                                     KotlinFunctionMetadata kotlinFunctionMetadata) {}

        @Override
        public void visitFunction(Clazz                              clazz,
                                  KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata,
                                  KotlinFunctionMetadata             kotlinFunctionMetadata)
        {
            syntheticClassKindMetadata.callableReferenceInfo
                = new FunctionReferenceInfo(this.ownerClass, this.ownerMetadata, kotlinFunctionMetadata);
        }
    }

    // Helper class to initialize the LocalVariableReferenceInfo.

    public static class LocalVariableReferenceInfoInitializer
    implements          KotlinMetadataVisitor
    {

        private final Clazz                              ownerClass;
        private final KotlinDeclarationContainerMetadata owner;
        private final KotlinSyntheticClassKindMetadata   syntheticClassKindMetadata;
        private final String name;
        private final String signature;

        LocalVariableReferenceInfoInitializer(Clazz                              ownerClass,
                                              KotlinDeclarationContainerMetadata ownerMetadata,
                                              KotlinSyntheticClassKindMetadata   syntheticClassKindMetadata,
                                              String                             name,
                                              String                             signature)
        {
            this.ownerClass                 = ownerClass;
            this.owner                      = ownerMetadata;
            this.syntheticClassKindMetadata = syntheticClassKindMetadata;
            this.name                       = name;
            this.signature                  = signature;
        }

        // Implementations for KotlinMetadataVisitor.

        @Override
        public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {}

        @Override
        public void visitKotlinDeclarationContainerMetadata(Clazz                              clazz,
                                                            KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata)
        {
            syntheticClassKindMetadata.callableReferenceInfo =
                new LocalVariableReferenceInfo(this.ownerClass, this.owner, this.name, this.signature);
        }
    }

    // Helper class to exit early from hierarchy property search.
    private static class PropertyFoundException extends RuntimeException {}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy