Version 2.15.0-160.0.dev
Merge commit 'cc9c3ff325c0c448747641df04c1b12cbfe1fb92' into 'dev'
diff --git a/pkg/analysis_server/lib/src/plugin/plugin_manager.dart b/pkg/analysis_server/lib/src/plugin/plugin_manager.dart
index 07c4956..5972a14 100644
--- a/pkg/analysis_server/lib/src/plugin/plugin_manager.dart
+++ b/pkg/analysis_server/lib/src/plugin/plugin_manager.dart
@@ -330,8 +330,10 @@
try {
var session = await plugin.start(byteStorePath, sdkPath);
session?.onDone.then((_) {
- _pluginMap.remove(path);
- _notifyPluginsChanged();
+ if (_pluginMap[path] == plugin) {
+ _pluginMap.remove(path);
+ _notifyPluginsChanged();
+ }
});
} catch (exception, stackTrace) {
// Record the exception (for debugging purposes) and record the fact
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart
index 64000e8..49751f1 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart
@@ -6,7 +6,7 @@
import 'package:analysis_server/src/services/correction/fix/data_driven/element_kind.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart'
- show ClassElement, ExtensionElement;
+ show ClassElement, ExtensionElement, PrefixElement;
import 'package:analyzer/dart/element/type.dart';
/// An object that can be used to determine whether an element is appropriate
@@ -131,9 +131,15 @@
return ['', node.name];
} else if (parent is MethodDeclaration && node == parent.name) {
return [node.name];
- } else if ((parent is MethodInvocation && node == parent.methodName) ||
- (parent is PrefixedIdentifier && node == parent.identifier) ||
- (parent is PropertyAccess && node == parent.propertyName)) {
+ } else if ((parent is MethodInvocation &&
+ node == parent.methodName &&
+ !_isPrefix(parent.target)) ||
+ (parent is PrefixedIdentifier &&
+ node == parent.identifier &&
+ !_isPrefix(parent.prefix)) ||
+ (parent is PropertyAccess &&
+ node == parent.propertyName &&
+ !_isPrefix(parent.target))) {
return _componentsFromParent(node);
}
return _componentsFromIdentifier(node);
@@ -262,6 +268,11 @@
return importedUris;
}
+ /// Return `true` if the [node] is a prefix
+ static bool _isPrefix(AstNode? node) {
+ return node is SimpleIdentifier && node.staticElement is PrefixElement;
+ }
+
/// Return the kinds of elements that could reasonably be referenced at the
/// location of the [node]. If [child] is not `null` then the [node] is a
/// parent of the [child].
@@ -283,7 +294,9 @@
ElementKind.enumKind,
ElementKind.mixinKind
];
- } else if (node.realTarget != null) {
+ }
+ var realTarget = node.realTarget;
+ if (realTarget != null && !_isPrefix(realTarget)) {
return const [ElementKind.constructorKind, ElementKind.methodKind];
}
return const [
@@ -304,7 +317,8 @@
ElementKind.typedefKind
];
} else if (node is PrefixedIdentifier) {
- if (node.prefix == child) {
+ var prefix = node.prefix;
+ if (prefix == child) {
return const [
ElementKind.classKind,
ElementKind.enumKind,
@@ -312,6 +326,26 @@
ElementKind.mixinKind,
ElementKind.typedefKind
];
+ } else if (prefix.staticElement is PrefixElement) {
+ var parent = node.parent;
+ if ((parent is NamedType && parent.parent is! ConstructorName) ||
+ (parent is PropertyAccess && parent.target == node)) {
+ return const [
+ ElementKind.classKind,
+ ElementKind.enumKind,
+ ElementKind.extensionKind,
+ ElementKind.mixinKind,
+ ElementKind.typedefKind
+ ];
+ }
+ return const [
+ // If the old class has been removed then this might have been a
+ // constructor invocation.
+ ElementKind.constructorKind,
+ ElementKind.getterKind,
+ ElementKind.setterKind,
+ ElementKind.variableKind
+ ];
}
return const [
ElementKind.fieldKind,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/rename.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/rename.dart
index 104d268..c825366 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/rename.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/rename.dart
@@ -52,6 +52,10 @@
// The constructor was renamed from an unnamed constructor to a named
// constructor.
builder.addSimpleInsertion(parent.end, '.$newName');
+ } else if (parent is PrefixedIdentifier) {
+ // The constructor was renamed from an unnamed constructor to a named
+ // constructor.
+ builder.addSimpleInsertion(parent.end, '.$newName');
} else {
// The constructor was renamed from a named constructor to another named
// constructor.
@@ -81,6 +85,8 @@
return _Data(node);
} else if (node is ConstructorName) {
return _Data(node.name);
+ } else if (node is PrefixedIdentifier) {
+ return _Data(node.identifier);
}
return null;
}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 3385e85..ab54076 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -725,6 +725,9 @@
CompileTimeErrorCode.UNDEFINED_OPERATOR: [
ImportLibrary.forExtensionMember,
],
+ CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME: [
+ DataDriven.newInstance,
+ ],
CompileTimeErrorCode.UNDEFINED_SETTER: [
DataDriven.newInstance,
// TODO(brianwilkerson) Support ImportLibrary for non-extension members.
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart
index 6d0dc25..cf1aeb9 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/rename_test.dart
@@ -21,6 +21,7 @@
defineReflectiveTests(RenameMethodTest);
defineReflectiveTests(RenameMixinTest);
defineReflectiveTests(RenameTopLevelFunctionTest);
+ defineReflectiveTests(RenameTopLevelVariableTest);
defineReflectiveTests(RenameTypedefTest);
});
}
@@ -130,6 +131,29 @@
''', errorFilter: ignoreUnusedImport);
}
+ Future<void> test_constructor_unnamed_removed_prefixed() async {
+ setPackageContent('''
+class New {
+ New();
+}
+''');
+ setPackageData(_rename(['Old'], 'New'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+void f() {
+ p.Old();
+}
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+void f() {
+ p.New();
+}
+''');
+ }
+
Future<void> test_inExtends_deprecated() async {
setPackageContent('''
@deprecated
@@ -501,6 +525,29 @@
}
''');
}
+
+ Future<void> test_unnamed_named_removed_prefixed() async {
+ setPackageContent('''
+class C {
+ C.a();
+}
+''');
+ setPackageData(_rename(['', 'C'], 'a'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+void f() {
+ p.C();
+}
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+void f() {
+ p.C.a();
+}
+''');
+ }
}
@reflectiveTest
@@ -591,6 +638,25 @@
var s = New.empty;
''', errorFilter: ignoreUnusedImport);
}
+
+ Future<void> test_staticField_removed_prefixed() async {
+ setPackageContent('''
+extension New on String {
+ static String empty = '';
+}
+''');
+ setPackageData(_rename(['Old'], 'New'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+var s = p.Old.empty;
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+var s = p.New.empty;
+''');
+ }
}
@reflectiveTest
@@ -772,6 +838,29 @@
}
''');
}
+
+ Future<void> test_static_reference_removed_prefixed() async {
+ setPackageContent('''
+class C {
+ static int b;
+}
+''');
+ setPackageData(_rename(['a', 'C'], 'b'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+void f() {
+ p.C.a;
+}
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+void f() {
+ p.C.b;
+}
+''');
+ }
}
@reflectiveTest
@@ -967,6 +1056,27 @@
}
''', errorFilter: ignoreUnusedImport);
}
+
+ Future<void> test_topLevel_reference_removed_prefixed() async {
+ setPackageContent('''
+int get b => 1;
+''');
+ setPackageData(_rename(['a'], 'b'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+void f() {
+ p.a;
+}
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+void f() {
+ p.b;
+}
+''');
+ }
}
@reflectiveTest
@@ -1122,6 +1232,29 @@
}
''');
}
+
+ Future<void> test_static_reference_removed_prefixed() async {
+ setPackageContent('''
+class C {
+ static int b() {}
+}
+''');
+ setPackageData(_rename(['a', 'C'], 'b'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+void f() {
+ p.C.a();
+}
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+void f() {
+ p.C.b();
+}
+''');
+ }
}
@reflectiveTest
@@ -1164,6 +1297,23 @@
class C with New {}
''', errorFilter: ignoreUnusedImport);
}
+
+ Future<void> test_inWith_removed_prefixed() async {
+ setPackageContent('''
+mixin New {}
+''');
+ setPackageData(_rename(['Old'], 'New'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+class C with p.Old {}
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+class C with p.New {}
+''');
+ }
}
@reflectiveTest
@@ -1214,6 +1364,110 @@
}
''', errorFilter: ignoreUnusedImport);
}
+
+ Future<void> test_removed_prefixed() async {
+ setPackageContent('''
+int b() {}
+''');
+ setPackageData(_rename(['a'], 'b'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+void f() {
+ p.a();
+}
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+void f() {
+ p.b();
+}
+''');
+ }
+}
+
+@reflectiveTest
+class RenameTopLevelVariableTest extends _AbstractRenameTest {
+ @override
+ String get _kind => 'variable';
+
+ Future<void> test_toStaticField_noPrefix_deprecated() async {
+ setPackageContent('''
+@deprecated
+int Old = 0;
+class C {
+ int New = 1;
+}
+''');
+ setPackageData(_rename(['Old'], 'C.New'));
+ await resolveTestCode('''
+import '$importUri';
+
+int f() => Old;
+''');
+ await assertHasFix('''
+import '$importUri';
+
+int f() => C.New;
+''');
+ }
+
+ Future<void> test_toTopLevel_withoutPrefix_deprecated() async {
+ setPackageContent('''
+@deprecated
+int Old = 0;
+int New = 1;
+''');
+ setPackageData(_rename(['Old'], 'New'));
+ await resolveTestCode('''
+import '$importUri';
+
+int f() => Old;
+''');
+ await assertHasFix('''
+import '$importUri';
+
+int f() => New;
+''');
+ }
+
+ Future<void> test_toTopLevel_withoutPrefix_removed() async {
+ setPackageContent('''
+C New = C();
+class C {}
+''');
+ setPackageData(_rename(['Old'], 'New'));
+ await resolveTestCode('''
+import '$importUri';
+
+C f() => Old;
+''');
+ await assertHasFix('''
+import '$importUri';
+
+C f() => New;
+''');
+ }
+
+ Future<void> test_toTopLevel_withPrefix_deprecated() async {
+ setPackageContent('''
+@deprecated
+int Old = 0;
+int New = 1;
+''');
+ setPackageData(_rename(['Old'], 'New'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+int f() => p.Old;
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+int f() => p.New;
+''');
+ }
}
@reflectiveTest
@@ -1256,6 +1510,23 @@
void f(New o) {}
''', errorFilter: ignoreUnusedImport);
}
+
+ Future<void> test_removed_prefixed() async {
+ setPackageContent('''
+typedef New = int Function(int);
+''');
+ setPackageData(_rename(['Old'], 'New'));
+ await resolveTestCode('''
+import '$importUri' as p;
+
+void f(p.Old o) {}
+''');
+ await assertHasFix('''
+import '$importUri' as p;
+
+void f(p.New o) {}
+''');
+ }
}
abstract class _AbstractRenameTest extends DataDrivenFixProcessorTest {
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 0af5e76..f8c0bb5 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -302,8 +302,6 @@
isolate->group()->api_state()->AllocatePersistentHandle();
handle->set_ptr(msg_array);
isolate->bequeath(std::unique_ptr<Bequest>(new Bequest(handle, port.Id())));
- // TODO(aam): Ensure there are no dart api calls after this point as we want
- // to ensure that validated message won't get tampered with.
Isolate::KillIfExists(isolate, Isolate::LibMsgId::kKillMsg);
// Drain interrupts before running so any IMMEDIATE operations on the current
// isolate happen synchronously.
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 7504a27..cfc1759 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -485,6 +485,16 @@
return reinterpret_cast<Dart_Handle>(acquired_error_handle);
}
+Dart_Handle Api::UnwindInProgressError() {
+ Thread* T = Thread::Current();
+ CHECK_API_SCOPE(T);
+ TransitionToVM transition(T);
+ HANDLESCOPE(T);
+ const String& message = String::Handle(
+ Z, String::New("No api calls are allowed while unwind is in progress"));
+ return Api::NewHandle(T, UnwindError::New(message));
+}
+
bool Api::IsValid(Dart_Handle handle) {
Isolate* isolate = Isolate::Current();
Thread* thread = Thread::Current();
diff --git a/runtime/vm/dart_api_impl.h b/runtime/vm/dart_api_impl.h
index bd5d3e9..683be98 100644
--- a/runtime/vm/dart_api_impl.h
+++ b/runtime/vm/dart_api_impl.h
@@ -194,6 +194,9 @@
// Gets the handle which holds the pre-created acquired error object.
static Dart_Handle AcquiredError(IsolateGroup* isolate_group);
+ // Gets the handle for unwind-is-in-progress error.
+ static Dart_Handle UnwindInProgressError();
+
// Returns true if the handle holds a Smi.
static bool IsSmi(Dart_Handle handle) {
// Important: we do not require current thread to be in VM state because
@@ -335,6 +338,9 @@
if (thread->no_callback_scope_depth() != 0) { \
return reinterpret_cast<Dart_Handle>( \
Api::AcquiredError(thread->isolate_group())); \
+ } \
+ if (thread->is_unwind_in_progress()) { \
+ return reinterpret_cast<Dart_Handle>(Api::UnwindInProgressError()); \
}
#define ASSERT_CALLBACK_STATE(thread) \
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index dada22e..863880e 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -707,6 +707,83 @@
EXPECT_STREQ(kRegularString, exception_cstr);
}
+void JustPropagateErrorNative(Dart_NativeArguments args) {
+ Dart_Handle closure = Dart_GetNativeArgument(args, 0);
+ EXPECT(Dart_IsClosure(closure));
+ Dart_Handle result = Dart_InvokeClosure(closure, 0, NULL);
+ EXPECT(Dart_IsError(result));
+ Dart_PropagateError(result);
+ UNREACHABLE();
+}
+
+static Dart_NativeFunction JustPropagateError_lookup(Dart_Handle name,
+ int argument_count,
+ bool* auto_setup_scope) {
+ ASSERT(auto_setup_scope != NULL);
+ *auto_setup_scope = true;
+ return JustPropagateErrorNative;
+}
+
+TEST_CASE(DartAPI_EnsureUnwindErrorHandled_WhenKilled) {
+ const char* kScriptChars = R"(
+import 'dart:isolate';
+
+exitRightNow() {
+ Isolate.current.kill(priority: Isolate.immediate);
+}
+
+@pragma("vm:external-name", "Test_nativeFunc")
+external void nativeFunc(closure);
+
+void Func1() {
+ nativeFunc(() => exitRightNow());
+}
+)";
+ Dart_Handle lib =
+ TestCase::LoadTestScript(kScriptChars, &JustPropagateError_lookup);
+ Dart_Handle result;
+
+ result = Dart_Invoke(lib, NewString("Func1"), 0, NULL);
+ EXPECT(Dart_IsError(result));
+ EXPECT_SUBSTRING("isolate terminated by Isolate.kill", Dart_GetError(result));
+
+ result = Dart_Invoke(lib, NewString("Func1"), 0, NULL);
+ EXPECT(Dart_IsError(result));
+ EXPECT_SUBSTRING("No api calls are allowed while unwind is in progress",
+ Dart_GetError(result));
+}
+
+TEST_CASE(DartAPI_EnsureUnwindErrorHandled_WhenSendAndExit) {
+ const char* kScriptChars = R"(
+import 'dart:isolate';
+import 'dart:_internal' show sendAndExit;
+
+sendAndExitNow() {
+ final receivePort = ReceivePort();
+ sendAndExit(receivePort.sendPort, true);
+}
+
+@pragma("vm:external-name", "Test_nativeFunc")
+external void nativeFunc(closure);
+
+void Func1() {
+ nativeFunc(() => sendAndExitNow());
+}
+)";
+ Dart_Handle lib =
+ TestCase::LoadTestScript(kScriptChars, &JustPropagateError_lookup);
+ Dart_Handle result;
+
+ result = Dart_Invoke(lib, NewString("Func1"), 0, NULL);
+ EXPECT(Dart_IsError(result));
+ EXPECT_SUBSTRING("isolate terminated by Isolate.kill", Dart_GetError(result));
+
+ result = Dart_Invoke(lib, NewString("Func1"), 0, NULL);
+ EXPECT(Dart_IsError(result));
+ EXPECT_SUBSTRING("No api calls are allowed while unwind is in progress",
+ Dart_GetError(result));
+}
+
// Should we propagate the error via Dart_SetReturnValue?
static bool use_set_return = false;
diff --git a/runtime/vm/heap/become.h b/runtime/vm/heap/become.h
index 4b945bf..73d43ef 100644
--- a/runtime/vm/heap/become.h
+++ b/runtime/vm/heap/become.h
@@ -27,8 +27,9 @@
ObjectPtr target() const { return target_; }
void set_target(ObjectPtr target) { target_ = target; }
- intptr_t HeapSize() {
- intptr_t size = UntaggedObject::SizeTag::decode(tags_);
+ intptr_t HeapSize() { return HeapSize(tags_); }
+ intptr_t HeapSize(uword tags) {
+ intptr_t size = UntaggedObject::SizeTag::decode(tags);
if (size != 0) return size;
return *SizeAddress();
}
diff --git a/runtime/vm/heap/freelist.h b/runtime/vm/heap/freelist.h
index 7a1e2d0..8040f5f 100644
--- a/runtime/vm/heap/freelist.h
+++ b/runtime/vm/heap/freelist.h
@@ -28,8 +28,9 @@
void set_next(FreeListElement* next) { next_ = next; }
- intptr_t HeapSize() {
- intptr_t size = UntaggedObject::SizeTag::decode(tags_);
+ intptr_t HeapSize() { return HeapSize(tags_); }
+ intptr_t HeapSize(uword tags) {
+ intptr_t size = UntaggedObject::SizeTag::decode(tags);
if (size != 0) return size;
return *SizeAddress();
}
diff --git a/runtime/vm/heap/scavenger.h b/runtime/vm/heap/scavenger.h
index 810a26e..4b73f85 100644
--- a/runtime/vm/heap/scavenger.h
+++ b/runtime/vm/heap/scavenger.h
@@ -121,7 +121,7 @@
ASSERT(owner_ == nullptr);
uword result = top_;
uword new_top = result + size;
- if (LIKELY(new_top < end_)) {
+ if (LIKELY(new_top <= end_)) {
top_ = new_top;
return result;
}
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 940b893..9d49ce6 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -1126,6 +1126,7 @@
if (!obj.IsSmi()) return Error::null();
const intptr_t priority = Smi::Cast(obj).Value();
if (priority == Isolate::kImmediateAction) {
+ Thread::Current()->StartUnwindError();
obj = message.At(2);
if (I->VerifyTerminateCapability(obj)) {
// We will kill the current isolate by returning an UnwindError.
diff --git a/runtime/vm/raw_object.cc b/runtime/vm/raw_object.cc
index 9e4013f..2deada7 100644
--- a/runtime/vm/raw_object.cc
+++ b/runtime/vm/raw_object.cc
@@ -222,13 +222,13 @@
case kFreeListElement: {
uword addr = UntaggedObject::ToAddr(this);
FreeListElement* element = reinterpret_cast<FreeListElement*>(addr);
- instance_size = element->HeapSize();
+ instance_size = element->HeapSize(tags);
break;
}
case kForwardingCorpse: {
uword addr = UntaggedObject::ToAddr(this);
ForwardingCorpse* element = reinterpret_cast<ForwardingCorpse*>(addr);
- instance_size = element->HeapSize();
+ instance_size = element->HeapSize(tags);
break;
}
case kWeakSerializationReferenceCid: {
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 994273d..54e4108 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -3635,6 +3635,9 @@
if (thread->no_callback_scope_depth() != 0) {
FATAL("Cannot invoke native callback when API callbacks are prohibited.");
}
+ if (thread->is_unwind_in_progress()) {
+ FATAL("Cannot invoke native callback while unwind error propagates.");
+ }
if (!thread->IsMutatorThread()) {
FATAL("Native callbacks must be invoked on the mutator thread.");
}
diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h
index baa448d..6824dd8 100644
--- a/runtime/vm/thread.h
+++ b/runtime/vm/thread.h
@@ -521,6 +521,10 @@
no_callback_scope_depth_ -= 1;
}
+ bool is_unwind_in_progress() const { return is_unwind_in_progress_; }
+
+ void StartUnwindError() { is_unwind_in_progress_ = true; }
+
#if defined(DEBUG)
void EnterCompiler() {
ASSERT(!IsInsideCompiler());
@@ -1182,6 +1186,8 @@
Thread* next_; // Used to chain the thread structures in an isolate.
bool is_mutator_thread_ = false;
+ bool is_unwind_in_progress_ = false;
+
#if defined(DEBUG)
bool inside_compiler_ = false;
#endif
diff --git a/sdk/lib/io/file_system_entity.dart b/sdk/lib/io/file_system_entity.dart
index 9a858de..4eedcf8 100644
--- a/sdk/lib/io/file_system_entity.dart
+++ b/sdk/lib/io/file_system_entity.dart
@@ -642,7 +642,7 @@
_toNullTerminatedUtf8Array(utf8.encoder.convert(s));
static Uint8List _toNullTerminatedUtf8Array(Uint8List l) {
- if (l.isNotEmpty && l.last != 0) {
+ if (l.isEmpty || (l.isNotEmpty && l.last != 0)) {
final tmp = new Uint8List(l.length + 1);
tmp.setRange(0, l.length, l);
return tmp;
diff --git a/tests/standalone/io/file_error_test.dart b/tests/standalone/io/file_error_test.dart
index ee80df4..2050e23 100644
--- a/tests/standalone/io/file_error_test.dart
+++ b/tests/standalone/io/file_error_test.dart
@@ -14,6 +14,20 @@
return Directory.systemTemp.createTempSync('dart_file_error');
}
+bool checkCannotOpenFileException(e) {
+ Expect.isTrue(e is FileSystemException);
+ Expect.isTrue(e.osError != null);
+ Expect.isTrue(e.toString().indexOf("Cannot open file") != -1);
+ if (Platform.operatingSystem == "linux") {
+ Expect.equals(2, e.osError.errorCode);
+ } else if (Platform.operatingSystem == "macos") {
+ Expect.equals(2, e.osError.errorCode);
+ } else if (Platform.operatingSystem == "windows") {
+ Expect.equals(3, e.osError.errorCode);
+ }
+ return true;
+}
+
bool checkNonExistentFileSystemException(e, str) {
Expect.isTrue(e is FileSystemException);
Expect.isTrue(e.osError != null);
@@ -36,6 +50,20 @@
e, "Cannot retrieve length of file");
}
+void testOpenBlankFilename() {
+ asyncStart();
+ var file = new File("");
+
+ // Non-existing file should throw exception.
+ Expect.throws(() => file.openSync(), (e) => checkCannotOpenFileException(e));
+
+ var openFuture = file.open(mode: FileMode.read);
+ openFuture.then((raf) => Expect.fail("Unreachable code")).catchError((error) {
+ checkCannotOpenFileException(error);
+ asyncEnd();
+ });
+}
+
void testOpenNonExistent() {
asyncStart();
Directory temp = tempDir();
@@ -409,6 +437,7 @@
}
main() {
+ testOpenBlankFilename();
testOpenNonExistent();
testDeleteNonExistent();
testLengthNonExistent();
diff --git a/tests/standalone_2/io/file_error_test.dart b/tests/standalone_2/io/file_error_test.dart
index ee55a05..d52f2ee 100644
--- a/tests/standalone_2/io/file_error_test.dart
+++ b/tests/standalone_2/io/file_error_test.dart
@@ -16,6 +16,20 @@
return Directory.systemTemp.createTempSync('dart_file_error');
}
+bool checkCannotOpenFileException(e) {
+ Expect.isTrue(e is FileSystemException);
+ Expect.isTrue(e.osError != null);
+ Expect.isTrue(e.toString().indexOf("Cannot open file") != -1);
+ if (Platform.operatingSystem == "linux") {
+ Expect.equals(2, e.osError.errorCode);
+ } else if (Platform.operatingSystem == "macos") {
+ Expect.equals(2, e.osError.errorCode);
+ } else if (Platform.operatingSystem == "windows") {
+ Expect.equals(3, e.osError.errorCode);
+ }
+ return true;
+}
+
bool checkNonExistentFileSystemException(e, str) {
Expect.isTrue(e is FileSystemException);
Expect.isTrue(e.osError != null);
@@ -38,6 +52,20 @@
e, "Cannot retrieve length of file");
}
+void testOpenBlankFilename() {
+ asyncStart();
+ var file = new File("");
+
+ // Non-existing file should throw exception.
+ Expect.throws(() => file.openSync(), (e) => checkCannotOpenFileException(e));
+
+ var openFuture = file.open(mode: FileMode.read);
+ openFuture.then((raf) => Expect.fail("Unreachable code")).catchError((error) {
+ checkCannotOpenFileException(error);
+ asyncEnd();
+ });
+}
+
void testOpenNonExistent() {
asyncStart();
Directory temp = tempDir();
@@ -411,6 +439,7 @@
}
main() {
+ testOpenBlankFilename();
testOpenNonExistent();
testDeleteNonExistent();
testLengthNonExistent();
diff --git a/tools/VERSION b/tools/VERSION
index e31f9b6..ba9cb17 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 159
+PRERELEASE 160
PRERELEASE_PATCH 0
\ No newline at end of file