fix: handle super case for invokespecial opcode (#1300)
This commit is contained in:
@@ -437,7 +437,9 @@ public class InsnDecoder {
|
||||
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);
|
||||
@@ -448,7 +450,7 @@ public class InsnDecoder {
|
||||
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());
|
||||
@@ -565,10 +567,27 @@ public class InsnDecoder {
|
||||
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);
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.concurrent.TimeoutException;
|
||||
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;
|
||||
@@ -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:
|
||||
@@ -523,11 +524,12 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.stream.Collectors;
|
||||
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;
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
+65
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
|
||||
@@ -97,6 +97,7 @@ public enum Opcode {
|
||||
INVOKE_SUPER_RANGE,
|
||||
INVOKE_VIRTUAL,
|
||||
INVOKE_VIRTUAL_RANGE,
|
||||
INVOKE_SPECIAL,
|
||||
|
||||
IGET,
|
||||
IPUT,
|
||||
|
||||
Reference in New Issue
Block a user