Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release_build_files/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ code.
### Upcoming
- Changes
- General (Android): **Breaking Change** Updated minSdkVersion to 24.
- General (MacOS Desktop): Fix for linker issue on XCode 27. (#1920)
- Auth: **Breaking Change:** Fixed spelling in `AuthError` enum values (`kAuthErrorWebStorateUnsupported` -> `kAuthErrorWebStorageUnsupported` and `kAuthErrorFederatedProviderAreadyInUse` -> `kAuthErrorFederatedProviderAlreadyInUse`).

### 13.13.0
Expand Down
49 changes: 41 additions & 8 deletions scripts/merge_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,14 @@ def demangle_symbol(symbol):
_cache["demangle"][FLAGS.platform][symbol] = demangled_symbol
break
if FLAGS.platform == "darwin" or FLAGS.platform == "ios":
# On Darwin, try demangling again without the leading _.
demangled_symbol = demangler.demangle(symbol[1:])
if demangled_symbol and (symbol[1:] != demangled_symbol):
_cache["demangle"][FLAGS.platform][symbol] = demangled_symbol
# On Darwin, try demangling again without the leading _ or l_.
for stripped in ([symbol[1:], symbol[2:]]
if symbol.startswith("l__Z") else [symbol[1:]]):
demangled_symbol = demangler.demangle(stripped)
if demangled_symbol and (stripped != demangled_symbol):
_cache["demangle"][FLAGS.platform][symbol] = demangled_symbol
break
if _cache["demangle"][FLAGS.platform][symbol] != symbol:
break
return _cache["demangle"][FLAGS.platform][symbol]

Expand Down Expand Up @@ -398,6 +402,37 @@ def is_cpp_symbol(symbol):
return "@" in symbol or "?" in symbol


def is_ignored_symbol(symbol):
"""Returns True if the given symbol should be ignored and never renamed.

Args:
symbol: Symbol to check.
Returns:
True if the symbol should be ignored, False otherwise.
"""
if FLAGS.platform in ("darwin", "ios"):
# Ignore any Objective-C or Objective-C++ methods and runtime metadata.
if ('[' in symbol or ']' in symbol or
symbol.startswith(("_OBJC_", "_objc_"))):
return True
# Ignore Mach-O assembler temporary labels and compiler metadata labels.
# Every real C or C++ symbol on Mach-O is emitted with a leading underscore
# (or "l__Z" for linker-private C++ symbols), so any non-C++ symbol lacking
# a leading underscore (ltmp*, lCPI*, lJTI*, l_.str, GCC_except_table*, etc.)
# is a compiler- or assembler-generated label that the linker discards or
# handles specially. Renaming an 'l'/'L' label turns it into a real symbol
# ("f_b_l...") at the address of the atom it labelled; when that atom is an
# Objective-C class reference, ld aborts in changeClassRefUseToGotUse with
# "unsupported reference (alias of) to class-ref".
if not symbol.startswith("_") and not is_cpp_symbol(symbol):
return True
elif FLAGS.platform == "windows":
# Don't rename $LN*, those are local symbols.
if symbol.startswith("$LN"):
return True
return False


def get_top_level_namespaces(demangled_symbols):
"""Gets all top-level C++ namespaces from a set of demangled symbols.

Expand Down Expand Up @@ -525,8 +560,7 @@ def read_symbols_from_archive(archive_file):
m = RE_NM_SYMBOLS_PLATFORM[FLAGS.platform].match(line)
if m:
symbol = m.group("symbol")
# Ignore any Objective-C or Objective-C++ methods.
if FLAGS.platform == "darwin" and ('[' in symbol or ']' in symbol):
if is_ignored_symbol(symbol):
continue

all_symbols.add(symbol)
Expand Down Expand Up @@ -654,8 +688,7 @@ def rename_symbol(symbol):
new_symbol = re.sub(r"(?<=[^a-z_])%s@@" % ns, r"%s@@" % new_ns, new_symbol)
new_renames[symbol] = new_symbol
else:
if FLAGS.platform == "windows" and symbol.startswith("$LN"):
# Don't rename $LN*, those are local symbols.
if is_ignored_symbol(symbol):
return new_renames
# C symbol. Just split, rename, and re-join.
(prefix, remainder) = split_symbol(symbol)
Expand Down
161 changes: 161 additions & 0 deletions scripts/merge_libraries_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,167 @@ def test_renaming_cpp_symbols(self):
if os.path.exists(tempdir):
shutil.rmtree(tempdir)

def test_macho_ignored_symbols(self):
"""Verify Mach-O temporary labels and Objective-C metadata are ignored."""
prev_platform = merge_libraries.FLAGS.platform
prev_namespaces = merge_libraries.FLAGS.hide_cpp_namespaces
prev_rename = merge_libraries.FLAGS.rename_string
try:
for platform in ("darwin", "ios"):
merge_libraries.FLAGS.platform = platform
merge_libraries.FLAGS.hide_cpp_namespaces = ["test_namespace"]
merge_libraries.FLAGS.rename_string = "f_b_"
merge_libraries.init_cache()
merge_libraries.init_demanglers()

ignored_symbols = [
# Assembler temporary labels
"ltmp0",
"ltmp1",
"lCPI0_0",
"lJTI0_0",
"l_.str",
"l_.str.1",
"l___PRETTY_FUNCTION__.foo",
"l__unnamed_cfstring_",
"L_OBJC_METH_VAR_NAME_0",
# Compiler metadata/exception labels without leading underscore
"GCC_except_table0",
"GCC_except_table14",
"EH_Frame1",
"func.eh",
# Objective-C methods and runtime metadata
"-[FIRApp configure]",
"+[FIRApp configure]",
"_OBJC_CLASSLIST_REFERENCES_$_",
"_OBJC_CLASSLIST_REFERENCES_$_.1",
"_OBJC_CLASS_$_NSError",
"_OBJC_METACLASS_$_FIRApp",
"_OBJC_IVAR_$_SomeClass._ivar",
"_OBJC_SELECTOR_REFERENCES_",
"_objc_msgSend$currentLocale",
]
for sym in ignored_symbols:
self.assertTrue(
merge_libraries.is_ignored_symbol(sym),
f"Expected {sym} to be ignored on {platform}")
self.assertEqual(
merge_libraries.rename_symbol(sym), {},
f"Expected {sym} not to be renamed on {platform}")

# Real C symbols should not be ignored and should be renamed.
self.assertFalse(merge_libraries.is_ignored_symbol("_global_c_symbol"))
self.assertEqual(
merge_libraries.rename_symbol("_global_c_symbol"),
{"_global_c_symbol": "_f_b_global_c_symbol"})

# Linker-private C++ symbols (l__Z*) should be recognized as C++ symbols,
# not ignored, and renamed while preserving the leading 'l__Z' prefix.
private_cpp_sym = "l__ZN14test_namespace9TestClass10TestMethodEv"
self.assertTrue(merge_libraries.is_cpp_symbol(private_cpp_sym))
self.assertFalse(merge_libraries.is_ignored_symbol(private_cpp_sym))
self.assertEqual(
merge_libraries.rename_symbol(private_cpp_sym),
{
private_cpp_sym:
"l__ZN18f_b_test_namespace9TestClass10TestMethodEv"
})

merge_libraries.shutdown_demanglers()
merge_libraries.shutdown_cache()
finally:
merge_libraries.FLAGS.platform = prev_platform
merge_libraries.FLAGS.hide_cpp_namespaces = prev_namespaces
merge_libraries.FLAGS.rename_string = prev_rename

def test_macho_objc_classref_linking(self):
"""Verify Objective-C++ object files filter temporary/ObjC symbols cleanly."""
if merge_libraries.FLAGS.platform != "darwin":
return

tempdir = tempfile.mkdtemp()
cwd = os.getcwd()
os.chdir(tempdir)
prev_rename = merge_libraries.FLAGS.rename_string
try:
merge_libraries.FLAGS.rename_string = "f_b_"
objc_src = os.path.join(tempdir, "test_objc.mm")
objc_obj = os.path.join(tempdir, "test_objc.o")
objc_archive = os.path.join(tempdir, "libtest_objc.a")
main_src = os.path.join(tempdir, "main.cc")
exe_out = os.path.join(tempdir, "test_exe")

with open(objc_src, "w") as f:
f.write("""
#import <Foundation/Foundation.h>
extern "C" void test_objc_c_func() {
NSLocale* loc = [NSLocale currentLocale];
NSError* err = [NSError errorWithDomain:[loc localeIdentifier] code:1 userInfo:nil];
(void)err;
}
""")
with open(main_src, "w") as f:
f.write("""
extern "C" void f_b_test_objc_c_func();
int main() {
f_b_test_objc_c_func();
return 0;
}
""")

subprocess.run(["clang++", "-c", objc_src, "-o", objc_obj], check=True)
subprocess.run(
[merge_libraries.FLAGS.binutils_ar_cmd, "rcs", objc_archive, objc_obj],
check=True)

(defined_symbols, all_symbols) = merge_libraries.read_symbols_from_archive(
objc_archive)

# Verify no ltmp*, l_.str*, or _OBJC_* symbols were read.
for sym in all_symbols:
self.assertFalse(
sym.startswith(("ltmp", "l_.str", "lCPI", "lJTI", "GCC_except_table")),
f"Unexpected compiler/assembler temporary symbol: {sym}")
self.assertFalse(
sym.startswith(("_OBJC_", "_objc_")),
f"Unexpected Objective-C runtime symbol: {sym}")

rename_symbols = {}
for sym in defined_symbols:
if not merge_libraries.is_cpp_symbol(sym):
rename_symbols.update(merge_libraries.rename_symbol(sym))

self.assertEqual(rename_symbols,
{"_test_objc_c_func": "_f_b_test_objc_c_func"})

redefinition_file = merge_libraries.create_symbol_redefinition_file(
rename_symbols)
self.assertIsNotNone(redefinition_file)

renamed_obj = os.path.join(tempdir, "test_objc_renamed.o")
merge_libraries.move_object_file(objc_obj, renamed_obj,
redefinition_file.name)

after_defined = merge_libraries.read_symbols_from_archive(renamed_obj)[0]
self.assertIn("_f_b_test_objc_c_func", after_defined)
self.assertNotIn("_test_objc_c_func", after_defined)

# When using llvm-objcopy (which preserves arm64 Mach-O compact unwind
# sections), also verify end-to-end linking with Apple's linker.
if "llvm-objcopy" in merge_libraries.FLAGS.binutils_objcopy_cmd:
subprocess.run(
[
"clang++", main_src, renamed_obj, "-framework", "Foundation",
"-o", exe_out
],
check=True)
self.assertTrue(os.path.isfile(exe_out))
finally:
merge_libraries.FLAGS.rename_string = prev_rename
os.chdir(cwd)
if os.path.exists(tempdir):
shutil.rmtree(tempdir)


if __name__ == "__main__":
absltest.main()
6 changes: 5 additions & 1 deletion scripts/merge_libraries_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ while getopts "t:P:Lh" opt; do
esac
done

readonly demangle_cmds=${tools_path}/c++filt,${tools_path}/demumble
if [[ -x ${tools_path}/demumble ]]; then
readonly demangle_cmds=${tools_path}/c++filt,${tools_path}/demumble
else
readonly demangle_cmds=${tools_path}/c++filt
fi
if [[ ${use_llvm_binutils} -eq 1 ]]; then
readonly binutils_objcopy=${tools_path}/llvm-objcopy
readonly binutils_nm=${tools_path}/llvm-nm
Expand Down
Loading