From 163afc3ae1e2e1619ca6ed7a5106b1a65cb5cf81 Mon Sep 17 00:00:00 2001 From: rotund_tapir Date: Sun, 27 Sep 2026 14:45:10 +0100 Subject: [PATCH 1/2] Grow ArrayCollection geometrically when inserting at an index add(index, item) went through slideRight(), which reserved exactly the missing slot, so every insertion reallocated and copied the whole array. Building a type block entry by entry, as merging tables does, was quadratic because of it. Also fix ensureCapacity(int) allocating too little when some capacity is already free: it subtracted the free slots from the request and then added the result to the size instead of to the current length, which could leave fewer free slots than asked for. --- .../utils/collection/ArrayCollection.java | 12 +++++-- .../utils/collection/ArrayCollectionTest.java | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/reandroid/utils/collection/ArrayCollectionTest.java diff --git a/src/main/java/com/reandroid/utils/collection/ArrayCollection.java b/src/main/java/com/reandroid/utils/collection/ArrayCollection.java index 558adb0a3..b7ec5f44b 100644 --- a/src/main/java/com/reandroid/utils/collection/ArrayCollection.java +++ b/src/main/java/com/reandroid/utils/collection/ArrayCollection.java @@ -1026,7 +1026,12 @@ private void slideLeft(int position, int amount){ private void slideRight(int position, int amount){ boolean locked = mLocked; this.mLocked = true; - ensureCapacity(amount); + if(availableCapacity() < amount){ + // Grow by the usual step rather than by exactly 'amount', otherwise + // inserting n items one by one reallocates and copies the array n times + int grow = calculateGrow(); + ensureCapacity(grow > amount ? grow : amount); + } Object[] elements = this.mElements; int size = this.size; int i = size - 1; @@ -1095,9 +1100,10 @@ public void ensureCapacity(int capacity) { return; } int size = this.size; - int length = size + capacity; - Object[] update = getNewArray(length); Object[] elements = this.mElements; + // 'capacity' is what is still missing on top of the free slots + int length = elements.length + capacity; + Object[] update = getNewArray(length); if(elements.length == 0 || size == 0){ this.mElements = update; return; diff --git a/src/test/java/com/reandroid/utils/collection/ArrayCollectionTest.java b/src/test/java/com/reandroid/utils/collection/ArrayCollectionTest.java new file mode 100644 index 000000000..75649e6f7 --- /dev/null +++ b/src/test/java/com/reandroid/utils/collection/ArrayCollectionTest.java @@ -0,0 +1,35 @@ +package com.reandroid.utils.collection; + +import org.junit.Assert; +import org.junit.Test; + +public class ArrayCollectionTest { + + @Test + public void testInsertAtIndexThenSetSize() { + ArrayCollection collection = new ArrayCollection<>(); + for (int i = 0; i < 1000; i++) { + collection.add(i / 2, i); + } + Assert.assertEquals(1000, collection.size()); + // insertions leave spare capacity behind, growing must still fit + collection.setSize(1500); + Assert.assertEquals(1500, collection.size()); + collection.ensureCapacity(10); + Assert.assertTrue(collection.availableCapacity() >= 10); + int free = collection.availableCapacity(); + collection.ensureCapacity(free + 5); + Assert.assertTrue(collection.availableCapacity() >= free + 5); + } + + @Test + public void testInsertOrder() { + ArrayCollection collection = new ArrayCollection<>(); + for (int i = 0; i < 100; i++) { + collection.add(0, i); + } + for (int i = 0; i < 100; i++) { + Assert.assertEquals(Integer.valueOf(99 - i), collection.get(i)); + } + } +} From e0793b799872ccdf7609fa815c98f94edd8a90a3 Mon Sep 17 00:00:00 2001 From: REAndroid Date: Mon, 28 Sep 2026 16:31:17 +0200 Subject: [PATCH 2/2] [DEX] Abstracting InstructionOpcode --- .../java/com/reandroid/dex/ins/Opcode.java | 35 +++++++++++++++++++ .../dex/program/InstructionOpcode.java | 33 +++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/main/java/com/reandroid/dex/ins/Opcode.java b/src/main/java/com/reandroid/dex/ins/Opcode.java index 4b01681f6..93918ff2d 100644 --- a/src/main/java/com/reandroid/dex/ins/Opcode.java +++ b/src/main/java/com/reandroid/dex/ins/Opcode.java @@ -1010,92 +1010,119 @@ public ProgramType programType() { public boolean hasOutRegisters() { return getRegisterFormat().isOut(); } + @Override public boolean isConstString() { int value = this.value; return value == 0x1a || value == 0x1b; } + @Override public boolean isArrayGet() { int value = this.value; return value >= 0x44 && value <= 0x4a; } + @Override public boolean isArrayPut() { int value = this.value; return value >= 0x4b && value <= 0x51; } + @Override public boolean isArrayOp() { int value = this.value; return value >= 0x44 && value <= 0x51; } + @Override public boolean isFieldOp() { int value = this.value; return value >= 0x52 && value <= 0x6d; } + @Override public boolean isFieldInstanceOp() { int value = this.value; return value >= 0x52 && value <= 0x5f; } + @Override public boolean isFieldInstanceGet() { int value = this.value; return value >= 0x52 && value <= 0x58; } + @Override public boolean isFieldInstancePut() { int value = this.value; return value >= 0x59 && value <= 0x5f; } + @Override public boolean isFieldStaticOp() { int value = this.value; return value >= 0x60 && value <= 0x6d; } + @Override public boolean isFieldStaticGet() { int value = this.value; return value >= 0x60 && value <= 0x66; } + @Override public boolean isFieldStaticPut() { int value = this.value; return value >= 0x67 && value <= 0x6d; } + @Override public boolean isFieldGet() { return isFieldInstanceGet() || isFieldStaticGet(); } + @Override public boolean isFieldPut() { return isFieldInstancePut() || isFieldStaticPut(); } + @Override public boolean isMethodInvokeVirtual() { int value = this.value; return value == 0x6e || value == 0x74; } + @Override public boolean isMethodInvokeSuper() { int value = this.value; return value == 0x6f || value == 0x75; } + @Override public boolean isMethodInvokeDirect() { int value = this.value; return value == 0x70 || value == 0x76; } + @Override public boolean isMethodInvokeStatic() { int value = this.value; return value == 0x71 || value == 0x77; } + @Override public boolean isMethodInvokeInterface() { int value = this.value; return value == 0x72 || value == 0x78; } + @Override public boolean isMethodInvoke() { int value = this.value; return value != 0x73 && value >= 0x6e && value <= 0x78; } + @Override + public boolean isConst() { + return isConstNumber() || isConstString() || this == CONST_CLASS; + } + @Override public boolean isConstNumber() { int value = this.value; return value >= 0x12 && value <= 0x19; } + @Override public boolean isConstInteger() { int value = this.value; return value >= 0x12 && value <= 0x15; } + @Override public boolean isConstWide() { int value = this.value; return value >= 0x16 && value <= 0x19; } + @Override public boolean isReturn() { int value = this.value; if (value >= 0x0e && value <= 0x11) { @@ -1103,26 +1130,32 @@ public boolean isReturn() { } return value == 0xf1 || value == 0x73; } + @Override public boolean isMoveResult() { int value = this.value; return value == 0x0a || value == 0x0b || value == 0x0c; } + @Override public boolean isMove() { int value = this.value; return value >= 0x1 && value <= 0x9; } + @Override public boolean isIfTest() { int value = this.value; return value >= 0x32 && value <= 0x3d; } + @Override public boolean isGoto() { int value = this.value; return value == 0x28 || value == 0x29 || value == 0x2a; } + @Override public boolean isSwitch() { int value = this.value; return value == 0x2b || value == 0x2c; } + @Override public boolean isPayload() { int value = this.value; return value == 0x100 || value == 0x200 || value == 0x300; @@ -1133,9 +1166,11 @@ public boolean isInsBranching() { public boolean isBranching() { return isInsBranching() || isSwitch(); } + @Override public boolean isMethodExit() { return isReturn() || this == THROW; } + @Override public boolean isRange() { return getRegisterFormat().isRange(); } diff --git a/src/main/java/com/reandroid/dex/program/InstructionOpcode.java b/src/main/java/com/reandroid/dex/program/InstructionOpcode.java index 8306c8307..02211dfc8 100644 --- a/src/main/java/com/reandroid/dex/program/InstructionOpcode.java +++ b/src/main/java/com/reandroid/dex/program/InstructionOpcode.java @@ -18,4 +18,37 @@ public interface InstructionOpcode extends ProgramTypeDescriptor { int getValue(); String getName(); + + boolean isArrayGet(); + boolean isArrayOp(); + boolean isArrayPut(); + boolean isConst(); + boolean isConstInteger(); + boolean isConstNumber(); + boolean isConstString(); + boolean isConstWide(); + boolean isFieldGet(); + boolean isFieldInstanceGet(); + boolean isFieldInstanceOp(); + boolean isFieldInstancePut(); + boolean isFieldOp(); + boolean isFieldPut(); + boolean isFieldStaticGet(); + boolean isFieldStaticOp(); + boolean isFieldStaticPut(); + boolean isGoto(); + boolean isIfTest(); + boolean isMethodExit(); + boolean isMethodInvoke(); + boolean isMethodInvokeDirect(); + boolean isMethodInvokeInterface(); + boolean isMethodInvokeStatic(); + boolean isMethodInvokeSuper(); + boolean isMethodInvokeVirtual(); + boolean isMove(); + boolean isMoveResult(); + boolean isPayload(); + boolean isRange(); + boolean isReturn(); + boolean isSwitch(); }