From 1678fe1670fa15eaaf560da0e38baa652939beec Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:47:17 +0000 Subject: [PATCH] GP-6627 cleanup NumericUtils hex string and mem byte iteration --- .../checksums/BasicChecksumAlgorithm.java | 6 +- .../CRC16CCITTChecksumAlgorithm.java | 6 +- .../checksums/CRC16ChecksumAlgorithm.java | 6 +- .../checksums/DigestChecksumAlgorithm.java | 6 +- .../core/checksums/MemoryInputStream.java | 32 +-- .../main/java/ghidra/app/util/ByteCopier.java | 49 +--- .../model/util/MemoryByteIteratorTest.java | 124 ++++++--- .../java/ghidra/util/NumericUtilities.java | 255 +++++++----------- .../model/util/MemoryByteIterator.java | 82 +++--- 9 files changed, 263 insertions(+), 303 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/BasicChecksumAlgorithm.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/BasicChecksumAlgorithm.java index 63b75d0620..635158c04d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/BasicChecksumAlgorithm.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/BasicChecksumAlgorithm.java @@ -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) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/CRC16CCITTChecksumAlgorithm.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/CRC16CCITTChecksumAlgorithm.java index 1b250f1261..dd0b4a08d6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/CRC16CCITTChecksumAlgorithm.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/CRC16CCITTChecksumAlgorithm.java @@ -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; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/CRC16ChecksumAlgorithm.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/CRC16ChecksumAlgorithm.java index cf8273e81a..f79bf7bd43 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/CRC16ChecksumAlgorithm.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/CRC16ChecksumAlgorithm.java @@ -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) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/DigestChecksumAlgorithm.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/DigestChecksumAlgorithm.java index e3b497a92d..c42b1a36d1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/DigestChecksumAlgorithm.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/DigestChecksumAlgorithm.java @@ -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(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/MemoryInputStream.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/MemoryInputStream.java index 3e6fd68689..7800574984 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/MemoryInputStream.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/checksums/MemoryInputStream.java @@ -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; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java index a8d28f1df5..dac8139f74 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/ByteCopier.java @@ -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 { - - 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 flavorList; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/util/MemoryByteIteratorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/util/MemoryByteIteratorTest.java index f63802e56a..3ea7819784 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/util/MemoryByteIteratorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/util/MemoryByteIteratorTest.java @@ -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); } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java index d297136b50..dff5258aa5 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/NumericUtilities.java @@ -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> 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> 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> INTEGER_TYPES = Set.of(Byte.class, Short.class, Integer.class, Long.class); + private static final Set> FLOATINGPOINT_TYPES = Set.of(Float.class, Double.class); // @formatter:on private NumericUtilities() { @@ -678,7 +661,7 @@ public final class NumericUtilities { /** * Provide renderings of number in different bases: *
    - *
  • 0 - renders number as an escaped character sequence
  • + *
  • 0 - renders number as a big endian escaped character sequence
  • *
  • 2 - renders number as a base-2 integer
  • *
  • 8 - renders number as a base-8 integer
  • *
  • 10 - renders number as a base-10 integer
  • @@ -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 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 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 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 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 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} - *

    - * All values are rendered in their signed 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} - *

    - * All values are rendered in their unsigned 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} - *

    - * 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); + }; } - } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/util/MemoryByteIterator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/util/MemoryByteIterator.java index 3bd0a9378c..afc826ea82 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/util/MemoryByteIterator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/util/MemoryByteIterator.java @@ -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 { + 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++]; } }