[lldb][gnustep] Add basic test and infrastructure for GNUstep ObjC runtime
authorStefan Gränitz <stefan.graenitz@gmail.com>
Tue, 16 May 2023 13:22:49 +0000 (15:22 +0200)
committerStefan Gränitz <stefan.graenitz@gmail.com>
Wed, 17 May 2023 08:37:38 +0000 (10:37 +0200)
This patch adds test infrastructure to utilize the GNUstep runtime in the LLDB test suite and adds coverage for features that already work on Linux. These seem accidental in parts, but it's a good early baseline. On Windows nothing works yet. Please find the repository for the GNUstep ObjC runtime here: https://github.com/gnustep/libobjc2

GNUstep support is disabled by default. CMake configuration involves two variables:
* `LLDB_TEST_OBJC_GNUSTEP=On` enables GNUstep support in the test suite. It requires the libobjc2 shared library and headers to be found.
* `LLDB_TEST_OBJC_GNUSTEP=Off` disables GNUstep support in the test suite and resets associated cache values if necessary (default).
* `LLDB_TEST_OBJC_GNUSTEP_DIR` allows to pass a custom installation root.

Differential Revision: https://reviews.llvm.org/D146058

lldb/cmake/modules/FindGNUstepObjC.cmake [new file with mode: 0644]
lldb/test/CMakeLists.txt
lldb/test/Shell/Expr/objc-gnustep-print.m [new file with mode: 0644]
lldb/test/Shell/helper/build.py
lldb/test/Shell/helper/toolchain.py
lldb/test/Shell/lit.cfg.py
lldb/test/Shell/lit.site.cfg.py.in

diff --git a/lldb/cmake/modules/FindGNUstepObjC.cmake b/lldb/cmake/modules/FindGNUstepObjC.cmake
new file mode 100644 (file)
index 0000000..e53a89e
--- /dev/null
@@ -0,0 +1,41 @@
+#.rst:
+# FindGNUstepObjC
+# ---------------
+#
+# Find the GNUstep libobjc2 shared library.
+
+set(gnustep_install_dir "")
+
+if (UNIX)
+  set(gnustep_lib lib/libobjc.so)
+  set(gnustep_header include/objc/runtime.h)
+  if (GNUstepObjC_DIR)
+    if (EXISTS "${GNUstepObjC_DIR}/${gnustep_lib}" AND
+        EXISTS "${GNUstepObjC_DIR}/${gnustep_header}")
+      set(gnustep_install_dir ${GNUstepObjC_DIR})
+    endif()
+  else()
+    set(gnustep_install_dir)
+    find_path(gnustep_install_dir NAMES lib/libobjc.so include/objc/runtime.h)
+  endif()
+  if (gnustep_install_dir)
+    set(GNUstepObjC_FOUND TRUE)
+  endif()
+elseif (WIN32)
+  set(gnustep_lib lib/objc.dll)
+  set(gnustep_header include/objc/runtime.h)
+  if (GNUstepObjC_DIR)
+    set(gnustep_install_dir ${GNUstepObjC_DIR})
+  else()
+    set(gnustep_install_dir "C:/Program Files (x86)/libobjc")
+  endif()
+  if (EXISTS "${gnustep_install_dir}/${gnustep_lib}" AND
+      EXISTS "${gnustep_install_dir}/${gnustep_header}")
+    set(GNUstepObjC_FOUND TRUE)
+  endif()
+endif()
+
+if (GNUstepObjC_FOUND)
+  set(GNUstepObjC_DIR ${gnustep_install_dir})
+  message(STATUS "Found GNUstep ObjC runtime: ${GNUstepObjC_DIR}/${gnustep_lib}")
+endif()
index ca15b96..38013e1 100644 (file)
@@ -50,6 +50,30 @@ set(LLDB_TEST_MODULE_CACHE_CLANG "${LLDB_TEST_BUILD_DIRECTORY}/module-cache-clan
 file(MAKE_DIRECTORY ${LLDB_TEST_MODULE_CACHE_LLDB})
 file(MAKE_DIRECTORY ${LLDB_TEST_MODULE_CACHE_CLANG})
 
+# Windows and Linux have no built-in ObjC runtime. Turn this on in order to run tests with GNUstep.
+option(LLDB_TEST_OBJC_GNUSTEP "Enable ObjC tests with GNUstep libobjc2 on non-Apple platforms" Off)
+set(LLDB_TEST_OBJC_GNUSTEP_DIR "" CACHE PATH "Custom path to the GNUstep shared library")
+
+if (LLDB_TEST_OBJC_GNUSTEP)
+  if (LLDB_TEST_OBJC_GNUSTEP_DIR)
+    set(GNUstepObjC_DIR ${LLDB_TEST_OBJC_GNUSTEP_DIR})
+  endif()
+  find_package(GNUstepObjC)
+  if (NOT GNUstepObjC_FOUND)
+    if (LLDB_TEST_OBJC_GNUSTEP_DIR)
+      message(FATAL_ERROR "Failed to find GNUstep libobjc2 in ${LLDB_TEST_OBJC_GNUSTEP_DIR}. "
+                          "Please check LLDB_TEST_OBJC_GNUSTEP_DIR or turn off LLDB_TEST_OBJC_GNUSTEP.")
+    else()
+      message(FATAL_ERROR "Failed to find GNUstep libobjc2. "
+                          "Please set LLDB_TEST_OBJC_GNUSTEP_DIR or turn off LLDB_TEST_OBJC_GNUSTEP.")
+    endif()
+  endif()
+  set(LLDB_TEST_OBJC_GNUSTEP_DIR ${GNUstepObjC_DIR})
+elseif (LLDB_TEST_OBJC_GNUSTEP_DIR)
+  message(STATUS "Reset LLDB_TEST_OBJC_GNUSTEP_DIR since LLDB_TEST_OBJC_GNUSTEP is off")
+  set(LLDB_TEST_OBJC_GNUSTEP_DIR "" CACHE PATH "Custom path to the GNUstep shared library" FORCE)
+endif()
+
 # LLVM_BUILD_MODE is used in lit.site.cfg
 if (CMAKE_CFG_INTDIR STREQUAL ".")
   set(LLVM_BUILD_MODE ".")
diff --git a/lldb/test/Shell/Expr/objc-gnustep-print.m b/lldb/test/Shell/Expr/objc-gnustep-print.m
new file mode 100644 (file)
index 0000000..565a96f
--- /dev/null
@@ -0,0 +1,62 @@
+// REQUIRES: objc-gnustep
+// XFAIL: system-windows
+//
+// RUN: %build %s --compiler=clang --objc-gnustep --output=%t
+
+#import "objc/runtime.h"
+
+@protocol NSCoding
+@end
+
+#ifdef __has_attribute
+#if __has_attribute(objc_root_class)
+__attribute__((objc_root_class))
+#endif
+#endif
+@interface NSObject <NSCoding> {
+  id isa;
+  int refcount;
+}
+@end
+@implementation NSObject
+- (id)class {
+  return object_getClass(self);
+}
++ (id)new {
+  return class_createInstance(self, 0);
+}
+@end
+
+@interface TestObj : NSObject {}
+- (int)ok;
+@end
+@implementation TestObj
+- (int)ok {
+  return self ? 0 : 1;
+}
+@end
+
+// RUN: %lldb -b -o "b objc-gnustep-print.m:35" -o "run" -o "p self" -o "p *self" -- %t | FileCheck %s --check-prefix=SELF
+//
+// SELF: (lldb) b objc-gnustep-print.m:35
+// SELF: Breakpoint {{.*}} at objc-gnustep-print.m
+//
+// SELF: (lldb) run
+// SELF: Process {{[0-9]+}} stopped
+// SELF: -[TestObj ok](self=[[SELF_PTR:0x[0-9a-f]+]]{{.*}}) at objc-gnustep-print.m:35
+//
+// SELF: (lldb) p self
+// SELF: (TestObj *) [[SELF_PTR]]
+//
+// SELF: (lldb) p *self
+// SELF: (TestObj) {
+// SELF:   NSObject = {
+// SELF:     isa
+// SELF:     refcount
+// SELF:   }
+// SELF: }
+
+int main() {
+  TestObj *t = [TestObj new];
+  return [t ok];
+}
index 55d871f..2c15d6d 100755 (executable)
@@ -49,6 +49,18 @@ parser.add_argument('--tools-dir',
                     action='append',
                     help='If specified, a path to search in addition to PATH when --compiler is not an exact path')
 
+parser.add_argument('--objc-gnustep-dir',
+                    metavar='directory',
+                    dest='objc_gnustep_dir',
+                    required=False,
+                    help='If specified, a path to GNUstep libobjc2 runtime for use on Windows and Linux')
+
+parser.add_argument('--objc-gnustep',
+                    dest='objc_gnustep',
+                    action='store_true',
+                    default=False,
+                    help='Include and link GNUstep libobjc2 (Windows and Linux only)')
+
 if sys.platform == 'darwin':
     parser.add_argument('--apple-sdk',
                         metavar='apple_sdk',
@@ -238,6 +250,10 @@ class Builder(object):
         self.obj_ext = obj_ext
         self.lib_paths = args.libs_dir
         self.std = args.std
+        assert not args.objc_gnustep or args.objc_gnustep_dir, \
+               "--objc-gnustep specified without path to libobjc2"
+        self.objc_gnustep_inc = os.path.join(args.objc_gnustep_dir, 'include') if args.objc_gnustep_dir else None
+        self.objc_gnustep_lib = os.path.join(args.objc_gnustep_dir, 'lib') if args.objc_gnustep_dir else None
 
     def _exe_file_name(self):
         assert self.mode != 'compile'
@@ -656,15 +672,20 @@ class GccBuilder(Builder):
             args.append('-static')
         args.append('-c')
 
-        args.extend(['-o', obj])
-        args.append(source)
-
         if sys.platform == 'darwin':
             args.extend(['-isysroot', self.apple_sdk])
+        elif self.objc_gnustep_inc:
+            if source.endswith('.m') or source.endswith('.mm'):
+                args.extend(['-fobjc-runtime=gnustep-2.0', '-I', self.objc_gnustep_inc])
+                if sys.platform == "win32":
+                    args.extend(['-Xclang', '-gcodeview', '-Xclang', '--dependent-lib=msvcrtd'])
 
         if self.std:
             args.append('-std={0}'.format(self.std))
 
+        args.extend(['-o', obj])
+        args.append(source)
+
         return ('compiling', [source], obj, None, args)
 
     def _get_link_command(self):
@@ -686,6 +707,12 @@ class GccBuilder(Builder):
 
         if sys.platform == 'darwin':
             args.extend(['-isysroot', self.apple_sdk])
+        elif self.objc_gnustep_lib:
+            args.extend(['-L', self.objc_gnustep_lib, '-lobjc'])
+            if sys.platform == 'linux':
+                args.extend(['-Wl,-rpath,' + self.objc_gnustep_lib])
+            elif sys.platform == 'win32':
+                args.extend(['-fuse-ld=lld-link', '-g', '-Xclang', '--dependent-lib=msvcrtd'])
 
         return ('linking', self._obj_file_names(), self._exe_file_name(), None, args)
 
index 69b55b9..c882223 100644 (file)
@@ -42,6 +42,8 @@ def use_lldb_substitutions(config):
         build_script_args.append('--tools-dir={0}'.format(config.lldb_tools_dir))
     if config.llvm_libs_dir:
         build_script_args.append('--libs-dir={0}'.format(config.llvm_libs_dir))
+    if config.objc_gnustep_dir:
+        build_script_args.append('--objc-gnustep-dir="{0}"'.format(config.objc_gnustep_dir))
 
     lldb_init = _get_lldb_init_path(config)
 
index 7717400..20dd8fc 100644 (file)
@@ -24,7 +24,7 @@ config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell)
 
 # suffixes: A list of file extensions to treat as test files. This is overriden
 # by individual lit.local.cfg files in the test subdirectories.
-config.suffixes = ['.test', '.cpp', '.s']
+config.suffixes = ['.test', '.cpp', '.s', '.m']
 
 # excludes: A list of directories to exclude from the testsuite. The 'Inputs'
 # subdirectories contain auxiliary inputs for various tests in their parent
@@ -135,6 +135,14 @@ if config.lldb_system_debugserver:
 if config.have_lldb_server:
     config.available_features.add('lldb-server')
 
+if config.objc_gnustep_dir:
+    config.available_features.add('objc-gnustep')
+    if platform.system() == 'Windows':
+        # objc.dll must be in PATH since Windows has no rpath
+        config.environment['PATH'] = os.path.pathsep.join((
+            os.path.join(config.objc_gnustep_dir, 'lib'),
+            config.environment.get('PATH','')))
+
 # NetBSD permits setting dbregs either if one is root
 # or if user_set_dbregs is enabled
 can_set_dbregs = True
index d58918c..f84cb60 100644 (file)
@@ -16,6 +16,7 @@ config.lldb_lit_tools_dir = lit_config.substitute(r"@LLDB_LIT_TOOLS_DIR@")
 config.target_triple = "@LLVM_TARGET_TRIPLE@"
 config.python_executable = "@Python3_EXECUTABLE@"
 config.have_zlib = @LLVM_ENABLE_ZLIB@
+config.objc_gnustep_dir = "@LLDB_TEST_OBJC_GNUSTEP_DIR@"
 config.lldb_enable_lzma = @LLDB_ENABLE_LZMA@
 config.host_triple = "@LLVM_HOST_TRIPLE@"
 config.lldb_bitness = 64 if @LLDB_IS_64_BITS@ else 32