Advertisement

Dictionary addon doesn't work with UInt values

Started by March 26, 2021 08:43 AM
2 comments, last by WitchLord 3 years, 8 months ago

Hello,

I stumbled upon a very weird bug today.

When using the standard script dictionary addon, you cannot store values as uints.

If you store it as an int it works fine, but as an uint it always returns 0.

I have attached the reproduction code here:

#include "../deps/angelscript/include/angelscript.h"
#include "../deps/angelscript/addon/scriptbuilder/scriptbuilder.h"
#include "../deps/angelscript/addon/scriptstdstring/scriptstdstring.h"
#include "../deps/angelscript/addon/scriptdictionary/scriptdictionary.h"
#include "../deps/angelscript/addon/scriptarray/scriptarray.h"
#include <iostream>

#define CHECK_RETURN(error) \
    if(r < 0) { std::cout << error << std::endl; return 0; }

static asIScriptEngine* engine = nullptr;

static void MessageCallback(const asSMessageInfo * msg, void* param)
{
    std::cout << msg->message << std::endl;
}

static const char* code = R"(
void Main()
{
	dictionary dict = GetDict();
    int64 intTest = int64(dict["test"]);
    uint64 uintTest = uint64(dict["test2"]);

    Print(formatInt(intTest));
    Print(formatUInt(uintTest));
}
)";

static void Print(const std::string& str)
{
    std::cout << str << std::endl;
}

static CScriptDictionary* GetDict()
{
    CScriptDictionary* dict = CScriptDictionary::Create(engine);
    int64_t testInt = 5;
    uint64_t testUint = 7;
    dict->Set("test", &testInt, asTYPEID_INT64);
    dict->Set("test2", &testUint, asTYPEID_UINT64);
    return dict;
}

int main()
{
	engine = asCreateScriptEngine();
    engine->SetMessageCallback(asFUNCTION(MessageCallback), nullptr, asCALL_CDECL);
    RegisterScriptArray(engine, true);
    RegisterStdString(engine);
    RegisterStdStringUtils(engine);
    RegisterScriptDictionary(engine);
    engine->RegisterGlobalFunction("void Print(const string&in msg)", asFUNCTION(Print), asCALL_CDECL);
    engine->RegisterGlobalFunction("dictionary@ GetDict()", asFUNCTION(GetDict), asCALL_CDECL);

	CScriptBuilder builder;
	int r = builder.StartNewModule(engine, "Module");
    CHECK_RETURN("Failed to start new module");
    asIScriptModule* mod = builder.GetModule();
    r = builder.AddSectionFromMemory("Main", code);
    CHECK_RETURN("Failed to add section");
    r = builder.BuildModule();
    CHECK_RETURN("Failed to build module");

    asIScriptContext* context = engine->CreateContext();
    asIScriptFunction* func = mod->GetFunctionByDecl("void Main()");
    
    r = context->Prepare(func);
    CHECK_RETURN("Failed to prepare context");
    r = context->Execute();
    CHECK_RETURN("Failed to execute");

    context->Unprepare();
    mod->Discard();
    engine->ShutDownAndRelease();

	return 0;
}

The output is:
5
0

None

Look into dictionary addon code and you'd have an answer quick ? Basically this:

// This overloaded method is implemented so that all integer and
// unsigned integers types will be stored in the dictionary as int64
// through implicit conversions. This simplifies the management of the
// numeric types when the script retrieves the stored value using a 
// different type.
void CScriptDictValue::Set(asIScriptEngine *engine, const asINT64 &value)

So dictionary stores all integer values as asINT64 - to make things simpler as mentioned in the comment. The problem here is that this function overload won't work with unsigned references, it would require another overload for unsigned types I think. So you can't use it this way:

uint64_t testUint = 7;
dict->Set("test2", testUint);

which is simpler and handles the type automatically for signed integers. What you need to do is to pass it the way you do, but set the type id to asINT64, not asUINT64 - otherwise there is mismatch when dictionary fetches the value and it looks for asINT64, but you explicitly set it to asUINT64, literally breaking its invariants.

uint64_t testUint = 7;
dict->Set("test2", &testUint, asTYPEID_INT64);

This will work and you can even confirm it's still unsigned range of values by doing:

uint64_t testUint = 0xFFFFFFFFFFFFFFFF;
dict->Set("test2", &testUint, asTYPEID_INT64);

and this should print in script 18446744073709551615

So it's not a bug per se, you used this slightly against the design - but I agree it could have the overload for unsigneds and you should not need to use the method where you pass pointer + typeid, as it's more error prone.


Where are we and when are we and who are we?
How many people in how many places at how many times?
Advertisement

I'll look into this.

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

This topic is closed to new replies.

Advertisement