Skip to content

Commit a4de589

Browse files
authored
[InstallAPI] Add support for parsing dSYMs (#86852)
InstallAPI does not directly look at object files in the dylib for verification. To help diagnose violations where a declaration is undiscovered in headers, parse the dSYM and look up the source location for symbols. Emitting out the source location with a diagnostic is enough for some IDE's (e.g. Xcode) to have them map back to editable source files.
1 parent 0f6ed4c commit a4de589

File tree

12 files changed

+263
-22
lines changed

12 files changed

+263
-22
lines changed

clang/include/clang/InstallAPI/DylibVerifier.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ enum class VerificationMode {
3131
class DylibVerifier : llvm::MachO::RecordVisitor {
3232
private:
3333
struct SymbolContext;
34+
struct DWARFContext;
3435

3536
public:
3637
enum class Result { NoVerify, Ignore, Valid, Invalid };
@@ -54,7 +55,7 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
5455
DiagnosticsEngine *Diag = nullptr;
5556

5657
// Handle diagnostics reporting for target level violations.
57-
void emitDiag(llvm::function_ref<void()> Report);
58+
void emitDiag(llvm::function_ref<void()> Report, RecordLoc *Loc = nullptr);
5859

5960
VerifierContext() = default;
6061
VerifierContext(DiagnosticsEngine *Diag) : Diag(Diag) {}
@@ -63,9 +64,10 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
6364
DylibVerifier() = default;
6465

6566
DylibVerifier(llvm::MachO::Records &&Dylib, DiagnosticsEngine *Diag,
66-
VerificationMode Mode, bool Demangle)
67+
VerificationMode Mode, bool Demangle, StringRef DSYMPath)
6768
: Dylib(std::move(Dylib)), Mode(Mode), Demangle(Demangle),
68-
Exports(std::make_unique<SymbolSet>()), Ctx(VerifierContext{Diag}) {}
69+
DSYMPath(DSYMPath), Exports(std::make_unique<SymbolSet>()),
70+
Ctx(VerifierContext{Diag}) {}
6971

7072
Result verify(GlobalRecord *R, const FrontendAttrs *FA);
7173
Result verify(ObjCInterfaceRecord *R, const FrontendAttrs *FA);
@@ -143,6 +145,12 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
143145
std::string getAnnotatedName(const Record *R, SymbolContext &SymCtx,
144146
bool ValidSourceLoc = true);
145147

148+
/// Extract source location for symbol implementations.
149+
/// As this is a relatively expensive operation, it is only used
150+
/// when there is a violation to report and there is not a known declaration
151+
/// in the interface.
152+
void accumulateSrcLocForDylibSymbols();
153+
146154
// Symbols in dylib.
147155
llvm::MachO::Records Dylib;
148156

@@ -152,11 +160,17 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
152160
// Attempt to demangle when reporting violations.
153161
bool Demangle = false;
154162

163+
// File path to DSYM file.
164+
StringRef DSYMPath;
165+
155166
// Valid symbols in final text file.
156167
std::unique_ptr<SymbolSet> Exports = std::make_unique<SymbolSet>();
157168

158169
// Track current state of verification while traversing AST.
159170
VerifierContext Ctx;
171+
172+
// Track DWARF provided source location for dylibs.
173+
DWARFContext *DWARFCtx = nullptr;
160174
};
161175

162176
} // namespace installapi

clang/include/clang/InstallAPI/MachO.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ using ObjCCategoryRecord = llvm::MachO::ObjCCategoryRecord;
3434
using ObjCIVarRecord = llvm::MachO::ObjCIVarRecord;
3535
using ObjCIFSymbolKind = llvm::MachO::ObjCIFSymbolKind;
3636
using Records = llvm::MachO::Records;
37+
using RecordLoc = llvm::MachO::RecordLoc;
3738
using RecordsSlice = llvm::MachO::RecordsSlice;
3839
using BinaryAttrs = llvm::MachO::RecordsSlice::BinaryAttrs;
3940
using SymbolSet = llvm::MachO::SymbolSet;

clang/lib/InstallAPI/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
set(LLVM_LINK_COMPONENTS
22
Support
33
TextAPI
4+
TextAPIBinaryReader
45
Demangle
56
Core
67
)

clang/lib/InstallAPI/DylibVerifier.cpp

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "clang/InstallAPI/FrontendRecords.h"
1111
#include "clang/InstallAPI/InstallAPIDiagnostic.h"
1212
#include "llvm/Demangle/Demangle.h"
13+
#include "llvm/TextAPI/DylibReader.h"
1314

1415
using namespace llvm::MachO;
1516

@@ -35,6 +36,14 @@ struct DylibVerifier::SymbolContext {
3536
bool Inlined = false;
3637
};
3738

39+
struct DylibVerifier::DWARFContext {
40+
// Track whether DSYM parsing has already been attempted to avoid re-parsing.
41+
bool ParsedDSYM{false};
42+
43+
// Lookup table for source locations by symbol name.
44+
DylibReader::SymbolToSourceLocMap SourceLocs{};
45+
};
46+
3847
static bool isCppMangled(StringRef Name) {
3948
// InstallAPI currently only supports itanium manglings.
4049
return (Name.starts_with("_Z") || Name.starts_with("__Z") ||
@@ -511,14 +520,16 @@ DylibVerifier::Result DylibVerifier::verify(GlobalRecord *R,
511520
return verifyImpl(R, SymCtx);
512521
}
513522

514-
void DylibVerifier::VerifierContext::emitDiag(
515-
llvm::function_ref<void()> Report) {
523+
void DylibVerifier::VerifierContext::emitDiag(llvm::function_ref<void()> Report,
524+
RecordLoc *Loc) {
516525
if (!DiscoveredFirstError) {
517526
Diag->Report(diag::warn_target)
518527
<< (PrintArch ? getArchitectureName(Target.Arch)
519528
: getTargetTripleName(Target));
520529
DiscoveredFirstError = true;
521530
}
531+
if (Loc && Loc->isValid())
532+
llvm::errs() << Loc->File << ":" << Loc->Line << ":" << 0 << ": ";
522533

523534
Report();
524535
}
@@ -561,37 +572,49 @@ void DylibVerifier::visitSymbolInDylib(const Record &R, SymbolContext &SymCtx) {
561572
return;
562573
}
563574

564-
// All checks at this point classify as some kind of violation that should be
565-
// reported.
575+
const bool IsLinkerSymbol = SymbolName.starts_with("$ld$");
576+
577+
// All checks at this point classify as some kind of violation.
578+
// The different verification modes dictate whether they are reported to the
579+
// user.
580+
if (IsLinkerSymbol || (Mode > VerificationMode::ErrorsOnly))
581+
accumulateSrcLocForDylibSymbols();
582+
RecordLoc Loc = DWARFCtx->SourceLocs.lookup(SymCtx.SymbolName);
566583

567584
// Regardless of verification mode, error out on mismatched special linker
568585
// symbols.
569-
if (SymbolName.starts_with("$ld$")) {
570-
Ctx.emitDiag([&]() {
571-
Ctx.Diag->Report(diag::err_header_symbol_missing)
572-
<< getAnnotatedName(&R, SymCtx, /*ValidSourceLoc=*/false);
573-
});
586+
if (IsLinkerSymbol) {
587+
Ctx.emitDiag(
588+
[&]() {
589+
Ctx.Diag->Report(diag::err_header_symbol_missing)
590+
<< getAnnotatedName(&R, SymCtx, Loc.isValid());
591+
},
592+
&Loc);
574593
updateState(Result::Invalid);
575594
return;
576595
}
577596

578597
// Missing declarations for exported symbols are hard errors on Pedantic mode.
579598
if (Mode == VerificationMode::Pedantic) {
580-
Ctx.emitDiag([&]() {
581-
Ctx.Diag->Report(diag::err_header_symbol_missing)
582-
<< getAnnotatedName(&R, SymCtx, /*ValidSourceLoc=*/false);
583-
});
599+
Ctx.emitDiag(
600+
[&]() {
601+
Ctx.Diag->Report(diag::err_header_symbol_missing)
602+
<< getAnnotatedName(&R, SymCtx, Loc.isValid());
603+
},
604+
&Loc);
584605
updateState(Result::Invalid);
585606
return;
586607
}
587608

588609
// Missing declarations for exported symbols are warnings on ErrorsAndWarnings
589610
// mode.
590611
if (Mode == VerificationMode::ErrorsAndWarnings) {
591-
Ctx.emitDiag([&]() {
592-
Ctx.Diag->Report(diag::warn_header_symbol_missing)
593-
<< getAnnotatedName(&R, SymCtx, /*ValidSourceLoc=*/false);
594-
});
612+
Ctx.emitDiag(
613+
[&]() {
614+
Ctx.Diag->Report(diag::warn_header_symbol_missing)
615+
<< getAnnotatedName(&R, SymCtx, Loc.isValid());
616+
},
617+
&Loc);
595618
updateState(Result::Ignore);
596619
return;
597620
}
@@ -622,6 +645,18 @@ void DylibVerifier::visitObjCIVar(const ObjCIVarRecord &R,
622645
visitSymbolInDylib(R, SymCtx);
623646
}
624647

648+
void DylibVerifier::accumulateSrcLocForDylibSymbols() {
649+
if (DSYMPath.empty())
650+
return;
651+
652+
assert(DWARFCtx != nullptr && "Expected an initialized DWARFContext");
653+
if (DWARFCtx->ParsedDSYM)
654+
return;
655+
DWARFCtx->ParsedDSYM = true;
656+
DWARFCtx->SourceLocs =
657+
DylibReader::accumulateSourceLocFromDSYM(DSYMPath, Ctx.Target);
658+
}
659+
625660
void DylibVerifier::visitObjCInterface(const ObjCInterfaceRecord &R) {
626661
if (R.isVerified())
627662
return;
@@ -655,6 +690,8 @@ DylibVerifier::Result DylibVerifier::verifyRemainingSymbols() {
655690
return Result::NoVerify;
656691
assert(!Dylib.empty() && "No binary to verify against");
657692

693+
DWARFContext DWARFInfo;
694+
DWARFCtx = &DWARFInfo;
658695
Ctx.DiscoveredFirstError = false;
659696
Ctx.PrintArch = true;
660697
for (std::shared_ptr<RecordsSlice> Slice : Dylib) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
; REQUIRES: system-darwin
2+
3+
; RUN: rm -rf %t
4+
; RUN: split-file %s %t
5+
6+
// Build a simple dylib with debug info.
7+
; RUN: %clang --target=arm64-apple-macos13 -g -dynamiclib %t/foo.c \
8+
; RUN: -current_version 1 -compatibility_version 1 -L%t/usr/lib \
9+
; RUN: -save-temps \
10+
; RUN: -o %t/foo.dylib -install_name %t/foo.dylib
11+
; RUN: dsymutil %t/foo.dylib -o %t/foo.dSYM
12+
13+
; RUN: not clang-installapi -x c++ --target=arm64-apple-macos13 \
14+
; RUN: -install_name %t/foo.dylib \
15+
; RUN: -current_version 1 -compatibility_version 1 \
16+
; RUN: -o %t/output.tbd \
17+
; RUN: --verify-against=%t/foo.dylib --dsym=%t/foo.dSYM \
18+
; RUN: --verify-mode=Pedantic 2>&1 | FileCheck %s
19+
20+
; CHECK: violations found for arm64
21+
; CHECK: foo.c:5:0: error: no declaration found for exported symbol 'bar' in dynamic library
22+
; CHECK: foo.c:1:0: error: no declaration found for exported symbol 'foo' in dynamic library
23+
24+
;--- foo.c
25+
int foo(void) {
26+
return 1;
27+
}
28+
extern char bar;
29+
char bar = 'a';
30+
31+
;--- usr/lib/libSystem.tbd
32+
{
33+
"main_library": {
34+
"install_names": [
35+
{"name": "/usr/lib/libSystem.B.dylib"}
36+
],
37+
"target_info": [
38+
{"target": "arm64-macos"}
39+
]
40+
},
41+
"tapi_tbd_version": 5
42+
}
43+

clang/tools/clang-installapi/InstallAPIOpts.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ def verify_mode_EQ : Joined<["--"], "verify-mode=">,
2929
HelpText<"Specify the severity and extend of the validation. Valid modes are ErrorsOnly, ErrorsAndWarnings, and Pedantic.">;
3030
def demangle : Flag<["--", "-"], "demangle">,
3131
HelpText<"Demangle symbols when printing warnings and errors">;
32+
def dsym: Joined<["--"], "dsym=">,
33+
MetaVarName<"<path>">, HelpText<"Specify dSYM path for enriched diagnostics.">;
3234

3335
// Additional input options.
3436
def extra_project_header : Separate<["-"], "extra-project-header">,

clang/tools/clang-installapi/Options.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ Options::processAndFilterOutInstallAPIOptions(ArrayRef<const char *> Args) {
241241
if (const Arg *A = ParsedArgs.getLastArg(OPT_verify_against))
242242
DriverOpts.DylibToVerify = A->getValue();
243243

244+
if (const Arg *A = ParsedArgs.getLastArg(OPT_dsym))
245+
DriverOpts.DSYMPath = A->getValue();
246+
244247
// Handle exclude & extra header directories or files.
245248
auto handleAdditionalInputArgs = [&](PathSeq &Headers,
246249
clang::installapi::ID OptID) {
@@ -522,7 +525,8 @@ InstallAPIContext Options::createContext() {
522525
}
523526

524527
Ctx.Verifier = std::make_unique<DylibVerifier>(
525-
std::move(*Slices), Diags, DriverOpts.VerifyMode, DriverOpts.Demangle);
528+
std::move(*Slices), Diags, DriverOpts.VerifyMode, DriverOpts.Demangle,
529+
DriverOpts.DSYMPath);
526530
return Ctx;
527531
}
528532

clang/tools/clang-installapi/Options.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ struct DriverOptions {
6767
/// \brief Output path.
6868
std::string OutputPath;
6969

70+
/// \brief DSYM path.
71+
std::string DSYMPath;
72+
7073
/// \brief File encoding to print.
7174
FileType OutFT = FileType::TBD_V5;
7275

llvm/include/llvm/TextAPI/DylibReader.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#ifndef LLVM_TEXTAPI_DYLIBREADER_H
1414
#define LLVM_TEXTAPI_DYLIBREADER_H
1515

16+
#include "llvm/ADT/StringMap.h"
1617
#include "llvm/Support/Error.h"
1718
#include "llvm/Support/MemoryBuffer.h"
1819
#include "llvm/TextAPI/ArchitectureSet.h"
@@ -43,6 +44,14 @@ Expected<Records> readFile(MemoryBufferRef Buffer, const ParseOption &Opt);
4344
/// \param Buffer Data that points to dylib.
4445
Expected<std::unique_ptr<InterfaceFile>> get(MemoryBufferRef Buffer);
4546

47+
using SymbolToSourceLocMap = llvm::StringMap<RecordLoc>;
48+
/// Get the source location for each symbol from dylib.
49+
///
50+
/// \param DSYM Path to DSYM file.
51+
/// \param T Requested target slice for dylib.
52+
SymbolToSourceLocMap accumulateSourceLocFromDSYM(const StringRef DSYM,
53+
const Target &T);
54+
4655
} // namespace llvm::MachO::DylibReader
4756

4857
#endif // LLVM_TEXTAPI_DYLIBREADER_H

llvm/include/llvm/TextAPI/Record.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@ LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
2727

2828
class RecordsSlice;
2929

30+
// Defines lightweight source location for records.
31+
struct RecordLoc {
32+
RecordLoc() = default;
33+
RecordLoc(std::string File, unsigned Line)
34+
: File(std::move(File)), Line(Line) {}
35+
36+
/// Whether there is source location tied to the RecordLoc object.
37+
bool isValid() const { return !File.empty(); }
38+
39+
bool operator==(const RecordLoc &O) const {
40+
return std::tie(File, Line) == std::tie(O.File, O.Line);
41+
}
42+
43+
const std::string File;
44+
const unsigned Line = 0;
45+
};
46+
3047
// Defines a list of linkage types.
3148
enum class RecordLinkage : uint8_t {
3249
// Unknown linkage.

llvm/lib/TextAPI/BinaryReader/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_llvm_component_library(LLVMTextAPIBinaryReader
22
DylibReader.cpp
33

44
LINK_COMPONENTS
5+
DebugInfoDWARF
56
Support
67
Object
78
TextAPI

0 commit comments

Comments
 (0)