dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.

Home Page:https://docs.microsoft.com/dotnet/core/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Analyzer] Incorrect lambda type passed to ConcurrentDictionary.GetOrAdd

stephentoub opened this issue · comments

ConcurrentDictionary<>.GetOrAdd exposes two overloads, both of which accept an argument (the key). Yet, this code compiles:

using System.Collections.Concurrent;

public static class LockProvider
{
    private static readonly ConcurrentDictionary<string, object> s_locks = new();
    
    public static object GetUniqueLock(string key) => s_locks.GetOrAdd(key, () => new object());
}

SharpLab

Why? Because a) there's also a GetOrAdd(TKey key, TValue value) overload, b) thanks to the lambda improvements in C# 10 it'll will infer a delegate type that matches the inputs/outputs of the lambda (in this case, Func<TValue>), and c) Func<TValue> is a TValue here (Func<TValue> : object). Thus, this code compiles down to effectively:

using System.Collections.Concurrent;

public static class LockProvider
{
    private static readonly ConcurrentDictionary<string, object> s_locks = new();
    
    public static object GetUniqueLock(string key)
    {
        Func<object> value = Cache.CachedDelegate ??= () => object();
        return s_locks.GetOrAdd(key, value);
    }
}

internal static class Cache
{
    public static Func<object>? CachedDelegate;
}

and the delegate is never actually used as a delegate. Rather, barring initialization race conditions, the same object ends up being used as the value for every key and thus being returned as the same lock object in this example for every key.

This has shown up in several code bases now, such that we should consider writing an analyzer for it, either for the specific case with ConcurrentDictionary, or possibly broadened out to cases where a lambda is being passed as a argument for some object or unconstrained generic parameter (if that wouldn't be too noisy).

cc: @jaredpar

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.