Skip to content

Conversation

jcelerier
Copy link

If a software tries to integrate rapidlib but already uses jsoncpp, then
this is undefined behaviour which gets flagged by addresssanitizer for instance.

I encountered this while trying to integrate RapidLib with https://ossia.io :
ossia integrates sh4lt which is a completely different package for sharing
video frames across processes on Linux.
sh4lt also uses jsoncpp as part of its implementation thus jsoncpp symbols are defined twice
(and the unlucky software crashes on startup).

Michael ZBYSZYNSKI and others added 5 commits May 14, 2025 06:12
If a software tries to integrate rapidlib but already uses jsoncpp, then
this is undefined behaviour which gets flagged by addresssanitizer for instance.

I encountered this while trying to integrate RapidLib with https://ossia.io :
ossia integrates sh4lt which is a completely different package for sharing
video frames across processes on Linux.
sh4lt also uses jsoncpp as part of its implementation thus jsoncpp symbols are defined twice
(and the unlucky software crashes on startup).
@mzed
Copy link
Owner

mzed commented Aug 12, 2025

Does it work if json.cpp is included privately? Like:

target_sources(${PROJECT_NAME} PRIVATE ${JSON_SRC})

?

@jcelerier
Copy link
Author

oops, I should fix that on my PR, a .cpp should never be target_sources(PUBLIC)

@mzed
Copy link
Owner

mzed commented Aug 12, 2025

I was wondering if there was a conflict if RapidLib integrates json.cpp privately. I think it should not conflict with sh4lt then. That's what "private" means, right?

@mzed
Copy link
Owner

mzed commented Aug 12, 2025

I just pushed some changes that might resolve your issue.

@jcelerier
Copy link
Author

jcelerier commented Aug 15, 2025

heya!

sadly this is not what target_sources(... PRIVATE ...) does. this is the default mode of adding sources when doing add_library(...)

target_sources(target PUBLIC foo.cpp bar.cpp) is only relevant when building INTERFACE libraries (e.g. "virtual" cmake targets that do not directly build anything themselves but can be useful to easily make other targets inherit a set of properties)

For instance if I do, given example.cpp:

 int main() { return VARIABLE; }


add_library(foo INTERFACE)
target_sources(foo PUBLIC example.cpp)

add_executable(exe1)
add_executable(exe2)
add_executable(exe3)

target_compile_definitions(exe1 PRIVATE VARIABLE=10)
target_compile_definitions(exe2 PRIVATE VARIABLE=20)
target_compile_definitions(exe3 PRIVATE VARIABLE=30)
target_link_libraries(exe1 PRIVATE foo)
target_link_libraries(exe2 PRIVATE foo)
target_link_libraries(exe3 PRIVATE foo)  

then the following build commands will be run:

/usr/bin/c++ -DVARIABLE=10   -MD -MT CMakeFiles/exe1.dir/example.cpp.o -MF CMakeFiles/exe1.dir/example.cpp.o.d -o CMakeFiles/exe1.dir/example.cpp.o -c /tmp/x/example.cpp
/usr/bin/c++ -DVARIABLE=20   -MD -MT CMakeFiles/exe2.dir/example.cpp.o -MF CMakeFiles/exe2.dir/example.cpp.o.d -o CMakeFiles/exe2.dir/example.cpp.o -c /tmp/x/example.cpp
/usr/bin/c++ -DVARIABLE=30   -MD -MT CMakeFiles/exe3.dir/example.cpp.o -MF CMakeFiles/exe3.dir/example.cpp.o.d -o CMakeFiles/exe3.dir/example.cpp.o -c /tmp/x/example.cpp

e.g. each target will build the file independently just like if I had done

add_executable(exe1 example.cpp)
add_executable(exe2 example.cpp)
add_executable(exe3 example.cpp)

In the case of building a library like rapidlib we never want this because this will cause duplicate symbols and the build will just fail: if you do:

 add_library(rapidlib)
 target_sources(rapidlib PUBLIC source.cpp)
 add_executable(app)
 target_link_libraries(app PRIVATE rapidlib)

then librapidlib.a will contains the symbols of source.cpp, but app will ALSO have its copy of source.cpp built along with it, and those symbols will clash (if it even builds).

There's really zero way to have multiple times the same symbol in a given single C++ executable, this is against the standard: https://en.wikipedia.org/wiki/One_Definition_Rule

At the root this isn't even a C++ issue but an operating system issue: the operating systems' executable formats (PE on windows, ELF on Linux and MachO on Mac) only support one symbol with a given name per binary as those are basically just a hashmap (symbol name -> offset in the binary) ; if a build creates two symbols with the same name at best one gets picked at random, and if it's from two versions of say, jsoncpp, then you could have one method taken from version A and another from version B and if the data structures have changed between versions then crashes will 100% happen.

The two options are:

1/ allow to enable / disable the third-party libraries
2/ make sure that every dependency can be obtained through find_package instead of vendored.

What I do in my libs is leave the choice so that people using them can either use the quick way or have more control depending on their needs and the existing libraries they may already have in their build infrastructure:

https://github.com/ossia/libossia/blob/master/cmake/deps/ctre.cmake

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.

2 participants