Merge remote-tracking branch 'origin/GP-6627_dev747368_cleanup_numericutils_byteiterator--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-04-02 09:09:39 -04:00
9 changed files with 263 additions and 303 deletions
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -101,7 +101,7 @@ public abstract class BasicChecksumAlgorithm extends ChecksumAlgorithm {
if (monitor.isCancelled()) {
throw new CancelledException();
}
long b = bytes.next() & 0xFF;
long b = Byte.toUnsignedLong(bytes.nextByte());
long next = (size == SupportedByteSize.CHECKSUM8) ? b
: b << ((numBytes - 1) - i % numBytes) * 8;
if (xor) {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -87,7 +87,7 @@ public class CRC16CCITTChecksumAlgorithm extends ChecksumAlgorithm {
if (monitor.isCancelled()) {
throw new CancelledException();
}
byte b = it.next();
byte b = it.nextByte();
int value = 0;
if (b < 0) {
value = b + 256;
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -88,7 +88,7 @@ public class CRC16ChecksumAlgorithm extends ChecksumAlgorithm {
if (monitor.isCancelled())
throw new CancelledException();
byte b = it.next();
byte b = it.nextByte();
crc = crctab16[(int) ((crc ^ b) & 0xff)] ^ (crc >> 8);
}
if (onesComp) {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -53,7 +53,7 @@ public abstract class DigestChecksumAlgorithm extends ChecksumAlgorithm {
if (monitor.isCancelled()) {
throw new CancelledException();
}
digester.update(bytes.next());
digester.update(bytes.nextByte());
}
checksum = digester.digest();
}
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,33 +15,22 @@
*/
package ghidra.app.plugin.core.checksums;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.util.MemoryByteIterator;
import java.io.IOException;
import java.io.InputStream;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.util.MemoryByteIterator;
class MemoryInputStream extends InputStream {
MemoryByteIterator it;
MemoryInputStream(Memory mem, AddressSetView set) {
it = new MemoryByteIterator(mem, set);
}
/**
* @see java.io.InputStream#read()
*/
@Override
public int read() throws IOException {
try {
if (it.hasNext()) {
return it.next() & 0xff;
}
}
catch (MemoryAccessException e) {
StackTraceElement ste = e.getStackTrace()[0];
throw new IOException(e.toString());
}
return -1;
public int read() throws IOException {
return it.hasNext() ? Byte.toUnsignedInt(it.nextByte()) : -1;
}
}
@@ -221,7 +221,7 @@ public abstract class ByteCopier {
TaskMonitor monitor) {
Memory memory = currentProgram.getMemory();
ByteIterator bytes = new ByteIterator(addresses, memory);
MemoryByteIterator bytes = new MemoryByteIterator(memory, addresses);
return NumericUtilities.convertBytesToString(bytes, delimiter);
}
@@ -545,53 +545,6 @@ public abstract class ByteCopier {
}
}
/**
* An iterator of bytes from memory. This class exists because the {@link MemoryByteIterator}
* throws an exception from its next() method, which will not work for us.
*/
private static class ByteIterator implements Iterator<Byte> {
private MemoryByteIterator byteIterator;
private Byte next;
ByteIterator(AddressSetView addresses, Memory memory) {
byteIterator = new MemoryByteIterator(memory, addresses);
}
@Override
public boolean hasNext() {
if (next != null) {
return true;
}
if (!byteIterator.hasNext()) {
return false;
}
try {
next = byteIterator.next();
}
catch (MemoryAccessException e) {
Msg.error(this, "Unable to read next byte", e);
return false;
}
return true;
}
@Override
public Byte next() {
if (next == null) {
throw new NoSuchElementException();
}
Byte result = next;
next = null;
return result;
}
}
public static class ProgrammingByteStringTransferable implements Transferable {
private List<DataFlavor> flavorList;
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,7 +15,10 @@
*/
package ghidra.program.model.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
import org.junit.*;
@@ -25,17 +28,11 @@ import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
public class MemoryByteIteratorTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private Program program;
private AddressFactory af;
/**
* @param arg0
*/
public MemoryByteIteratorTest() {
super();
}
private Address addr(String a) {
return af.getAddress(a);
}
@@ -45,46 +42,113 @@ public class MemoryByteIteratorTest extends AbstractGhidraHeadedIntegrationTest
builder.createMemory("test1", "0x1001000", 0x6600);
builder.setBytes("0x1001000", "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
builder.setBytes("0x1001140", "DE AD BE EF");
builder.setBytes("0x1004ffe", "EE FF"); // these values are right before the buffer boundary
builder.setBytes("0x1005000", "AA BB CC DD"); // these values are right after the buffer boundary
return builder.getProgram();
}
@Before
public void setUp() throws Exception {
@Before
public void setUp() throws Exception {
env = new TestEnv();
program = buildProgram("notepad", ProgramBuilder._TOY);
af = program.getAddressFactory();
af = program.getAddressFactory();
}
@After
public void tearDown() {
@After
public void tearDown() {
env.release(program);
env.dispose();
}
@Test
public void testIterator() throws Exception {
@Test
public void testEmptyRange() {
MemoryByteIterator it = new MemoryByteIterator(program.getMemory(), new AddressSet());
long count =
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false).count();
assertEquals(0, count);
}
@Test
public void test1Byte() {
MemoryByteIterator it =
new MemoryByteIterator(program.getMemory(), new AddressSet(addr("0x1001000")));
long count =
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false).count();
assertEquals(1, count);
}
@Test
public void test2Byte() {
MemoryByteIterator it = new MemoryByteIterator(program.getMemory(),
new AddressSet(addr("0x1001000"), addr("0x1001001")));
long count =
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false).count();
assertEquals(2, count);
}
@Test
public void testIterator() throws Exception {
AddressSet set = new AddressSet(addr("0x1000000"), addr("0x100100f"));
set.addRange(addr("0x1001140"), addr("0x1001144"));
MemoryByteIterator it = new MemoryByteIterator(program.getMemory(), set);
int total = 0;
int n = 0;
while(it.hasNext()) {
int count = 0;
while (it.hasNext()) {
byte b = it.next();
n++;
count++;
total += b;
}
assertEquals(21, n);
assertEquals(21, count);
assertEquals(-80, total);
}
@Test
public void testIterator2() throws Exception {
@Test
public void testIterator2() throws Exception {
AddressSet set = new AddressSet(addr("0x1001000"), addr("0x10075ff"));
MemoryByteIterator it = new MemoryByteIterator(program.getMemory(), set);
int n = 0;
while(it.hasNext()) {
it.next();
n++;
long count =
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false).count();
assertEquals(0x6600, count);
}
@Test
public void testReadBytesNearBufferBoundary() {
AddressSet set = new AddressSet(addr("0x1001000"), addr("0x10075ff"));
MemoryByteIterator it = new MemoryByteIterator(program.getMemory(), set);
int index = 0;
int testcount = 0;
while (it.hasNext()) {
int b = Byte.toUnsignedInt(it.nextByte());
switch (index) {
//@formatter:off
case MemoryByteIterator.MAX_BUF_SIZE - 2: assertEquals(0xee, b); testcount++; break;
case MemoryByteIterator.MAX_BUF_SIZE - 1: assertEquals(0xff, b); testcount++; break;
case MemoryByteIterator.MAX_BUF_SIZE: assertEquals(0xaa, b); testcount++; break;
case MemoryByteIterator.MAX_BUF_SIZE + 1: assertEquals(0xbb, b); testcount++; break;
//@formatter:on
}
index++;
}
assertEquals(0x6600, n);
assertEquals(4, testcount);
}
@Test
public void testOOBAddrRange() {
// Test that invalid / unmapped addresses cause the iterator to signal end-of-iteration
AddressSet set = new AddressSet(addr("0x10075ff"), addr("0x1007700"));
MemoryByteIterator it = new MemoryByteIterator(program.getMemory(), set);
long count =
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false).count();
assertEquals(1, count);
}
@Test
public void testOOBAddrRange2() {
// Test that invalid / unmapped addresses cause the iterator to signal end-of-iteration
AddressSet set = new AddressSet(addr("0x1007600"), addr("0x1007700"));
MemoryByteIterator it = new MemoryByteIterator(program.getMemory(), set);
long count =
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false).count();
assertEquals(0, count);
}
}
@@ -18,15 +18,12 @@ package ghidra.util;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.lang3.StringUtils;
import util.CollectionUtils;
public final class NumericUtilities {
public static final BigInteger MAX_UNSIGNED_LONG = new BigInteger("ffffffffffffffff", 16);
public static final BigInteger MAX_SIGNED_LONG = new BigInteger("7fffffffffffffff", 16);
@@ -38,23 +35,9 @@ public final class NumericUtilities {
private final static String BIN_PREFIX = "0B";
private final static String OCT_PREFIX = "0";
private final static Set<Class<? extends Number>> INTEGER_TYPES = new HashSet<>();
static {
INTEGER_TYPES.add(Byte.class);
INTEGER_TYPES.add(Short.class);
INTEGER_TYPES.add(Integer.class);
INTEGER_TYPES.add(Long.class);
}
private static final Set<Class<? extends Number>> FLOATINGPOINT_TYPES = new HashSet<>();
static {
FLOATINGPOINT_TYPES.add(Float.class);
FLOATINGPOINT_TYPES.add(Double.class);
}
// @formatter:off
private static IntegerRadixRenderer SIGNED_INTEGER_RENDERER = new SignedIntegerRadixRenderer();
private static IntegerRadixRenderer UNSIGNED_INTEGER_RENDERER = new UnsignedIntegerRadixRenderer();
private static IntegerRadixRenderer DEFAULT_INTEGER_RENDERER = new DefaultIntegerRadixRenderer();
private final static Set<Class<? extends Number>> INTEGER_TYPES = Set.of(Byte.class, Short.class, Integer.class, Long.class);
private static final Set<Class<? extends Number>> FLOATINGPOINT_TYPES = Set.of(Float.class, Double.class);
// @formatter:on
private NumericUtilities() {
@@ -678,7 +661,7 @@ public final class NumericUtilities {
/**
* Provide renderings of <code>number</code> in different bases:
* <ul>
* <li><code>0</code> - renders <code>number</code> as an escaped character sequence</li>
* <li><code>0</code> - renders <code>number</code> as a big endian escaped character sequence</li>
* <li><code>2</code> - renders <code>number</code> as a <code>base-2</code> integer</li>
* <li><code>8</code> - renders <code>number</code> as a <code>base-8</code> integer</li>
* <li><code>10</code> - renders <code>number</code> as a <code>base-10</code> integer</li>
@@ -807,32 +790,16 @@ public final class NumericUtilities {
*/
public static String formatNumber(long number, int radix, SignednessFormatMode mode) {
if (radix == 0) {
byte[] bytes = new byte[8];
bytes[0] = (byte) (number >> 56);
bytes[1] = (byte) (number >> 48);
bytes[2] = (byte) (number >> 40);
bytes[3] = (byte) (number >> 32);
bytes[4] = (byte) (number >> 24);
bytes[5] = (byte) (number >> 16);
bytes[6] = (byte) (number >> 8);
bytes[7] = (byte) (number >> 0);
byte[] bytes = BigEndianDataConverter.INSTANCE.getBytes(number);
return StringUtilities.toQuotedString(bytes);
}
IntegerRadixRenderer renderer = null;
switch (mode) {
case SIGNED:
renderer = SIGNED_INTEGER_RENDERER;
break;
case UNSIGNED:
renderer = UNSIGNED_INTEGER_RENDERER;
break;
case DEFAULT:
renderer = DEFAULT_INTEGER_RENDERER;
break;
}
return renderer.toString(number, radix);
BiFunction<Long, Integer, String> renderer = switch (mode) {
case SIGNED -> NumericUtilities::signedIntegerRadixToString;
case UNSIGNED -> NumericUtilities::unsignedIntegerRadixToString;
case DEFAULT -> NumericUtilities::defaultIntegerRadixToString;
};
return renderer.apply(number, radix);
}
/**
@@ -888,20 +855,6 @@ public final class NumericUtilities {
return null;
}
/**
* Convert the given byte into a two character String, padding with a leading 0 if needed.
*
* @param b the byte
* @return the byte string
*/
public static String toString(byte b) {
String bs = Integer.toHexString(b & 0xff);
if (bs.length() == 1) {
return "0" + bs;
}
return bs;
}
/**
* Convert a byte array into a hexadecimal string.
*
@@ -909,21 +862,18 @@ public final class NumericUtilities {
* @return hex string representation
*/
public static String convertBytesToString(byte[] bytes) {
return convertBytesToString(bytes, null);
return convertBytesToString(bytes, 0, bytes.length, null);
}
/**
* Convert a byte array into a hexadecimal string.
*
* @param bytes byte array
* @param delimeter the text between byte strings
* @param delimiter the text between byte strings, {@code null} ok
* @return hex string representation
*/
public static String convertBytesToString(byte[] bytes, String delimeter) {
if (bytes == null) {
return null;
}
return convertBytesToString(bytes, 0, bytes.length, delimeter);
public static String convertBytesToString(byte[] bytes, String delimiter) {
return convertBytesToString(bytes, 0, bytes.length, delimiter);
}
/**
@@ -932,49 +882,77 @@ public final class NumericUtilities {
* @param bytes byte array
* @param start start index
* @param len number of bytes to convert
* @param delimeter the text between byte strings
* @param delimiter the text between byte strings, {@code null} ok
* @return hex string representation
*/
public static String convertBytesToString(byte[] bytes, int start, int len, String delimeter) {
public static String convertBytesToString(byte[] bytes, int start, int len, String delimiter) {
if (bytes == null) {
return null;
}
delimiter = Objects.requireNonNullElse(delimiter, "");
Iterator<Byte> iterator = IteratorUtils.arrayIterator(bytes, start, start + len);
return convertBytesToString(iterator, delimeter);
int end = start + len;
Objects.checkFromToIndex(start, end, bytes.length);
StringBuilder sb = new StringBuilder(len * (2 + delimiter.length()));
for (int i = start; i < end; i++) {
if (!sb.isEmpty()) {
sb.append(delimiter);
}
String s = Integer.toHexString(Byte.toUnsignedInt(bytes[i]));
if (s.length() < 2) {
sb.append('0');
}
sb.append(s);
}
return sb.toString();
}
/**
* Convert a bytes into a hexadecimal string.
* Convert bytes into a hexadecimal string.
*
* @param bytes an iterator of bytes
* @param delimiter the text between byte strings; null is allowed
* @param delimiter the text between byte strings, {@code null} ok
* @return hex string representation
*/
public static String convertBytesToString(Iterator<Byte> bytes, String delimiter) {
delimiter = Objects.requireNonNullElse(delimiter, "");
return convertBytesToString(() -> bytes, delimiter);
StringBuilder sb = new StringBuilder();
while (bytes.hasNext()) {
Byte b = bytes.next();
if (!sb.isEmpty()) {
sb.append(delimiter);
}
String s = Integer.toHexString(Byte.toUnsignedInt(b));
if (s.length() < 2) {
sb.append('0');
}
sb.append(s);
}
return sb.toString();
}
/**
* Convert a bytes into a hexadecimal string.
* Convert bytes into a hexadecimal string.
*
* @param bytes an iterable of bytes
* @param delimiter the text between byte strings; null is allowed
* @param delimiter the text between byte strings, {@code null} ok
* @return hex string representation
*/
public static String convertBytesToString(Iterable<Byte> bytes, String delimiter) {
return convertBytesToString(CollectionUtils.asStream(bytes), delimiter);
return convertBytesToString(bytes.iterator(), delimiter);
}
/**
* Convert a bytes into a hexadecimal string.
* Convert bytes into a hexadecimal string.
*
* @param bytes an stream of bytes
* @param delimiter the text between byte strings; null is allowed
* @param delimiter the text between byte strings, {@code null} ok
* @return hex string representation
*/
public static String convertBytesToString(Stream<Byte> bytes, String delimiter) {
delimiter = (delimiter == null) ? "" : delimiter;
return bytes.map(NumericUtilities::toString).collect(Collectors.joining(delimiter));
return convertBytesToString(bytes.iterator(), delimiter);
}
/**
@@ -1020,94 +998,53 @@ public final class NumericUtilities {
}
/**
* Provides the protocol for rendering integer-type numbers in different signed-ness modes.
* Renders provided numbers as signed values.
*
* @param number value
* @param radix 2,8,10,16
* @return formatted string
*/
private static interface IntegerRadixRenderer {
/**
* Format the given number in the provided radix base.
*
* @param number the number to render
* @param radix the base in which to render
* @return a string representing the provided number in the given base
*/
public String toString(long number, int radix);
private static String signedIntegerRadixToString(long number, int radix) {
return switch (radix) {
case 2 -> Long.toString(number, radix) + "b";
case 8 -> Long.toString(number, radix) + "o";
case 10 -> Long.toString(number, radix);
case 16 -> Long.toString(number, radix) + "h";
default -> throw new IllegalArgumentException("Unsupported radix " + radix);
};
}
/**
* Renders provided numbers as signed values
* Renders provided numbers as unsigned values.
*
* @param number value
* @param radix 2,8,10,16
* @return formatted string
*/
private final static class SignedIntegerRadixRenderer implements IntegerRadixRenderer {
/**
* {@inheritDoc}
* <p>
* All values are rendered in their <i>signed</i> form
**/
@Override
public String toString(long number, int radix) {
switch (radix) {
case 2:
return Long.toString(number, radix) + "b";
case 8:
return Long.toString(number, radix) + "o";
case 10:
return Long.toString(number, radix);
case 16:
return Long.toString(number, radix) + "h";
}
throw new IllegalArgumentException("Unsupported radix");
}
}
/**
* Renders provided numbers as unsigned values
*/
private final static class UnsignedIntegerRadixRenderer implements IntegerRadixRenderer {
/**
* {@inheritDoc}
* <p>
* All values are rendered in their <i>unsigned</i> form
**/
@Override
public String toString(long number, int radix) {
switch (radix) {
case 2:
return Long.toBinaryString(number) + "b";
case 8:
return Long.toOctalString(number) + "o";
case 10:
return Long.toUnsignedString(number);
case 16:
return Long.toHexString(number) + "h";
}
throw new IllegalArgumentException("Unsupported radix");
}
private static String unsignedIntegerRadixToString(long number, int radix) {
return switch (radix) {
case 2 -> Long.toBinaryString(number) + "b";
case 8 -> Long.toOctalString(number) + "o";
case 10 -> Long.toUnsignedString(number);
case 16 -> Long.toHexString(number) + "h";
default -> throw new IllegalArgumentException("Unsupported radix " + radix);
};
}
/**
* Renders provided numbers in a more human-friendly manner
*
* @param number value
* @param radix 2,8,10,16
* @return formatted string
*/
private final static class DefaultIntegerRadixRenderer implements IntegerRadixRenderer {
/**
* {@inheritDoc}
* <p>
* Values to be rendered in binary, octal, or hexadecimal bases are rendered as unsigned,
* numbers rendered in decimal are rendered as signed.
*/
@Override
public String toString(long number, int radix) {
switch (radix) {
case 2:
return new UnsignedIntegerRadixRenderer().toString(number, radix);
case 8:
return new UnsignedIntegerRadixRenderer().toString(number, radix);
case 10:
return new SignedIntegerRadixRenderer().toString(number, radix);
case 16:
return new UnsignedIntegerRadixRenderer().toString(number, radix);
}
throw new IllegalArgumentException("Unsupported radix");
}
private static String defaultIntegerRadixToString(long number, int radix) {
return switch (radix) {
case 2 -> unsignedIntegerRadixToString(number, radix);
case 8 -> unsignedIntegerRadixToString(number, radix);
case 10 -> signedIntegerRadixToString(number, radix);
case 16 -> unsignedIntegerRadixToString(number, radix);
default -> throw new IllegalArgumentException("Unsupported radix " + radix);
};
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -15,6 +15,9 @@
*/
package ghidra.program.model.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
@@ -22,13 +25,14 @@ import ghidra.program.model.mem.MemoryAccessException;
/**
* Class to iterate over the bytes in memory for an address set.
*/
public class MemoryByteIterator {
private static final int BUF_SIZE = 16 * 1024;
public class MemoryByteIterator implements Iterator<Byte> {
public static final int MAX_BUF_SIZE = 16 * 1024;
private Memory mem;
private AddressSet addrSet;
byte[] buf;
int count = 0;
int pos;
private byte[] buf;
private int bufSize;
private int pos;
/**
* Construct a memoryIterator
@@ -37,39 +41,53 @@ public class MemoryByteIterator {
*/
public MemoryByteIterator(Memory mem, AddressSetView set) {
this.mem = mem;
addrSet = set.intersect(mem);
buf = new byte[BUF_SIZE];
this.addrSet = set.intersect(mem);
this.buf = new byte[(int) Math.min(MAX_BUF_SIZE, set.getNumAddresses())];
}
/**
* Returns true if there are more bytes to iterate over
*/
@Override
public boolean hasNext() {
return count != 0 || !addrSet.isEmpty();
ensureBuffer();
return pos < bufSize;
}
@Override
public Byte next() {
return nextByte();
}
/**
* Returns the next byte.
* @throws MemoryAccessException if the next byte could not be read
* {@return the next primitive byte. Use this method if you want to avoid the cost of
* boxing Bytes the normal next() method returns}
*
* @throws NoSuchElementException if the iteration has no more elements
*/
public byte next() throws MemoryAccessException {
if (count == 0) {
AddressRange range = addrSet.iterator().next();
Address start = range.getMinAddress();
long size = range.getLength();
if (size > BUF_SIZE) {
range = new AddressRangeImpl(start, start.add(BUF_SIZE - 1));
size = BUF_SIZE;
}
count = (int) size;
pos = 0;
addrSet.delete(range);
mem.getBytes(start, buf, 0, count);
public byte nextByte() {
ensureBuffer();
if (pos < bufSize) {
byte result = buf[pos];
pos++;
return result;
}
throw new NoSuchElementException();
}
private void ensureBuffer() {
if (pos >= bufSize && !addrSet.isEmpty()) {
AddressRange firstRange = addrSet.getFirstRange();
Address addr = firstRange.getMinAddress();
int readSize = (int) Math.min(firstRange.getLength(), buf.length);
addrSet.deleteFromMin(addr.add(readSize - 1)); // remove from addrSet before possible exception in getBytes()
pos = 0;
try {
bufSize = mem.getBytes(addr, buf, 0, readSize);
}
catch (MemoryAccessException e) {
bufSize = 0;
}
}
count--;
return buf[pos++];
}
}