/item override (and /item fill) targeting the charged_projectiles component through a "contents"
slot source makes the server thread spin 2,147,483,647 times inside
SimpleMutableContainer#insertNewSlots before it returns. The command then completes normally, with
no error and nothing in the log — the only symptom is that the server is completely frozen for
several seconds. Measured 0.5 to 8.8 seconds depending on the variant and JIT state; a clean
measurement on my machine was 2,555 ms for /item override.
A repeating command block re-triggers this every tick, which makes the freeze permanent.
Steps to Reproduce
Create a world with cheats on.
Hold any item. The charged_projectiles component does not need to be present already, because
the code path uses getOrDefault.Run:
/item override entity @s {type:"contents",component:"charged_projectiles",slot_source:{type:"slot_range",slots:"weapon.mainhand"}} with arrowThe command reports success. The server is unresponsive for several seconds first.
A second trigger, through the /item fill provider, with container.0 empty:
/item fill entity @s {type:"contents",component:"charged_projectiles",slot_source:{type:"slot_range",slots:"weapon.mainhand"}} from entity @s container.0
/item replace is not affected — it terminates after a single iteration.
Expected Results
The insertion loop stops as soon as an insertion fails, so the command returns immediately.
Cause
SimpleMutableContainer#insertNewSlots (world/item/component/SimpleMutableContainer.java:61-72):
protected int insertNewSlots(final ItemProvider newItems, final SlotSelector slotSelector) {
int successCount = 0;
while (newItems.hasNext() && this.canInsertNewSlots() && slotSelector.trySelectSlot(ItemStack.EMPTY)) {
boolean success = this.addSlotWithItem(newItems.next());
if (success) {
successCount++;
}
}
return successCount;}
There is no break when an insertion fails, and for charged_projectiles all three loop conditions
are permanently true:
newItems.hasNext(): /item override builds
ItemProvider.of(items).orElseProvide(ItemStack.EMPTY) (ItemCommands.java:189), and
FallbackItemProvider#hasNext is a literal "return true" (ItemProvider.java:53-55). /item fill
builds ItemProvider.cycle(items) (ItemCommands.java:185), whose hasNext is also permanently
true when the source is non-empty.this.canInsertNewSlots(): ChargedProjectiles.Mutable#canInsertNewSlots is items.size() < 1024,
and items never grows, because ChargedProjectiles.Mutable#addSlotWithItem is
"return !itemStack.isEmpty() && super.addSlotWithItem(itemStack);" — it rejects the empty
fallback stack without adding a slot.slotSelector.trySelectSlot(...): SlotSelector.tracking hardcodes Integer.MAX_VALUE as its limit
(SlotSelector.java:23-25).
So the loop runs until the selector's counter saturates at 2^31-1.
Why this is a bug and not a design choice
There are exactly three ContainerComponent.Mutable implementations, and one of them already has
the missing guard. BundleContents.Mutable overrides insertNewSlots for precisely this reason
(world/item/component/BundleContents.java:298-311):
boolean success = newItems.findNextNonEmpty() && this.addSlotWithItem(newItems.peek());
if (!success) {
break;
}
ItemContainerContents.Mutable does not need the guard because its slot count does grow, so
size < 256 terminates it. ChargedProjectiles.Mutable is the only one that can fail an insertion
without growing, and it is the only one that inherits the break-less base loop.
Measured, all three driven with the same provider the /item override path builds:
ChargedProjectiles replaced=1 iterations=2147483647 2555 ms
ItemContainerContents replaced=256 iterations=256 0 ms
BundleContents replaced=1 iterations=2 0 ms
Related
MC-310315 ("number providers that reference themselves crash the game", Resolved/Fixed in
26.3 Snapshot 6) shows Mojang treats non-termination in this subsystem as a defect and fixed a
different instance of it in this very build.
Comments 0
No comments.