| Flutter iOS 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
5 #include "flutter/shell/platform/darwin/common/availability_version_check.h"
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
availability_version_check.h
flutter
Definition: accessibility_bridge.h:26
flutter::IsEncodedVersionLessThanOrSame
bool IsEncodedVersionLessThanOrSame(uint32_t encoded_lhs, ProductVersion rhs)
Definition: availability_version_check.cc:122
flutter::ProductVersion
std::tuple< int32_t, int32_t, int32_t > ProductVersion
Definition: availability_version_check.h:15
flutter::ProductVersionFromSystemVersionPList
std::optional< ProductVersion > ProductVersionFromSystemVersionPList()
Definition: availability_version_check.cc:53