2
2
// Use of this source code is governed by a BSD-style license that can be
3
3
// found in the LICENSE file.
4
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>
5
12
#include < dispatch/dispatch.h>
6
13
#include < dlfcn.h>
7
- #include < cstdint>
8
14
15
+ #include " flutter/fml/build_config.h"
16
+ #include " flutter/fml/file.h"
9
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.
10
39
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
12
41
// https://github.com/flutter/engine/pull/44711.
13
42
14
43
// 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
16
133
17
134
namespace {
18
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
+
19
140
typedef uint32_t dyld_platform_t ;
20
141
21
142
typedef struct {
@@ -36,13 +157,41 @@ void InitializeAvailabilityCheck(void* unused) {
36
157
}
37
158
AvailabilityVersionCheck = reinterpret_cast <AvailabilityVersionCheckFn>(
38
159
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
+ }
40
178
}
41
179
42
180
extern " C" bool _availability_version_check (uint32_t count,
43
181
dyld_build_version_t versions[]) {
44
182
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);
46
195
}
47
196
48
197
} // namespace
0 commit comments