Bug - Fixed [JavaScript] Importing ASH script fails if function name has leading underscore

  • KoLmafia revision: r20672
Normally, JavaScript scripts can import ASH scripts using require() and call user-defined ASH functions in the top-level scope. However, the script fails if there exists an ASH function with a leading underscore.

For example, given the following ASH script:
Code:
void _foo() {
  print("foo bar");
}

Importing this from JavaScript results in an error :
Code:
> js require("funcname.ash");
Unexpected error, debug log printed.

The debug log looks like this:
Code:
class java.lang.StringIndexOutOfBoundsException: String index out of range: 0
java.lang.StringIndexOutOfBoundsException: String index out of range: 0
    at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:47)
    at java.base/java.lang.String.charAt(String.java:693)
    at net.sourceforge.kolmafia.textui.javascript.JavascriptRuntime.toCamelCase(JavascriptRuntime.java:99)
    at net.sourceforge.kolmafia.textui.javascript.SafeRequire.call(SafeRequire.java:79)
    at org.mozilla.javascript.optimizer.OptRuntime.callName(OptRuntime.java:76)
    at org.mozilla.javascript.gen.command_line_3._c_script_0(command line:1)
    at org.mozilla.javascript.gen.command_line_3.call(command line)
    at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:412)
    at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3578)
    at org.mozilla.javascript.gen.command_line_3.call(command line)
    at org.mozilla.javascript.gen.command_line_3.exec(command line)
    at org.mozilla.javascript.Context.evaluateString(Context.java:1234)
    at net.sourceforge.kolmafia.textui.javascript.JavascriptRuntime.executeRun(JavascriptRuntime.java:267)
    at net.sourceforge.kolmafia.textui.javascript.JavascriptRuntime.execute(JavascriptRuntime.java:231)
    at net.sourceforge.kolmafia.textui.AbstractRuntime.execute(AbstractRuntime.java:61)
    at net.sourceforge.kolmafia.textui.command.JavaScriptCommand.run(JavaScriptCommand.java:56)
    at net.sourceforge.kolmafia.KoLmafiaCLI.doExecuteCommand(KoLmafiaCLI.java:593)
    at net.sourceforge.kolmafia.KoLmafiaCLI.executeCommand(KoLmafiaCLI.java:546)
    at net.sourceforge.kolmafia.KoLmafiaCLI.executeLine(KoLmafiaCLI.java:448)
    at net.sourceforge.kolmafia.KoLmafiaCLI.executeLine(KoLmafiaCLI.java:316)
    at net.sourceforge.kolmafia.KoLmafiaCLI$CommandProcessorThread.run(KoLmafiaCLI.java:238)

...which can be traced to JavaScriptRuntime.java:
Java:
public static String toCamelCase( String name )
{
       if ( name == null )
       {
              return null;
       }

       boolean first = true;
       StringBuilder result = new StringBuilder();
       for ( String word : name.split( "_" ) )
       {
              if ( first )
              {
                     result.append( word.charAt( 0 ) ); // StringIndexOutOfBoundsException: String index out of range
                     first = false;
              }
              else
              {
                     result.append( Character.toUpperCase( word.charAt( 0 ) ) );
              }
              result.append( word.substring( 1 ) );
       }

       return result.toString();
}

It seems to be caused by an empty string token.

Edit: Some more edge cases I tested:
Code:
void _this_crashes() {}
void this__crashes() {}
void noCrash_() {}
void noCrash__() {}
void noCrash___() {}
 
Last edited:
Another minor bug I discovered while investigating this issue: toCamelCase() discards any trailing underscores.
Code:
foo_bar    => fooBar
foo_bar_   => fooBar
foo_bar__  => fooBar
foo_bar___ => fooBar

This is due to how Java's String.split() works:
Trailing empty strings are therefore not included in the resulting array.

It can be amended by using the two-parameter version of String.split() and passing a negative integer as the second argument.



Here's a modified toCamelCase() that fixes both issues (invalid index access on empty string, and stripping of trailing underscores):

Java:
public static String toCamelCase( String name )
{
       if ( name == null )
       {
              return null;
       }

       boolean first = true;
       StringBuilder result = new StringBuilder();
       for ( String word : name.split( "_", -1 ) )
       {
              if ( word.isEmpty() )
              {
                     result.append( "_" );
              }
              else if ( first )
              {
                     result.append( word );
                     first = false;
              }
              else
              {
                     result.append( Character.toUpperCase( word.charAt( 0 ) ) );
                     result.append( word.substring( 1 ) );
              }
       }

       return result.toString();
}

Results:
Code:
_foo_bar                      => _fooBar
foo__bar                      => foo_Bar
trailing_                     => trailing_
trailing__                    => trailing__
trailing___                   => trailing___
____pathological____case_____ => ____pathological___Case_____
 

gausie

D̰͕̝͚̤̥̙̐̇̑͗̒e͍͔͎͈͔ͥ̉̔̅́̈l̠̪̜͓̲ͧ̍̈́͛v̻̾ͤe͗̃ͥ̐̊ͬp̔͒ͪ
I'm going to do some live bug fixing on Wednesday on the ASS discord, will fix this.

A quick think tells me we want to do a replacement like this

 

gausie

D̰͕̝͚̤̥̙̐̇̑͗̒e͍͔͎͈͔ͥ̉̔̅́̈l̠̪̜͓̲ͧ̍̈́͛v̻̾ͤe͗̃ͥ̐̊ͬp̔͒ͪ
Top