Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tracing] add null checks when iterating headers in propagators #6460

Merged
merged 3 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions tracer/src/Datadog.Trace/Propagators/ParseUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using Datadog.Trace.Headers;
using Datadog.Trace.Logging;
using Datadog.Trace.Util;

namespace Datadog.Trace.Propagators
{
Expand Down Expand Up @@ -67,9 +64,15 @@ internal class ParseUtility
return null;

// IEnumerable version (different method to avoid try/finally in the caller)
static bool TryParse(IEnumerable<string?> headerValues, ref bool hasValue, out ulong result)
static bool TryParse(IEnumerable<string?>? headerValues, ref bool hasValue, out ulong result)
{
result = 0;

if (headerValues is null)
{
return false;
}

foreach (string? headerValue in headerValues)
{
if (ulong.TryParse(headerValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
Expand Down Expand Up @@ -133,9 +136,15 @@ static bool TryParse(IEnumerable<string?> headerValues, ref bool hasValue, out u
return null;

// IEnumerable version (different method to avoid try/finally in the caller)
static bool TryParse(IEnumerable<string?> headerValues, ref bool hasValue, out int result)
static bool TryParse(IEnumerable<string?>? headerValues, ref bool hasValue, out int result)
{
result = 0;

if (headerValues is null)
{
return false;
}

foreach (string? headerValue in headerValues)
{
if (int.TryParse(headerValue, out result))
Expand Down Expand Up @@ -175,8 +184,13 @@ static bool TryParse(IEnumerable<string?> headerValues, ref bool hasValue, out i
return ParseStringIEnumerable(headerValues);

// IEnumerable version (different method to avoid try/finally in the caller)
static string? ParseStringIEnumerable(IEnumerable<string?> headerValues)
static string? ParseStringIEnumerable(IEnumerable<string?>? headerValues)
{
if (headerValues is null)
{
return null;
}

foreach (string? headerValue in headerValues)
{
if (!string.IsNullOrEmpty(headerValue))
Expand Down Expand Up @@ -211,8 +225,13 @@ static bool TryParse(IEnumerable<string?> headerValues, ref bool hasValue, out i
return ParseStringIEnumerable(headerValues);

// IEnumerable version (different method to avoid try/finally in the caller)
static string? ParseStringIEnumerable(IEnumerable<string?> headerValues)
static string? ParseStringIEnumerable(IEnumerable<string?>? headerValues)
{
if (headerValues is null)
{
return null;
}

foreach (string? headerValue in headerValues)
{
if (!string.IsNullOrEmpty(headerValue))
Expand Down
120 changes: 65 additions & 55 deletions tracer/test/Datadog.Trace.Tests/Propagators/ParseUtilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,86 +14,96 @@ namespace Datadog.Trace.Tests.Propagators
{
public class ParseUtilityTests
{
public static TheoryData<IEnumerable<string>, ulong?> UInt64
=> new()
{
{ new[] { "42" }, 42 },
{ new[] { "4x" }, null },
{ new[] { "4x", "42" }, 42 }, // return first valid value
{ new[] { string.Empty }, null },
{ new[] { (string)null }, null },
{ new List<string> { "42" }, 42 },
{ new List<string> { "4x" }, null },
{ new List<string> { string.Empty }, null },
{ new List<string> { null }, null },
{ null, null }, // null collection returns null
};

public static TheoryData<IEnumerable<string>, int?> Int32
=> new()
{
{ new[] { "42" }, 42 },
{ new[] { "4x" }, null },
{ new[] { "4x", "42" }, 42 }, // return first valid value
{ new[] { string.Empty }, null },
{ new[] { (string)null }, null },
{ new List<string> { "42" }, 42 },
{ new List<string> { "4x" }, null },
{ new List<string> { string.Empty }, null },
{ new List<string> { null }, null },
{ null, null }, // null collection returns null
};

public static TheoryData<IEnumerable<string>, string> String
=> new()
{
{ new[] { "42" }, "42" },
{ new[] { "4x" }, "4x" },
{ new[] { "4x", "42" }, "4x" }, // return first valid value
{ new[] { string.Empty }, null }, // null or empty returns null
{ new[] { (string)null }, null }, // null or empty returns null
{ new List<string> { "42" }, "42" },
{ new List<string> { "4x" }, "4x" },
{ new List<string> { string.Empty }, null }, // null or empty returns null
{ new List<string> { null }, null }, // null or empty returns null
{ null, null }, // null collection returns null
};

[Theory]
[InlineData("42", 42UL)]
[InlineData("4x", null)]
public void ParseUInt64Test(string actual, object expected)
[MemberData(nameof(UInt64))]
public void ParseUInt64Test(IEnumerable<string> values, ulong? expected)
{
var actualResult = ParseUtility.ParseUInt64(
(object)null,
new FuncGetter<object>((carrier, name) => new[] { actual }),
string.Empty);

actualResult.Should().Be((ulong?)expected);

actualResult = ParseUtility.ParseUInt64(
var result = ParseUtility.ParseUInt64(
(object)null,
new FuncGetter<object>((carrier, name) => new List<string> { actual }),
new FuncGetter<object>((_, _) => values),
string.Empty);

actualResult.Should().Be((ulong?)expected);
result.Should().Be(expected);
}

[Theory]
[InlineData("42", 42)]
[InlineData("4x", null)]
public void ParseInt32Test(string actual, object expected)
[MemberData(nameof(Int32))]
public void ParseInt32Test(IEnumerable<string> values, int? expected)
{
var actualResult = ParseUtility.ParseInt32(
var result = ParseUtility.ParseInt32(
(object)null,
new FuncGetter<object>((carrier, name) => new[] { actual }),
new FuncGetter<object>((_, _) => values),
string.Empty);

actualResult.Should().Be((int?)expected);

actualResult = ParseUtility.ParseInt32(
(object)null,
new FuncGetter<object>((carrier, name) => new List<string> { actual }),
string.Empty);

actualResult.Should().Be((int?)expected);
result.Should().Be(expected);
}

[Theory]
[InlineData("42", "42")]
[InlineData("4x", "4x")]
[InlineData("", null)]
[InlineData(null, null)]
public void ParseString(string actual, string expected)
[MemberData(nameof(String))]
public void ParseStringTest(IEnumerable<string> values, string expected)
{
var actualResult = ParseUtility.ParseString(
var result = ParseUtility.ParseString(
(object)null,
new FuncGetter<object>((carrier, name) => new[] { actual }),
new FuncGetter<object>((_, _) => values),
string.Empty);

actualResult.Should().Be(expected);

actualResult = ParseUtility.ParseString(
(object)null,
new FuncGetter<object>((carrier, name) => new List<string> { actual }),
string.Empty);

actualResult.Should().Be(expected);
result.Should().Be(expected);
}

[Theory]
[InlineData("42", "42")]
[InlineData("4x", "4x")]
[InlineData("", null)]
[InlineData(null, null)]
public void ParseStringWithHeaders(string actual, string expected)
[MemberData(nameof(String))]
public void ParseStringWithHeaders(IEnumerable<string> values, string expected)
{
var actualResult = ParseUtility.ParseString(
new HeaderStruct(() => new[] { actual }),
string.Empty);

actualResult.Should().Be(expected);

actualResult = ParseUtility.ParseString(
new HeaderStruct(() => new List<string> { actual }),
var result = ParseUtility.ParseString(
new HeaderStruct(() => values),
string.Empty);

actualResult.Should().Be(expected);
result.Should().Be(expected);
}

private readonly struct FuncGetter<TCarrier> : ICarrierGetter<TCarrier>
Expand Down
Loading