1//===- DylibVerifier.cpp ----------------------------------------*- C++--*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/InstallAPI/DylibVerifier.h"
10#include "DiagnosticBuilderWrappers.h"
11#include "clang/InstallAPI/FrontendRecords.h"
12#include "clang/InstallAPI/InstallAPIDiagnostic.h"
13#include "llvm/Demangle/Demangle.h"
14#include "llvm/TextAPI/DylibReader.h"
15
16using namespace llvm::MachO;
17
18namespace clang {
19namespace installapi {
20
21ArchitectureSet &LibAttrs::getArchSet(StringRef Attr) {
22 auto *It = llvm::find_if(Range&: LibraryAttributes, P: [&Attr](const auto &Input) {
23 return Attr == Input.first;
24 });
25 if (It != LibraryAttributes.end())
26 return It->second;
27 LibraryAttributes.push_back(Elt: {Attr.str(), ArchitectureSet()});
28 return LibraryAttributes.back().second;
29}
30
31std::optional<LibAttrs::Entry> LibAttrs::find(StringRef Attr) const {
32 auto *It = llvm::find_if(Range: LibraryAttributes, P: [&Attr](const auto &Input) {
33 return Attr == Input.first;
34 });
35 if (It == LibraryAttributes.end())
36 return std::nullopt;
37 return *It;
38}
39
40/// Metadata stored about a mapping of a declaration to a symbol.
41struct DylibVerifier::SymbolContext {
42 // Name to use for all querying and verification
43 // purposes.
44 std::string SymbolName{""};
45
46 // Kind to map symbol type against record.
47 EncodeKind Kind = EncodeKind::GlobalSymbol;
48
49 // Frontend Attributes tied to the AST.
50 const FrontendAttrs *FA = nullptr;
51
52 // The ObjCInterface symbol type, if applicable.
53 ObjCIFSymbolKind ObjCIFKind = ObjCIFSymbolKind::None;
54
55 // Whether Decl is inlined.
56 bool Inlined = false;
57};
58
59struct DylibVerifier::DWARFContext {
60 // Track whether DSYM parsing has already been attempted to avoid re-parsing.
61 bool ParsedDSYM{false};
62
63 // Lookup table for source locations by symbol name.
64 DylibReader::SymbolToSourceLocMap SourceLocs{};
65};
66
67static bool isCppMangled(StringRef Name) {
68 // InstallAPI currently only supports itanium manglings.
69 return (Name.starts_with(Prefix: "_Z") || Name.starts_with(Prefix: "__Z") ||
70 Name.starts_with(Prefix: "___Z"));
71}
72
73static std::string demangle(StringRef Name) {
74 // InstallAPI currently only supports itanium manglings.
75 if (!isCppMangled(Name))
76 return Name.str();
77 char *Result = llvm::itaniumDemangle(mangled_name: Name);
78 if (!Result)
79 return Name.str();
80
81 std::string Demangled(Result);
82 free(ptr: Result);
83 return Demangled;
84}
85
86std::string DylibVerifier::getAnnotatedName(const Record *R,
87 SymbolContext &SymCtx,
88 bool ValidSourceLoc) {
89 assert(!SymCtx.SymbolName.empty() && "Expected symbol name");
90
91 const StringRef SymbolName = SymCtx.SymbolName;
92 std::string PrettyName =
93 (Demangle && (SymCtx.Kind == EncodeKind::GlobalSymbol))
94 ? demangle(Name: SymbolName)
95 : SymbolName.str();
96
97 std::string Annotation;
98 if (R->isWeakDefined())
99 Annotation += "(weak-def) ";
100 if (R->isWeakReferenced())
101 Annotation += "(weak-ref) ";
102 if (R->isThreadLocalValue())
103 Annotation += "(tlv) ";
104
105 // Check if symbol represents only part of a @interface declaration.
106 switch (SymCtx.ObjCIFKind) {
107 default:
108 break;
109 case ObjCIFSymbolKind::EHType:
110 return Annotation + "Exception Type of " + PrettyName;
111 case ObjCIFSymbolKind::MetaClass:
112 return Annotation + "Metaclass of " + PrettyName;
113 case ObjCIFSymbolKind::Class:
114 return Annotation + "Class of " + PrettyName;
115 }
116
117 // Only print symbol type prefix or leading "_" if there is no source location
118 // tied to it. This can only ever happen when the location has to come from
119 // debug info.
120 if (ValidSourceLoc) {
121 StringRef PrettyNameRef(PrettyName);
122 if ((SymCtx.Kind == EncodeKind::GlobalSymbol) &&
123 !isCppMangled(Name: SymbolName) && PrettyNameRef.starts_with(Prefix: "_"))
124 return Annotation + PrettyNameRef.drop_front(N: 1).str();
125 return Annotation + PrettyName;
126 }
127
128 switch (SymCtx.Kind) {
129 case EncodeKind::GlobalSymbol:
130 return Annotation + PrettyName;
131 case EncodeKind::ObjectiveCInstanceVariable:
132 return Annotation + "(ObjC IVar) " + PrettyName;
133 case EncodeKind::ObjectiveCClass:
134 return Annotation + "(ObjC Class) " + PrettyName;
135 case EncodeKind::ObjectiveCClassEHType:
136 return Annotation + "(ObjC Class EH) " + PrettyName;
137 }
138
139 llvm_unreachable("unexpected case for EncodeKind");
140}
141
142static DylibVerifier::Result updateResult(const DylibVerifier::Result Prev,
143 const DylibVerifier::Result Curr) {
144 if (Prev == Curr)
145 return Prev;
146
147 // Never update from invalid or noverify state.
148 if ((Prev == DylibVerifier::Result::Invalid) ||
149 (Prev == DylibVerifier::Result::NoVerify))
150 return Prev;
151
152 // Don't let an ignored verification remove a valid one.
153 if (Prev == DylibVerifier::Result::Valid &&
154 Curr == DylibVerifier::Result::Ignore)
155 return Prev;
156
157 return Curr;
158}
159// __private_extern__ is a deprecated specifier that clang does not
160// respect in all contexts, it should just be considered hidden for InstallAPI.
161static bool shouldIgnorePrivateExternAttr(const Decl *D) {
162 if (const FunctionDecl *FD = cast<FunctionDecl>(Val: D))
163 return FD->getStorageClass() == StorageClass::SC_PrivateExtern;
164 if (const VarDecl *VD = cast<VarDecl>(Val: D))
165 return VD->getStorageClass() == StorageClass::SC_PrivateExtern;
166
167 return false;
168}
169
170Record *findRecordFromSlice(const RecordsSlice *Slice, StringRef Name,
171 EncodeKind Kind) {
172 switch (Kind) {
173 case EncodeKind::GlobalSymbol:
174 return Slice->findGlobal(Name);
175 case EncodeKind::ObjectiveCInstanceVariable:
176 return Slice->findObjCIVar(IsScopedName: Name.contains(C: '.'), Name);
177 case EncodeKind::ObjectiveCClass:
178 case EncodeKind::ObjectiveCClassEHType:
179 return Slice->findObjCInterface(Name);
180 }
181 llvm_unreachable("unexpected end when finding record");
182}
183
184void DylibVerifier::updateState(Result State) {
185 Ctx.FrontendState = updateResult(Prev: Ctx.FrontendState, Curr: State);
186}
187
188void DylibVerifier::addSymbol(const Record *R, SymbolContext &SymCtx,
189 TargetList &&Targets) {
190 if (Targets.empty())
191 Targets = {Ctx.Target};
192
193 Exports->addGlobal(Kind: SymCtx.Kind, Name: SymCtx.SymbolName, Flags: R->getFlags(), Targets);
194}
195
196bool DylibVerifier::shouldIgnoreObsolete(const Record *R, SymbolContext &SymCtx,
197 const Record *DR) {
198 if (!SymCtx.FA->Avail.isObsoleted())
199 return false;
200
201 if (Zippered)
202 DeferredZipperedSymbols[SymCtx.SymbolName].emplace_back(args: ZipperedDeclSource{
203 .FA: SymCtx.FA, .SrcMgr: &Ctx.Diag->getSourceManager(), .T: Ctx.Target});
204 return true;
205}
206
207bool DylibVerifier::shouldIgnoreReexport(const Record *R,
208 SymbolContext &SymCtx) const {
209 StringRef SymName = SymCtx.SymbolName;
210 // Linker directive symbols can never be ignored.
211 if (SymName.starts_with(Prefix: "$ld$"))
212 return false;
213
214 if (Reexports.empty())
215 return false;
216
217 for (const InterfaceFile &Lib : Reexports) {
218 if (!Lib.hasTarget(Targ: Ctx.Target))
219 continue;
220 if (auto Sym = Lib.getSymbol(Kind: SymCtx.Kind, Name: SymName, ObjCIF: SymCtx.ObjCIFKind))
221 if ((*Sym)->hasTarget(Targ: Ctx.Target))
222 return true;
223 }
224 return false;
225}
226
227bool DylibVerifier::shouldIgnoreInternalZipperedSymbol(
228 const Record *R, const SymbolContext &SymCtx) const {
229 if (!Zippered)
230 return false;
231
232 return Exports->findSymbol(Kind: SymCtx.Kind, Name: SymCtx.SymbolName,
233 ObjCIF: SymCtx.ObjCIFKind) != nullptr;
234}
235
236bool DylibVerifier::shouldIgnoreZipperedAvailability(const Record *R,
237 SymbolContext &SymCtx) {
238 if (!(Zippered && SymCtx.FA->Avail.isUnavailable()))
239 return false;
240
241 // Collect source location incase there is an exported symbol to diagnose
242 // during `verifyRemainingSymbols`.
243 DeferredZipperedSymbols[SymCtx.SymbolName].emplace_back(
244 args: ZipperedDeclSource{.FA: SymCtx.FA, .SrcMgr: SourceManagers.back().get(), .T: Ctx.Target});
245
246 return true;
247}
248
249bool DylibVerifier::compareObjCInterfaceSymbols(const Record *R,
250 SymbolContext &SymCtx,
251 const ObjCInterfaceRecord *DR) {
252 const bool IsDeclVersionComplete =
253 ((SymCtx.ObjCIFKind & ObjCIFSymbolKind::Class) ==
254 ObjCIFSymbolKind::Class) &&
255 ((SymCtx.ObjCIFKind & ObjCIFSymbolKind::MetaClass) ==
256 ObjCIFSymbolKind::MetaClass);
257
258 const bool IsDylibVersionComplete = DR->isCompleteInterface();
259
260 // The common case, a complete ObjCInterface.
261 if (IsDeclVersionComplete && IsDylibVersionComplete)
262 return true;
263
264 auto PrintDiagnostic = [&](auto SymLinkage, const Record *Record,
265 StringRef SymName, bool PrintAsWarning = false) {
266 if (SymLinkage == RecordLinkage::Unknown)
267 Ctx.emitDiag(Report: [&]() {
268 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: PrintAsWarning
269 ? diag::warn_library_missing_symbol
270 : diag::err_library_missing_symbol)
271 << SymName;
272 });
273 else
274 Ctx.emitDiag(Report: [&]() {
275 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: PrintAsWarning
276 ? diag::warn_library_hidden_symbol
277 : diag::err_library_hidden_symbol)
278 << SymName;
279 });
280 };
281
282 if (IsDeclVersionComplete) {
283 // The decl represents a complete ObjCInterface, but the symbols in the
284 // dylib do not. Determine which symbol is missing. To keep older projects
285 // building, treat this as a warning.
286 if (!DR->isExportedSymbol(CurrType: ObjCIFSymbolKind::Class)) {
287 SymCtx.ObjCIFKind = ObjCIFSymbolKind::Class;
288 PrintDiagnostic(DR->getLinkageForSymbol(CurrType: ObjCIFSymbolKind::Class), R,
289 getAnnotatedName(R, SymCtx),
290 /*PrintAsWarning=*/true);
291 }
292 if (!DR->isExportedSymbol(CurrType: ObjCIFSymbolKind::MetaClass)) {
293 SymCtx.ObjCIFKind = ObjCIFSymbolKind::MetaClass;
294 PrintDiagnostic(DR->getLinkageForSymbol(CurrType: ObjCIFSymbolKind::MetaClass), R,
295 getAnnotatedName(R, SymCtx),
296 /*PrintAsWarning=*/true);
297 }
298 return true;
299 }
300
301 if (DR->isExportedSymbol(CurrType: SymCtx.ObjCIFKind)) {
302 if (!IsDylibVersionComplete) {
303 // Both the declaration and dylib have a non-complete interface.
304 SymCtx.Kind = EncodeKind::GlobalSymbol;
305 SymCtx.SymbolName = R->getName();
306 }
307 return true;
308 }
309
310 // At this point that means there was not a matching class symbol
311 // to represent the one discovered as a declaration.
312 PrintDiagnostic(DR->getLinkageForSymbol(CurrType: SymCtx.ObjCIFKind), R,
313 SymCtx.SymbolName);
314 return false;
315}
316
317DylibVerifier::Result DylibVerifier::compareVisibility(const Record *R,
318 SymbolContext &SymCtx,
319 const Record *DR) {
320
321 if (R->isExported()) {
322 if (!DR) {
323 Ctx.emitDiag(Report: [&]() {
324 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: diag::err_library_missing_symbol)
325 << getAnnotatedName(R, SymCtx);
326 });
327 return Result::Invalid;
328 }
329 if (DR->isInternal()) {
330 Ctx.emitDiag(Report: [&]() {
331 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: diag::err_library_hidden_symbol)
332 << getAnnotatedName(R, SymCtx);
333 });
334 return Result::Invalid;
335 }
336 }
337
338 // Emit a diagnostic for hidden declarations with external symbols, except
339 // when theres an inlined attribute.
340 if ((R->isInternal() && !SymCtx.Inlined) && DR && DR->isExported()) {
341
342 if (Mode == VerificationMode::ErrorsOnly)
343 return Result::Ignore;
344
345 if (shouldIgnorePrivateExternAttr(D: SymCtx.FA->D))
346 return Result::Ignore;
347
348 if (shouldIgnoreInternalZipperedSymbol(R, SymCtx))
349 return Result::Ignore;
350
351 unsigned ID;
352 Result Outcome;
353 if (Mode == VerificationMode::ErrorsAndWarnings) {
354 ID = diag::warn_header_hidden_symbol;
355 Outcome = Result::Ignore;
356 } else {
357 ID = diag::err_header_hidden_symbol;
358 Outcome = Result::Invalid;
359 }
360 Ctx.emitDiag(Report: [&]() {
361 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: ID) << getAnnotatedName(R, SymCtx);
362 });
363 return Outcome;
364 }
365
366 if (R->isInternal())
367 return Result::Ignore;
368
369 return Result::Valid;
370}
371
372DylibVerifier::Result DylibVerifier::compareAvailability(const Record *R,
373 SymbolContext &SymCtx,
374 const Record *DR) {
375 if (!SymCtx.FA->Avail.isUnavailable())
376 return Result::Valid;
377
378 if (shouldIgnoreZipperedAvailability(R, SymCtx))
379 return Result::Ignore;
380
381 const bool IsDeclAvailable = SymCtx.FA->Avail.isUnavailable();
382
383 switch (Mode) {
384 case VerificationMode::ErrorsAndWarnings:
385 Ctx.emitDiag(Report: [&]() {
386 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: diag::warn_header_availability_mismatch)
387 << getAnnotatedName(R, SymCtx) << IsDeclAvailable << IsDeclAvailable;
388 });
389 return Result::Ignore;
390 case VerificationMode::Pedantic:
391 Ctx.emitDiag(Report: [&]() {
392 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: diag::err_header_availability_mismatch)
393 << getAnnotatedName(R, SymCtx) << IsDeclAvailable << IsDeclAvailable;
394 });
395 return Result::Invalid;
396 case VerificationMode::ErrorsOnly:
397 return Result::Ignore;
398 case VerificationMode::Invalid:
399 llvm_unreachable("Unexpected verification mode symbol verification");
400 }
401 llvm_unreachable("Unexpected verification mode symbol verification");
402}
403
404bool DylibVerifier::compareSymbolFlags(const Record *R, SymbolContext &SymCtx,
405 const Record *DR) {
406 if (DR->isThreadLocalValue() && !R->isThreadLocalValue()) {
407 Ctx.emitDiag(Report: [&]() {
408 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: diag::err_dylib_symbol_flags_mismatch)
409 << getAnnotatedName(R: DR, SymCtx) << DR->isThreadLocalValue();
410 });
411 return false;
412 }
413 if (!DR->isThreadLocalValue() && R->isThreadLocalValue()) {
414 Ctx.emitDiag(Report: [&]() {
415 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: diag::err_header_symbol_flags_mismatch)
416 << getAnnotatedName(R, SymCtx) << R->isThreadLocalValue();
417 });
418 return false;
419 }
420
421 if (DR->isWeakDefined() && !R->isWeakDefined()) {
422 Ctx.emitDiag(Report: [&]() {
423 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: diag::err_dylib_symbol_flags_mismatch)
424 << getAnnotatedName(R: DR, SymCtx) << R->isWeakDefined();
425 });
426 return false;
427 }
428 if (!DR->isWeakDefined() && R->isWeakDefined()) {
429 Ctx.emitDiag(Report: [&]() {
430 Ctx.Diag->Report(Loc: SymCtx.FA->Loc, DiagID: diag::err_header_symbol_flags_mismatch)
431 << getAnnotatedName(R, SymCtx) << R->isWeakDefined();
432 });
433 return false;
434 }
435
436 return true;
437}
438
439DylibVerifier::Result DylibVerifier::verifyImpl(Record *R,
440 SymbolContext &SymCtx) {
441 R->setVerify();
442 if (!canVerify()) {
443 // Accumulate symbols when not in verifying against dylib.
444 if (R->isExported() && !SymCtx.FA->Avail.isUnavailable() &&
445 !SymCtx.FA->Avail.isObsoleted()) {
446 addSymbol(R, SymCtx);
447 }
448 return Ctx.FrontendState;
449 }
450
451 if (shouldIgnoreReexport(R, SymCtx)) {
452 updateState(State: Result::Ignore);
453 return Ctx.FrontendState;
454 }
455
456 Record *DR =
457 findRecordFromSlice(Slice: Ctx.DylibSlice, Name: SymCtx.SymbolName, Kind: SymCtx.Kind);
458 if (DR)
459 DR->setVerify();
460
461 if (shouldIgnoreObsolete(R, SymCtx, DR)) {
462 updateState(State: Result::Ignore);
463 return Ctx.FrontendState;
464 }
465
466 // Unavailable declarations don't need matching symbols.
467 if (SymCtx.FA->Avail.isUnavailable() && (!DR || DR->isInternal())) {
468 updateState(State: Result::Valid);
469 return Ctx.FrontendState;
470 }
471
472 Result VisibilityCheck = compareVisibility(R, SymCtx, DR);
473 if (VisibilityCheck != Result::Valid) {
474 updateState(State: VisibilityCheck);
475 return Ctx.FrontendState;
476 }
477
478 // All missing symbol cases to diagnose have been handled now.
479 if (!DR) {
480 updateState(State: Result::Ignore);
481 return Ctx.FrontendState;
482 }
483
484 // Check for mismatching ObjC interfaces.
485 if (SymCtx.ObjCIFKind != ObjCIFSymbolKind::None) {
486 if (!compareObjCInterfaceSymbols(
487 R, SymCtx, DR: Ctx.DylibSlice->findObjCInterface(Name: DR->getName()))) {
488 updateState(State: Result::Invalid);
489 return Ctx.FrontendState;
490 }
491 }
492
493 Result AvailabilityCheck = compareAvailability(R, SymCtx, DR);
494 if (AvailabilityCheck != Result::Valid) {
495 updateState(State: AvailabilityCheck);
496 return Ctx.FrontendState;
497 }
498
499 if (!compareSymbolFlags(R, SymCtx, DR)) {
500 updateState(State: Result::Invalid);
501 return Ctx.FrontendState;
502 }
503
504 addSymbol(R, SymCtx);
505 updateState(State: Result::Valid);
506 return Ctx.FrontendState;
507}
508
509bool DylibVerifier::canVerify() {
510 return Ctx.FrontendState != Result::NoVerify;
511}
512
513void DylibVerifier::assignSlice(const Target &T) {
514 assert(T == Ctx.Target && "Active targets should match.");
515 if (Dylib.empty())
516 return;
517
518 // Note: there are no reexport slices with binaries, as opposed to TBD files,
519 // so it can be assumed that the target match is the active top-level library.
520 auto It = find_if(
521 Range&: Dylib, P: [&T](const auto &Slice) { return T == Slice->getTarget(); });
522
523 assert(It != Dylib.end() && "Target slice should always exist.");
524 Ctx.DylibSlice = It->get();
525}
526
527void DylibVerifier::setTarget(const Target &T) {
528 Ctx.Target = T;
529 Ctx.DiscoveredFirstError = false;
530 if (Dylib.empty()) {
531 updateState(State: Result::NoVerify);
532 return;
533 }
534 updateState(State: Result::Ignore);
535 assignSlice(T);
536}
537
538void DylibVerifier::setSourceManager(
539 IntrusiveRefCntPtr<SourceManager> SourceMgr) {
540 if (!Ctx.Diag)
541 return;
542 SourceManagers.push_back(Elt: std::move(SourceMgr));
543 Ctx.Diag->setSourceManager(SourceManagers.back().get());
544}
545
546DylibVerifier::Result DylibVerifier::verify(ObjCIVarRecord *R,
547 const FrontendAttrs *FA,
548 const StringRef SuperClass) {
549 if (R->isVerified())
550 return getState();
551
552 std::string FullName =
553 ObjCIVarRecord::createScopedName(SuperClass, IVar: R->getName());
554 SymbolContext SymCtx{.SymbolName: FullName, .Kind: EncodeKind::ObjectiveCInstanceVariable, .FA: FA};
555 return verifyImpl(R, SymCtx);
556}
557
558static ObjCIFSymbolKind assignObjCIFSymbolKind(const ObjCInterfaceRecord *R) {
559 ObjCIFSymbolKind Result = ObjCIFSymbolKind::None;
560 if (R->getLinkageForSymbol(CurrType: ObjCIFSymbolKind::Class) != RecordLinkage::Unknown)
561 Result |= ObjCIFSymbolKind::Class;
562 if (R->getLinkageForSymbol(CurrType: ObjCIFSymbolKind::MetaClass) !=
563 RecordLinkage::Unknown)
564 Result |= ObjCIFSymbolKind::MetaClass;
565 if (R->getLinkageForSymbol(CurrType: ObjCIFSymbolKind::EHType) !=
566 RecordLinkage::Unknown)
567 Result |= ObjCIFSymbolKind::EHType;
568 return Result;
569}
570
571DylibVerifier::Result DylibVerifier::verify(ObjCInterfaceRecord *R,
572 const FrontendAttrs *FA) {
573 if (R->isVerified())
574 return getState();
575 SymbolContext SymCtx;
576 SymCtx.SymbolName = R->getName();
577 SymCtx.ObjCIFKind = assignObjCIFSymbolKind(R);
578
579 SymCtx.Kind = R->hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType
580 : EncodeKind::ObjectiveCClass;
581 SymCtx.FA = FA;
582
583 return verifyImpl(R, SymCtx);
584}
585
586DylibVerifier::Result DylibVerifier::verify(GlobalRecord *R,
587 const FrontendAttrs *FA) {
588 if (R->isVerified())
589 return getState();
590
591 // Global classifications could be obfusciated with `asm`.
592 SimpleSymbol Sym = parseSymbol(SymName: R->getName());
593 SymbolContext SymCtx;
594 SymCtx.SymbolName = Sym.Name;
595 SymCtx.Kind = Sym.Kind;
596 SymCtx.FA = FA;
597 SymCtx.Inlined = R->isInlined();
598 return verifyImpl(R, SymCtx);
599}
600
601void DylibVerifier::VerifierContext::emitDiag(llvm::function_ref<void()> Report,
602 RecordLoc *Loc) {
603 if (!DiscoveredFirstError) {
604 Diag->Report(DiagID: diag::warn_target)
605 << (PrintArch ? getArchitectureName(Arch: Target.Arch)
606 : getTargetTripleName(Targ: Target));
607 DiscoveredFirstError = true;
608 }
609 if (Loc && Loc->isValid())
610 llvm::errs() << Loc->File << ":" << Loc->Line << ":" << 0 << ": ";
611
612 Report();
613}
614
615// The existence of weak-defined RTTI can not always be inferred from the
616// header files because they can be generated as part of an implementation
617// file.
618// InstallAPI doesn't warn about weak-defined RTTI, because this doesn't affect
619// static linking and so can be ignored for text-api files.
620static bool shouldIgnoreCpp(StringRef Name, bool IsWeakDef) {
621 return (IsWeakDef &&
622 (Name.starts_with(Prefix: "__ZTI") || Name.starts_with(Prefix: "__ZTS")));
623}
624void DylibVerifier::visitSymbolInDylib(const Record &R, SymbolContext &SymCtx) {
625 // Undefined symbols should not be in InstallAPI generated text-api files.
626 if (R.isUndefined()) {
627 updateState(State: Result::Valid);
628 return;
629 }
630
631 // Internal symbols should not be in InstallAPI generated text-api files.
632 if (R.isInternal()) {
633 updateState(State: Result::Valid);
634 return;
635 }
636
637 // Allow zippered symbols with potentially mismatching availability
638 // between macOS and macCatalyst in the final text-api file.
639 const StringRef SymbolName(SymCtx.SymbolName);
640 if (const Symbol *Sym = Exports->findSymbol(Kind: SymCtx.Kind, Name: SymCtx.SymbolName,
641 ObjCIF: SymCtx.ObjCIFKind)) {
642 if (Sym->hasArchitecture(Arch: Ctx.Target.Arch)) {
643 updateState(State: Result::Ignore);
644 return;
645 }
646 }
647
648 const bool IsLinkerSymbol = SymbolName.starts_with(Prefix: "$ld$");
649
650 if (R.isVerified()) {
651 // Check for unavailable symbols.
652 // This should only occur in the zippered case where we ignored
653 // availability until all headers have been parsed.
654 auto It = DeferredZipperedSymbols.find(Key: SymCtx.SymbolName);
655 if (It == DeferredZipperedSymbols.end()) {
656 updateState(State: Result::Valid);
657 return;
658 }
659
660 ZipperedDeclSources Locs;
661 for (const ZipperedDeclSource &ZSource : It->second) {
662 if (ZSource.FA->Avail.isObsoleted()) {
663 updateState(State: Result::Ignore);
664 return;
665 }
666 if (ZSource.T.Arch != Ctx.Target.Arch)
667 continue;
668 Locs.emplace_back(args: ZSource);
669 }
670 assert(Locs.size() == 2 && "Expected two decls for zippered symbol");
671
672 // Print violating declarations per platform.
673 for (const ZipperedDeclSource &ZSource : Locs) {
674 unsigned DiagID = 0;
675 if (Mode == VerificationMode::Pedantic || IsLinkerSymbol) {
676 updateState(State: Result::Invalid);
677 DiagID = diag::err_header_availability_mismatch;
678 } else if (Mode == VerificationMode::ErrorsAndWarnings) {
679 updateState(State: Result::Ignore);
680 DiagID = diag::warn_header_availability_mismatch;
681 } else {
682 updateState(State: Result::Ignore);
683 return;
684 }
685 // Bypass emitDiag banner and print the target everytime.
686 Ctx.Diag->setSourceManager(ZSource.SrcMgr);
687 Ctx.Diag->Report(DiagID: diag::warn_target) << getTargetTripleName(Targ: ZSource.T);
688 Ctx.Diag->Report(Loc: ZSource.FA->Loc, DiagID)
689 << getAnnotatedName(R: &R, SymCtx) << ZSource.FA->Avail.isUnavailable()
690 << ZSource.FA->Avail.isUnavailable();
691 }
692 return;
693 }
694
695 if (shouldIgnoreCpp(Name: SymbolName, IsWeakDef: R.isWeakDefined())) {
696 updateState(State: Result::Valid);
697 return;
698 }
699
700 if (Aliases.count(x: {SymbolName.str(), SymCtx.Kind})) {
701 updateState(State: Result::Valid);
702 return;
703 }
704
705 // All checks at this point classify as some kind of violation.
706 // The different verification modes dictate whether they are reported to the
707 // user.
708 if (IsLinkerSymbol || (Mode > VerificationMode::ErrorsOnly))
709 accumulateSrcLocForDylibSymbols();
710 RecordLoc Loc = DWARFCtx->SourceLocs.lookup(Key: SymCtx.SymbolName);
711
712 // Regardless of verification mode, error out on mismatched special linker
713 // symbols.
714 if (IsLinkerSymbol) {
715 Ctx.emitDiag(
716 Report: [&]() {
717 Ctx.Diag->Report(DiagID: diag::err_header_symbol_missing)
718 << getAnnotatedName(R: &R, SymCtx, ValidSourceLoc: Loc.isValid());
719 },
720 Loc: &Loc);
721 updateState(State: Result::Invalid);
722 return;
723 }
724
725 // Missing declarations for exported symbols are hard errors on Pedantic mode.
726 if (Mode == VerificationMode::Pedantic) {
727 Ctx.emitDiag(
728 Report: [&]() {
729 Ctx.Diag->Report(DiagID: diag::err_header_symbol_missing)
730 << getAnnotatedName(R: &R, SymCtx, ValidSourceLoc: Loc.isValid());
731 },
732 Loc: &Loc);
733 updateState(State: Result::Invalid);
734 return;
735 }
736
737 // Missing declarations for exported symbols are warnings on ErrorsAndWarnings
738 // mode.
739 if (Mode == VerificationMode::ErrorsAndWarnings) {
740 Ctx.emitDiag(
741 Report: [&]() {
742 Ctx.Diag->Report(DiagID: diag::warn_header_symbol_missing)
743 << getAnnotatedName(R: &R, SymCtx, ValidSourceLoc: Loc.isValid());
744 },
745 Loc: &Loc);
746 updateState(State: Result::Ignore);
747 return;
748 }
749
750 // Missing declarations are dropped for ErrorsOnly mode. It is the last
751 // remaining mode.
752 updateState(State: Result::Ignore);
753}
754
755void DylibVerifier::visitGlobal(const GlobalRecord &R) {
756 SymbolContext SymCtx;
757 SimpleSymbol Sym = parseSymbol(SymName: R.getName());
758 SymCtx.SymbolName = Sym.Name;
759 SymCtx.Kind = Sym.Kind;
760 visitSymbolInDylib(R, SymCtx);
761}
762
763void DylibVerifier::visitObjCIVar(const ObjCIVarRecord &R,
764 const StringRef Super) {
765 SymbolContext SymCtx;
766 SymCtx.SymbolName = ObjCIVarRecord::createScopedName(SuperClass: Super, IVar: R.getName());
767 SymCtx.Kind = EncodeKind::ObjectiveCInstanceVariable;
768 visitSymbolInDylib(R, SymCtx);
769}
770
771void DylibVerifier::accumulateSrcLocForDylibSymbols() {
772 if (DSYMPath.empty())
773 return;
774
775 assert(DWARFCtx != nullptr && "Expected an initialized DWARFContext");
776 if (DWARFCtx->ParsedDSYM)
777 return;
778 DWARFCtx->ParsedDSYM = true;
779 DWARFCtx->SourceLocs =
780 DylibReader::accumulateSourceLocFromDSYM(DSYM: DSYMPath, T: Ctx.Target);
781}
782
783void DylibVerifier::visitObjCInterface(const ObjCInterfaceRecord &R) {
784 SymbolContext SymCtx;
785 SymCtx.SymbolName = R.getName();
786 SymCtx.ObjCIFKind = assignObjCIFSymbolKind(R: &R);
787 if (SymCtx.ObjCIFKind > ObjCIFSymbolKind::EHType) {
788 if (R.hasExceptionAttribute()) {
789 SymCtx.Kind = EncodeKind::ObjectiveCClassEHType;
790 visitSymbolInDylib(R, SymCtx);
791 }
792 SymCtx.Kind = EncodeKind::ObjectiveCClass;
793 visitSymbolInDylib(R, SymCtx);
794 } else {
795 SymCtx.Kind = R.hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType
796 : EncodeKind::ObjectiveCClass;
797 visitSymbolInDylib(R, SymCtx);
798 }
799
800 for (const ObjCIVarRecord *IV : R.getObjCIVars())
801 visitObjCIVar(R: *IV, Super: R.getName());
802}
803
804void DylibVerifier::visitObjCCategory(const ObjCCategoryRecord &R) {
805 for (const ObjCIVarRecord *IV : R.getObjCIVars())
806 visitObjCIVar(R: *IV, Super: R.getSuperClassName());
807}
808
809DylibVerifier::Result DylibVerifier::verifyRemainingSymbols() {
810 if (getState() == Result::NoVerify)
811 return Result::NoVerify;
812 assert(!Dylib.empty() && "No binary to verify against");
813
814 DWARFContext DWARFInfo;
815 DWARFCtx = &DWARFInfo;
816 Ctx.Target = Target(Architecture::AK_unknown, PlatformType::PLATFORM_UNKNOWN);
817 for (std::shared_ptr<RecordsSlice> Slice : Dylib) {
818 if (Ctx.Target.Arch == Slice->getTarget().Arch)
819 continue;
820 Ctx.DiscoveredFirstError = false;
821 Ctx.PrintArch = true;
822 Ctx.Target = Slice->getTarget();
823 Ctx.DylibSlice = Slice.get();
824 Slice->visit(V&: *this);
825 }
826 return getState();
827}
828
829bool DylibVerifier::verifyBinaryAttrs(const ArrayRef<Target> ProvidedTargets,
830 const BinaryAttrs &ProvidedBA,
831 const LibAttrs &ProvidedReexports,
832 const LibAttrs &ProvidedClients,
833 const LibAttrs &ProvidedRPaths,
834 const FileType &FT) {
835 assert(!Dylib.empty() && "Need dylib to verify.");
836
837 // Pickup any load commands that can differ per slice to compare.
838 TargetList DylibTargets;
839 LibAttrs DylibReexports;
840 LibAttrs DylibClients;
841 LibAttrs DylibRPaths;
842 for (const std::shared_ptr<RecordsSlice> &RS : Dylib) {
843 DylibTargets.push_back(Elt: RS->getTarget());
844 const BinaryAttrs &BinInfo = RS->getBinaryAttrs();
845 for (const StringRef LibName : BinInfo.RexportedLibraries)
846 DylibReexports.getArchSet(Attr: LibName).set(DylibTargets.back().Arch);
847 for (const StringRef LibName : BinInfo.AllowableClients)
848 DylibClients.getArchSet(Attr: LibName).set(DylibTargets.back().Arch);
849 // Compare attributes that are only representable in >= TBD_V5.
850 if (FT >= FileType::TBD_V5)
851 for (const StringRef Name : BinInfo.RPaths)
852 DylibRPaths.getArchSet(Attr: Name).set(DylibTargets.back().Arch);
853 }
854
855 // Check targets first.
856 ArchitectureSet ProvidedArchs = mapToArchitectureSet(Targets: ProvidedTargets);
857 ArchitectureSet DylibArchs = mapToArchitectureSet(Targets: DylibTargets);
858 if (ProvidedArchs != DylibArchs) {
859 Ctx.Diag->Report(DiagID: diag::err_architecture_mismatch)
860 << ProvidedArchs << DylibArchs;
861 return false;
862 }
863 auto ProvidedPlatforms = mapToPlatformVersionSet(Targets: ProvidedTargets);
864 auto DylibPlatforms = mapToPlatformVersionSet(Targets: DylibTargets);
865 if (ProvidedPlatforms != DylibPlatforms) {
866 const bool DiffMinOS =
867 mapToPlatformSet(Targets: ProvidedTargets) == mapToPlatformSet(Targets: DylibTargets);
868 if (DiffMinOS)
869 Ctx.Diag->Report(DiagID: diag::warn_platform_mismatch)
870 << ProvidedPlatforms << DylibPlatforms;
871 else {
872 Ctx.Diag->Report(DiagID: diag::err_platform_mismatch)
873 << ProvidedPlatforms << DylibPlatforms;
874 return false;
875 }
876 }
877
878 // Because InstallAPI requires certain attributes to match across architecture
879 // slices, take the first one to compare those with.
880 const BinaryAttrs &DylibBA = (*Dylib.begin())->getBinaryAttrs();
881
882 if (ProvidedBA.InstallName != DylibBA.InstallName) {
883 Ctx.Diag->Report(DiagID: diag::err_install_name_mismatch)
884 << ProvidedBA.InstallName << DylibBA.InstallName;
885 return false;
886 }
887
888 if (ProvidedBA.CurrentVersion != DylibBA.CurrentVersion) {
889 Ctx.Diag->Report(DiagID: diag::err_current_version_mismatch)
890 << ProvidedBA.CurrentVersion << DylibBA.CurrentVersion;
891 return false;
892 }
893
894 if (ProvidedBA.CompatVersion != DylibBA.CompatVersion) {
895 Ctx.Diag->Report(DiagID: diag::err_compatibility_version_mismatch)
896 << ProvidedBA.CompatVersion << DylibBA.CompatVersion;
897 return false;
898 }
899
900 if (ProvidedBA.AppExtensionSafe != DylibBA.AppExtensionSafe) {
901 Ctx.Diag->Report(DiagID: diag::err_appextension_safe_mismatch)
902 << (ProvidedBA.AppExtensionSafe ? "true" : "false")
903 << (DylibBA.AppExtensionSafe ? "true" : "false");
904 return false;
905 }
906
907 if (!DylibBA.TwoLevelNamespace) {
908 Ctx.Diag->Report(DiagID: diag::err_no_twolevel_namespace);
909 return false;
910 }
911
912 if (ProvidedBA.OSLibNotForSharedCache != DylibBA.OSLibNotForSharedCache) {
913 Ctx.Diag->Report(DiagID: diag::err_shared_cache_eligiblity_mismatch)
914 << (ProvidedBA.OSLibNotForSharedCache ? "true" : "false")
915 << (DylibBA.OSLibNotForSharedCache ? "true" : "false");
916 return false;
917 }
918
919 if (ProvidedBA.ParentUmbrella.empty() && !DylibBA.ParentUmbrella.empty()) {
920 Ctx.Diag->Report(DiagID: diag::err_parent_umbrella_missing)
921 << "installAPI option" << DylibBA.ParentUmbrella;
922 return false;
923 }
924
925 if (!ProvidedBA.ParentUmbrella.empty() && DylibBA.ParentUmbrella.empty()) {
926 Ctx.Diag->Report(DiagID: diag::err_parent_umbrella_missing)
927 << "binary file" << ProvidedBA.ParentUmbrella;
928 return false;
929 }
930
931 if ((!ProvidedBA.ParentUmbrella.empty()) &&
932 (ProvidedBA.ParentUmbrella != DylibBA.ParentUmbrella)) {
933 Ctx.Diag->Report(DiagID: diag::err_parent_umbrella_mismatch)
934 << ProvidedBA.ParentUmbrella << DylibBA.ParentUmbrella;
935 return false;
936 }
937
938 auto CompareLibraries = [&](const LibAttrs &Provided, const LibAttrs &Dylib,
939 unsigned DiagID_missing, unsigned DiagID_mismatch,
940 bool Fatal = true) {
941 if (Provided == Dylib)
942 return true;
943
944 for (const LibAttrs::Entry &PEntry : Provided.get()) {
945 const auto &[PAttr, PArchSet] = PEntry;
946 auto DAttrEntry = Dylib.find(Attr: PAttr);
947 if (!DAttrEntry) {
948 Ctx.Diag->Report(DiagID: DiagID_missing) << "binary file" << PEntry;
949 if (Fatal)
950 return false;
951 }
952
953 if (PArchSet != DAttrEntry->second) {
954 Ctx.Diag->Report(DiagID: DiagID_mismatch) << PEntry << *DAttrEntry;
955 if (Fatal)
956 return false;
957 }
958 }
959
960 for (const LibAttrs::Entry &DEntry : Dylib.get()) {
961 const auto &[DAttr, DArchSet] = DEntry;
962 const auto &PAttrEntry = Provided.find(Attr: DAttr);
963 if (!PAttrEntry) {
964 Ctx.Diag->Report(DiagID: DiagID_missing) << "installAPI option" << DEntry;
965 if (!Fatal)
966 continue;
967 return false;
968 }
969
970 if (PAttrEntry->second != DArchSet) {
971 if (Fatal)
972 llvm_unreachable("this case was already covered above.");
973 }
974 }
975 return true;
976 };
977
978 if (!CompareLibraries(ProvidedReexports, DylibReexports,
979 diag::err_reexported_libraries_missing,
980 diag::err_reexported_libraries_mismatch))
981 return false;
982
983 if (!CompareLibraries(ProvidedClients, DylibClients,
984 diag::err_allowable_clients_missing,
985 diag::err_allowable_clients_mismatch))
986 return false;
987
988 if (FT >= FileType::TBD_V5) {
989 // Ignore rpath differences if building an asan variant, since the
990 // compiler injects additional paths.
991 // FIXME: Building with sanitizers does not always change the install
992 // name, so this is not a foolproof solution.
993 if (!ProvidedBA.InstallName.ends_with(Suffix: "_asan")) {
994 if (!CompareLibraries(ProvidedRPaths, DylibRPaths,
995 diag::warn_rpaths_missing,
996 diag::warn_rpaths_mismatch,
997 /*Fatal=*/false))
998 return true;
999 }
1000 }
1001
1002 return true;
1003}
1004
1005std::unique_ptr<SymbolSet> DylibVerifier::takeExports() {
1006 for (const auto &[Alias, Base] : Aliases) {
1007 TargetList Targets;
1008 SymbolFlags Flags = SymbolFlags::None;
1009 if (const Symbol *Sym = Exports->findSymbol(Kind: Base.second, Name: Base.first)) {
1010 Flags = Sym->getFlags();
1011 Targets = {Sym->targets().begin(), Sym->targets().end()};
1012 }
1013
1014 Record R(Alias.first, RecordLinkage::Exported, Flags);
1015 SymbolContext SymCtx;
1016 SymCtx.SymbolName = Alias.first;
1017 SymCtx.Kind = Alias.second;
1018 addSymbol(R: &R, SymCtx, Targets: std::move(Targets));
1019 }
1020
1021 return std::move(Exports);
1022}
1023
1024} // namespace installapi
1025} // namespace clang
1026