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