Version 2.14.0-181.0.dev

Merge commit '6c254fab89adbeebfb616cdec497260db4cb7cf7' into 'dev'
diff --git a/runtime/lib/object.cc b/runtime/lib/object.cc
index fb999cf..93b086d 100644
--- a/runtime/lib/object.cc
+++ b/runtime/lib/object.cc
@@ -52,19 +52,15 @@
 
 DEFINE_NATIVE_ENTRY(Object_setHashIfNotSetYet, 0, 2) {
   GET_NON_NULL_NATIVE_ARGUMENT(Smi, hash, arguments->NativeArgAt(1));
-  const intptr_t current_hash = GetHash(isolate, arguments->NativeArgAt(0));
-  if (current_hash != 0) {
-    return Smi::New(current_hash);
-  }
 #if defined(HASH_IN_OBJECT_HEADER)
-  Object::SetCachedHash(arguments->NativeArgAt(0), hash.Value());
+  return Smi::New(
+      Object::SetCachedHashIfNotSet(arguments->NativeArgAt(0), hash.Value()));
 #else
   const Instance& instance =
       Instance::CheckedHandle(zone, arguments->NativeArgAt(0));
-  Heap* heap = isolate->group()->heap();
-  heap->SetHash(instance.ptr(), hash.Value());
+  Heap* heap = thread->heap();
+  return Smi::New(heap->SetHashIfNotSet(instance.ptr(), hash.Value()));
 #endif
-  return hash.ptr();
 }
 
 DEFINE_NATIVE_ENTRY(Object_toString, 0, 1) {
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm64.cc b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
index 9b1b6e38..cf58e60 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
@@ -1601,26 +1601,27 @@
 
 void AsmIntrinsifier::Object_setHashIfNotSetYet(Assembler* assembler,
                                                 Label* normal_ir_body) {
-  Label already_set;
-  __ ldr(R0, Address(SP, 1 * target::kWordSize));  // Object.
-  __ ldr(R1, FieldAddress(R0, target::String::hash_offset(), kFourBytes),
-         kUnsignedFourBytes);
-  __ cbnz(&already_set, R1, kFourBytes);
-  __ ldr(R1, Address(SP, 0 * target::kWordSize));  // Value.
+  __ ldp(/*Value=*/R1, /*Object=*/R0, Address(SP, 0, Address::PairOffset));
   // R0: Untagged address of header word (ldxr/stxr do not support offsets).
   __ sub(R0, R0, Operand(kHeapObjectTag));
   __ SmiUntag(R1);
   __ LslImmediate(R3, R1, target::UntaggedObject::kHashTagPos);
-  Label retry;
+
+  Label retry, already_set_in_r4;
   __ Bind(&retry);
   __ ldxr(R2, R0, kEightBytes);
+  __ LsrImmediate(R4, R2, target::UntaggedObject::kHashTagPos);
+  __ cbnz(&already_set_in_r4, R4);
   __ orr(R2, R2, Operand(R3));
   __ stxr(R4, R2, R0, kEightBytes);
   __ cbnz(&retry, R4);
   // Fall-through with R1 containing new hash value (untagged).
-  __ Bind(&already_set);
   __ SmiTag(R0, R1);
   __ ret();
+  __ Bind(&already_set_in_r4);
+  __ clrex();
+  __ SmiTag(R0, R4);
+  __ ret();
 }
 
 void GenerateSubstringMatchesSpecialization(Assembler* assembler,
diff --git a/runtime/vm/compiler/asm_intrinsifier_test.cc b/runtime/vm/compiler/asm_intrinsifier_test.cc
new file mode 100644
index 0000000..6fbc49d
--- /dev/null
+++ b/runtime/vm/compiler/asm_intrinsifier_test.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "platform/assert.h"
+
+#include "vm/globals.h"
+#include "vm/symbols.h"
+#include "vm/unit_test.h"
+
+namespace dart {
+
+static intptr_t GetHash(Isolate* isolate, const ObjectPtr obj) {
+#if defined(HASH_IN_OBJECT_HEADER)
+  return Object::GetCachedHash(obj);
+#else
+  Heap* heap = isolate->group()->heap();
+  ASSERT(obj->IsDartInstance());
+  return heap->GetHash(obj);
+#endif
+}
+
+ISOLATE_UNIT_TEST_CASE(AsmIntrinsifier_SetHashIfNotSetYet) {
+  auto I = Isolate::Current();
+  const auto& corelib = Library::Handle(Library::CoreLibrary());
+  const auto& name = String::Handle(String::New("_setHashIfNotSetYet"));
+  const auto& symbol = String::Handle(Symbols::New(thread, name));
+
+  const auto& function =
+      Function::Handle(corelib.LookupFunctionAllowPrivate(symbol));
+  const auto& object_class =
+      Class::Handle(corelib.LookupClass(Symbols::Object()));
+
+  auto& smi0 = Smi::Handle(Smi::New(0));
+  auto& smi21 = Smi::Handle(Smi::New(21));
+  auto& smi42 = Smi::Handle(Smi::New(42));
+  const auto& obj = Object::Handle(Instance::New(object_class));
+  const auto& args = Array::Handle(Array::New(2));
+
+  const auto& args_descriptor_array =
+      Array::Handle(ArgumentsDescriptor::NewBoxed(0, 2, Array::empty_array()));
+
+  // Initialized to 0
+  EXPECT_EQ(smi0.ptr(), Smi::New(GetHash(I, obj.ptr())));
+
+  // Lazily set to 42 on first call.
+  args.SetAt(0, obj);
+  args.SetAt(1, smi42);
+  EXPECT_EQ(smi42.ptr(),
+            DartEntry::InvokeFunction(function, args, args_descriptor_array));
+  EXPECT_EQ(smi42.ptr(), Smi::New(GetHash(I, obj.ptr())));
+
+  // Stays at 42 on subsequent calls.
+  args.SetAt(0, obj);
+  args.SetAt(1, smi21);
+  EXPECT_EQ(smi42.ptr(),
+            DartEntry::InvokeFunction(function, args, args_descriptor_array));
+  EXPECT_EQ(smi42.ptr(), Smi::New(GetHash(I, obj.ptr())));
+
+  const auto& obj2 = Object::Handle(Instance::New(object_class));
+  const auto& smiMax = Smi::Handle(Smi::New(0xffffffff));
+
+  // Initialized to 0
+  EXPECT_EQ(smi0.ptr(), Smi::New(GetHash(I, obj2.ptr())));
+
+  // Lazily set to smiMax first call.
+  args.SetAt(0, obj2);
+  args.SetAt(1, smiMax);
+  EXPECT_EQ(smiMax.ptr(),
+            DartEntry::InvokeFunction(function, args, args_descriptor_array));
+  EXPECT_EQ(smiMax.ptr(), Smi::New(GetHash(I, obj2.ptr())));
+
+  // Stays at smiMax on subsequent calls.
+  args.SetAt(0, obj2);
+  args.SetAt(1, smi21);
+  EXPECT_EQ(smiMax.ptr(),
+            DartEntry::InvokeFunction(function, args, args_descriptor_array));
+  EXPECT_EQ(smiMax.ptr(), Smi::New(GetHash(I, obj2.ptr())));
+}
+
+}  // namespace dart
diff --git a/runtime/vm/compiler/asm_intrinsifier_x64.cc b/runtime/vm/compiler/asm_intrinsifier_x64.cc
index 7c068d9..eb5b694 100644
--- a/runtime/vm/compiler/asm_intrinsifier_x64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_x64.cc
@@ -1455,23 +1455,35 @@
 
 void AsmIntrinsifier::Object_setHashIfNotSetYet(Assembler* assembler,
                                                 Label* normal_ir_body) {
-  Label already_set;
-  __ movq(RAX, Address(RSP, +2 * target::kWordSize));  // Object.
-  __ movl(RDX, FieldAddress(RAX, target::String::hash_offset()));
-  __ testl(RDX, RDX);
-  __ j(NOT_ZERO, &already_set, AssemblerBase::kNearJump);
-  __ movq(RDX, Address(RSP, +1 * target::kWordSize));  // Value.
-  __ SmiUntag(RDX);
-  __ shlq(RDX, Immediate(target::UntaggedObject::kHashTagPos));
-  // lock+orq is an atomic read-modify-write.
-  __ lock();
-  __ orq(FieldAddress(RAX, target::Object::tags_offset()), RDX);
-  __ movq(RAX, Address(RSP, +1 * target::kWordSize));
-  __ ret();
-  __ Bind(&already_set);
-  __ movl(RAX, RDX);
+  ASSERT(target::String::hash_offset() == 4);
+
+  __ movq(RBX, Address(RSP, +2 * target::kWordSize));  // Object.
+  __ movq(RCX, Address(RSP, +1 * target::kWordSize));  // Value.
+  __ SmiUntag(RCX);
+  __ MoveRegister(RDX, RCX);
+  __ shlq(RDX, Immediate(32));
+
+  Label retry, success, already_in_rax;
+  __ Bind(&retry);
+  // RAX is used by "cmpxchgq" as comparison value (if comparison succeeds the
+  // store is performed).
+  __ movq(RAX, FieldAddress(RBX, 0));
+  __ TestImmediate(RAX, Immediate(0xffffffff00000000));
+  __ BranchIf(NOT_ZERO, &already_in_rax);
+  __ MoveRegister(RSI, RAX);
+  __ orq(RSI, RDX);
+  __ LockCmpxchgq(FieldAddress(RBX, 0), RSI);
+  __ BranchIf(NOT_ZERO, &retry);
+  // Fall-through with RCX containing new hash value (untagged)
+  __ Bind(&success);
+  __ SmiTag(RCX);
+  __ MoveRegister(RAX, RCX);
+  __ Ret();
+
+  __ Bind(&already_in_rax);
+  __ shrq(RAX, Immediate(32));
   __ SmiTag(RAX);
-  __ ret();
+  __ Ret();
 }
 
 void GenerateSubstringMatchesSpecialization(Assembler* assembler,
diff --git a/runtime/vm/compiler/backend/linearscan.cc b/runtime/vm/compiler/backend/linearscan.cc
index 219cc0d..8e3e457 100644
--- a/runtime/vm/compiler/backend/linearscan.cc
+++ b/runtime/vm/compiler/backend/linearscan.cc
@@ -2239,17 +2239,29 @@
     }
   }
 
-  TRACE_ALLOC(THR_Print("assigning free register "));
-  TRACE_ALLOC(MakeRegisterLocation(candidate).Print());
-  TRACE_ALLOC(THR_Print(" to v%" Pd "\n", unallocated->vreg()));
-
   if (free_until != kMaxPosition) {
     // There was an intersection. Split unallocated.
     TRACE_ALLOC(THR_Print("  splitting at %" Pd "\n", free_until));
     LiveRange* tail = unallocated->SplitAt(free_until);
     AddToUnallocated(tail);
+
+    // If unallocated represents a constant value and does not have
+    // any uses then avoid using a register for it.
+    if (unallocated->first_use() == NULL) {
+      if (unallocated->vreg() >= 0) {
+        LiveRange* parent = GetLiveRange(unallocated->vreg());
+        if (parent->spill_slot().IsConstant()) {
+          Spill(unallocated);
+          return true;
+        }
+      }
+    }
   }
 
+  TRACE_ALLOC(THR_Print("  assigning free register "));
+  TRACE_ALLOC(MakeRegisterLocation(candidate).Print());
+  TRACE_ALLOC(THR_Print(" to v%" Pd "\n", unallocated->vreg()));
+
   registers_[candidate]->Add(unallocated);
   unallocated->set_assigned_location(MakeRegisterLocation(candidate));
   return true;
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index 51b81d1..05fd213 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -154,6 +154,7 @@
 ]
 
 compiler_sources_tests = [
+  "asm_intrinsifier_test.cc",
   "assembler/assembler_arm64_test.cc",
   "assembler/assembler_arm_test.cc",
   "assembler/assembler_ia32_test.cc",
diff --git a/runtime/vm/heap/become.cc b/runtime/vm/heap/become.cc
index 1e733b8..4bb646d 100644
--- a/runtime/vm/heap/become.cc
+++ b/runtime/vm/heap/become.cc
@@ -297,7 +297,7 @@
     ForwardObjectTo(before_obj, after_obj);
     heap->ForwardWeakEntries(before_obj, after_obj);
 #if defined(HASH_IN_OBJECT_HEADER)
-    Object::SetCachedHash(after_obj, Object::GetCachedHash(before_obj));
+    Object::SetCachedHashIfNotSet(after_obj, Object::GetCachedHash(before_obj));
 #endif
   }
 
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index 616c6bf..51c2b28 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -893,6 +893,17 @@
   }
 }
 
+intptr_t Heap::SetWeakEntryIfNonExistent(ObjectPtr raw_obj,
+                                         WeakSelector sel,
+                                         intptr_t val) {
+  if (!raw_obj->IsSmiOrOldObject()) {
+    return new_weak_tables_[sel]->SetValueIfNonExistent(raw_obj, val);
+  } else {
+    ASSERT(raw_obj->IsSmiOrOldObject());
+    return old_weak_tables_[sel]->SetValueIfNonExistent(raw_obj, val);
+  }
+}
+
 void Heap::ForwardWeakEntries(ObjectPtr before_object, ObjectPtr after_object) {
   const auto before_space =
       !before_object->IsSmiOrOldObject() ? Heap::kNew : Heap::kOld;
diff --git a/runtime/vm/heap/heap.h b/runtime/vm/heap/heap.h
index 6ec7705..a410336 100644
--- a/runtime/vm/heap/heap.h
+++ b/runtime/vm/heap/heap.h
@@ -211,8 +211,8 @@
 #if !defined(HASH_IN_OBJECT_HEADER)
   // Associate an identity hashCode with an object. An non-existent hashCode
   // is equal to 0.
-  void SetHash(ObjectPtr raw_obj, intptr_t hash) {
-    SetWeakEntry(raw_obj, kIdentityHashes, hash);
+  intptr_t SetHashIfNotSet(ObjectPtr raw_obj, intptr_t hash) {
+    return SetWeakEntryIfNonExistent(raw_obj, kIdentityHashes, hash);
   }
   intptr_t GetHash(ObjectPtr raw_obj) const {
     return GetWeakEntry(raw_obj, kIdentityHashes);
@@ -251,6 +251,9 @@
   // Used by the GC algorithms to propagate weak entries.
   intptr_t GetWeakEntry(ObjectPtr raw_obj, WeakSelector sel) const;
   void SetWeakEntry(ObjectPtr raw_obj, WeakSelector sel, intptr_t val);
+  intptr_t SetWeakEntryIfNonExistent(ObjectPtr raw_obj,
+                                     WeakSelector sel,
+                                     intptr_t val);
 
   WeakTable* GetWeakTable(Space space, WeakSelector selector) const {
     if (space == kNew) {
diff --git a/runtime/vm/heap/weak_table.h b/runtime/vm/heap/weak_table.h
index d5ddc2e..3062b53 100644
--- a/runtime/vm/heap/weak_table.h
+++ b/runtime/vm/heap/weak_table.h
@@ -62,6 +62,16 @@
     return SetValueExclusive(key, val);
   }
 
+  intptr_t SetValueIfNonExistent(ObjectPtr key, intptr_t val) {
+    MutexLocker ml(&mutex_);
+    const auto old_value = GetValueExclusive(key);
+    if (old_value == kNoValue) {
+      SetValueExclusive(key, val);
+      return val;
+    }
+    return old_value;
+  }
+
   // The following "exclusive" methods must only be called from call sites
   // which are known to have exclusive access to the weak table.
   //
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index 46124a8..2402951 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -224,7 +224,7 @@
   }
 #if defined(HASH_IN_OBJECT_HEADER)
   const uint32_t hash = Object::GetCachedHash(instance.ptr());
-  Object::SetCachedHash(result.ptr(), hash);
+  Object::SetCachedHashIfNotSet(result.ptr(), hash);
 #endif
 
   // Morph the context from instance to result using mapping_.
diff --git a/runtime/vm/json_test.cc b/runtime/vm/json_test.cc
index 60c679e..ed619c2 100644
--- a/runtime/vm/json_test.cc
+++ b/runtime/vm/json_test.cc
@@ -169,8 +169,12 @@
     JSONObject jsobj(&jsarr);
     jsobj.AddProperty("object_key", Object::Handle(Object::null()));
   }
-  char buffer[1024];
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 2048;
+  char buffer[kBufferSize];
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   ElideJSONSubstring("objects", buffer, buffer);
   EXPECT_STREQ(
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index abe44ee..cf4a20d 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -1323,7 +1323,7 @@
           counter_ += 2011;  // The year Dart was announced and a prime.
           counter_ &= 0x3fffffff;
           if (counter_ == 0) counter_++;
-          Object::SetCachedHash(obj, counter_);
+          Object::SetCachedHashIfNotSet(obj, counter_);
         }
       }
 #endif
@@ -1431,7 +1431,7 @@
     OneByteStringPtr str = static_cast<OneByteStringPtr>(object);
     if (String::GetCachedHash(str) == 0) {
       intptr_t hash = String::Hash(str);
-      String::SetCachedHash(str, hash);
+      String::SetCachedHashIfNotSet(str, hash);
     }
     intptr_t size = OneByteString::UnroundedSize(str);
     ASSERT(size <= str->untag()->HeapSize());
@@ -1441,7 +1441,7 @@
     TwoByteStringPtr str = static_cast<TwoByteStringPtr>(object);
     if (String::GetCachedHash(str) == 0) {
       intptr_t hash = String::Hash(str);
-      String::SetCachedHash(str, hash);
+      String::SetCachedHashIfNotSet(str, hash);
     }
     ASSERT(String::GetCachedHash(str) != 0);
     intptr_t size = TwoByteString::UnroundedSize(str);
@@ -1453,14 +1453,14 @@
         static_cast<ExternalOneByteStringPtr>(object);
     if (String::GetCachedHash(str) == 0) {
       intptr_t hash = String::Hash(str);
-      String::SetCachedHash(str, hash);
+      String::SetCachedHashIfNotSet(str, hash);
     }
   } else if (cid == kExternalTwoByteStringCid) {
     ExternalTwoByteStringPtr str =
         static_cast<ExternalTwoByteStringPtr>(object);
     if (String::GetCachedHash(str) == 0) {
       intptr_t hash = String::Hash(str);
-      String::SetCachedHash(str, hash);
+      String::SetCachedHashIfNotSet(str, hash);
     }
   } else if (cid == kCodeSourceMapCid) {
     CodeSourceMapPtr map = CodeSourceMap::RawCast(object);
@@ -2592,6 +2592,9 @@
   tags = UntaggedObject::OldAndNotMarkedBit::update(is_old, tags);
   tags = UntaggedObject::OldAndNotRememberedBit::update(is_old, tags);
   tags = UntaggedObject::NewBit::update(!is_old, tags);
+#if defined(HASH_IN_OBJECT_HEADER)
+  tags = UntaggedObject::HashTag::update(0, tags);
+#endif
   reinterpret_cast<UntaggedObject*>(address)->tags_ = tags;
 }
 
@@ -23712,7 +23715,9 @@
     NoSafepointScope no_safepoint;
     result ^= raw;
     result.SetLength(len);
-    result.SetHash(0);
+#if !defined(HASH_IN_OBJECT_HEADER)
+    result.ptr()->untag()->set_hash(Smi::New(0));
+#endif
   }
   return TwoByteString::raw(result);
 }
@@ -23869,7 +23874,9 @@
     NoSafepointScope no_safepoint;
     result ^= raw;
     result.SetLength(len);
-    result.SetHash(0);
+#if !defined(HASH_IN_OBJECT_HEADER)
+    result.ptr()->untag()->set_hash(Smi::New(0));
+#endif
     SetExternalData(result, data, peer);
   }
   AddFinalizer(result, peer, callback, external_allocation_size);
@@ -23899,7 +23906,9 @@
     NoSafepointScope no_safepoint;
     result ^= raw;
     result.SetLength(len);
-    result.SetHash(0);
+#if !defined(HASH_IN_OBJECT_HEADER)
+    result.ptr()->untag()->set_hash(Smi::New(0));
+#endif
     SetExternalData(result, data, peer);
   }
   AddFinalizer(result, peer, callback, external_allocation_size);
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index d778a53..fe21539 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -417,8 +417,8 @@
     return obj->untag()->GetHeaderHash();
   }
 
-  static void SetCachedHash(ObjectPtr obj, uint32_t hash) {
-    obj->untag()->SetHeaderHash(hash);
+  static uint32_t SetCachedHashIfNotSet(ObjectPtr obj, uint32_t hash) {
+    return obj->untag()->SetHeaderHashIfNotSet(hash);
   }
 #endif
 
@@ -9079,7 +9079,8 @@
       return result;
     }
     result = String::Hash(*this, 0, this->Length());
-    SetCachedHash(ptr(), result);
+    uword set_hash = SetCachedHashIfNotSet(ptr(), result);
+    ASSERT(set_hash == result);
     return result;
   }
 
@@ -9326,8 +9327,18 @@
     return Smi::Value(obj->untag()->hash_);
   }
 
-  static void SetCachedHash(StringPtr obj, uint32_t hash) {
+  static uint32_t SetCachedHashIfNotSet(StringPtr obj, uint32_t hash) {
+    ASSERT(Smi::Value(obj->untag()->hash_) == 0 ||
+           Smi::Value(obj->untag()->hash_) == static_cast<intptr_t>(hash));
+    return SetCachedHash(obj, hash);
+  }
+  static uint32_t SetCachedHash(StringPtr obj, uint32_t hash) {
     obj->untag()->hash_ = Smi::New(hash);
+    return hash;
+  }
+#else
+  static uint32_t SetCachedHash(StringPtr obj, uint32_t hash) {
+    return Object::SetCachedHashIfNotSet(obj, hash);
   }
 #endif
 
@@ -9344,7 +9355,10 @@
     untag()->set_length(Smi::New(value));
   }
 
-  void SetHash(intptr_t value) const { SetCachedHash(ptr(), value); }
+  void SetHash(intptr_t value) const {
+    const intptr_t hash_set = SetCachedHashIfNotSet(ptr(), value);
+    ASSERT(hash_set == value);
+  }
 
   template <typename HandleType, typename ElementType, typename CallbackType>
   static void ReadFromImpl(SnapshotReader* reader,
diff --git a/runtime/vm/object_graph.cc b/runtime/vm/object_graph.cc
index 1ce7b76..9ee1b59 100644
--- a/runtime/vm/object_graph.cc
+++ b/runtime/vm/object_graph.cc
@@ -1346,7 +1346,7 @@
   if (hash == 0) {
     ASSERT(!thread->heap()->old_space()->IsObjectFromImagePages(obj));
     hash = GenerateHash(thread->random());
-    Object::SetCachedHash(obj, hash);
+    Object::SetCachedHashIfNotSet(obj, hash);
   }
 #else
   Heap* heap = thread->heap();
@@ -1354,7 +1354,7 @@
   if (hash == 0) {
     ASSERT(!heap->old_space()->IsObjectFromImagePages(obj));
     hash = GenerateHash(thread->random());
-    heap->SetHash(obj, hash);
+    heap->SetHashIfNotSet(obj, hash);
   }
 #endif
   return hash;
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index 6136da2..7afae88 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -4335,7 +4335,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(PrintJSONPrimitives) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   Isolate* isolate = Isolate::Current();
 
   // Class reference
@@ -4343,7 +4345,9 @@
     JSONStream js;
     Class& cls = Class::Handle(isolate->group()->object_store()->bool_class());
     cls.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
 
     EXPECT_STREQ(
@@ -4366,7 +4370,9 @@
         Function::Handle(Resolver::ResolveFunction(Z, cls, func_name));
     ASSERT(!func.IsNull());
     func.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     EXPECT_STREQ(
         "{\"type\":\"@Function\",\"fixedId\":true,\"id\":\"\","
@@ -4392,7 +4398,9 @@
     Library& lib =
         Library::Handle(isolate->group()->object_store()->core_library());
     lib.PrintJSON(&js, true);
-    ElideJSONSubstring("libraries", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("libraries", json_str, buffer);
     EXPECT_STREQ(
         "{\"type\":\"@Library\",\"fixedId\":true,\"id\":\"\","
         "\"name\":\"dart.core\",\"uri\":\"dart:core\"}",
@@ -4402,7 +4410,9 @@
   {
     JSONStream js;
     Bool::True().PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     EXPECT_STREQ(
         "{\"type\":\"@Instance\",\"_vmType\":\"Bool\",\"class\":{\"type\":\"@"
@@ -4421,7 +4431,9 @@
     JSONStream js;
     const Integer& smi = Integer::Handle(Integer::New(7));
     smi.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("_Smi@", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     EXPECT_STREQ(
@@ -4441,7 +4453,9 @@
     JSONStream js;
     const Integer& smi = Integer::Handle(Integer::New(Mint::kMinValue));
     smi.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("objects", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     ElideJSONSubstring("_Mint@", buffer, buffer);
@@ -4462,7 +4476,9 @@
     JSONStream js;
     const Double& dub = Double::Handle(Double::New(0.1234));
     dub.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("objects", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     ElideJSONSubstring("_Double@", buffer, buffer);
@@ -4483,7 +4499,9 @@
     JSONStream js;
     const String& str = String::Handle(String::New("dw"));
     str.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("objects", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     ElideJSONSubstring("_OneByteString@", buffer, buffer);
@@ -4504,7 +4522,9 @@
     JSONStream js;
     const Array& array = Array::Handle(Array::New(0));
     array.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("objects", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     ElideJSONSubstring("_List@", buffer, buffer);
@@ -4525,7 +4545,9 @@
     const GrowableObjectArray& array =
         GrowableObjectArray::Handle(GrowableObjectArray::New());
     array.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("objects", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     ElideJSONSubstring("_GrowableList@", buffer, buffer);
@@ -4547,7 +4569,9 @@
     const LinkedHashMap& array =
         LinkedHashMap::Handle(LinkedHashMap::NewDefault());
     array.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("objects", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     ElideJSONSubstring("_InternalLinkedHashMap@", buffer, buffer);
@@ -4569,7 +4593,9 @@
     JSONStream js;
     Instance& tag = Instance::Handle(isolate->default_tag());
     tag.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("objects", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     ElideJSONSubstring("_UserTag@", buffer, buffer);
@@ -4597,7 +4623,9 @@
     Instance& type =
         Instance::Handle(isolate->group()->object_store()->bool_type());
     type.PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("objects", buffer, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     ElideJSONSubstring("_Type@", buffer, buffer);
@@ -4628,7 +4656,9 @@
   {
     JSONStream js;
     Object::null_object().PrintJSON(&js, true);
-    ElideJSONSubstring("classes", js.ToCString(), buffer);
+    const char* json_str = js.ToCString();
+    ASSERT(strlen(json_str) < kBufferSize);
+    ElideJSONSubstring("classes", json_str, buffer);
     ElideJSONSubstring("libraries", buffer, buffer);
     EXPECT_STREQ(
         "{\"type\":\"@Instance\",\"_vmType\":\"null\",\"class\":{\"type\":\"@"
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index 9955114..8d3185d 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -318,6 +318,26 @@
     }
 
     template <class TagBitField>
+    typename TagBitField::Type UpdateConditional(
+        typename TagBitField::Type value_to_be_set,
+        typename TagBitField::Type conditional_old_value) {
+      T old_tags = tags_.load(std::memory_order_relaxed);
+      while (true) {
+        // This operation is only performed if the condition is met.
+        auto old_value = TagBitField::decode(old_tags);
+        if (old_value != conditional_old_value) {
+          return old_value;
+        }
+        T new_tags = TagBitField::update(value_to_be_set, old_tags);
+        if (tags_.compare_exchange_weak(old_tags, new_tags,
+                                        std::memory_order_relaxed)) {
+          return value_to_be_set;
+        }
+        // [old_tags] was updated to it's current value.
+      }
+    }
+
+    template <class TagBitField>
     bool TryAcquire() {
       T mask = TagBitField::encode(true);
       T old_tags = tags_.fetch_or(mask, std::memory_order_relaxed);
@@ -426,7 +446,9 @@
 
 #if defined(HASH_IN_OBJECT_HEADER)
   uint32_t GetHeaderHash() const { return tags_.Read<HashTag>(); }
-  void SetHeaderHash(uint32_t h) { tags_.Update<HashTag>(h); }
+  uint32_t SetHeaderHashIfNotSet(uint32_t h) {
+    return tags_.UpdateConditional<HashTag>(h, /*conditional_old_value=*/0);
+  }
 #endif
 
   intptr_t HeapSize() const {
diff --git a/runtime/vm/source_report_test.cc b/runtime/vm/source_report_test.cc
index f7314c0..ad8c574 100644
--- a/runtime/vm/source_report_test.cc
+++ b/runtime/vm/source_report_test.cc
@@ -27,7 +27,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_NoCalls) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "main() {\n"
       "}";
@@ -40,7 +42,9 @@
   SourceReport report(SourceReport::kCoverage);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("libraries", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("libraries", json_str, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":"
 
@@ -55,7 +59,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_SimpleCall) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {}\n"
       "helper1() {}\n"
@@ -76,7 +82,9 @@
   SourceReport report(SourceReport::kCoverage);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
@@ -99,7 +107,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_ForceCompile) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {}\n"
       "helper1() {}\n"
@@ -120,7 +130,9 @@
   SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
 
   EXPECT_STREQ(
@@ -145,7 +157,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_UnusedClass_NoForceCompile) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {}\n"
       "class Unused {\n"
@@ -164,7 +178,9 @@
   SourceReport report(SourceReport::kCoverage);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
@@ -187,7 +203,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_UnusedClass_ForceCompile) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {}\n"
       "class Unused {\n"
@@ -206,7 +224,9 @@
   SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
@@ -230,7 +250,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_UnusedClass_ForceCompileError) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {}\n"
       "class Unused {\n"
@@ -249,7 +271,9 @@
   SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
@@ -278,7 +302,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_NestedFunctions) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {\n"
       "  nestedHelper0() {}\n"
@@ -303,7 +329,9 @@
   SourceReport report(SourceReport::kCoverage);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
 
   EXPECT_STREQ(
@@ -334,7 +362,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_RestrictedRange) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {\n"
       "  nestedHelper0() {}\n"
@@ -362,7 +392,9 @@
   JSONStream js;
   // Restrict the report to only helper0 and it's nested functions.
   report.PrintJSON(&js, script, helper.token_pos(), helper.end_token_pos());
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
 
   EXPECT_STREQ(
@@ -464,7 +496,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_CallSites_SimpleCall) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {}\n"
       "helper1() {}\n"
@@ -481,7 +515,9 @@
   SourceReport report(SourceReport::kCallSites);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
@@ -513,7 +549,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_CallSites_PolymorphicCall) {
-  char buffer[4096];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 4096;
+  char buffer[kBufferSize];
   const char* kScript =
       "class Common {\n"
       "  func() {}\n"
@@ -543,7 +581,9 @@
   SourceReport report(SourceReport::kCallSites);
   JSONStream js;
   report.PrintJSON(&js, script, helper.token_pos(), helper.end_token_pos());
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
@@ -625,7 +665,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_MultipleReports) {
-  char buffer[2048];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 2048;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {}\n"
       "helper1() {}\n"
@@ -642,7 +684,9 @@
   SourceReport report(SourceReport::kCallSites | SourceReport::kCoverage);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
@@ -675,7 +719,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_PossibleBreakpoints_Simple) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "helper0() {}\n"
       "helper1() {}\n"
@@ -696,7 +742,9 @@
   SourceReport report(SourceReport::kPossibleBreakpoints);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
@@ -719,7 +767,9 @@
 }
 
 ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Issue35453_NoSuchMethod) {
-  char buffer[1024];
+  // WARNING: This MUST be big enough for the serialised JSON string.
+  const int kBufferSize = 1024;
+  char buffer[kBufferSize];
   const char* kScript =
       "class Foo {\n"
       "  void bar() {}\n"
@@ -740,7 +790,9 @@
   SourceReport report(SourceReport::kCoverage, SourceReport::kForceCompile);
   JSONStream js;
   report.PrintJSON(&js, script);
-  ElideJSONSubstring("classes", js.ToCString(), buffer);
+  const char* json_str = js.ToCString();
+  ASSERT(strlen(json_str) < kBufferSize);
+  ElideJSONSubstring("classes", json_str, buffer);
   ElideJSONSubstring("libraries", buffer, buffer);
   EXPECT_STREQ(
       "{\"type\":\"SourceReport\",\"ranges\":["
diff --git a/runtime/vm/unit_test.h b/runtime/vm/unit_test.h
index aa82e8d..025f8cd 100644
--- a/runtime/vm/unit_test.h
+++ b/runtime/vm/unit_test.h
@@ -690,6 +690,7 @@
 //
 //    out = "\"id\":\"\""
 //
+// WARNING: This function is not safe to use if `in` is bigger than `out`!
 void ElideJSONSubstring(const char* prefix, const char* in, char* out);
 
 template <typename T>
diff --git a/sdk/lib/_http/http_headers.dart b/sdk/lib/_http/http_headers.dart
index 7d87b88..77b9c22 100644
--- a/sdk/lib/_http/http_headers.dart
+++ b/sdk/lib/_http/http_headers.dart
@@ -503,8 +503,19 @@
     _mutable = false;
   }
 
-  void _build(BytesBuilder builder) {
+  void _build(BytesBuilder builder, {bool skipZeroContentLength = false}) {
+    // per https://tools.ietf.org/html/rfc7230#section-3.3.2
+    // A user agent SHOULD NOT send a
+    // Content-Length header field when the request message does not
+    // contain a payload body and the method semantics do not anticipate
+    // such a body.
+    String? ignoreHeader = _contentLength == 0 && skipZeroContentLength
+        ? HttpHeaders.contentLengthHeader
+        : null;
     _headers.forEach((String name, List<String> values) {
+      if (ignoreHeader == name) {
+        return;
+      }
       String originalName = _originalHeaderName(name);
       bool fold = _foldHeader(name);
       var nameData = originalName.codeUnits;
diff --git a/sdk/lib/_http/http_impl.dart b/sdk/lib/_http/http_impl.dart
index b61f273..6c7b768 100644
--- a/sdk/lib/_http/http_impl.dart
+++ b/sdk/lib/_http/http_impl.dart
@@ -1583,7 +1583,11 @@
     headers._finalize();
 
     // Write headers.
-    headers._build(buffer);
+    headers._build(buffer,
+        skipZeroContentLength: method == "CONNECT" ||
+            method == "DELETE" ||
+            method == "GET" ||
+            method == "HEAD");
     buffer.addByte(_CharCode.CR);
     buffer.addByte(_CharCode.LF);
     Uint8List headerBytes = buffer.takeBytes();
diff --git a/tests/standalone/io/http_content_length_test.dart b/tests/standalone/io/http_content_length_test.dart
index 15d6520..12e1e99 100644
--- a/tests/standalone/io/http_content_length_test.dart
+++ b/tests/standalone/io/http_content_length_test.dart
@@ -16,8 +16,8 @@
   int count = 0;
   HttpServer.bind("127.0.0.1", 0, backlog: totalConnections).then((server) {
     server.listen((HttpRequest request) {
-      Expect.equals("0", request.headers.value('content-length'));
-      Expect.equals(0, request.contentLength);
+      Expect.equals(null, request.headers.value('content-length'));
+      Expect.equals(-1, request.contentLength);
       var response = request.response;
       response.contentLength = 0;
       response.done.then((_) {
diff --git a/tests/standalone/io/http_detach_socket_test.dart b/tests/standalone/io/http_detach_socket_test.dart
index fdbc257..480c88d 100644
--- a/tests/standalone/io/http_detach_socket_test.dart
+++ b/tests/standalone/io/http_detach_socket_test.dart
@@ -110,14 +110,13 @@
       socket.listen((data) => body.write(new String.fromCharCodes(data)),
           onDone: () {
         List<String> lines = body.toString().split("\r\n");
-        Expect.equals(6, lines.length);
+        Expect.equals(5, lines.length);
         Expect.equals("GET / HTTP/1.1", lines[0]);
-        Expect.equals("", lines[4]);
-        Expect.equals("Some data", lines[5]);
-        lines.sort(); // Lines 1-3 becomes 3-5 in a fixed order.
+        Expect.equals("", lines[3]);
+        Expect.equals("Some data", lines[4]);
+        lines.sort(); // Lines 1-2 becomes 3-4 in a fixed order.
         Expect.equals("accept-encoding: gzip", lines[3]);
-        Expect.equals("content-length: 0", lines[4]);
-        Expect.equals("host: 127.0.0.1:${port}", lines[5]);
+        Expect.equals("host: 127.0.0.1:${port}", lines[4]);
         socket.close();
       });
       server.close();
diff --git a/tests/standalone_2/io/http_content_length_test.dart b/tests/standalone_2/io/http_content_length_test.dart
index 7b81398..708a7c1 100644
--- a/tests/standalone_2/io/http_content_length_test.dart
+++ b/tests/standalone_2/io/http_content_length_test.dart
@@ -18,8 +18,8 @@
   int count = 0;
   HttpServer.bind("127.0.0.1", 0, backlog: totalConnections).then((server) {
     server.listen((HttpRequest request) {
-      Expect.equals("0", request.headers.value('content-length'));
-      Expect.equals(0, request.contentLength);
+      Expect.equals(null, request.headers.value('content-length'));
+      Expect.equals(-1, request.contentLength);
       var response = request.response;
       response.contentLength = 0;
       response.done.then((_) {
diff --git a/tests/standalone_2/io/http_detach_socket_test.dart b/tests/standalone_2/io/http_detach_socket_test.dart
index bb33690..1613ace 100644
--- a/tests/standalone_2/io/http_detach_socket_test.dart
+++ b/tests/standalone_2/io/http_detach_socket_test.dart
@@ -112,14 +112,13 @@
       socket.listen((data) => body.write(new String.fromCharCodes(data)),
           onDone: () {
         List<String> lines = body.toString().split("\r\n");
-        Expect.equals(6, lines.length);
+        Expect.equals(5, lines.length);
         Expect.equals("GET / HTTP/1.1", lines[0]);
-        Expect.equals("", lines[4]);
-        Expect.equals("Some data", lines[5]);
-        lines.sort(); // Lines 1-3 becomes 3-5 in a fixed order.
+        Expect.equals("", lines[3]);
+        Expect.equals("Some data", lines[4]);
+        lines.sort(); // Lines 1-2 becomes 3-4 in a fixed order.
         Expect.equals("accept-encoding: gzip", lines[3]);
-        Expect.equals("content-length: 0", lines[4]);
-        Expect.equals("host: 127.0.0.1:${port}", lines[5]);
+        Expect.equals("host: 127.0.0.1:${port}", lines[4]);
         socket.close();
       });
       server.close();
diff --git a/tools/VERSION b/tools/VERSION
index 888f7d0..93fc29b 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 180
+PRERELEASE 181
 PRERELEASE_PATCH 0
\ No newline at end of file