-
Notifications
You must be signed in to change notification settings - Fork 6k
/
Copy pathavailability_version_check.cc
197 lines (164 loc) · 7.14 KB
/
availability_version_check.cc
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
187
188
189
190
191
192
193
194
195
196
197
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/darwin/common/availability_version_check.h"
#include <cstdint>
#include <optional>
#include <tuple>
#include <CoreFoundation/CoreFoundation.h>
#include <dispatch/dispatch.h>
#include <dlfcn.h>
#include "flutter/fml/build_config.h"
#include "flutter/fml/file.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/platform/darwin/cf_utils.h"
// The implementation of _availability_version_check defined in this file is
// based on the code in the clang-rt library at:
//
// https://github.com/llvm/llvm-project/blob/e315bf25a843582de39257e1345408a10dc08224/compiler-rt/lib/builtins/os_version_check.c
//
// Flutter provides its own implementation due to an issue introduced in recent
// versions of Clang following Clang 18 in which the clang-rt library declares
// weak linkage against the _availability_version_check symbol. This declaration
// causes apps to be rejected from the App Store. When Flutter statically links
// the implementation below, the weak linkage is satisfied at Engine build time,
// the symbol is no longer exposed from the Engine dylib, and apps will then
// not be rejected from the App Store.
//
// The implementation of _availability_version_check can delegate to the
// dynamically looked-up symbol on recent iOS versions, but the lookup will fail
// on iOS 11 and 12. When the lookup fails, the current OS version must be
// retrieved from a plist file at a well-known path. The logic for this below is
// copied from the clang-rt implementation and adapted for the Engine.
// See more context in https://github.com/flutter/flutter/issues/132130 and
// https://github.com/flutter/engine/pull/44711.
// TODO(zanderso): Remove this after Clang 18 rolls into Xcode.
// https://github.com/flutter/flutter/issues/133203.
#define CF_PROPERTY_LIST_IMMUTABLE 0
namespace flutter {
// This function parses the platform's version information out of a plist file
// at a well-known path. It parses the plist file using CoreFoundation functions
// to match the implementation in the clang-rt library.
std::optional<ProductVersion> ProductVersionFromSystemVersionPList() {
std::string plist_path = "/System/Library/CoreServices/SystemVersion.plist";
#if FML_OS_IOS_SIMULATOR
char* plist_path_prefix = getenv("IPHONE_SIMULATOR_ROOT");
if (!plist_path_prefix) {
FML_DLOG(ERROR) << "Failed to getenv IPHONE_SIMULATOR_ROOT";
return std::nullopt;
}
plist_path = std::string(plist_path_prefix) + plist_path;
#endif // FML_OS_IOS_SIMULATOR
auto plist_mapping = fml::FileMapping::CreateReadOnly(plist_path);
// Get the file buffer into CF's format. We pass in a null allocator here *
// because we free PListBuf ourselves
auto file_contents = fml::CFRef<CFDataRef>(CFDataCreateWithBytesNoCopy(
nullptr, plist_mapping->GetMapping(),
static_cast<CFIndex>(plist_mapping->GetSize()), kCFAllocatorNull));
if (!file_contents) {
FML_DLOG(ERROR) << "Failed to CFDataCreateWithBytesNoCopyFunc";
return std::nullopt;
}
auto plist = fml::CFRef<CFDictionaryRef>(
reinterpret_cast<CFDictionaryRef>(CFPropertyListCreateWithData(
nullptr, file_contents, CF_PROPERTY_LIST_IMMUTABLE, nullptr,
nullptr)));
if (!plist) {
FML_DLOG(ERROR) << "Failed to CFPropertyListCreateWithDataFunc or "
"CFPropertyListCreateFromXMLDataFunc";
return std::nullopt;
}
auto product_version =
fml::CFRef<CFStringRef>(CFStringCreateWithCStringNoCopy(
nullptr, "ProductVersion", kCFStringEncodingASCII, kCFAllocatorNull));
if (!product_version) {
FML_DLOG(ERROR) << "Failed to CFStringCreateWithCStringNoCopyFunc";
return std::nullopt;
}
CFTypeRef opaque_value = CFDictionaryGetValue(plist, product_version);
if (!opaque_value || CFGetTypeID(opaque_value) != CFStringGetTypeID()) {
FML_DLOG(ERROR) << "Failed to CFDictionaryGetValueFunc";
return std::nullopt;
}
char version_str[32];
if (!CFStringGetCString(reinterpret_cast<CFStringRef>(opaque_value),
version_str, sizeof(version_str),
kCFStringEncodingUTF8)) {
FML_DLOG(ERROR) << "Failed to CFStringGetCStringFunc";
return std::nullopt;
}
int32_t major = 0;
int32_t minor = 0;
int32_t subminor = 0;
int matches = sscanf(version_str, "%d.%d.%d", &major, &minor, &subminor);
// A major version number is sufficient. The minor and subminor numbers might
// not be present.
if (matches < 1) {
FML_DLOG(ERROR) << "Failed to match product version string: "
<< version_str;
return std::nullopt;
}
return ProductVersion{major, minor, subminor};
}
bool IsEncodedVersionLessThanOrSame(uint32_t encoded_lhs, ProductVersion rhs) {
// Parse the values out of encoded_lhs, then compare against rhs.
const int32_t major = (encoded_lhs >> 16) & 0xffff;
const int32_t minor = (encoded_lhs >> 8) & 0xff;
const int32_t subminor = encoded_lhs & 0xff;
auto lhs = ProductVersion{major, minor, subminor};
return lhs <= rhs;
}
} // namespace flutter
namespace {
// The host's OS version when the dynamic lookup of _availability_version_check
// has failed.
static flutter::ProductVersion g_version;
typedef uint32_t dyld_platform_t;
typedef struct {
dyld_platform_t platform;
uint32_t version;
} dyld_build_version_t;
typedef bool (*AvailabilityVersionCheckFn)(uint32_t count,
dyld_build_version_t versions[]);
AvailabilityVersionCheckFn AvailabilityVersionCheck;
dispatch_once_t DispatchOnceCounter;
void InitializeAvailabilityCheck(void* unused) {
if (AvailabilityVersionCheck) {
return;
}
AvailabilityVersionCheck = reinterpret_cast<AvailabilityVersionCheckFn>(
dlsym(RTLD_DEFAULT, "_availability_version_check"));
if (AvailabilityVersionCheck) {
return;
}
// If _availability_version_check can't be dynamically loaded, then version
// information must be parsed out of a system plist file.
auto product_version = flutter::ProductVersionFromSystemVersionPList();
if (product_version.has_value()) {
g_version = product_version.value();
} else {
// If reading version info out of the system plist file fails, then
// fall back to the minimum version that Flutter supports.
#if FML_OS_IOS || FML_OS_IOS_SIMULATOR
g_version = std::make_tuple(11, 0, 0);
#elif FML_OS_MACOSX
g_version = std::make_tuple(10, 14, 0);
#endif // FML_OS_MACOSX
}
}
extern "C" bool _availability_version_check(uint32_t count,
dyld_build_version_t versions[]) {
dispatch_once_f(&DispatchOnceCounter, NULL, InitializeAvailabilityCheck);
if (AvailabilityVersionCheck) {
return AvailabilityVersionCheck(count, versions);
}
if (count == 0) {
return true;
}
// This function is called in only one place in the clang-rt implementation
// where there is only one element in the array.
return flutter::IsEncodedVersionLessThanOrSame(versions[0].version,
g_version);
}
} // namespace