[native assets] Propagate OS error on invocation via `PATHEXT`

Native assets resolution will now fail with an error message similar
to:

```
Invalid argument(s): Couldn't resolve native function '<...>' in
'<...>' : Failed to canonicalize the script uri
'<path without extension>'. OS error: 'The system cannot find the file
specified.
```

Supporting PATHEXT will be done in a follow up CL.

TEST=pkg/dartdev/test/native_assets/build_test.dart

Bug: https://github.com/dart-lang/sdk/issues/60946
Change-Id: I7db07b6c310ca8badd509c1d5d0595f671f1062d
Cq-Include-Trybots: luci.dart.try:pkg-win-release-arm64-try,pkg-win-release-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/435162
Reviewed-by: Michael Goderbauer <goderbauer@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
diff --git a/pkg/dartdev/test/native_assets/build_test.dart b/pkg/dartdev/test/native_assets/build_test.dart
index bef7771..bd59b50 100644
--- a/pkg/dartdev/test/native_assets/build_test.dart
+++ b/pkg/dartdev/test/native_assets/build_test.dart
@@ -66,6 +66,31 @@
           final link = Link.fromUri(
               tempUri.resolve(OS.current.executableFileName('my_link')));
           await link.create(absoluteExeUri.toFilePath());
+          if (OS.current == OS.windows) {
+            for (final exeUri in [
+              removeDotExe(link.uri),
+              removeDotExe(absoluteExeUri),
+              removeDotExe(relativeExeUri),
+            ]) {
+              final result = await runProcess(
+                executable: exeUri,
+                arguments: [],
+                workingDirectory: dartAppUri,
+                logger: logger,
+              );
+              // TODO(https://dartbug.com/60946): Support PATHEXT.
+              expect(result.exitCode, isNot(0));
+              expect(
+                result.stderr,
+                stringContainsInOrder(
+                  [
+                    'Failed to canonicalize the script uri',
+                    'The system cannot find the file specified.',
+                  ],
+                ),
+              );
+            }
+          }
           for (final exeUri in [
             absoluteExeUri,
             relativeExeUri,
@@ -354,3 +379,12 @@
     }
   }
 }
+
+Uri removeDotExe(Uri withExe) {
+  final exeName = withExe.pathSegments.lastWhere((e) => e.isNotEmpty);
+  if (!exeName.endsWith('.exe')) {
+    throw StateError('Expected executable to end in .exe, got $exeName');
+  }
+  final fileName = exeName.replaceAll('.exe', '');
+  return withExe.resolve(fileName);
+}
diff --git a/runtime/bin/native_assets_api_impl.cc b/runtime/bin/native_assets_api_impl.cc
index 3834c68..1595d59 100644
--- a/runtime/bin/native_assets_api_impl.cc
+++ b/runtime/bin/native_assets_api_impl.cc
@@ -4,6 +4,7 @@
 
 #include "include/bin/native_assets_api.h"
 
+#include "bin/utils.h"
 #include "platform/globals.h"
 #include "platform/utils.h"
 
@@ -54,7 +55,7 @@
 
 // Get a file uri with only forward slashes from the script path.
 // Returned string must be freed by caller.
-CStringUniquePtr CleanScriptUri(const char* script_uri) {
+CStringUniquePtr CleanScriptUri(const char* script_uri, char** error) {
   CStringUniquePtr script_path;
 
   if (strlen(script_uri) > file_schema_length &&
@@ -68,8 +69,15 @@
   }
 
   // Resolve symlinks.
-  char canon_path[PATH_MAX];
-  File::GetCanonicalPath(nullptr, script_path.get(), canon_path, PATH_MAX);
+  const char* canon_path = File::GetCanonicalPath(nullptr, script_path.get());
+  if (canon_path == nullptr) {
+    OSError os_error;
+    SET_ERROR_MSG(
+        error,
+        "Failed to canonicalize the script uri '%s'. OS error: '%s' (%i).",
+        script_path.get(), os_error.message(), os_error.code());
+    return CStringUniquePtr();
+  }
 
   // Convert path to Uri. Inspired by sdk/lib/core/uri.dart _makeFileUri.
   // Only has a single case, the path is always absolute and always a file.
@@ -141,7 +149,10 @@
                                    const char* script_uri,
                                    char** error) {
   void* handle = nullptr;
-  CStringUniquePtr platform_script_cstr = CleanScriptUri(script_uri);
+  CStringUniquePtr platform_script_cstr = CleanScriptUri(script_uri, error);
+  if (*error != nullptr) {
+    return nullptr;
+  }
   const intptr_t len = strlen(path);
   char* path_copy = reinterpret_cast<char*>(malloc(len + 1));
   snprintf(path_copy, len + 1, "%s", path);