未验证 提交 f0fb74b8 编写于 作者: C Chinmay Garde 提交者: GitHub

Avoid crashing and display error if the process cannot be prepared for JIT mode Dart VM. (#20980)

上级 af90dd36
......@@ -582,8 +582,8 @@ FILE: ../../../flutter/runtime/embedder_resources.h
FILE: ../../../flutter/runtime/fixtures/runtime_test.dart
FILE: ../../../flutter/runtime/platform_data.cc
FILE: ../../../flutter/runtime/platform_data.h
FILE: ../../../flutter/runtime/ptrace_ios.cc
FILE: ../../../flutter/runtime/ptrace_ios.h
FILE: ../../../flutter/runtime/ptrace_check.cc
FILE: ../../../flutter/runtime/ptrace_check.h
FILE: ../../../flutter/runtime/runtime_controller.cc
FILE: ../../../flutter/runtime/runtime_controller.h
FILE: ../../../flutter/runtime/runtime_delegate.cc
......
// 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.
// FLUTTER_NOLINT
#include <fstream>
#include <iterator>
......@@ -129,7 +130,7 @@ void DartCallbackCache::LoadCacheFromDisk() {
Document d;
d.Parse(cache_contents.c_str());
if (d.HasParseError() || !d.IsArray()) {
FML_LOG(WARNING) << "Could not parse callback cache, aborting restore";
FML_LOG(INFO) << "Could not parse callback cache, aborting restore";
// TODO(bkonyi): log and bail (delete cache?)
return;
}
......
......@@ -55,8 +55,7 @@ source_set("runtime") {
"embedder_resources.h",
"platform_data.cc",
"platform_data.h",
"ptrace_ios.cc",
"ptrace_ios.h",
"ptrace_check.h",
"runtime_controller.cc",
"runtime_controller.h",
"runtime_delegate.cc",
......@@ -67,6 +66,11 @@ source_set("runtime") {
"skia_concurrent_executor.h",
]
if (is_ios && flutter_runtime_mode == "debug") {
# These contain references to private APIs and this TU must only be compiled in debug runtime modes.
sources += [ "ptrace_check.cc" ]
}
public_deps = [ "//third_party/rapidjson" ]
public_configs = [ "//flutter:config" ]
......
......@@ -24,7 +24,7 @@
#include "flutter/lib/ui/dart_ui.h"
#include "flutter/runtime/dart_isolate.h"
#include "flutter/runtime/dart_service_isolate.h"
#include "flutter/runtime/ptrace_ios.h"
#include "flutter/runtime/ptrace_check.h"
#include "third_party/dart/runtime/include/bin/dart_io_api.h"
#include "third_party/skia/include/core/SkExecutor.h"
#include "third_party/tonic/converter/dart_converter.h"
......@@ -330,7 +330,12 @@ DartVM::DartVM(std::shared_ptr<const DartVMData> vm_data,
PushBackAll(&args, kDartWriteProtectCodeArgs,
fml::size(kDartWriteProtectCodeArgs));
#else
EnsureDebuggedIOS(settings_);
const bool tracing_result = EnableTracingIfNecessary(settings_);
// This check should only trip if the embedding made no attempts to enable
// tracing. At this point, it is too late display user visible messages. Just
// log and die.
FML_CHECK(tracing_result)
<< "Tracing not enabled before attempting to run JIT mode VM.";
#if TARGET_CPU_ARM
// Tell Dart in JIT mode to not use integer division on armv7
// Ideally, this would be detected at runtime by Dart.
......
......@@ -19,28 +19,41 @@
// - go/decommissioning-dbc
// - go/decommissioning-dbc-engine
// - go/decommissioning-dbc-tools
#include "flutter/common/settings.h"
#include "flutter/fml/build_config.h" // For OS_IOS.
#if OS_IOS && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
#include "flutter/runtime/ptrace_check.h"
#if TRACING_CHECKS_NECESSARY
// These headers should only be needed in debug mode.
#include <sys/sysctl.h>
#include <sys/types.h>
#include <mutex>
#include "flutter/fml/build_config.h"
// Being extra careful and adding additional landmines that will prevent
// compilation of this TU in an incorrect runtime mode.
static_assert(OS_IOS, "This translation unit is iOS specific.");
static_assert(FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG,
"This translation unit must only be compiled in the debug "
"runtime mode as it "
"contains private API usage.");
#define PT_TRACE_ME 0
#define PT_SIGEXC 12
extern "C" int ptrace(int request, pid_t pid, caddr_t addr, int data);
static bool DebuggedIOS(const flutter::Settings& vm_settings) {
namespace flutter {
static bool IsLaunchedByFlutterCLI(const Settings& vm_settings) {
// Only the Flutter CLI passes "--enable-checked-mode". Therefore, if the flag
// is present, we have been launched by "ios-deploy" via "debugserver".
//
// We choose this flag because it is always passed to launch debug builds.
if (vm_settings.enable_checked_mode) {
return true;
}
return vm_settings.enable_checked_mode;
}
static bool IsLaunchedByXcode() {
// Use "sysctl()" to check if we're currently being debugged (e.g. by Xcode).
// We could also check "getppid() != 1" (launchd), but this is more direct.
const pid_t self = getpid();
......@@ -48,7 +61,7 @@ static bool DebuggedIOS(const flutter::Settings& vm_settings) {
auto proc = std::make_unique<struct kinfo_proc>();
size_t proc_size = sizeof(struct kinfo_proc);
if (sysctl(mib, 4, proc.get(), &proc_size, nullptr, 0) < 0) {
if (::sysctl(mib, 4, proc.get(), &proc_size, nullptr, 0) < 0) {
FML_LOG(ERROR) << "Could not execute sysctl() to get current process info: "
<< strerror(errno);
return false;
......@@ -57,18 +70,16 @@ static bool DebuggedIOS(const flutter::Settings& vm_settings) {
return proc->kp_proc.p_flag & P_TRACED;
}
void EnsureDebuggedIOS(const flutter::Settings& vm_settings) {
if (DebuggedIOS(vm_settings)) {
return;
}
if (ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1) {
static bool EnableTracingManually(const Settings& vm_settings) {
if (::ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1) {
FML_LOG(ERROR) << "Could not call ptrace(PT_TRACE_ME): " << strerror(errno);
// No use trying PT_SIGEXC -- it's only needed if PT_TRACE_ME succeeds.
return;
return false;
}
if (ptrace(PT_SIGEXC, 0, nullptr, 0) == -1) {
if (::ptrace(PT_SIGEXC, 0, nullptr, 0) == -1) {
FML_LOG(ERROR) << "Could not call ptrace(PT_SIGEXC): " << strerror(errno);
return false;
}
// The previous operation causes this process to not be reaped after it
......@@ -78,11 +89,12 @@ void EnsureDebuggedIOS(const flutter::Settings& vm_settings) {
size_t maxproc = 0;
size_t maxproc_size = sizeof(size_t);
const int sysctl_result =
sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, nullptr, 0);
::sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, nullptr, 0);
if (sysctl_result < 0) {
FML_LOG(ERROR)
<< "Could not execute sysctl() to determine process count limit: "
<< strerror(errno);
return false;
}
const char* warning =
......@@ -98,6 +110,39 @@ void EnsureDebuggedIOS(const flutter::Settings& vm_settings) {
{
FML_LOG(ERROR) << warning;
}
return true;
}
static bool EnableTracingIfNecessaryOnce(const Settings& vm_settings) {
if (IsLaunchedByFlutterCLI(vm_settings)) {
return true;
}
if (IsLaunchedByXcode()) {
return true;
}
return EnableTracingManually(vm_settings);
}
#endif // OS_IOS && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
static TracingResult sTracingResult = TracingResult::kNotAttempted;
bool EnableTracingIfNecessaryImpl(const Settings& vm_settings) {
static std::once_flag tracing_flag;
std::call_once(tracing_flag, [&vm_settings]() {
sTracingResult = EnableTracingIfNecessaryOnce(vm_settings)
? TracingResult::kEnabled
: TracingResult::kDisabled;
});
return sTracingResult != TracingResult::kDisabled;
}
TracingResult GetTracingResultImpl() {
return sTracingResult;
}
} // namespace flutter
#endif // TRACING_CHECKS_NECESSARY
// 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.
#ifndef FLUTTER_RUNTIME_PTRACE_CHECK_H_
#define FLUTTER_RUNTIME_PTRACE_CHECK_H_
#include "flutter/common/settings.h"
#include "flutter/fml/build_config.h"
namespace flutter {
#define TRACING_CHECKS_NECESSARY \
OS_IOS && !TARGET_OS_SIMULATOR && \
(FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
enum class TracingResult {
kNotAttempted,
kEnabled,
kNotNecessary = kEnabled,
kDisabled,
};
#if TRACING_CHECKS_NECESSARY
bool EnableTracingIfNecessaryImpl(const Settings& vm_settings);
TracingResult GetTracingResultImpl();
#endif // TRACING_CHECKS_NECESSARY
//------------------------------------------------------------------------------
/// @brief Enables tracing in the process so that JIT mode VMs may be
/// launched. Explicitly enabling tracing is not required on all
/// platforms. On platforms where it is not required, calling this
/// method will return true. If tracing is required but cannot be
/// enabled, it is the responsibility of the caller to display the
/// appropriate error message to the user as subsequent attempts to
/// launch the VM in JIT mode will cause process termination.
///
/// This method may be called multiple times and will return the
/// same result. There are no threading restrictions.
///
/// @param[in] vm_settings The settings used to launch the VM.
///
/// @return If tracing was enabled.
///
inline bool EnableTracingIfNecessary(const Settings& vm_settings) {
#if TRACING_CHECKS_NECESSARY
return EnableTracingIfNecessaryImpl(vm_settings);
#else // TRACING_CHECKS_NECESSARY
return true;
#endif // TRACING_CHECKS_NECESSARY
}
//------------------------------------------------------------------------------
/// @brief Returns if a tracing check has been performed and its result. To
/// enable tracing, the Settings object used to launch the VM is
/// required. Components may want to display messages based on the
/// result of a previous tracing check without actually having the
/// settings object. This accessor can be used instead.
///
/// @return The tracing result.
///
inline TracingResult GetTracingResult() {
#if TRACING_CHECKS_NECESSARY
return GetTracingResultImpl();
#else // TRACING_CHECKS_NECESSARY
return TracingResult::kNotNecessary;
#endif // TRACING_CHECKS_NECESSARY
}
} // namespace flutter
#endif // FLUTTER_RUNTIME_PTRACE_CHECK_H_
// 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.
#ifndef FLUTTER_RUNTIME_PTRACE_IOS_H_
#define FLUTTER_RUNTIME_PTRACE_IOS_H_
#include "flutter/common/settings.h"
#if OS_IOS && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
// Ensure that the current process is or was ptrace()-d at some point in its
// life. Can only be used within debug builds for iOS.
void EnsureDebuggedIOS(const flutter::Settings& vm_settings);
#endif
#endif // FLUTTER_RUNTIME_PTRACE_IOS_H_
......@@ -11,6 +11,7 @@
#include "flutter/fml/message_loop.h"
#include "flutter/fml/platform/darwin/platform_version.h"
#include "flutter/fml/trace_event.h"
#include "flutter/runtime/ptrace_check.h"
#include "flutter/shell/common/engine.h"
#include "flutter/shell/common/platform_view.h"
#include "flutter/shell/common/shell.h"
......@@ -114,6 +115,16 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
else
_dartProject.reset([project retain]);
if (!EnableTracingIfNecessary([_dartProject.get() settings])) {
NSLog(
@"Cannot create a FlutterEngine instance in debug mode without Flutter tooling or "
@"Xcode.\n\nTo launch in debug mode in iOS 14+, run flutter run from Flutter tools, run "
@"from an IDE with a Flutter IDE plugin or run the iOS project from Xcode.\nAlternatively "
@"profile and release mode apps can be launched from the home screen.");
[self release];
return nil;
}
_pluginPublications = [NSMutableDictionary new];
_registrars = [[NSMutableDictionary alloc] init];
_platformViewsController.reset(new flutter::FlutterPlatformViewsController());
......@@ -514,6 +525,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
_threadHost.ui_thread->GetTaskRunner(), // ui
_threadHost.io_thread->GetTaskRunner() // io
);
// Create the shell. This is a blocking operation.
_shell = flutter::Shell::Create(std::move(task_runners), // task runners
std::move(platformData), // window data
......
......@@ -40,7 +40,12 @@
}
- (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate opaque:(BOOL)opaque {
FML_DCHECK(delegate) << "Delegate must not be nil.";
if (delegate == nil) {
NSLog(@"FlutterView delegate was nil.");
[self release];
return nil;
}
self = [super initWithFrame:CGRectNull];
if (self) {
......
......@@ -13,6 +13,7 @@
#include "flutter/fml/message_loop.h"
#include "flutter/fml/platform/darwin/platform_version.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
#include "flutter/runtime/ptrace_check.h"
#include "flutter/shell/common/thread_host.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
......@@ -71,7 +72,7 @@ typedef enum UIAccessibilityContrast : NSInteger {
BOOL _initialized;
BOOL _viewOpaque;
BOOL _engineNeedsLaunch;
NSMutableSet<NSNumber*>* _ongoingTouches;
fml::scoped_nsobject<NSMutableSet<NSNumber*>> _ongoingTouches;
// This scroll view is a workaround to accomodate iOS 13 and higher. There isn't a way to get
// touches on the status bar to trigger scrolling to the top of a scroll view. We place a
// UIScrollView with height zero and a content offset so we can get those events. See also:
......@@ -102,7 +103,7 @@ typedef enum UIAccessibilityContrast : NSInteger {
_engineNeedsLaunch = NO;
_flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
_weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
_ongoingTouches = [[NSMutableSet alloc] init];
_ongoingTouches.reset([[NSMutableSet alloc] init]);
[self performCommonViewControllerInitialization];
[engine setViewController:self];
......@@ -145,7 +146,7 @@ typedef enum UIAccessibilityContrast : NSInteger {
- (void)awakeFromNib {
[super awakeFromNib];
if (!_engine.get()) {
if (!_engine) {
[self sharedSetupWithProject:nil initialRoute:nil];
}
}
......@@ -156,15 +157,22 @@ typedef enum UIAccessibilityContrast : NSInteger {
- (void)sharedSetupWithProject:(nullable FlutterDartProject*)project
initialRoute:(nullable NSString*)initialRoute {
auto engine = fml::scoped_nsobject<FlutterEngine>{[[FlutterEngine alloc]
initWithName:@"io.flutter"
project:project
allowHeadlessExecution:self.engineAllowHeadlessExecution]};
if (!engine) {
return;
}
_viewOpaque = YES;
_weakFactory = std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(self);
_engine.reset([[FlutterEngine alloc] initWithName:@"io.flutter"
project:project
allowHeadlessExecution:self.engineAllowHeadlessExecution]);
_engine = std::move(engine);
_flutterView.reset([[FlutterView alloc] initWithDelegate:_engine opaque:self.isViewOpaque]);
[_engine.get() createShell:nil libraryURI:nil initialRoute:initialRoute];
_engineNeedsLaunch = YES;
_ongoingTouches = [[NSMutableSet alloc] init];
_ongoingTouches.reset([[NSMutableSet alloc] init]);
[self loadDefaultSplashScreenView];
[self performCommonViewControllerInitialization];
}
......@@ -310,8 +318,38 @@ typedef enum UIAccessibilityContrast : NSInteger {
#pragma mark - Loading the view
static UIView* GetViewOrPlaceholder(UIView* existing_view) {
if (existing_view) {
return existing_view;
}
auto placeholder = [[[UIView alloc] init] autorelease];
placeholder.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
placeholder.backgroundColor = UIColor.whiteColor;
placeholder.autoresizesSubviews = YES;
// Only add the label when we know we have failed to enable tracing (and it was necessary).
// Otherwise, a spurious warning will be shown in cases where an engine cannot be initialized for
// other reasons.
if (flutter::GetTracingResult() == flutter::TracingResult::kDisabled) {
auto messageLabel = [[[UILabel alloc] init] autorelease];
messageLabel.numberOfLines = 0u;
messageLabel.textAlignment = NSTextAlignmentCenter;
messageLabel.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
messageLabel.text =
@"In iOS 14+, Flutter application in debug mode can only be launched from Flutter tooling, "
@"IDEs with Flutter plugins or from Xcode.\n\nAlternatively, build in profile or release "
@"modes to enable re-launching from the home screen.";
[placeholder addSubview:messageLabel];
}
return placeholder;
}
- (void)loadView {
self.view = _flutterView.get();
self.view = GetViewOrPlaceholder(_flutterView.get());
self.view.multipleTouchEnabled = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
......@@ -346,6 +384,9 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView*)scrollView {
if (!_engine) {
return NO;
}
CGPoint statusBarPoint = CGPointZero;
sendFakeTouchEvent(_engine.get(), statusBarPoint, flutter::PointerData::Change::kDown);
sendFakeTouchEvent(_engine.get(), statusBarPoint, flutter::PointerData::Change::kUp);
......@@ -414,6 +455,10 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
}
- (void)installFirstFrameCallback {
if (!_engine) {
return;
}
fml::WeakPtr<flutter::PlatformViewIOS> weakPlatformView = [_engine.get() platformView];
if (!weakPlatformView) {
return;
......@@ -516,6 +561,10 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
#pragma mark - Surface creation and teardown updates
- (void)surfaceUpdated:(BOOL)appeared {
if (!_engine) {
return;
}
// NotifyCreated/NotifyDestroyed are synchronous and require hops between the UI and raster
// thread.
if (appeared) {
......@@ -536,14 +585,12 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
- (void)viewDidLoad {
TRACE_EVENT0("flutter", "viewDidLoad");
if (_engineNeedsLaunch) {
if (_engine && _engineNeedsLaunch) {
[_engine.get() launchEngine:nil libraryURI:nil];
[_engine.get() setViewController:self];
_engineNeedsLaunch = NO;
}
FML_DCHECK([_engine.get() viewController] == self)
<< "FlutterViewController's view is loaded but is not attached to a FlutterEngine";
[_engine.get() attachView];
[super viewDidLoad];
......@@ -594,12 +641,12 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
}
- (void)flushOngoingTouches {
if (_ongoingTouches.count > 0) {
auto packet = std::make_unique<flutter::PointerDataPacket>(_ongoingTouches.count);
if (_engine && _ongoingTouches.get().count > 0) {
auto packet = std::make_unique<flutter::PointerDataPacket>(_ongoingTouches.get().count);
size_t pointer_index = 0;
// If the view controller is going away, we want to flush cancel all the ongoing
// touches to the framework so nothing gets orphaned.
for (NSNumber* device in _ongoingTouches) {
for (NSNumber* device in _ongoingTouches.get()) {
// Create fake PointerData to balance out each previously started one for the framework.
flutter::PointerData pointer_data;
pointer_data.Clear();
......@@ -633,7 +680,6 @@ static void sendFakeTouchEvent(FlutterEngine* engine,
object:self
userInfo:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_ongoingTouches release];
[super dealloc];
}
......@@ -719,6 +765,10 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
// touch is specified in the second argument.
- (void)dispatchTouches:(NSSet*)touches
pointerDataChangeOverride:(flutter::PointerData::Change*)overridden_change {
if (!_engine) {
return;
}
const CGFloat scale = [UIScreen mainScreen].scale;
auto packet = std::make_unique<flutter::PointerDataPacket>(touches.count);
......@@ -887,7 +937,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
// This must run after updateViewportMetrics so that the surface creation tasks are queued after
// the viewport metrics update tasks.
if (firstViewBoundsUpdate && applicationIsActive) {
if (firstViewBoundsUpdate && applicationIsActive && _engine) {
[self surfaceUpdated:YES];
flutter::Shell& shell = [_engine.get() shell];
......@@ -1040,6 +1090,9 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
#pragma mark - Accessibility
- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
if (!_engine) {
return;
}
auto platformView = [_engine.get() platformView];
int32_t flags = 0;
if (UIAccessibilityIsInvertColorsEnabled())
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册