Sign in to follow this  
_Vicious_

Assertion failure in as_configgroup.cpp

Recommended Posts

_Vicious_    330
Hello Andreas,

I hit the following assertion failure upon config group shutdown (v 2.2.2): [url="http://www.foopics.com/showfull/0d39350e85fa24ccd844c3d87c9d3257"]http://www.foopics.c...844c3d87c9d3257[/url]
It seems to be related to string factory method/function somehow. What this assertion failure might indicate?

Share this post


Link to post
Share on other sites
WitchLord    4677
It means that something didn't go as I had expected when writing the code. It is quite possible a bug in my code, though as it is in the ValidateNoUsage() method it is possible it doesn't affect your application if you compile the library in release mode.

Do you know how to reproduce the problem? I'll need to know this in order to debug it and fix the code.

Thanks,
Andreas

Share this post


Link to post
Share on other sites
WitchLord    4677
It never happens for me, and I always test in debug mode.

I'll need help to narrow it down. As it is now I have no idea what the problem might be.

Is it possible for you to create a small test that reproduce the problem?

Share this post


Link to post
Share on other sites
_Vicious_    330
On a side note, I've also hit an assertion failure in as_array.h:
asASSERT(index < length);

in asCArray<T>::operator []

The values for index and length are 388 and 76 respectively.

This is the faulty line:
return registeredGlobalFuncs[id];

in asCScriptEngine::GetGlobalFunctionByDecl

Share this post


Link to post
Share on other sites
_Vicious_    330
I think the reason for the first error was that I was trying to do something unexpected for the AS :)
Basically, I wanted to be able to call function pointer in this manner:
[code]
other.pain( other, ent, 10.9, 10.0 );
[/code]

for objects that had the .pain callback set in the C code (in the game not all of the game objects are controlled through scripting). For that, I created a proxy function:
[code]
void G_CallPain( edict_t *ent, edict_t *attacker, float kick, float damage )
{
if( ent->pain )
ent->pain( ent, attacker, kick, damage );
else if( ent->scriptSpawned && ent->asPainFunc )
G_asCallMapEntityPain( ent, attacker, kick, damage );
}
[/code]
, registered it in AS as a global function and then set the asPainFunc function pointer in AddRef method like this:
[code]
if( !obj->scriptSpawned ) {
obj->asPainFunc = asEntityCallPainFuncPtr;
[/code]

The key thing is that asPainFunc also holds the .pain function pointer set from the script.

Share this post


Link to post
Share on other sites
WitchLord    4677
This should work.

I'll make some new tests with this info and see if it allows me to reproduce the problem.



How is the G_CallPain function registered? It is a global function in the application but you seem to call it as if it was an object method in the script.

Share this post


Link to post
Share on other sites
WitchLord    4677
I'm still not able to reproduce the problem. I wrote the following test in the hopes that a funcdef in a dynamic config group may have been the cause.

[code]

{
asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);

engine->BeginConfigGroup("gr");
engine->RegisterObjectType("type", 0, asOBJ_REF);
engine->RegisterObjectBehaviour("type", asBEHAVE_FACTORY, "type @f()", asFUNCTION(Type::Factory), asCALL_CDECL);
engine->RegisterObjectBehaviour("type", asBEHAVE_ADDREF, "void f()", asMETHOD(Type,AddRef), asCALL_THISCALL);
engine->RegisterObjectBehaviour("type", asBEHAVE_RELEASE, "void f()", asMETHOD(Type,Release), asCALL_THISCALL);
engine->RegisterFuncdef("void fun(type @, int)");
engine->RegisterObjectProperty("type", "fun @callback", asOFFSET(Type,callback));
engine->EndConfigGroup();

asIScriptModule *mod = engine->GetModule("mod", asGM_ALWAYS_CREATE);
mod->AddScriptSection("s",
"void func(type @, int) {} \n"
"void main() \n"
"{ \n"
" type t; \n"
" @t.callback = func; \n"
" t.callback(t, 1); \n"
"} \n");

int r = mod->Build();
if( r < 0 )
TEST_FAILED;

r = ExecuteString(engine, "main()", mod);
if( r != asEXECUTION_FINISHED )
TEST_FAILED;

engine->Release();
}
[/code]

The test passed without any problems, so I'm obviously missing something.

Share this post


Link to post
Share on other sites
_Vicious_    330
Yes, G_CallPain is a global function but I'm actually calling it as an object method there. Or even, I'm not calling it at all, but rather assign a pointer holding object's 'pain' funcdef property to it. After all, a function pointer is a function pointer, isn't it? ;)

Share this post


Link to post
Share on other sites
WitchLord    4677
I tried assigning a registered function to the function pointer and call that instead of the script function. This didn't work as there was a bug in the VM when executing the asBC_CallPtr instruction in that it failed to recognize that the function was a registered function instead of a script function.

However, after fixing that bug, in revision 1117, I'm still not able to reproduce the problem you have. :(

Share this post


Link to post
Share on other sites
_Vicious_    330
Not sure what's changed but now I'm getting a crash in asCScriptEngine::CallObjectMethod:
[CODE]
void (asCSimpleDummy::*f)() = p.mthd;
obj = (void*)(size_t(obj) + i->baseOffset);
[b](((asCSimpleDummy*)obj)->*f)();[/b]
[/CODE]

Unwinding the stack a bit:
[CODE]
// Release previous object held by destination pointer
if( *d != 0 )
[b]engine->CallObjectMethod(*d, beh->release);[/b]
// Increase ref counter of wanted object
if( s != 0 )
engine->CallObjectMethod(s, beh->addref);
[/CODE]

The crash is triggered by setting object's function pointers to global functions in the AddReference behavior:
[CODE]
obj->asThinkFunc = asEntityCallThinkFuncPtr;
obj->asTouchFunc = asEntityCallTouchFuncPtr;
obj->asUseFunc = asEntityCallUseFuncPtr;
obj->asStopFunc = asEntityCallStopFuncPtr;
obj->asPainFunc = asEntityCallPainFuncPtr;
obj->asDieFunc = asEntityCallDieFuncPtr;
[/CODE]

All of those asEntityCall*FuncPtr are of type void * and hold return values of asGetGlobalFunctionByDecl.

Share this post


Link to post
Share on other sites
_Vicious_    330
Ah, if I change this part of the code:
[CODE]
obj->asThinkFunc = asEntityCallThinkFuncPtr;
obj->asTouchFunc = asEntityCallTouchFuncPtr;
obj->asUseFunc = asEntityCallUseFuncPtr;
obj->asStopFunc = asEntityCallStopFuncPtr;
obj->asPainFunc = asEntityCallPainFuncPtr;
obj->asDieFunc = asEntityCallDieFuncPtr;
[/CODE]

to

[CODE]
// obj->asThinkFunc = asEntityCallThinkFuncPtr;
// obj->asTouchFunc = asEntityCallTouchFuncPtr;
obj->asUseFunc = asEntityCallUseFuncPtr;
// obj->asStopFunc = asEntityCallStopFuncPtr;
// obj->asPainFunc = asEntityCallPainFuncPtr;
// obj->asDieFunc = asEntityCallDieFuncPtr;
[/CODE]

I then hit the assertion from the first post. I'm quite sure the use function is never called though.

Share this post


Link to post
Share on other sites
WitchLord    4677
That's the second time you mentioned setting the function pointers in the AddRef behaviour. What exactly do you mean with this?

The method you register with asBEHAVE_ADDREF is not supposed to change anything but the internal reference counter of the object.

The crash you're getting looks like it is caused by a previous invalid pointer cast, e.g. you do a C style cast (or reinterpret_cast) where a static_cast/dynamic_cast is needed to re-evaluate the pointer.

Try setting a break point in the function where you're setting the function pointers and verify that the object pointer is really pointing to what you think it is. I believe you'll find that it is pointing to something else. If I'm right you'll need to track down where the object pointer was misinterpreted as something it was not and fix that.

Share this post


Link to post
Share on other sites
_Vicious_    330
Hm... In release build program execution simply stops with 'Pure virtual function call' message. I'll try to investigate this further.

Ok, nvm, fixed this. The assertion failure is still there though. It seems to hit several functions and behavoirs, involving several classes.

I now believe this is somehow related to setting a funcdef property in the script in this manner:

[code]
void trigger_capture_area_think( cEntity @ent )
{
}

void trigger_capture_area( cEntity @ent )
{
@ent.think = trigger_capture_area_think;
}
[/code]

when both 'trigger_capture_area' and the think function are called from the application.

Share this post


Link to post
Share on other sites
_Vicious_    330
In the aforementioned case, in ValidateNoUsage, asCObjectType *type is going to reference the 'cEntity' class and 'asCScriptFunction *func', trigger_capture_area_think.

Maybe trigger_capture_area_think isn't removed because it is still being referenced by an object through a funcdef?

Share this post


Link to post
Share on other sites
WitchLord    4677
Yes! Finally I was able to reproduce the problem with the following code:

[code]

{
asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);

engine->BeginConfigGroup("gr");
engine->RegisterObjectType("type", 0, asOBJ_REF);
engine->RegisterObjectBehaviour("type", asBEHAVE_ADDREF, "void f()", asMETHOD(Type,AddRef), asCALL_THISCALL);
engine->RegisterObjectBehaviour("type", asBEHAVE_RELEASE, "void f()", asMETHOD(Type,Release), asCALL_THISCALL);
engine->RegisterFuncdef("void fun(type @)");
engine->RegisterObjectProperty("type", "fun @callback", asOFFSET(Type,callback));
engine->EndConfigGroup();

asIScriptModule *mod = engine->GetModule("mod", asGM_ALWAYS_CREATE);
mod->AddScriptSection("s",
"void func(type @) {} \n"
"void main(type @t) \n"
"{ \n"
" @t.callback = func; \n"
"} \n");

int r = mod->Build();
if( r < 0 )
TEST_FAILED;

Type *t = new Type();

// Call the function that sets the callback on the object
asIScriptFunction *m = mod->GetFunctionByName("main");
asIScriptContext *ctx = engine->CreateContext();
ctx->Prepare(m);
ctx->SetArgObject(0, t);
r = ctx->Execute();
if( r != asEXECUTION_FINISHED )
TEST_FAILED;
ctx->Release();

// Release the engine, while the object holding the callback is still alive
engine->Release();

t->Release();
}
[/code]

The problem happens because the cEntity object is still alive when the engine is released. If you release the cEntity object before releasing the engine then you should be able to avoid the problem in your game.

I'll investigate what needs to be done to fix the bug in the library.


Thanks for helping me figure out how to reproduce the problem.

Share this post


Link to post
Share on other sites
_Vicious_    330
Yeah, I had the same conclusion in my mind. Thanks for being so patient with me [img]http://public.gamedev.net//public/style_emoticons/default/smile.png[/img]

However, releasing objects prior to AS shutdown is somewhat problematic due to objects cross-referencing each other, I'm pretty sure that'd totally fuck up AS reference counting.

Share this post


Link to post
Share on other sites
WitchLord    4677
I've found a solution in AngelScript. You can get it in revision 1134.

As the script function is still kept alive by the application when the engine is released, the engine will report this with an error message, and will destroy the internals of the script function. It won't destroy the actual function object, so the application can still call the Release() method afterwards without crashing.

It is not correct to release the engine before all other objects that use it has been released. I've tried very hard to avoid memory leaks should this happen, but I cannot guarantee that I've covered all possible scenarios.

Share this post


Link to post
Share on other sites
WitchLord    4677
Just releasing the objects that the application no longer will use should be enough. If AS is not currently referencing the object, then the object will be destroyed, otherwise it will be destroyed when AS releases its reference too.

As each reference counted object is responsible for destroying itself when the refCount reaches 0, the modules that references the objects doesn't need to know about whom might still be referencing the object.

The problem arises when objects may be referencing each other in a circular manner, preventing the refCount from ever reaching 0. That is why AngelScript has the garbage collector, to detect and destroy such occurances.

Perhaps you could explain a bit more about how your application is structured? So I can understand why it is not safe to simply release the objects before releasing the engine.

Share this post


Link to post
Share on other sites
_Vicious_    330
In our game AS is the only module that uses reference counting on objects. Basically all objects are stored in a huge preallocated array which is then freed at shutdown. Obviously we can't free that array prior to shutting down the AS engine otherwise it'll just crash upon referencing a freed memory region. Manipulating the reference counters before releasing the AS engine might be a possibility but that doesn't sound too safe.

In your test case you didn't release the test object in the application either and this is why the problem could be reproduced. How'd you modify the test case to avoid hitting the problem?

Share this post


Link to post
Share on other sites
WitchLord    4677
In my test I would simply release the 't' variable before I release the engine.

In your case it would probably make more sense to just release the callbacks, before releasing the engine. The actual objects that are in the preallocated array can be kept alive.

Using my test as example, the cleanup would look like this:

[code]
if( t->callback )
{
// Release the callback and any other objects obtained from the script engine before releasing the engine
t->callback->Release();
t->callback = 0;
}

// Release the engine
engine->Release();

// Release the application owned object last
t->Release();
[/code]
However, if it is too much work then you should be able to leave your code as it is now with the fix I made in the library. The engine will complain about the callbacks not being released, but it shouldn't cause any problems for you. Even memory leaks at this moment probably isn't too much to worry about since the application is shutting down anyway, right?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this