Add check to verify libc++ is shared by LLVM/Clang

This check is needed because if static/dynamic linking is mixed incorrectly,
it's possible for Clang and LLVM to end up with duplicate "copies" of libc++.

This is not benign: Static variables are not shared, so equality comparisons
that depend on pointers to static variables will fail. One such failure is
std::generic_category(), which causes POSIX error codes to compare as unequal
when passed between LLVM and Clang.

I believe this is the cause of https://github.com/ziglang/zig/issues/11168

In order to avoid affecting build times when Zig is repeatedly invoked,
we only enable this check for "zig env" and "zig version"
This commit is contained in:
Cody Tapscott 2022-07-11 18:46:24 -07:00
parent 0fc79d602b
commit b0525344a2
4 changed files with 50 additions and 1 deletions

View File

@ -1913,3 +1913,6 @@ extern fn ZigClangLoadFromCommandLine(
errors_len: *usize,
resources_path: [*:0]const u8,
) ?*ASTUnit;
pub const isLLVMUsingSeparateLibcxx = ZigClangIsLLVMUsingSeparateLibcxx;
extern fn ZigClangIsLLVMUsingSeparateLibcxx() bool;

View File

@ -174,6 +174,17 @@ pub fn main() anyerror!void {
return mainArgs(gpa, arena, args);
}
/// Check that LLVM and Clang have been linked properly so that they are using the same
/// libc++ and can safely share objects with pointers to static variables in libc++
fn verifyLibcxxCorrectlyLinked() void {
if (build_options.have_llvm and ZigClangIsLLVMUsingSeparateLibcxx()) {
fatal(
\\Zig was built/linked incorrectly: LLVM and Clang have separate copies of libc++
\\ If you are dynamically linking LLVM, make sure you dynamically link libc++ too
, .{});
}
}
pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
if (args.len <= 1) {
std.log.info("{s}", .{usage});
@ -261,8 +272,12 @@ pub fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
const stdout = io.getStdOut().writer();
return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
} else if (mem.eql(u8, cmd, "version")) {
return std.io.getStdOut().writeAll(build_options.version ++ "\n");
try std.io.getStdOut().writeAll(build_options.version ++ "\n");
// Check libc++ linkage to make sure Zig was built correctly, but only for "env" and "version"
// to avoid affecting the startup time for build-critical commands (check takes about ~10 μs)
return verifyLibcxxCorrectlyLinked();
} else if (mem.eql(u8, cmd, "env")) {
verifyLibcxxCorrectlyLinked();
return @import("print_env.zig").cmdEnv(arena, cmd_args, io.getStdOut().writer());
} else if (mem.eql(u8, cmd, "zen")) {
return io.getStdOut().writeAll(info_zen);
@ -4481,6 +4496,8 @@ pub const info_zen =
\\
;
extern fn ZigClangIsLLVMUsingSeparateLibcxx() bool;
extern "c" fn ZigClang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
extern "c" fn ZigLlvmAr_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;

View File

@ -3432,3 +3432,31 @@ const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct Zi
const llvm::APSInt *result = &casted->getInitVal();
return reinterpret_cast<const ZigClangAPSInt *>(result);
}
// Get a pointer to a static variable in libc++ from LLVM and make sure that
// it matches our own.
//
// This check is needed because if static/dynamic linking is mixed incorrectly,
// it's possible for Clang and LLVM to end up with duplicate "copies" of libc++.
//
// This is not benign: Static variables are not shared, so equality comparisons
// that depend on pointers to static variables will fail. One such failure is
// std::generic_category(), which causes POSIX error codes to compare as unequal
// when passed between LLVM and Clang.
//
// See also: https://github.com/ziglang/zig/issues/11168
bool ZigClangIsLLVMUsingSeparateLibcxx() {
// Temporarily create an InMemoryFileSystem, so that we can perform a file
// lookup that is guaranteed to fail.
auto FS = new llvm::vfs::InMemoryFileSystem(true);
auto StatusOrErr = FS->status("foo.txt");
delete FS;
// This should return a POSIX (generic_category) error code, but if LLVM has
// its own copy of libc++ this will actually be a separate category instance.
assert(!StatusOrErr);
auto EC = StatusOrErr.getError();
return EC.category() != std::generic_category();
}

View File

@ -1418,4 +1418,5 @@ ZIG_EXTERN_C const struct ZigClangRecordDecl *ZigClangFieldDecl_getParent(const
ZIG_EXTERN_C unsigned ZigClangFieldDecl_getFieldIndex(const struct ZigClangFieldDecl *);
ZIG_EXTERN_C const struct ZigClangAPSInt *ZigClangEnumConstantDecl_getInitVal(const struct ZigClangEnumConstantDecl *);
ZIG_EXTERN_C bool ZigClangIsLLVMUsingSeparateLibcxx();
#endif