Skip to content

Commit

Permalink
Support more constant types (#11)
Browse files Browse the repository at this point in the history
* Added support for byte, short, and char simple constants

* Added support for byte and short flag constants
  • Loading branch information
Daomephsta authored Apr 12, 2021
1 parent b894a47 commit 792c707
Show file tree
Hide file tree
Showing 9 changed files with 512 additions and 37 deletions.
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();
// Compare floating point numbers by double value, to support widening comparisons
if (isFloatingPoint(literalValue) && isFloatingPoint(literal))
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)
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

0 comments on commit 792c707

Please sign in to comment.