TMap::Contains() returns false for an existing key

Hello,
I am using a TMap with a custom key type and I might have found a bug.

In short:

  • I start with a TMap::Add() of three key-value pairs to the TMap, everything looks fine
  • I then TMap::Add() a fourth key-value pair
  • TMap::Contains() now returns false for the second key I added

Please note that this only happens for a few combinations of values and data types. The hash values of the 4 keys are all different.

I am attaching the two files needed to reproduce: a unit test file and the Build.cs (needed since I’m using PhysX types directly). Testing should be straightforward:

  • Create a new C++ basic code project (suggested name: FailTest. No starter content required)
  • Add the FailingTest.cpp and the FailTest.Build.cs files to the sources
  • Refresh the visual studio solution, recompile the code
  • Restart the editor and run the “Failing” unit test. An ensure() will trigger.

I’ve been testing this in both “DebugGame Editor” and “Development Editor” configurations for the UE 4.12.5, launcher version.
As a last note, slight changes to the code (including using FVector instead of PxVec3, or removing the void* from the struct) will change behavior, which is extremely puzzling to me.

Hey ,

I am not seeing the same behavior on my end. I can continue to see the second element through the ensure( ), add( ), and contains( ) function calls.

Have you tried running this outside of the unit test environment to see if you see the same result?

Hi, thanks for taking a look into this.

I managed to reproduce this behavior on 3 different PCs:

  • one is a Windows 8.1, UE 4.12.4, Visual Studio 2015 Update 2 machine

  • the other two are Windows 10, UE 4.12.5, VS 2015 Update 3 machines

I was not able to reproduce it by moving the code outside of the unreal engine’s unit testing framework. Even slight modifications of the code will cause the error to just disappear. Managing to get a minimal working example was not fun!

I am now attaching the full minimal project, would you kindly give this a second shot? For me reproducing the error is as simple as:

  • open the .uproject file in UE 4.12.5, launcher version with no plugins installed, wait for the editor to finish compiling the dll

  • go to the menu “Window->Developer Tools->Session Frontend”

  • select the editor instance, go to the automation tab, select and run the FailTest

and an “Ensure condition failed” message pops up in a message log window.

Alternatively, the same error happens on my three PCs if I generate the Visual studio 2015 files, build and run the project even in “DebugGame Editor” mode.

Thank you again for your time,

Hey ,

Thank you for submitting a bug report with such detailed repro steps. I have reproduced this issue and logged a report for it here: https://issues.unrealengine.com/issue/UE-33818. You can track the report’s status as the issue is reviewed by our development staff.

The bug was backlogged, but i’m randomly experiencing the same issue, outside of the test environment, and using int32 as keys.

Hey,

I know this is 2 years late, but I just stumbled across this after it being linked from another issue. I’m not sure why this bug report was never assigned to me (I am the owner of TMap and the Unreal containers) - but it looks to me like the problem here is due to padding within the struct:

struct FContactData
{
    float Radius;
    PxVec3 Point;
    PxVec3 Normal;
    // 4 bytes of uninitialised padding inserted by compiler here!
    void const * Pointer;

    bool operator==(FContactData const & Other) const
    {
        return Radius == Other.Radius
            && Point == Other.Point
            && Normal == Other.Normal
            && Pointer == Other.Pointer;
    }

    friend uint32 GetTypeHash(const FContactData& Offset)
    {
        // This CRC hits all bytes in the struct, including the uninitialised padding
        return FCrc::MemCrc32(&Offset, sizeof(FContactData)); 
    }
};

This is because the Pointer member needs to be at an offset which is a multiple of 8 (in a Win64 build), yet the sum of the members before is (effectively) 7 floats: 7*sizeof(float) == 28. So the compiler will insert 4 hidden bytes of padding before Pointer to ensure that it’s correctly aligned.

However, this padding is left uninitialised by the compiler, and isn’t being taken into account in the GetTypeHash() overload, which is just CRCing the entire memory footprint of the object, uninitialised padding included.

If you want to be able to CRC objects like this, you should manually insert the padding yourself and ensure it’s always initialised to 0. A good test for this is to do:

static_assert(sizeof(FContactData) == sizeof(float) + sizeof(PxVec3) + sizeof(PxVec3) + sizeof(void const *), "The memory footprint of FContactData is not covered by all its members");

Sorry for the delayed reply on this - hope this helps anyone else out who comes across this problem.

Steve

My padding suggestion above doesn’t cover your issue, so I think it is unrelated - I would suggest describing your repro steps in a new AnswerHub post, and linking to it from here.

Steve

I’m trying to reproduce it, but after 2 hours still noting, it’s pretty randomic.
In my case i have 2 TMap:
TMap (int32, FVector2D) and TMap (int32,int32).

The key is in common between the 2 (i mean that they always have the same amount of elements with the same keys), and add-remove are made always together.
Sometimes it happens that the vector map’s contains returns false even if the key is in, and the other map correctly states it has it.