Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
48namespace 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.
53std::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
122bool 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
134namespace {
135
136// The host's OS version when the dynamic lookup of _availability_version_check
137// has failed.
138static flutter::ProductVersion g_version;
139
140typedef uint32_t dyld_platform_t;
141
142typedef struct {
143 dyld_platform_t platform;
144 uint32_t version;
145} dyld_build_version_t;
146
147typedef bool (*AvailabilityVersionCheckFn)(uint32_t count,
148 dyld_build_version_t versions[]);
149
150AvailabilityVersionCheckFn AvailabilityVersionCheck;
151
152dispatch_once_t DispatchOnceCounter;
153
154void 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
180extern "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
static bool unused
int count
#define CF_PROPERTY_LIST_IMMUTABLE
static std::unique_ptr< FileMapping > CreateReadOnly(const std::string &path)
Definition mapping.cc:20
#define FML_DLOG(severity)
Definition logging.h:102
bool IsEncodedVersionLessThanOrSame(uint32_t encoded_lhs, ProductVersion rhs)
std::tuple< int32_t, int32_t, int32_t > ProductVersion
std::optional< ProductVersion > ProductVersionFromSystemVersionPList()
#define ERROR(message)