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 {
operator ()llvm::dsymutil::CFDeleter27   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:
CFString(CFStringRef CFStr=nullptr)43   CFString(CFStringRef CFStr = nullptr) : CFReleaser<CFStringRef>(CFStr) {}
44 
UTF8(std::string & Str) const45   const char *UTF8(std::string &Str) const {
46     return CFString::UTF8(get(), Str);
47   }
48 
GetLength() const49   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.
UTF8(CFStringRef CFStr,std::string & Str)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:
CFBundle(StringRef Path)87   CFBundle(StringRef Path) : CFReleaser<CFBundleRef>() { SetFromPath(Path); }
88 
CFBundle(CFURLRef Url)89   CFBundle(CFURLRef Url)
90       : CFReleaser<CFBundleRef>(Url ? ::CFBundleCreate(nullptr, Url)
91                                     : nullptr) {}
92 
93   /// Return the bundle identifier.
GetIdentifier() const94   CFStringRef GetIdentifier() const {
95     if (CFBundleRef bundle = get())
96       return ::CFBundleGetIdentifier(bundle);
97     return nullptr;
98   }
99 
100   /// Return value for key.
GetValueForInfoDictionaryKey(CFStringRef key) const101   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 
SetFromPath(StringRef Path)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.
getBundleInfo(StringRef ExePath)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