Fix _availability_version_check for iOS 11 and 12 (flutter/engine#48624)
This PR ports more of the implementation of availability checking from
clang-rt into the Engine. In particular, when the call to look up the
symbol `_availability_version_check` fails, this PR falls back on
reading the platform version information out of a plist file at a
well-known location, as is done
[here](2fd66e6eb6/compiler-rt/lib/builtins/os_version_check.c (L163)).
This change fixes a mistake in
https://github.com/flutter/engine/pull/44711, which didn't account for
`_availability_version_check` not being available on iOS 11 and 12.
Fixes https://github.com/flutter/flutter/issues/138711
This commit is contained in:
@@ -201,6 +201,7 @@ group("unittests") {
|
||||
public_deps += [
|
||||
"//flutter/impeller/golden_tests:impeller_golden_tests",
|
||||
"//flutter/shell/gpu:gpu_surface_metal_unittests",
|
||||
"//flutter/shell/platform/darwin/common:availability_version_check_unittests",
|
||||
"//flutter/shell/platform/darwin/common:framework_common_unittests",
|
||||
"//flutter/third_party/spring_animation:spring_animation_unittests",
|
||||
]
|
||||
|
||||
@@ -303,6 +303,7 @@
|
||||
../../../flutter/shell/platform/common/text_input_model_unittests.cc
|
||||
../../../flutter/shell/platform/common/text_range_unittests.cc
|
||||
../../../flutter/shell/platform/darwin/Doxyfile
|
||||
../../../flutter/shell/platform/darwin/common/availability_version_check_unittests.cc
|
||||
../../../flutter/shell/platform/darwin/common/framework/Source/flutter_codecs_unittest.mm
|
||||
../../../flutter/shell/platform/darwin/common/framework/Source/flutter_standard_codec_unittest.mm
|
||||
../../../flutter/shell/platform/darwin/macos/README.md
|
||||
|
||||
@@ -6465,6 +6465,7 @@ ORIGIN: ../../../flutter/shell/platform/common/text_input_model.cc + ../../../fl
|
||||
ORIGIN: ../../../flutter/shell/platform/common/text_input_model.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/common/text_range.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/common/availability_version_check.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/common/availability_version_check.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/common/buffer_conversions.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/common/buffer_conversions.mm + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/common/command_line.h + ../../../flutter/LICENSE
|
||||
@@ -6597,6 +6598,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibilit
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/availability_version_check_test.mm + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.mm + ../../../flutter/LICENSE
|
||||
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
|
||||
FILE: ../../../flutter/shell/platform/common/text_input_model.h
|
||||
FILE: ../../../flutter/shell/platform/common/text_range.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/common/availability_version_check.cc
|
||||
FILE: ../../../flutter/shell/platform/darwin/common/availability_version_check.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/common/buffer_conversions.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/common/buffer_conversions.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/common/command_line.h
|
||||
@@ -9414,6 +9417,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/availability_version_check_test.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.h
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection.mm
|
||||
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/connection_collection_test.mm
|
||||
|
||||
@@ -50,6 +50,25 @@ source_set("availability_version_check") {
|
||||
public_configs = [ "//flutter:config" ]
|
||||
}
|
||||
|
||||
test_fixtures("availability_version_check_fixtures") {
|
||||
fixtures = []
|
||||
}
|
||||
|
||||
executable("availability_version_check_unittests") {
|
||||
testonly = true
|
||||
|
||||
sources = [ "availability_version_check_unittests.cc" ]
|
||||
|
||||
deps = [
|
||||
":availability_version_check",
|
||||
":availability_version_check_fixtures",
|
||||
"//flutter/fml",
|
||||
"//flutter/testing",
|
||||
]
|
||||
|
||||
public_configs = [ "//flutter:config" ]
|
||||
}
|
||||
|
||||
# Shared framework headers end up in the same folder as platform-specific
|
||||
# framework headers when consumed by clients, so the include paths assume they
|
||||
# are next to each other.
|
||||
|
||||
@@ -2,20 +2,141 @@
|
||||
// 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 <cstdint>
|
||||
|
||||
#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"
|
||||
|
||||
// See context in https://github.com/flutter/flutter/issues/132130 and
|
||||
// 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
|
||||
// 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 {
|
||||
@@ -36,13 +157,41 @@ void InitializeAvailabilityCheck(void* unused) {
|
||||
}
|
||||
AvailabilityVersionCheck = reinterpret_cast<AvailabilityVersionCheckFn>(
|
||||
dlsym(RTLD_DEFAULT, "_availability_version_check"));
|
||||
FML_CHECK(AvailabilityVersionCheck);
|
||||
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);
|
||||
return AvailabilityVersionCheck(count, versions);
|
||||
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
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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 <cstdint>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace flutter {
|
||||
|
||||
using ProductVersion =
|
||||
std::tuple<int32_t /* major */, int32_t /* minor */, int32_t /* patch */>;
|
||||
|
||||
std::optional<ProductVersion> ProductVersionFromSystemVersionPList();
|
||||
|
||||
bool IsEncodedVersionLessThanOrSame(uint32_t encoded_lhs, ProductVersion rhs);
|
||||
|
||||
} // namespace flutter
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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 <tuple>
|
||||
|
||||
#include "flutter/shell/platform/darwin/common/availability_version_check.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(AvailabilityVersionCheck, CanDecodeSystemPlist) {
|
||||
auto maybe_product_version = flutter::ProductVersionFromSystemVersionPList();
|
||||
ASSERT_TRUE(maybe_product_version.has_value());
|
||||
if (maybe_product_version.has_value()) {
|
||||
auto product_version = maybe_product_version.value();
|
||||
ASSERT_GT(product_version, std::make_tuple(0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t ConstructVersion(uint32_t major,
|
||||
uint32_t minor,
|
||||
uint32_t subminor) {
|
||||
return ((major & 0xffff) << 16) | ((minor & 0xff) << 8) | (subminor & 0xff);
|
||||
}
|
||||
|
||||
TEST(AvailabilityVersionCheck, CanParseAndCompareVersions) {
|
||||
auto rhs_version = std::make_tuple(17, 2, 0);
|
||||
uint32_t encoded_lower_version = ConstructVersion(12, 3, 7);
|
||||
ASSERT_TRUE(flutter::IsEncodedVersionLessThanOrSame(encoded_lower_version,
|
||||
rhs_version));
|
||||
|
||||
uint32_t encoded_higher_version = ConstructVersion(42, 0, 1);
|
||||
ASSERT_FALSE(flutter::IsEncodedVersionLessThanOrSame(encoded_higher_version,
|
||||
rhs_version));
|
||||
}
|
||||
@@ -273,6 +273,7 @@ shared_library("ios_test_flutter") {
|
||||
"framework/Source/UIViewController_FlutterScreenAndSceneIfLoadedTest.mm",
|
||||
"framework/Source/VsyncWaiterIosTest.mm",
|
||||
"framework/Source/accessibility_bridge_test.mm",
|
||||
"framework/Source/availability_version_check_test.mm",
|
||||
"framework/Source/connection_collection_test.mm",
|
||||
"platform_message_handler_ios_test.mm",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
|
||||
#import <tuple>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "flutter/shell/platform/darwin/common/availability_version_check.h"
|
||||
|
||||
@interface AvailabilityVersionCheckTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation AvailabilityVersionCheckTest
|
||||
|
||||
- (void)testSimple {
|
||||
auto maybe_product_version = flutter::ProductVersionFromSystemVersionPList();
|
||||
XCTAssertTrue(maybe_product_version.has_value());
|
||||
if (maybe_product_version.has_value()) {
|
||||
auto product_version = maybe_product_version.value();
|
||||
XCTAssertTrue(product_version > std::make_tuple(0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -435,6 +435,7 @@ def run_cc_tests(build_dir, executable_filter, coverage, capture_core_dump):
|
||||
unittests += [
|
||||
# The accessibility library only supports Mac and Windows.
|
||||
make_test('accessibility_unittests'),
|
||||
make_test('availability_version_check_unittests'),
|
||||
make_test('framework_common_unittests'),
|
||||
make_test('spring_animation_unittests'),
|
||||
make_test('gpu_surface_metal_unittests'),
|
||||
|
||||
Reference in New Issue
Block a user