Home | History | Annotate | Line # | Download | only in dsymutil
      1 //===- tools/dsymutil/CFBundle.cpp - CFBundle helper ------------*- 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 "CFBundle.h"
     10 
     11 #ifdef __APPLE__
     12 #include "llvm/Support/FileSystem.h"
     13 #include "llvm/Support/Path.h"
     14 #include "llvm/Support/raw_ostream.h"
     15 #include <CoreFoundation/CoreFoundation.h>
     16 #include <assert.h>
     17 #include <glob.h>
     18 #include <memory>
     19 #endif
     20 
     21 namespace llvm {
     22 namespace dsymutil {
     23 
     24 #ifdef __APPLE__
     25 /// Deleter that calls CFRelease rather than deleting the pointer.
     26 template <typename T> struct CFDeleter {
     27   void operator()(T *P) {
     28     if (P)
     29       ::CFRelease(P);
     30   }
     31 };
     32 
     33 /// This helper owns any CoreFoundation pointer and will call CFRelease() on
     34 /// any valid pointer it owns unless that pointer is explicitly released using
     35 /// the release() member function.
     36 template <typename T>
     37 using CFReleaser = std::unique_ptr<std::remove_pointer_t<T>,
     38                                    CFDeleter<std::remove_pointer_t<T>>>;
     39 
     40 /// RAII wrapper around CFBundleRef.
     41 class CFString : public CFReleaser<CFStringRef> {
     42 public:
     43   CFString(CFStringRef CFStr = nullptr) : CFReleaser<CFStringRef>(CFStr) {}
     44 
     45   const char *UTF8(std::string &Str) const {
     46     return CFString::UTF8(get(), Str);
     47   }
     48 
     49   CFIndex GetLength() const {
     50     if (CFStringRef Str = get())
     51       return CFStringGetLength(Str);
     52     return 0;
     53   }
     54 
     55   static const char *UTF8(CFStringRef CFStr, std::string &Str);
     56 };
     57 
     58 /// Static function that puts a copy of the UTF-8 contents of CFStringRef into
     59 /// std::string and returns the C string pointer that is contained in the
     60 /// std::string when successful, nullptr otherwise.
     61 ///
     62 /// This allows the std::string parameter to own the extracted string, and also
     63 /// allows that string to be returned as a C string pointer that can be used.
     64 const char *CFString::UTF8(CFStringRef CFStr, std::string &Str) {
     65   if (!CFStr)
     66     return nullptr;
     67 
     68   const CFStringEncoding Encoding = kCFStringEncodingUTF8;
     69   CFIndex MaxUTF8StrLength = CFStringGetLength(CFStr);
     70   MaxUTF8StrLength =
     71       CFStringGetMaximumSizeForEncoding(MaxUTF8StrLength, Encoding);
     72   if (MaxUTF8StrLength > 0) {
     73     Str.resize(MaxUTF8StrLength);
     74     if (!Str.empty() &&
     75         CFStringGetCString(CFStr, &Str[0], Str.size(), Encoding)) {
     76       Str.resize(strlen(Str.c_str()));
     77       return Str.c_str();
     78     }
     79   }
     80 
     81   return nullptr;
     82 }
     83 
     84 /// RAII wrapper around CFBundleRef.
     85 class CFBundle : public CFReleaser<CFBundleRef> {
     86 public:
     87   CFBundle(StringRef Path) : CFReleaser<CFBundleRef>() { SetFromPath(Path); }
     88 
     89   CFBundle(CFURLRef Url)
     90       : CFReleaser<CFBundleRef>(Url ? ::CFBundleCreate(nullptr, Url)
     91                                     : nullptr) {}
     92 
     93   /// Return the bundle identifier.
     94   CFStringRef GetIdentifier() const {
     95     if (CFBundleRef bundle = get())
     96       return ::CFBundleGetIdentifier(bundle);
     97     return nullptr;
     98   }
     99 
    100   /// Return value for key.
    101   CFTypeRef GetValueForInfoDictionaryKey(CFStringRef key) const {
    102     if (CFBundleRef bundle = get())
    103       return ::CFBundleGetValueForInfoDictionaryKey(bundle, key);
    104     return nullptr;
    105   }
    106 
    107 private:
    108   /// Helper to initialize this instance with a new bundle created from the
    109   /// given path. This function will recursively remove components from the
    110   /// path in its search for the nearest Info.plist.
    111   void SetFromPath(StringRef Path);
    112 };
    113 
    114 void CFBundle::SetFromPath(StringRef Path) {
    115   // Start from an empty/invalid CFBundle.
    116   reset();
    117 
    118   if (Path.empty() || !sys::fs::exists(Path))
    119     return;
    120 
    121   SmallString<256> RealPath;
    122   sys::fs::real_path(Path, RealPath, /*expand_tilde*/ true);
    123 
    124   do {
    125     // Create a CFURL from the current path and use it to create a CFBundle.
    126     CFReleaser<CFURLRef> BundleURL(::CFURLCreateFromFileSystemRepresentation(
    127         kCFAllocatorDefault, (const UInt8 *)RealPath.data(), RealPath.size(),
    128         false));
    129     reset(::CFBundleCreate(kCFAllocatorDefault, BundleURL.get()));
    130 
    131     // If we have a valid bundle and find its identifier we are done.
    132     if (get() != nullptr) {
    133       if (GetIdentifier() != nullptr)
    134         return;
    135       reset();
    136     }
    137 
    138     // Remove the last component of the path and try again until there's
    139     // nothing left but the root.
    140     sys::path::remove_filename(RealPath);
    141   } while (RealPath != sys::path::root_name(RealPath));
    142 }
    143 #endif
    144 
    145 /// On Darwin, try and find the original executable's Info.plist to extract
    146 /// information about the bundle. Return default values on other platforms.
    147 CFBundleInfo getBundleInfo(StringRef ExePath) {
    148   CFBundleInfo BundleInfo;
    149 
    150 #ifdef __APPLE__
    151   auto PrintError = [&](CFTypeID TypeID) {
    152     CFString TypeIDCFStr(::CFCopyTypeIDDescription(TypeID));
    153     std::string TypeIDStr;
    154     errs() << "The Info.plist key \"CFBundleShortVersionString\" is"
    155            << "a " << TypeIDCFStr.UTF8(TypeIDStr)
    156            << ", but it should be a string in: " << ExePath << ".\n";
    157   };
    158 
    159   CFBundle Bundle(ExePath);
    160   if (CFStringRef BundleID = Bundle.GetIdentifier()) {
    161     CFString::UTF8(BundleID, BundleInfo.IDStr);
    162     if (CFTypeRef TypeRef =
    163             Bundle.GetValueForInfoDictionaryKey(CFSTR("CFBundleVersion"))) {
    164       CFTypeID TypeID = ::CFGetTypeID(TypeRef);
    165       if (TypeID == ::CFStringGetTypeID())
    166         CFString::UTF8((CFStringRef)TypeRef, BundleInfo.VersionStr);
    167       else
    168         PrintError(TypeID);
    169     }
    170     if (CFTypeRef TypeRef = Bundle.GetValueForInfoDictionaryKey(
    171             CFSTR("CFBundleShortVersionString"))) {
    172       CFTypeID TypeID = ::CFGetTypeID(TypeRef);
    173       if (TypeID == ::CFStringGetTypeID())
    174         CFString::UTF8((CFStringRef)TypeRef, BundleInfo.ShortVersionStr);
    175       else
    176         PrintError(TypeID);
    177     }
    178   }
    179 #endif
    180 
    181   return BundleInfo;
    182 }
    183 
    184 } // end namespace dsymutil
    185 } // end namespace llvm
    186