Repository: microsoft/terminal (language: cpp)

Issue:
Empty "folder" entry in "newTabMenu" causes WT to be unable to start
### Windows Terminal version

1.17.3482.0

### Windows build number

_No response_

### Other Software

_No response_

### Steps to reproduce

1. In `settings.json`, update the `"newtabMenu"` setting to look like this:

```
    [
        {
            "type": "remainingProfiles"
        },
        {
            "type": "folder"
        }
    ],
```

2. Launch Terminal

### Expected Behavior

Either nothing happens, or Terminal gives me a warning

### Actual Behavior

I get an empty window and a system error dialog that says "Windows Terminal.exe has stopped working"


Discussion / hints:
Stack is in

```


0:000> k
 # Child-SP          RetAddr               Call Site
00 00000087`896fe1c0 00007ffc`97013795     Microsoft_Terminal_Settings_Model!winrt::impl::consume_Windows_Foundation_Collections_IVector<winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntry>,winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntry>::Size+0x20 [C:\a\_work\1\s\src\cascadia\TerminalSettingsModel\Generated Files\winrt\Windows.Foundation.Collections.h @ 201] 
01 (Inline Function) --------`--------     Microsoft_Terminal_Settings_Model!winrt::impl::get_end_iterator+0x9 [C:\a\_work\1\s\src\cascadia\TerminalSettingsModel\Generated Files\winrt\base.h @ 8591] 
02 (Inline Function) --------`--------     Microsoft_Terminal_Settings_Model!winrt::impl::consume_Windows_Foundation_Collections_IIterable<winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntry>,winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntry>::end+0x9 [C:\a\_work\1\s\src\cascadia\TerminalSettingsModel\Generated Files\winrt\Windows.Foundation.Collections.h @ 727] 
03 00000087`896fe200 00007ffc`970136a0     Microsoft_Terminal_Settings_Model!winrt::Microsoft::Terminal::Settings::Model::implementation::FolderEntry::Entries+0x9d [C:\a\_work\1\s\src\cascadia\TerminalSettingsModel\FolderEntry.cpp @ 67] 
04 00000087`896fe2d0 00007ffc`a5de056c     Microsoft_Terminal_Settings_Model!winrt::impl::produce<winrt::Microsoft::Terminal::Settings::Model::implementation::FolderEntry,winrt::Microsoft::Terminal::Settings::Model::IFolderEntry>::Entries+0x24 [C:\a\_work\1\s\src\cascadia\TerminalSettingsModel\Generated Files\winrt\Microsoft.Terminal.Settings.Model.h @ 6015] 
05 00000087`896fe300 00007ffc`a5e03bfa     TerminalApp!winrt::impl::consume_Microsoft_Terminal_Settings_Model_ICommand<winrt::Microsoft::Terminal::Settings::Model::ICommand>::NestedCommands+0x38 [C:\a\_work\1\s\src\cascadia\TerminalApp\Generated Files\winrt\Microsoft.Terminal.Settings.Model.h @ 1069] 
06 00000087`896fe350 00007ffc`a5da9dd4     TerminalApp!winrt::TerminalApp::implementation::TerminalPage::_CreateNewTabFlyoutItems+0x863ae [C:\a\_work\1\s\src\cascadia\TerminalApp\TerminalPage.cpp @ 962] 
07 00000087`896fe5b0 00007ffc`a5e7c41b     TerminalApp!winrt::TerminalApp::implementation::TerminalPage::_CreateNewTabFlyout+0xf4 [C:\a\_work\1\s\src\cascadia\TerminalApp\TerminalPage.cpp @ 831] 
08 00000087`896feb00 00007ffc`a5e01de7     TerminalApp!winrt::TerminalApp::implementation::TerminalPage::_RefreshUIForSettingsReload+0x4f7 [C:\a\_work\1\s\src\cascadia\TerminalApp\TerminalPage.cpp @ 2908] 
09 00000087`896fed20 00007ffc`a5ddeed3     TerminalApp!winrt::TerminalApp::implementation::TerminalPage::SetSettings+0x8d96b [C:\a\_work\1\s\src\cascadia\TerminalApp\TerminalPage.cpp @ 144] 
0a 00000087`896feda0 00007ffc`a5dde7dd     TerminalApp!winrt::TerminalApp::implementation::AppLogic::ReloadSettings+0x123 [C:\a\_work\1\s\src\cascadia\TerminalApp\AppLogic.cpp @ 1077] 
0b 00000087`896fedf0 00007ffc`a5dde8ae     TerminalApp!`winrt::TerminalApp::implementation::AppLogic::AppLogic'::`2'::<lambda_1>::operator()+0x8d [C:\a\_work\1\s\src\cascadia\TerminalApp\AppLogic.cpp @ 198] 
0c (Inline Function) --------`--------     T


```

Fix patch:
diff --git a/src/cascadia/LocalTests_SettingsModel/NewTabMenuTests.cpp b/src/cascadia/LocalTests_SettingsModel/NewTabMenuTests.cpp
new file mode 100644
index 00000000000..f9e4625e995
--- /dev/null
+++ b/src/cascadia/LocalTests_SettingsModel/NewTabMenuTests.cpp
@@ -0,0 +1,107 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+
+#include "../TerminalSettingsModel/NewTabMenuEntry.h"
+#include "../TerminalSettingsModel/FolderEntry.h"
+#include "../TerminalSettingsModel/CascadiaSettings.h"
+#include "../types/inc/colorTable.hpp"
+#include "JsonTestClass.h"
+
+#include <defaults.h>
+
+using namespace Microsoft::Console;
+using namespace winrt::Microsoft::Terminal;
+using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
+using namespace WEX::Logging;
+using namespace WEX::TestExecution;
+using namespace WEX::Common;
+
+namespace SettingsModelLocalTests
+{
+    // TODO:microsoft/terminal#3838:
+    // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
+    // an updated TAEF that will let us install framework packages when the test
+    // package is deployed. Until then, these tests won't deploy in CI.
+
+    class NewTabMenuTests : public JsonTestClass
+    {
+        // Use a custom AppxManifest to ensure that we can activate winrt types
+        // from our test. This property will tell taef to manually use this as
+        // the AppxManifest for this test class.
+        // This does not yet work for anything XAML-y. See TabTests.cpp for more
+        // details on that.
+        BEGIN_TEST_CLASS(NewTabMenuTests)
+            TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
+            TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
+        END_TEST_CLASS()
+
+        TEST_METHOD(DefaultsToRemainingProfiles);
+        TEST_METHOD(ParseEmptyFolder);
+    };
+
+    void NewTabMenuTests::DefaultsToRemainingProfiles()
+    {
+        Log::Comment(L"If the user doesn't customize the menu, put one entry for each profile");
+
+        static constexpr std::string_view settingsString{ R"json({
+        })json" };
+
+        try
+        {
+            const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
+
+            VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
+
+            const auto& entries = settings->GlobalSettings().NewTabMenu();
+            VERIFY_ARE_EQUAL(1u, entries.Size());
+            VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType::RemainingProfiles, entries.GetAt(0).Type());
+        }
+        catch (const SettingsException& ex)
+        {
+            auto loadError = ex.Error();
+            loadError;
+            throw ex;
+        }
+        catch (const SettingsTypedDeserializationException& e)
+        {
+            auto deserializationErrorMessage = til::u8u16(e.what());
+            Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
+            throw e;
+        }
+    }
+
+    void NewTabMenuTests::ParseEmptyFolder()
+    {
+        Log::Comment(L"GH #14557 - An empty folder entry shouldn't crash");
+
+        static constexpr std::string_view settingsString{ R"json({
+            "newTabMenu": [
+                { "type": "folder" }
+            ]
+        })json" };
+
+        try
+        {
+            const auto settings{ winrt::make_self<CascadiaSettings>(settingsString, DefaultJson) };
+
+            VERIFY_ARE_EQUAL(0u, settings->Warnings().Size());
+
+            const auto& entries = settings->GlobalSettings().NewTabMenu();
+            VERIFY_ARE_EQUAL(1u, entries.Size());
+        }
+        catch (const SettingsException& ex)
+        {
+            auto loadError = ex.Error();
+            loadError;
+            throw ex;
+        }
+        catch (const SettingsTypedDeserializationException& e)
+        {
+            auto deserializationErrorMessage = til::u8u16(e.what());
+            Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
+            throw e;
+        }
+    }
+}
diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp
index 4f9fb528f99..d4cbd49aafe 100644
--- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp
+++ b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp
@@ -406,6 +406,12 @@ namespace SettingsModelLocalTests
             "$schema" : "https://aka.ms/terminal-profiles-schema",
             "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
             "disabledProfileSources": [ "Windows.Terminal.Wsl" ],
+            "newTabMenu":
+            [
+                {
+                    "type": "remainingProfiles"
+                }
+            ],
             "profiles": {
                 "defaults": {
                     "font": {
diff --git a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj
index 7e8a44321b3..771e5e33d52 100644
--- a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj
+++ b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj
@@ -39,6 +39,7 @@
     <ClCompile Include="KeyBindingsTests.cpp" />
     <ClCompile Include="CommandTests.cpp" />
     <ClCompile Include="DeserializationTests.cpp" />
+    <ClCompile Include="NewTabMenuTests.cpp" />
     <ClCompile Include="SerializationTests.cpp" />
     <ClCompile Include="TerminalSettingsTests.cpp" />
     <ClCompile Include="ThemeTests.cpp" />
diff --git a/src/cascadia/TerminalSettingsModel/FolderEntry.cpp b/src/cascadia/TerminalSettingsModel/FolderEntry.cpp
index 54af1d8c773..633f529a86a 100644
--- a/src/cascadia/TerminalSettingsModel/FolderEntry.cpp
+++ b/src/cascadia/TerminalSettingsModel/FolderEntry.cpp
@@ -63,6 +63,10 @@ IVector<NewTabMenuEntryModel> FolderEntry::Entries() const
     // We filter the full list of entries from JSON to just include the
     // non-empty ones.
     IVector<NewTabMenuEntryModel> result{ winrt::single_threaded_vector<NewTabMenuEntryModel>() };
+    if (_Entries == nullptr)
+    {
+        return result;
+    }
 
     for (const auto& entry : _Entries)
     {


Test patch: