Fix SVACE error in USD loader 97/318397/11
authorRichard Huang <r.huang@samsung.com>
Fri, 27 Sep 2024 09:36:48 +0000 (10:36 +0100)
committerRichard Huang <r.huang@samsung.com>
Mon, 30 Sep 2024 17:32:39 +0000 (18:32 +0100)
Change-Id: Ie67d09a62106fe952aedd646979308300423d46e

automated-tests/src/dali-usd-loader-dynamic-lib-func-override/CMakeLists.txt [new file with mode: 0644]
automated-tests/src/dali-usd-loader-dynamic-lib-func-override/tct-dali-usd-loader-dynamic-lib-func-override-core.cpp [new file with mode: 0644]
automated-tests/src/dali-usd-loader-dynamic-lib-func-override/utc-Dali-UsdLoaderDynamicLibFuncOverride.cpp [new file with mode: 0644]
dali-scene3d/public-api/loader/model-loader.cpp

diff --git a/automated-tests/src/dali-usd-loader-dynamic-lib-func-override/CMakeLists.txt b/automated-tests/src/dali-usd-loader-dynamic-lib-func-override/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f60432a
--- /dev/null
@@ -0,0 +1,98 @@
+SET(PKG_NAME "dali-usd-loader-dynamic-lib-func-override")
+
+SET(EXEC_NAME "tct-${PKG_NAME}-core")
+SET(RPM_NAME "core-${PKG_NAME}-tests")
+
+SET(CAPI_LIB "dali-usd-loader-dynamic-lib-func-override")
+
+# List of test case sources (Only these get parsed for test cases)
+SET(TC_SOURCES
+  utc-Dali-UsdLoaderDynamicLibFuncOverride.cpp
+  )
+
+# List of test harness files (Won't get parsed for test cases)
+SET(TEST_HARNESS_DIR "../dali-toolkit/dali-toolkit-test-utils")
+
+SET(TEST_HARNESS_SOURCES
+  ${TEST_HARNESS_DIR}/toolkit-adaptor.cpp
+  ${TEST_HARNESS_DIR}/toolkit-application.cpp
+  ${TEST_HARNESS_DIR}/toolkit-async-task-manager.cpp
+  ${TEST_HARNESS_DIR}/toolkit-event-thread-callback.cpp
+  ${TEST_HARNESS_DIR}/toolkit-environment-variable.cpp
+  ${TEST_HARNESS_DIR}/toolkit-input-method-context.cpp
+  ${TEST_HARNESS_DIR}/toolkit-input-method-options.cpp
+  ${TEST_HARNESS_DIR}/toolkit-lifecycle-controller.cpp
+  ${TEST_HARNESS_DIR}/toolkit-orientation.cpp
+  ${TEST_HARNESS_DIR}/toolkit-style-monitor.cpp
+  ${TEST_HARNESS_DIR}/toolkit-test-application.cpp
+  ${TEST_HARNESS_DIR}/toolkit-timer.cpp
+  ${TEST_HARNESS_DIR}/toolkit-trigger-event-factory.cpp
+  ${TEST_HARNESS_DIR}/toolkit-window.cpp
+  ${TEST_HARNESS_DIR}/toolkit-scene-holder.cpp
+  ${TEST_HARNESS_DIR}/dali-test-suite-utils.cpp
+  ${TEST_HARNESS_DIR}/dali-toolkit-test-suite-utils.cpp
+  ${TEST_HARNESS_DIR}/dummy-control.cpp
+  ${TEST_HARNESS_DIR}/mesh-builder.cpp
+  ${TEST_HARNESS_DIR}/test-actor-utils.cpp
+  ${TEST_HARNESS_DIR}/test-animation-data.cpp
+  ${TEST_HARNESS_DIR}/test-application.cpp
+  ${TEST_HARNESS_DIR}/test-button.cpp
+  ${TEST_HARNESS_DIR}/test-harness.cpp
+  ${TEST_HARNESS_DIR}/test-gesture-generator.cpp
+  ${TEST_HARNESS_DIR}/test-gl-abstraction.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-sync-impl.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-sync-object.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-buffer.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-command-buffer.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-framebuffer.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-controller.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-texture.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-sampler.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-program.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-pipeline.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-shader.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-reflection.cpp
+  ${TEST_HARNESS_DIR}/test-platform-abstraction.cpp
+  ${TEST_HARNESS_DIR}/test-render-controller.cpp
+  ${TEST_HARNESS_DIR}/test-render-surface.cpp
+  ${TEST_HARNESS_DIR}/test-trace-call-stack.cpp
+)
+
+PKG_CHECK_MODULES(${CAPI_LIB} REQUIRED
+  dali2-core
+  dali2-adaptor
+  dali2-toolkit
+  dali2-scene3d
+)
+
+ADD_COMPILE_OPTIONS( -O0 -ggdb --coverage -Wall -Werror -DDEBUG_ENABLED)
+ADD_COMPILE_OPTIONS( ${${CAPI_LIB}_CFLAGS_OTHER} )
+
+ADD_DEFINITIONS(-DTEST_RESOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../resources\" )
+
+FOREACH(directory ${${CAPI_LIB}_LIBRARY_DIRS})
+    SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -L${directory}")
+ENDFOREACH(directory ${CAPI_LIB_LIBRARY_DIRS})
+
+INCLUDE_DIRECTORIES(
+    ../../../
+    ${${CAPI_LIB}_INCLUDE_DIRS}
+    ../dali-toolkit/dali-toolkit-test-utils
+)
+
+ADD_CUSTOM_COMMAND(
+  COMMAND ${SCRIPT_DIR}/tcheadgen.sh ${EXEC_NAME}.h ${TC_SOURCES}
+  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+  OUTPUT ${EXEC_NAME}.h
+  COMMENT "Generating test tables"
+  )
+
+ADD_EXECUTABLE(${EXEC_NAME} ${EXEC_NAME}.h ${EXEC_NAME}.cpp ${TC_SOURCES} ${TEST_HARNESS_SOURCES})
+TARGET_LINK_LIBRARIES(${EXEC_NAME}
+    ${${CAPI_LIB}_LIBRARIES}
+    -lpthread -ldl --coverage
+)
+
+INSTALL(PROGRAMS ${EXEC_NAME}
+    DESTINATION ${BIN_DIR}/${EXEC_NAME}
+)
diff --git a/automated-tests/src/dali-usd-loader-dynamic-lib-func-override/tct-dali-usd-loader-dynamic-lib-func-override-core.cpp b/automated-tests/src/dali-usd-loader-dynamic-lib-func-override/tct-dali-usd-loader-dynamic-lib-func-override-core.cpp
new file mode 100644 (file)
index 0000000..e479c8e
--- /dev/null
@@ -0,0 +1,9 @@
+#include <test-harness.h>
+
+// Must come second
+#include "tct-dali-usd-loader-dynamic-lib-func-override-core.h"
+
+int main(int argc, char* const argv[])
+{
+  return TestHarness::RunTests(argc, argv, tc_array);
+}
diff --git a/automated-tests/src/dali-usd-loader-dynamic-lib-func-override/utc-Dali-UsdLoaderDynamicLibFuncOverride.cpp b/automated-tests/src/dali-usd-loader-dynamic-lib-func-override/utc-Dali-UsdLoaderDynamicLibFuncOverride.cpp
new file mode 100644 (file)
index 0000000..556cc9a
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// Enable debug log for test coverage
+#define DEBUG_ENABLED 1
+
+#include <dali-scene3d/public-api/loader/load-result.h>
+#include <dali-scene3d/public-api/loader/model-loader.h>
+#include <dali-scene3d/public-api/loader/resource-bundle.h>
+#include <dali-scene3d/public-api/loader/scene-definition.h>
+#include <dali-test-suite-utils.h>
+#include <dlfcn.h>
+#include <string_view>
+
+using namespace Dali;
+using namespace Dali::Scene3D::Loader;
+
+namespace
+{
+struct Context
+{
+  ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) {
+    return TEST_RESOURCE_DIR "/";
+  };
+
+  ResourceBundle  resources;
+  SceneDefinition scene;
+  SceneMetadata   metaData;
+
+  std::vector<AnimationDefinition>      animations;
+  std::vector<AnimationGroupDefinition> animationGroups;
+  std::vector<CameraParameters>         cameras;
+  std::vector<LightParameters>          lights;
+
+  LoadResult loadResult{
+    resources,
+    scene,
+    metaData,
+    animations,
+    animationGroups,
+    cameras,
+    lights};
+
+  Dali::Scene3D::Loader::ModelLoader* loader;
+};
+
+bool gDlopenOverrideEnabled = false;
+bool gDlsymOverrideEnabled  = false;
+
+extern "C" void* DlopenProxy(const char* filename, int flag)
+{
+  if(gDlopenOverrideEnabled)
+  {
+    return nullptr;
+  }
+  else
+  {
+    return dlopen("libdali2-scene3d.so", RTLD_LAZY);
+  }
+}
+
+extern "C" void* DlsymProxy(void* handle, const char* symbol)
+{
+  if(gDlsymOverrideEnabled)
+  {
+    return nullptr;
+  }
+  else
+  {
+    return dlsym(handle, symbol);
+  }
+}
+
+} // namespace
+
+int UtcDaliUsdLoaderDlopenFail(void)
+{
+  // Only make dlopen fail
+  gDlopenOverrideEnabled = true;
+  gDlsymOverrideEnabled  = false;
+
+  Context ctx;
+
+  ctx.loader = new Dali::Scene3D::Loader::ModelLoader(TEST_RESOURCE_DIR "/usd/CesiumMan.usdz", ctx.pathProvider(ResourceType::Mesh) + "/", ctx.loadResult);
+  DALI_TEST_EQUAL(ctx.loader->LoadModel(ctx.pathProvider, true), false);
+
+  DALI_TEST_EQUAL(0, ctx.scene.GetRoots().size());
+  DALI_TEST_EQUAL(0, ctx.scene.GetNodeCount());
+
+  DALI_TEST_EQUAL(0, ctx.resources.mEnvironmentMaps.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mMaterials.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mMeshes.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mShaders.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mSkeletons.size());
+
+  DALI_TEST_EQUAL(0, ctx.cameras.size());
+  DALI_TEST_EQUAL(0, ctx.lights.size());
+  DALI_TEST_EQUAL(0, ctx.animations.size());
+  DALI_TEST_EQUAL(0, ctx.animationGroups.size());
+
+  delete ctx.loader;
+
+  END_TEST;
+}
+
+int UtcDaliUsdLoaderDlsymFail(void)
+{
+  // Only make dlsym fail
+  gDlopenOverrideEnabled = false;
+  gDlsymOverrideEnabled  = true;
+
+  Context ctx;
+
+  ctx.loader = new Dali::Scene3D::Loader::ModelLoader(TEST_RESOURCE_DIR "/usd/CesiumMan.usdz", ctx.pathProvider(ResourceType::Mesh) + "/", ctx.loadResult);
+  DALI_TEST_EQUAL(ctx.loader->LoadModel(ctx.pathProvider, true), false);
+
+  DALI_TEST_EQUAL(0, ctx.scene.GetRoots().size());
+  DALI_TEST_EQUAL(0, ctx.scene.GetNodeCount());
+
+  DALI_TEST_EQUAL(0, ctx.resources.mEnvironmentMaps.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mMaterials.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mMeshes.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mShaders.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mSkeletons.size());
+
+  DALI_TEST_EQUAL(0, ctx.cameras.size());
+  DALI_TEST_EQUAL(0, ctx.lights.size());
+  DALI_TEST_EQUAL(0, ctx.animations.size());
+  DALI_TEST_EQUAL(0, ctx.animationGroups.size());
+
+  delete ctx.loader;
+
+  END_TEST;
+}
index 595e1871c424eccee409f285c12e268b610475d3..939a438f0165e3506e98386825def855124acdde 100644 (file)
@@ -34,19 +34,48 @@ namespace Dali::Scene3D::Loader
 {
 namespace
 {
-static constexpr std::string_view OBJ_EXTENSION      = ".obj";
-static constexpr std::string_view GLTF_EXTENSION     = ".gltf";
-static constexpr std::string_view GLB_EXTENSION      = ".glb";
-static constexpr std::string_view DLI_EXTENSION      = ".dli";
-static constexpr std::string_view USD_EXTENSION      = ".usd";
-static constexpr std::string_view USDZ_EXTENSION     = ".usdz";
-static constexpr std::string_view USDA_EXTENSION     = ".usda";
-static constexpr std::string_view USDC_EXTENSION     = ".usdc";
-static constexpr std::string_view METADATA_EXTENSION = "metadata";
+constexpr std::string_view OBJ_EXTENSION      = ".obj";
+constexpr std::string_view GLTF_EXTENSION     = ".gltf";
+constexpr std::string_view GLB_EXTENSION      = ".glb";
+constexpr std::string_view DLI_EXTENSION      = ".dli";
+constexpr std::string_view USD_EXTENSION      = ".usd";
+constexpr std::string_view USDZ_EXTENSION     = ".usdz";
+constexpr std::string_view USDA_EXTENSION     = ".usda";
+constexpr std::string_view USDC_EXTENSION     = ".usdc";
+constexpr std::string_view METADATA_EXTENSION = "metadata";
 
 const char* USD_LOADER_SO("libdali2-usd-loader.so");
 const char* CREATE_USD_LOADER_SYMBOL("CreateUsdLoader");
 
+// Custom deleter for dlopen handles
+void DlcloseDeleter(void* handle)
+{
+  if(handle)
+  {
+    dlclose(handle);
+  }
+}
+
+// Static shared pointer to store a pointer to the dlopen handle
+std::shared_ptr<void*> gUsdLoaderHandle(nullptr, DlcloseDeleter);
+
+using CreateUsdLoaderFunc = ModelLoaderImpl* (*)();
+CreateUsdLoaderFunc gCreateUsdLoaderFunc(nullptr);
+
+// Poxy function for `dlopen` to allow easy overriding in test environments.
+extern "C" void* DlopenProxy(const char* filename, int flag)
+{
+  // Calls the real dlopen
+  return dlopen(filename, flag);
+}
+
+// Poxy function for `dlsym` to allow easy overriding in test environments.
+extern "C" void* DlsymProxy(void* handle, const char* name)
+{
+  // Calls the real dlsym
+  return dlsym(handle, name);
+}
+
 } // namespace
 
 ModelLoader::ModelLoader(const std::string& modelUrl, const std::string& resourceDirectoryUrl, Dali::Scene3D::Loader::LoadResult& loadResult)
@@ -138,29 +167,45 @@ void ModelLoader::CreateModelLoader()
     // Attempt to load the USD loader library dynamically
     // Once loaded we will keep it open so that any subsequent loading of USD models
     // doesn't require loading the same library repeatedly.
-    void* handle(dlopen(USD_LOADER_SO, RTLD_LAZY));
-    if(!handle)
+    if(!gUsdLoaderHandle || !(*gUsdLoaderHandle))
     {
-      // The shared library failed to load
-      DALI_LOG_ERROR("ModelLoader::CreateModelLoader, dlopen error: %s\n", dlerror());
-      return;
-    }
-
-    // Dynamically link to the CreateUsdLoader function in the shared library at runtime.
-    using CreateUsdLoaderFunc           = Dali::Scene3D::Loader::ModelLoaderImpl* (*)();
-    CreateUsdLoaderFunc createUsdLoader = reinterpret_cast<CreateUsdLoaderFunc>(dlsym(handle, CREATE_USD_LOADER_SYMBOL));
-
-    if(!createUsdLoader)
-    {
-      // Close the shared library if the symbol couldn't be found
-      dlclose(handle);
-      DALI_LOG_ERROR("Cannot find CreateUsdLoader function: %s\n", dlerror());
-
-      return;
+      void* handle = DlopenProxy(USD_LOADER_SO, RTLD_LAZY);
+
+      if(!handle)
+      {
+        // The shared library failed to load
+        DALI_LOG_ERROR("ModelLoader::CreateModelLoader, dlopen error: %s\n", dlerror());
+        return;
+      }
+
+      // Store the handle in the shared_ptr (passing the handle and the custom deleter)
+      gUsdLoaderHandle = std::shared_ptr<void*>(new void*(handle), [](void* ptr) {
+        if(ptr)
+        {
+          DlcloseDeleter(*(static_cast<void**>(ptr))); // Call custom deleter
+          delete static_cast<void**>(ptr);             // Clean up dynamically allocated memory
+        }
+      });
+
+      // Dynamically link to the CreateUsdLoader function in the shared library at runtime.
+      // Cache the function pointer only if it hasn't been loaded yet
+      if(!gCreateUsdLoaderFunc)
+      {
+        gCreateUsdLoaderFunc = reinterpret_cast<CreateUsdLoaderFunc>(DlsymProxy(*gUsdLoaderHandle.get(), CREATE_USD_LOADER_SYMBOL));
+        if(!gCreateUsdLoaderFunc)
+        {
+          // If the symbol couldn't be found, reset the shared_ptr to invoke the custom deleter
+          gUsdLoaderHandle.reset(); // This will automatically call dlclose via the custom deleter
+
+          DALI_LOG_ERROR("Cannot find CreateUsdLoader function: %s\n", dlerror());
+
+          return;
+        }
+      }
     }
 
     // Create an instance of USD loader
-    mImpl = ModelLoaderImplUniquePtr(createUsdLoader());
+    mImpl = ModelLoaderImplUniquePtr(gCreateUsdLoaderFunc());
   }
   else
   {
@@ -193,5 +238,4 @@ void ModelLoader::LoadResource(Dali::Scene3D::Loader::ResourceBundle::PathProvid
     GetResources().LoadResources(pathProvider);
   }
 }
-
 } // namespace Dali::Scene3D::Loader