[mediacodec] Fix CodecAdapterSW shutdown order

Sometimes a member held references to a member that destructed earlier.

TEST: fx run-test codec_adapter_sw_test
Change-Id: I4ad8dc14d562aac45aa7e53963b3a7d4f6c33a43
diff --git a/garnet/bin/media/codecs/sw/codec_adapter_sw.h b/garnet/bin/media/codecs/sw/codec_adapter_sw.h
index 263642e..4c21cb2 100644
--- a/garnet/bin/media/codecs/sw/codec_adapter_sw.h
+++ b/garnet/bin/media/codecs/sw/codec_adapter_sw.h
@@ -336,8 +336,8 @@
   BlockingMpscQueue<CodecInputItem> input_queue_;
   BlockingMpscQueue<CodecPacket*> free_output_packets_;
 
-  std::map<CodecPacket*, LocalOutput> in_use_by_client_ FXL_GUARDED_BY(lock_);
   BufferPool output_buffer_pool_;
+  std::map<CodecPacket*, LocalOutput> in_use_by_client_ FXL_GUARDED_BY(lock_);
 
   // Buffers the client has added but that we cannot use until configuration is
   // complete.
diff --git a/garnet/bin/media/codecs/test/BUILD.gn b/garnet/bin/media/codecs/test/BUILD.gn
index 1998b64..2b9ddc4 100644
--- a/garnet/bin/media/codecs/test/BUILD.gn
+++ b/garnet/bin/media/codecs/test/BUILD.gn
@@ -10,6 +10,7 @@
   testonly = true
   deps = [
     ":chunk_input_stream_tests",
+    ":codec_adapter_sw_test",
     ":mpsc_queue_tests",
     ":output_sink_tests",
     ":raw_frames",
@@ -20,6 +21,33 @@
   ]
 }
 
+executable("codec_adapter_sw_test_bin") {
+  output_name = "codec_adapter_sw_test"
+  testonly = true
+
+  sources = [
+    "codec_adapter_sw_test.cc",
+  ]
+
+  deps = [
+    "//garnet/bin/media/codecs/sw:codec_adapter_sw",
+    "//src/lib/fxl/test:gtest_main",
+  ]
+}
+
+test_package("codec_adapter_sw_test") {
+  tests = [
+    {
+      name = "codec_adapter_sw_test"
+      environments = basic_envs
+    },
+  ]
+
+  deps = [
+    ":codec_adapter_sw_test_bin",
+  ]
+}
+
 source_set("raw_frames") {
   testonly = true
 
diff --git a/garnet/bin/media/codecs/test/codec_adapter_sw_test.cc b/garnet/bin/media/codecs/test/codec_adapter_sw_test.cc
new file mode 100644
index 0000000..127a73ea
--- /dev/null
+++ b/garnet/bin/media/codecs/test/codec_adapter_sw_test.cc
@@ -0,0 +1,63 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "../sw/codec_adapter_sw.h"
+
+#include <lib/fit/defer.h>
+#include <lib/fit/function.h>
+
+#include "gtest/gtest.h"
+
+class CodecAdapterSWDummy
+    : public CodecAdapterSW<fit::deferred_action<fit::closure>> {
+ public:
+  CodecAdapterSWDummy(std::mutex& lock)
+      : CodecAdapterSW(lock,
+                       /* bad ptr to pass non-null assert */ reinterpret_cast<
+                           CodecAdapterEvents*>(0xaa)) {}
+
+  // Much like the real fit::defer(s) in in_use_by_client_, the fit::defer we're
+  // putting in in_use_by_client_ touches output_buffer_pool_ in a way that'll
+  // crash if output_buffer_pool_ is already destructed.
+  void EntangleClientMapAndBufferPoolDestructors() {
+    std::lock_guard<std::mutex> lock(lock_);
+    fit::closure deferred = [this]() {
+      // This call will crash if output_buffer_pool_ has already been
+      // destructed, much like the real-world fit::defer(s) that would be in
+      // in_use_by_client_ if we delete CodecAdapterSW with stuff in flight.
+      output_buffer_pool_.has_buffers_in_use();
+    };
+    in_use_by_client_[nullptr] = fit::defer(std::move(deferred));
+  }
+
+  fuchsia::sysmem::BufferCollectionConstraints
+  CoreCodecGetBufferCollectionConstraints(
+      CodecPort port,
+      const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
+      const fuchsia::media::StreamBufferPartialSettings& partial_settings)
+      override {
+    return fuchsia::sysmem::BufferCollectionConstraints();
+  }
+
+  void CoreCodecSetBufferCollectionInfo(
+      CodecPort port,
+      const fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info)
+      override {}
+
+ protected:
+  virtual void ProcessInputLoop() override {}
+
+  virtual void CleanUpAfterStream() override {}
+
+  virtual std::pair<fuchsia::media::FormatDetails, size_t> OutputFormatDetails()
+      override {
+    return {fuchsia::media::FormatDetails(), 0};
+  }
+};
+
+TEST(CodecAdapterSW, DoesNotCrashOnDestruction) {
+  // To pass, this test must not crash.
+  std::mutex lock;
+  auto under_test = CodecAdapterSWDummy(lock);
+}
diff --git a/garnet/bin/media/codecs/test/meta/codec_adapter_sw_test.cmx b/garnet/bin/media/codecs/test/meta/codec_adapter_sw_test.cmx
new file mode 100644
index 0000000..8bcb07f
--- /dev/null
+++ b/garnet/bin/media/codecs/test/meta/codec_adapter_sw_test.cmx
@@ -0,0 +1,9 @@
+{
+    "program": {
+        "binary": "test/codec_adapter_sw_test"
+    },
+    "sandbox": {
+        "features": [],
+        "services": []
+    }
+}