Skip to content

Commit

Permalink
Fixed stack size resolution for exchanged duplication instructions.
Browse files Browse the repository at this point in the history
  • Loading branch information
raphw committed Aug 1, 2016
1 parent 2e7db6b commit 46c8a94
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 24 deletions.
Expand Up @@ -50,6 +50,25 @@ public static StackSize of(Class<?> type) {
}
}

/**
* Represents a numeric size as a {@link StackSize}.
*
* @param size The size to represent. Must be {@code 0}, {@code 1} or {@code 2}.
* @return A stack size representation for the given value.
*/
public static StackSize of(int size) {
switch (size) {
case 0:
return ZERO;
case 1:
return SINGLE;
case 2:
return DOUBLE;
default:
throw new IllegalArgumentException("Unexpected stack size value: " + size);
}
}

/**
* Returns the sum of all operand stack sizes.
*
Expand Down
Expand Up @@ -66,27 +66,45 @@ public StackAwareMethodVisitor(MethodVisitor methodVisitor, MethodDescription me
/**
* Adjusts the current state of the operand stack.
*
* @param change The change of the current operation of the operand stack. Must not be larger than {@code 2}.
* @param delta The change of the current operation of the operand stack. Must not be larger than {@code 2}.
*/
private void adjustStack(int change) {
if (change == 1) {
current.add(StackSize.SINGLE);
} else if (change == 2) {
current.add(StackSize.DOUBLE);
} else if (change > 2) {
throw new IllegalStateException("Cannot push multiple values onto the operand stack: " + change);
private void adjustStack(int delta) {
adjustStack(delta, 0);
}

/**
* Adjusts the current state of the operand stack.
*
* @param delta The change of the current operation of the operand stack. Must not be larger than {@code 2}.
* @param offset The offset of the value within the operand stack. Must be bigger then {@code 0} and smaller than
* the current stack size. Only permitted if the supplied {@code delta} is positive.
*/
private void adjustStack(int delta, int offset) {
if (delta > 2) {
throw new IllegalStateException("Cannot push multiple values onto the operand stack: " + delta);
} else if (delta > 0) {
int position = current.size();
while (offset > 0) {
offset -= current.get(--position).getSize();
}
if (offset != 0) {
throw new IllegalStateException("Unexpected offset remainder: " + offset);
}
current.add(position, StackSize.of(delta));
} else if (offset != 0) {
throw new IllegalStateException("Cannot specify non-zero offset " + offset + " for non-incrementing value: " + delta);
} else {
while (change < 0) {
while (delta < 0) {
// The operand stack can legally underflow while traversing dead code.
if (current.isEmpty()) {
return;
}
change += current.remove(current.size() - 1).getSize();
delta += current.remove(current.size() - 1).getSize();
}
if (change == 1) {
if (delta == 1) {
current.add(StackSize.SINGLE);
} else if (change != 0) {
throw new IllegalStateException("Unexpected remainder on the operand stack: " + change);
} else if (delta != 0) {
throw new IllegalStateException("Unexpected remainder on the operand stack: " + delta);
}
}
}
Expand Down Expand Up @@ -169,6 +187,14 @@ public void visitInsn(int opcode) {
case Opcodes.ATHROW:
current.clear();
break;
case Opcodes.DUP_X1:
case Opcodes.DUP2_X1:
adjustStack(SIZE_CHANGE[opcode], SIZE_CHANGE[opcode] + 1);
break;
case Opcodes.DUP_X2:
case Opcodes.DUP2_X2:
adjustStack(SIZE_CHANGE[opcode], SIZE_CHANGE[opcode] + 2);
break;
default:
adjustStack(SIZE_CHANGE[opcode]);
}
Expand Down
@@ -0,0 +1,104 @@
package net.bytebuddy.asm;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.util.Arrays;
import java.util.Collection;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

@RunWith(Parameterized.class)
public class AdviceExchangedDuplicationTest {

private static final String FOO = "foo";

private static final int NUMERIC_VALUE = 42;

@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[][]{
{Opcodes.DUP_X1, int.class, int.class, NUMERIC_VALUE, NUMERIC_VALUE},
{Opcodes.DUP_X2, int.class, long.class, NUMERIC_VALUE, (long) NUMERIC_VALUE},
{Opcodes.DUP2_X1, long.class, int.class, (long) NUMERIC_VALUE, NUMERIC_VALUE},
{Opcodes.DUP2_X2, long.class, long.class, (long) NUMERIC_VALUE, (long) NUMERIC_VALUE}
}
);
}

private final int duplication;

private final Class<?> valueType, ignoredValueType;

private final Object value, ignoredValue;

public AdviceExchangedDuplicationTest(int duplication, Class<?> valueType, Class<?> ignoredValueType, Object value, Object ignoredValue) {
this.duplication = duplication;
this.valueType = valueType;
this.ignoredValueType = ignoredValueType;
this.value = value;
this.ignoredValue = ignoredValue;
}

@Test
public void testAdvice() throws Exception {
Class<?> type = new ByteBuddy()
.subclass(Object.class)
.defineMethod(FOO, valueType, Visibility.PUBLIC)
.intercept(new DuplicationImplementation())
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER_PERSISTENT)
.getLoaded();
Class<?> redefined = new ByteBuddy()
.redefine(type)
.visit(Advice.to(AdviceExchangedDuplicationTest.class).on(named(FOO)))
.make()
.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
assertThat(redefined.getDeclaredMethod(FOO).invoke(redefined.getDeclaredConstructor().newInstance()), is(value));
}

@Advice.OnMethodExit
@SuppressWarnings("unused")
private static void exit() {
/* empty */
}

private class DuplicationImplementation implements Implementation, ByteCodeAppender {

@Override
public ByteCodeAppender appender(Target implementationTarget) {
return this;
}

@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}

@Override
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
methodVisitor.visitLdcInsn(ignoredValue);
methodVisitor.visitLdcInsn(value);
methodVisitor.visitInsn(duplication);
methodVisitor.visitInsn(Type.getType(valueType).getSize() == 2 ? Opcodes.POP2 : Opcodes.POP);
methodVisitor.visitInsn(Type.getType(ignoredValueType).getSize() == 2 ? Opcodes.POP2 : Opcodes.POP);
methodVisitor.visitInsn(Type.getType(valueType).getOpcode(Opcodes.IRETURN));
return new Size(Type.getType(valueType).getSize() * 2 + Type.getType(ignoredValueType).getSize(), instrumentedMethod.getStackSize());
}
}
}
Expand Up @@ -15,31 +15,44 @@ public class StackSizeTest {

private final Class<?> type;

private final int size;

private final StackSize stackSize;

public StackSizeTest(Class<?> type, StackSize stackSize) {
public StackSizeTest(Class<?> type, int size, StackSize stackSize) {
this.type = type;
this.size = size;
this.stackSize = stackSize;
}

@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{void.class, StackSize.ZERO},
{boolean.class, StackSize.SINGLE},
{byte.class, StackSize.SINGLE},
{short.class, StackSize.SINGLE},
{int.class, StackSize.SINGLE},
{char.class, StackSize.SINGLE},
{float.class, StackSize.SINGLE},
{long.class, StackSize.DOUBLE},
{double.class, StackSize.DOUBLE},
{Object.class, StackSize.SINGLE},
{void.class, 0, StackSize.ZERO},
{boolean.class, 1, StackSize.SINGLE},
{byte.class, 1, StackSize.SINGLE},
{short.class, 1, StackSize.SINGLE},
{int.class, 1, StackSize.SINGLE},
{char.class, 1, StackSize.SINGLE},
{float.class, 1, StackSize.SINGLE},
{long.class, 2, StackSize.DOUBLE},
{double.class, 2, StackSize.DOUBLE},
{Object.class, 1, StackSize.SINGLE},
});
}

@Test
public void testStackSize() throws Exception {
assertThat(StackSize.of(type), is(stackSize));
}

@Test
public void testStackSizeValue() throws Exception {
assertThat(StackSize.of(type).getSize(), is(size));
}

@Test
public void testStackSizeResolution() throws Exception {
assertThat(StackSize.of(size), is(stackSize));
}
}

0 comments on commit 46c8a94

Please sign in to comment.