Skip to content

Commit dbcbb16

Browse files
Enable nullable referenrce types in DebugEngineHost (#1592)
This PR contains the work to enable nullable reference types for the first project in MIEngine -- the DebugEngineHost implementations. **Changes:** - Turn on `<Nullable>enable</Nullable>` for the DebugEngineHost projects and update APIs/fields to nullable annotations (`?`, `is null`, `is not null`, null-forgiving where needed). - Add shared compatibility code for NRT on older frameworks (`NullableHelpers`, `NullableAttributes`) and flow annotations (e.g., `[MaybeNullWhen(false)]`). - Set repo-wide C# language version to 12.0 and simplify shared source inclusion via `*.cs` globs + `<Link>`.
1 parent 768ae31 commit dbcbb16

40 files changed

Lines changed: 596 additions & 260 deletions

build/all_projects.settings.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
<ToolsHome>$(MIEngineRoot)\tools</ToolsHome>
2222
<IntermediateOutputPath>$(MIEngineRoot)obj\$(Configuration)\$(MSBuildProjectName)\</IntermediateOutputPath>
2323
<NuGetExePath>$(ToolsHome)\NuGet\NuGet.exe</NuGetExePath>
24+
<!--NOTE: This is the same language version that is the default for .NET 8 projects -->
25+
<LangVersion>12.0</LangVersion>
2426
</PropertyGroup>
2527

2628
<!-- Source Link -->

src/DebugEngineHost.Common/HostLogChannel.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft. All rights reserved.
1+
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
/*
@@ -51,18 +51,18 @@ public interface ILogChannel
5151
public class HostLogChannel : ILogChannel
5252
{
5353
private readonly Action<string> _log;
54-
private StreamWriter _logFile;
54+
private StreamWriter? _logFile;
5555
private LogLevel _minLevelToBeLogged;
5656

5757
private readonly object _lock = new object();
5858

59-
private HostLogChannel() { }
59+
private HostLogChannel() { _log = null!; }
6060

61-
public HostLogChannel(Action<string> logAction, string file, LogLevel logLevel)
61+
public HostLogChannel(Action<string> logAction, string? file, LogLevel logLevel)
6262
{
6363
_log = logAction;
6464

65-
if (!string.IsNullOrEmpty(file))
65+
if (!IsNullOrEmpty(file))
6666
{
6767
_logFile = File.CreateText(file);
6868
}

src/DebugEngineHost.Stub/DebugEngineHost.Stub.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<!--Fix the assembly version for DebugEngineHost as all the versions of this dll must have the same assembly identity
44
NOTE: Ths must be set BEFORE improting miengine.settings.targets-->
55
<AssemblyVersion>1.0.0</AssemblyVersion>
6+
<Nullable>enable</Nullable>
67
</PropertyGroup>
78
<Import Project="..\..\build\miengine.settings.targets" />
89
<PropertyGroup>
@@ -23,6 +24,12 @@
2324

2425
<Import Project="..\..\build\Debugger.PIAs.Portable.Packages.settings.targets" />
2526

27+
<ItemGroup>
28+
<Compile Include="$(MIEngineRoot)\src\Shared\NullableAttributes.cs">
29+
<Link>Shared\%(Filename).cs</Link>
30+
</Compile>
31+
</ItemGroup>
32+
2633
<ItemGroup Label="NuGet Packages">
2734
<PackageReference Include="Microsoft.VisualStudioEng.MicroBuild.Core" Version="$(Microsoft_VisualStudioEng_MicroBuild_Core_Version)" GeneratePathProperty="true"/>
2835
</ItemGroup>

src/DebugEngineHost.Stub/DebugEngineHost.ref.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
// Copyright (c) Microsoft. All rights reserved.
1+
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using Microsoft.VisualStudio.Debugger.Interop;
55
using System;
66
using System.Collections.Generic;
7-
using System.Diagnostics;
7+
using ConditionalAttribute = global::System.Diagnostics.ConditionalAttribute;
88
using System.Threading;
99

1010

@@ -88,7 +88,7 @@ public void Dispose() { }
8888
/// <param name="valueName">Name of the value to obtain</param>
8989
/// <returns>[Optional] null if the value doesn't exist, otherwise the value
9090
/// </returns>
91-
public object GetValue(string valueName)
91+
public object? GetValue(string valueName)
9292
{
9393
throw new NotImplementedException();
9494
}
@@ -146,7 +146,7 @@ public string RegistryRoot
146146
/// </summary>
147147
/// <param name="metric">The metric to read.</param>
148148
/// <returns>[Optional] value of the metric. Null if the metric is not defined.</returns>
149-
public object GetEngineMetric(string metric)
149+
public object? GetEngineMetric(string metric)
150150
{
151151
throw new NotImplementedException();
152152
}
@@ -178,7 +178,7 @@ public T GetDebuggerConfigurationSetting<T>(string settingName, T defaultValue)
178178
/// </summary>
179179
/// <param name="launcherTypeName">launch options type name</param>
180180
/// <returns></returns>
181-
public object GetCustomLauncher(string launcherTypeName)
181+
public object? GetCustomLauncher(string launcherTypeName)
182182
{
183183
throw new NotImplementedException();
184184
}
@@ -277,8 +277,8 @@ public static void EnableNatvisDiagnostics(Action<string> callback, LogLevel lev
277277
/// <summary>
278278
/// Sets the log file to write to.
279279
/// </summary>
280-
/// <param name="logFile">The file to write engine logs to.</param>
281-
public static void SetEngineLogFile(string logFile)
280+
/// <param name="logFile">The file to write engine logs to, or null if none</param>
281+
public static void SetEngineLogFile(string? logFile)
282282
{
283283
throw new NotImplementedException();
284284
}
@@ -287,7 +287,7 @@ public static void SetEngineLogFile(string logFile)
287287
/// Gets the engine log channel created by 'EnableHostLogging'
288288
/// </summary>
289289
/// <returns>A logger object if logging is enabled, or null if it is not</returns>
290-
public static ILogChannel GetEngineLogChannel()
290+
public static ILogChannel? GetEngineLogChannel()
291291
{
292292
throw new NotImplementedException();
293293
}
@@ -296,7 +296,7 @@ public static ILogChannel GetEngineLogChannel()
296296
/// Gets the Natvis log channel if its been created.
297297
/// </summary>
298298
/// <returns>A logger object if logging is enabled, or null if it is not</returns>
299-
public static ILogChannel GetNatvisLogChannel()
299+
public static ILogChannel? GetNatvisLogChannel()
300300
{
301301
throw new NotImplementedException();
302302
}
@@ -339,7 +339,7 @@ public static class HostLoader
339339
/// <param name="clsid">CLSID to CoCreate</param>
340340
/// <returns>[Optional] loaded object. Null if the type is not registered, or points to a type that doesn't exist</returns>
341341
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Co")]
342-
public static object VsCoCreateManagedObject(HostConfigurationStore configStore, Guid clsid)
342+
public static object? VsCoCreateManagedObject(HostConfigurationStore configStore, Guid clsid)
343343
{
344344
throw new NotImplementedException();
345345
}
@@ -472,15 +472,15 @@ public static void FindNatvis(NatvisLoader loader)
472472
/// <summary>
473473
/// Enable's tracking the VS 'Natvis Diagnostic Messages (C++ only)' setting.
474474
/// </summary>
475-
public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger)
475+
public static IDisposable? WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger)
476476
{
477477
throw new NotImplementedException();
478478
}
479479

480480
/// <summary>
481481
/// Return the solution's root directory, null if no solution
482482
/// </summary>
483-
public static string FindSolutionRoot()
483+
public static string? FindSolutionRoot()
484484
{
485485
throw new NotImplementedException();
486486
}
@@ -663,7 +663,7 @@ public static void SendEvent(string eventName, params KeyValuePair<string, objec
663663
/// </summary>
664664
/// <param name="currentException">Exception object to report.</param>
665665
/// <param name="engineName">Name of the engine reporting the exception. Ex:Microsoft.MIEngine</param>
666-
public static void ReportCurrentException(Exception currentException, string engineName)
666+
public static void ReportCurrentException(Exception currentException, string? engineName)
667667
{
668668
throw new NotImplementedException();
669669
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
global using static global::Microsoft.DebugEngineHost.NullableHelpers;
5+
6+
namespace Microsoft.DebugEngineHost
7+
{
8+
using System.Diagnostics.CodeAnalysis;
9+
using ConditionalAttribute = System.Diagnostics.ConditionalAttribute;
10+
using SysDebug = System.Diagnostics.Debug;
11+
12+
#pragma warning disable 8763 // A method marked [DoesNotReturn] should not return.
13+
14+
/// <summary>
15+
/// Helper class to support nullable reference work when compiling against .NET Standard / .NET Framework
16+
/// </summary>
17+
public static class NullableHelpers
18+
{
19+
/// <summary>
20+
/// Wrapper around string.IsNullOrEmpty to add the `[NotNullWhen(false)]` annotation
21+
/// </summary>
22+
/// <param name="s">string to test</param>
23+
/// <returns>True if the string is null or empty</returns>
24+
static public bool IsNullOrEmpty([NotNullWhen(false)] string? s)
25+
{
26+
return string.IsNullOrEmpty(s);
27+
}
28+
29+
/// <summary>
30+
/// Wrapper around string.IsNullOrWhiteSpace to add the `[NotNullWhen(false)]` annotation
31+
/// </summary>
32+
/// <param name="s">string to test</param>
33+
/// <returns>True if the string is null, empty, or only whitespace</returns>
34+
static public bool IsNullOrWhiteSpace([NotNullWhen(false)] string? s)
35+
{
36+
return string.IsNullOrWhiteSpace(s);
37+
}
38+
39+
/// <summary>
40+
/// This is a shim on top of the <see cref="System.Diagnostics.Debug"/> class which adds attributes used
41+
/// in nullability analysis. This is important because without the DoesNotReturnIf/DoesNotReturn attributes,
42+
/// on Debug.Assert/Debug.Fail the C# compiler will see code like:
43+
/// <code>
44+
/// Debug.Assert(myArg != null, "Invalid argument")
45+
/// </code>
46+
/// And decide that because the code was attempting to handle 'myArg' being null, that it must be possible
47+
/// for it to be null.
48+
/// </summary>
49+
[System.Diagnostics.DebuggerNonUserCode()]
50+
public static class Debug
51+
{
52+
/// <summary>
53+
/// Checks for a condition; if the condition is false, displays a message box that shows the call stack.
54+
/// </summary>
55+
/// <param name="condition">The conditional expression to evaluate. If the condition is true, a failure message is not sent and the message box is not displayed.</param>
56+
[Conditional("DEBUG")]
57+
public static void Assert([DoesNotReturnIf(false)] bool condition)
58+
{
59+
SysDebug.Assert(condition);
60+
}
61+
62+
/// <summary>
63+
/// Checks for a condition; if the condition is false, outputs a specified message and displays a message box that shows the call stack.
64+
/// </summary>
65+
/// <param name="condition">The conditional expression to evaluate. If the condition is true, the specified message is not sent and the message box is not displayed.</param>
66+
/// <param name="message">The message to send to the <see cref="System.Diagnostics.Trace.Listeners"/> collection.</param>
67+
[Conditional("DEBUG")]
68+
public static void Assert([DoesNotReturnIf(false)] bool condition, string message)
69+
{
70+
SysDebug.Assert(condition, message);
71+
}
72+
73+
/// <summary>
74+
/// Checks for a condition; if the condition is false, outputs two specified messages and displays a message box that shows the call stack.
75+
/// </summary>
76+
/// <param name="condition">The conditional expression to evaluate. If the condition is true, the specified messages are not sent and the message box is not displayed.</param>
77+
/// <param name="message">The message to send to the <see cref="System.Diagnostics.Trace.Listeners"/> collection.</param>
78+
/// <param name="detailMessage">The detailed message to send to the <see cref="System.Diagnostics.Trace.Listeners"/> collection.</param>
79+
[Conditional("DEBUG")]
80+
public static void Assert([DoesNotReturnIf(false)] bool condition, string message, string detailMessage)
81+
{
82+
SysDebug.Assert(condition, message, detailMessage);
83+
}
84+
85+
/// <summary>
86+
/// Emits the specified error message.
87+
/// </summary>
88+
/// <param name="message">A message to emit.</param>
89+
[Conditional("DEBUG")]
90+
[DoesNotReturn]
91+
public static void Fail(string message)
92+
{
93+
SysDebug.Fail(message);
94+
}
95+
96+
/// <summary>
97+
/// Emits an error message and a detailed error message.
98+
/// </summary>
99+
/// <param name="message">A message to emit.</param>
100+
/// <param name="detailMessage">A detailed message to emit.</param>
101+
[Conditional("DEBUG")]
102+
[DoesNotReturn]
103+
public static void Fail(string message, string detailMessage)
104+
{
105+
SysDebug.Fail(message, detailMessage);
106+
}
107+
108+
/// <summary>
109+
/// Writes a message followed by a line terminator to the debugger.
110+
/// </summary>
111+
/// <param name="message">A message to write.</param>
112+
[Conditional("DEBUG")]
113+
public static void WriteLine(string message)
114+
{
115+
SysDebug.WriteLine(message);
116+
}
117+
118+
/// <summary>
119+
/// Writes the value of the object's <see cref="object.ToString"/> method to the debugger.
120+
/// </summary>
121+
/// <param name="value">An object whose value is sent to the debugger.</param>
122+
[Conditional("DEBUG")]
123+
public static void WriteLine(object value)
124+
{
125+
SysDebug.WriteLine(value);
126+
}
127+
128+
/// <summary>
129+
/// Writes a category name and message to the debugger.
130+
/// </summary>
131+
/// <param name="message">A message to write.</param>
132+
/// <param name="category">A category name used to organize the output.</param>
133+
[Conditional("DEBUG")]
134+
public static void WriteLine(string message, string category)
135+
{
136+
SysDebug.WriteLine(message, category);
137+
}
138+
139+
/// <summary>
140+
/// Writes a category name and the value of the object's <see cref="object.ToString"/> method to the debugger.
141+
/// </summary>
142+
/// <param name="value">An object whose value is sent to the debugger.</param>
143+
/// <param name="category">A category name used to organize the output.</param>
144+
[Conditional("DEBUG")]
145+
public static void WriteLine(object value, string category)
146+
{
147+
SysDebug.WriteLine(value, category);
148+
}
149+
}
150+
}
151+
}

src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<!--Fix the assembly version for DebugEngineHost as all the versions of this dll must have the same assembly identity
44
NOTE: Ths must be set BEFORE importing miengine.settings.targets-->
55
<AssemblyVersion>1.0.0</AssemblyVersion>
6+
<Nullable>enable</Nullable>
67
</PropertyGroup>
78
<Import Project="..\..\build\miengine.settings.targets" />
89
<PropertyGroup>
@@ -20,11 +21,16 @@
2021
<TargetFramework>netstandard2.0</TargetFramework>
2122
</PropertyGroup>
2223

23-
<ItemGroup Label="Compile Shared Interfaces">
24-
<Compile Include="$(MIEngineRoot)\src\DebugEngineHost.Stub\Shared\Microsoft.VisualStudio.Debugger.Interop.DAP.cs" />
25-
<Compile Include="..\DebugEngineHost.Stub\Shared\Microsoft.VisualStudio.Debugger.Interop.MI.cs" Link="Microsoft.VisualStudio.Debugger.Interop.MI.cs" />
26-
27-
<Compile Include="$(MIEngineRoot)\src\DebugEngineHost.Common\HostLogChannel.cs" />
24+
<ItemGroup>
25+
<Compile Include="$(MIEngineRoot)\src\DebugEngineHost.Stub\Shared\*.cs">
26+
<Link>Shared\%(Filename).cs</Link>
27+
</Compile>
28+
<Compile Include="$(MIEngineRoot)\src\DebugEngineHost.Common\*.cs">
29+
<Link>Shared\%(Filename).cs</Link>
30+
</Compile>
31+
<Compile Include="$(MIEngineRoot)\src\Shared\NullableAttributes.cs">
32+
<Link>Shared\%(Filename).cs</Link>
33+
</Compile>
2834
</ItemGroup>
2935

3036
<ItemGroup>

src/DebugEngineHost.VSCode/HostConfigurationSection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public void Dispose()
2121
GC.SuppressFinalize(this);
2222
}
2323

24-
public object GetValue(string valueName)
24+
public object? GetValue(string valueName)
2525
{
2626
ExceptionSettings.TriggerState state;
2727
if (_defaultTriggers.TryGetValue(valueName, out state))

src/DebugEngineHost.VSCode/HostConfigurationStore.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ public sealed class HostConfigurationStore
1515

1616
public HostConfigurationStore(string adapterId)
1717
{
18-
_config = EngineConfiguration.TryGet(adapterId);
19-
if (_config == null)
18+
EngineConfiguration? config = EngineConfiguration.TryGet(adapterId);
19+
if (config is null)
2020
{
2121
throw new ArgumentOutOfRangeException(nameof(adapterId));
2222
}
23+
_config = config;
2324
}
2425

2526
public void SetEngineGuid(Guid value)
@@ -35,12 +36,12 @@ public string RegistryRoot
3536
}
3637
}
3738

38-
public object GetCustomLauncher(string launcherTypeName)
39+
public object? GetCustomLauncher(string launcherTypeName)
3940
{
4041
throw new NotImplementedException();
4142
}
4243

43-
public object GetEngineMetric(string metric)
44+
public object? GetEngineMetric(string metric)
4445
{
4546
if (string.CompareOrdinal("GlobalVisualizersDirectory", metric) == 0)
4647
{
@@ -55,7 +56,7 @@ public object GetEngineMetric(string metric)
5556
public void GetExceptionCategorySettings(Guid categoryId, out HostConfigurationSection categoryConfigSection, out string categoryName)
5657
{
5758
var category = _config.ExceptionSettings.Categories.FirstOrDefault((x) => x.Id == categoryId);
58-
if (category == null)
59+
if (category is null)
5960
{
6061
throw new InvalidDataException(string.Format(CultureInfo.CurrentCulture, HostResources.Error_ExceptionCategoryMissing, categoryId));
6162
}

0 commit comments

Comments
 (0)