| // 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 |