Prepare the C++ interface for publication.

* Use PIMPL idiom in the C++ interface.
* Clean up interface for assembling and disassembling.
* Add validation into C++ interface.
* Add more tests for the C++ interface.
diff --git a/source/opt/build_module.cpp b/source/opt/build_module.cpp
index 5686275..d42c7f8 100644
--- a/source/opt/build_module.cpp
+++ b/source/opt/build_module.cpp
@@ -67,7 +67,7 @@
   SpvTools t(env);
   t.SetMessageConsumer(consumer);
   std::vector<uint32_t> binary;
-  if (t.Assemble(text, &binary) != SPV_SUCCESS) return nullptr;
+  if (!t.Assemble(text, &binary)) return nullptr;
   return BuildModule(env, consumer, binary);
 }
 
diff --git a/source/opt/libspirv.cpp b/source/opt/libspirv.cpp
index 2b7850b..b3f89f1 100644
--- a/source/opt/libspirv.cpp
+++ b/source/opt/libspirv.cpp
@@ -16,46 +16,58 @@
 
 #include "ir_loader.h"
 #include "make_unique.h"
+#include "message.h"
 #include "table.h"
 
 namespace spvtools {
 
+// Structs for holding the data members for SpvTools.
+struct SpvTools::Impl {
+  explicit Impl(spv_target_env env) : context(spvContextCreate(env)) {
+    // The default consumer in spv_context_t is a null consumer, which provides
+    // equivalent functionality (from the user's perspective) as a real consumer
+    // does nothing.
+  }
+  ~Impl() { spvContextDestroy(context); }
+
+  spv_context context;  // C interface context object.
+};
+
+SpvTools::SpvTools(spv_target_env env) : impl_(new Impl(env)) {}
+
+SpvTools::~SpvTools() {}
+
 void SpvTools::SetMessageConsumer(MessageConsumer consumer) {
-  SetContextMessageConsumer(context_, std::move(consumer));
+  SetContextMessageConsumer(impl_->context, std::move(consumer));
 }
 
-spv_result_t SpvTools::Assemble(const std::string& text,
-                                std::vector<uint32_t>* binary) {
+bool SpvTools::Assemble(const std::string& text,
+                        std::vector<uint32_t>* binary) const {
   spv_binary spvbinary = nullptr;
-  spv_diagnostic diagnostic = nullptr;
-
-  spv_result_t status = spvTextToBinary(context_, text.data(), text.size(),
-                                        &spvbinary, &diagnostic);
+  spv_result_t status = spvTextToBinary(impl_->context, text.data(),
+                                        text.size(), &spvbinary, nullptr);
   if (status == SPV_SUCCESS) {
     binary->assign(spvbinary->code, spvbinary->code + spvbinary->wordCount);
   }
-
-  spvDiagnosticDestroy(diagnostic);
   spvBinaryDestroy(spvbinary);
-
-  return status;
+  return status == SPV_SUCCESS;
 }
 
-spv_result_t SpvTools::Disassemble(const std::vector<uint32_t>& binary,
-                                   std::string* text, uint32_t options) {
+bool SpvTools::Disassemble(const std::vector<uint32_t>& binary,
+                           std::string* text, uint32_t options) const {
   spv_text spvtext = nullptr;
-  spv_diagnostic diagnostic = nullptr;
-
-  spv_result_t status = spvBinaryToText(context_, binary.data(), binary.size(),
-                                        options, &spvtext, &diagnostic);
+  spv_result_t status = spvBinaryToText(
+      impl_->context, binary.data(), binary.size(), options, &spvtext, nullptr);
   if (status == SPV_SUCCESS) {
     text->assign(spvtext->str, spvtext->str + spvtext->length);
   }
-
-  spvDiagnosticDestroy(diagnostic);
   spvTextDestroy(spvtext);
+  return status == SPV_SUCCESS;
+}
 
-  return status;
+bool SpvTools::Validate(const std::vector<uint32_t>& binary) const {
+  spv_const_binary_t b = {binary.data(), binary.size()};
+  return spvValidate(impl_->context, &b, nullptr) == SPV_SUCCESS;
 }
 
 }  // namespace spvtools
diff --git a/source/opt/libspirv.hpp b/source/opt/libspirv.hpp
index fed408a..63dde2f 100644
--- a/source/opt/libspirv.hpp
+++ b/source/opt/libspirv.hpp
@@ -15,6 +15,7 @@
 #ifndef SPIRV_TOOLS_LIBSPIRV_HPP_
 #define SPIRV_TOOLS_LIBSPIRV_HPP_
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -25,36 +26,58 @@
 
 // C++ interface for SPIRV-Tools functionalities. It wraps the context
 // (including target environment and the corresponding SPIR-V grammar) and
-// provides methods for assembling, disassembling, validating, and optimizing.
+// provides methods for assembling, disassembling, and validating.
 //
-// Instances of this class are thread-safe.
+// Instances of this class provide basic thread-safety guarantee.
 class SpvTools {
  public:
-  // Creates an instance targeting the given environment |env|.
-  SpvTools(spv_target_env env) : context_(spvContextCreate(env)) {}
+  enum {
+    // Default disassembling option used by Disassemble():
+    // * Avoid prefix comments from decoding the SPIR-V module header, and
+    // * Use friendly names for variables.
+    kDefaultDisassembleOption = SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+                                SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES
+  };
 
-  ~SpvTools() { spvContextDestroy(context_); }
+  // Constructs an instance targeting the given environment |env|.
+  //
+  // The constructed instance will have an empty message consumer, which just
+  // ignores all messages from the library. Use SetMessageConsumer() to supply
+  // one if messages are of concern.
+  explicit SpvTools(spv_target_env env);
 
-  // Sets the message consumer to the given |consumer|.
+  // Disables copy/move constructor/assignment operations.
+  SpvTools(const SpvTools&) = delete;
+  SpvTools(SpvTools&&) = delete;
+  SpvTools& operator=(const SpvTools&) = delete;
+  SpvTools& operator=(SpvTools&&) = delete;
+
+  // Destructs this instance.
+  ~SpvTools();
+
+  // Sets the message consumer to the given |consumer|. The |consumer| will be
+  // invoked once for each message communicated from the library.
   void SetMessageConsumer(MessageConsumer consumer);
 
   // Assembles the given assembly |text| and writes the result to |binary|.
-  // Returns SPV_SUCCESS on successful assembling.
-  spv_result_t Assemble(const std::string& text, std::vector<uint32_t>* binary);
+  // Returns true on successful assembling. |binary| will be kept untouched if
+  // assembling is unsuccessful.
+  bool Assemble(const std::string& text, std::vector<uint32_t>* binary) const;
 
-  // Disassembles the given SPIR-V |binary| with the given options and returns
-  // the assembly. By default the options are set to generate assembly with
-  // friendly variable names and no SPIR-V assembly header. Returns SPV_SUCCESS
-  // on successful disassembling.
-  spv_result_t Disassemble(
-      const std::vector<uint32_t>& binary, std::string* text,
-      uint32_t options = SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
-                         SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  // Disassembles the given SPIR-V |binary| with the given |options| and writes
+  // the assembly to |text|. Returns ture on successful disassembling. |text|
+  // will be kept untouched if diassembling is unsuccessful.
+  bool Disassemble(const std::vector<uint32_t>& binary, std::string* text,
+                   uint32_t options = kDefaultDisassembleOption) const;
+
+  // Validates the given SPIR-V |binary|. Returns true if no issues are found.
+  // Otherwise, returns false and communicates issues via the message consumer
+  // registered.
+  bool Validate(const std::vector<uint32_t>& binary) const;
 
  private:
-  // Context for the current invocation. Thread-safety of this class depends on
-  // the constness of this field.
-  spv_context context_;
+  struct Impl;  // Opaque struct for holding the data fields used by this class.
+  std::unique_ptr<Impl> impl_;  // Unique pointer to implementation data.
 };
 
 }  // namespace spvtools
diff --git a/test/cpp_interface.cpp b/test/cpp_interface.cpp
index 104958d..a585951 100644
--- a/test/cpp_interface.cpp
+++ b/test/cpp_interface.cpp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include "opt/libspirv.hpp"
@@ -20,40 +21,143 @@
 namespace {
 
 using namespace spvtools;
+using ::testing::ContainerEq;
 
 TEST(CppInterface, SuccessfulRoundTrip) {
   const std::string input_text = "%2 = OpSizeOf %1 %3\n";
   SpvTools t(SPV_ENV_UNIVERSAL_1_1);
 
   std::vector<uint32_t> binary;
-  EXPECT_EQ(SPV_SUCCESS, t.Assemble(input_text, &binary));
+  EXPECT_TRUE(t.Assemble(input_text, &binary));
   EXPECT_TRUE(binary.size() > 5u);
   EXPECT_EQ(SpvMagicNumber, binary[0]);
   EXPECT_EQ(SpvVersion, binary[1]);
 
+  // This cannot pass validation since %1 is not defined.
+  t.SetMessageConsumer([](MessageLevel level, const char* source,
+                          const spv_position_t& position, const char* message) {
+    EXPECT_EQ(MessageLevel::Error, level);
+    EXPECT_STREQ("", source);
+    EXPECT_EQ(0u, position.line);
+    EXPECT_EQ(0u, position.column);
+    EXPECT_EQ(1u, position.index);
+    EXPECT_STREQ("ID 1 has not been defined", message);
+  });
+  EXPECT_FALSE(t.Validate(binary));
+
   std::string output_text;
-  EXPECT_EQ(SPV_SUCCESS, t.Disassemble(binary, &output_text));
+  EXPECT_TRUE(t.Disassemble(binary, &output_text));
   EXPECT_EQ(input_text, output_text);
 }
 
+TEST(CppInterface, AssembleEmptyModule) {
+  std::vector<uint32_t> binary(10, 42);
+  SpvTools t(SPV_ENV_UNIVERSAL_1_1);
+  EXPECT_TRUE(t.Assemble("", &binary));
+  // We only have the header.
+  EXPECT_EQ(5u, binary.size());
+  EXPECT_EQ(SpvMagicNumber, binary[0]);
+  EXPECT_EQ(SpvVersion, binary[1]);
+}
+
 TEST(CppInterface, AssembleWithWrongTargetEnv) {
   const std::string input_text = "%r = OpSizeOf %type %pointer";
   SpvTools t(SPV_ENV_UNIVERSAL_1_0);
+  int invocation_count = 0;
+  t.SetMessageConsumer(
+      [&invocation_count](MessageLevel level, const char* source,
+                          const spv_position_t& position, const char* message) {
+        ++invocation_count;
+        EXPECT_EQ(MessageLevel::Error, level);
+        EXPECT_STREQ("", source);
+        EXPECT_EQ(0u, position.line);
+        EXPECT_EQ(5u, position.column);
+        EXPECT_EQ(5u, position.index);
+        EXPECT_STREQ("Invalid Opcode name 'OpSizeOf'", message);
+      });
 
-  std::vector<uint32_t> binary;
-  EXPECT_EQ(SPV_ERROR_INVALID_TEXT, t.Assemble(input_text, &binary));
+  std::vector<uint32_t> binary = {42, 42};
+  EXPECT_FALSE(t.Assemble(input_text, &binary));
+  EXPECT_THAT(binary, ContainerEq(std::vector<uint32_t>{42, 42}));
+  EXPECT_EQ(1, invocation_count);
+}
+
+TEST(CppInterface, DisassembleEmptyModule) {
+  std::string text(10, 'x');
+  SpvTools t(SPV_ENV_UNIVERSAL_1_1);
+  int invocation_count = 0;
+  t.SetMessageConsumer(
+      [&invocation_count](MessageLevel level, const char* source,
+                          const spv_position_t& position, const char* message) {
+        ++invocation_count;
+        EXPECT_EQ(MessageLevel::Error, level);
+        EXPECT_STREQ("", source);
+        EXPECT_EQ(0u, position.line);
+        EXPECT_EQ(0u, position.column);
+        EXPECT_EQ(0u, position.index);
+        EXPECT_STREQ("Missing module.", message);
+      });
+  EXPECT_FALSE(t.Disassemble({}, &text));
+  EXPECT_EQ("xxxxxxxxxx", text);  // The original string is unmodified.
+  EXPECT_EQ(1, invocation_count);
 }
 
 TEST(CppInterface, DisassembleWithWrongTargetEnv) {
   const std::string input_text = "%r = OpSizeOf %type %pointer";
   SpvTools t11(SPV_ENV_UNIVERSAL_1_1);
   SpvTools t10(SPV_ENV_UNIVERSAL_1_0);
+  int invocation_count = 0;
+  t10.SetMessageConsumer(
+      [&invocation_count](MessageLevel level, const char* source,
+                          const spv_position_t& position, const char* message) {
+        ++invocation_count;
+        EXPECT_EQ(MessageLevel::Error, level);
+        EXPECT_STREQ("", source);
+        EXPECT_EQ(0u, position.line);
+        EXPECT_EQ(0u, position.column);
+        EXPECT_EQ(5u, position.index);
+        EXPECT_STREQ("Invalid opcode: 321", message);
+      });
 
   std::vector<uint32_t> binary;
-  EXPECT_EQ(SPV_SUCCESS, t11.Assemble(input_text, &binary));
+  EXPECT_TRUE(t11.Assemble(input_text, &binary));
 
-  std::string output_text;
-  EXPECT_EQ(SPV_ERROR_INVALID_BINARY, t10.Disassemble(binary, &output_text));
+  std::string output_text(10, 'x');
+  EXPECT_FALSE(t10.Disassemble(binary, &output_text));
+  EXPECT_EQ("xxxxxxxxxx", output_text);  // The original string is unmodified.
+}
+
+TEST(CppInterface, SuccessfulValidation) {
+  const std::string input_text =
+      "OpCapability Shader\nOpMemoryModel Logical GLSL450";
+  SpvTools t(SPV_ENV_UNIVERSAL_1_1);
+  int invocation_count = 0;
+  t.SetMessageConsumer(
+      [&invocation_count](MessageLevel, const char*, const spv_position_t&,
+                          const char*) { ++invocation_count; });
+
+  std::vector<uint32_t> binary;
+  EXPECT_TRUE(t.Assemble(input_text, &binary));
+  EXPECT_TRUE(t.Validate(binary));
+  EXPECT_EQ(0, invocation_count);
+}
+
+TEST(CppInterface, ValidateEmptyModule) {
+  SpvTools t(SPV_ENV_UNIVERSAL_1_1);
+  int invocation_count = 0;
+  t.SetMessageConsumer(
+      [&invocation_count](MessageLevel level, const char* source,
+                          const spv_position_t& position, const char* message) {
+        ++invocation_count;
+        EXPECT_EQ(MessageLevel::Error, level);
+        EXPECT_STREQ("", source);
+        EXPECT_EQ(0u, position.line);
+        EXPECT_EQ(0u, position.column);
+        EXPECT_EQ(0u, position.index);
+        EXPECT_STREQ("Invalid SPIR-V magic number.", message);
+      });
+  EXPECT_FALSE(t.Validate({}));
+  EXPECT_EQ(1, invocation_count);
 }
 
 }  // anonymous namespace
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index df41ad5..9cc831c 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -63,7 +63,7 @@
     std::vector<uint32_t> binary;
     module->ToBinary(&binary, skip_nop);
     std::string optimized;
-    EXPECT_EQ(SPV_SUCCESS, tools_.Disassemble(binary, &optimized))
+    EXPECT_TRUE(tools_.Disassemble(binary, &optimized))
         << "Disassembling failed for shader:\n"
         << original << std::endl;
     return std::make_tuple(optimized, modified);
@@ -122,7 +122,7 @@
     module->ToBinary(&binary, /* skip_nop = */ false);
 
     std::string optimized;
-    EXPECT_EQ(SPV_SUCCESS, tools_.Disassemble(binary, &optimized));
+    EXPECT_TRUE(tools_.Disassemble(binary, &optimized));
     EXPECT_EQ(expected, optimized);
   }
 
diff --git a/test/opt/test_ir_loader.cpp b/test/opt/test_ir_loader.cpp
index 27860c7..b989380 100644
--- a/test/opt/test_ir_loader.cpp
+++ b/test/opt/test_ir_loader.cpp
@@ -32,7 +32,7 @@
   module->ToBinary(&binary, /* skip_nop = */ false);
 
   std::string disassembled_text;
-  EXPECT_EQ(SPV_SUCCESS, t.Disassemble(binary, &disassembled_text));
+  EXPECT_TRUE(t.Disassemble(binary, &disassembled_text));
   EXPECT_EQ(text, disassembled_text);
 }
 
@@ -224,7 +224,7 @@
   module->ToBinary(&binary, /* skip_nop = */ false);
 
   std::string disassembled_text;
-  EXPECT_EQ(SPV_SUCCESS, t.Disassemble(binary, &disassembled_text));
+  EXPECT_TRUE(t.Disassemble(binary, &disassembled_text));
   EXPECT_EQ(text, disassembled_text);
 }