Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a027917

Browse files
committed
Fix _availability_version_check for iOS 11 and 12
1 parent 9b85b76 commit a027917

File tree

10 files changed

+260
-5
lines changed

10 files changed

+260
-5
lines changed

BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ group("unittests") {
201201
public_deps += [
202202
"//flutter/impeller/golden_tests:impeller_golden_tests",
203203
"//flutter/shell/gpu:gpu_surface_metal_unittests",
204+
"//flutter/shell/platform/darwin/common:availability_version_check_unittests",
204205
"//flutter/shell/platform/darwin/common:framework_common_unittests",
205206
"//flutter/third_party/spring_animation:spring_animation_unittests",
206207
]

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@
303303
../../../flutter/shell/platform/common/text_input_model_unittests.cc
304304
../../../flutter/shell/platform/common/text_range_unittests.cc
305305
../../../flutter/shell/platform/darwin/Doxyfile
306+
../../../flutter/shell/platform/darwin/common/availability_version_check_unittests.cc
306307
../../../flutter/shell/platform/darwin/common/framework/Source/flutter_codecs_unittest.mm
307308
../../../flutter/shell/platform/darwin/common/framework/Source/flutter_standard_codec_unittest.mm
308309
../../../flutter/shell/platform/darwin/macos/README.md

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6465,6 +6465,7 @@ ORIGIN: ../../../flutter/shell/platform/common/text_input_model.cc + ../../../fl
64656465
ORIGIN: ../../../flutter/shell/platform/common/text_input_model.h + ../../../flutter/LICENSE
64666466
ORIGIN: ../../../flutter/shell/platform/common/text_range.h + ../../../flutter/LICENSE
64676467
ORIGIN: ../../../flutter/shell/platform/darwin/common/availability_version_check.cc + ../../../flutter/LICENSE
6468+
ORIGIN: ../../../flutter/shell/platform/darwin/common/availability_version_check.h + ../../../flutter/LICENSE
64686469
ORIGIN: ../../../flutter/shell/platform/darwin/common/buffer_conversions.h + ../../../flutter/LICENSE
64696470
ORIGIN: ../../../flutter/shell/platform/darwin/common/buffer_conversions.mm + ../../../flutter/LICENSE
64706471
ORIGIN: ../../../flutter/shell/platform/darwin/common/command_line.h + ../../../flutter/LICENSE
@@ -6597,6 +6598,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibilit
65976598
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm + ../../../flutter/LICENSE
65986599
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h + ../../../flutter/LICENSE
65996600
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm + ../../../flutter/LICENSE
6601+
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/availability_version_check_test.mm + ../../../flutter/LICENSE
66006602
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.h + ../../../flutter/LICENSE
66016603
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.mm + ../../../flutter/LICENSE
66026604
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection_test.mm + ../../../flutter/LICENSE
@@ -9281,6 +9283,7 @@ FILE: ../../../flutter/shell/platform/common/text_input_model.cc
92819283
FILE: ../../../flutter/shell/platform/common/text_input_model.h
92829284
FILE: ../../../flutter/shell/platform/common/text_range.h
92839285
FILE: ../../../flutter/shell/platform/darwin/common/availability_version_check.cc
9286+
FILE: ../../../flutter/shell/platform/darwin/common/availability_version_check.h
92849287
FILE: ../../../flutter/shell/platform/darwin/common/buffer_conversions.h
92859288
FILE: ../../../flutter/shell/platform/darwin/common/buffer_conversions.mm
92869289
FILE: ../../../flutter/shell/platform/darwin/common/command_line.h
@@ -9414,6 +9417,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_
94149417
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
94159418
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h
94169419
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm
9420+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/availability_version_check_test.mm
94179421
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.h
94189422
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.mm
94199423
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection_test.mm

shell/platform/darwin/common/BUILD.gn

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ source_set("availability_version_check") {
5050
public_configs = [ "//flutter:config" ]
5151
}
5252

53+
test_fixtures("availability_version_check_fixtures") {
54+
fixtures = []
55+
}
56+
57+
executable("availability_version_check_unittests") {
58+
testonly = true
59+
60+
sources = [ "availability_version_check_unittests.cc" ]
61+
62+
deps = [
63+
":availability_version_check",
64+
":availability_version_check_fixtures",
65+
"//flutter/fml",
66+
"//flutter/testing",
67+
]
68+
69+
public_configs = [ "//flutter:config" ]
70+
}
71+
5372
# Shared framework headers end up in the same folder as platform-specific
5473
# framework headers when consumed by clients, so the include paths assume they
5574
# are next to each other.

shell/platform/darwin/common/availability_version_check.cc

Lines changed: 154 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,141 @@
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

17134
namespace {
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+
19140
typedef uint32_t dyld_platform_t;
20141

21142
typedef 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

42180
extern "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
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 <cstdint>
6+
#include <optional>
7+
#include <tuple>
8+
9+
namespace flutter {
10+
11+
using ProductVersion =
12+
std::tuple<int32_t /* major */, int32_t /* minor */, int32_t /* patch */>;
13+
14+
std::optional<ProductVersion> ProductVersionFromSystemVersionPList();
15+
16+
bool IsEncodedVersionLessThanOrSame(uint32_t encoded_lhs, ProductVersion rhs);
17+
18+
} // namespace flutter
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 <tuple>
6+
7+
#include "flutter/shell/platform/darwin/common/availability_version_check.h"
8+
9+
#include "gtest/gtest.h"
10+
11+
TEST(AvailabilityVersionCheck, CanDecodeSystemPlist) {
12+
auto maybe_product_version = flutter::ProductVersionFromSystemVersionPList();
13+
ASSERT_TRUE(maybe_product_version.has_value());
14+
if (maybe_product_version.has_value()) {
15+
auto product_version = maybe_product_version.value();
16+
ASSERT_GT(product_version, std::make_tuple(0, 0, 0));
17+
}
18+
}
19+
20+
static inline uint32_t ConstructVersion(uint32_t major,
21+
uint32_t minor,
22+
uint32_t subminor) {
23+
return ((major & 0xffff) << 16) | ((minor & 0xff) << 8) | (subminor & 0xff);
24+
}
25+
26+
TEST(AvailabilityVersionCheck, CanParseAndCompareVersions) {
27+
auto rhs_version = std::make_tuple(17, 2, 0);
28+
uint32_t encoded_lower_version = ConstructVersion(12, 3, 7);
29+
ASSERT_TRUE(flutter::IsEncodedVersionLessThanOrSame(encoded_lower_version,
30+
rhs_version));
31+
32+
uint32_t encoded_higher_version = ConstructVersion(42, 0, 1);
33+
ASSERT_FALSE(flutter::IsEncodedVersionLessThanOrSame(encoded_higher_version,
34+
rhs_version));
35+
}

shell/platform/darwin/ios/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ shared_library("ios_test_flutter") {
273273
"framework/Source/UIViewController_FlutterScreenAndSceneIfLoadedTest.mm",
274274
"framework/Source/VsyncWaiterIosTest.mm",
275275
"framework/Source/accessibility_bridge_test.mm",
276+
"framework/Source/availability_version_check_test.mm",
276277
"framework/Source/connection_collection_test.mm",
277278
"platform_message_handler_ios_test.mm",
278279
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
#import <tuple>
6+
7+
#import <OCMock/OCMock.h>
8+
#import <XCTest/XCTest.h>
9+
10+
#import "flutter/shell/platform/darwin/common/availability_version_check.h"
11+
12+
@interface AvailabilityVersionCheckTest : XCTestCase
13+
@end
14+
15+
@implementation AvailabilityVersionCheckTest
16+
17+
- (void)testSimple {
18+
auto maybe_product_version = flutter::ProductVersionFromSystemVersionPList();
19+
XCTAssertTrue(maybe_product_version.has_value());
20+
if (maybe_product_version.has_value()) {
21+
auto product_version = maybe_product_version.value();
22+
XCTAssertTrue(product_version > std::make_tuple(0, 0, 0));
23+
}
24+
}
25+
26+
@end

testing/run_tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ def make_test(name, flags=None, extra_env=None):
435435
unittests += [
436436
# The accessibility library only supports Mac and Windows.
437437
make_test('accessibility_unittests'),
438+
make_test('availability_version_check_unittests'),
438439
make_test('framework_common_unittests'),
439440
make_test('spring_animation_unittests'),
440441
make_test('gpu_surface_metal_unittests'),

0 commit comments

Comments
 (0)