xref: /llvm-project/clang/lib/InstallAPI/DylibVerifier.cpp (revision 936519f25cb4fabc19f1241e838e938926801156)
1 #include "clang/InstallAPI/DylibVerifier.h"
2 #include "clang/InstallAPI/FrontendRecords.h"
3 #include "clang/InstallAPI/InstallAPIDiagnostic.h"
4 #include "llvm/Demangle/Demangle.h"
5 
6 using namespace llvm::MachO;
7 
8 namespace clang {
9 namespace installapi {
10 
11 /// Metadata stored about a mapping of a declaration to a symbol.
12 struct DylibVerifier::SymbolContext {
13   // Name to use for printing in diagnostics.
14   std::string PrettyPrintName{""};
15 
16   // Name to use for all querying and verification
17   // purposes.
18   std::string SymbolName{""};
19 
20   // Kind to map symbol type against record.
21   EncodeKind Kind = EncodeKind::GlobalSymbol;
22 
23   // Frontend Attributes tied to the AST.
24   const FrontendAttrs *FA = nullptr;
25 
26   // The ObjCInterface symbol type, if applicable.
27   ObjCIFSymbolKind ObjCIFKind = ObjCIFSymbolKind::None;
28 
29   // Whether Decl is inlined.
30   bool Inlined = false;
31 };
32 
33 static std::string
34 getAnnotatedName(const Record *R, EncodeKind Kind, StringRef Name,
35                  bool ValidSourceLoc = true,
36                  ObjCIFSymbolKind ObjCIF = ObjCIFSymbolKind::None) {
37   assert(!Name.empty() && "Need symbol name for printing");
38 
39   std::string Annotation;
40   if (R->isWeakDefined())
41     Annotation += "(weak-def) ";
42   if (R->isWeakReferenced())
43     Annotation += "(weak-ref) ";
44   if (R->isThreadLocalValue())
45     Annotation += "(tlv) ";
46 
47   // Check if symbol represents only part of a @interface declaration.
48   const bool IsAnnotatedObjCClass = ((ObjCIF != ObjCIFSymbolKind::None) &&
49                                      (ObjCIF <= ObjCIFSymbolKind::EHType));
50 
51   if (IsAnnotatedObjCClass) {
52     if (ObjCIF == ObjCIFSymbolKind::EHType)
53       Annotation += "Exception Type of ";
54     if (ObjCIF == ObjCIFSymbolKind::MetaClass)
55       Annotation += "Metaclass of ";
56     if (ObjCIF == ObjCIFSymbolKind::Class)
57       Annotation += "Class of ";
58   }
59 
60   // Only print symbol type prefix or leading "_" if there is no source location
61   // tied to it. This can only ever happen when the location has to come from
62   // debug info.
63   if (ValidSourceLoc) {
64     if ((Kind == EncodeKind::GlobalSymbol) && Name.starts_with("_"))
65       return Annotation + Name.drop_front(1).str();
66     return Annotation + Name.str();
67   }
68 
69   if (IsAnnotatedObjCClass)
70     return Annotation + Name.str();
71 
72   switch (Kind) {
73   case EncodeKind::GlobalSymbol:
74     return Annotation + Name.str();
75   case EncodeKind::ObjectiveCInstanceVariable:
76     return Annotation + "(ObjC IVar) " + Name.str();
77   case EncodeKind::ObjectiveCClass:
78     return Annotation + "(ObjC Class) " + Name.str();
79   case EncodeKind::ObjectiveCClassEHType:
80     return Annotation + "(ObjC Class EH) " + Name.str();
81   }
82 
83   llvm_unreachable("unexpected case for EncodeKind");
84 }
85 
86 static std::string demangle(StringRef Name) {
87   // InstallAPI currently only supports itanium manglings.
88   if (!(Name.starts_with("_Z") || Name.starts_with("__Z") ||
89         Name.starts_with("___Z")))
90     return Name.str();
91   char *Result = llvm::itaniumDemangle(Name);
92   if (!Result)
93     return Name.str();
94 
95   std::string Demangled(Result);
96   free(Result);
97   return Demangled;
98 }
99 
100 static DylibVerifier::Result updateResult(const DylibVerifier::Result Prev,
101                                           const DylibVerifier::Result Curr) {
102   if (Prev == Curr)
103     return Prev;
104 
105   // Never update from invalid or noverify state.
106   if ((Prev == DylibVerifier::Result::Invalid) ||
107       (Prev == DylibVerifier::Result::NoVerify))
108     return Prev;
109 
110   // Don't let an ignored verification remove a valid one.
111   if (Prev == DylibVerifier::Result::Valid &&
112       Curr == DylibVerifier::Result::Ignore)
113     return Prev;
114 
115   return Curr;
116 }
117 // __private_extern__ is a deprecated specifier that clang does not
118 // respect in all contexts, it should just be considered hidden for InstallAPI.
119 static bool shouldIgnorePrivateExternAttr(const Decl *D) {
120   if (const FunctionDecl *FD = cast<FunctionDecl>(D))
121     return FD->getStorageClass() == StorageClass::SC_PrivateExtern;
122   if (const VarDecl *VD = cast<VarDecl>(D))
123     return VD->getStorageClass() == StorageClass::SC_PrivateExtern;
124 
125   return false;
126 }
127 
128 Record *findRecordFromSlice(const RecordsSlice *Slice, StringRef Name,
129                             EncodeKind Kind) {
130   switch (Kind) {
131   case EncodeKind::GlobalSymbol:
132     return Slice->findGlobal(Name);
133   case EncodeKind::ObjectiveCInstanceVariable:
134     return Slice->findObjCIVar(Name.contains('.'), Name);
135   case EncodeKind::ObjectiveCClass:
136   case EncodeKind::ObjectiveCClassEHType:
137     return Slice->findObjCInterface(Name);
138   }
139   llvm_unreachable("unexpected end when finding record");
140 }
141 
142 void DylibVerifier::updateState(Result State) {
143   Ctx.FrontendState = updateResult(Ctx.FrontendState, State);
144 }
145 
146 void DylibVerifier::addSymbol(const Record *R, SymbolContext &SymCtx,
147                               TargetList &&Targets) {
148   if (Targets.empty())
149     Targets = {Ctx.Target};
150 
151   Exports->addGlobal(SymCtx.Kind, SymCtx.SymbolName, R->getFlags(), Targets);
152 }
153 
154 bool DylibVerifier::shouldIgnoreObsolete(const Record *R, SymbolContext &SymCtx,
155                                          const Record *DR) {
156   return SymCtx.FA->Avail.isObsoleted();
157 }
158 
159 bool DylibVerifier::compareObjCInterfaceSymbols(const Record *R,
160                                                 SymbolContext &SymCtx,
161                                                 const ObjCInterfaceRecord *DR) {
162   const bool IsDeclVersionComplete =
163       ((SymCtx.ObjCIFKind & ObjCIFSymbolKind::Class) ==
164        ObjCIFSymbolKind::Class) &&
165       ((SymCtx.ObjCIFKind & ObjCIFSymbolKind::MetaClass) ==
166        ObjCIFSymbolKind::MetaClass);
167 
168   const bool IsDylibVersionComplete = DR->isCompleteInterface();
169 
170   // The common case, a complete ObjCInterface.
171   if (IsDeclVersionComplete && IsDylibVersionComplete)
172     return true;
173 
174   auto PrintDiagnostic = [&](auto SymLinkage, const Record *Record,
175                              StringRef SymName, bool PrintAsWarning = false) {
176     if (SymLinkage == RecordLinkage::Unknown)
177       Ctx.emitDiag([&]() {
178         Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
179                          PrintAsWarning ? diag::warn_library_missing_symbol
180                                         : diag::err_library_missing_symbol)
181             << SymName;
182       });
183     else
184       Ctx.emitDiag([&]() {
185         Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
186                          PrintAsWarning ? diag::warn_library_hidden_symbol
187                                         : diag::err_library_hidden_symbol)
188             << SymName;
189       });
190   };
191 
192   if (IsDeclVersionComplete) {
193     // The decl represents a complete ObjCInterface, but the symbols in the
194     // dylib do not. Determine which symbol is missing. To keep older projects
195     // building, treat this as a warning.
196     if (!DR->isExportedSymbol(ObjCIFSymbolKind::Class))
197       PrintDiagnostic(DR->getLinkageForSymbol(ObjCIFSymbolKind::Class), R,
198                       getAnnotatedName(R, SymCtx.Kind, SymCtx.PrettyPrintName,
199                                        /*ValidSourceLoc=*/true,
200                                        ObjCIFSymbolKind::Class),
201                       /*PrintAsWarning=*/true);
202 
203     if (!DR->isExportedSymbol(ObjCIFSymbolKind::MetaClass))
204       PrintDiagnostic(DR->getLinkageForSymbol(ObjCIFSymbolKind::MetaClass), R,
205                       getAnnotatedName(R, SymCtx.Kind, SymCtx.PrettyPrintName,
206                                        /*ValidSourceLoc=*/true,
207                                        ObjCIFSymbolKind::MetaClass),
208                       /*PrintAsWarning=*/true);
209     return true;
210   }
211 
212   if (DR->isExportedSymbol(SymCtx.ObjCIFKind)) {
213     if (!IsDylibVersionComplete) {
214       // Both the declaration and dylib have a non-complete interface.
215       SymCtx.Kind = EncodeKind::GlobalSymbol;
216       SymCtx.SymbolName = R->getName();
217     }
218     return true;
219   }
220 
221   // At this point that means there was not a matching class symbol
222   // to represent the one discovered as a declaration.
223   PrintDiagnostic(DR->getLinkageForSymbol(SymCtx.ObjCIFKind), R,
224                   SymCtx.PrettyPrintName);
225   return false;
226 }
227 
228 DylibVerifier::Result DylibVerifier::compareVisibility(const Record *R,
229                                                        SymbolContext &SymCtx,
230                                                        const Record *DR) {
231 
232   if (R->isExported()) {
233     if (!DR) {
234       Ctx.emitDiag([&]() {
235         Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
236                          diag::err_library_missing_symbol)
237             << SymCtx.PrettyPrintName;
238       });
239       return Result::Invalid;
240     }
241     if (DR->isInternal()) {
242       Ctx.emitDiag([&]() {
243         Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
244                          diag::err_library_hidden_symbol)
245             << SymCtx.PrettyPrintName;
246       });
247       return Result::Invalid;
248     }
249   }
250 
251   // Emit a diagnostic for hidden declarations with external symbols, except
252   // when theres an inlined attribute.
253   if ((R->isInternal() && !SymCtx.Inlined) && DR && DR->isExported()) {
254 
255     if (Mode == VerificationMode::ErrorsOnly)
256       return Result::Ignore;
257 
258     if (shouldIgnorePrivateExternAttr(SymCtx.FA->D))
259       return Result::Ignore;
260 
261     unsigned ID;
262     Result Outcome;
263     if (Mode == VerificationMode::ErrorsAndWarnings) {
264       ID = diag::warn_header_hidden_symbol;
265       Outcome = Result::Ignore;
266     } else {
267       ID = diag::err_header_hidden_symbol;
268       Outcome = Result::Invalid;
269     }
270     Ctx.emitDiag([&]() {
271       Ctx.Diag->Report(SymCtx.FA->D->getLocation(), ID)
272           << SymCtx.PrettyPrintName;
273     });
274     return Outcome;
275   }
276 
277   if (R->isInternal())
278     return Result::Ignore;
279 
280   return Result::Valid;
281 }
282 
283 DylibVerifier::Result DylibVerifier::compareAvailability(const Record *R,
284                                                          SymbolContext &SymCtx,
285                                                          const Record *DR) {
286   if (!SymCtx.FA->Avail.isUnavailable())
287     return Result::Valid;
288 
289   const bool IsDeclAvailable = SymCtx.FA->Avail.isUnavailable();
290 
291   switch (Mode) {
292   case VerificationMode::ErrorsAndWarnings:
293     Ctx.emitDiag([&]() {
294       Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
295                        diag::warn_header_availability_mismatch)
296           << SymCtx.PrettyPrintName << IsDeclAvailable << IsDeclAvailable;
297     });
298     return Result::Ignore;
299   case VerificationMode::Pedantic:
300     Ctx.emitDiag([&]() {
301       Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
302                        diag::err_header_availability_mismatch)
303           << SymCtx.PrettyPrintName << IsDeclAvailable << IsDeclAvailable;
304     });
305     return Result::Invalid;
306   case VerificationMode::ErrorsOnly:
307     return Result::Ignore;
308   case VerificationMode::Invalid:
309     llvm_unreachable("Unexpected verification mode symbol verification");
310   }
311   llvm_unreachable("Unexpected verification mode symbol verification");
312 }
313 
314 bool DylibVerifier::compareSymbolFlags(const Record *R, SymbolContext &SymCtx,
315                                        const Record *DR) {
316   std::string DisplayName =
317       Demangle ? demangle(DR->getName()) : DR->getName().str();
318 
319   if (DR->isThreadLocalValue() && !R->isThreadLocalValue()) {
320     Ctx.emitDiag([&]() {
321       Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
322                        diag::err_dylib_symbol_flags_mismatch)
323           << getAnnotatedName(DR, SymCtx.Kind, DisplayName)
324           << DR->isThreadLocalValue();
325     });
326     return false;
327   }
328   if (!DR->isThreadLocalValue() && R->isThreadLocalValue()) {
329     Ctx.emitDiag([&]() {
330       SymCtx.FA->D->getLocation(),
331           Ctx.Diag->Report(diag::err_header_symbol_flags_mismatch)
332               << SymCtx.PrettyPrintName << R->isThreadLocalValue();
333     });
334     return false;
335   }
336 
337   if (DR->isWeakDefined() && !R->isWeakDefined()) {
338     Ctx.emitDiag([&]() {
339       Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
340                        diag::err_dylib_symbol_flags_mismatch)
341           << getAnnotatedName(DR, SymCtx.Kind, DisplayName)
342           << R->isWeakDefined();
343     });
344     return false;
345   }
346   if (!DR->isWeakDefined() && R->isWeakDefined()) {
347     Ctx.emitDiag([&]() {
348       Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
349                        diag::err_header_symbol_flags_mismatch)
350           << SymCtx.PrettyPrintName << R->isWeakDefined();
351     });
352     return false;
353   }
354 
355   return true;
356 }
357 
358 DylibVerifier::Result DylibVerifier::verifyImpl(Record *R,
359                                                 SymbolContext &SymCtx) {
360   R->setVerify();
361   if (!canVerify()) {
362     // Accumulate symbols when not in verifying against dylib.
363     if (R->isExported() && !SymCtx.FA->Avail.isUnavailable() &&
364         !SymCtx.FA->Avail.isObsoleted()) {
365       addSymbol(R, SymCtx);
366     }
367     return Ctx.FrontendState;
368   }
369 
370   Record *DR =
371       findRecordFromSlice(Ctx.DylibSlice, SymCtx.SymbolName, SymCtx.Kind);
372   if (DR)
373     DR->setVerify();
374 
375   if (shouldIgnoreObsolete(R, SymCtx, DR)) {
376     updateState(Result::Ignore);
377     return Ctx.FrontendState;
378   }
379 
380   // Unavailable declarations don't need matching symbols.
381   if (SymCtx.FA->Avail.isUnavailable() && (!DR || DR->isInternal())) {
382     updateState(Result::Valid);
383     return Ctx.FrontendState;
384   }
385 
386   Result VisibilityCheck = compareVisibility(R, SymCtx, DR);
387   if (VisibilityCheck != Result::Valid) {
388     updateState(VisibilityCheck);
389     return Ctx.FrontendState;
390   }
391 
392   // All missing symbol cases to diagnose have been handled now.
393   if (!DR) {
394     updateState(Result::Ignore);
395     return Ctx.FrontendState;
396   }
397 
398   // Check for mismatching ObjC interfaces.
399   if (SymCtx.ObjCIFKind != ObjCIFSymbolKind::None) {
400     if (!compareObjCInterfaceSymbols(
401             R, SymCtx, Ctx.DylibSlice->findObjCInterface(DR->getName()))) {
402       updateState(Result::Invalid);
403       return Ctx.FrontendState;
404     }
405   }
406 
407   Result AvailabilityCheck = compareAvailability(R, SymCtx, DR);
408   if (AvailabilityCheck != Result::Valid) {
409     updateState(AvailabilityCheck);
410     return Ctx.FrontendState;
411   }
412 
413   if (!compareSymbolFlags(R, SymCtx, DR)) {
414     updateState(Result::Invalid);
415     return Ctx.FrontendState;
416   }
417 
418   addSymbol(R, SymCtx);
419   updateState(Result::Valid);
420   return Ctx.FrontendState;
421 }
422 
423 bool DylibVerifier::canVerify() {
424   return Ctx.FrontendState != Result::NoVerify;
425 }
426 
427 void DylibVerifier::assignSlice(const Target &T) {
428   assert(T == Ctx.Target && "Active targets should match.");
429   if (Dylib.empty())
430     return;
431 
432   // Note: there are no reexport slices with binaries, as opposed to TBD files,
433   // so it can be assumed that the target match is the active top-level library.
434   auto It = find_if(
435       Dylib, [&T](const auto &Slice) { return T == Slice->getTarget(); });
436 
437   assert(It != Dylib.end() && "Target slice should always exist.");
438   Ctx.DylibSlice = It->get();
439 }
440 
441 void DylibVerifier::setTarget(const Target &T) {
442   Ctx.Target = T;
443   Ctx.DiscoveredFirstError = false;
444   if (Dylib.empty()) {
445     updateState(Result::NoVerify);
446     return;
447   }
448   updateState(Result::Ignore);
449   assignSlice(T);
450 }
451 
452 DylibVerifier::Result DylibVerifier::verify(ObjCIVarRecord *R,
453                                             const FrontendAttrs *FA,
454                                             const StringRef SuperClass) {
455   if (R->isVerified())
456     return getState();
457 
458   std::string FullName =
459       ObjCIVarRecord::createScopedName(SuperClass, R->getName());
460   SymbolContext SymCtx{
461       getAnnotatedName(R, EncodeKind::ObjectiveCInstanceVariable,
462                        Demangle ? demangle(FullName) : FullName),
463       FullName, EncodeKind::ObjectiveCInstanceVariable, FA};
464   return verifyImpl(R, SymCtx);
465 }
466 
467 static ObjCIFSymbolKind assignObjCIFSymbolKind(const ObjCInterfaceRecord *R) {
468   ObjCIFSymbolKind Result = ObjCIFSymbolKind::None;
469   if (R->getLinkageForSymbol(ObjCIFSymbolKind::Class) != RecordLinkage::Unknown)
470     Result |= ObjCIFSymbolKind::Class;
471   if (R->getLinkageForSymbol(ObjCIFSymbolKind::MetaClass) !=
472       RecordLinkage::Unknown)
473     Result |= ObjCIFSymbolKind::MetaClass;
474   if (R->getLinkageForSymbol(ObjCIFSymbolKind::EHType) !=
475       RecordLinkage::Unknown)
476     Result |= ObjCIFSymbolKind::EHType;
477   return Result;
478 }
479 
480 DylibVerifier::Result DylibVerifier::verify(ObjCInterfaceRecord *R,
481                                             const FrontendAttrs *FA) {
482   if (R->isVerified())
483     return getState();
484   SymbolContext SymCtx;
485   SymCtx.SymbolName = R->getName();
486   SymCtx.ObjCIFKind = assignObjCIFSymbolKind(R);
487 
488   std::string DisplayName =
489       Demangle ? demangle(SymCtx.SymbolName) : SymCtx.SymbolName;
490   SymCtx.Kind = R->hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType
491                                            : EncodeKind::ObjectiveCClass;
492   SymCtx.PrettyPrintName = getAnnotatedName(R, SymCtx.Kind, DisplayName);
493   SymCtx.FA = FA;
494 
495   return verifyImpl(R, SymCtx);
496 }
497 
498 DylibVerifier::Result DylibVerifier::verify(GlobalRecord *R,
499                                             const FrontendAttrs *FA) {
500   if (R->isVerified())
501     return getState();
502 
503   // Global classifications could be obfusciated with `asm`.
504   SimpleSymbol Sym = parseSymbol(R->getName());
505   SymbolContext SymCtx;
506   SymCtx.SymbolName = Sym.Name;
507   SymCtx.PrettyPrintName =
508       getAnnotatedName(R, Sym.Kind, Demangle ? demangle(Sym.Name) : Sym.Name);
509   SymCtx.Kind = Sym.Kind;
510   SymCtx.FA = FA;
511   SymCtx.Inlined = R->isInlined();
512   return verifyImpl(R, SymCtx);
513 }
514 
515 void DylibVerifier::VerifierContext::emitDiag(
516     llvm::function_ref<void()> Report) {
517   if (!DiscoveredFirstError) {
518     Diag->Report(diag::warn_target)
519         << (PrintArch ? getArchitectureName(Target.Arch)
520                       : getTargetTripleName(Target));
521     DiscoveredFirstError = true;
522   }
523 
524   Report();
525 }
526 
527 } // namespace installapi
528 } // namespace clang
529