Skip to content

Conversation

@grandixximo
Copy link

@grandixximo grandixximo commented Jan 31, 2026

Problem

IniFile::Find() returned a pointer to a static buffer, causing reentrancy bugs when multiple Find() calls were made before using the first result:

const char *a = inifile.Find("A", "SEC");  // Points to static buffer
const char *b = inifile.Find("B", "SEC");  // Overwrites the buffer!
// 'a' now points to corrupted data

The code had a comment acknowledging this issue:

// WTF, return a pointer to the middle of a local buffer?
// FIX: this is totally non-reentrant.
static char line[LINELEN + 2] = "";

Solution

Changed IniFile::Find() to return std::optional<std::string> instead of std::optional<const char*>. Each caller now owns their own copy of the result.

Before

const char *a = inifile.Find("A", "SEC");  // Pointer to shared buffer
const char *b = inifile.Find("B", "SEC");  // Buffer overwritten - 'a' invalid

After

auto a = inifile.Find("A", "SEC");  // Own copy as std::string
auto b = inifile.Find("B", "SEC");  // Separate copy - both valid
printf("%s\n", a->c_str());         // Still works!

Changes Made

Core API Changes

  • inifile.hh: Changed Find() signature to return std::optional<std::string>
  • inifile.cc: Removed static buffer, return std::string by value
  • emcIniFile.hh: Updated wrapper class to match new return type

Caller Updates (20 files)

All callers updated to use the new API:

  • Use s->c_str() when passing to C APIs requiring const char*
  • Use *s == "value" instead of strcmp() for string comparisons
  • Use s->find_first_of() instead of strchr() for character searches
  • Use s->length() instead of strlen()
  • Use s->empty() instead of checking s[0]

Migration Guide

String Output

// Before
if (auto s = inifile.Find("TAG", "SEC")) {
    printf("%s", *s);  // *s was const char*
}

// After
if (auto s = inifile.Find("TAG", "SEC")) {
    printf("%s", s->c_str());  // s is std::string, use .c_str()
}

String Comparison

// Before
if (auto s = inifile.Find("TAG", "SEC")) {
    if (strcmp(*s, "VALUE") == 0) { ... }
}

// After
if (auto s = inifile.Find("TAG", "SEC")) {
    if (*s == "VALUE") { ... }  // Cleaner!
}

Character Search

// Before
if (auto s = inifile.Find("COORDS", "TRAJ")) {
    if (strchr(*s, 'X')) { ... }
}

// After
if (auto s = inifile.Find("COORDS", "TRAJ")) {
    if (s->find_first_of("xX") != std::string::npos) { ... }
}

C API Compatibility

The legacy C function iniFind() maintains backward compatibility but still uses static storage:

extern "C" const char *iniFind(FILE *fp, const char *tag, const char *section)
{
    static std::string result_storage;  // Still non-reentrant!
    auto result = f.Find(tag, section);
    if (!result) return nullptr;
    result_storage = *result;
    return result_storage.c_str();
}

Important: C code calling iniFind() must still copy the result immediately if it needs to be preserved. This preserves the original C API behavior for backward compatibility.

Benefits

  • Thread-safe for C++ callers: Each call gets its own string copy
  • No buffer lifetime issues: Results remain valid regardless of subsequent calls
  • Cleaner code: Can use C++ string operations instead of C string functions
  • No functional changes: All updates are behavior-preserving
  • Backward compatible: C API maintains existing behavior

Build Status

✅ Compiles successfully with no warnings or errors

@BsAtHome
Copy link
Contributor

Why not fix the C API at the same time? It isn't used many places.

@grandixximo
Copy link
Author

Fixed the C API as well, both C and C++ APIs should now be reentrant/thread-safe.

@BsAtHome
Copy link
Contributor

To be pedantic...
You might want to add a [[deprecated]] to iniFind() if that works in c++ functions with C linkage. Otherwise I suggest to use __attribute__((deprecated)).

@grandixximo
Copy link
Author

No problem, testing some stuff on the 9d branch, will add deprecated ASAP.

@grandixximo
Copy link
Author

grandixximo commented Jan 31, 2026

Done, I just forced pushed, so it stays in two relatively clean commits, attribute((deprecated)) more in line with the codebase. Devs should get a warning on compilation if they try to use it

Luca Toniolo added 2 commits February 1, 2026 08:04
IniFile::Find() used a static buffer to return results, causing bugs
when multiple Find() calls were made before using the first result.

Change Find() to return std::optional<std::string> instead of
std::optional<const char*>. Each caller now owns their result copy.

Core changes:
- Remove static buffer from Find(), return std::string by value
- Update all callers to use s->c_str() where const char* is needed
- Use string methods (.empty(), .length(), ==) instead of C functions

C API compatibility maintained via static storage in iniFind() wrapper.

No functional changes - all updates are behavior-preserving.
Follow-up to the C++ IniFile::Find() reentrancy fix. The previous
commit changed Find() to return std::string by value, but the C API
wrapper iniFind() still used static storage, making it non-reentrant.

Add iniFindString() that takes a caller-provided buffer, making it
safe for concurrent use and multiple sequential calls. Reimplement
iniFind() as a deprecated wrapper around iniFindString().

Migrate all C callers to iniFindString():
- mb2hal: 11 call sites
- vfdb_vfd: 2 call sites
- vfs11_vfd: 4 call sites
- halcmd: 2 call sites

Also clean up dead commented-out code in mb2hal and halrmt.
@andypugh andypugh merged commit 9e74a8f into LinuxCNC:master Feb 1, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants