MixinInjectorTest

This commit is contained in:
Lucwousin
2019-10-29 03:11:15 +01:00
parent 633e6dc0e6
commit 8fabcfb0d5
3 changed files with 299 additions and 2 deletions

View File

@@ -29,9 +29,9 @@ dependencies {
implementation("org.projectlombok:lombok:1.18.10")
testImplementation("junit:junit:4.12")
testImplementation("com.openosrs:mixins:${project.version}")
// testRuntimeOnly("com.openosrs.rs:rs-client:${project.version}")
testCompileOnly("com.openosrs.rs:runescape-api:${project.version}")
// testRuntimeOnly("com.openosrs:mixins:${project.version}")
// testRuntimeOnly("net.runelite.rs:vanilla")
}

View File

@@ -1,5 +1,6 @@
package com.openosrs.injector.injectors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.openosrs.injector.InjectUtil;
@@ -62,7 +63,12 @@ public class MixinInjector extends AbstractInjector
public void inject() throws Injexception
{
final Map<Provider<ClassFile>, List<ClassFile>> mixinTargets = initTargets();
inject(mixinTargets);
}
@VisibleForTesting
void inject(Map<Provider<ClassFile>, List<ClassFile>> mixinTargets) throws Injexception
{
for (Map.Entry<Provider<ClassFile>, List<ClassFile>> entry : mixinTargets.entrySet())
{
injectFields(entry.getKey(), entry.getValue());
@@ -245,7 +251,7 @@ public class MixinInjector extends AbstractInjector
}
else
{
deobSourceMethod = InjectUtil.findMethodDeep(inject.toDeob(targetClass.getClassName()), copiedName, mixinMethod.getDescriptor().rsApiToRsClient());
deobSourceMethod = InjectUtil.findMethodDeep(inject.toDeob(targetClass.getName()), copiedName, mixinMethod.getDescriptor().rsApiToRsClient());
}
if (mixinMethod.isStatic() != deobSourceMethod.isStatic())

View File

@@ -0,0 +1,291 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.openosrs.injector.injectors;
import com.google.common.io.ByteStreams;
import com.openosrs.injector.TestInjection;
import com.openosrs.injector.rsapi.RSApi;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Provider;
import net.runelite.api.mixins.Copy;
import net.runelite.api.mixins.Replace;
import net.runelite.api.mixins.Shadow;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import static net.runelite.asm.Type.INT;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.signature.Signature;
import net.runelite.asm.visitors.ClassFileVisitor;
import net.runelite.deob.util.JarUtil;
import net.runelite.mapping.ObfuscatedName;
import net.runelite.mapping.ObfuscatedSignature;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
@ObfuscatedName("com/openosrs/injector/injectors/VanillaTarget")
class DeobTarget
{
@ObfuscatedName("ob_foo4")
private static int foo4;
@ObfuscatedName("ob_foo3")
@ObfuscatedSignature(
signature = "(I)V",
garbageValue = "123"
)
private void foo3()
{
// De-obfuscated foo3
System.out.println("foo3");
}
}
class VanillaTarget
{
private static int ob_foo4;
private void ob_foo3(int garbageValue)
{
// Obfuscated foo3
if (garbageValue != 123)
{
return;
}
System.out.println("foo3");
}
}
abstract class Source
{
@net.runelite.api.mixins.Inject
private static int foo;
@Shadow("foo4")
private static int foo4;
@net.runelite.api.mixins.Inject
private void foo2()
{
}
@Copy("foo3")
abstract void foo3();
@Replace("foo3")
private void rl$foo3()
{
System.out.println("replaced");
System.out.println(foo4);
foo3();
}
}
// Test shadowing the "foo" field injected by Source
abstract class Source2
{
@Shadow("foo")
private static int foo;
@net.runelite.api.mixins.Inject
private void foo5()
{
System.out.println(foo);
}
}
public class MixinInjectorTest
{
@Test
public void testInject() throws Exception
{
InputStream deobIn = getClass().getResourceAsStream("DeobTarget.class");
ClassFile deobTarget = JarUtil.loadClass(ByteStreams.toByteArray(deobIn));
ClassGroup deob = new ClassGroup();
deob.addClass(deobTarget);
InputStream vanillaIn = getClass().getResourceAsStream("VanillaTarget.class");
ClassFile vanillaTarget = JarUtil.loadClass(ByteStreams.toByteArray(vanillaIn));
ClassGroup vanilla = new ClassGroup();
vanilla.addClass(vanillaTarget);
Map<Provider<ClassFile>, List<ClassFile>> mixinClasses = new HashMap<>();
mixinClasses.put(() -> loadClass(Source.class), Collections.singletonList(vanillaTarget));
mixinClasses.put(() -> loadClass(Source2.class), Collections.singletonList(vanillaTarget));
TestInjection inject = new TestInjection(vanilla, deob, new ClassGroup(), new RSApi());
new MixinInjector(inject).inject(mixinClasses);
// Check if "foo" has been injected
Field foo = vanillaTarget.findField("foo");
assertNotNull(foo);
assertEquals(INT, foo.getType());
assertEquals(ACC_PUBLIC | ACC_STATIC, foo.getAccessFlags());
// Check if "foo2()V" has been injected
Method foo2 = vanillaTarget.findMethod("foo2");
assertNotNull(foo2);
assertEquals(new Signature("()V"), foo2.getDescriptor());
assertEquals(ACC_PUBLIC, foo2.getAccessFlags());
// Check if "ob_foo3(I)V" was copied
Method foo3 = vanillaTarget.findMethod("copy$foo3");
assertNotNull(foo3);
assertEquals(new Signature("(I)V"), foo3.getDescriptor());
assertEquals(ACC_PUBLIC, foo3.getAccessFlags());
// Check if "ob_foo3(I)V" was replaced
Method ob_foo3 = vanillaTarget.findMethod("ob_foo3");
assertNotNull(ob_foo3);
assertEquals(new Signature("(I)V"), ob_foo3.getDescriptor());
assertEquals(ob_foo3
.getCode()
.getInstructions()
.getInstructions()
.stream()
.filter(i -> i instanceof LDC && ((LDC) i).getConstant().equals("replaced"))
.count(), 1);
// Check that the "foo4" field access in the new code body was mapped correctly
assertEquals(ob_foo3
.getCode()
.getInstructions()
.getInstructions()
.stream()
.filter(i ->
{
if (!(i instanceof GetStatic))
{
return false;
}
net.runelite.asm.pool.Field field = ((GetStatic) i).getField();
if (!field.getClazz().getName().equals("com/openosrs/injector/injectors/VanillaTarget"))
{
return false;
}
if (!field.getName().equals("ob_foo4"))
{
return false;
}
return true;
})
.count(), 1);
// Check that the "foo3()" call in the new code body was mapped to the copy
assertEquals(ob_foo3
.getCode()
.getInstructions()
.getInstructions()
.stream()
.filter(i ->
{
if (!(i instanceof InvokeVirtual))
{
return false;
}
net.runelite.asm.pool.Method method = ((InvokeVirtual) i).getMethod();
if (!method.getClazz().getName().equals("com/openosrs/injector/injectors/VanillaTarget"))
{
return false;
}
if (!method.getName().equals("copy$foo3"))
{
return false;
}
return true;
})
.count(), 1);
// Check if "foo5()V" was injected
Method foo5 = vanillaTarget.findMethod("foo5");
assertNotNull(foo5);
assertEquals(new Signature("()V"), foo5.getDescriptor());
assertEquals(ACC_PUBLIC, foo5.getAccessFlags());
// Check that the shadow "foo" field access was mapped correctly
assertEquals(foo5
.getCode()
.getInstructions()
.getInstructions()
.stream()
.filter(i ->
{
if (!(i instanceof GetStatic))
{
return false;
}
net.runelite.asm.pool.Field field = ((GetStatic) i).getField();
if (!field.getClazz().getName().equals("com/openosrs/injector/injectors/VanillaTarget"))
{
return false;
}
if (!field.getName().equals("foo"))
{
return false;
}
return true;
})
.count(), 1);
}
private static ClassFile loadClass(Class<?> clazz)
{
try (InputStream is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class"))
{
ClassReader reader = new ClassReader(is);
ClassFileVisitor cv = new ClassFileVisitor();
reader.accept(cv, 0);
return cv.getClassFile();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}