Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix _availability_version_check for iOS 11 and 12 #48624

Merged
merged 1 commit into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand Down
1 change: 1 addition & 0 deletions ci/licenses_golden/excluded_files
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions shell/platform/darwin/common/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
159 changes: 154 additions & 5 deletions shell/platform/darwin/common/availability_version_check.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"

// 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 context in https://github.com/flutter/flutter/issues/132130 and
// 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";
zanderso marked this conversation as resolved.
Show resolved Hide resolved
#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 {
Expand All @@ -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) {
zanderso marked this conversation as resolved.
Show resolved Hide resolved
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
18 changes: 18 additions & 0 deletions shell/platform/darwin/common/availability_version_check.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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));
}
1 change: 1 addition & 0 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand Down
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions testing/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ def make_test(name, flags=None, extra_env=None):
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'),
Expand Down