diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchModel.java index 1b4de807d9..491750fce3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchModel.java @@ -339,9 +339,10 @@ public class ScalarSearchModel extends AddressBasedTableModel { 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 { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/scalar/Scalar.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/scalar/Scalar.java index e48f4072e8..723748a460 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/scalar/Scalar.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/scalar/Scalar.java @@ -18,96 +18,76 @@ package ghidra.program.model.scalar; import java.math.BigInteger; /** - *

- * 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. - *

- * - *

- * 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. - *

+ * 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 { - 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 { /** * 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 { return new BigInteger(signum, data); } - /** - *

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); - } - /** *

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 { 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 { @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; - } - - /** - *

Adds the integer n to this. - * Computes (this = this + n). - * @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 { return bitLength; } - /** - *

The bit number n in this Scalar is set to zero. Computes - * (this = this & ~(1<<n)). Bits are numbered 0..bitlength()-1 - * with 0 being the least significant bit.

- * @param n the bit to clear in this scalar. - * - * @throws IndexOutOfBoundsException if n >= bitLength(). - */ - public Scalar clearBit(int n) { - if (n < 0 || n > bitLength - 1) { - throw new IllegalArgumentException(); - } - return new Scalar(bitLength, value & ~(1 << n)); - } - - /** - *

The bit number n in this Scalar is flipped. Computes - * (this = this ^ (1<<n)). Bits are numbered 0..bitlength()-1 - * with 0 being the least significant bit.

- * @param n the bit to flip. - * @throws IndexOutOfBoundsException if n >= bitLength(). - */ - public Scalar flipBit(int n) { - if (n < 0 || n > bitLength - 1) { - throw new IllegalArgumentException(); - } - return new Scalar(bitLength, value ^ (1 << n)); - } - - /** - *

The bit number n in this Scalar is set to one. Computes - * (this = this | (1<<n)). Bits are numbered 0..bitlength()-1 - * with 0 being the least significant bit.

- * - * @param n the bit to set. - * @throws IndexOutOfBoundsException if n >= bitLength(). - */ - public Scalar setBit(int n) { - if (n < 0 || n > bitLength - 1) { - throw new IllegalArgumentException(); - } - return new Scalar(bitLength, value | (1 << n)); - } - - /** - *

Sets this = this << n.

- * @param n the number of bits to shift left. - * @throws ArithmeticException if n < 0. - */ - public Scalar shiftLeft(int n) { - if (n < 0 || n > bitLength - 1) { - throw new IllegalArgumentException(); - } - return new Scalar(bitLength, value << n); - } - - /** - *

Sets this = this >> n using 0 as the fill bit.

- * @param n the number of bits to shift right. - * @throws ArithmeticException if n < 0. - */ - public Scalar shiftRight(int n) { - if (n < 0 || n > bitLength - 1) { - throw new IllegalArgumentException(); - } - return new Scalar(bitLength, value >>> n); - } - - /** - *

Sets this = this >> n replicating the sign bit.

- * @param n the number of bits to arithmetically shift. - * @throws ArithmeticException if n < 0. - */ - public Scalar shiftRightSign(int n) { - if (n < 0 || n > bitLength - 1) { - throw new IllegalArgumentException(); - } - return new Scalar(bitLength, value >> n); - } - - /** - *

Sets this = this - n.

- * @param n the value to subtract from this scalar to produce a new scalar. - */ - public Scalar subtract(long n) { - return add(-n); - } - /** *

Returns true if and only if the designated bit is set to one. * Computes ((this & (1<<n)) != 0). Bits are numbered @@ -339,7 +191,7 @@ public class Scalar implements Comparable { 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 { return new String(buf); } - /** - * @see java.lang.Object#toString() - */ @Override public String toString() { return toString(16, false, true, "0x", ""); diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/scalar/ScalarTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/scalar/ScalarTest.java index 72afc6a7db..10391fd204 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/scalar/ScalarTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/scalar/ScalarTest.java @@ -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"); + } + }