Skip to content
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
19 changes: 18 additions & 1 deletion CodeConverter/CSharp/CommonConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -664,11 +664,28 @@ public bool HasOutAttribute(VBSyntax.AttributeListSyntax a)

public bool IsExtensionAttribute(VBSyntax.AttributeSyntax a)
{
if (a.SyntaxTree != SemanticModel.SyntaxTree)
return AttributeNameMatches(a, "Extension");
return (SemanticModel.GetTypeInfo(a).ConvertedType?.GetFullMetadataName())
?.Equals(ExtensionAttributeType.FullName, StringComparison.Ordinal) == true;
}

public bool IsOutAttribute(VBSyntax.AttributeSyntax a) => SemanticModel.GetTypeInfo(a).ConvertedType.IsOutAttribute();
public bool IsOutAttribute(VBSyntax.AttributeSyntax a)
{
if (a.SyntaxTree != SemanticModel.SyntaxTree)
return AttributeNameMatches(a, "Out");
return SemanticModel.GetTypeInfo(a).ConvertedType.IsOutAttribute();
}

// SemanticModel.GetTypeInfo throws when the node is not in its syntax tree; fall back to name matching.
private static bool AttributeNameMatches(VBSyntax.AttributeSyntax a, string shortName)
{
var name = a.Name.ToString();
return name.Equals(shortName, StringComparison.Ordinal) ||
name.Equals(shortName + "Attribute", StringComparison.Ordinal) ||
name.EndsWith("." + shortName, StringComparison.Ordinal) ||
name.EndsWith("." + shortName + "Attribute", StringComparison.Ordinal);
}

public ISymbol GetCsOriginalSymbolOrNull(ISymbol symbol)
{
Expand Down
47 changes: 47 additions & 0 deletions Tests/CSharp/ExpressionTests/ByRefTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Threading.Tasks;
using ICSharpCode.CodeConverter.Common;
using ICSharpCode.CodeConverter.CSharp;
using ICSharpCode.CodeConverter.Tests.TestRunners;
using Xunit;

Expand Down Expand Up @@ -914,4 +916,49 @@ public static void LogAndReset(ref int arg)
}");
}

[Fact]
public async Task Issue1225_OutAttributeOnParameterFromOtherFileDoesNotCrashAsync()
{
// Issue #1225: IsOutAttribute called SemanticModel.GetTypeInfo(attribute) on an attribute node
// that belongs to a different syntax tree (parameter declared in a separate source file).
// This caused ArgumentException "Knoten ist nicht innerhalb Syntaxbaum" /
// "Node is not within syntax tree". The fix falls back to name-based checking
// when the attribute's syntax tree differs from the current semantic model's tree.
var serviceFileContent = @"Imports System.Runtime.InteropServices
Public Class LicenseService
Public Shared ReadOnly Property Instance As LicenseService
Get
Return New LicenseService()
End Get
End Property

Public Function GetLicenseMaybe(<Out> ByRef licenseName As String) As Boolean
licenseName = Nothing
Return False
End Function
End Class";

var callerFileContent = @"Public Class Caller
Public Sub Test(licenseName As String)
Dim res = LicenseService.Instance.GetLicenseMaybe(licenseName)
End Sub
End Class";

var options = new TextConversionOptions(DefaultReferences.With()) { ShowCompilationErrors = true };
var languageConversion = new VBToCSConversion { ConversionOptions = options };
var serviceTree = languageConversion.CreateTree(serviceFileContent);
var callerTree = languageConversion.CreateTree(callerFileContent);

// Add both files to the same project so that the VB compilation sees both
var serviceDoc = await languageConversion.CreateProjectDocumentFromTreeAsync(serviceTree, options.References);
var callerDoc = serviceDoc.Project.AddDocumentFromTree(callerTree);

// Convert only the caller document; its parameter info comes from the service file's syntax tree
var result = await ProjectConversion.ConvertSingleAsync<VBToCSConversion>(callerDoc, options);
var output = (result.ConvertedCode ?? "") + (result.GetExceptionsAsString() ?? "");

Assert.DoesNotContain("#error", output);
Assert.Contains("GetLicenseMaybe", output);
}

}
Loading