Flutter macOS Embedder
availability_version_check.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #include <cstdint>
8 #include <optional>
9 #include <tuple>
10 
11 #include <CoreFoundation/CoreFoundation.h>
12 #include <dispatch/dispatch.h>
13 #include <dlfcn.h>
14 
15 #include "flutter/fml/build_config.h"
16 #include "flutter/fml/file.h"
17 #include "flutter/fml/logging.h"
18 #include "flutter/fml/mapping.h"
19 #include "flutter/fml/platform/darwin/cf_utils.h"
20 
21 // The implementation of _availability_version_check defined in this file is
22 // based on the code in the clang-rt library at:
23 //
24 // https://github.com/llvm/llvm-project/blob/e315bf25a843582de39257e1345408a10dc08224/compiler-rt/lib/builtins/os_version_check.c
25 //
26 // Flutter provides its own implementation due to an issue introduced in recent
27 // versions of Clang following Clang 18 in which the clang-rt library declares
28 // weak linkage against the _availability_version_check symbol. This declaration
29 // causes apps to be rejected from the App Store. When Flutter statically links
30 // the implementation below, the weak linkage is satisfied at Engine build time,
31 // the symbol is no longer exposed from the Engine dylib, and apps will then
32 // not be rejected from the App Store.
33 //
34 // The implementation of _availability_version_check can delegate to the
35 // dynamically looked-up symbol on recent iOS versions, but the lookup will fail
36 // on iOS 11 and 12. When the lookup fails, the current OS version must be
37 // retrieved from a plist file at a well-known path. The logic for this below is
38 // copied from the clang-rt implementation and adapted for the Engine.
39 
40 // See more context in https://github.com/flutter/flutter/issues/132130 and
41 // https://github.com/flutter/engine/pull/44711.
42 
43 // TODO(zanderso): Remove this after Clang 18 rolls into Xcode.
44 // https://github.com/flutter/flutter/issues/133203.
45 
46 #define CF_PROPERTY_LIST_IMMUTABLE 0
47 
48 namespace flutter {
49 
50 // This function parses the platform's version information out of a plist file
51 // at a well-known path. It parses the plist file using CoreFoundation functions
52 // to match the implementation in the clang-rt library.
53 std::optional<ProductVersion> ProductVersionFromSystemVersionPList() {
54  std::string plist_path = "/System/Library/CoreServices/SystemVersion.plist";
55 #if FML_OS_IOS_SIMULATOR
56  char* plist_path_prefix = getenv("IPHONE_SIMULATOR_ROOT");
57  if (!plist_path_prefix) {
58  FML_DLOG(ERROR) << "Failed to getenv IPHONE_SIMULATOR_ROOT";
59  return std::nullopt;
60  }
61  plist_path = std::string(plist_path_prefix) + plist_path;
62 #endif // FML_OS_IOS_SIMULATOR
63 
64  auto plist_mapping = fml::FileMapping::CreateReadOnly(plist_path);
65 
66  // Get the file buffer into CF's format. We pass in a null allocator here *
67  // because we free PListBuf ourselves
68  auto file_contents = fml::CFRef<CFDataRef>(CFDataCreateWithBytesNoCopy(
69  nullptr, plist_mapping->GetMapping(),
70  static_cast<CFIndex>(plist_mapping->GetSize()), kCFAllocatorNull));
71  if (!file_contents) {
72  FML_DLOG(ERROR) << "Failed to CFDataCreateWithBytesNoCopyFunc";
73  return std::nullopt;
74  }
75 
76  auto plist = fml::CFRef<CFDictionaryRef>(
77  reinterpret_cast<CFDictionaryRef>(CFPropertyListCreateWithData(
78  nullptr, file_contents, CF_PROPERTY_LIST_IMMUTABLE, nullptr,
79  nullptr)));
80  if (!plist) {
81  FML_DLOG(ERROR) << "Failed to CFPropertyListCreateWithDataFunc or "
82  "CFPropertyListCreateFromXMLDataFunc";
83  return std::nullopt;
84  }
85 
86  auto product_version =
87  fml::CFRef<CFStringRef>(CFStringCreateWithCStringNoCopy(
88  nullptr, "ProductVersion", kCFStringEncodingASCII, kCFAllocatorNull));
89  if (!product_version) {
90  FML_DLOG(ERROR) << "Failed to CFStringCreateWithCStringNoCopyFunc";
91  return std::nullopt;
92  }
93  CFTypeRef opaque_value = CFDictionaryGetValue(plist, product_version);
94  if (!opaque_value || CFGetTypeID(opaque_value) != CFStringGetTypeID()) {
95  FML_DLOG(ERROR) << "Failed to CFDictionaryGetValueFunc";
96  return std::nullopt;
97  }
98 
99  char version_str[32];
100  if (!CFStringGetCString(reinterpret_cast<CFStringRef>(opaque_value),
101  version_str, sizeof(version_str),
102  kCFStringEncodingUTF8)) {
103  FML_DLOG(ERROR) << "Failed to CFStringGetCStringFunc";
104  return std::nullopt;
105  }
106 
107  int32_t major = 0;
108  int32_t minor = 0;
109  int32_t subminor = 0;
110  int matches = sscanf(version_str, "%d.%d.%d", &major, &minor, &subminor);
111  // A major version number is sufficient. The minor and subminor numbers might
112  // not be present.
113  if (matches < 1) {
114  FML_DLOG(ERROR) << "Failed to match product version string: "
115  << version_str;
116  return std::nullopt;
117  }
118 
119  return ProductVersion{major, minor, subminor};
120 }
121 
122 bool IsEncodedVersionLessThanOrSame(uint32_t encoded_lhs, ProductVersion rhs) {
123  // Parse the values out of encoded_lhs, then compare against rhs.
124  const int32_t major = (encoded_lhs >> 16) & 0xffff;
125  const int32_t minor = (encoded_lhs >> 8) & 0xff;
126  const int32_t subminor = encoded_lhs & 0xff;
127  auto lhs = ProductVersion{major, minor, subminor};
128 
129  return lhs <= rhs;
130 }
131 
132 } // namespace flutter
133 
134 namespace {
135 
136 // The host's OS version when the dynamic lookup of _availability_version_check
137 // has failed.
138 static flutter::ProductVersion g_version;
139 
140 typedef uint32_t dyld_platform_t;
141 
142 typedef struct {
143  dyld_platform_t platform;
144  uint32_t version;
145 } dyld_build_version_t;
146 
147 typedef bool (*AvailabilityVersionCheckFn)(uint32_t count,
148  dyld_build_version_t versions[]);
149 
150 AvailabilityVersionCheckFn AvailabilityVersionCheck;
151 
152 dispatch_once_t DispatchOnceCounter;
153 
154 void InitializeAvailabilityCheck(void* unused) {
155  if (AvailabilityVersionCheck) {
156  return;
157  }
158  AvailabilityVersionCheck = reinterpret_cast<AvailabilityVersionCheckFn>(
159  dlsym(RTLD_DEFAULT, "_availability_version_check"));
160  if (AvailabilityVersionCheck) {
161  return;
162  }
163 
164  // If _availability_version_check can't be dynamically loaded, then version
165  // information must be parsed out of a system plist file.
166  auto product_version = flutter::ProductVersionFromSystemVersionPList();
167  if (product_version.has_value()) {
168  g_version = product_version.value();
169  } else {
170  // If reading version info out of the system plist file fails, then
171  // fall back to the minimum version that Flutter supports.
172 #if FML_OS_IOS || FML_OS_IOS_SIMULATOR
173  g_version = std::make_tuple(11, 0, 0);
174 #elif FML_OS_MACOSX
175  g_version = std::make_tuple(10, 14, 0);
176 #endif // FML_OS_MACOSX
177  }
178 }
179 
180 extern "C" bool _availability_version_check(uint32_t count,
181  dyld_build_version_t versions[]) {
182  dispatch_once_f(&DispatchOnceCounter, NULL, InitializeAvailabilityCheck);
183  if (AvailabilityVersionCheck) {
184  return AvailabilityVersionCheck(count, versions);
185  }
186 
187  if (count == 0) {
188  return true;
189  }
190 
191  // This function is called in only one place in the clang-rt implementation
192  // where there is only one element in the array.
193  return flutter::IsEncodedVersionLessThanOrSame(versions[0].version,
194  g_version);
195 }
196 
197 } // namespace
CF_PROPERTY_LIST_IMMUTABLE
#define CF_PROPERTY_LIST_IMMUTABLE
Definition: availability_version_check.cc:46
flutter::ProductVersion
std::tuple< int32_t, int32_t, int32_t > ProductVersion
Definition: availability_version_check.h:15
flutter
Definition: AccessibilityBridgeMac.h:16
flutter::ProductVersionFromSystemVersionPList
std::optional< ProductVersion > ProductVersionFromSystemVersionPList()
Definition: availability_version_check.cc:53
availability_version_check.h
flutter::IsEncodedVersionLessThanOrSame
bool IsEncodedVersionLessThanOrSame(uint32_t encoded_lhs, ProductVersion rhs)
Definition: availability_version_check.cc:122