diff --git a/runtime/bin/file_macos.cc b/runtime/bin/file_macos.cc index 334252e602e..d763adddea1 100644 --- a/runtime/bin/file_macos.cc +++ b/runtime/bin/file_macos.cc @@ -161,7 +161,8 @@ int64_t File::Read(void* buffer, int64_t num_bytes) { } int64_t File::Write(const void* buffer, int64_t num_bytes) { - ASSERT(handle_->fd() >= 0); + // Invalid argument error will pop if num_bytes exceeds the limit. + ASSERT(handle_->fd() >= 0 && num_bytes <= kMaxInt32); return TEMP_FAILURE_RETRY(write(handle_->fd(), buffer, num_bytes)); } diff --git a/runtime/bin/file_support.cc b/runtime/bin/file_support.cc index dd078392dcc..c67e3e379b5 100644 --- a/runtime/bin/file_support.cc +++ b/runtime/bin/file_support.cc @@ -56,7 +56,13 @@ bool File::WriteFully(const void* buffer, int64_t num_bytes) { int64_t remaining = num_bytes; const char* current_buffer = reinterpret_cast(buffer); while (remaining > 0) { - int64_t bytes_written = Write(current_buffer, remaining); + // On Windows, narrowing conversion from int64_t to DWORD will cause + // unexpected error. + // On MacOS, a single write() with more than kMaxInt32 will have + // "invalid argument" error. + // Therefore, limit the size for single write. + int64_t byte_to_write = num_bytes > kMaxInt32 ? kMaxInt32 : num_bytes; + int64_t bytes_written = Write(current_buffer, byte_to_write); if (bytes_written < 0) { return false; } diff --git a/runtime/bin/file_win.cc b/runtime/bin/file_win.cc index 48c384ae401..0d440760854 100644 --- a/runtime/bin/file_win.cc +++ b/runtime/bin/file_win.cc @@ -146,7 +146,8 @@ int64_t File::Read(void* buffer, int64_t num_bytes) { int64_t File::Write(const void* buffer, int64_t num_bytes) { int fd = handle_->fd(); - ASSERT(fd >= 0); + // Avoid narrowing conversion + ASSERT(fd >= 0 && num_bytes <= MAXDWORD && num_bytes >= 0); HANDLE handle = reinterpret_cast(_get_osfhandle(fd)); DWORD written = 0; BOOL result = WriteFile(handle, buffer, num_bytes, &written, NULL); diff --git a/tests/standalone_2/io/file_write_as_test.dart b/tests/standalone_2/io/file_write_as_test.dart index ce13ebdca8c..db851c9f1f0 100644 --- a/tests/standalone_2/io/file_write_as_test.dart +++ b/tests/standalone_2/io/file_write_as_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:io'; +import 'dart:typed_data'; import "package:async_helper/async_helper.dart"; import "package:expect/expect.dart"; @@ -27,6 +28,25 @@ testWriteAsStringSync(dir) { Expect.equals('$data$data', f.readAsStringSync()); } +testWriteWithLargeList(dir) { + // 0x100000000 exceeds the maximum of unsigned long. + // This should no longer hang. + // Issue: https://github.com/dart-lang/sdk/issues/40339 + var bytes; + try { + bytes = Uint8List(0x100000000); + } catch (e) { + // Create a big Uint8List may lead to OOM. This is acceptable. + Expect.isTrue(e.toString().contains('Out of Memory')); + return; + } + if (Platform.isWindows) { + File('NUL').writeAsBytesSync(bytes); + } else { + File('/dev/null').writeAsBytesSync(bytes); + } +} + Future testWriteAsBytes(dir) { var completer = new Completer(); var f = new File('${dir.path}/bytes.txt'); @@ -73,6 +93,7 @@ main() { var tempDir = Directory.systemTemp.createTempSync('dart_file_write_as'); testWriteAsBytesSync(tempDir); testWriteAsStringSync(tempDir); + testWriteWithLargeList(tempDir); testWriteAsBytes(tempDir).then((_) { return testWriteAsString(tempDir); }).then((_) {