Fix a bug where windows would not currently follow symlinks in stat() calls.

Also clarify documentation.

TEST=Updated tests to cover stats() calls on symlinks.
Bug: https://github.com/dart-lang/sdk/issues/20389
Change-Id: I8555bacc2f83cad024ad8ef7c2f23aa97069ed2e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/218671
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
diff --git a/runtime/bin/file_win.cc b/runtime/bin/file_win.cc
index bb24d00..6cb8860 100644
--- a/runtime/bin/file_win.cc
+++ b/runtime/bin/file_win.cc
@@ -896,7 +896,7 @@
 
 void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
   const char* prefixed_name = PrefixLongFilePath(name);
-  File::Type type = GetType(namespc, prefixed_name, false);
+  File::Type type = GetType(namespc, prefixed_name, true);
   data[kType] = type;
   if (type != kDoesNotExist) {
     struct _stat64 st;
diff --git a/sdk/lib/io/file_system_entity.dart b/sdk/lib/io/file_system_entity.dart
index 4eedcf8..ca14f1f 100644
--- a/sdk/lib/io/file_system_entity.dart
+++ b/sdk/lib/io/file_system_entity.dart
@@ -93,6 +93,9 @@
 
   /// Calls the operating system's `stat()` function (or equivalent) on [path].
   ///
+  /// If [path] is a symbolic link then it is resolved and results for the
+  /// resulting file are returned.
+  ///
   /// Returns a [FileStat] object containing the data returned by `stat()`.
   /// If the call fails, returns a [FileStat] object with [FileStat.type] set to
   /// [FileSystemEntityType.notFound] and the other fields invalid.
@@ -123,6 +126,9 @@
   /// Asynchronously calls the operating system's `stat()` function (or
   /// equivalent) on [path].
   ///
+  /// If [path] is a symbolic link then it is resolved and results for the
+  /// resulting file are returned.
+  ///
   /// Returns a [Future] which completes with the same results as [statSync].
   static Future<FileStat> stat(String path) {
     final IOOverrides? overrides = IOOverrides.current;
@@ -356,6 +362,9 @@
   /// Returns a `Future<FileStat>` object containing the data returned by
   /// `stat()`.
   ///
+  /// If [path] is a symbolic link then it is resolved and results for the
+  /// resulting file are returned.
+  ///
   /// If the call fails, completes the future with a [FileStat] object
   /// with `.type` set to [FileSystemEntityType.notFound] and the other fields
   /// invalid.
@@ -367,6 +376,9 @@
   ///
   /// Returns a [FileStat] object containing the data returned by `stat()`.
   ///
+  /// If [path] is a symbolic link then it is resolved and results for the
+  /// resulting file are returned.
+  ///
   /// If the call fails, returns a [FileStat] object with `.type` set to
   /// [FileSystemEntityType.notFound] and the other fields invalid.
   FileStat statSync() => FileStat.statSync(path);
diff --git a/tests/standalone/io/file_stat_test.dart b/tests/standalone/io/file_stat_test.dart
index 86054da..8cc3e0e 100644
--- a/tests/standalone/io/file_stat_test.dart
+++ b/tests/standalone/io/file_stat_test.dart
@@ -19,11 +19,16 @@
   Expect.equals(FileSystemEntityType.notFound, fileStat.type);
   Expect.equals(FileSystemEntityType.notFound, fileStatDirect.type);
   file.writeAsStringSync("Dart IO library test of FileStat");
+  Link link = new Link(join(directory.path, "link"));
+  link.createSync(file.path);
   new Timer(const Duration(seconds: 2), () {
     file.readAsStringSync();
     directory.listSync();
     FileStat fileStat = FileStat.statSync(file.path);
     FileStat fileStatDirect = file.statSync();
+    FileStat linkStat = FileStat.statSync(link.path);
+    FileStat linkStatDirect = link.statSync();
+
     Expect.equals(FileSystemEntityType.file, fileStat.type);
     Expect.equals(32, fileStat.size);
     Expect.equals(FileSystemEntityType.file, fileStatDirect.type);
@@ -44,6 +49,13 @@
           directoryStat.changed.compareTo(directoryStat.accessed) < 0);
     }
     Expect.equals(7 << 6, directoryStat.mode & (7 << 6)); // Includes +urwx.
+
+    // Verify that statSync resolves the link.
+    Expect.equals(FileSystemEntityType.file, linkStat.type);
+    Expect.equals(32, linkStat.size);
+    Expect.equals(FileSystemEntityType.file, linkStatDirect.type);
+    Expect.equals(32, linkStatDirect.size);
+
     directory.deleteSync(recursive: true);
   });
 }
diff --git a/tests/standalone_2/io/file_stat_test.dart b/tests/standalone_2/io/file_stat_test.dart
index 9af5190..48b05b2 100644
--- a/tests/standalone_2/io/file_stat_test.dart
+++ b/tests/standalone_2/io/file_stat_test.dart
@@ -21,11 +21,16 @@
   Expect.equals(FileSystemEntityType.notFound, fileStat.type);
   Expect.equals(FileSystemEntityType.notFound, fileStatDirect.type);
   file.writeAsStringSync("Dart IO library test of FileStat");
+  Link link = new Link(join(directory.path, "link"));
+  link.createSync(file.path);
   new Timer(const Duration(seconds: 2), () {
     file.readAsStringSync();
     directory.listSync();
     FileStat fileStat = FileStat.statSync(file.path);
     FileStat fileStatDirect = file.statSync();
+    FileStat linkStat = FileStat.statSync(link.path);
+    FileStat linkStatDirect = link.statSync();
+
     Expect.equals(FileSystemEntityType.file, fileStat.type);
     Expect.equals(32, fileStat.size);
     Expect.equals(FileSystemEntityType.file, fileStatDirect.type);
@@ -46,6 +51,13 @@
           .isTrue(directoryStat.changed.compareTo(directoryStat.accessed) < 0);
     }
     Expect.equals(7 << 6, directoryStat.mode & (7 << 6)); // Includes +urwx.
+
+    // Verify that statSync resolves the link.
+    Expect.equals(FileSystemEntityType.file, linkStat.type);
+    Expect.equals(32, linkStat.size);
+    Expect.equals(FileSystemEntityType.file, linkStatDirect.type);
+    Expect.equals(32, linkStatDirect.size);
+
     directory.deleteSync(recursive: true);
   });
 }
@@ -53,8 +65,7 @@
 Future testStatAsync() {
   return Directory.systemTemp.createTemp('dart_file_stat').then((directory) {
     File file = new File(join(directory.path, "file"));
-    return FileStat
-        .stat(file.path)
+    return FileStat.stat(file.path)
         .then((fileStat) =>
             Expect.equals(FileSystemEntityType.notFound, fileStat.type))
         .then((_) => file.stat())