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

w.asm.patcher.AsmPatcher Maven / Gradle / Ivy

/*
 *    Copyright 2023 Whilein
 *
 *    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 w.asm.patcher;

import lombok.*;
import lombok.experimental.FieldDefaults;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.*;
import w.agent.AgentInstrumentation;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
 * @author whilein
 */
@UtilityClass
public class AsmPatcher {

    public @NotNull AsmRedefine redefine(
            final @NotNull Class type
    ) {
        return new AsmRedefineImpl(
                type,
                new ArrayList<>()
        );
    }

    @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    private static final class Interceptor {
        Matcher matcher;
        Function mv;
    }

    @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    private static final class AsmRedefineMethodImpl implements AsmRedefineMethod {

        Matcher matcher;
        AsmRedefineImpl redefine;

        @Override
        public @NotNull AsmRedefine intercept(
                final @NotNull Function<@NotNull MethodVisitor, @NotNull MethodVisitor> mv
        ) {
            redefine.interceptors.add(new Interceptor(matcher, mv));

            return redefine;
        }
    }

    @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    private static final class AsmRedefineImpl implements AsmRedefine {

        Class type;
        List interceptors;

        @Override
        public @NotNull AsmRedefineMethod on(final @NotNull Matcher matcher) {
            return new AsmRedefineMethodImpl(matcher, this);
        }

        @Override
        public void apply(final @NotNull File file) {
            _apply(file);
        }

        @Override
        public void apply() {
            _apply(null);
        }

        @SneakyThrows
        private void _apply(final File file) {
            if (interceptors.isEmpty()) {
                return;
            }

            val internalType = Type.getInternalName(type);

            AgentInstrumentation.addTransformer(new ClassFileTransformer() {
                @Override
                public byte[] transform(
                        final ClassLoader loader,
                        final String className,
                        final Class classBeingRedefined,
                        final ProtectionDomain protectionDomain,
                        final byte[] buffer
                ) throws IllegalClassFormatException {
                    if (className.equals(internalType)) {
                        try {
                            val reader = new ClassReader(buffer);

                            val writer = new ClassWriter(
                                    reader,
                                    ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS
                            );

                            reader.accept(new ClassVisitor(Opcodes.ASM9, writer) {
                                @Override
                                public MethodVisitor visitMethod(
                                        final int access,
                                        final String name,
                                        final String descriptor,
                                        final String signature,
                                        final String[] exceptions
                                ) {
                                    MethodVisitor mv = super.visitMethod(
                                            access,
                                            name,
                                            descriptor,
                                            signature,
                                            exceptions
                                    );

                                    val methodInfo = ImmutableMethodInfo.from(name, descriptor);

                                    for (val interceptor : interceptors) {
                                        if (interceptor.matcher.matches(methodInfo)) {
                                            mv = interceptor.mv.apply(mv);
                                        }
                                    }

                                    return mv;
                                }
                            }, 0);

                            val bytes = writer.toByteArray();

                            if (file != null) {
                                val classFile = new File(file, className + ".class");
                                val packageDir = classFile.getParentFile();

                                if (!packageDir.exists() && !packageDir.mkdirs()) {
                                    throw new IOException("Cannot make dir for " + classFile);
                                }

                                try (val os = new FileOutputStream(classFile)) {
                                    os.write(bytes);
                                }
                            }

                            return bytes;
                        } catch (final Exception e) {
                            e.printStackTrace();
                        } finally {
                            AgentInstrumentation.removeTransformer(this);
                        }
                    }

                    return ClassFileTransformer.super.transform(loader, className,
                            classBeingRedefined, protectionDomain, buffer);
                }
            }, true);

            AgentInstrumentation.retransformClasses(type);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy