14

I've just decompiled some 3rd party source to debug an issue, using DotPeek. The output code contains some unusual operators, which AFAIK aren't valid C#, so I'm wondering what they mean...

The extract looks like (with Dotpeek comments included, as they are probably relevant);

protected internal void DoReceive(ref byte[] Buffer, int MaxSize, out int Written)
{
    Written = 0;
    .
    .
    .        
    // ISSUE: explicit reference operation
    // ISSUE: variable of a reference type
    int& local = @Written;
    int num = SomeMethod();
    .
    .
    .
    // ISSUE: explicit reference operation
    ^local = num;
}

So, 3 unusual operators in there... int& = @Written seems to be assigning a pointer to a variable that is named pointlessly with the @ character?

But what is ^local = num; ???

OK, here is the equivalent snippet from ILSpy, which makes more sense, I guess the decompile to C# didn't produce a valid equivalent?

'C#'

 int& local = @Written;
 byte[] numArray2 = this.FInSpool;
 int num = (int) __Global.Min(numArray2 == null ? 0L : (long) numArray2.Length, (long) MaxSize);
 ^local = num;

IL

 byte[] expr_22 = this.FInSpool;
 Written = (int)__Global.Min((long)((expr_22 == null) ? 0 : expr_22.Length), (long)MaxSize);

So, I guess the 'C#' isn't quite valid? That IL would be valid C#, not sure why DotPeek produced the output it did. Perhaps I'll stick to ILSpy for this one...?

RJ Lohan
  • 6,497
  • 3
  • 34
  • 54
  • Are you sure you decompiled it to C#? THose are C++/CLI operators. – D Stanley Dec 17 '12 at 22:34
  • My guess would be `^local` relates to the fact that `Written` is an out parameter. It seems to be dereferencing the pointer. – Matt Greer Dec 17 '12 at 22:35
  • 1
    I don't think int& is a valid term in C#? Can you paste the original MSIL if possible? C# only exposes a subset of MSIL, so it has either been incorrectly decompiled, or the source language isn't C#. – Dominic Zukiewicz Dec 17 '12 at 22:36
  • Can you show us a) the IL and b) the Reflector output in C# and c) the original source? MSIL frequently uses (safe!) managed pointers internally. You are seeing a manifestation of them. – usr Dec 17 '12 at 22:51
  • @usr -> b) no sorry, no license for Reflector. c) no, it's non-open source 3rd party code. Will add the IL, it does make things a bit clearer. – RJ Lohan Dec 17 '12 at 23:03
  • DotPeek _still_ hasn't fixed this issue ten years later, so for those who stumble upon this question, [this answer](https://stackoverflow.com/questions/11067395/what-is-the-ampersand-character-at-the-end-of-an-object-type/11067654#11067654) explains the cause of the issue quite well. – Jacob Stamm Jan 22 '22 at 04:20

3 Answers3

9

If you look at the raw IL (from ildasm, not the C# equivalent via IL Spy), that may help you see what the decompiler is trying to say. 'Out' parameters are represented using a (managed) typed-reference, which isn't explicitly exposed as a type in C#. 'Instances' of this type can normally only be passed as parameters to methods accepting typed references ('ref' or 'out' parameters.) See OpCodes.Mkrefany for more information.

What dotPeek is complaining about is that this 'out' typed reference was stored from the argument into a local variable slot, then written to later via the local slot. The '@' and '^' are placeholders used to indicate this unexpected behavior detected by the decompiler (the one the ISSUE comments describe.)

It's possible the code was compiled from C++/CLI and thus the IL looks different from the typical C# compiler output. It's also possible this is some level of minor obfuscation to confuse decompilers (though I don't think so.) I don't think this is functionally any different from loading the reference from its argument onto the operation stack directly (avoiding the use of a local variable slot), but I could be wrong.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • 1
    This does seem to fit the situation. Thanks for the explanation. – RJ Lohan Dec 17 '12 at 23:36
  • 2
    Actually, I misspoke a bit; the particular method in your example is probably not using typed references (via mkrefany), but rather address references (i.e. type int&). That said, the basic issue is still the same; the decompiler wasn't expecting the int& to get stored into a local variable before being used. – Dan Bryant Dec 17 '12 at 23:46
3

Putting a @ before a name allows you to use a reserved name for a variable, For example if I wanted to have a variable called return I would need to do this.

public int Weird()
{
    int @return = 0;
    return @return;
}

See this SO question for more details.


Putting a ^ before the name ... umm, no clue. (will update as I research I could find any info on what ^ means when not being used as a XOR)

Community
  • 1
  • 1
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 1
    Thanks, understand the usage of @, I guess it's just not necessary in this case, as 'Written' is not a reserved word? – RJ Lohan Dec 17 '12 at 22:37
  • `written` is not reserved from what [I could tell](http://msdn.microsoft.com/en-us/library/aa664671%28v=vs.71%29.aspx), but it could one of the those context sensitive keywords [like get or set](http://msdn.microsoft.com/en-us/library/aa664452%28v=vs.71%29.aspx). – Scott Chamberlain Dec 17 '12 at 22:51
  • Or, there could be a literal @ sign in the identifier name. It's not legal C#, but, for example, the C# compiler itself uses strange characters such as `<` and `>` in the names of lambda functions. Or maybe the decompiler uses @ for some special purpose. – Qwertie Dec 17 '12 at 22:58
  • In this case, `@` is being generated because the decompiler is confused about [ref locals](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/ref-returns) and has nothing to do with reserved words. [This SO answer](https://stackoverflow.com/a/11067654/4028303) explains it well. – Jacob Stamm Jan 22 '22 at 03:39
0

It's clearly a decompilation issue. Because of broad set of languages supported decompiler to any particular language may not always find exact match, but still tries to produce some output. Decompiler MAY try to produce somewhat equivalent output, like in this case could be:

protected internal void DoReceive(ref byte[] Buffer, int MaxSize, out int Written)
{
    Written = 0;
    .        
    int num = SomeMethod();
    .
    Written = num;
}

but SHOULD it really do this? in this case decompiler actually provided you with a hint, so you could decide if this is important for your particular case, as there MAY be some side effects.

aiodintsov
  • 2,545
  • 15
  • 17