reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
//===- tools/dsymutil/CFBundle.cpp - CFBundle helper ------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "CFBundle.h"

#ifdef __APPLE__
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <CoreFoundation/CoreFoundation.h>
#include <assert.h>
#include <glob.h>
#include <memory>
#endif

namespace llvm {
namespace dsymutil {

#ifdef __APPLE__
/// Deleter that calls CFRelease rather than deleting the pointer.
template <typename T> struct CFDeleter {
  void operator()(T *P) {
    if (P)
      ::CFRelease(P);
  }
};

/// This helper owns any CoreFoundation pointer and will call CFRelease() on
/// any valid pointer it owns unless that pointer is explicitly released using
/// the release() member function.
template <typename T>
using CFReleaser =
    std::unique_ptr<typename std::remove_pointer<T>::type,
                    CFDeleter<typename std::remove_pointer<T>::type>>;

/// RAII wrapper around CFBundleRef.
class CFString : public CFReleaser<CFStringRef> {
public:
  CFString(CFStringRef CFStr = nullptr) : CFReleaser<CFStringRef>(CFStr) {}

  const char *UTF8(std::string &Str) const {
    return CFString::UTF8(get(), Str);
  }

  CFIndex GetLength() const {
    if (CFStringRef Str = get())
      return CFStringGetLength(Str);
    return 0;
  }

  static const char *UTF8(CFStringRef CFStr, std::string &Str);
};

/// Static function that puts a copy of the UTF-8 contents of CFStringRef into
/// std::string and returns the C string pointer that is contained in the
/// std::string when successful, nullptr otherwise.
///
/// This allows the std::string parameter to own the extracted string, and also
/// allows that string to be returned as a C string pointer that can be used.
const char *CFString::UTF8(CFStringRef CFStr, std::string &Str) {
  if (!CFStr)
    return nullptr;

  const CFStringEncoding Encoding = kCFStringEncodingUTF8;
  CFIndex MaxUTF8StrLength = CFStringGetLength(CFStr);
  MaxUTF8StrLength =
      CFStringGetMaximumSizeForEncoding(MaxUTF8StrLength, Encoding);
  if (MaxUTF8StrLength > 0) {
    Str.resize(MaxUTF8StrLength);
    if (!Str.empty() &&
        CFStringGetCString(CFStr, &Str[0], Str.size(), Encoding)) {
      Str.resize(strlen(Str.c_str()));
      return Str.c_str();
    }
  }

  return nullptr;
}

/// RAII wrapper around CFBundleRef.
class CFBundle : public CFReleaser<CFBundleRef> {
public:
  CFBundle(StringRef Path) : CFReleaser<CFBundleRef>() { SetFromPath(Path); }

  CFBundle(CFURLRef Url)
      : CFReleaser<CFBundleRef>(Url ? ::CFBundleCreate(nullptr, Url)
                                    : nullptr) {}

  /// Return the bundle identifier.
  CFStringRef GetIdentifier() const {
    if (CFBundleRef bundle = get())
      return ::CFBundleGetIdentifier(bundle);
    return nullptr;
  }

  /// Return value for key.
  CFTypeRef GetValueForInfoDictionaryKey(CFStringRef key) const {
    if (CFBundleRef bundle = get())
      return ::CFBundleGetValueForInfoDictionaryKey(bundle, key);
    return nullptr;
  }

private:
  /// Helper to initialize this instance with a new bundle created from the
  /// given path. This function will recursively remove components from the
  /// path in its search for the nearest Info.plist.
  void SetFromPath(StringRef Path);
};

void CFBundle::SetFromPath(StringRef Path) {
  // Start from an empty/invalid CFBundle.
  reset();

  if (Path.empty() || !sys::fs::exists(Path))
    return;

  SmallString<256> RealPath;
  sys::fs::real_path(Path, RealPath, /*expand_tilde*/ true);

  do {
    // Create a CFURL from the current path and use it to create a CFBundle.
    CFReleaser<CFURLRef> BundleURL(::CFURLCreateFromFileSystemRepresentation(
        kCFAllocatorDefault, (const UInt8 *)RealPath.data(), RealPath.size(),
        false));
    reset(::CFBundleCreate(kCFAllocatorDefault, BundleURL.get()));

    // If we have a valid bundle and find its identifier we are done.
    if (get() != nullptr) {
      if (GetIdentifier() != nullptr)
        return;
      reset();
    }

    // Remove the last component of the path and try again until there's
    // nothing left but the root.
    sys::path::remove_filename(RealPath);
  } while (RealPath != sys::path::root_name(RealPath));
}
#endif

/// On Darwin, try and find the original executable's Info.plist to extract
/// information about the bundle. Return default values on other platforms.
CFBundleInfo getBundleInfo(StringRef ExePath) {
  CFBundleInfo BundleInfo;

#ifdef __APPLE__
  auto PrintError = [&](CFTypeID TypeID) {
    CFString TypeIDCFStr(::CFCopyTypeIDDescription(TypeID));
    std::string TypeIDStr;
    errs() << "The Info.plist key \"CFBundleShortVersionString\" is"
           << "a " << TypeIDCFStr.UTF8(TypeIDStr)
           << ", but it should be a string in: " << ExePath << ".\n";
  };

  CFBundle Bundle(ExePath);
  if (CFStringRef BundleID = Bundle.GetIdentifier()) {
    CFString::UTF8(BundleID, BundleInfo.IDStr);
    if (CFTypeRef TypeRef =
            Bundle.GetValueForInfoDictionaryKey(CFSTR("CFBundleVersion"))) {
      CFTypeID TypeID = ::CFGetTypeID(TypeRef);
      if (TypeID == ::CFStringGetTypeID())
        CFString::UTF8((CFStringRef)TypeRef, BundleInfo.VersionStr);
      else
        PrintError(TypeID);
    }
    if (CFTypeRef TypeRef = Bundle.GetValueForInfoDictionaryKey(
            CFSTR("CFBundleShortVersionString"))) {
      CFTypeID TypeID = ::CFGetTypeID(TypeRef);
      if (TypeID == ::CFStringGetTypeID())
        CFString::UTF8((CFStringRef)TypeRef, BundleInfo.ShortVersionStr);
      else
        PrintError(TypeID);
    }
  }
#endif

  return BundleInfo;
}

} // end namespace dsymutil
} // end namespace llvm