Skip to content

Commit f0fb74b

Browse files
authored
Avoid crashing and display error if the process cannot be prepared for JIT mode Dart VM. (flutter#20980)
1 parent af90dd3 commit f0fb74b

File tree

10 files changed

+240
-61
lines changed

10 files changed

+240
-61
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,8 @@ FILE: ../../../flutter/runtime/embedder_resources.h
582582
FILE: ../../../flutter/runtime/fixtures/runtime_test.dart
583583
FILE: ../../../flutter/runtime/platform_data.cc
584584
FILE: ../../../flutter/runtime/platform_data.h
585-
FILE: ../../../flutter/runtime/ptrace_ios.cc
586-
FILE: ../../../flutter/runtime/ptrace_ios.h
585+
FILE: ../../../flutter/runtime/ptrace_check.cc
586+
FILE: ../../../flutter/runtime/ptrace_check.h
587587
FILE: ../../../flutter/runtime/runtime_controller.cc
588588
FILE: ../../../flutter/runtime/runtime_controller.h
589589
FILE: ../../../flutter/runtime/runtime_delegate.cc

lib/ui/plugins/callback_cache.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4+
// FLUTTER_NOLINT
45

56
#include <fstream>
67
#include <iterator>
@@ -129,7 +130,7 @@ void DartCallbackCache::LoadCacheFromDisk() {
129130
Document d;
130131
d.Parse(cache_contents.c_str());
131132
if (d.HasParseError() || !d.IsArray()) {
132-
FML_LOG(WARNING) << "Could not parse callback cache, aborting restore";
133+
FML_LOG(INFO) << "Could not parse callback cache, aborting restore";
133134
// TODO(bkonyi): log and bail (delete cache?)
134135
return;
135136
}

runtime/BUILD.gn

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ source_set("runtime") {
5555
"embedder_resources.h",
5656
"platform_data.cc",
5757
"platform_data.h",
58-
"ptrace_ios.cc",
59-
"ptrace_ios.h",
58+
"ptrace_check.h",
6059
"runtime_controller.cc",
6160
"runtime_controller.h",
6261
"runtime_delegate.cc",
@@ -67,6 +66,11 @@ source_set("runtime") {
6766
"skia_concurrent_executor.h",
6867
]
6968

69+
if (is_ios && flutter_runtime_mode == "debug") {
70+
# These contain references to private APIs and this TU must only be compiled in debug runtime modes.
71+
sources += [ "ptrace_check.cc" ]
72+
}
73+
7074
public_deps = [ "//third_party/rapidjson" ]
7175

7276
public_configs = [ "//flutter:config" ]

runtime/dart_vm.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
#include "flutter/lib/ui/dart_ui.h"
2525
#include "flutter/runtime/dart_isolate.h"
2626
#include "flutter/runtime/dart_service_isolate.h"
27-
#include "flutter/runtime/ptrace_ios.h"
27+
#include "flutter/runtime/ptrace_check.h"
2828
#include "third_party/dart/runtime/include/bin/dart_io_api.h"
2929
#include "third_party/skia/include/core/SkExecutor.h"
3030
#include "third_party/tonic/converter/dart_converter.h"
@@ -330,7 +330,12 @@ DartVM::DartVM(std::shared_ptr<const DartVMData> vm_data,
330330
PushBackAll(&args, kDartWriteProtectCodeArgs,
331331
fml::size(kDartWriteProtectCodeArgs));
332332
#else
333-
EnsureDebuggedIOS(settings_);
333+
const bool tracing_result = EnableTracingIfNecessary(settings_);
334+
// This check should only trip if the embedding made no attempts to enable
335+
// tracing. At this point, it is too late display user visible messages. Just
336+
// log and die.
337+
FML_CHECK(tracing_result)
338+
<< "Tracing not enabled before attempting to run JIT mode VM.";
334339
#if TARGET_CPU_ARM
335340
// Tell Dart in JIT mode to not use integer division on armv7
336341
// Ideally, this would be detected at runtime by Dart.

runtime/ptrace_ios.cc renamed to runtime/ptrace_check.cc

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,49 @@
1919
// - go/decommissioning-dbc
2020
// - go/decommissioning-dbc-engine
2121
// - go/decommissioning-dbc-tools
22-
#include "flutter/common/settings.h"
23-
#include "flutter/fml/build_config.h" // For OS_IOS.
2422

25-
#if OS_IOS && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
23+
#include "flutter/runtime/ptrace_check.h"
24+
25+
#if TRACING_CHECKS_NECESSARY
2626

27-
// These headers should only be needed in debug mode.
2827
#include <sys/sysctl.h>
2928
#include <sys/types.h>
3029

30+
#include <mutex>
31+
32+
#include "flutter/fml/build_config.h"
33+
34+
// Being extra careful and adding additional landmines that will prevent
35+
// compilation of this TU in an incorrect runtime mode.
36+
static_assert(OS_IOS, "This translation unit is iOS specific.");
37+
static_assert(FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG,
38+
"This translation unit must only be compiled in the debug "
39+
"runtime mode as it "
40+
"contains private API usage.");
41+
3142
#define PT_TRACE_ME 0
3243
#define PT_SIGEXC 12
3344
extern "C" int ptrace(int request, pid_t pid, caddr_t addr, int data);
3445

35-
static bool DebuggedIOS(const flutter::Settings& vm_settings) {
46+
namespace flutter {
47+
48+
static bool IsLaunchedByFlutterCLI(const Settings& vm_settings) {
3649
// Only the Flutter CLI passes "--enable-checked-mode". Therefore, if the flag
3750
// is present, we have been launched by "ios-deploy" via "debugserver".
3851
//
3952
// We choose this flag because it is always passed to launch debug builds.
40-
if (vm_settings.enable_checked_mode) {
41-
return true;
42-
}
53+
return vm_settings.enable_checked_mode;
54+
}
4355

56+
static bool IsLaunchedByXcode() {
4457
// Use "sysctl()" to check if we're currently being debugged (e.g. by Xcode).
4558
// We could also check "getppid() != 1" (launchd), but this is more direct.
4659
const pid_t self = getpid();
4760
int mib[5] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, self, 0};
4861

4962
auto proc = std::make_unique<struct kinfo_proc>();
5063
size_t proc_size = sizeof(struct kinfo_proc);
51-
if (sysctl(mib, 4, proc.get(), &proc_size, nullptr, 0) < 0) {
64+
if (::sysctl(mib, 4, proc.get(), &proc_size, nullptr, 0) < 0) {
5265
FML_LOG(ERROR) << "Could not execute sysctl() to get current process info: "
5366
<< strerror(errno);
5467
return false;
@@ -57,18 +70,16 @@ static bool DebuggedIOS(const flutter::Settings& vm_settings) {
5770
return proc->kp_proc.p_flag & P_TRACED;
5871
}
5972

60-
void EnsureDebuggedIOS(const flutter::Settings& vm_settings) {
61-
if (DebuggedIOS(vm_settings)) {
62-
return;
63-
}
64-
65-
if (ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1) {
73+
static bool EnableTracingManually(const Settings& vm_settings) {
74+
if (::ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1) {
6675
FML_LOG(ERROR) << "Could not call ptrace(PT_TRACE_ME): " << strerror(errno);
6776
// No use trying PT_SIGEXC -- it's only needed if PT_TRACE_ME succeeds.
68-
return;
77+
return false;
6978
}
70-
if (ptrace(PT_SIGEXC, 0, nullptr, 0) == -1) {
79+
80+
if (::ptrace(PT_SIGEXC, 0, nullptr, 0) == -1) {
7181
FML_LOG(ERROR) << "Could not call ptrace(PT_SIGEXC): " << strerror(errno);
82+
return false;
7283
}
7384

7485
// The previous operation causes this process to not be reaped after it
@@ -78,11 +89,12 @@ void EnsureDebuggedIOS(const flutter::Settings& vm_settings) {
7889
size_t maxproc = 0;
7990
size_t maxproc_size = sizeof(size_t);
8091
const int sysctl_result =
81-
sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, nullptr, 0);
92+
::sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, nullptr, 0);
8293
if (sysctl_result < 0) {
8394
FML_LOG(ERROR)
8495
<< "Could not execute sysctl() to determine process count limit: "
8596
<< strerror(errno);
97+
return false;
8698
}
8799

88100
const char* warning =
@@ -98,6 +110,39 @@ void EnsureDebuggedIOS(const flutter::Settings& vm_settings) {
98110
{
99111
FML_LOG(ERROR) << warning;
100112
}
113+
114+
return true;
115+
}
116+
117+
static bool EnableTracingIfNecessaryOnce(const Settings& vm_settings) {
118+
if (IsLaunchedByFlutterCLI(vm_settings)) {
119+
return true;
120+
}
121+
122+
if (IsLaunchedByXcode()) {
123+
return true;
124+
}
125+
126+
return EnableTracingManually(vm_settings);
101127
}
102128

103-
#endif // OS_IOS && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
129+
static TracingResult sTracingResult = TracingResult::kNotAttempted;
130+
131+
bool EnableTracingIfNecessaryImpl(const Settings& vm_settings) {
132+
static std::once_flag tracing_flag;
133+
134+
std::call_once(tracing_flag, [&vm_settings]() {
135+
sTracingResult = EnableTracingIfNecessaryOnce(vm_settings)
136+
? TracingResult::kEnabled
137+
: TracingResult::kDisabled;
138+
});
139+
return sTracingResult != TracingResult::kDisabled;
140+
}
141+
142+
TracingResult GetTracingResultImpl() {
143+
return sTracingResult;
144+
}
145+
146+
} // namespace flutter
147+
148+
#endif // TRACING_CHECKS_NECESSARY

runtime/ptrace_check.h

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_RUNTIME_PTRACE_CHECK_H_
6+
#define FLUTTER_RUNTIME_PTRACE_CHECK_H_
7+
8+
#include "flutter/common/settings.h"
9+
#include "flutter/fml/build_config.h"
10+
11+
namespace flutter {
12+
13+
#define TRACING_CHECKS_NECESSARY \
14+
OS_IOS && !TARGET_OS_SIMULATOR && \
15+
(FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
16+
17+
enum class TracingResult {
18+
kNotAttempted,
19+
kEnabled,
20+
kNotNecessary = kEnabled,
21+
kDisabled,
22+
};
23+
24+
#if TRACING_CHECKS_NECESSARY
25+
bool EnableTracingIfNecessaryImpl(const Settings& vm_settings);
26+
TracingResult GetTracingResultImpl();
27+
#endif // TRACING_CHECKS_NECESSARY
28+
29+
//------------------------------------------------------------------------------
30+
/// @brief Enables tracing in the process so that JIT mode VMs may be
31+
/// launched. Explicitly enabling tracing is not required on all
32+
/// platforms. On platforms where it is not required, calling this
33+
/// method will return true. If tracing is required but cannot be
34+
/// enabled, it is the responsibility of the caller to display the
35+
/// appropriate error message to the user as subsequent attempts to
36+
/// launch the VM in JIT mode will cause process termination.
37+
///
38+
/// This method may be called multiple times and will return the
39+
/// same result. There are no threading restrictions.
40+
///
41+
/// @param[in] vm_settings The settings used to launch the VM.
42+
///
43+
/// @return If tracing was enabled.
44+
///
45+
inline bool EnableTracingIfNecessary(const Settings& vm_settings) {
46+
#if TRACING_CHECKS_NECESSARY
47+
return EnableTracingIfNecessaryImpl(vm_settings);
48+
#else // TRACING_CHECKS_NECESSARY
49+
return true;
50+
#endif // TRACING_CHECKS_NECESSARY
51+
}
52+
53+
//------------------------------------------------------------------------------
54+
/// @brief Returns if a tracing check has been performed and its result. To
55+
/// enable tracing, the Settings object used to launch the VM is
56+
/// required. Components may want to display messages based on the
57+
/// result of a previous tracing check without actually having the
58+
/// settings object. This accessor can be used instead.
59+
///
60+
/// @return The tracing result.
61+
///
62+
inline TracingResult GetTracingResult() {
63+
#if TRACING_CHECKS_NECESSARY
64+
return GetTracingResultImpl();
65+
#else // TRACING_CHECKS_NECESSARY
66+
return TracingResult::kNotNecessary;
67+
#endif // TRACING_CHECKS_NECESSARY
68+
}
69+
70+
} // namespace flutter
71+
72+
#endif // FLUTTER_RUNTIME_PTRACE_CHECK_H_

runtime/ptrace_ios.h

Lines changed: 0 additions & 18 deletions
This file was deleted.

shell/platform/darwin/ios/framework/Source/FlutterEngine.mm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "flutter/fml/message_loop.h"
1212
#include "flutter/fml/platform/darwin/platform_version.h"
1313
#include "flutter/fml/trace_event.h"
14+
#include "flutter/runtime/ptrace_check.h"
1415
#include "flutter/shell/common/engine.h"
1516
#include "flutter/shell/common/platform_view.h"
1617
#include "flutter/shell/common/shell.h"
@@ -114,6 +115,16 @@ - (instancetype)initWithName:(NSString*)labelPrefix
114115
else
115116
_dartProject.reset([project retain]);
116117

118+
if (!EnableTracingIfNecessary([_dartProject.get() settings])) {
119+
NSLog(
120+
@"Cannot create a FlutterEngine instance in debug mode without Flutter tooling or "
121+
@"Xcode.\n\nTo launch in debug mode in iOS 14+, run flutter run from Flutter tools, run "
122+
@"from an IDE with a Flutter IDE plugin or run the iOS project from Xcode.\nAlternatively "
123+
@"profile and release mode apps can be launched from the home screen.");
124+
[self release];
125+
return nil;
126+
}
127+
117128
_pluginPublications = [NSMutableDictionary new];
118129
_registrars = [[NSMutableDictionary alloc] init];
119130
_platformViewsController.reset(new flutter::FlutterPlatformViewsController());
@@ -514,6 +525,7 @@ - (BOOL)createShell:(NSString*)entrypoint
514525
_threadHost.ui_thread->GetTaskRunner(), // ui
515526
_threadHost.io_thread->GetTaskRunner() // io
516527
);
528+
517529
// Create the shell. This is a blocking operation.
518530
_shell = flutter::Shell::Create(std::move(task_runners), // task runners
519531
std::move(platformData), // window data

shell/platform/darwin/ios/framework/Source/FlutterView.mm

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ - (instancetype)initWithCoder:(NSCoder*)aDecoder {
4040
}
4141

4242
- (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate opaque:(BOOL)opaque {
43-
FML_DCHECK(delegate) << "Delegate must not be nil.";
43+
if (delegate == nil) {
44+
NSLog(@"FlutterView delegate was nil.");
45+
[self release];
46+
return nil;
47+
}
48+
4449
self = [super initWithFrame:CGRectNull];
4550

4651
if (self) {

0 commit comments

Comments
 (0)