Skip to content

Commit 6fcb3d3

Browse files
a-sivaCommit Queue
authored andcommitted
Fix for #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 <[email protected]> Reviewed-by: Nicholas Shahan <[email protected]>
1 parent aa8f15f commit 6fcb3d3

File tree

7 files changed

+136
-17
lines changed

7 files changed

+136
-17
lines changed

runtime/bin/dartdev.cc

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ class DartDev {
502502
int idx = 0;
503503
char err_msg[256];
504504
err_msg[0] = '\0';
505-
intptr_t num_args = argc + 2;
505+
intptr_t num_args = argc + 3;
506506
char** exec_argv = new char*[num_args];
507507
#if defined(DART_HOST_OS_WINDOWS)
508508
char* exec_name = StringUtilsWin::ArgumentEscape(dartvm_path.get());
@@ -515,11 +515,20 @@ class DartDev {
515515
Platform::ResolveExecutablePathInto(dart_path, kPathBufSize);
516516
idx += 1;
517517
#if defined(DART_HOST_OS_WINDOWS)
518-
char* dart_name = Utils::SCreate("--executable_name=%s", dart_path);
518+
char* dart_name =
519+
Utils::SCreate("--resolved_executable_name=%s", dart_path);
520+
exec_argv[idx] = StringUtilsWin::ArgumentEscape(dart_name);
521+
free(dart_name);
522+
idx += 1;
523+
dart_name =
524+
Utils::SCreate("--executable_name=%s", Platform::GetExecutableName());
519525
exec_argv[idx] = StringUtilsWin::ArgumentEscape(dart_name);
520526
free(dart_name);
521527
#else
522-
exec_argv[idx] = Utils::SCreate("--executable_name=%s", dart_path);
528+
exec_argv[idx] = Utils::SCreate("--resolved_executable_name=%s", dart_path);
529+
idx += 1;
530+
exec_argv[idx] =
531+
Utils::SCreate("--executable_name=%s", Platform::GetExecutableName());
523532
#endif
524533
for (intptr_t i = 1; i < argc; ++i) {
525534
#if defined(DART_HOST_OS_WINDOWS)
@@ -629,9 +638,9 @@ class DartDev {
629638
};
630639
// Total count of arguments to be passed to the script being execed.
631640
if (mark_main_isolate_as_system_isolate) {
632-
argc_ = argc + num_vm_options + 4;
641+
argc_ = argc + num_vm_options + 5;
633642
} else {
634-
argc_ = argc + num_vm_options + 3;
643+
argc_ = argc + num_vm_options + 4;
635644
}
636645

637646
// Array of arguments to be passed to the script being execed.
@@ -657,11 +666,18 @@ class DartDev {
657666
char dart_path[kPathBufSize];
658667
Platform::ResolveExecutablePathInto(dart_path, kPathBufSize);
659668
#if defined(DART_HOST_OS_WINDOWS)
660-
char* dart_name = Utils::SCreate("--executable_name=%s", dart_path);
669+
char* dart_name =
670+
Utils::SCreate("--resolved_executable_name=%s", dart_path);
671+
argv_[idx++] = StringUtilsWin::ArgumentEscape(dart_name);
672+
free(dart_name);
673+
dart_name =
674+
Utils::SCreate("--executable_name=%s", Platform::GetExecutableName());
661675
argv_[idx++] = StringUtilsWin::ArgumentEscape(dart_name);
662676
free(dart_name);
663677
#else
664-
argv_[idx++] = Utils::SCreate("--executable_name=%s", dart_path);
678+
argv_[idx++] = Utils::SCreate("--resolved_executable_name=%s", dart_path);
679+
argv_[idx++] =
680+
Utils::SCreate("--executable_name=%s", Platform::GetExecutableName());
665681
#endif
666682
}
667683
if (mark_main_isolate_as_system_isolate) {

runtime/bin/dartdev_options.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ bool Options::ParseDartDevArguments(int argc,
148148
CommandLineOptions* dart_vm_options,
149149
CommandLineOptions* dart_options,
150150
bool* skip_dartdev) {
151+
// Store the executable name.
152+
Platform::SetExecutableName(argv[0]);
153+
151154
// First figure out if a dartdev command has been explicitly specified.
152155
*skip_dartdev = false;
153156
int tmp_i = 1;
@@ -317,9 +320,6 @@ bool Options::ParseDartDevArguments(int argc,
317320
i++;
318321
}
319322

320-
// Store the executable name.
321-
Platform::SetExecutableName(argv[0]);
322-
323323
// Verify consistency of arguments.
324324
if ((packages_file_ != nullptr) && (strlen(packages_file_) == 0)) {
325325
Syslog::PrintErr("Empty package file name specified.\n");

runtime/bin/main_options.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,12 @@ bool Options::ParseArguments(int argc,
226226
// Store the executable name.
227227
if (Options::executable_name() != nullptr) {
228228
Platform::SetExecutableName(Options::executable_name());
229-
Platform::SetResolvedExecutableName(Options::executable_name());
230229
} else {
231230
Platform::SetExecutableName(argv[0]);
232231
}
232+
if (Options::resolved_executable_name() != nullptr) {
233+
Platform::SetResolvedExecutableName(Options::resolved_executable_name());
234+
}
233235
}
234236

235237
// Verify consistency of arguments.

runtime/bin/main_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace bin {
2929
V(namespace, namespc) \
3030
V(write_service_info, vm_write_service_info_filename) \
3131
V(executable_name, executable_name) \
32+
V(resolved_executable_name, resolved_executable_name) \
3233
/* The purpose of these flags is documented in */ \
3334
/* pkg/dartdev/lib/src/commands/compilation_server.dart. */ \
3435
V(resident_server_info_file, resident_server_info_file_path) \

runtime/bin/platform_macos.cc

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,18 +255,36 @@ const char* Platform::ResolveExecutablePath() {
255255
}
256256

257257
intptr_t Platform::ResolveExecutablePathInto(char* result, size_t result_size) {
258-
// Get the required length of the buffer.
259-
uint32_t path_size = 0;
260-
if (_NSGetExecutablePath(nullptr, &path_size) == 0) {
258+
// A temporary buffer to hold the initial executable path.
259+
char exe_path[PATH_MAX + 1];
260+
uint32_t exe_path_size = sizeof(exe_path);
261+
262+
// Get the path of the executable.
263+
if (_NSGetExecutablePath(exe_path, &exe_path_size) != 0) {
264+
// The buffer was too small. Get the required size and handle the error.
265+
// For this specific use case, we assume PATH_MAX is sufficient.
261266
return -1;
262267
}
263-
if (path_size > result_size) {
268+
269+
// Now, canonicalize the path to resolve symlinks.
270+
// realpath is the standard POSIX function for this purpose.
271+
char resolved_exe_path[PATH_MAX + 1];
272+
if (realpath(exe_path, resolved_exe_path) == nullptr) {
264273
return -1;
265274
}
266-
if (_NSGetExecutablePath(result, &path_size) != 0) {
275+
276+
// Check if the resolved path fits into the destination buffer.
277+
size_t resolved_size = strlen(resolved_exe_path);
278+
if (resolved_size >= result_size) {
279+
// The buffer is too small.
267280
return -1;
268281
}
269-
return path_size;
282+
283+
// Safely copy the resolved path.
284+
strncpy(result, resolved_exe_path, resolved_size);
285+
result[resolved_size] = '\0';
286+
287+
return resolved_size;
270288
}
271289

272290
void Platform::SetProcessName(const char* name) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This test was generated by Gemini.
6+
7+
import 'dart:io';
8+
import 'package:test/test.dart';
9+
10+
// The simple script to be run in a child process.
11+
// It prints both executable paths to standard output.
12+
const String _testScriptContent = '''
13+
import 'dart:io';
14+
15+
void main() {
16+
print(Platform.executable);
17+
print(Platform.resolvedExecutable);
18+
}
19+
''';
20+
21+
void main() {
22+
group('Platform executable properties', () {
23+
test(
24+
'Platform.executable and Platform.resolvedExecutable should differ '
25+
'when executed via a symbolic link',
26+
() {
27+
// 1. Get the absolute path to the current Dart executable.
28+
final dartExecutablePath = Platform.resolvedExecutable;
29+
30+
// 2. Create a temporary directory and the necessary files.
31+
final tempDir = Directory.systemTemp.createTempSync(
32+
'dart_symlink_test_',
33+
);
34+
final symlinkPath =
35+
'${tempDir.path}${Platform.pathSeparator}dart_symlink';
36+
final childScriptPath =
37+
'${tempDir.path}${Platform.pathSeparator}child_script.dart';
38+
39+
try {
40+
// Create the symbolic link.
41+
Link(symlinkPath).createSync(dartExecutablePath, recursive: true);
42+
43+
// Write the child script to a file.
44+
File(childScriptPath).writeAsStringSync(_testScriptContent);
45+
46+
// 3. Execute the child script using the symlink.
47+
final result = Process.runSync(
48+
symlinkPath, // This is the key: use the symlink path
49+
[childScriptPath],
50+
runInShell: true,
51+
);
52+
53+
// Verify the child process exited successfully.
54+
expect(
55+
result.exitCode,
56+
0,
57+
reason: 'Child process failed: ${result.stderr}',
58+
);
59+
60+
// 4. Parse the output.
61+
final output = result.stdout.toString().trim().split('\n');
62+
final executablePath = output[0];
63+
final resolvedExecutablePath = output[1];
64+
65+
// 5. Assert the paths are different.
66+
expect(executablePath, isNot(equals(resolvedExecutablePath)));
67+
print('Executable: $executablePath');
68+
print('Resolved Executable: $resolvedExecutablePath');
69+
} finally {
70+
// Clean up the temporary directory.
71+
if (tempDir.existsSync()) {
72+
tempDir.deleteSync(recursive: true);
73+
}
74+
}
75+
},
76+
// This is the important part: use the 'skip' parameter.
77+
// The test will be skipped unless the platform is Linux or MacOS.
78+
skip: (!Platform.isLinux && !Platform.isMacOS) ? true : false,
79+
);
80+
});
81+
}

tests/standalone/standalone_kernel.status

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ io/http_response_deadline_test: Skip # Flaky.
4949
io/http_reuse_server_port_test: Skip # Flaky.
5050
io/http_server_close_response_after_error_test: Skip # Flaky.
5151
io/http_shutdown_test: Skip # Flaky.
52+
io/platform_executable_test: Skip # The test assumes a JIT invocation.
5253
io/process_child_test: Skip # The test does not exercise the exec path on AOT
5354
io/raw_datagram_socket_test: Skip # Flaky.
5455
io/raw_secure_server_closing_test: Skip # Flaky

0 commit comments

Comments
 (0)