Exception in thread "Thread-1" java.lang.StringIndexOutOfBoundsException: String index out of range: -1

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Exception in thread "Thread-1" java.lang.StringIndexOutOfBoundsException: String index out of range: -1

jsAJvuEp7b
This post was updated on .

The Problem

When you try to open one of your compiled.vm files in the VMEmulator, either it sits there and says "Loading...", or (if you still have its terminal open), you get an error like
Exception in thread "Thread-1" java.lang.StringIndexOutOfBoundsException: String index out of range: -1
This is followed by a stack trace.

tl;dr:

This probably means that you're calling or defining a function without a period in it.

How to Fix

Both your "function " lines and your "call " lines need to be prefaced by their class.

So if you have defined a function "function Bat.hide {num_local}", then when you call it, you need to "call Bat.hide {num_args}".

Just having "call hide {num_args}" or "function hide {num_local}" will cause the VMProgram to throw
java.lang.StringIndexOutOfBoundsException: String index out of range: -1

How I Troubleshot this

I will say, I was very pleased with how I figured this out.

The Error

Exception in thread "Thread-1" java.lang.StringIndexOutOfBoundsException: String index out of range: -1
        at java.lang.String.substring(String.java:1967)
        at Hack.VMEmulator.VMProgram.getAddress(Unknown Source)
        at Hack.VMEmulator.VMProgram.buildProgram(Unknown Source)
        at Hack.VMEmulator.VMProgram.loadProgram(Unknown Source)
        at Hack.VMEmulator.VMProgram$LoadProgramTask.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

From the above, I could tell that the error I was seeing was a failure to substring (on index -1) in the getAddress method of the VMProgram class in the Hack.VMEmulator package.

The Hunt

I opened NetBeans, and used it to open JAR files in
nand2tetris/tools/bin/lib
 until I found the Hack.VMEmulator package in Simulator.jar, and sure enough it had a VMProgram class.
Simulators.jar contains the package and class in which the stack trace originates

Because this is a compiled class file, unfortunately most of the source is not present, only compiled Java ByteCode (we're getting quite meta here)
Compiled Java ByteCode for VMProgram.getAddress()

Piecing it Back Together

Thankfully, Wikipedia hosts a table of the Java ByteCode operations listings

I found-and-replaced the Mnemonics in the VMProgram.class file until I had a pretty good understanding of what was occurring (hint, Java's pushing variables to, and popping them from, its stack).
    public short getAddress(String string) throws Hack.Controller.ProgramException {
        // <editor-fold defaultstate="collapsed" desc="Compiled Code">
        /* 0: aload_0
load a reference onto the stack from local variable 0

         * 1: getfield      Hack/VMEmulator/VMProgram.functions:Ljava/util/Hashtable;
get a field value of an object objectref, where the field is identified by field reference in the constant pool index (indexbyte1 << 8 + indexbyte2)

         * 4: aload_1
load a reference onto the stack from local variable 1

         * 5: invokevirtual java/util/Hashtable.get:(Ljava/lang/Object;)Ljava/lang/Object;
invoke virtual method on object objectref and puts the result on the stack (might be void); the method is identified by method reference index in constant pool (indexbyte1 << 8 + indexbyte2)

         * 8: checkcast     java/lang/Short
checks whether an objectref is of a certain type, the class reference of which is in the constant pool at index (indexbyte1 << 8 + indexbyte2)

         * 11: astore_2
store a reference into local variable 2

         * 12: aload_2
load a reference onto the stack from local variable 2

         * 13: ifnull        21
if value is null, branch to instruction at branchoffset (signed short constructed from unsigned bytes branchbyte1 << 8 + branchbyte2)

         * 16: aload_2
load a reference onto the stack from local variable 2

         * 17: invokevirtual java/lang/Short.shortValue:()S
invoke virtual method on object objectref and puts the result on the stack (might be void); the method is identified by method reference index in constant pool (indexbyte1 << 8 + indexbyte2)

         * 20: ireturn
         * 21: aload_1
load a reference onto the stack from local variable 1

         * 22: iconst_0
load the int value 0 onto the stack

         * 23: aload_1
load a reference onto the stack from local variable 1

         * 24: ldc           .
push a constant #index from a constant pool (String, int, float, Class, java.lang.invoke.MethodType, or java.lang.invoke.MethodHandle) onto the stack           .

         * 26: invokevirtual java/lang/String.indexOf:(Ljava/lang/String;)I
invoke virtual method on object objectref and puts the result on the stack (might be void); the method is identified by method reference index in constant pool (indexbyte1 << 8 + indexbyte2)

         * 29: invokevirtual java/lang/String.substring:(II)Ljava/lang/String;
invoke virtual method on object objectref and puts the result on the stack (might be void); the method is identified by method reference index in constant pool (indexbyte1 << 8 + indexbyte2)
Ops 0 and 1 seem to indicate that we're pushing some Hashtable "functions" onto our stack.
Ops 4 and 5 suggest that we're pushing a (String?) key onto the stack, and calling functions.get(key).
Ops 8, 11, and 12 are something about checking whether the returned value is a Short
Op 13 jumps to Op 21 "ifnull," and the ops in between appear to return (thus not causing our StringIndexOutOfBoundsException), so let's go to Op 21.
Op 21 pushes the same key onto the stack (from local 1).
Op 22 pushes int 0 onto the stack
Op 23 pushes the same key from local 1
Op 24 pushes a literal "." onto the stack
Op 26 calls String.indexOf; it's looking for a period in your function name and when it doesn't find it, returns -1
Op 29 calls String.substring, but since our two topmost stack values are (-1, 0), we get an StringIndexOutOfBoundsException.

Boom.

Nand2Tetris made me understand Java.
Reply | Threaded
Open this post in threaded view
|

Re: Exception in thread "Thread-1" java.lang.StringIndexOutOfBoundsException: String index out of range: -1

ivant
Great job!

I don't know if you are aware, but the nand2tetris software suite is open source and the code is available from here: https://www.nand2tetris.org/software (scroll to the bottom of the page).

Shameless self plug: I also created a project in github. You can read about the changes here.
Reply | Threaded
Open this post in threaded view
|

Re: Exception in thread "Thread-1" java.lang.StringIndexOutOfBoundsException: String index out of range: -1

jsAJvuEp7b
Haha that source would have made this process less tedious

For closure, the source corresponding to the above compiled Java ByteCode is
public short getAddress(String functionName) throws ProgramException {
    Short address = (Short)functions.get(functionName);
    if (address != null) {
        return address.shortValue();
    } else {
        String className =
            functionName.substring(0, functionName.indexOf("."));
from
{nand2tetris_source_zipfile}/SimulatorsPackageSource/Hack/VMEmulator/VMProgram.java
This confirms that, indeed, if your function name does not have a "." in it, the VMProgram will raise an unhandled
java.lang.StringIndexOutOfBoundsException
Reply | Threaded
Open this post in threaded view
|

Re: Exception in thread "Thread-1" java.lang.StringIndexOutOfBoundsException: String index out of range: -1

FraZZler
In reply to this post by jsAJvuEp7b
Thank you so much for the effort. You saved me a ton of time!