Compare commits

...

2 Commits

Author SHA1 Message Date
Niels Laute
5c248f277e Address PR review feedback
- Add NREs to spell-check expect.txt (DEFAULTTONEAREST already present).

- Fix forbidden-pattern: 'Otherwise ' -> '; otherwise' in catch comment.

- Rename shadowing 'providerSettings' local in UpdateSettings lambda to 'allProviderSettings' to disambiguate from the outer connected ProviderSettings instance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-28 09:34:02 +02:00
Niels Laute
8316cf26e0 CmdPal: Fix NullReferenceException on startup loading built-in providers
The catch handler in CommandProviderWrapper.LoadTopLevelCommands dereferenced Extension!.PackageFamilyName, but Extension is null for built-in (in-proc) providers constructed via the (ICommandProvider, TaskScheduler) ctor. Any exception thrown inside the try block during builtin load therefore caused a second NRE inside the catch itself, which escaped LoadBuiltinsAsync and aborted shell startup, masking the original exception.

This was made reachable by PR #46451 (commit 4337f8e5ff, 'CmdPal: Make settings and app state immutable'), which:

- Added a new settingsService.UpdateSettings(...) lambda at the top of LoadTopLevelCommands that runs before displayInfoInitialized can be set, giving more code paths that throw inside the try block.

- Replaced ProviderSettings/DockSettings with init-only ImmutableDictionary/record properties whose default initializers are silently overridden by null when an older settings.json is deserialized, producing real NREs (e.g. s.ProviderSettings.TryGetValue, settings.DockSettings.AllPinnedCommands) that hit the buggy catch.

Fix:

- Make the catch handler null-safe so the real exception is logged for built-in providers (Extension?.PackageFamilyName ?? ProviderId).

- Coalesce s.ProviderSettings against ImmutableDictionary.Empty in the new UpdateSettings lambda and in GetProviderSettings.

- Coalesce settings.DockSettings against new DockSettings() in InitializeCommands so a stale settings.json cannot crash builtin loading.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-28 09:20:54 +02:00
2 changed files with 17 additions and 5 deletions

View File

@@ -1224,6 +1224,7 @@ NPH
npmjs
NPU
NResize
NREs
NTAPI
ntdll
ntfs

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.Models;
@@ -127,7 +128,8 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
private ProviderSettings GetProviderSettings(SettingsModel settings)
{
if (!settings.ProviderSettings.TryGetValue(ProviderId, out var ps))
var providerSettings = settings.ProviderSettings;
if (providerSettings is null || !providerSettings.TryGetValue(ProviderId, out var ps))
{
ps = new ProviderSettings();
}
@@ -151,7 +153,8 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
settingsService.UpdateSettings(
s =>
{
if (!s.ProviderSettings.TryGetValue(ProviderId, out var ps))
var allProviderSettings = s.ProviderSettings ?? ImmutableDictionary<string, ProviderSettings>.Empty;
if (!allProviderSettings.TryGetValue(ProviderId, out var ps))
{
ps = new ProviderSettings();
}
@@ -160,7 +163,7 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
return s with
{
ProviderSettings = s.ProviderSettings.SetItem(ProviderId, newPs),
ProviderSettings = allProviderSettings.SetItem(ProviderId, newPs),
};
},
hotReload: false);
@@ -238,7 +241,11 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
}
catch (Exception e)
{
Logger.LogError($"Failed to load commands from extension {Extension!.PackageFamilyName}", e);
// Note: Extension is null for in-proc built-in providers, so this
// log line MUST stay null-safe; otherwise the catch itself NREs
// and takes down LoadBuiltinsAsync / shell startup.
var providerLabel = Extension?.PackageFamilyName ?? ProviderId;
Logger.LogError($"Failed to load commands from {providerLabel}", e);
if (!displayInfoInitialized)
{
@@ -320,7 +327,11 @@ public sealed class CommandProviderWrapper : ICommandProviderContext
}
}
var dockSettings = settings.DockSettings;
// DockSettings can be null when loading a settings.json that was
// produced by a CmdPal version older than the dock-settings change
// (e.g. an in-place upgrade from 0.97.x). Fall back to defaults so
// built-in providers can still load instead of NRE-ing on startup.
var dockSettings = settings.DockSettings ?? new DockSettings();
var allPinnedCommands = dockSettings.AllPinnedCommands;
var pinnedBandsForThisProvider = allPinnedCommands.Where(c => c.ProviderId == ProviderId);