/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.tools.agent; import java.util.HashMap; import java.util.Map; import org.spongepowered.asm.logging.Level; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.service.ServiceNotAvailableError; import org.spongepowered.asm.util.Constants; /** * Class loader that is used to load fake mixin classes so that they can be * re-defined. */ class MixinAgentClassLoader extends ClassLoader { /** * Mapping of mixin mixin classes to their fake classes */ private Map, byte[]> mixins = new HashMap, byte[]>(); /** * Mapping that keep track of bytecode for classes that are targeted by * mixins */ private Map targets = new HashMap(); /** * Add a fake mixin class * * @param name Name of the fake class */ void addMixinClass(String name) { MixinAgentClassLoader.log(Level.DEBUG, "Mixin class {} added to class loader", name); try { byte[] bytes = this.materialise(name); Class clazz = this.defineClass(name, bytes, 0, bytes.length); // apparently the class needs to be instantiated at least once // to be including in list returned by allClasses() method in jdi api clazz.getDeclaredConstructor().newInstance(); this.mixins.put(clazz, bytes); } catch (Throwable e) { MixinAgentClassLoader.log(Level.ERROR, "Catching {}", e); } } /** * Registers the bytecode for a class targeted by a mixin * * @param name Name of the target clas * @param classNode ASM tree node of the target class */ void addTargetClass(String name, ClassNode classNode) { synchronized (this.targets) { if (this.targets.containsKey(name)) { return; } try { ClassWriter cw = new ClassWriter(0); classNode.accept(cw); this.targets.put(name, cw.toByteArray()); } catch (Exception ex) { MixinAgentClassLoader.log(Level.ERROR, "Error storing original class bytecode for {} in mixin hotswap agent. {}: {}", name, ex.getClass().getName(), ex.getMessage()); MixinAgentClassLoader.log(Level.DEBUG, ex.toString()); } } } /** * Gets the bytecode for a fake mixin class * * @param clazz Mixin class * @return Bytecode of the fake mixin class */ byte[] getFakeMixinBytecode(Class clazz) { return this.mixins.get(clazz); } /** * Gets the original bytecode for a target class * * @param name Name of the target class * @return Original bytecode */ byte[] getOriginalTargetBytecode(String name) { synchronized (this.targets) { return this.targets.get(name); } } /** * Generates the simplest possible class that is instantiable * * @param name Name of the generated class * @return Bytecode of the generated class */ private byte[] materialise(String name) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); cw.visit(MixinEnvironment.getCompatibilityLevel().getClassVersion(), Opcodes.ACC_PUBLIC, name.replace('.', '/'), null, Type.getInternalName(Object.class), null); // create init method MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, Constants.CTOR, "()V", null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), Constants.CTOR, "()V", false); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); cw.visitEnd(); return cw.toByteArray(); } /** * Wrapper for logger since we can't access the log abstraction in premain * * @param level the logging level * @param message the message to log * @param params parameters to the message */ public static void log(Level level, String message, Object... params) { try { MixinService.getService().getLogger("mixin.agent").log(level, message, params); } catch (ServiceNotAvailableError err) { System.err.printf("MixinAgent: %s: %s", level.name(), String.format(message, params)); } } }