Skip to content

Commit 0a80c56

Browse files
committed
chore: Fix null reference complaints under .NET 8.0
Signed-off-by: Jon Skeet <[email protected]>
1 parent 623e51f commit 0a80c56

File tree

9 files changed

+51
-31
lines changed

9 files changed

+51
-31
lines changed

src/CloudNative.CloudEvents.Amqp/AmqpExtensions.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Cloud Native Foundation.
1+
// Copyright (c) Cloud Native Foundation.
22
// Licensed under the Apache 2.0 license.
33
// See LICENSE file in the project root for full license information.
44

@@ -8,6 +8,7 @@
88
using CloudNative.CloudEvents.Core;
99
using System;
1010
using System.Collections.Generic;
11+
using System.Diagnostics.CodeAnalysis;
1112
using System.IO;
1213
using System.Net.Mime;
1314

@@ -145,7 +146,7 @@ public static CloudEvent ToCloudEvent(
145146
}
146147
}
147148

148-
private static bool HasCloudEventsContentType(Message message, out string? contentType)
149+
private static bool HasCloudEventsContentType(Message message, [NotNullWhen(true)] out string? contentType)
149150
{
150151
contentType = message.Properties.ContentType?.ToString();
151152
return MimeUtilities.IsCloudEventsContentType(contentType);
@@ -249,4 +250,4 @@ private static ApplicationProperties MapHeaders(CloudEvent cloudEvent, string pr
249250
return applicationProperties;
250251
}
251252
}
252-
}
253+
}

src/CloudNative.CloudEvents.Amqp/CloudNative.CloudEvents.Amqp.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<PackageReference Include="AMQPNetLite" Version="2.4.2" />
1313
<PackageReference Include="AMQPNetLite.Serialization" Version="2.4.2" />
1414
<ProjectReference Include="..\CloudNative.CloudEvents\CloudNative.CloudEvents.csproj" />
15+
<!-- Source-only package with nullable reference annotations. -->
16+
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="All" />
1517
</ItemGroup>
1618

1719
</Project>

src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,9 @@ private static RecordSchema ParseEmbeddedSchema()
185185
// will fail and that's okay since the type is useless without the proper schema.
186186
using var sr = new StreamReader(typeof(AvroEventFormatter)
187187
.Assembly
188-
.GetManifestResourceStream("CloudNative.CloudEvents.Avro.AvroSchema.json"));
188+
.GetManifestResourceStream("CloudNative.CloudEvents.Avro.AvroSchema.json")!);
189189

190190
return (RecordSchema) Schema.Parse(sr.ReadToEnd());
191191
}
192192
}
193-
}
193+
}

src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Cloud Native Foundation.
1+
// Copyright (c) Cloud Native Foundation.
22
// Licensed under the Apache 2.0 license.
33
// See LICENSE file in the project root for full license information.
44

@@ -345,7 +345,7 @@ protected virtual void DecodeStructuredModeDataBase64Property(JToken dataBase64T
345345
{
346346
throw new ArgumentException($"Structured mode property '{DataBase64PropertyName}' must be a string, when present.");
347347
}
348-
cloudEvent.Data = Convert.FromBase64String((string?) dataBase64Token);
348+
cloudEvent.Data = Convert.FromBase64String((string) dataBase64Token!);
349349
}
350350

351351
/// <summary>
@@ -498,7 +498,7 @@ private void WriteCloudEventForBatchOrStructuredMode(JsonWriter writer, CloudEve
498498
/// </summary>
499499
/// <param name="data">The CloudEvent to infer the data content from. Must not be null.</param>
500500
/// <returns>The inferred data content type, or null if no inference is performed.</returns>
501-
protected override string? InferDataContentType(object data) => data is byte[]? null : JsonMediaType;
501+
protected override string? InferDataContentType(object data) => data is byte[] ? null : JsonMediaType;
502502

503503
/// <summary>
504504
/// Encodes structured mode data within a CloudEvent, writing it to the specified <see cref="JsonWriter"/>.
@@ -524,7 +524,14 @@ protected virtual void EncodeStructuredModeData(CloudEvent cloudEvent, JsonWrite
524524
}
525525
else
526526
{
527-
ContentType dataContentType = new ContentType(GetOrInferDataContentType(cloudEvent));
527+
string? dataContentTypeText = GetOrInferDataContentType(cloudEvent);
528+
// This would only happen in a derived class which overrides GetOrInferDataContentType further...
529+
// This class infers application/json for anything other than byte arrays.
530+
if (dataContentTypeText is null)
531+
{
532+
throw new ArgumentException("Data content type cannot be inferred");
533+
}
534+
ContentType dataContentType = new ContentType(dataContentTypeText);
528535
if (IsJsonMediaType(dataContentType.MediaType))
529536
{
530537
writer.WritePropertyName(DataPropertyName);
@@ -710,4 +717,4 @@ protected override void DecodeStructuredModeDataProperty(JToken dataToken, Cloud
710717
protected override void DecodeStructuredModeDataBase64Property(JToken dataBase64Token, CloudEvent cloudEvent) =>
711718
throw new ArgumentException($"Data unexpectedly represented using '{DataBase64PropertyName}' within structured mode CloudEvent.");
712719
}
713-
}
720+
}

src/CloudNative.CloudEvents/CloudEventFormatterAttribute.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public CloudEventFormatterAttribute(Type formatterType) =>
5656
throw new ArgumentException($"The {nameof(CloudEventFormatterAttribute)} on type {targetType} has no converter type specified.", nameof(targetType));
5757
}
5858

59-
object instance;
59+
object? instance;
6060
try
6161
{
6262
instance = Activator.CreateInstance(formatterType);

src/CloudNative.CloudEvents/Core/BinaryDataUtilities.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static ReadOnlyMemory<byte> ToReadOnlyMemory(Stream stream)
6565
public static MemoryStream AsStream(ReadOnlyMemory<byte> memory)
6666
{
6767
var segment = GetArraySegment(memory);
68-
return new MemoryStream(segment.Array, segment.Offset, segment.Count, false);
68+
return new MemoryStream(segment.Array!, segment.Offset, segment.Count, false);
6969
}
7070

7171
/// <summary>
@@ -79,7 +79,7 @@ public static string GetString(ReadOnlyMemory<byte> memory, Encoding encoding)
7979

8080
// TODO: If we introduce an additional netstandard2.1 target, we can use encoding.GetString(memory.Span)
8181
var segment = GetArraySegment(memory);
82-
return encoding.GetString(segment.Array, segment.Offset, segment.Count);
82+
return encoding.GetString(segment.Array!, segment.Offset, segment.Count);
8383
}
8484

8585
/// <summary>
@@ -92,7 +92,7 @@ public static async Task CopyToStreamAsync(ReadOnlyMemory<byte> source, Stream d
9292
{
9393
Validation.CheckNotNull(destination, nameof(destination));
9494
var segment = GetArraySegment(source);
95-
await destination.WriteAsync(segment.Array, segment.Offset, segment.Count).ConfigureAwait(false);
95+
await destination.WriteAsync(segment.Array!, segment.Offset, segment.Count).ConfigureAwait(false);
9696
}
9797

9898
/// <summary>
@@ -108,13 +108,14 @@ public static byte[] AsArray(ReadOnlyMemory<byte> memory)
108108
var segment = GetArraySegment(memory);
109109
// We probably don't actually need to check the offset: if the count is the same as the length,
110110
// I can't see how the offset can be non-zero. But it doesn't *hurt* as a check.
111-
return segment.Offset == 0 && segment.Count == segment.Array.Length
111+
return segment.Offset == 0 && segment.Count == segment.Array!.Length
112112
? segment.Array
113113
: memory.ToArray();
114114
}
115115

116+
// Note: when this returns, the Array property of the returned segment is guaranteed not to be null.
116117
private static ArraySegment<byte> GetArraySegment(ReadOnlyMemory<byte> memory) =>
117-
MemoryMarshal.TryGetArray(memory, out var segment)
118+
MemoryMarshal.TryGetArray(memory, out var segment) && segment.Array is not null
118119
? segment
119120
: new ArraySegment<byte>(memory.ToArray());
120121
}

src/CloudNative.CloudEvents/Core/MimeUtilities.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Copyright 2021 Cloud Native Foundation.
1+
// Copyright 2021 Cloud Native Foundation.
22
// Licensed under the Apache 2.0 license.
33
// See LICENSE file in the project root for full license information.
44

55
using System;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Net.Http.Headers;
78
using System.Net.Mime;
89
using System.Text;
@@ -57,7 +58,7 @@ public static Encoding GetEncoding(ContentType? contentType) =>
5758
var header = new MediaTypeHeaderValue(contentType.MediaType);
5859
foreach (string parameterName in contentType.Parameters.Keys)
5960
{
60-
header.Parameters.Add(new NameValueHeaderValue(parameterName, contentType.Parameters[parameterName].ToString()));
61+
header.Parameters.Add(new NameValueHeaderValue(parameterName, contentType.Parameters[parameterName]));
6162
}
6263
return header;
6364
}
@@ -76,7 +77,7 @@ public static Encoding GetEncoding(ContentType? contentType) =>
7677
/// </summary>
7778
/// <param name="contentType">The content type to check. May be null, in which case the result is false.</param>
7879
/// <returns>true if the given content type denotes a (non-batch) CloudEvent; false otherwise</returns>
79-
public static bool IsCloudEventsContentType(string? contentType) =>
80+
public static bool IsCloudEventsContentType([NotNullWhen(true)] string? contentType) =>
8081
contentType is string &&
8182
contentType.StartsWith(MediaType, StringComparison.InvariantCultureIgnoreCase) &&
8283
!contentType.StartsWith(BatchMediaType, StringComparison.InvariantCultureIgnoreCase);
@@ -86,7 +87,7 @@ contentType is string &&
8687
/// </summary>
8788
/// <param name="contentType">The content type to check. May be null, in which case the result is false.</param>
8889
/// <returns>true if the given content type represents a CloudEvent batch; false otherwise</returns>
89-
public static bool IsCloudEventsBatchContentType(string? contentType) =>
90+
public static bool IsCloudEventsBatchContentType([NotNullWhen(true)] string? contentType) =>
9091
contentType is string && contentType.StartsWith(BatchMediaType, StringComparison.InvariantCultureIgnoreCase);
9192
}
9293
}

src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// Copyright (c) Cloud Native Foundation.
1+
// Copyright (c) Cloud Native Foundation.
22
// Licensed under the Apache 2.0 license.
33
// See LICENSE file in the project root for full license information.
44

55
using CloudNative.CloudEvents.Core;
66
using System;
77
using System.Collections.Generic;
8+
using System.Diagnostics.CodeAnalysis;
89
using System.Linq;
910
using System.Net.Http;
1011
using System.Net.Http.Headers;
@@ -130,7 +131,7 @@ public static Task<CloudEvent> ToCloudEventAsync(
130131
return ToCloudEventInternalAsync(httpRequestMessage.Headers, httpRequestMessage.Content, formatter, extensionAttributes, nameof(httpRequestMessage));
131132
}
132133

133-
private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent content,
134+
private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent? content,
134135
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
135136
{
136137
Validation.CheckNotNull(formatter, nameof(formatter));
@@ -142,7 +143,7 @@ private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders head
142143
}
143144
else
144145
{
145-
string? versionId = MaybeGetVersionId(headers) ?? MaybeGetVersionId(content.Headers);
146+
string? versionId = MaybeGetVersionId(headers) ?? MaybeGetVersionId(content?.Headers);
146147
if (versionId is null)
147148
{
148149
throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(paramName));
@@ -151,7 +152,8 @@ private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders head
151152
?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", paramName);
152153

153154
var cloudEvent = new CloudEvent(version, extensionAttributes);
154-
foreach (var header in headers.Concat(content.Headers))
155+
var allHeaders = content is null ? headers : headers.Concat(content.Headers);
156+
foreach (var header in allHeaders)
155157
{
156158
string? attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key);
157159
if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
@@ -231,7 +233,7 @@ public static Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchAsync(
231233
return ToCloudEventBatchInternalAsync(httpRequestMessage.Content, formatter, extensionAttributes, nameof(httpRequestMessage));
232234
}
233235

234-
private static async Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchInternalAsync(HttpContent content,
236+
private static async Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchInternalAsync(HttpContent? content,
235237
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
236238
{
237239
Validation.CheckNotNull(formatter, nameof(formatter));
@@ -331,21 +333,21 @@ public static HttpContent ToHttpContent(this IReadOnlyList<CloudEvent> cloudEven
331333
}
332334

333335
private static ByteArrayContent ToByteArrayContent(ReadOnlyMemory<byte> content) =>
334-
MemoryMarshal.TryGetArray(content, out var segment)
336+
MemoryMarshal.TryGetArray(content, out var segment) && segment.Array is not null
335337
? new ByteArrayContent(segment.Array, segment.Offset, segment.Count)
336338
// TODO: Just throw?
337339
: new ByteArrayContent(content.ToArray());
338340

339341
// TODO: This would include "application/cloudeventsarerubbish" for example...
340-
private static bool HasCloudEventsContentType(HttpContent content) =>
342+
private static bool HasCloudEventsContentType([NotNullWhen(true)] HttpContent? content) =>
341343
MimeUtilities.IsCloudEventsContentType(content?.Headers?.ContentType?.MediaType);
342344

343-
private static bool HasCloudEventsBatchContentType(HttpContent content) =>
345+
private static bool HasCloudEventsBatchContentType([NotNullWhen(true)] HttpContent? content) =>
344346
MimeUtilities.IsCloudEventsBatchContentType(content?.Headers?.ContentType?.MediaType);
345347

346348
private static string? MaybeGetVersionId(HttpHeaders? headers) =>
347349
headers is not null && headers.Contains(HttpUtilities.SpecVersionHttpHeader)
348350
? headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First()
349351
: null;
350352
}
351-
}
353+
}

src/CloudNative.CloudEvents/Http/HttpListenerExtensions.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ private static async Task<CloudEvent> ToCloudEventAsyncImpl(HttpListenerRequest
179179
}
180180
else
181181
{
182-
string versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader];
182+
string? versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader];
183183
if (versionId is null)
184184
{
185185
throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(httpListenerRequest));
@@ -191,12 +191,18 @@ private static async Task<CloudEvent> ToCloudEventAsyncImpl(HttpListenerRequest
191191
var headers = httpListenerRequest.Headers;
192192
foreach (var key in headers.AllKeys)
193193
{
194+
// It would be highly unusual for either the key or the value to be null, but
195+
// the contract claims it's possible. Skip it if so.
196+
if (key is null || headers[key] is not string headerValue)
197+
{
198+
continue;
199+
}
194200
string? attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key);
195201
if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
196202
{
197203
continue;
198204
}
199-
string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]);
205+
string attributeValue = HttpUtilities.DecodeHeaderValue(headerValue);
200206
cloudEvent.SetAttributeFromString(attributeName, attributeValue);
201207
}
202208

0 commit comments

Comments
 (0)