Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved String Literal may be garbage collected when using IL2CPP

Discussion in 'Scripting' started by Alan-Liu, Nov 6, 2021.

  1. Alan-Liu

    Alan-Liu

    Joined:
    Jan 23, 2014
    Posts:
    390
    I tried to make a simple project to reproduce the issue, but it took minutes to hours to reproduce the issue unsteadily. I thought it's insufficent to just submit a bug report.
    So, I decided to post the details here first.

    The following code is the simplification of our code:
    Code (CSharp):
    1. Entity ParseChildFile(string file)
    2. {
    3.     Entity entity = null;
    4.  
    5.     if (file.StartsWith("AAA"))
    6.     {
    7.         // Do something
    8.     }
    9.     else if (file.StartsWith("BBB"))
    10.     {
    11.         // Do something
    12.      
    13.         file = file.Replace(".json", "/trigger_list.txt");
    14.      
    15.         // Do something
    16.     }
    17.     // ...
    18.     else
    19.     {
    20.         Debug.LogError("Invalid file: " + file);
    21.     }
    22.  
    23.     return entity;
    24. }
    The method can be called from multiple threads simultaneously, and it seems that the content of the string literal can become wrong(eg. "AAA" or "/trigger_list.txt" become another runtime created string).

    After some investigation, I found string literals are created when the method is the first time to be executed by the following code:
    Code (CSharp):
    1. void il2cpp::vm::MetadataCache::IntializeMethodMetadataRange(uint32_t start, uint32_t count, const il2cpp::utils::dynamic_array<Il2CppMetadataUsage>& expectedUsages, bool throwOnError) IL2CPP_DISABLE_TSAN
    2. {
    3.     for (uint32_t i = 0; i < count; i++)
    4.     {
    5.         // ...
    6.  
    7.         Il2CppMetadataUsage usage = GetEncodedIndexType(encodedSourceIndex);
    8.         if (IsMatchingUsage(usage, expectedUsages))
    9.         {
    10.             uint32_t decodedIndex = GetDecodedMethodIndex(encodedSourceIndex);
    11.             switch (usage)
    12.             {
    13.                 // ...
    14.              
    15.                 case kIl2CppMetadataUsageStringLiteral:
    16.                     *s_Il2CppMetadataRegistration->metadataUsages[destinationIndex] = GetStringLiteralFromIndex(decodedIndex);
    17.                     break;
    18.                 default:
    19.                     IL2CPP_NOT_IMPLEMENTED(il2cpp::vm::MetadataCache::InitializeMethodMetadata);
    20.                     break;
    21.             }
    22.         }
    23.     }
    24. }
    25.  
    26. Il2CppString* il2cpp::vm::MetadataCache::GetStringLiteralFromIndex(StringLiteralIndex index)
    27. {
    28.     if (index == kStringLiteralIndexInvalid)
    29.         return NULL;
    30.     IL2CPP_ASSERT(index >= 0 && static_cast<uint32_t>(index) < s_GlobalMetadataHeader->stringLiteralCount / sizeof(Il2CppStringLiteral) && "Invalid string literal index ");
    31.     if (s_StringLiteralTable[index])
    32.         return s_StringLiteralTable[index];
    33.     const Il2CppStringLiteral* stringLiteral = (const Il2CppStringLiteral*)((const char*)s_GlobalMetadata + s_GlobalMetadataHeader->stringLiteralOffset) + index;
    34.     s_StringLiteralTable[index] = String::NewLen((const char*)s_GlobalMetadata + s_GlobalMetadataHeader->stringLiteralDataOffset + stringLiteral->dataIndex, stringLiteral->length);
    35.     il2cpp::gc::GarbageCollector::SetWriteBarrier((void**)s_StringLiteralTable + index);
    36.     return s_StringLiteralTable[index];
    37. }
    38.  
    Provided that the method ParseChildFile hasn't been executed before, and there are two threads to execute the method now.
    Thread 1 executes to the end of GetStringLiteralFromIndex and will return the string it created,
    Thread 2 also executes to the end of GetStringLiteralFromIndex and will return the string it created, and s_StringLiteralTable stores the string which Thread 2 created.
    Then, two threads both return to IntializeMethodMetadataRange and assign the string to metadataUsages.
    If it happens that metadataUsages stores the string returned by Thread 1, things will become wrong.
    Because the string returned by Thread 1 is not referened by s_StringLiteralTable, and will be garbage collected later.

    I encountered this issue in Unity 2017.4.40, and the reproducible project is built using 2019.4.32 (Prepared to submit the bug report).
     
    Last edited: Nov 7, 2021
    PraetorBlue likes this.
  2. Alan-Liu

    Alan-Liu

    Joined:
    Jan 23, 2014
    Posts:
    390
  3. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,921
    PraetorBlue likes this.
  4. Alan-Liu

    Alan-Liu

    Joined:
    Jan 23, 2014
    Posts:
    390
    Thank you for replying.
    The case number is 1379139.
     
    JoshPeterson likes this.