Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

weird 'missing_key' bug since v4.0.3+ #1638

Open
hs-vc opened this issue Mar 6, 2025 · 5 comments
Open

weird 'missing_key' bug since v4.0.3+ #1638

hs-vc opened this issue Mar 6, 2025 · 5 comments

Comments

@hs-vc
Copy link

hs-vc commented Mar 6, 2025

Since Glaze version 4.0.3, the following minimal reproducible example produces an unexpected missing_key runtime error when parsing JSON.

This bug seems unusual because making seemingly unrelated modifications (e.g., small changes to the project structure) can prevent reproducing the issue.

src/main.cpp:

#include <glaze/glaze.hpp>

namespace {
    struct tmp_struct {
        bool success;
    };

    void parse_string(const std::string &m) {
        tmp_struct response{};

        auto e = glz::read<glz::opts{
            .error_on_unknown_keys = false,
            .error_on_missing_keys = true
        }>(response, m);

        if (e) {
            throw std::runtime_error(glz::format_error(e));
        };
    }
}


int main() {
    parse_string(R"({"success":true})");
    return 0;
}

src/unused.cpp:

#include <glaze/glaze.hpp>

namespace {
    struct tmp_struct {
        int retCode;
    };

    auto unused_function() {
        tmp_struct response{};
        return glz::read<glz::opts{
            .error_on_unknown_keys = false,
            .error_on_missing_keys = true
        }>(response, "");
    }
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(glaze_test)

set(CMAKE_CXX_STANDARD 23)

include(FetchContent)

FetchContent_Declare(
        glaze
        GIT_REPOSITORY https://github.com/stephenberry/glaze.git
        GIT_TAG v5.0.0  # any version since 4.0.3 should produce same result
        GIT_SHALLOW TRUE
)

FetchContent_MakeAvailable(glaze)

# Do not change the order of sources
add_executable(glaze_test
        src/unused.cpp
        src/main.cpp
        )
target_link_libraries(glaze_test PRIVATE glaze::glaze)

This issue appears in v4.0.3 and later, including v5.0.0, but does not occur in earlier versions.

@stephenberry
Copy link
Owner

What compiler are you using? And, if you remove the anonymous namespace (namespace {) does the issue go away?

@hs-vc
Copy link
Author

hs-vc commented Mar 7, 2025

This issue was found on GCC 14.2.0, but it works fine with GCC 13.3.0. The OS in use is Ubuntu 24.04.2 LTS.

Regarding the namespace issue, removing the anonymous namespace in either src/unused.cpp or src/main.cpp resolves the issue, but removing it from both files does not.

@hs-vc
Copy link
Author

hs-vc commented Mar 7, 2025

After further checking the output of each object file, I found they contain a conflicting symbol. Running nm unused.cpp.o | grep ZN3glz10comparitorIL shows:

0000000000000000 W _ZN3glz10comparitorIL_ZZNS_12decode_indexIXtlNS_4optsELj10ELb1ELb0ELb0ELb1ELb1ELb0ELb0ELc32ELh3ELb1ELb0ELb1EEEN12_GLOBAL__N_110tmp_structELm0ERS4_JRmERNS_7contextERPKcSB_EEvOT2_OT4_OT5_OT6_DpOT3_E15KeyWithEndQuoteELm8EcEEbPKT1_

And running nm main.cpp.o | grep ZN3glz10comparitorIL shows the exact same symbol.

The demangled name of the conflicting symbol is:

bool glz::comparitor<glz::decode_index<
    glz::opts{10u, true, false, false, true, true, false, false, (char)32, (unsigned char)3, true, false, true}, 
    (anonymous namespace)::tmp_struct, 
    0ul, 
    (anonymous namespace)::tmp_struct&, 
    unsigned long&, 
    glz::context&, 
    char const*&, 
    char const*&
>((anonymous namespace)::tmp_struct&, glz::context&, char const*&, char const*&, unsigned long&)::KeyWithEndQuote, 8ul, char>(char const*)

What I don’t understand is why this symbol is marked as globally visible with a weak (W) linkage, even though it is instantiated with a struct inside an anonymous namespace. While the exact reason for this is not clear, it seems that at link time, the definition from unused.cpp.o is being selected instead of the one from main.cpp.o.

@hs-vc
Copy link
Author

hs-vc commented Mar 7, 2025

It seems using const std::string_view& as template argument result in some unexpected behavior, as in below minimal project.

src/a.hpp:

#pragma once
#include <string_view>

template <const std::string_view& Str>
auto get_length() {
    return Str.length();
}

src/main.cpp:

#include <iostream>
#include "a.hpp"

namespace {
    static constexpr std::string_view sssss = "123";
}


int main() {
    std::cout << get_length<sssss>() << std::endl;
    return 0;
}

src/unused.cpp:

#include <string_view>
#include "a.hpp"


namespace {
    static constexpr std::string_view sssss = "12345";
    auto unused_function() {
        return get_length<sssss>();
    }
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.30)
project(linkage_test)

set(CMAKE_CXX_STANDARD 23)

add_executable(linkage_test src/unused.cpp src/main.cpp)

Surprisingly, this program outputs 5, not 3. The issue arises because unused.cpp.o and main.cpp.o contain conflicting definitions of auto get_length<(anonymous namespace)::sssss>(), as shown by running nm:

nm unused.cpp.o main.cpp.o | grep _Z10get_lengthIL_ZN12_GLOBAL__N_1L5sssssEEEDav
0000000000000000 W _Z10get_lengthIL_ZN12_GLOBAL__N_1L5sssssEEEDav
0000000000000000 W _Z10get_lengthIL_ZN12_GLOBAL__N_1L5sssssEEEDav

The function is emitted as a W, meaning the linker arbitrarily picks one definition, which results in unexpected behavior.

@stephenberry
Copy link
Owner

Wow, thanks for diving into this issue! This is very interesting. The template instantiation should be based on the reference of the std::string_view, which should have different addresses. This seems like a compiler bug. Do you want me to report this to GCC bugzilla? I can then add their feedback here.

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

No branches or pull requests

2 participants