[API proposal] Attribute for passing caller identity implicitly #9017
Replies: 55 comments
-
Tagging subscribers to this area: @dotnet/area-system-runtime Issue DetailsRationaleNumber of libraries, including the new minimal ASP.NET host, use Proposalnamespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class CallerIdentityAttribute : Attribute
{
public CallerIdentityAttribute();
}
} This attribute can be applied to parameters of type
|
Beta Was this translation helpful? Give feedback.
-
@jaredpar Thoughts on this proposal? cc @davidfowl |
Beta Was this translation helpful? Give feedback.
-
So in the case of ASP.NET Core, it would look like this: public class WebApplication
{
public static WebApplicationBuilder CreateBuilder(string[] args, [CallerIdentityAttribute]Assembly assembly = null);
} Usage: var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", context => context.Response.WriteAsync("Hello World!"));
app.Run(); The compiler would do this (since we're in Main): var builder = WebApplication.CreateBuilder(args, typeof(Program).Assembly);
var app = builder.Build();
app.MapGet("/", context => context.Response.WriteAsync("Hello World!"));
app.Run(); That's slick. I like it. |
Beta Was this translation helpful? Give feedback.
-
We should consider marking |
Beta Was this translation helpful? Give feedback.
-
Should this be in csharplang? |
Beta Was this translation helpful? Give feedback.
-
Agree. This should start in csharplang. |
Beta Was this translation helpful? Give feedback.
-
Also see: #87 If those attributes were collapsed into a single attribute that passed a |
Beta Was this translation helpful? Give feedback.
-
I expect that we will frequently need to add trimming annotations like |
Beta Was this translation helpful? Give feedback.
-
Overall this looks pretty reasonable to me. For the class Program {
static void Main() {
PrintMe();
var x = () => PrintMe();
x();
}
static void PrintMe([CallerIdentityAttribute]Type type = null) => Console.WriteLine(type.FullName);
} Does this print
Or
|
Beta Was this translation helpful? Give feedback.
-
Good question. I think it depends on the usage. Should this work? class Program {
static void Main() {
PrintMe();
var x = () => PrintMe();
x();
}
static void PrintMe([CallerIdentityAttribute]Type type = null)
{
var m = type.GetMethod(nameof(PrintMe), BindingFlags.Public | BindingFlags.Static); // Does this return null?
}
} |
Beta Was this translation helpful? Give feedback.
-
That is explicitly undefined 😄. The compiler does not specify how lambdas are emitted, except in special cases, because we want to be free to change it between versions. Hence the relationship in terms of metadata between I know that often the language / framework says an item in metadata is undefined but in reality it never changes hence developers feel safe in taking dependencies on it. Lambda metadata is one area we've changed several times though and broken developers who took dependencies on it. |
Beta Was this translation helpful? Give feedback.
-
I think it makes more sense to emit the declared types/methods, given that it is not specified how lambdas and other similar constructs are emitted. |
Beta Was this translation helpful? Give feedback.
-
To make example in #4984 (comment) work as written, how are we going to retrofit the existing method to use the new pattern? public class WebApplication
{
// Existing method
public static WebApplicationBuilder CreateBuilder(string[] args);
// New method
public static WebApplicationBuilder CreateBuilder(string[] args, [CallerIdentityAttribute] Assembly assembly = null);
} I assume that |
Beta Was this translation helpful? Give feedback.
-
Believe we had the same overload issue with |
Beta Was this translation helpful? Give feedback.
-
Yeah, it'd be nice if you had: Debug.Assert(condition); that we could change the method: public static void Assert([DoesNotReturnIf(false)] bool condition, string? message) to instead be: public static void Assert([DoesNotReturnIf(false)] bool condition, [CallerArgumentExpression("condition")] string? message = null) such that the compiler would emit: Debug.Assert(condition, "condition"); Unfortunately, we already have the |
Beta Was this translation helpful? Give feedback.
-
Outside of caller parameters, and more in line with #6041 (which was closed as a duplicate of this, hence posting here, though it does seem not entirely related), I often find myself needing the assembly name of the current type -- for example, to compose RCL wwwroot content paths of the form |
Beta Was this translation helpful? Give feedback.
-
With |
Beta Was this translation helpful? Give feedback.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
-
I have an additional scenario/request for this feature, but I'm not sure it's an easy or even reasonable one, but I'd like to at least describe the scenario so it's on the radar. I am not using NativeAOT nor trimming in this context. Just crossgen/R2R. This is in the context of Paint.NET's plugin systems and APIs. I very occasionally have a need to establish the identity of the calling method -- but, I also want to make sure that the caller cannot lie, spoof, or just get it wrong, and it'd be nice to not add additional visible complexity to things. My proposal is to add a public sealed class ClassThatPluginsUse
{
// Maybe this would be required to have [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ClassThatPluginsUse() : this((object?)null) { }
private ClassThatPluginsUse(object? ignored, [CallerIdentity(SkipFrames = 1)] Assembly? callingAssembly = null)
{
// ... do something that depends on who the caller is
}
} I want the plugin to do I'm not going to make the argument that this would be a security feature 😂 This about wanting to be able to change behavior based on the app version that the plugin is compiled against. Call it a "plugin version contract" if you will. I can then call into I currently have a need for this in an API where I forgot to check a constructor parameter for public sealed class TabContainerControlInfo
: StatefulControlInfo<TabContainerState, TabContainerStateProperty>
{
// The private-protected base class constructor accepts `null` and this is normal. This parameterless constructor is the right way to do that.
public TabContainerControlInfo()
: base((TabContainerStateProperty?)null)
{
}
// But I forgot to do a null-check here on the stateProperty parameter
public TabContainerControlInfo(TabContainerStateProperty stateProperty)
: base(stateProperty) // should be e.g. `stateProperty ?? throw new ArgumentNullException(nameof(stateProperty))`
{
}
... This has resulted in some confusion and a bunch of wasted debugging time by plugin authors who were then getting a But I can't add the null check here without breaking the 10+ plugins that are already using this API incorrectly. Those plugins work fine today because they weren't using the I want to add the null-check, but only if the caller (the plugin) was compiled against the new version of my app. It's very hard to achieve this otherwise; I prototyped a system where I would use a An alternative way of versioning this API would be to In Paint.NET I try to maintain strict binary compatibility for plugins, and there are plugins that are almost 20 years old that still work fine today. This is the best experience for the end-user/customer: breaking a plugin to correct an API does not really affect the plugin author, it only makes things worse for the end-user/customer by breaking their work/creative-flow. Having additional tools for versioning the plugin APIs makes it easier for me to uphold all of this. |
Beta Was this translation helpful? Give feedback.
-
@rickbrew I'm not sure how that would work though. This attribute, like all other caller info attributes, would need to be filled in at the call site by the compiler. How would the compiler insert the info from 2 frames up? And moreover, as with all caller info attributes, the user can simply provide whatever value they want manually if they so desire, so it really wouldn't be spoof-free either. It very much feels like, to get the functionality you want, you need to actually do the stack walk with a |
Beta Was this translation helpful? Give feedback.
-
Right, I understand -- but note that the constructor taking the |
Beta Was this translation helpful? Give feedback.
-
Also, by describing my scenario it may result in someone realizing "oh that can actually be achieved in this other way that is feasible to add support for". |
Beta Was this translation helpful? Give feedback.
-
This doesn't seem to help though? What is the compiler actually going to insert at the call site of the method? |
Beta Was this translation helpful? Give feedback.
-
I don't know I'm not really a compiler person ¯\(ツ)/¯ I'm not really trying to propose an implementation strategy, and I did note that I didn't know if it was feasible. I thought the scenario could be an interesting one to chew on. It's not unheard of for an API to forget to validate parameters but in a way that doesn't cause problems because they're passed on to other APIs where those parameter values are okay. |
Beta Was this translation helpful? Give feedback.
-
I'm also describing this because it seems there's a push to deprecate things like |
Beta Was this translation helpful? Give feedback.
-
The problem with The API proposed here would be guaranteed to give the caller (unless the caller goes out of the way to spoof this but then ¯(ツ)/¯). The SkipFrames extension would just reintroduce the same problem because it would have to do a stack walk at runtime. There are no plans to deprecate |
Beta Was this translation helpful? Give feedback.
-
It's been a year now. Is any progress made on this? |
Beta Was this translation helpful? Give feedback.
-
No. When we make progress we update the issues and discussions on the topic. |
Beta Was this translation helpful? Give feedback.
-
We need this feature for resolving apache/logging-log4net#233 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Rationale
Number of libraries, including the new minimal ASP.NET host, use
Assembly.GetCallingAssembly()
for convenience.Assembly.GetCallingAssembly()
is slow and unreliable API, and also not compatible with AOT technologies (e.g. dotnet/runtime#53825). We need a fast, reliable and AOT-friendly replacement ofAssembly.GetCallingAssembly()
.Proposal
This attribute can be applied to parameters of type
System.Reflection.Assembly
,System.Reflection.Type
,System.Reflection.MethodBase
. When the C# compiler sees parameter tagged with this attribute, it will provide default value for it, according to the caller context:System.Reflection.Assembly
:typeof(containing_type).Assembly
System.Reflection.Type
:typeof(containing_type)
System.Reflection.MethodBase
:MethodBase.GetMethodFromHandle(ldtoken containing_method)
Design meetings
Beta Was this translation helpful? Give feedback.
All reactions