[Tutorial] Known Runtime/Compiler PAWN Bugs
#1

Introduction

Known (or suspected) compiler bugs. Feel free to add more if there are any.

1)
  • Code

    pawn Code:
    return "Some string";
  • Problem

    Returning string literals directly crashes the compiler.

  • Solution

    Put the string you want to return in to an array. If you use "static const" to declare the array the resulting code is either identical to, or in some cases BETTER THAN, the original version returning a string literal.

    pawn Code:
    static const
        szcHi[] = "hi";
    return szcHi;
2)
  • Code

    pawn Code:
    string = (a == 5) ? "is five" : "is not five";
  • Problem

    Strings can be concatenated by the compiler, for example this will compile as the single string "Hello World":

    pawn Code:
    string = "Hello" " " "World";
    However, the compiler doesn't recognise that ":" in a ternary operator can end a string so thinks it is an invalid part of the string. The result is many odd errors such as:

    pawn Code:
    <path>\errors.pwn(11) : error 001: expected token: "-string end-", but found "-identifier-"
    Pawn compiler 3.2.3664          Copyright (c) 1997-2006, ITB CompuPhase

    1 Error.
    I should note that this is MY fault, string concatenation is one of the modifications I made in the SA-MP version of the compiler.

  • Solution

    To fix this, just enclose the strings in brackets, which the commpiler DOES recognise as a valid string end. Note again that the does not affect the generated code in any way so there is no run-time cost associated with this fix.

    pawn Code:
    string = (a == 5) ? ("is five") : ("is not five");
3)
  • Code

    pawn Code:
    new gGlobalVariable = SomeFunction();
  • Problem

    Calling a function to initialise a global variable outside of a function will crash the compiler.

  • Solution

    Call the function to initialise the variable in "OnGameModeInit", "OnFilterScriptInit", or "main".

    4)

  • Code

    pawn Code:
    #emit SYSREQ.C fblockread
  • Problem

    When using "SYSREQ.C" to call a native function, the compiler will not add that native function to the list of used native functions stored in the .AMX file. If the function is not already there the compiler will crash.

  • Solution

    The simplest way to fix this crash is to add a function something like the following BEFORE the function in which "SYSREQ.C" is found:

    pawn Code:
    forward SYSREQ_FIX();

    public SYSREQ_FIX()
    {
        fblockread(File:0, "", 0);
    }
    It must come BEFORE the function begin written, not after, and not inside. This code will still crash the compiler if "fblockread" (or whatever other native function you are using) has not been previously used:

    pawn Code:
    MyFunc()
    {
        fblockread(File:0, "", 0);
        #emit SYSREQ.C fblockread
    }
5)
  • Code

    pawn Code:
    stock Function1()
    {
    }

    stock Function3()
    {
        #emit CONST.pri Function2
    }

    stock Function2()
    {
    }
  • Problem

    It seems that if a function uses "CONST.pri" to get the address of a function, and that function comes after other functions in the same file, and those functions are stock and not included, then "CONST.pri" will get the wrong value.

  • Solution

    I suspect what is happening here is that the compiler first compiles all the functions in a file, and from there gets the address of any functions used in "CONST.pri". THEN it removes any functions that are not used and shifts all later functions up in memory. This causes the address of the functions to change but any "CONST.pri" (or "CONST.alt") uses are not updated.

    The solution is simple - because as far as I can tell the problem only occurs within a file, just move any functions used in "CONST.pri" to near the top of the file.

    Note that I am not ENTRELY sure of the circumstances under which this happens yet (I only found it last night). When it happened the function whose address I was getting was AFTER the function in which the address was being got. That may have also contributed to the issue and thus simply moving the target function up should solve the issue.
6)
  • Code

    Unknown.

  • Problem

    The compiler has a line length limit of 512. Normaly if this limit is breached you will get a line length error, but it is possible when using macros to push over this limit without the compiler noticing and thus crash the compiler. I have no idea exactly what causes it though.

  • Solution

    Write shorter lines. Normally this is simpler than people think:

    pawn Code:
    format(str, sizeof (str),
        "A long string",
        other,
        parameters);
    That splits the function call over 4 lines, making each one of them shorter. However, that's only a solution to the normal too long problem that gives a compiler error. The bug is when the error doesn't happen and the compiler crashes instead.
7)
  • Code

    pawn Code:
    #emit CALL main
  • Problem

    You can't use the "CALL" assembly instruction with "#emit" - it seems to crash the compiler.

  • Solution

    If you can, just call the function normally:

    pawn Code:
    main();
    If you can't and need to use assembly use this code to get the return address and jump to the start manually:

    pawn Code:
    #emit LCTRL      6
    #emit ADD.C      28
    #emit PUSH.pri
    #emit CONST.pri main
    #emit SCTRL      6
  • 8 )

  • Code

    pawn Code:
    #include <a_samp>

    _:operator=(Taggg:a)
    {
        return _:a + 5;
    }

    main()
    {
        new
            a = d();
        printf("%d", a);
    }

    forward Taggg:d();

    Taggg:d()
    {
        return Taggg:5;
    }
  • Problem

    If you use a custom assignment operator (as "Float:" does) and forward a tag returning function AFTER it is first used instead of before, the compiler can think that the function is both not used and used, so generate code assuming that it exists but never include it and result in the wrong code being called (the example above will endlessly loop "main").

    More information can be found here: https://sampforum.blast.hk/showthread.php?tid=425970

  • Solution

    Place the tag returning function's "forward" above where it is first used (here "main"). Alternatively remove the "forward" all together, that will generate the correct code but with a warning.
9)
  • Code

    pawn Code:
    new gVar;

    stock SomeFunction()
    {
    #if defined main
        gVar = (gVar ? 0 : 1);
    #endif
        return 1;
    }

    main()
    {
        return SomeFunction() ? 0 : 1;
    }
  • Problem

    The code above will generate the first ternary operator on the SECOND compiler pass but not the first (the first time it doesn't yet know that "main" is defined later on). As a result, for some unknown reason, any other ternary operators in your mode will have the wrong code generated and cause a crash.

    More information can be found here: https://groups.******.com/forum/?fro...pt/1Cg2C8dn-0A

  • Solution

    Don't put ternary operators inside condition code generation that relies on "defined".
10)
  • Code

    pawn Code:
    new gVar;

    stock SomeFunction()
    {
        if (gSomeVar == SOMETHING)
        {
            print("In if.");
        }
        #emit LCTRL 6
        #emit STOR.pri gOtherVar
        return 1;
    }
  • Problem

    When "#emit" assembly comes directly after an "if" statement, the compiler incorrectly assigns the assembly INSIDE the "if" statement instead of after it. The code above will compile as if you wrote this:

    pawn Code:
    new gVar;

    stock SomeFunction()
    {
        if (gSomeVar == SOMETHING)
        {
            print("In if.");
            #emit LCTRL 6
            #emit STOR.pri gOtherVar
        }
        return 1;
    }
    It doesn't matter what the "if" check is, what is inside the braces, or what assembly you use.

  • Solution

    The simplest solution is to place extra empty braces between the end of the "if" statement and the assembly:

    pawn Code:
    new gSomeVar, gOtherVar;

    stock SomeFunction()
    {
        if (gSomeVar == SOMETHING)
        {
            print("In if.");
        }
        {}
        #emit LCTRL 6
        #emit STOR.pri gOtherVar
        return 1;
    }
    With decent compiler flags that will not produce any extra assembly.
11)
  • Code

    pawn Code:
    stock VaradicFunction(static_, args, ...)
    {
        new
            str[128];
        // Other code here.
        return str;
    }
  • Problem

    Returning a string from a varadic function causes a run-time crash (not a compiler crash).

  • Solution

    This code curtesy of Slice:

    pawn Code:
    stock VaradicFunction(static_, args, ...)
    {
        new
            str[128];
        // Other code here.
        #emit LOAD.S.pri 8
        #emit ADD.C 12
        #emit MOVE.alt
        #emit LCTRL 5
        #emit ADD
        #emit LOAD.I
        #emit STOR.S.pri 24 // 16 + (static_args * 4)
        return str;
    }
Note that the last assembly line must be adjusted for your function.

Credits to Y_Less..
Reply
#2

12)
  • Code
    Code:
    #if defined FILTERSCRIPT
    	public OnFilterScriptInit()
    	{
    	   	new idx = funcidx("OnPlayerCommandPerformed");
    	   	if(idx == -1)
    	   	{
    			#emit LOAD.S.alt  idx
    	   	}
    	}
    #else
    	public OnGameModeInit()
    	{
    		new idx = funcidx("OnPlayerCommandPerformed");
    	   	if(idx != -1)
    	   	{
    			#emit LOAD.S.alt  idx
    	   	}
    	}
    #endif
  • Problem

    You get this error "error 017: undefined symbol "idx" though there is no error.

    If you are using a gamemode (it will give rise to an error in OnFilterscriptInit), then the compiler gives that error for the line "#emit LOAD.S.alt idx" which is inside OnFilterScriptInit.The compiler isn't supposed to check OnFilterScriptInit because #if defined FILTERSCRIPT will fail.The compiler ignores the "new idx" because the #if failed but it doesn't ignore "#emit LOAD.S.alt idx".

    The generalized version of this bug is that #emit code when placed inside #if is always added irrespective of the falsity of the condition given to #if.

  • Solution:

    The solution here is to make idx a global variable and change your inline assembly so that it will work with global variable.
Reply
#3

13)
  • Code
    Code:
    goto abc; //No error
    #emit LOAD.pri a
    #emit LOAD.alt b
    #emit JSLESS abc //Error, undefined symbol abc
    
    abc:
    your code
  • Problem

    The compiler allows you to use goto with lables that are defined later (somewhere down the file after your goto statement). But unfortunately the compiler does not recognize labels that are defined later when you use #emit.
  • Solution:

    No idea!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)