Skip to content

Commit

Permalink
Fix _availability_version_check for iOS 11 and 12 (#48624)
Browse files Browse the repository at this point in the history
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](https://github.com/llvm/llvm-project/blob/2fd66e6eb659701b9d4c88708d55d5854a246815/compiler-rt/lib/builtins/os_version_check.c#L163).

This change fixes a mistake in
#44711, which didn't account for
`_availability_version_check` not being available on iOS 11 and 12.

Fixes flutter/flutter#138711
  • Loading branch information
zanderso authored Dec 11, 2023
1 parent 44d12b4 commit 5587d26
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 5 deletions.
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";
#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) {
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

0 comments on commit 5587d26

Please sign in to comment.