Skip to content

Fix sandbox static init and finalize - #123

Merged
flemming-n-larsen merged 2 commits into
mainfrom
fix-sandbox-static-init-and-finalize
Nov 2, 2026
Merged

flemming-n-larsen merged 2 commits into
mainfrom
fix-sandbox-static-init-and-finalize

Conversation

@jkflying

@jkflying jkflying commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Static initializers running during cleanup

When a robot class loader is cleaned up, e.g. after building the robot database, cleanStaticReferences sets the robot's static reference fields to null. Setting a static field initializes the class, so a static initializer that hadn't run yet ran on the cleanup thread instead of on the robot's thread. Its System.out output went to the main console instead of the robot's console, and robot code ran while building the robot database, which made it slower than needed.

Cleanup now skips classes that haven't been initialized, using sun.misc.Unsafe.shouldBeInitialized(). Such classes can't hold references anyway, because their static fields were never set. If the check isn't available, nothing is cleaned.

This only matters on Java 8–11. On Java 12+, cleaning static references already fails before any field is set, because Field.modifiers can't be accessed anymore.

finalize() running on the JVM's finalizer thread

The JVM calls finalize() on its finalizer thread, which the sandbox treats as safe. RobotClassRewriter now renames finalize()V methods of robot classes, and calls to them, to robocode$finalize()V, so they no longer override Object.finalize() and the JVM never calls them. Calls to finalize() of Java classes, e.g. super.finalize() with Object as superclass, are not renamed.

Impact on the RoboRumble participants:

  • 3 of 1472 robot jars declare finalize(). All of these methods only clear fields, call super.finalize(), or close Rhino generators, so robots lose nothing by them no longer being called.
  • The classes of kid.Toa .0.5 and kid.team.OmegaSquad .0.2 load and verify with their finalize() methods renamed.
  • inonit.robocode.javascript.MyFirstRobot 1.1 fails to load the same Rhino classes with or without this change, as the verifier already denies some of them. This is a separate issue.

Testing

  • RobotClassLoaderTest.finalizeIsRenamed compiles a robot with a finalize() method, and checks that it is renamed and that super.finalize() still works. It fails without the rewriter change.
  • The full test suite passes on Java 17 and Java 25.
  • The static initializer fix has no automated test, because the bug only exists on Java 8–11 and the tests now run on Java 17+. It was checked manually on Java 8 with a robot whose static initializer prints: before the fix, the output appeared during cleanup; after it, nothing did.

🤖 Generated with Claude Code

jkflying and others added 2 commits November 1, 2026 22:34
…ializers outside the robot's thread

When a robot class loader is cleaned up, for example after building the robot
database, cleanStaticReferences sets the robot's static reference fields to
null. Setting a static field makes the JVM initialize the class, so a static
initializer that hadn't run yet now ran on the thread doing the cleanup instead
of on the robot's thread. Its System.out output then went to the main console
instead of the robot's console. This also meant robot code ran during a robot
database build, making the build slower than needed.

Cleanup now skips classes that haven't been initialized, using
sun.misc.Unsafe.shouldBeInitialized(). Such classes can't hold references
anyway, because their static fields were never set. If the check isn't
available, nothing is cleaned.

This only matters on Java 8-11. On Java 12+ cleaning static references fails
earlier because Field.modifiers can't be accessed anymore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lizer thread

The JVM calls finalize() of unreachable objects on its finalizer thread, not on
a robot's thread. A robot class that overrides Object.finalize() therefore gets
its code run on a thread that the sandbox treats as safe, where robot code is
not checked like on the robot's own threads.

RobotClassRewriter now renames finalize()V methods of robot classes, and calls
to them, to robocode$finalize()V. They no longer override Object.finalize(), so
the JVM never calls them. Calls to finalize() of Java classes, e.g.
super.finalize() with Object as superclass, are not renamed. A call to a
finalize() that a robot class only inherits throws NoSuchMethodError, but only
if the robot calls it itself.

Impact on the RoboRumble participants: 3 of 1472 robot jars declare finalize(),
and all of these methods only clear fields, call super.finalize(), or close
Rhino generators, so robots lose nothing by them no longer being called. The
classes of kid.Toa .0.5 and kid.team.OmegaSquad .0.2 load and verify with their
finalize() methods renamed. inonit.robocode.javascript.MyFirstRobot 1.1 fails
to load the same Rhino classes with or without this change, as the verifier
already denies some of them.

RobotClassLoaderTest.finalizeIsRenamed compiles a robot with a finalize()
method, and checks that it is renamed and that super.finalize() still works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@flemming-n-larsen
flemming-n-larsen merged commit 3e1b1d2 into main Nov 2, 2026
2 checks passed
@flemming-n-larsen
flemming-n-larsen deleted the fix-sandbox-static-init-and-finalize branch November 2, 2026 19:07
flemming-n-larsen added a commit that referenced this pull request Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants