Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,272 @@ public void addHasNext() {
mv.visitEnd();
}

public void addRemove() {
MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, "remove", "()V", null, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you write this code?

Copy link
Contributor Author

@lycoris106 lycoris106 Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simply follow each line in the original remove method and convert each line into asm visitor by referring to existing NonDex code like addHasNext methods. The one part I had to find other reference and ask LLM for help is the Complex condition part, which requires some complex jump instructions. But overall the syntax are taken from other existing part in the file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry we didn't tell you about ASMifier https://asm.ow2.io/faq.html#Q10

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EdwinIngJ did tell me about the tool, I used it to verify my instructions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tested your fix across multiple Java versions (for example, Java 8)? In some cases, the implementation can differ between versions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please double check java -version. If you're running on your VM, please email me the details so I can try out myself.

Copy link
Contributor Author

@lycoris106 lycoris106 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I was accidentally using the Oracle JDK 25 instead of OpenJDK 25. Now the build and test works fine. @EdwinIngJ Could you tell me more about the failure with Java 11? I tested in all above Java version including Java 11 and it worked fine.

My Java 11 JDK:

chihful2@fa25-cs527-009:~/forks/NonDex$ java --version
openjdk 11.0.29 2025-10-21
OpenJDK Runtime Environment (build 11.0.29+7-post-Ubuntu-1ubuntu124.04)
OpenJDK 64-Bit Server VM (build 11.0.29+7-post-Ubuntu-1ubuntu124.04, mixed mode, sharing)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the error running the integration tests:

[INFO] Building: comprehensive/pom.xml
[INFO] run post-build script verify.groovy
[INFO]   The build exited with code 1. See /home/edwinji2/temp/NonDex/nondex-maven-plugin/target/it/comprehensive/build.log for details.
[INFO]           comprehensive/pom.xml ............................ FAILED (17.35 s)
[INFO] Running edu.illinois.nondex.core.MapTest
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.001 s <<< FAILURE! - in edu.illinois.nondex.core.MapTest
[ERROR] testRemove[2](edu.illinois.nondex.core.MapTest)  Time elapsed: 0 s  <<< FAILURE!
java.lang.AssertionError
	at edu.illinois.nondex.core.MapTest.testRemove(MapTest.java:141)

[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.009 s <<< FAILURE! - in edu.illinois.nondex.core.MapTest
[ERROR] testRemove[2](edu.illinois.nondex.core.MapTest)  Time elapsed: 0 s  <<< FAILURE!
java.lang.AssertionError
	at edu.illinois.nondex.core.MapTest.testRemove(MapTest.java:141)

[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Failures: 
[ERROR] edu.illinois.nondex.core.MapTest.testRemove[2](edu.illinois.nondex.core.MapTest)
[ERROR]   Run 1: MapTest.testRemove:141
[ERROR]   Run 2: MapTest.testRemove:141
[ERROR]   Run 3: MapTest.testRemove:141

Here is my Java version:

edwinji2@fa25-cs527-004:~/temp/NonDex$ java -version
openjdk version "11.0.29" 2025-10-21
OpenJDK Runtime Environment (build 11.0.29+7-post-Ubuntu-1ubuntu124.04)
OpenJDK 64-Bit Server VM (build 11.0.29+7-post-Ubuntu-1ubuntu124.04, mixed mode, sharing)
edwinji2@fa25-cs527-004:~/temp/NonDex$ javac -version
javac 11.0.29

Copy link
Contributor Author

@lycoris106 lycoris106 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still couldn't reproduce the error myself. Running the integration tests with mvn clean install and running mvn clean verify -pl nondex-maven-plugin afterwards all passed for me. The MapTest in integration test should works exactly the same as the test in nondex-test module right? Is there a clean way to run the test possibly with different seeds like nondexRuns?

My integration tests result:

[INFO] --- invoker:3.9.1:integration-test (integration-test) @ nondex-maven-plugin ---
[INFO] Building: failing-it/pom.xml
[INFO] run post-build script verify.groovy
[INFO]           failing-it/pom.xml ............................... SUCCESS (11.36 s)
[INFO] Building: simple-multimodule-it/pom.xml
[INFO] run post-build script verify.groovy
[INFO]           simple-multimodule-it/pom.xml .................... SUCCESS (11.43 s)
[INFO] Building: clean-it/pom.xml
[INFO] run post-build script verify.groovy
[INFO]           clean-it/pom.xml ................................. SUCCESS (8.756 s)
[INFO] Building: comprehensive/pom.xml
[INFO] run post-build script verify.groovy
[INFO]           comprehensive/pom.xml ............................ SUCCESS (14.43 s)
[INFO] Building: simple-it/pom.xml
[INFO] run post-build script verify.groovy
[INFO]           simple-it/pom.xml ................................ SUCCESS (9.554 s)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I also ran the same commands. I'm not aware of a clean way of specifying the seed in the test. But Github action was recently added to master. Try rebasing on top of master and let the Github action run and maybe that will provide more insight.

mv.visitCode();

mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "lastReturnedIndex",
"I");
mv.visitInsn(Opcodes.ICONST_M1);
Label l0 = new Label();
mv.visitJumpInsn(Opcodes.IF_ICMPNE, l0);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalStateException", "<init>", "()V", false);
mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(l0);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "this$0",
"Ljava/util/IdentityHashMap;");
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap", "modCount", "I");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "expectedModCount",
"I");
Label l1 = new Label();
mv.visitJumpInsn(Opcodes.IF_ICMPEQ, l1);
mv.visitTypeInsn(Opcodes.NEW, "java/util/ConcurrentModificationException");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/ConcurrentModificationException", "<init>", "()V", false);
mv.visitInsn(Opcodes.ATHROW);

// Update modCount and expectedModCount
mv.visitLabel(l1);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "this$0",
"Ljava/util/IdentityHashMap;");
mv.visitInsn(Opcodes.DUP);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap", "modCount", "I");
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IADD);
mv.visitInsn(Opcodes.DUP_X1);
mv.visitFieldInsn(Opcodes.PUTFIELD, "java/util/IdentityHashMap", "modCount", "I");
mv.visitFieldInsn(Opcodes.PUTFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "expectedModCount",
"I");

// Initialize deletedSlot with lastReturnedIndex
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "lastReturnedIndex",
"I");
mv.visitVarInsn(Opcodes.ISTORE, 1); // deletedSlot in local var 1

// Reset lastReturnedIndex
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.ICONST_M1);
mv.visitFieldInsn(Opcodes.PUTFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "lastReturnedIndex",
"I");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitFieldInsn(Opcodes.PUTFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "index",
"I");

// Reset indexValid
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.ICONST_0);
mv.visitFieldInsn(Opcodes.PUTFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "indexValid",
"Z");

// Initialize tab as reference to traversalTable
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "traversalTable",
"[Ljava/lang/Object;");
mv.visitVarInsn(Opcodes.ASTORE, 2); // tab in local var 2

mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitInsn(Opcodes.ARRAYLENGTH);
mv.visitVarInsn(Opcodes.ISTORE, 3); // len in local var 3
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitVarInsn(Opcodes.ISTORE, 4); // d in local var 4

// Get key to be removed
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitInsn(Opcodes.AALOAD);
mv.visitVarInsn(Opcodes.ASTORE, 5); // key in local var 5

// Remove key and value
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.AASTORE);

mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IADD);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.AASTORE);

// Decrement the size
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap$IdentityHashMapIterator", "this$0",
"Ljava/util/IdentityHashMap;");
mv.visitInsn(Opcodes.DUP);
mv.visitFieldInsn(Opcodes.GETFIELD, "java/util/IdentityHashMap", "size", "I");
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.ISUB);
mv.visitFieldInsn(Opcodes.PUTFIELD, "java/util/IdentityHashMap", "size", "I");

// Inlined nextKeyIndex()
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitInsn(Opcodes.ICONST_2);
mv.visitInsn(Opcodes.IADD);
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ILOAD, 3);

Label elseLabel = new Label();
Label endLabel = new Label();
mv.visitJumpInsn(Opcodes.IF_ICMPGE, elseLabel);
mv.visitJumpInsn(Opcodes.GOTO, endLabel);
mv.visitLabel(elseLabel);
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { Opcodes.INTEGER });
mv.visitInsn(Opcodes.POP);
mv.visitInsn(Opcodes.ICONST_0);
mv.visitLabel(endLabel);
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { Opcodes.INTEGER });
mv.visitVarInsn(Opcodes.ISTORE, 6); // i in local var 6

Label loopStart = new Label();
Label loopEnd = new Label();
Label loopContinue = new Label();

mv.visitLabel(loopStart);
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { Opcodes.INTEGER }, 0, null);

mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitInsn(Opcodes.AALOAD);
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ASTORE, 7); // item in local var 7

// Check if item==null
mv.visitJumpInsn(Opcodes.IFNULL, loopEnd);

// Compute hash by inlining hash(item, len): ((h << 1) - (h << 8)) & (length - 1)
mv.visitVarInsn(Opcodes.ALOAD, 7);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "identityHashCode", "(Ljava/lang/Object;)I", false);
mv.visitInsn(Opcodes.DUP);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.ISHL); // h << 1
mv.visitInsn(Opcodes.SWAP);
mv.visitIntInsn(Opcodes.BIPUSH, 8);
mv.visitInsn(Opcodes.ISHL);
mv.visitInsn(Opcodes.ISUB);
mv.visitVarInsn(Opcodes.ILOAD, 3);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.ISUB);
mv.visitInsn(Opcodes.IAND);
mv.visitVarInsn(Opcodes.ISTORE, 8); // r in local var 8

// Complex conditional: if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i))
Label conditionFalse = new Label();
Label conditionTrue = new Label();

// Check cond1: (i < r && (r <= d || d <= i))
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitVarInsn(Opcodes.ILOAD, 8);
Label cond1False = new Label();
mv.visitJumpInsn(Opcodes.IF_ICMPGE, cond1False);
mv.visitVarInsn(Opcodes.ILOAD, 8);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitJumpInsn(Opcodes.IF_ICMPLE, conditionTrue);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitJumpInsn(Opcodes.IF_ICMPLE, conditionTrue);

// cond1 is false, check cond2
mv.visitLabel(cond1False);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);

mv.visitVarInsn(Opcodes.ILOAD, 8);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitJumpInsn(Opcodes.IF_ICMPGT, conditionFalse);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitJumpInsn(Opcodes.IF_ICMPGT, conditionFalse);

// Condition is true, shift the values
mv.visitLabel(conditionTrue);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);

// Update t[d] to item
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitVarInsn(Opcodes.ALOAD, 7);
mv.visitInsn(Opcodes.AASTORE);

// Shift tab[i + 1] into tab[d + 1]
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 4);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IADD);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IADD);
mv.visitInsn(Opcodes.AALOAD);
mv.visitInsn(Opcodes.AASTORE);

// Clear tab[i] to null;
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.AASTORE);

// Clear tab[i + 1] to null
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IADD);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.AASTORE);

// Set d to i
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitVarInsn(Opcodes.ISTORE, 4);

// Update i = nextKeyIndex(i, len), inlined
mv.visitVarInsn(Opcodes.ILOAD, 6);
mv.visitInsn(Opcodes.ICONST_2);
mv.visitInsn(Opcodes.IADD);
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ILOAD, 3);

Label elseLabel2 = new Label();
Label endLabel2 = new Label();
mv.visitJumpInsn(Opcodes.IF_ICMPGE, elseLabel2);
mv.visitJumpInsn(Opcodes.GOTO, endLabel2);
mv.visitLabel(elseLabel2);
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { Opcodes.INTEGER });
mv.visitInsn(Opcodes.POP);
mv.visitInsn(Opcodes.ICONST_0);
mv.visitLabel(endLabel2);
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { Opcodes.INTEGER });
mv.visitVarInsn(Opcodes.ISTORE, 6);

mv.visitJumpInsn(Opcodes.GOTO, loopStart);
mv.visitLabel(loopEnd);
mv.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);

mv.visitLabel(conditionFalse);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);

mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(4, 6);
mv.visitEnd();
}

@Override
public void visitEnd() {
addOrder();
addKeys();
addIdx();
addNextIndex();
addHasNext();
addRemove();
super.visitEnd();
}

Expand All @@ -181,6 +440,9 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
if ("nextIndex".equals(name)) {
return super.visitMethod(access, "originalNextIndex", desc, signature, exceptions);
}
if ("remove".equals(name)) {
return super.visitMethod(access, "originalRemove", desc, signature, exceptions);
}
if ("<init>".equals(name) && "(Ljava/util/IdentityHashMap;)V".equals(desc)) {
return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, desc, signature, exceptions)) {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
The MIT License (MIT)
Copyright (c) 2015 Alex Gyori
Copyright (c) 2022 Kaiyao Ke
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add your name here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkstyle enforces fixed header so I couldn't add my name in it. I added my name in comment above the test case instead, is this ok?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine as you did it, although we/you could have changed the Checkstyle config.

Copyright (c) 2015 Owolabi Legunsen
Copyright (c) 2015 Darko Marinov
Copyright (c) 2015 August Shi


Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package edu.illinois.nondex.core;

import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;

import org.junit.Test;

public class IdentityHashMapTest {

// Author: Chih-Fu Lai (2025)
// Added test coverage for IdentityHashMap.removeAll iterator behavior
@Test
public void testRemoveAllArrayIndexOutOfBounds() {
for (int i = 0; i < 999; i++) {
IdentityHashMap<Object, Object> map = new IdentityHashMap<>(4);

Object k1 = new Object();
Object k2 = new Object();
Object k3 = new Object();

map.put(k1, 1);
map.put(k2, 2);
map.put(k3, 3);

map.keySet().removeAll(Collections.singleton(k2));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some assertion to check (e.g., that the size of map is two after this), or are you simply checking that this doesn't throw an exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I am simply checking that this doesn't throw an exception. I consider it redundant to add additional assertions because most map behaviors are already checked by tests in MapTest, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, fine to not have an assertion.

}
}
}