From 6fcb3d394e23f2aa929616461ef041fc0de83640 Mon Sep 17 00:00:00 2001 From: asiva Date: Fri, 1 Aug 2025 11:11:18 -0700 Subject: [PATCH] Fix for https://github.com/dart-lang/sdk/issues/61206 Ensure Platform.executable is not resolved. TEST=new test case added Bug: 61206 Change-Id: I0522869f57d519168542b453dc2e827d9d5e6486 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/442942 Commit-Queue: Siva Annamalai Reviewed-by: Nicholas Shahan --- runtime/bin/dartdev.cc | 30 +++++-- runtime/bin/dartdev_options.cc | 6 +- runtime/bin/main_options.cc | 4 +- runtime/bin/main_options.h | 1 + runtime/bin/platform_macos.cc | 30 +++++-- .../io/platform_executable_test.dart | 81 +++++++++++++++++++ tests/standalone/standalone_kernel.status | 1 + 7 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 tests/standalone/io/platform_executable_test.dart diff --git a/runtime/bin/dartdev.cc b/runtime/bin/dartdev.cc index d6b3c22cb31..e270cd1d981 100644 --- a/runtime/bin/dartdev.cc +++ b/runtime/bin/dartdev.cc @@ -502,7 +502,7 @@ class DartDev { int idx = 0; char err_msg[256]; err_msg[0] = '\0'; - intptr_t num_args = argc + 2; + intptr_t num_args = argc + 3; char** exec_argv = new char*[num_args]; #if defined(DART_HOST_OS_WINDOWS) char* exec_name = StringUtilsWin::ArgumentEscape(dartvm_path.get()); @@ -515,11 +515,20 @@ class DartDev { Platform::ResolveExecutablePathInto(dart_path, kPathBufSize); idx += 1; #if defined(DART_HOST_OS_WINDOWS) - char* dart_name = Utils::SCreate("--executable_name=%s", dart_path); + char* dart_name = + Utils::SCreate("--resolved_executable_name=%s", dart_path); + exec_argv[idx] = StringUtilsWin::ArgumentEscape(dart_name); + free(dart_name); + idx += 1; + dart_name = + Utils::SCreate("--executable_name=%s", Platform::GetExecutableName()); exec_argv[idx] = StringUtilsWin::ArgumentEscape(dart_name); free(dart_name); #else - exec_argv[idx] = Utils::SCreate("--executable_name=%s", dart_path); + exec_argv[idx] = Utils::SCreate("--resolved_executable_name=%s", dart_path); + idx += 1; + exec_argv[idx] = + Utils::SCreate("--executable_name=%s", Platform::GetExecutableName()); #endif for (intptr_t i = 1; i < argc; ++i) { #if defined(DART_HOST_OS_WINDOWS) @@ -629,9 +638,9 @@ class DartDev { }; // Total count of arguments to be passed to the script being execed. if (mark_main_isolate_as_system_isolate) { - argc_ = argc + num_vm_options + 4; + argc_ = argc + num_vm_options + 5; } else { - argc_ = argc + num_vm_options + 3; + argc_ = argc + num_vm_options + 4; } // Array of arguments to be passed to the script being execed. @@ -657,11 +666,18 @@ class DartDev { char dart_path[kPathBufSize]; Platform::ResolveExecutablePathInto(dart_path, kPathBufSize); #if defined(DART_HOST_OS_WINDOWS) - char* dart_name = Utils::SCreate("--executable_name=%s", dart_path); + char* dart_name = + Utils::SCreate("--resolved_executable_name=%s", dart_path); + argv_[idx++] = StringUtilsWin::ArgumentEscape(dart_name); + free(dart_name); + dart_name = + Utils::SCreate("--executable_name=%s", Platform::GetExecutableName()); argv_[idx++] = StringUtilsWin::ArgumentEscape(dart_name); free(dart_name); #else - argv_[idx++] = Utils::SCreate("--executable_name=%s", dart_path); + argv_[idx++] = Utils::SCreate("--resolved_executable_name=%s", dart_path); + argv_[idx++] = + Utils::SCreate("--executable_name=%s", Platform::GetExecutableName()); #endif } if (mark_main_isolate_as_system_isolate) { diff --git a/runtime/bin/dartdev_options.cc b/runtime/bin/dartdev_options.cc index 1eab81fb1a4..edda1be64da 100644 --- a/runtime/bin/dartdev_options.cc +++ b/runtime/bin/dartdev_options.cc @@ -148,6 +148,9 @@ bool Options::ParseDartDevArguments(int argc, CommandLineOptions* dart_vm_options, CommandLineOptions* dart_options, bool* skip_dartdev) { + // Store the executable name. + Platform::SetExecutableName(argv[0]); + // First figure out if a dartdev command has been explicitly specified. *skip_dartdev = false; int tmp_i = 1; @@ -317,9 +320,6 @@ bool Options::ParseDartDevArguments(int argc, i++; } - // Store the executable name. - Platform::SetExecutableName(argv[0]); - // Verify consistency of arguments. if ((packages_file_ != nullptr) && (strlen(packages_file_) == 0)) { Syslog::PrintErr("Empty package file name specified.\n"); diff --git a/runtime/bin/main_options.cc b/runtime/bin/main_options.cc index 0340f58f86c..88e10357d90 100644 --- a/runtime/bin/main_options.cc +++ b/runtime/bin/main_options.cc @@ -226,10 +226,12 @@ bool Options::ParseArguments(int argc, // Store the executable name. if (Options::executable_name() != nullptr) { Platform::SetExecutableName(Options::executable_name()); - Platform::SetResolvedExecutableName(Options::executable_name()); } else { Platform::SetExecutableName(argv[0]); } + if (Options::resolved_executable_name() != nullptr) { + Platform::SetResolvedExecutableName(Options::resolved_executable_name()); + } } // Verify consistency of arguments. diff --git a/runtime/bin/main_options.h b/runtime/bin/main_options.h index a4e9740f933..fb901e7b826 100644 --- a/runtime/bin/main_options.h +++ b/runtime/bin/main_options.h @@ -29,6 +29,7 @@ namespace bin { V(namespace, namespc) \ V(write_service_info, vm_write_service_info_filename) \ V(executable_name, executable_name) \ + V(resolved_executable_name, resolved_executable_name) \ /* The purpose of these flags is documented in */ \ /* pkg/dartdev/lib/src/commands/compilation_server.dart. */ \ V(resident_server_info_file, resident_server_info_file_path) \ diff --git a/runtime/bin/platform_macos.cc b/runtime/bin/platform_macos.cc index 1ba2e4798ec..ce93140f1c3 100644 --- a/runtime/bin/platform_macos.cc +++ b/runtime/bin/platform_macos.cc @@ -255,18 +255,36 @@ const char* Platform::ResolveExecutablePath() { } intptr_t Platform::ResolveExecutablePathInto(char* result, size_t result_size) { - // Get the required length of the buffer. - uint32_t path_size = 0; - if (_NSGetExecutablePath(nullptr, &path_size) == 0) { + // A temporary buffer to hold the initial executable path. + char exe_path[PATH_MAX + 1]; + uint32_t exe_path_size = sizeof(exe_path); + + // Get the path of the executable. + if (_NSGetExecutablePath(exe_path, &exe_path_size) != 0) { + // The buffer was too small. Get the required size and handle the error. + // For this specific use case, we assume PATH_MAX is sufficient. return -1; } - if (path_size > result_size) { + + // Now, canonicalize the path to resolve symlinks. + // realpath is the standard POSIX function for this purpose. + char resolved_exe_path[PATH_MAX + 1]; + if (realpath(exe_path, resolved_exe_path) == nullptr) { return -1; } - if (_NSGetExecutablePath(result, &path_size) != 0) { + + // Check if the resolved path fits into the destination buffer. + size_t resolved_size = strlen(resolved_exe_path); + if (resolved_size >= result_size) { + // The buffer is too small. return -1; } - return path_size; + + // Safely copy the resolved path. + strncpy(result, resolved_exe_path, resolved_size); + result[resolved_size] = '\0'; + + return resolved_size; } void Platform::SetProcessName(const char* name) { diff --git a/tests/standalone/io/platform_executable_test.dart b/tests/standalone/io/platform_executable_test.dart new file mode 100644 index 00000000000..bc09b9bc888 --- /dev/null +++ b/tests/standalone/io/platform_executable_test.dart @@ -0,0 +1,81 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// This test was generated by Gemini. + +import 'dart:io'; +import 'package:test/test.dart'; + +// The simple script to be run in a child process. +// It prints both executable paths to standard output. +const String _testScriptContent = ''' +import 'dart:io'; + +void main() { + print(Platform.executable); + print(Platform.resolvedExecutable); +} +'''; + +void main() { + group('Platform executable properties', () { + test( + 'Platform.executable and Platform.resolvedExecutable should differ ' + 'when executed via a symbolic link', + () { + // 1. Get the absolute path to the current Dart executable. + final dartExecutablePath = Platform.resolvedExecutable; + + // 2. Create a temporary directory and the necessary files. + final tempDir = Directory.systemTemp.createTempSync( + 'dart_symlink_test_', + ); + final symlinkPath = + '${tempDir.path}${Platform.pathSeparator}dart_symlink'; + final childScriptPath = + '${tempDir.path}${Platform.pathSeparator}child_script.dart'; + + try { + // Create the symbolic link. + Link(symlinkPath).createSync(dartExecutablePath, recursive: true); + + // Write the child script to a file. + File(childScriptPath).writeAsStringSync(_testScriptContent); + + // 3. Execute the child script using the symlink. + final result = Process.runSync( + symlinkPath, // This is the key: use the symlink path + [childScriptPath], + runInShell: true, + ); + + // Verify the child process exited successfully. + expect( + result.exitCode, + 0, + reason: 'Child process failed: ${result.stderr}', + ); + + // 4. Parse the output. + final output = result.stdout.toString().trim().split('\n'); + final executablePath = output[0]; + final resolvedExecutablePath = output[1]; + + // 5. Assert the paths are different. + expect(executablePath, isNot(equals(resolvedExecutablePath))); + print('Executable: $executablePath'); + print('Resolved Executable: $resolvedExecutablePath'); + } finally { + // Clean up the temporary directory. + if (tempDir.existsSync()) { + tempDir.deleteSync(recursive: true); + } + } + }, + // This is the important part: use the 'skip' parameter. + // The test will be skipped unless the platform is Linux or MacOS. + skip: (!Platform.isLinux && !Platform.isMacOS) ? true : false, + ); + }); +} diff --git a/tests/standalone/standalone_kernel.status b/tests/standalone/standalone_kernel.status index 0a1813431c3..a8e888e3c72 100644 --- a/tests/standalone/standalone_kernel.status +++ b/tests/standalone/standalone_kernel.status @@ -49,6 +49,7 @@ io/http_response_deadline_test: Skip # Flaky. io/http_reuse_server_port_test: Skip # Flaky. io/http_server_close_response_after_error_test: Skip # Flaky. io/http_shutdown_test: Skip # Flaky. +io/platform_executable_test: Skip # The test assumes a JIT invocation. io/process_child_test: Skip # The test does not exercise the exec path on AOT io/raw_datagram_socket_test: Skip # Flaky. io/raw_secure_server_closing_test: Skip # Flaky