From 9ea2c73504dbaa75398f7b4fe0f72ed670a5b7e4 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 7 Sep 2023 17:23:18 -0700 Subject: [PATCH] [dylink] Fix multiple loading of the same library with RTLD_LOCAL When a library is loaded for a second time we were only handling the RTLD_GLOBAL case. Fixes: #20203 --- src/library_dylink.js | 19 ++++++++------- test/test_core.py | 54 +++++++++++++++++++++++++++++++++---------- test/test_other.py | 3 +-- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/library_dylink.js b/src/library_dylink.js index 7b6c4d7c0824..632e20aa61e3 100644 --- a/src/library_dylink.js +++ b/src/library_dylink.js @@ -959,15 +959,18 @@ var LibraryDylink = { var dso = LDSO.loadedLibsByName[libName]; if (dso) { // the library is being loaded or has been loaded already. - // - // however it could be previously loaded only locally and if we get - // load request with global=true we have to make it globally visible now. - if (flags.global && !dso.global) { - dso.global = true; - if (dso.exports !== 'loading') { - // ^^^ if module is 'loading' - symbols merging will be eventually done by the loader. - mergeLibSymbols(dso.exports, libName) +#if ASSERTIONS + assert(dso.exports !== 'loading', `Attempt to load '${libName}' twice before the first load completed`); +#endif + if (!flags.global) { + if (localScope) { + Object.assign(localScope, dso.exports); } + } else if (!dso.global) { + // The library was previously loaded only locally but not + // we have a request with global=true. + dso.global = true; + mergeLibSymbols(dso.exports, libName) } // same for "nodelete" if (flags.nodelete && dso.refcount !== Infinity) { diff --git a/test/test_core.py b/test/test_core.py index 6465bef50af9..809e6b91d102 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -4102,29 +4102,46 @@ def test_dlfcn_asyncify(self): @needs_dylink def test_dlfcn_rtld_local(self): + # Create two shared libraries that both depend on a third. + # liba.so -> libsub.so + # libb.so -> libsub.so create_file('liba.c', r''' #include - void func_b(); + void func_sub(); void func_a() { printf("func_a\n"); // Call a function from a dependent DSO. This symbol should // be available here even though liba itself is loaded with RTLD_LOCAL. - func_b(); + func_sub(); } ''') create_file('libb.c', r''' #include + void func_sub(); + void func_b() { printf("func_b\n"); + // Call a function from a dependent DSO. This symbol should + // be available here even though liba itself is loaded with RTLD_LOCAL. + func_sub(); + } + ''') + + create_file('libsub.c', r''' + #include + + void func_sub() { + printf("func_sub\n"); } ''') - self.build_dlfcn_lib('libb.c', outfile='libb.so') - self.build_dlfcn_lib('liba.c', outfile='liba.so', emcc_args=['libb.so']) + self.build_dlfcn_lib('libsub.c', outfile='libsub.so') + self.build_dlfcn_lib('libb.c', outfile='libb.so', emcc_args=['libsub.so']) + self.build_dlfcn_lib('liba.c', outfile='liba.so', emcc_args=['libsub.so']) self.prep_dlfcn_main(['liba.so', 'libb.so', '-L.']) create_file('main.c', r''' @@ -4133,28 +4150,41 @@ def test_dlfcn_rtld_local(self): #include int main() { + void* handle; + void (*f)(); + printf("main\n"); - void* handle = dlopen("liba.so", RTLD_NOW|RTLD_LOCAL); + // Call a function from libb + handle = dlopen("liba.so", RTLD_NOW|RTLD_LOCAL); assert(handle); - void (*f)(); f = dlsym(handle, "func_a"); assert(f); f(); - // Verify that symbols from liba.so and libb.so are not globally + // Same for libb + handle = dlopen("libb.so", RTLD_NOW|RTLD_LOCAL); + assert(handle); + + f = dlsym(handle, "func_b"); + assert(f); + f(); + + // Verify that symbols from all three libraries are not globally // visible. - void* func_a = dlsym(RTLD_DEFAULT, "func_a"); - assert(func_a == NULL); - void* func_b = dlsym(RTLD_DEFAULT, "func_b"); - assert(func_b == NULL); + f = dlsym(RTLD_DEFAULT, "func_a"); + assert(f == NULL); + f = dlsym(RTLD_DEFAULT, "func_b"); + assert(f == NULL); + f = dlsym(RTLD_DEFAULT, "func_sub"); + assert(f == NULL); printf("done\n"); return 0; } ''') - self.do_runf('main.c', 'main\nfunc_a\nfunc_b\ndone\n') + self.do_runf('main.c', 'main\nfunc_a\nfunc_sub\nfunc_b\nfunc_sub\ndone\n') def dylink_test(self, main, side, expected=None, header=None, force_c=False, main_module=2, **kwargs): diff --git a/test/test_other.py b/test/test_other.py index 4527651253dc..9ef799e8b690 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -8339,8 +8339,7 @@ def test_side_module_naming(self): self.assertTrue(building.is_wasm_dylib(target)) create_file('main.c', '') - self.run_process([EMCC, '-sMAIN_MODULE=2', 'main.c', '-Werror', target]) - self.run_js('a.out.js') + self.do_runf('main.c', emcc_args=['-sMAIN_MODULE=2', 'main.c', '-Werror', target]) def test_side_module_missing(self): self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside1.wasm'])