<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/56440>56440</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
lld:MachO crashes when linking a same ObjC archives from both LC_LINKER_OPTION and command line options
</td>
</tr>
<tr>
<th>Labels</th>
<td>
new issue
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
PRESIDENT810
</td>
</tr>
</table>
<pre>
What causes the crash:
1. Archives loaded because of LC_LINKER_OPTION will not be influenced by -ObjC option, so if there are any ObjC symbols present in such archives, ld64.lld will not lazy-load these symbols.
2. ld64.lld will lazy-load symbols from archives, and cache the loaded archives. If the same archive is encountered again, it first search this file in the cache, and return the cached archive if cache hit.
3. If an ObjC method is invoked dynamically but not statically (no undefined symbol reference to it), then the linker will not link its symbol unless -ObjC flag is specified, which forces the linker to load all ObjC symbols.
Therefore, if there is an archive loaded first due to LC_LINKER_OPTION, it will not lazy-load its ObjC symbols. If such archive is present in our command line option with -ObjC flag, ld64.lld will realize this archive is loaded and just skip it. However, archives loaded from command line option are affected by -ObjC flags, which means even if we need ObjC symbols from those archives, they will not be loaded because previously ld64.lld already load the archive from LC_LINKER_OPTION without -ObjC flag.
I managed to reproduce this problem in https://github.com/PRESIDENT810/LazyArchiveBug. It consists of Foo.framework, Bar.framework and FooBar as the final executable program. When linking FooBar, it first loads Bar.framework, and as ld64.lld is loading Bar.framework, it loads Foo.framework due to Bar.framework's LC_LINKER_OPTION, but without loading its ObjC symbols. Therefore, Foo's ObjC symbols are not linked into the final executable, because they are not explicitly referenced in main.m from FooBar, but rather create an instance dynamically from a string.
In main.m:
```ObjectiveC
Class myClass = NSClassFromString(@"foo");
id myObj = [[myClass alloc] init];
assert(myObj);
[myObj fooFunc];
```
Therefore when FooBar is running, it cannot create myClass because there is no ObjC symbol "foo" in the executable, so myObj will not be initialized successfully and become nil, which causes the assertion to fail.
Note if you exchange the command line arguments' order of `-framework Bar` and `-framework Foo`, and make ld64.lld sees Foo.framework first, then FooBar is linked normally because ld64.lld will lazy load all of Foo's ObjC symbols.
In out project this behavior actually causes our application to crash, but I'm not very good at ObjC and don't know how to make my demo project crash to reproduce the exactly the same scenario. Though I think this one could still give a good explanation.
I think we can record the ForceLoad state in ld64.lld's cached archive, and if the same archive is encountered but with bigger loading scope (i.g. allLoad > forceLoadObjC > noLoad), we should load this archive again and replace the cached one. This should be straightforward to implement. Or we can first process all command line options then deal with libraries we need to load. This should conform to ld64's behavior and eliminate more discrepancy between ld64.lld and ld64. This will certainly requires a bigger change to lld's code. Not sure how to do this right but I might try my first solution for now.
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJyFV9tu2zgQ_Rr7hYjg-Bb7wQ-5YoPNJsW2QB8LWqIkNhTpJam47tfvGZKyJcfYBZwmssi5nDlzZro1xWHzveae5bx1wjFfC5Zb7urR7HY0eRhNbq8zdmvzWn7grTK8EAXbinCamZK93P94eX798_HvH29fvj2_vbK9VIpp43GISV2qVuicrhzY1dv25z0zOy-NHk3vmTNMluTQCsbpRx9YOOIOzdYox3ZWOKE9zDDX5jUOxTDosiqW80yp4uRP8d-HKwqQTCK4ZCWLWUyzsyun45270ppm4IPrArDkQIRQSbl3BzL2HIJnjjei-5ZJx5CvabVHVjhccRlylZ6V0jrPnKCjuIiTpVSEUcSc_HROrfCt7X1fnOyXKaJa-pTZLETCdcSuEb42BcUh9Yd5x9XioHkjc67UgW1bH7Bynvv01Wi60oa1uhCl1KJDAyGUyAClYx5l8qPpmoJDRDEsJfW7sD3w8YxjrrveaiWcSyUvFa8oIrcTuSylKMjUvpbAoTQ2T6xLJuEuVAXBDdiQso3_fiPS4G5A7EgiuAAMHVSpXhH2og2JnLM1leYChyiXgXsCuc9C8tYjqGkty03TUPmQiUg8h2lf92D4TF0ruJK_RWREz3ZHN9j72RJx3uUOUWXsD7MXH8IGrpz1ZWDwpShCe5WlyH2_FSkgd6pFI7gGfT9QYkC6F0wLnB50ZHAAgjkxaBTAfxg0_plOAKcPaVoHuh2T5wqZFwfWdewx9-DjgqzALdh7inxAiGeGnHkFn6iyFTtrijZPoOJhq0RDVaq93zlStukTPhVsttsMgOHhy9-PX58fHl-_ra4neHwBE5Ls3bUVqg-FNNpJB15A9p6MyUqLzt8b-04Q3HF7-iJUDUfwJeOR3Wgurpj4JfLWc4RDUVU4n7Hv1FNEfqmrdGmgGASQG9rvdAK2j3gmypCRT2dlZ2UQdtcUZ8dv3MU2IenoitA5-twkg8aEt2BuQCGiYqcYKJfUiOASQMFn4k_gV3dR_NopmUsPMh1FiuyAAVJnTeTPCUeK23ISCMw1wT3NGZyGApK49cUx6j-00SK3Ibs648ehOFpO4ge5oavAkvv44l5x6F5ziL9Hswf2-jX8_QTrX4NpKO5oDo5NS8JnStI6u4u3ZYGrMBkujhZ3-HSmEKLJR4sHxA45Xjwc7-ClsBDoVbjZtxZukzU4emp13r91TOCirEIRQMrEYDDLtlqHyAOXcq6pEAnOLsBesaIWY6z0Ks-OCXcjb1hrbAMx2OECIb0M-liQ-GJUuLKlWhH74dBg9GqpThrW22IiLqR-YFjJpRqU9NX4ME0PpkUgec11Fcf8QD65rdoGCg-Vu2HGFiARmh-oXZ26iHi2nISIhi-I_sA39WrD38WpW50Q590Yuv04ZE_Yp0bRxjZxhiecP68zp8EZFepT852TmnoZOkQMjlK5FTWHUkO1ct8GbwlQGm98R43HO0jjlpg67Bm-mlA1jKYDqwx2ECyVwTllX9DKd-PZuzZ7VuMHBgIizYEVojHHMILVcxEnriAihHNcuFwuNLfSkOSYtqrZM2WAJSTkYTQVsiWgPaFT0WDhMSzSD8wKSuNshEQDe1q6NPznKHhw-ERLyktYFT0xHvztwA8QD3e0ruDy_9fDTlPZVlYVuNXpqsvNTtBmJjOMHtQhOB_NHuO-RE8BWfpGG3pM-xlid3XIO43V3k4RdtG0XgKBBGyKHYARkrSkxftoPgghl1Xt4XPPbZisstlhkqIhMvZmO6TimEK1qD0D_S6sIC7SusC2E1NWcmtRQHCr2zTS5jeMA0MX_pvwEpgHvE8shQ-hZIPRQUJEwlVIB13aQdypU_xeCN3bOSgmeoguQuPkEAkAE6bJP63ERgeipHp0sgDfXalNAaBeaYVu4S0xuTARaktwxX5gTfjboxlA8bT7G9WG7kFGqNs-GxebWbGerfnYY56JDXmZ3f6FkrzFRiB0-usBj3SKbdUtf2FubQ0w_bQ1hf_BXCjGuLVq8x_LkFIf3a-r1Jl4lM61tPA9LZbz-WRcb5bFer2dbnmZr8r5clXMFvNZsby5WYr5bLUuVmPFt0K5DU2y6VSLPQsmaOgtHsZyM51Mp5ObyWqyxMVVNl1MrpeT6_WSX5fFarHGlBQNCTfFkRlbje0mhLRtsbfOJ4q2sdNLCL6swKTgDvZ5i23FbvqL3Tj434T4_wU79ShG">