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