Add CFXJSE_Value::IsEmpty() and use it in SetArray()

Because values have yet another possibility: they might be empty
and v8 will assert if we try to set an array element to an empty
value. Note that this is an entirely different state than being
undefined, or null, which it handles properly. In fact, newly
created CFXJSE_Values are IsEmpty().

Needed to fix a segv in a forthcoming CL.

Change-Id: I74f075ab3e483533e9c6ad81254e2b819553cdf8
Reviewed-on: https://pdfium-review.googlesource.com/c/48750
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fxjs/BUILD.gn b/fxjs/BUILD.gn
index e4435b3..706884b 100644
--- a/fxjs/BUILD.gn
+++ b/fxjs/BUILD.gn
@@ -478,7 +478,10 @@
     pdfium_root_dir = "../"
 
     if (pdf_enable_xfa) {
-      sources += [ "xfa/cfxjse_formcalc_context_embeddertest.cpp" ]
+      sources += [
+        "xfa/cfxjse_formcalc_context_embeddertest.cpp",
+        "xfa/cfxjse_value_embeddertest.cpp",
+      ]
       deps += [ "..///xfa/fxfa" ]
     }
   }
diff --git a/fxjs/xfa/cfxjse_value.cpp b/fxjs/xfa/cfxjse_value.cpp
index e668c07..48bd923 100644
--- a/fxjs/xfa/cfxjse_value.cpp
+++ b/fxjs/xfa/cfxjse_value.cpp
@@ -103,6 +103,8 @@
   v8::Local<v8::Context> context = GetIsolate()->GetCurrentContext();
   uint32_t count = 0;
   for (auto& v : values) {
+    if (v->IsEmpty())
+      v->SetUndefined();
     hArrayObject
         ->Set(
             context, count++,
@@ -302,8 +304,12 @@
   return true;
 }
 
+bool CFXJSE_Value::IsEmpty() const {
+  return m_hValue.IsEmpty();
+}
+
 bool CFXJSE_Value::IsUndefined() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -313,7 +319,7 @@
 }
 
 bool CFXJSE_Value::IsNull() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -323,7 +329,7 @@
 }
 
 bool CFXJSE_Value::IsBoolean() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -333,7 +339,7 @@
 }
 
 bool CFXJSE_Value::IsString() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -343,7 +349,7 @@
 }
 
 bool CFXJSE_Value::IsNumber() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -353,7 +359,7 @@
 }
 
 bool CFXJSE_Value::IsInteger() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -363,7 +369,7 @@
 }
 
 bool CFXJSE_Value::IsObject() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -373,7 +379,7 @@
 }
 
 bool CFXJSE_Value::IsArray() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -383,7 +389,7 @@
 }
 
 bool CFXJSE_Value::IsFunction() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -393,7 +399,7 @@
 }
 
 bool CFXJSE_Value::IsDate() const {
-  if (m_hValue.IsEmpty())
+  if (IsEmpty())
     return false;
 
   CFXJSE_ScopeUtil_IsolateHandle scope(GetIsolate());
@@ -403,7 +409,7 @@
 }
 
 bool CFXJSE_Value::ToBoolean() const {
-  ASSERT(!m_hValue.IsEmpty());
+  ASSERT(!IsEmpty());
   CFXJSE_ScopeUtil_IsolateHandleRootContext scope(GetIsolate());
   v8::Local<v8::Value> hValue =
       v8::Local<v8::Value>::New(GetIsolate(), m_hValue);
@@ -411,7 +417,7 @@
 }
 
 float CFXJSE_Value::ToFloat() const {
-  ASSERT(!m_hValue.IsEmpty());
+  ASSERT(!IsEmpty());
   CFXJSE_ScopeUtil_IsolateHandleRootContext scope(GetIsolate());
   v8::Local<v8::Value> hValue =
       v8::Local<v8::Value>::New(GetIsolate(), m_hValue);
@@ -420,7 +426,7 @@
 }
 
 double CFXJSE_Value::ToDouble() const {
-  ASSERT(!m_hValue.IsEmpty());
+  ASSERT(!IsEmpty());
   CFXJSE_ScopeUtil_IsolateHandleRootContext scope(GetIsolate());
   v8::Local<v8::Value> hValue =
       v8::Local<v8::Value>::New(GetIsolate(), m_hValue);
@@ -428,7 +434,7 @@
 }
 
 int32_t CFXJSE_Value::ToInteger() const {
-  ASSERT(!m_hValue.IsEmpty());
+  ASSERT(!IsEmpty());
   CFXJSE_ScopeUtil_IsolateHandleRootContext scope(GetIsolate());
   v8::Local<v8::Value> hValue =
       v8::Local<v8::Value>::New(GetIsolate(), m_hValue);
@@ -437,7 +443,7 @@
 }
 
 ByteString CFXJSE_Value::ToString() const {
-  ASSERT(!m_hValue.IsEmpty());
+  ASSERT(!IsEmpty());
   CFXJSE_ScopeUtil_IsolateHandleRootContext scope(GetIsolate());
   v8::Local<v8::Value> hValue =
       v8::Local<v8::Value>::New(GetIsolate(), m_hValue);
diff --git a/fxjs/xfa/cfxjse_value.h b/fxjs/xfa/cfxjse_value.h
index 38a16e9..7bd605c 100644
--- a/fxjs/xfa/cfxjse_value.h
+++ b/fxjs/xfa/cfxjse_value.h
@@ -23,6 +23,7 @@
   explicit CFXJSE_Value(v8::Isolate* pIsolate);
   ~CFXJSE_Value();
 
+  bool IsEmpty() const;
   bool IsUndefined() const;
   bool IsNull() const;
   bool IsBoolean() const;
diff --git a/fxjs/xfa/cfxjse_value_embeddertest.cpp b/fxjs/xfa/cfxjse_value_embeddertest.cpp
new file mode 100644
index 0000000..988111e
--- /dev/null
+++ b/fxjs/xfa/cfxjse_value_embeddertest.cpp
@@ -0,0 +1,45 @@
+// Copyright 2019 PDFium 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 "fxjs/xfa/cfxjse_value.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "fxjs/xfa/cfxjse_engine.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/xfa_js_embedder_test.h"
+#include "third_party/base/ptr_util.h"
+
+class CFXJSE_ValueEmbedderTest : public XFAJSEmbedderTest {};
+
+TEST_F(CFXJSE_ValueEmbedderTest, Empty) {
+  ASSERT_TRUE(OpenDocument("simple_xfa.pdf"));
+
+  auto pValue = pdfium::MakeUnique<CFXJSE_Value>(GetIsolate());
+  EXPECT_TRUE(pValue->IsEmpty());
+  EXPECT_FALSE(pValue->IsUndefined());
+  EXPECT_FALSE(pValue->IsNull());
+  EXPECT_FALSE(pValue->IsBoolean());
+  EXPECT_FALSE(pValue->IsString());
+  EXPECT_FALSE(pValue->IsNumber());
+  EXPECT_FALSE(pValue->IsObject());
+  EXPECT_FALSE(pValue->IsArray());
+  EXPECT_FALSE(pValue->IsFunction());
+  EXPECT_FALSE(pValue->IsDate());
+}
+
+TEST_F(CFXJSE_ValueEmbedderTest, EmptyArrayInsert) {
+  ASSERT_TRUE(OpenDocument("simple_xfa.pdf"));
+
+  // Test inserting empty values into arrays.
+  auto pValue = pdfium::MakeUnique<CFXJSE_Value>(GetIsolate());
+  std::vector<std::unique_ptr<CFXJSE_Value>> vec;
+  vec.push_back(std::move(pValue));
+
+  CFXJSE_Value array(GetIsolate());
+  array.SetArray(vec);
+  EXPECT_TRUE(array.IsArray());
+}