blob: 1564bec4335f0e93421633c52f518a1bb1ab04fc [file] [log] [blame]
// 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 "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 "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 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.
#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 {
dyld_platform_t platform;
uint32_t version;
} dyld_build_version_t;
typedef bool (*AvailabilityVersionCheckFn)(uint32_t count,
dyld_build_version_t versions[]);
AvailabilityVersionCheckFn AvailabilityVersionCheck;
dispatch_once_t DispatchOnceCounter;
void InitializeAvailabilityCheck(void* unused) {
if (AvailabilityVersionCheck) {
return;
}
AvailabilityVersionCheck = reinterpret_cast<AvailabilityVersionCheckFn>(
dlsym(RTLD_DEFAULT, "_availability_version_check"));
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);
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