Merge remote-tracking branch 'origin/GP-3166_dev747368_fix_Scalar_bittwiddling--SQUASHED'

This commit is contained in:
Ryan Kurtz
2023-03-15 13:29:27 -04:00
3 changed files with 150 additions and 214 deletions
@@ -339,9 +339,10 @@ public class ScalarSearchModel extends AddressBasedTableModel<ScalarRowObject> {
return (o1.isSigned() ? 1 : -1);
}
return o1.compareTo(o2);
return o1.isSigned()
? Long.compare(o1.getSignedValue(), o2.getSignedValue())
: Long.compareUnsigned(o1.getUnsignedValue(), o2.getUnsignedValue());
}
}
private abstract class AbstractScalarValueRenderer extends AbstractGColumnRenderer<Scalar> {
@@ -18,96 +18,76 @@ package ghidra.program.model.scalar;
import java.math.BigInteger;
/**
* <p>
* The Scalar defines a immutable fixed bit signed integer.
* Bit operations on a Scalar expect Scalar to act as a number in the
* two's complement format. Scalar was designed to be used as an
* offset (difference between two Addresses), an arithmetic operand,
* and also potentially for simulating registers.
* </p>
*
* <p>
* If an operation varies depending on whether the Scalar is
* treated as signed or unsigned, there are usally two version such as
* multiply and unsignedMultiply. Please note that this means that
* the Comparable interface treats the number as signed.
* </p>
* The Scalar defines a immutable integer stored in an arbitrary number of bits (0..64), along
* with a preferred signed-ness attribute.
*/
public class Scalar implements Comparable<Scalar> {
private static final long[] BITMASKS = new long[65];
static {
// populate the BITMASKS for each possible bit length
// up to 64
long value = 1;
for (int i = 1; i < 65; ++i) {
BITMASKS[i] = value;
value = (value << 1) + 1;
}
}
private byte bitLength;
private long value;
private boolean signed;
public class Scalar {
private final long value;
private final byte bitLength;
private final byte unusedBits; // complement of bitLength
private final boolean signed;
/**
* Constructor
* @param bitLength number of bits
* @param value value of the scalar
* @param signed true for a signed value, false for an unsigned value.
*/
public Scalar(int bitLength, long value, boolean signed) {
this.signed = signed;
if (!(bitLength == 0 && value == 0) && (bitLength < 1 || bitLength > 64)) {
throw new IllegalArgumentException("Bit length must be >= 1 and <= 64");
}
this.bitLength = (byte) bitLength;
this.value = value & BITMASKS[bitLength];
}
/**
* Returns true if scalar was created as a signed value
*/
public boolean isSigned() {
return signed;
}
/**
* Constructor a new signed scalar object.
* @param bitLength number of bits
* @param value value of the scalar
* Construct a new signed scalar object.
*
* @param bitLength number of bits, valid values are 1..64, or 0 if value is also 0
* @param value value of the scalar, any bits that are set above bitLength will be ignored
*/
public Scalar(int bitLength, long value) {
this(bitLength, value, true);
}
/**
* Get the value as signed.
* Construct a new scalar.
*
* @param bitLength number of bits, valid values are 1..64, or 0 if value is also 0
* @param value value of the scalar, any bits that are set above bitLength will be ignored
* @param signed true for a signed value, false for an unsigned value.
*/
public Scalar(int bitLength, long value, boolean signed) {
if (!(bitLength == 0 && value == 0) && (bitLength < 1 || bitLength > 64)) {
throw new IllegalArgumentException("Bit length must be >= 1 and <= 64");
}
this.signed = signed;
this.bitLength = (byte) bitLength;
this.unusedBits = (byte)(64 /*sizeof(long)*8*/ - bitLength);
this.value = (value << unusedBits) >>> unusedBits; // eliminate upper bits that are outside bitLength
}
/**
* Returns true if scalar was created as a signed value
*
* @return boolean true if this scalar was created as a signed value, false if was created as
* unsigned
*/
public boolean isSigned() {
return signed;
}
/**
* Get the value as a signed long, where the highest bit of the value, if set, will be
* extended to fill the remaining bits of a java long.
*
* @return signed value
*/
public long getSignedValue() {
if (value == 0) { // just in case the bitLength is 0
return 0;
}
if (testBit(bitLength - 1)) {
return (value | (~BITMASKS[bitLength]));
}
return (value << unusedBits) >> unusedBits; // if value has highbit set, sign extend it
}
/**
* Get the value as an unsigned long.
*
* @return unsigned value
*/
public long getUnsignedValue() {
return value;
}
/**
* Get the value as unsigned.
*/
public long getUnsignedValue() {
if (value == 0) { // just in case the bitLength is 0
return 0;
}
return (value & BITMASKS[bitLength]);
}
/**
* Returns the value as a signed value if it was created signed, otherwise the value is
* returned as an unsigned value
* Returns the value in its preferred signed-ness. See {@link #getSignedValue()} and
* {@link #getUnsignedValue()}.
*
* @return value, as either signed or unsigned, depending on how this instance was created
*/
public long getValue() {
return signed ? getSignedValue() : value;
@@ -115,6 +95,8 @@ public class Scalar implements Comparable<Scalar> {
/**
* Returns the BigInteger representation of the value.
*
* @return new BigInteger representation of the value
*/
public BigInteger getBigInteger() {
int signum = (signed && testBit(bitLength - 1)) ? -1 : 1;
@@ -134,18 +116,6 @@ public class Scalar implements Comparable<Scalar> {
return new BigInteger(signum, data);
}
/**
* <p>Creates a new Scalar of the same size as this scalar but with the
* given value
*
* @param newValue the Scalar value which will be used to initialize
* the new Scalar.
* @return the new Scalar.
*/
public Scalar newScalar(long newValue) {
return new Scalar(bitLength, newValue, signed);
}
/**
* <p>Returns a byte array representing this Scalar. The size of
* the byte array is the number of bytes required to hold the
@@ -164,9 +134,6 @@ public class Scalar implements Comparable<Scalar> {
return data;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
@@ -195,34 +162,7 @@ public class Scalar implements Comparable<Scalar> {
@Override
public int hashCode() {
return (int) (value ^ (value >>> 32));
}
/**
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Scalar other) {
if (bitLength == 64 || other.bitLength == 64) {
return getBigInteger().compareTo(other.getBigInteger());
}
long v = getValue() - other.getValue();
if (v > 0) {
return 1;
}
else if (v < 0) {
return -1;
}
return 0;
}
/**
* <p>Adds the integer n to <code>this</code>.
* Computes (<code>this = this + n</code>).
* @param n the value to add to this scalars value to produce a new scalar.
*/
public Scalar add(long n) {
return new Scalar(bitLength, (value + n) & BITMASKS[bitLength]);
return Long.hashCode(value);
}
/**
@@ -237,94 +177,6 @@ public class Scalar implements Comparable<Scalar> {
return bitLength;
}
/**
* <p>The bit number n in this Scalar is set to zero. Computes
* (this = this &amp; ~(1&lt;&lt;n)). Bits are numbered 0..bitlength()-1
* with 0 being the least significant bit.</p>
* @param n the bit to clear in this scalar.
*
* @throws IndexOutOfBoundsException if n &gt;= bitLength().
*/
public Scalar clearBit(int n) {
if (n < 0 || n > bitLength - 1) {
throw new IllegalArgumentException();
}
return new Scalar(bitLength, value & ~(1 << n));
}
/**
* <p>The bit number n in this Scalar is flipped. Computes
* (this = this ^ (1&lt;&lt;n)). Bits are numbered 0..bitlength()-1
* with 0 being the least significant bit.</p>
* @param n the bit to flip.
* @throws IndexOutOfBoundsException if n &gt;= bitLength().
*/
public Scalar flipBit(int n) {
if (n < 0 || n > bitLength - 1) {
throw new IllegalArgumentException();
}
return new Scalar(bitLength, value ^ (1 << n));
}
/**
* <p>The bit number n in this Scalar is set to one. Computes
* (this = this | (1&lt;&lt;n)). Bits are numbered 0..bitlength()-1
* with 0 being the least significant bit.</p>
*
* @param n the bit to set.
* @throws IndexOutOfBoundsException if n &gt;= bitLength().
*/
public Scalar setBit(int n) {
if (n < 0 || n > bitLength - 1) {
throw new IllegalArgumentException();
}
return new Scalar(bitLength, value | (1 << n));
}
/**
* <p>Sets <code>this = this &lt;&lt; n</code>.</p>
* @param n the number of bits to shift left.
* @throws ArithmeticException if n &lt; 0.
*/
public Scalar shiftLeft(int n) {
if (n < 0 || n > bitLength - 1) {
throw new IllegalArgumentException();
}
return new Scalar(bitLength, value << n);
}
/**
* <p>Sets <code>this = this &gt;&gt; n</code> using 0 as the fill bit.</p>
* @param n the number of bits to shift right.
* @throws ArithmeticException if n &lt; 0.
*/
public Scalar shiftRight(int n) {
if (n < 0 || n > bitLength - 1) {
throw new IllegalArgumentException();
}
return new Scalar(bitLength, value >>> n);
}
/**
* <p>Sets <code>this = this &gt;&gt; n</code> replicating the sign bit.</p>
* @param n the number of bits to arithmetically shift.
* @throws ArithmeticException if n &lt; 0.
*/
public Scalar shiftRightSign(int n) {
if (n < 0 || n > bitLength - 1) {
throw new IllegalArgumentException();
}
return new Scalar(bitLength, value >> n);
}
/**
* <p>Sets <code>this = this - n</code>.</p>
* @param n the value to subtract from this scalar to produce a new scalar.
*/
public Scalar subtract(long n) {
return add(-n);
}
/**
* <p>Returns true if and only if the designated bit is set to one.
* Computes ((this &amp; (1&lt;&lt;n)) != 0). Bits are numbered
@@ -339,7 +191,7 @@ public class Scalar implements Comparable<Scalar> {
if (n < 0 || n > bitLength - 1) {
throw new IllegalArgumentException();
}
return (value & (1 << n)) != 0;
return (value & (1L << n)) != 0;
}
/**
@@ -419,9 +271,6 @@ public class Scalar implements Comparable<Scalar> {
return new String(buf);
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return toString(16, false, true, "0x", "");
@@ -15,15 +15,30 @@
*/
package ghidra.program.model.scalar;
import static org.junit.Assert.*;
import java.math.BigInteger;
import org.junit.Assert;
import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.util.NumericUtilities;
public class ScalarTest extends AbstractGenericTest {
public ScalarTest() {
super();
@Test
public void testScalar0bits() {
// test special allowance for 0-bitlength scalars that have a 0 value
assertEquals(0, new Scalar(0, 0).getUnsignedValue());
assertEquals(0, new Scalar(0, 0).getSignedValue());
}
@Test(expected = IllegalArgumentException.class)
public void testScalar0bitsFailure() {
// test special allowance for 0-bitlength scalars that don't have a 0 value
new Scalar(0, 1 /* any non-zero intial value */);
fail("Scalar ctor should fail when asked to create a 0 bitLength scalar that isn't 0 value");
}
@Test
@@ -137,4 +152,75 @@ public class ScalarTest extends AbstractGenericTest {
Assert.assertEquals(Byte.MAX_VALUE, s.getUnsignedValue());
}
@Test
public void testIgnoreExtraBits() {
// tests that extra bits in the long initial value are ignored when creating value
assertEquals(0x22, new Scalar(8, 0x1122).getValue());
assertEquals(0x1122, new Scalar(16, 0x1122).getValue());
}
@Test
public void testLeftShiftByMoreThan32() {
// tests that the impl doesn't mess up when dealing with masks / bits that are larger
// than what can be held in a 32bit int. (eg. 1 << 33 is an error, needs to be 1L << 33)
assertEquals(55, new Scalar(32, 55).getSignedValue());
assertEquals(55, new Scalar(33, 55).getSignedValue()); // would fail if shift-by-32 error
assertEquals(0x8000, new Scalar(32, 0x8000).getSignedValue());
assertEquals(0x8000, new Scalar(48, 0x8000).getSignedValue()); // would fail if shift-by-32 error
assertEquals(0x40000000L, new Scalar(32, 0x40000000L).getSignedValue());
assertEquals(0x40000000L, new Scalar(63, 0x40000000L).getSignedValue()); // would fail
assertFalse(new Scalar(64, 0x1_0000_0000L).testBit(31));
assertTrue(new Scalar(64, 0x1_0000_0000L).testBit(32));
assertFalse(new Scalar(64, 0x1_0000_0000L).testBit(33));
}
@Test
public void testGetBigInt() {
assertEquals(BigInteger.valueOf(-1), new Scalar(8, 0xffL).getBigInteger());
assertEquals(BigInteger.valueOf(-1), new Scalar(32, 0xffffffffL).getBigInteger());
assertEquals(BigInteger.valueOf(-1), new Scalar(64, -1L).getBigInteger());
assertEquals(BigInteger.valueOf(Byte.MIN_VALUE), new Scalar(8, 0x80L).getBigInteger());
assertEquals(BigInteger.valueOf(Short.MIN_VALUE), new Scalar(16, 0x8000L).getBigInteger());
assertEquals(BigInteger.valueOf(Integer.MIN_VALUE),
new Scalar(32, 0x80000000L).getBigInteger());
assertEquals(BigInteger.valueOf(Long.MIN_VALUE),
new Scalar(64, Long.MIN_VALUE).getBigInteger());
assertEquals(BigInteger.valueOf(0x80L), new Scalar(9, 0x80L).getBigInteger());
assertEquals(BigInteger.valueOf(0x8000L), new Scalar(17, 0x8000L).getBigInteger());
assertEquals(BigInteger.valueOf(0x8000_0000L),
new Scalar(33, 0x8000_0000L).getBigInteger());
assertEquals(BigInteger.valueOf(0x1_8000_0000L),
new Scalar(34, 0x1_8000_0000L).getBigInteger());
}
@Test
public void testTestBit() {
Scalar scalar = new Scalar(64, -1, true);
for (int bitnum = 0; bitnum < 64; bitnum++) {
assertTrue(scalar.testBit(bitnum));
}
// test top half of int64 are 0's, bottom half are 1's
scalar = new Scalar(64, NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG);
for (int bitnum = 0; bitnum < 32; bitnum++) {
assertTrue(scalar.testBit(bitnum));
}
for (int bitnum = 32; bitnum < 64; bitnum++) {
assertFalse(scalar.testBit(bitnum));
}
}
@Test(expected = IllegalArgumentException.class)
public void testScalar0bitsTestBit() {
Scalar scalar = new Scalar(0, 0);
scalar.testBit(0);
fail("Scalar testBit(0) should fail");
}
}