22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
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>
512#include < dispatch/dispatch.h>
613#include < dlfcn.h>
7- #include < cstdint>
814
15+ #include " flutter/fml/build_config.h"
16+ #include " flutter/fml/file.h"
917#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.
1039
11- // See context in https://github.com/flutter/flutter/issues/132130 and
40+ // See more context in https://github.com/flutter/flutter/issues/132130 and
1241// https://github.com/flutter/engine/pull/44711.
1342
1443// TODO(zanderso): Remove this after Clang 18 rolls into Xcode.
15- // https://github.com/flutter/flutter/issues/133203
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
16133
17134namespace {
18135
136+ // The host's OS version when the dynamic lookup of _availability_version_check
137+ // has failed.
138+ static flutter::ProductVersion g_version;
139+
19140typedef uint32_t dyld_platform_t ;
20141
21142typedef struct {
@@ -36,13 +157,41 @@ void InitializeAvailabilityCheck(void* unused) {
36157 }
37158 AvailabilityVersionCheck = reinterpret_cast <AvailabilityVersionCheckFn>(
38159 dlsym (RTLD_DEFAULT, " _availability_version_check" ));
39- FML_CHECK (AvailabilityVersionCheck);
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+ }
40178}
41179
42180extern " C" bool _availability_version_check (uint32_t count,
43181 dyld_build_version_t versions[]) {
44182 dispatch_once_f (&DispatchOnceCounter, NULL , InitializeAvailabilityCheck);
45- return AvailabilityVersionCheck (count, versions);
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);
46195}
47196
48197} // namespace
0 commit comments