Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.Kinds;
Expand All @@ -17,6 +18,35 @@ protected ElementAccess(ExpressionNodeInfo info, ExpressionSyntax qualifier, Bra
private readonly ExpressionSyntax qualifier;
private readonly BracketedArgumentListSyntax argumentList;


private IPropertySymbol? GetIndexerSymbol()
{
var symbol = Context.GetSymbolInfo(base.Syntax).Symbol;

if (symbol is IPropertySymbol { IsIndexer: true } indexer)
return indexer;

// In some cases, Roslyn translates the use of range expressions directly into method calls.
// E.g. `a[0..3]` is translated into `a.Slice(0, 3)`, if `a` is a `Span<T>`.
// In this case, we want to populate the indexer access as normal (as this reflects the source code more accurately).
if (symbol is IMethodSymbol method)
{
var indexers = method
.ContainingType
.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.IsIndexer);

var intIndexer = indexers
.Where(i => i.Parameters.Length == 1 && i.Parameters[0].Type.SpecialType == SpecialType.System_Int32)
.FirstOrDefault();

return intIndexer;
}
Comment on lines +32 to +45
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is reasonable to assume that Roslyn only translates a[range] into a method call for integer range expressions (and thus we implicitly want to extract the indexer with an int parameter).


return null;
}

protected override void PopulateExpression(TextWriter trapFile)
{
if (Kind == ExprKind.POINTER_INDIRECTION)
Expand All @@ -32,9 +62,8 @@ protected override void PopulateExpression(TextWriter trapFile)
Create(Context, qualifier, this, -1);
PopulateArguments(trapFile, argumentList, 0);

var symbolInfo = Context.GetSymbolInfo(base.Syntax);

if (symbolInfo.Symbol is IPropertySymbol indexer)
var indexer = GetIndexerSymbol();
if (indexer is not null)
{
trapFile.expr_access(this, Indexer.Create(Context, indexer));
}
Expand Down
4 changes: 4 additions & 0 deletions csharp/ql/lib/change-notes/2026-05-21-spanaccess-range.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved extraction of span range-access expressions (for example, `a[0..3]`) so the indexer is recorded as the call target.
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
| Quality.cs:26:19:26:26 | access to indexer | Call without target $@. | Quality.cs:26:19:26:26 | access to indexer | access to indexer |
| Quality.cs:29:21:29:27 | access to indexer | Call without target $@. | Quality.cs:29:21:29:27 | access to indexer | access to indexer |
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,5 @@
| Quality.cs:20:13:20:23 | access to property MyProperty6 | Call without target $@. | Quality.cs:20:13:20:23 | access to property MyProperty6 | access to property MyProperty6 |
| Quality.cs:23:9:23:14 | access to event Event1 | Call without target $@. | Quality.cs:23:9:23:14 | access to event Event1 | access to event Event1 |
| Quality.cs:23:9:23:30 | delegate call | Call without target $@. | Quality.cs:23:9:23:30 | delegate call | delegate call |
| Quality.cs:26:19:26:26 | access to indexer | Call without target $@. | Quality.cs:26:19:26:26 | access to indexer | access to indexer |
| Quality.cs:29:21:29:27 | access to indexer | Call without target $@. | Quality.cs:29:21:29:27 | access to indexer | access to indexer |
| Quality.cs:38:16:38:26 | access to property MyProperty2 | Call without target $@. | Quality.cs:38:16:38:26 | access to property MyProperty2 | access to property MyProperty2 |
| Quality.cs:50:20:50:26 | object creation of type T | Call without target $@. | Quality.cs:50:20:50:26 | object creation of type T | object creation of type T |
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ public Test()
Event1.Invoke(this, 5);

var str = "abcd";
var sub = str[..3]; // TODO: this is not an indexer call, but rather a `str.Substring(0, 3)` call.
var sub = str[..3];

Span<int> sp = null;
var slice = sp[..3]; // TODO: this is not an indexer call, but rather a `sp.Slice(0, 3)` call.
var slice = sp[..3];

Span<byte> guidBytes = stackalloc byte[16];
guidBytes[08] = 1;
Expand Down
Loading