🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Error when assigning script object to dictionary

Started by
3 comments, last by WitchLord 5 years, 7 months ago

Hi,

I got these strange errors in my application:


[SCRIPT] [ERROR]  (0, 0) : Object {245683}. GC cannot destroy an object of type '�t|' as it doesn't know how many references to there are.
[SCRIPT] [ERROR]  (0, 0) : Object {245593}. GC cannot destroy an object of type '�t|' as it doesn't know how many references to there are.
[SCRIPT] [ERROR]  (0, 0) : Object {245677}. GC cannot destroy an object of type '�t|' as it doesn't know how many references to there are.

The type string is always different. And Valgrind says this:


==29943== Invalid read of size 4
==29943==    at 0x5188E00: asCAtomic::get() const (as_atomic.cpp:52)
==29943==    by 0x51A1761: asCScriptEngine::CallObjectMethodRetInt(void*, int) const (as_scriptengine.cpp:4203)
==29943==    by 0x5192A71: asCGarbageCollector::DestroyOldGarbage() (as_gc.cpp:564)
==29943==    by 0x519370D: asCGarbageCollector::GarbageCollect(unsigned int, unsigned int) (as_gc.cpp:238)
==29943==    by 0x519DD03: asCScriptEngine::GarbageCollect(unsigned int, unsigned int) (as_scriptengine.cpp:4636)
==29943==    by 0x5190261: asCContext::Execute() (as_context.cpp:1356)
==29943==    by 0x4FDF9A4: RunScriptMethod<void> (ScriptExecutor.h:196)
==29943==    by 0x4FDF9A4: Leviathan::ScriptSystemWrapper::CreateAndDestroyNodes() (ScriptSystemWrapper.cpp:134)
==29943==    by 0x4FD2D28: Leviathan::GameWorld::HandleAddedAndDeleted() (GameWorld.cpp:593)
==29943==    by 0x4FF09A5: Leviathan::StandardWorld::HandleAddedAndDeleted() (StandardWorld.cpp:690)
==29943==    by 0x4FD87F3: Leviathan::GameWorld::Tick(int) (GameWorld.cpp:528)
==29943==    by 0x4FA1F1E: Leviathan::Engine::Tick() (Engine.cpp:895)
==29943==    by 0x507FB0F: Leviathan::LeviathanApplication::RunMessageLoop() (Application.cpp:137)
==29943==  Address 0x9e2a2c40 is 16 bytes inside a block of size 148 free'd
==29943==    at 0x4C2FDAC: free (vg_replace_malloc.c:530)
==29943==    by 0x51AE63A: asCScriptObject::Release() const (as_scriptobject.cpp:647)
==29943==    by 0x51A1120: asCScriptEngine::CallObjectMethod(void*, asSSystemFunctionInterface*, asCScriptFunction*) const (as_scriptengine.cpp:4050)
==29943==    by 0x518FE03: asCContext::ExecuteNext() (as_context.cpp:2813)
==29943==    by 0x519011C: asCContext::Execute() (as_context.cpp:1324)
==29943==    by 0x50296FC: Leviathan::ScriptRunResult<int> Leviathan::ScriptExecutor::RunScriptMethod<int, Leviathan::GenericEvent*&>(Leviathan::ScriptRunningSetup&, asIScriptFunction*, void*, Leviathan::GenericEvent*&) (ScriptExecutor.h:196)
==29943==    by 0x502BF2A: non-virtual thunk to Leviathan::Script::EventListener::OnGenericEvent(Leviathan::GenericEvent*) (new_allocator.h:125)
==29943==    by 0x50724CC: Leviathan::EventHandler::CallEvent(Leviathan::GenericEvent*) (EventHandler.cpp:65)
==29943==    by 0x50608DA: CallEvent (EventHandler.h:38)
==29943==    by 0x50608DA: Leviathan::GUI::View::OnProcessMessageReceived(scoped_refptr<CefBrowser>, cef_process_id_t, scoped_refptr<CefProcessMessage>) (GuiView.cpp:860)
==29943==    by 0x51328D6: (anonymous namespace)::client_on_process_message_received(_cef_client_t*, _cef_browser_t*, cef_process_id_t, _cef_process_message_t*) (client_cpptoc.cc:264)
==29943==    by 0x77EC397: ??? (in /home/hhyyrylainen/Projects/thrive/build/bin/lib/libcef.so)
==29943==    by 0x93A7639: ??? (in /home/hhyyrylainen/Projects/thrive/build/bin/lib/libcef.so)
==29943==  Block was alloc'd at
==29943==    at 0x4C2EBAB: malloc (vg_replace_malloc.c:299)
==29943==    by 0x518FA79: asCContext::ExecuteNext() (as_context.cpp:2710)
==29943==    by 0x519011C: asCContext::Execute() (as_context.cpp:1324)
==29943==    by 0x50296FC: Leviathan::ScriptRunResult<int> Leviathan::ScriptExecutor::RunScriptMethod<int, Leviathan::GenericEvent*&>(Leviathan::ScriptRunningSetup&, asIScriptFunction*, void*, Leviathan::GenericEvent*&) (ScriptExecutor.h:196)
==29943==    by 0x502BF2A: non-virtual thunk to Leviathan::Script::EventListener::OnGenericEvent(Leviathan::GenericEvent*) (new_allocator.h:125)
==29943==    by 0x50724CC: Leviathan::EventHandler::CallEvent(Leviathan::GenericEvent*) (EventHandler.cpp:65)
==29943==    by 0x50608DA: CallEvent (EventHandler.h:38)
==29943==    by 0x50608DA: Leviathan::GUI::View::OnProcessMessageReceived(scoped_refptr<CefBrowser>, cef_process_id_t, scoped_refptr<CefProcessMessage>) (GuiView.cpp:860)
==29943==    by 0x51328D6: (anonymous namespace)::client_on_process_message_received(_cef_client_t*, _cef_browser_t*, cef_process_id_t, _cef_process_message_t*) (client_cpptoc.cc:264)
==29943==    by 0x77EC397: ??? (in /home/hhyyrylainen/Projects/thrive/build/bin/lib/libcef.so)
==29943==    by 0x93A7639: ??? (in /home/hhyyrylainen/Projects/thrive/build/bin/lib/libcef.so)
==29943==    by 0x93A747D: ??? (in /home/hhyyrylainen/Projects/thrive/build/bin/lib/libcef.so)
==29943==    by 0x93A6AA9: ??? (in /home/hhyyrylainen/Projects/thrive/build/bin/lib/libcef.so)

After looking for a minimal example that would cause an issue here, I think I got it (this doesn't lead to the same error, though):


class Organelle{
}

class PlacedOrganelle{

    PlacedOrganelle(Organelle@ organelle)
    {
        @this.organelle = organelle;
    }

    Organelle@ organelle;
}

void RunTest()
{
    PlacedOrganelle@ organelle = PlacedOrganelle(Organelle());

    dictionary data;
	
  	// This alternative works
    // @data["organelle"] = organelle;
  
    // This causes errors
    data["organelle"] = organelle;

    PlacedOrganelle@ organelle2 = cast<PlacedOrganelle>(data["organelle"]);
}

The error that I get with that is:


[SCRIPT] [ERROR]  (0, 0) : Failed in call to function 'CreateScriptObject' (Code: asNO_FUNCTION, -6)

And it goes away if I add "@" to the assignment into the dictionary. This same thing also fixes my full application so I think this is the same error in both even if the behavior is different.

Tested with revision 2547. I tried updating to see if there was a fix (I'd like to get a compile error with the incorrect code), but revision 2557 fails to compile with (this error repeats quite a few times):


In file included from /home/hhyyrylainen/Projects/Leviathan/build/ThirdParty/include/add_on/scriptmath/scriptmathcomplex.h:6,
                 from /home/hhyyrylainen/Projects/Leviathan/build/ThirdParty/include/add_on/scriptmath/scriptmathcomplex.cpp:5:
/home/hhyyrylainen/Projects/Leviathan/build/ThirdParty/include/add_on/scriptmath/scriptmathcomplex.cpp: Funktio ”void RegisterScriptMathComplex_Native(asIScriptEngine*)”:
/home/hhyyrylainen/Projects/Leviathan/build/ThirdParty/include/angelscript.h:421:62: virhe: cast from ”float*” to ”int” loses precision [-fpermissive]
 #define asOFFSET(s,m) ((int)(&reinterpret_cast<s*>(100000)->m)-100000)
                                                              ^
/home/hhyyrylainen/Projects/Leviathan/build/ThirdParty/include/add_on/scriptmath/scriptmathcomplex.cpp:177:59: huom: in expansion of macro ”asOFFSET”
  r = engine->RegisterObjectProperty("complex", "float r", asOFFSET(Complex, r)); assert( r >= 0 );
                                                           ^~~~~~~~
/home/hhyyrylainen/Projects/Leviathan/build/ThirdParty/include/angelscript.h:421:62: virhe: cast from ”float*” to ”int” loses precision [-fpermissive]
 #define asOFFSET(s,m) ((int)(&reinterpret_cast<s*>(100000)->m)-100000)
                                                              ^
/home/hhyyrylainen/Projects/Leviathan/build/ThirdParty/include/add_on/scriptmath/scriptmathcomplex.cpp:178:59: huom: in expansion of macro ”asOFFSET”
  r = engine->RegisterObjectProperty("complex", "float i", asOFFSET(Complex, i)); assert( r >= 0 );
                                                           ^~~~~~~~

I'm using "gcc (GCC) 8.2.1 20181011 (Red Hat 8.2.1-4)"

Advertisement

Thanks. I'll look into this. However from the evidence you showed I don't think you've managed to reproduce your original problem.

In your original problem it appears that you have a case of objects being destroyed too early, i.e. Release() called on pointers not owned by the code that is calling the method. This leads to the invalid reads that Valgrind reports. Changing the script to use a handle assign instead of value assign is probably just camoflaging the error rather than actually fixing it.

In your minimal code you've identified a runtime problem that I'll need to look into. It can probably not be caught at compile time, but should at least produce a proper script exception showing that the object doesn't have a default constructor.

The problem with asOFFSET is caused.by the latest change. I'll make an additional change to make sure the code works on gnuc again.

I'll reply when I've made the changes.

Regards,

Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

I ran my full code again with valgrind (with the added "@") and I'm no longer getting any errors related to the scripts (just some other stuff like uninitialized values used deep in other libraries).

So I think that also fixes my full code as I don't get any messages on shutdown and valgrind is quiet after I trigger that code.

My full code is here if you want to have a look: https://github.com/Revolutionary-Games/Thrive/blob/fix_everything/scripts/microbe_stage/microbe_editor/microbe_editor.as#L296

You are correct that my example code can be fixed by adding a default constructor for PlacedOrganelle. I tried more accurately following what my actual code looks like like this:


class Organelle{

    Organelle(const string &in name, int cost)
    {
        this.cost = cost;
        this.name = name;
    }
    
    int cost;
    string name;    
}

class PlacedOrganelle{

    // Default constructor doesn't help
    // PlacedOrganelle(){}

    PlacedOrganelle(Organelle@ organelle, int q, int r, int rotation)
    {
        @this._organelle = organelle;
        this.q = q;
        this.r = r;
        this.rotation = rotation;
    }

    PlacedOrganelle(PlacedOrganelle@ typefromother, int q, int r, int rotation)
    {
        @this._organelle = typefromother._organelle;
        this.q = q;
        this.r = r;
        this.rotation = rotation;
    }

    PlacedOrganelle(PlacedOrganelle@ other)
    {
        @this._organelle = other._organelle;
        this.q = other.q;
        this.r = other.r;
        this.rotation = other.rotation;

        // _commonConstructor();
    }

    ~PlacedOrganelle()
    {
    }

    int q;
    int r;
    int rotation;

    const Organelle@ organelle {
        get const{
            return _organelle;
        }
    }

    private Organelle@ _organelle;
}

void RunTest()
{
    PlacedOrganelle@ organelle = PlacedOrganelle(Organelle("cytoplasm", 10), 1, 1, 0);

    dictionary data;

    data["organelle"] = organelle;

    PlacedOrganelle@ organelle2 = cast<PlacedOrganelle>(data["organelle"]);
}

And I now get (even when I add a default constructor for PlacedOrganelle) a segmentation fault:


Program received signal SIGSEGV, Segmentation fault.
0x00000000008db160 in ?? ()

0  in ??
1  in asCScriptEngine::CallObjectMethod(void*, asSSystemFunctionInterface*, asCScriptFunction*) const of /home/hhyyrylainen/Projects/Leviathan/ThirdParty/angelscript/sdk/angelscript/source/as_scriptengine.cpp:4024
2  in asCContext::ExecuteNext of /home/hhyyrylainen/Projects/Leviathan/ThirdParty/angelscript/sdk/angelscript/source/as_context.cpp:2813
3  in asCContext::Execute() of /home/hhyyrylainen/Projects/Leviathan/ThirdParty/angelscript/sdk/angelscript/source/as_context.cpp:1324
4  in Leviathan::ScriptExecutor::RunScript<void> of /home/hhyyrylainen/Projects/Leviathan/Engine/Script/ScriptExecutor.h:802
5  in Leviathan::ScriptExecutor::RunScript<void> of /home/hhyyrylainen/Projects/Leviathan/Engine/Script/ScriptExecutor.h:86
6  in ____C_A_T_C_H____T_E_S_T____34 of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/TestFiles/Script.cpp:791
7  in Catch::TestCase::invoke of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:10910
8  in Catch::RunContext::invokeActiveTestCase() of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:9774
9  in Catch::RunContext::runCurrentTest(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:9748
10 in Catch::RunContext::runTest(Catch::TestCase const&) of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:9524
11 in Catch::(anonymous namespace)::runTests of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:10073
12 in Catch::Session::runInternal() of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:10266
13 in Catch::Session::run of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:10223
14 in Catch::Session::run<char> of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:13744
15 in main of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:13744
16 in __libc_start_main of /lib64/libc.so.6
17 in _start of /home/hhyyrylainen/Projects/Leviathan/LeviathanTest/catch/catch.hpp:14069

I was able to extract this callstack with gdb from the asContext:


	> /home/hhyyrylainen/Projects/Leviathan/build/bin/Data/Scripts/tests/AnonymousDelegateLeak.as:72 void RunTest()

Line 72 is the last line of the file and the closing } for function RunTest so it crashes when exiting the function, I presume.

 

Thanks for the code snippet for reproducing the problem. I've identified the cause.

The copy constructor in your PlacedOrganelle script class is taking the other object by handle rather than by reference. Unfortunately there is a bug in the library that doesn't properly treat this when the asIScriptEngine::CreateScriptObjectCopy is used. This causes the call to the copy constructor to release the handle without the refcount first being incremented, thus leading to the object being destroyed too early as I mentioned earlier.

The fix for the bug is in as_scriptobject.cpp line 166. Change the call from SetArgAddress to SetArgObject, and it will work. I'll have this fix checked in as soon as I can.

Alternatively you can change your script to use & for the copy constructor, i.e. 


 PlacedOrganelle(PlacedOrganelle& other)

 

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement