MINIFICPP-1394 - Use stack allocated buffer for first event api call

Signed-off-by: Arpad Boda <aboda@apache.org>

This closes #928
diff --git a/extensions/windows-event-log/ConsumeWindowsEventLog.cpp b/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
index 29cb131..db0f4b1 100644
--- a/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
+++ b/extensions/windows-event-log/ConsumeWindowsEventLog.cpp
@@ -39,6 +39,7 @@
 #include "core/ProcessContext.h"
 #include "core/ProcessSession.h"
 #include "Bookmark.h"
+#include "utils/Deleters.h"
 
 #include "utils/gsl.h"
 
@@ -514,30 +515,36 @@
 
 bool ConsumeWindowsEventLog::createEventRender(EVT_HANDLE hEvent, EventRender& eventRender) {
   logger_->log_trace("Rendering an event");
-  DWORD size = 0;
+  WCHAR stackBuffer[4096];
+  DWORD size = sizeof(stackBuffer);
+  using Deleter = utils::StackAwareDeleter<WCHAR, utils::FreeDeleter>;
+  std::unique_ptr<WCHAR, Deleter> buf{stackBuffer, Deleter{ stackBuffer }};
+
   DWORD used = 0;
   DWORD propertyCount = 0;
-  EvtRender(NULL, hEvent, EvtRenderEventXml, size, 0, &used, &propertyCount);
-  if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) {
-    LOG_LAST_ERROR(EvtRender);
-    return false;
+  if (!EvtRender(NULL, hEvent, EvtRenderEventXml, size, buf.get(), &used, &propertyCount)) {
+    if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) {
+      LOG_LAST_ERROR(EvtRender);
+      return false;
+    }
+    if (used > maxBufferSize_) {
+      logger_->log_error("Dropping event because it couldn't be rendered within %" PRIu64 " bytes.", maxBufferSize_);
+      return false;
+    }
+    size = used;
+    buf.reset((LPWSTR)malloc(size));
+    if (!buf) {
+      return false;
+    }
+    if (!EvtRender(NULL, hEvent, EvtRenderEventXml, size, buf.get(), &used, &propertyCount)) {
+      LOG_LAST_ERROR(EvtRender);
+      return false;
+    }
   }
 
-  if (used > maxBufferSize_) {
-    logger_->log_error("Dropping event because it couldn't be rendered within %" PRIu64 " bytes.", maxBufferSize_);
-    return false;
-  }
+  logger_->log_debug("Event rendered with size %" PRIu32 ". Performing doc traversing...", used);
 
-  size = used;
-  std::vector<wchar_t> buf(size / 2 + 1);
-  if (!EvtRender(NULL, hEvent, EvtRenderEventXml, size, &buf[0], &used, &propertyCount)) {
-    LOG_LAST_ERROR(EvtRender);
-    return false;
-  }
-
-  logger_->log_debug("Event rendered with size %" PRIu32 ". Performing doc traversing...", size);
-
-  std::string xml = wel::to_string(&buf[0]);
+  std::string xml = wel::to_string(buf.get());
 
   pugi::xml_document doc;
   pugi::xml_parse_result result = doc.load_string(xml.c_str());
diff --git a/extensions/windows-event-log/wel/WindowsEventLog.cpp b/extensions/windows-event-log/wel/WindowsEventLog.cpp
index e0a8930..8d1334b 100644
--- a/extensions/windows-event-log/wel/WindowsEventLog.cpp
+++ b/extensions/windows-event-log/wel/WindowsEventLog.cpp
@@ -31,10 +31,12 @@
 
 void WindowsEventLogMetadataImpl::renderMetadata() {
   DWORD status = ERROR_SUCCESS;
-  DWORD dwBufferSize = 0;
+  EVT_VARIANT stackBuffer[4096];
+  DWORD dwBufferSize = sizeof(stackBuffer);
+  using Deleter = utils::StackAwareDeleter<EVT_VARIANT, utils::FreeDeleter>;
+  std::unique_ptr<EVT_VARIANT, Deleter> rendered_values{ stackBuffer, Deleter{stackBuffer} };
   DWORD dwBufferUsed = 0;
   DWORD dwPropertyCount = 0;
-  std::unique_ptr< EVT_VARIANT, utils::FreeDeleter> rendered_values;
 
   auto context = EvtCreateRenderContext(0, NULL, EvtRenderContextSystem);
   if (context == NULL) {
@@ -43,13 +45,14 @@
   const auto contextGuard = gsl::finally([&context](){
     EvtClose(context);
   });
-  if (!EvtRender(context, event_ptr_, EvtRenderEventValues, dwBufferSize, nullptr, &dwBufferUsed, &dwPropertyCount)) {
+  if (!EvtRender(context, event_ptr_, EvtRenderEventValues, dwBufferSize, rendered_values.get(), &dwBufferUsed, &dwPropertyCount)) {
     if (ERROR_INSUFFICIENT_BUFFER == (status = GetLastError())) {
       dwBufferSize = dwBufferUsed;
-      rendered_values = std::unique_ptr<EVT_VARIANT, utils::FreeDeleter>((PEVT_VARIANT)(malloc(dwBufferSize)));
-      if (rendered_values) {
-        EvtRender(context, event_ptr_, EvtRenderEventValues, dwBufferSize, rendered_values.get(), &dwBufferUsed, &dwPropertyCount);
+      rendered_values.reset((PEVT_VARIANT)(malloc(dwBufferSize)));
+      if (!rendered_values) {
+        return;
       }
+      EvtRender(context, event_ptr_, EvtRenderEventValues, dwBufferSize, rendered_values.get(), &dwBufferUsed, &dwPropertyCount);
     }
     else {
       return;
@@ -118,9 +121,11 @@
 }
 
 std::string WindowsEventLogMetadataImpl::getEventData(EVT_FORMAT_MESSAGE_FLAGS flags) const {
-  LPWSTR string_buffer = NULL;
-  DWORD string_buffer_size = 0;
-  DWORD string_buffer_used = 0;
+  WCHAR stack_buffer[4096];
+  DWORD num_chars_in_buffer = sizeof(stack_buffer) / sizeof(stack_buffer[0]);
+  using Deleter = utils::StackAwareDeleter<WCHAR, utils::FreeDeleter>;
+  std::unique_ptr<WCHAR, Deleter> buffer{ stack_buffer, Deleter{stack_buffer} };
+  DWORD num_chars_used = 0;
   DWORD result = 0;
 
   std::string event_data;
@@ -129,56 +134,56 @@
     return event_data;
   }
 
-  if (!EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, NULL, flags, string_buffer_size, string_buffer, &string_buffer_used)) {
+
+  if (!EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, NULL, flags, num_chars_in_buffer, buffer.get(), &num_chars_used)) {
     result = GetLastError();
     if (ERROR_INSUFFICIENT_BUFFER == result) {
-      string_buffer_size = string_buffer_used;
+      num_chars_in_buffer = num_chars_used;
 
-      string_buffer = (LPWSTR) malloc(string_buffer_size * sizeof(WCHAR));
-
-      if (string_buffer) {
-
-        if ((EvtFormatMessageKeyword == flags))
-          string_buffer[string_buffer_size - 1] = L'\0';
-
-        EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, NULL, flags, string_buffer_size, string_buffer, &string_buffer_used);
-        if ((EvtFormatMessageKeyword == flags))
-          string_buffer[string_buffer_used - 1] = L'\0';
-        std::wstring str(string_buffer);
-        event_data = std::string(str.begin(), str.end());
-        free(string_buffer);
+      buffer.reset((LPWSTR) malloc(num_chars_in_buffer * sizeof(WCHAR)));
+      if (!buffer) {
+        return event_data;
       }
+
+      EvtFormatMessage(metadata_ptr_, event_ptr_, 0, 0, NULL, flags, num_chars_in_buffer, buffer.get(), &num_chars_used);
     }
   }
+  if (EvtFormatMessageKeyword == flags) {
+    buffer.get()[num_chars_used - 1] = L'\0';
+  }
+  std::wstring str(buffer.get());
+  event_data = std::string(str.begin(), str.end());
   return event_data;
 }
 
 std::string WindowsEventLogHandler::getEventMessage(EVT_HANDLE eventHandle) const {
   std::string returnValue;
-  std::unique_ptr<WCHAR, utils::FreeDeleter> pBuffer;
-  DWORD dwBufferSize = 0;
-  DWORD dwBufferUsed = 0;
+  WCHAR stack_buffer[4096];
+  DWORD num_chars_in_buffer = sizeof(stack_buffer) / sizeof(stack_buffer[0]);
+  using Deleter = utils::StackAwareDeleter<WCHAR, utils::FreeDeleter>;
+  std::unique_ptr<WCHAR, Deleter> buffer{ stack_buffer, Deleter{stack_buffer} };
+  DWORD num_chars_used = 0;
   DWORD status = 0;
 
-  EvtFormatMessage(metadata_provider_, eventHandle, 0, 0, NULL, EvtFormatMessageEvent, dwBufferSize, pBuffer.get(), &dwBufferUsed);
-  if (dwBufferUsed == 0) {
+  EvtFormatMessage(metadata_provider_, eventHandle, 0, 0, NULL, EvtFormatMessageEvent, num_chars_in_buffer, buffer.get(), &num_chars_used);
+  if (num_chars_used == 0) {
     return returnValue;
   }
 
   //  we need to get the size of the buffer
   status = GetLastError();
   if (ERROR_INSUFFICIENT_BUFFER == status) {
-    dwBufferSize = dwBufferUsed;
+    num_chars_in_buffer = num_chars_used;
 
     /* All C++ examples use malloc and even HeapAlloc in some cases. To avoid any problems ( with EvtFormatMessage calling
       free for example ) we will continue to use malloc and use a custom deleter with unique_ptr.
     '*/
-    pBuffer = std::unique_ptr<WCHAR, utils::FreeDeleter>((LPWSTR)malloc(dwBufferSize * sizeof(WCHAR)));
-    if (!pBuffer) {
+    buffer.reset((LPWSTR)malloc(num_chars_in_buffer * sizeof(WCHAR)));
+    if (!buffer) {
       return returnValue;
     }
 
-    EvtFormatMessage(metadata_provider_, eventHandle, 0, 0, NULL, EvtFormatMessageEvent, dwBufferSize, pBuffer.get(), &dwBufferUsed);
+    EvtFormatMessage(metadata_provider_, eventHandle, 0, 0, NULL, EvtFormatMessageEvent, num_chars_in_buffer, buffer.get(), &num_chars_used);
   }
 
   if (ERROR_EVT_MESSAGE_NOT_FOUND == status || ERROR_EVT_MESSAGE_ID_NOT_FOUND == status) {
@@ -186,7 +191,7 @@
   }
 
   // convert wstring to std::string
-  return to_string(pBuffer.get());
+  return to_string(buffer.get());
 }
 
 void WindowsEventLogHeader::setDelimiter(const std::string &delim) {
diff --git a/libminifi/include/utils/Deleters.h b/libminifi/include/utils/Deleters.h
index e694475..9ae11e2 100644
--- a/libminifi/include/utils/Deleters.h
+++ b/libminifi/include/utils/Deleters.h
@@ -26,6 +26,7 @@
 #include <netdb.h>
 #include <ifaddrs.h>
 #endif /* WIN32 */
+#include <utility>
 
 namespace org {
 namespace apache {
@@ -40,6 +41,31 @@
   }
 };
 
+/**
+ * Allows smart pointers to store a pointer both
+ * to the stack and the heap while ensuring selective
+ * destruction of only heap allocated objects.
+ * @tparam T type of the object
+ * @tparam D type of the deleter used for heap instances
+ */
+template<typename T, typename D>
+struct StackAwareDeleter {
+  template<typename ...Args>
+  explicit StackAwareDeleter(T* stack_instance, Args&& ...args)
+    : stack_instance_{stack_instance},
+      impl_{std::forward<Args>(args)...} {}
+
+  void operator()(T* const ptr) const noexcept(noexcept(impl_(ptr))) {
+    if (ptr != stack_instance_) {
+      impl_(ptr);
+    }
+  }
+
+ private:
+  T* stack_instance_;
+  D impl_;
+};
+
 struct addrinfo_deleter {
   void operator()(addrinfo* const p) const noexcept {
     freeaddrinfo(p);