Skip to content

Conversation

alx32
Copy link
Contributor

@alx32 alx32 commented May 9, 2024

In ObjC we can have categories that define a +load method that is called when the category is loaded. In such cases, we shouldn't optimize the category. These categories are present in the __objc_nlcatlist section. So we scan these section for such categories and ignore them from optimization.

@alx32 alx32 requested review from kyulee-com, thevinster and ellishg May 9, 2024 01:16
@alx32 alx32 marked this pull request as ready for review May 9, 2024 01:18
@llvmbot
Copy link
Member

llvmbot commented May 9, 2024

@llvm/pr-subscribers-lld

@llvm/pr-subscribers-lld-macho

Author: None (alx32)

Changes

In ObjC we can have categories that define a +load method that is called when the category is loaded. In such cases, we shouldn't optimize the category. These categories are present in the __objc_nlcatlist section. So we scan these section for such categories and ignore them from optimization.


Full diff: https://github.com/llvm/llvm-project/pull/91548.diff

2 Files Affected:

  • (modified) lld/MachO/ObjC.cpp (+24)
  • (modified) lld/test/MachO/objc-category-merging-complete-test.s (+47)
diff --git a/lld/MachO/ObjC.cpp b/lld/MachO/ObjC.cpp
index 15b89a808b05e..96ec646095be8 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -428,6 +428,7 @@ class ObjcCategoryMerger {
   static void doCleanup();
 
 private:
+  DenseSet<const Symbol *> collectNlCategories();
   void collectAndValidateCategoriesData();
   void
   mergeCategoriesIntoSingleCategory(std::vector<InfoInputCategory> &categories);
@@ -1060,7 +1061,27 @@ void ObjcCategoryMerger::createSymbolReference(Defined *refFrom,
   refFrom->isec()->relocs.push_back(r);
 }
 
+// Get the list of categories in the '__objc_nlcatlist' section. We can't
+// optimize these as they have a '+load' method that has to be called at
+// runtime.
+DenseSet<const Symbol *> ObjcCategoryMerger::collectNlCategories() {
+  DenseSet<const Symbol *> nlCategories;
+
+  for (InputSection *sec : allInputSections) {
+    if (sec->getName() != section_names::objcNonLazyCatList)
+      continue;
+
+    for (auto &r : sec->relocs) {
+      const Symbol *sym = r.referent.dyn_cast<Symbol *>();
+      nlCategories.insert(sym);
+    }
+  }
+  return nlCategories;
+}
+
 void ObjcCategoryMerger::collectAndValidateCategoriesData() {
+  auto nlCategories = collectNlCategories();
+
   for (InputSection *sec : allInputSections) {
     if (sec->getName() != section_names::objcCatList)
       continue;
@@ -1074,6 +1095,9 @@ void ObjcCategoryMerger::collectAndValidateCategoriesData() {
       assert(categorySym &&
              "Failed to get a valid category at __objc_catlit offset");
 
+      if (nlCategories.count(categorySym))
+        continue;
+
       // We only support ObjC categories (no swift + @objc)
       // TODO: Support swift + @objc categories also
       if (!categorySym->getName().starts_with(objc::symbol_names::category))
diff --git a/lld/test/MachO/objc-category-merging-complete-test.s b/lld/test/MachO/objc-category-merging-complete-test.s
index d2d264a3f26c2..74400177b550d 100644
--- a/lld/test/MachO/objc-category-merging-complete-test.s
+++ b/lld/test/MachO/objc-category-merging-complete-test.s
@@ -88,6 +88,7 @@ MERGE_CATS-NEXT:                 name {{.*}} MyProtocol02Prop
 MERGE_CATS-NEXT:            attributes {{.*}} Ti,R,D
 MERGE_CATS-NEXT:                 name {{.*}} MyProtocol03Prop
 MERGE_CATS-NEXT:            attributes {{.*}} Ti,R,D
+MERGE_CATS:        __OBJC_$_CATEGORY_MyBaseClass_$_Category04
 
 
 NO_MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass(Category02|Category03)
@@ -431,6 +432,15 @@ L_OBJC_IMAGE_INFO:
 ## @dynamic MyProtocol03Prop;
 ## @end
 ##
+## // This category shouldn't be merged
+## @interface MyBaseClass(Category04)
+## + (void)load;
+## @end
+##
+## @implementation MyBaseClass(Category04)
+## + (void)load {}
+## @end
+##
 ## int main() {
 ##     return 0;
 ## }
@@ -493,6 +503,12 @@ L_OBJC_IMAGE_INFO:
 	b	_OUTLINED_FUNCTION_0
 	.cfi_endproc
                                         ; -- End function
+	.p2align	2
+"+[MyBaseClass(Category04) load]":
+	.cfi_startproc
+; %bb.0:
+	ret
+	.cfi_endproc
 	.globl	_main                           ; -- Begin function main
 	.p2align	2
 _main:                                  ; @main
@@ -746,11 +762,42 @@ __OBJC_$_CATEGORY_MyBaseClass_$_Category03:
 	.quad	0
 	.long	64                              ; 0x40
 	.space	4
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_.15:
+	.asciz	"Category04"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.16:
+	.asciz	"load"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0
+__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category04:
+	.long	24
+	.long	1
+	.quad	l_OBJC_METH_VAR_NAME_.16
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"+[MyBaseClass(Category04) load]"
+	.p2align	3, 0x0
+__OBJC_$_CATEGORY_MyBaseClass_$_Category04:
+	.quad	l_OBJC_CLASS_NAME_.15
+	.quad	_OBJC_CLASS_$_MyBaseClass
+	.quad	0
+	.quad	__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category04
+	.quad	0
+	.quad	0
+	.quad	0
+	.long	64
+	.space	4
 	.section	__DATA,__objc_catlist,regular,no_dead_strip
 	.p2align	3, 0x0                          ; @"OBJC_LABEL_CATEGORY_$"
 l_OBJC_LABEL_CATEGORY_$:
 	.quad	__OBJC_$_CATEGORY_MyBaseClass_$_Category02
 	.quad	__OBJC_$_CATEGORY_MyBaseClass_$_Category03
+	.quad	__OBJC_$_CATEGORY_MyBaseClass_$_Category04
+	.section	__DATA,__objc_nlcatlist,regular,no_dead_strip
+	.p2align	3, 0x0
+l_OBJC_LABEL_NONLAZY_CATEGORY_$:
+	.quad	__OBJC_$_CATEGORY_MyBaseClass_$_Category04
+
 	.no_dead_strip	__OBJC_LABEL_PROTOCOL_$_MyProtocol02
 	.no_dead_strip	__OBJC_LABEL_PROTOCOL_$_MyProtocol03
 	.no_dead_strip	__OBJC_PROTOCOL_$_MyProtocol02

Copy link
Contributor

@kyulee-com kyulee-com left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@alx32 alx32 merged commit c416e43 into llvm:main May 9, 2024
@alx32 alx32 deleted the 10_nl_catlist branch May 9, 2024 13:47
@thurstond
Copy link
Contributor

This might have broken a buildbot:

https://lab.llvm.org/buildbot/#/builders/5/builds/43261/steps/9/logs/stdio

FAIL: lld :: MachO/objc-category-merging-complete-test.s (15243 of 82613)
******************** TEST 'lld :: MachO/objc-category-merging-complete-test.s' FAILED ********************
...
==1885306==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f86a1c54c78 at pc 0x5652c1d32412 bp 0x7f869cef18c0 sp 0x7f869cef1080
READ of size 8 at 0x7f86a1c54c78 thread T5
    #0 0x5652c1d32411 in __asan_memcpy /b/sanitizer-x86_64-linux-fast/build/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:63:3
    #1 0x5652c2a11736 in lld::macho::ConcatInputSection::writeTo(unsigned char*) /b/sanitizer-x86_64-linux-fast/build/llvm-project/lld/MachO/InputSection.cpp:218:3
    #2 0x5652c2991cb3 in lld::macho::ConcatOutputSection::writeTo(unsigned char*) const /b/sanitizer-x86_64-linux-fast/build/llvm-project/lld/MachO/ConcatOutputSection.cpp:361:11
    #3 0x5652c20563b5 in operator() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/ADT/STLFunctionalExtras.h:68:12
    #4 0x5652c20563b5 in operator() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:242:11
    #5 0x5652c20563b5 in __invoke<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:240:16) &> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:150:25
    #6 0x5652c20563b5 in __call<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:240:16) &> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:225:5
    #7 0x5652c20563b5 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:172:12
    #8 0x5652c20563b5 in std::__1::__function::__func<llvm::parallelFor(unsigned long, unsigned long, llvm::function_ref<void (unsigned long)>)::$_0, std::__1::allocator<llvm::parallelFor(unsigned long, unsigned long, llvm::function_ref<void (unsigned long)>)::$_0>, void ()>::operator()() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:314:10
    #9 0x5652c2056164 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:431:12
    #10 0x5652c2056164 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:990:10
    #11 0x5652c2056164 in operator() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:214:11
    #12 0x5652c2056164 in __invoke<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:213:9) &> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:150:25
    #13 0x5652c2056164 in __call<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:213:9) &> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:225:5
    #14 0x5652c2056164 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:172:12
    #15 0x5652c2056164 in std::__1::__function::__func<llvm::parallel::TaskGroup::spawn(std::__1::function<void ()>, bool)::$_0, std::__1::allocator<llvm::parallel::TaskGroup::spawn(std::__1::function<void ()>, bool)::$_0>, void ()>::operator()() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:314:10
    #16 0x5652c204fe99 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:431:12
    #17 0x5652c204fe99 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:990:10
    #18 0x5652c204fe99 in llvm::parallel::detail::(anonymous namespace)::ThreadPoolExecutor::work(llvm::ThreadPoolStrategy, unsigned int) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:141:7
    #19 0x5652c2050414 in operator() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:64:36
    #20 0x5652c2050414 in __invoke<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:64:30)> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:150:25
    #21 0x5652c2050414 in __thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, (lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:64:30)> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__thread/thread.h:193:3
    #22 0x5652c2050414 in void* std::__1::__thread_proxy[abi:nn190000]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, llvm::parallel::detail::(anonymous namespace)::ThreadPoolExecutor::ThreadPoolExecutor(llvm::ThreadPoolStrategy)::'lambda'()::operator()() const::'lambda'()>>(void*) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__thread/thread.h:202:3
    #23 0x5652c1d32058 in asan_thread_start(void*) /b/sanitizer-x86_64-linux-fast/build/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
    #24 0x7f86a5c97b59  (/lib/x86_64-linux-gnu/libc.so.6+0x97b59) (BuildId: 6a981b07a3731293c24c10a21397416d3c3d52ed)
    #25 0x7f86a5d285fb  (/lib/x86_64-linux-gnu/libc.so.6+0x1285fb) (BuildId: 6a981b07a3731293c24c10a21397416d3c3d52ed)
Address 0x7f86a1c54c78 is located in stack of thread T0 at offset 120 in frame
    #0 0x5652c2a30fff in (anonymous namespace)::ObjcCategoryMerger::doMerge() /b/sanitizer-x86_64-linux-fast/build/llvm-project/lld/MachO/ObjC.cpp:1265

@alx32
Copy link
Contributor Author

alx32 commented May 9, 2024

This might have broken a buildbot:

https://lab.llvm.org/buildbot/#/builders/5/builds/43261/steps/9/logs/stdio

FAIL: lld :: MachO/objc-category-merging-complete-test.s (15243 of 82613)
******************** TEST 'lld :: MachO/objc-category-merging-complete-test.s' FAILED ********************
...
==1885306==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f86a1c54c78 at pc 0x5652c1d32412 bp 0x7f869cef18c0 sp 0x7f869cef1080
READ of size 8 at 0x7f86a1c54c78 thread T5
    #0 0x5652c1d32411 in __asan_memcpy /b/sanitizer-x86_64-linux-fast/build/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:63:3
    #1 0x5652c2a11736 in lld::macho::ConcatInputSection::writeTo(unsigned char*) /b/sanitizer-x86_64-linux-fast/build/llvm-project/lld/MachO/InputSection.cpp:218:3
    #2 0x5652c2991cb3 in lld::macho::ConcatOutputSection::writeTo(unsigned char*) const /b/sanitizer-x86_64-linux-fast/build/llvm-project/lld/MachO/ConcatOutputSection.cpp:361:11
    #3 0x5652c20563b5 in operator() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/ADT/STLFunctionalExtras.h:68:12
    #4 0x5652c20563b5 in operator() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:242:11
    #5 0x5652c20563b5 in __invoke<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:240:16) &> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:150:25
    #6 0x5652c20563b5 in __call<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:240:16) &> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:225:5
    #7 0x5652c20563b5 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:172:12
    #8 0x5652c20563b5 in std::__1::__function::__func<llvm::parallelFor(unsigned long, unsigned long, llvm::function_ref<void (unsigned long)>)::$_0, std::__1::allocator<llvm::parallelFor(unsigned long, unsigned long, llvm::function_ref<void (unsigned long)>)::$_0>, void ()>::operator()() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:314:10
    #9 0x5652c2056164 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:431:12
    #10 0x5652c2056164 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:990:10
    #11 0x5652c2056164 in operator() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:214:11
    #12 0x5652c2056164 in __invoke<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:213:9) &> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:150:25
    #13 0x5652c2056164 in __call<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:213:9) &> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:225:5
    #14 0x5652c2056164 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:172:12
    #15 0x5652c2056164 in std::__1::__function::__func<llvm::parallel::TaskGroup::spawn(std::__1::function<void ()>, bool)::$_0, std::__1::allocator<llvm::parallel::TaskGroup::spawn(std::__1::function<void ()>, bool)::$_0>, void ()>::operator()() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:314:10
    #16 0x5652c204fe99 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:431:12
    #17 0x5652c204fe99 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__functional/function.h:990:10
    #18 0x5652c204fe99 in llvm::parallel::detail::(anonymous namespace)::ThreadPoolExecutor::work(llvm::ThreadPoolStrategy, unsigned int) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:141:7
    #19 0x5652c2050414 in operator() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:64:36
    #20 0x5652c2050414 in __invoke<(lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:64:30)> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__type_traits/invoke.h:150:25
    #21 0x5652c2050414 in __thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, (lambda at /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Support/Parallel.cpp:64:30)> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__thread/thread.h:193:3
    #22 0x5652c2050414 in void* std::__1::__thread_proxy[abi:nn190000]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, llvm::parallel::detail::(anonymous namespace)::ThreadPoolExecutor::ThreadPoolExecutor(llvm::ThreadPoolStrategy)::'lambda'()::operator()() const::'lambda'()>>(void*) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__thread/thread.h:202:3
    #23 0x5652c1d32058 in asan_thread_start(void*) /b/sanitizer-x86_64-linux-fast/build/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
    #24 0x7f86a5c97b59  (/lib/x86_64-linux-gnu/libc.so.6+0x97b59) (BuildId: 6a981b07a3731293c24c10a21397416d3c3d52ed)
    #25 0x7f86a5d285fb  (/lib/x86_64-linux-gnu/libc.so.6+0x1285fb) (BuildId: 6a981b07a3731293c24c10a21397416d3c3d52ed)
Address 0x7f86a1c54c78 is located in stack of thread T0 at offset 120 in frame
    #0 0x5652c2a30fff in (anonymous namespace)::ObjcCategoryMerger::doMerge() /b/sanitizer-x86_64-linux-fast/build/llvm-project/lld/MachO/ObjC.cpp:1265

Thanks for pointing this out, I'll forward fix ASAP or revert later today.

@alx32
Copy link
Contributor Author

alx32 commented May 9, 2024

Fixed via #91680

@thurstond
Copy link
Contributor

Fixed via #91680

Thank you @alx32 !

alx32 added a commit that referenced this pull request May 10, 2024
FIxing the address sanitizer issue reported in
#91548 .
The problem comes from the assignment `auto bodyData = newSectionData`
which defaults to `SmallVector<uint8_t> data = newSectionData` - which
actually creates a copy of the data, placed on the stack.
By explicitly using `ArrayRef` instead, we make sure that the original
copy is used.
We also change the assignment in `ObjcCategoryMerger::newStringData`
from `auto` to `SmallVector<uint8_t> &` to make it explicit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants