Use AutoRestorer to scope script_node_array_ in CFXJSE_Engine

Introduce CFXJSE_Engine::ScriptNodeArrayScope to manage the lifetime
and restoration of script_node_array_ using AutoRestorer. This replaces
the manual pair of SetNodesOfRunScript(&refNodes) and
SetNodesOfRunScript(nullptr).

Fix two issues: Restoring to nullptr in all cases was likely a bug
for nested calculate scripts. Failure to set to nullptr upon entry
in the non-calculate cases was likely a bug allowing the nested script
to pollute the caller's ref-nodes.

Using an RAII scope ensures that nested script invocations restore the
outer script's node array upon completion and prevents leaving dangling
pointers if an early return occurs.

Change-Id: Ia405f3f6a0457e2b30850ddc9efe3bbd15a4b478
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/162130
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fxjs/xfa/cfxjse_engine.cpp b/fxjs/xfa/cfxjse_engine.cpp
index 6064507..5981872 100644
--- a/fxjs/xfa/cfxjse_engine.cpp
+++ b/fxjs/xfa/cfxjse_engine.cpp
@@ -191,6 +191,13 @@
   engine_->event_param_ = prev_event_param_;
 }
 
+CFXJSE_Engine::ScriptNodeArrayScope::ScriptNodeArrayScope(
+    CFXJSE_Engine* engine,
+    std::vector<cppgc::Persistent<CXFA_Node>>* array)
+    : restorer_(&engine->script_node_array_, array) {}
+
+CFXJSE_Engine::ScriptNodeArrayScope::~ScriptNodeArrayScope() = default;
+
 // The runtime belongs to the form fill environment, which may depart before
 // this object does, so look it up rather than holding it.
 CJS_Runtime* CFXJSE_Engine::GetCJSRuntime() const {
@@ -951,11 +958,6 @@
   return NewNormalXFAObject(pObject);
 }
 
-void CFXJSE_Engine::SetNodesOfRunScript(
-    std::vector<cppgc::Persistent<CXFA_Node>>* pArray) {
-  script_node_array_ = pArray;
-}
-
 void CFXJSE_Engine::AddNodesOfRunScript(CXFA_Node* pNode) {
   if (script_node_array_ &&
       !pdfium::Contains(*script_node_array_, pNode,
diff --git a/fxjs/xfa/cfxjse_engine.h b/fxjs/xfa/cfxjse_engine.h
index fafc729..4cccc25 100644
--- a/fxjs/xfa/cfxjse_engine.h
+++ b/fxjs/xfa/cfxjse_engine.h
@@ -12,6 +12,7 @@
 #include <type_traits>
 #include <vector>
 
+#include "core/fxcrt/autorestorer.h"
 #include "core/fxcrt/mask.h"
 #include "core/fxcrt/unowned_ptr.h"
 #include "fxjs/cfx_isolate_wrapper.h"
@@ -129,6 +130,19 @@
   };
   friend class EventParamScope;
 
+  class ScriptNodeArrayScope {
+    CPPGC_STACK_ALLOCATED();
+
+   public:
+    ScriptNodeArrayScope(CFXJSE_Engine* engine,
+                         std::vector<cppgc::Persistent<CXFA_Node>>* array);
+    ~ScriptNodeArrayScope();
+
+   private:
+    AutoRestorer<UnownedPtr<std::vector<cppgc::Persistent<CXFA_Node>>>>
+        restorer_;
+  };
+
   CXFA_Node* GetEventTarget() const { return target_; }
   CXFA_EventParam* GetEventParam() const { return event_param_; }
 
@@ -152,7 +166,6 @@
   CFXJSE_Class* GetJseNormalClass() const { return js_class_; }
   CXFA_Document* GetDocument() const { return document_.Get(); }
 
-  void SetNodesOfRunScript(std::vector<cppgc::Persistent<CXFA_Node>>* pArray);
   void AddNodesOfRunScript(CXFA_Node* pNode);
 
   void SetRunAtType(XFA_AttributeValue eRunAt) { run_at_type_ = eRunAt; }
diff --git a/xfa/fxfa/parser/cxfa_node.cpp b/xfa/fxfa/parser/cxfa_node.cpp
index f7e2475..7c0e3e4 100644
--- a/xfa/fxfa/parser/cxfa_node.cpp
+++ b/xfa/fxfa/parser/cxfa_node.cpp
@@ -2934,11 +2934,15 @@
       context, pEventParam->targeted_ ? this : nullptr, pEventParam);
   context->SetRunAtType(script->GetRunAt());
 
+  // Avoid wasted work in non-calculate events by not attaching a ref node
+  // array. Always attaching ref nodes would be functionally equivalent, but
+  // every node whose value is read during a non-calculate script would be
+  // pinned until ExecuteBoolScript() exits.
+  const bool needs_ref_nodes = pEventParam->type_ == XFA_EVENT_InitCalculate ||
+                               pEventParam->type_ == XFA_EVENT_Calculate;
   std::vector<cppgc::Persistent<CXFA_Node>> refNodes;
-  if (pEventParam->type_ == XFA_EVENT_InitCalculate ||
-      pEventParam->type_ == XFA_EVENT_Calculate) {
-    context->SetNodesOfRunScript(&refNodes);
-  }
+  CFXJSE_Engine::ScriptNodeArrayScope node_array_scope(
+      context, needs_ref_nodes ? &refNodes : nullptr);
 
   CFXJSE_Context::ExecutionResult exec_result;
   {
@@ -2988,7 +2992,6 @@
       }
     }
   }
-  context->SetNodesOfRunScript(nullptr);
 
   return {iRet, !value.IsEmpty() && fxv8::IsBoolean(value) &&
                     fxv8::ReentrantToBooleanHelper(isolate, value)};