Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support more constant types #11

Merged
merged 7 commits into from
Apr 12, 2021
28 changes: 27 additions & 1 deletion src/main/java/daomephsta/unpick/impl/AbstractInsnNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ public static Object getLiteralValue(AbstractInsnNode insn)

public static boolean isLiteral(AbstractInsnNode insn, Object literal)
{
return getLiteralValue(insn).equals(literal);
Object literalValue = getLiteralValue(insn);
// Chars are stored as ints, so conversion is necessary
if (literal instanceof Character && literalValue instanceof Integer)
{
Character charLiteral = (char) (int) literalValue;
return literal.equals(charLiteral);
}
// Compare integers by long value, to support widening comparisons
if (isIntegral(literalValue) && isIntegral(literal))
return ((Number) literalValue).longValue() == ((Number) literal).longValue();
Daomephsta marked this conversation as resolved.
Show resolved Hide resolved
// Compare floating point numbers by double value, to support widening comparisons
if (isFloatingPoint(literalValue) && isFloatingPoint(literal))
Daomephsta marked this conversation as resolved.
Show resolved Hide resolved
return ((Number) literalValue).doubleValue() == ((Number) literal).doubleValue();
return literalValue.equals(literal);
}

private static boolean isIntegral(Object literal)
{
return literal instanceof Byte ||
literal instanceof Short ||
literal instanceof Integer ||
literal instanceof Long;
}

private static boolean isFloatingPoint(Object literal)
{
return literal instanceof Float || literal instanceof Double;
}
}
18 changes: 16 additions & 2 deletions src/main/java/daomephsta/unpick/impl/InstructionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ else if (number instanceof Double)
return pushesDouble(number.doubleValue());
else if (number instanceof Float)
return pushesFloat(number.floatValue());
else //Shorts, bytes, and chars are all ints internally
else //Shorts and bytes are all ints internally
return pushesInt(number.intValue());
}
else if (value instanceof Character)
return pushesChar((char) value);
else if (value instanceof Boolean)
return pushesBoolean((boolean) value);
else if (value instanceof String)
Expand All @@ -43,9 +45,11 @@ else if (number instanceof Double)
pushesDouble(method, number.doubleValue());
else if (number instanceof Float)
pushesFloat(method, number.floatValue());
else //Shorts, bytes, and chars are all ints internally
else //Shorts and bytes are all ints internally
pushesInt(method, number.intValue());
}
else if (value instanceof Character)
pushesChar(method, (char) value);
else if (value instanceof Boolean)
pushesBoolean(method, (boolean) value);
else if (value instanceof String)
Expand All @@ -66,6 +70,16 @@ public static void pushesBoolean(MethodVisitor method, boolean bool)
method.visitInsn(bool ? ICONST_1 : ICONST_0);
}

public static AbstractInsnNode pushesChar(char c)
{
return pushesInt(c);
}

public static void pushesChar(MethodVisitor method, char c)
{
pushesInt(method, c);
}

private static final int[] I_OPCODES = {ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5};
public static AbstractInsnNode pushesInt(int i)
{
Expand Down
101 changes: 92 additions & 9 deletions src/main/java/daomephsta/unpick/impl/IntegerType.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,67 @@
package daomephsta.unpick.impl;

import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Collectors;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnNode;

public enum IntegerType
{
BYTE(Byte.class, byte.class, Type.BYTE_TYPE, Opcodes.IAND, Opcodes.IRETURN)
{
@Override
public AbstractInsnNode createLiteralPushInsn(long literal)
{ return InstructionFactory.pushesInt((byte) literal); }

@Override
public void appendLiteralPushInsn(MethodVisitor mv, long literal)
{ InstructionFactory.pushesInt(mv, (byte) literal); }

@Override
public Number box(long value)
{ return Byte.valueOf((byte) value); }

@Override
public Number binaryNegate(Number value)
{ return (byte) ~value.byteValue(); }

@Override
public long toUnsignedLong(Number value)
{ return Byte.toUnsignedLong(value.byteValue()); }

@Override
public Number parse(String valueString)
{ return Byte.parseByte(valueString); }
},
SHORT(Short.class, short.class, Type.SHORT_TYPE, Opcodes.IAND, Opcodes.IRETURN)
{
@Override
public AbstractInsnNode createLiteralPushInsn(long literal)
{ return InstructionFactory.pushesInt((short) literal); }

@Override
public void appendLiteralPushInsn(MethodVisitor mv, long literal)
{ InstructionFactory.pushesInt(mv, (short) literal); }

@Override
public Number box(long value)
{ return Short.valueOf((short) value); }

@Override
public Number binaryNegate(Number value)
{ return (short) ~value.shortValue(); }

@Override
public long toUnsignedLong(Number value)
{ return Short.toUnsignedLong(value.shortValue()); }

@Override
public Number parse(String valueString)
{ return Short.parseShort(valueString); }
},
INT(Integer.class, int.class, Type.INT_TYPE, Opcodes.IAND, Opcodes.IRETURN)
{
@Override
Expand All @@ -18,7 +74,7 @@ public void appendLiteralPushInsn(MethodVisitor mv, long literal)

@Override
public Number box(long value)
{ return new Integer((int) value); }
{ return Integer.valueOf((int) value); }

@Override
public Number binaryNegate(Number value)
Expand All @@ -27,6 +83,10 @@ public Number binaryNegate(Number value)
@Override
public long toUnsignedLong(Number value)
{ return Integer.toUnsignedLong(value.intValue()); }

@Override
public Number parse(String valueString)
{ return Integer.parseInt(valueString); }
},
LONG(Long.class, long.class, Type.LONG_TYPE, Opcodes.LAND, Opcodes.LRETURN)
{
Expand All @@ -40,7 +100,7 @@ public void appendLiteralPushInsn(MethodVisitor mv, long literal)

@Override
public Number box(long value)
{ return new Long(value); }
{ return Long.valueOf(value); }

@Override
public Number binaryNegate(Number value)
Expand All @@ -49,6 +109,10 @@ public Number binaryNegate(Number value)
@Override
public long toUnsignedLong(Number value)
{ return value.longValue(); }

@Override
public Number parse(String valueString)
{ return Long.parseLong(valueString); }
};

private final Class<? extends Number> boxed, primitive;
Expand All @@ -64,14 +128,31 @@ private IntegerType(Class<? extends Number> boxed, Class<? extends Number> primi
this.returnOpcode = returnOpcode;
}

public static IntegerType from(Class<?> clazz)
public static IntegerType from(Type type)
{
for (IntegerType intType : values())
{
if (intType.type == type)
return intType;
}
throw new IllegalArgumentException(type + " is not one of: " + describeValidTypes());
}

public static IntegerType from(Object literal)
{
for (IntegerType type : values())
{
if (literal.getClass() == type.getBoxClass() || literal.getClass() == type.getPrimitiveClass())
return type;
}
throw new IllegalArgumentException(literal + " is not one of: " + describeValidTypes());
}

private static String describeValidTypes()
{
if (clazz == Integer.class || clazz == int.class)
return INT;
else if (clazz == Long.class || clazz == long.class)
return LONG;
else
throw new IllegalArgumentException("Expected an integer or long, got " + clazz);
return Arrays.stream(values())
.map(t -> t.name().toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", "));
}

public AbstractInsnNode createAndInsn()
Expand Down Expand Up @@ -158,4 +239,6 @@ public Class<? extends Number> getPrimitiveClass()
public abstract Number binaryNegate(Number value);

public abstract long toUnsignedLong(Number value);

public abstract Number parse(String valueString);
}
68 changes: 66 additions & 2 deletions src/main/java/daomephsta/unpick/impl/LiteralType.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,70 @@
package daomephsta.unpick.impl;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnNode;

public enum LiteralType
{
BYTE(Byte.class, byte.class, Type.BYTE_TYPE, Opcodes.IRETURN)
{
@Override
public AbstractInsnNode createLiteralPushInsn(Object literal)
{ return InstructionFactory.pushesInt(((Number) literal).byteValue()); }

@Override
public void appendLiteralPushInsn(MethodVisitor mv, Object literal)
{ InstructionFactory.pushesInt(mv, ((Number) literal).byteValue()); }

@Override
public Object parse(String valueString)
{ return Byte.parseByte(valueString); }
},
SHORT(Short.class, short.class, Type.SHORT_TYPE, Opcodes.IRETURN)
{
@Override
public AbstractInsnNode createLiteralPushInsn(Object literal)
{ return InstructionFactory.pushesInt(((Number) literal).shortValue()); }

@Override
public void appendLiteralPushInsn(MethodVisitor mv, Object literal)
{ InstructionFactory.pushesInt(mv, ((Number) literal).shortValue()); }

@Override
public Object parse(String valueString)
{ return Short.parseShort(valueString); }
},
CHAR(Character.class, char.class, Type.CHAR_TYPE, Opcodes.IRETURN)
{
@Override
public AbstractInsnNode createLiteralPushInsn(Object literal)
{ return InstructionFactory.pushesChar((char) literal); }

@Override
public void appendLiteralPushInsn(MethodVisitor mv, Object literal)
{ InstructionFactory.pushesChar(mv, (char) literal); }

@Override
public Object parse(String valueString)
{
// Unicode escape parsing
Matcher m = UNICODE_ESCAPE.matcher(valueString);
if (m.matches())
return (char) Integer.parseInt(m.group(1), 16);
// Plain java char parsing
if (valueString.length() != 1)
Daomephsta marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalArgumentException(valueString + " is not a single character or valid unicode escape");
return valueString.charAt(0);
}
},
INT(Integer.class, int.class, Type.INT_TYPE, Opcodes.IRETURN)
{
@Override
Expand Down Expand Up @@ -94,6 +150,7 @@ public Object parse(String valueString)
{ return Type.getType(valueString); }
};

private static final Pattern UNICODE_ESCAPE = Pattern.compile("\\\\u+([0-9a-fA-F]{1,4})");
private static final Map<Class<?>, LiteralType> valuesByClass = new HashMap<>();
private static final Map<Type, LiteralType> valuesByType = new HashMap<>();
static
Expand Down Expand Up @@ -123,15 +180,22 @@ public static LiteralType from(Class<?> clazz)
if (valuesByClass.containsKey(clazz))
return valuesByClass.get(clazz);
else
throw new IllegalArgumentException(clazz + " is not an int, long, float, double, String, or type reference");
throw new IllegalArgumentException(clazz + " is not one of: " + describeValidTypes());
}

public static LiteralType from(Type type)
{
if (valuesByType.containsKey(type))
return valuesByType.get(type);
else
throw new IllegalArgumentException(type + " is not an int, float, long, double, String, or type reference");
throw new IllegalArgumentException(type + " is not one of: " + describeValidTypes());
}

private static String describeValidTypes()
{
return Arrays.stream(values())
.map(t -> t.name().toLowerCase(Locale.ROOT).replace('_', ' '))
.collect(Collectors.joining(", "));
}

public AbstractInsnNode createReturnInsn()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,10 @@ public ResolutionException(String message)
{
super(message);
}

public ResolutionException(String message, Throwable cause)
{
super(message, cause);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public boolean canReplace(Context context)
public void generateReplacements(Context context)
{
Number literalNum = (Number) AbstractInsnNodes.getLiteralValue(context.getArgSeed());
IntegerType integerType = IntegerType.from(literalNum.getClass());
IntegerType integerType = IntegerType.from(literalNum);

resolveAllConstants(context.getConstantResolver());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.objectweb.asm.Type;

import daomephsta.unpick.constantmappers.datadriven.parser.UnpickSyntaxException;
import daomephsta.unpick.impl.IntegerType;

/**
* Represents a flag field. The value and descriptor may be
Expand Down Expand Up @@ -42,11 +43,7 @@ protected Number parseValue(String valueString)
{
try
{
if (descriptor == Type.INT_TYPE)
return Integer.parseInt(valueString);
else if (descriptor == Type.LONG_TYPE)
return Long.parseLong(valueString);
else throw new UnpickSyntaxException("Cannot parse value " + valueString + " with descriptor " + descriptor);
return IntegerType.from(descriptor).parse(valueString);
}
catch (IllegalArgumentException e)
{
Expand All @@ -57,10 +54,16 @@ else if (descriptor == Type.LONG_TYPE)
@Override
protected void setValue(Object value) throws ResolutionException
{
if (value instanceof Long || value instanceof Integer)
try
{
// Will throw if value is not of an integral type
IntegerType.from(value);
this.value = value;
else
throw new ResolutionException(this + " is not of a valid flag type. Flags must be ints or longs.");
}
catch (IllegalArgumentException e)
{
throw new ResolutionException(value + " is not of a valid flag type", e);
}
}

@Override
Expand Down
Loading