Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,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
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/plugins/callback_cache.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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>
Expand Down Expand Up @@ -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;
}
Expand Down
8 changes: 6 additions & 2 deletions runtime/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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" ]
Expand Down
9 changes: 7 additions & 2 deletions runtime/dart_vm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -329,7 +329,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.
Expand Down
83 changes: 64 additions & 19 deletions runtime/ptrace_ios.cc → runtime/ptrace_check.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,49 @@
// - 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();
int mib[5] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, self, 0};

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;
Expand All @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some code comments for readers (e.g. these 2 ifs are expected to fail on iOS 14, the rest should let people jit on iOS 13 etc)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can expect that. It's a private API and all implementation detail that can change at anytime without warning. I'd rather just be resilient to errors.

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
Expand All @@ -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 =
Expand All @@ -98,6 +110,39 @@ void EnsureDebuggedIOS(const flutter::Settings& vm_settings) {
{
FML_LOG(ERROR) << warning;
}

return true;
}

static bool EnableTracingIfNecessaryOnce(const Settings& vm_settings) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the name have once in it? Nothing here enforces the once-ness here it seems.

Copy link
Member

@xster xster Sep 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, seems like the name is reflects the expectation rather than the implementation... Seems a bit strange but I don't have a strong opinion. If you expect something, you should probably assert it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the usual C convention of of calling a method that is supposed to be called when a mutex is held with the Locked suffix.

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
72 changes: 72 additions & 0 deletions runtime/ptrace_check.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What part would you like clarified?

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_
18 changes: 0 additions & 18 deletions runtime/ptrace_ios.h

This file was deleted.

12 changes: 12 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -114,6 +115,16 @@ - (instancetype)initWithName:(NSString*)labelPrefix
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];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we assert here or do something more eye-catching than a console message?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we assert, we will take down the process. We don't want to do that. Instead, we want to display an error message in existing applications.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@To launch in debug mode, run flutter run from Flutter tools, run from an IDE with a Flutter IDE plugin or run the iOS project from Xcode

It's implicit but we should describe the solution explicitly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack.

return nil;
}

_pluginPublications = [NSMutableDictionary new];
_registrars = [[NSMutableDictionary alloc] init];
_platformViewsController.reset(new flutter::FlutterPlatformViewsController());
Expand Down Expand Up @@ -514,6 +525,7 @@ - (BOOL)createShell:(NSString*)entrypoint
_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
Expand Down
7 changes: 6 additions & 1 deletion shell/platform/darwin/ios/framework/Source/FlutterView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ - (instancetype)initWithCoder:(NSCoder*)aDecoder {
}

- (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) {
Expand Down
Loading