bmx-ng / bcc

A next-generation bcc parser for BlitzMax
zlib License
33 stars 13 forks source link

Slicing results of ReadLine(...).Split(...) doesn't work #611

Open AXKuhta opened 1 year ago

AXKuhta commented 1 year ago

Given a file test.txt that contains This is a test string. Sentence 1. Sentence 2. and the following program:

Framework BRL.StandardIO
Import BRL.Filesystem

Local File:TStream = ReadFile("test.txt")
Local Arr:String[] = ReadLine(File).Split(".")[1..]

Print(Arr[0])

The expected result is Sentence 1 but in reality it prints nothing.

Workaround:

- Local Arr:String[] = ReadLine(File).Split(".")[1..]
+ Local Arr:String[] = ReadLine(File).Split(".")
+ Arr = Arr[1..]

Diffing working C code against broken C code reveals what appears to be the problem:

- BBARRAY bbt_Arr=bbStringSplit((BBSTRING)brl_stream_ReadLine((struct brl_stream_TStream_obj*)bbt_File),&_s1);
- bbt_Arr=bbArraySlice("$",bbt_Arr,1,(bbt_Arr)->scales[0]);
+ BBARRAY bbt_Arr=bbArraySlice("$",bbStringSplit((BBSTRING)brl_stream_ReadLine((struct brl_stream_TStream_obj*)bbt_File),&_s1),1,(bbStringSplit((BBSTRING)brl_stream_ReadLine((struct brl_stream_TStream_obj*)bbt_File),&_s1))->scales[0]);

ReadLine(File).Split(".") gets executed twice!

BlitzMax version: BlitzMax_win32_0.129.3.45

GWRon commented 1 year ago

You mixed the second diff (top is the "multi line" and bottom the "single line") (not a biggy, just wanted to mention)

-       BBARRAY bbt_Arr=bbArraySlice("$",bbStringSplit((BBSTRING)brl_stream_ReadLine2((struct brl_stream_TStream_obj*)bbt_File),((BBString*)&_s1)),1,(bbStringSplit((BBSTRING)brl_stream_ReadLine2((struct brl_stream_TStream_obj*)bbt_File),((BBString*)&_s1)))->scales[0]);
+       BBARRAY bbt_Arr=bbStringSplit((BBSTRING)brl_stream_ReadLine2((struct brl_stream_TStream_obj*)bbt_File),((BBString*)&_s1));
+       bbt_Arr=bbArraySlice("$",bbt_Arr,1,(bbt_Arr)->scales[0]);

I also reformatted the output a bit so it is easier to see what is happening:

/* single line */
BBARRAY bbt_Arr=
    bbArraySlice(
        "$",
        bbStringSplit(
            (BBSTRING)brl_stream_ReadLine2(
                (struct brl_stream_TStream_obj*)bbt_File
            ),
            ((BBString*)&_s1)),
            1,
            (bbStringSplit(
                (BBSTRING)brl_stream_ReadLine2(
                    (struct brl_stream_TStream_obj*)bbt_File
                ),
                ((BBString*)&_s1))
            )->scales[0]
    );

/* multi line */
BBARRAY bbt_Arr=
    bbStringSplit(
        (BBSTRING)brl_stream_ReadLine2(
            (struct brl_stream_TStream_obj*)bbt_File
        ),
        ((BBString*)&_s1)
    );

bbt_Arr=
    bbArraySlice(
        "$",
        bbt_Arr,
        1,
        (bbt_Arr)->scales[0]
    );

My guess is, that tries to do things without a temporary variable - and thus is resolving this "what to split" two times. In this very case it does not work out as it is not a variable it reads but a method call - so the content changes "inbetween".

This thing might also introduce bugs in multi threaded environments (if the "string getter" locks/unlocks a mutex and thus allows modification inbetween the execution of this daisy-chain.

The issue is not Readline().Split() but StringGetMethod()[arraySlice..] with the emphasize on the lack of the "count" part in the slice.

arr[1..]

this will transform to

    bbArraySlice(
        "$",
        bbt_Arr,
        1,
        (bbt_Arr)->scales[0]
    );

You left out the right hand side of the slice, so it wants to slice till the end - to know what the end is, it will ask "->scales[0]" of the array. and as the above code does not have that array in a variable already ... it uses whatever is needed to get the "array" -> and thus leads to the double "bbsplit(readline)".

It might affect not just "array slicing". The issue might arise everywhere you can leave out information and it will have to read it from the object "used on" (for now I can only imagine array slicing but am not sure if other stuff exists too).

GWRon commented 1 year ago

This is a more concentrated sample:

Framework BRL.StandardIO
Import BRL.Filesystem
Import Brl.Random

Function GetStringArray:String[]()
    Local s:String[] = ["hello", "world", String(Rand(10000))]
    Print "returning " + s[2]
    Return s
End Function

Print GetStringArray()[2..][0]

Output will show that the method is called twice and thus resulting in different values:

returning 1747
returning 6406
6406
GWRon commented 1 year ago

Even shorter:

SuperStrict
Framework BRL.StandardIO

Function GetString:String()
    Print "GetString() called"
    Return "Test"
End Function
Print GetString()[0..]
GetString() called
GetString() called
Test