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 <asiva@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
This commit is contained in:
asiva
2025-08-01 11:11:18 -07:00
committed by Commit Queue
parent aa8f15fe48
commit 6fcb3d394e
7 changed files with 136 additions and 17 deletions
+23 -7
View File
@@ -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) {
+3 -3
View File
@@ -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");
+3 -1
View File
@@ -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.
+1
View File
@@ -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) \
+24 -6
View File
@@ -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) {
@@ -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,
);
});
}
@@ -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