Skip to content

Commit

Permalink
fix: handle super case for invokespecial opcode (#1300)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Dec 2, 2021
1 parent 59ef569 commit 4cc00bd
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,9 @@ protected InsnNode decode(InsnData insn) throws DecodeException {
case INVOKE_VIRTUAL:
return invoke(insn, InvokeType.VIRTUAL, false);
case INVOKE_CUSTOM:
return invoke(insn, InvokeType.CUSTOM, false);
return invokeCustom(insn, false);
case INVOKE_SPECIAL:
return invokeSpecial(insn);

case INVOKE_DIRECT_RANGE:
return invoke(insn, InvokeType.DIRECT, true);
Expand All @@ -448,7 +450,7 @@ protected InsnNode decode(InsnData insn) throws DecodeException {
case INVOKE_VIRTUAL_RANGE:
return invoke(insn, InvokeType.VIRTUAL, true);
case INVOKE_CUSTOM_RANGE:
return invoke(insn, InvokeType.CUSTOM, true);
return invokeCustom(insn, true);

case NEW_INSTANCE:
ArgType clsType = ArgType.parse(insn.getIndexAsType());
Expand Down Expand Up @@ -565,10 +567,27 @@ private InsnNode cast(InsnData insn, ArgType from, ArgType to) {
return inode;
}

private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
if (type == InvokeType.CUSTOM) {
return InvokeCustomBuilder.build(method, insn, isRange);
private InsnNode invokeCustom(InsnData insn, boolean isRange) {
return InvokeCustomBuilder.build(method, insn, isRange);
}

private InsnNode invokeSpecial(InsnData insn) {
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
if (mthRef == null) {
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
}
MethodInfo mthInfo = MethodInfo.fromRef(root, mthRef);
// convert 'special' to 'direct/super' same as dx
InvokeType type;
if (mthInfo.isConstructor() || Objects.equals(mthInfo.getDeclClass(), method.getParentClass().getClassInfo())) {
type = InvokeType.DIRECT;
} else {
type = InvokeType.SUPER;
}
return new InvokeNode(mthInfo, insn, type, false);
}

private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
if (mthRef == null) {
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
Expand Down
8 changes: 5 additions & 3 deletions jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -74,7 +75,7 @@ public abstract class IntegrationTest extends TestUtils {
/**
* Set 'TEST_INPUT_PLUGIN' env variable to use 'java' or 'dx' input in tests
*/
private static final boolean USE_JAVA_INPUT = Utils.getOrElse(System.getenv("TEST_INPUT_PLUGIN"), DEFAULT_INPUT_PLUGIN).equals("java");
static final boolean USE_JAVA_INPUT = Utils.getOrElse(System.getenv("TEST_INPUT_PLUGIN"), DEFAULT_INPUT_PLUGIN).equals("java");

/**
* Run auto check method if defined:
Expand Down Expand Up @@ -523,11 +524,12 @@ protected void printOffsets() {
printOffsets = true;
}

protected void useJavaInput() {
public void useJavaInput() {
this.useJavaInput = true;
}

protected void useDexInput() {
public void useDexInput() {
Assumptions.assumeFalse(USE_JAVA_INPUT, "skip dex input tests");
this.useJavaInput = false;
}

Expand Down
2 changes: 2 additions & 0 deletions jadx-core/src/test/java/jadx/tests/api/SmaliTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.stream.Stream;

import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;

import jadx.api.JadxInternalAccess;
Expand All @@ -24,6 +25,7 @@ public abstract class SmaliTest extends IntegrationTest {

@BeforeEach
public void init() {
Assumptions.assumeFalse(USE_JAVA_INPUT, "skip smali test for java input tests");
super.init();
this.useDexInput();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package jadx.tests.api.extensions.inputs;

import java.util.function.Consumer;

import jadx.tests.api.IntegrationTest;

public enum InputPlugin implements Consumer<IntegrationTest> {
DEX {
@Override
public void accept(IntegrationTest test) {
test.useDexInput();
}
},
JAVA {
@Override
public void accept(IntegrationTest test) {
test.useJavaInput();
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package jadx.tests.api.extensions.inputs;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import org.junit.platform.commons.util.AnnotationUtils;
import org.junit.platform.commons.util.Preconditions;

import jadx.tests.api.IntegrationTest;

import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;

public class JadxInputPluginsExtension implements TestTemplateInvocationContextProvider {

@Override
public boolean supportsTestTemplate(ExtensionContext context) {
return isAnnotated(context.getTestMethod(), TestWithInputPlugins.class);
}

@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
Preconditions.condition(IntegrationTest.class.isAssignableFrom(context.getRequiredTestClass()),
"@TestWithInputPlugins should be used only in IntegrationTest subclasses");

Method testMethod = context.getRequiredTestMethod();
boolean testAnnAdded = AnnotationUtils.findAnnotation(testMethod, Test.class).isPresent();
Preconditions.condition(!testAnnAdded, "@Test annotation should be removed");

TestWithInputPlugins inputPluginAnn = AnnotationUtils.findAnnotation(testMethod, TestWithInputPlugins.class).get();
return Stream.of(inputPluginAnn.value())
.sorted()
.map(RunWithInputPlugin::new);
}

private static class RunWithInputPlugin implements TestTemplateInvocationContext {
private final InputPlugin plugin;

public RunWithInputPlugin(InputPlugin plugin) {
this.plugin = plugin;
}

@Override
public String getDisplayName(int invocationIndex) {
return plugin.name().toLowerCase(Locale.ROOT) + " input";
}

@Override
public List<Extension> getAdditionalExtensions() {
return Collections.singletonList(beforeTest());
}

private BeforeTestExecutionCallback beforeTest() {
return execContext -> plugin.accept((IntegrationTest) execContext.getRequiredTestInstance());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package jadx.tests.api.extensions.inputs;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;

@TestTemplate
@ExtendWith(JadxInputPluginsExtension.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface TestWithInputPlugins {

InputPlugin[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package jadx.tests.integration.invoke;

import jadx.tests.api.IntegrationTest;
import jadx.tests.api.extensions.inputs.InputPlugin;
import jadx.tests.api.extensions.inputs.TestWithInputPlugins;

import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;

public class TestSuperInvoke2 extends IntegrationTest {

public static class TestCls {
@Override
public String toString() {
return super.toString();
}

public void check() {
assertThat(new TestCls().toString()).containsOne("@");
}
}

@TestWithInputPlugins({ InputPlugin.DEX, InputPlugin.JAVA })
public void test() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("return super.toString();");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ public class JavaInsnsRegister {
register(arr, 0xb5, "putfield", 2, 2, Opcode.IPUT, InsnIndexType.FIELD_REF, s -> s.idx(s.u2()).pop(0).pop(1));

invoke(arr, 0xb6, "invokevirtual", 2, Opcode.INVOKE_VIRTUAL);
invoke(arr, 0xb7, "invokespecial", 2, Opcode.INVOKE_DIRECT);
invoke(arr, 0xb7, "invokespecial", 2, Opcode.INVOKE_SPECIAL);
invoke(arr, 0xb8, "invokestatic", 2, Opcode.INVOKE_STATIC);
invoke(arr, 0xb9, "invokeinterface", 4, Opcode.INVOKE_INTERFACE);
invoke(arr, 0xba, "invokedynamic", 4, Opcode.INVOKE_CUSTOM);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public enum Opcode {
INVOKE_SUPER_RANGE,
INVOKE_VIRTUAL,
INVOKE_VIRTUAL_RANGE,
INVOKE_SPECIAL,

IGET,
IPUT,
Expand Down

0 comments on commit 4cc00bd

Please sign in to comment.