Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C#: params modifier on collection types. #18329

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions csharp/ql/lib/change-notes/2024-12-20-collection-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* C# 13: Added QL library support for *collection* like type `params` parameters.
3 changes: 2 additions & 1 deletion csharp/ql/lib/semmle/code/csharp/Callable.qll
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Stmt
import Type
import exprs.Call
private import commons.QualifiedName
private import commons.Collections
private import semmle.code.csharp.ExprOrStmtParent
private import semmle.code.csharp.metrics.Complexity
private import TypeRef
Expand Down Expand Up @@ -273,7 +274,7 @@ class Method extends Callable, Virtualizable, Attributable, @method {
Type getParamsType() {
exists(Parameter last | last = this.getParameter(this.getNumberOfParameters() - 1) |
last.isParams() and
result = last.getType().(ArrayType).getElementType()
result = last.getType().(ParamsCollectionType).getElementType()
)
}

Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/lib/semmle/code/csharp/Variable.qll
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class Parameter extends LocalScopeVariable, Attributable, TopLevelExprParent, @p
predicate isOutOrRef() { this.isOut() or this.isRef() }

/**
* Holds if this parameter is a parameter array. For example, `args`
* Holds if this parameter is a parameter collection. For example, `args`
* is a parameter array in
*
* ```csharp
Expand Down
38 changes: 38 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/commons/Collections.qll
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import csharp
import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.frameworks.system.collections.Generic

private string modifyMethodName() {
result =
Expand Down Expand Up @@ -67,6 +69,42 @@ class CollectionType extends RefType {
}
}

/**
* A collection type that can be used as a `params` parameter type.
*/
abstract private class ParamsCollectionTypeImpl extends ValueOrRefType {
/**
* Gets the element type of this collection, for example `int` in `IEnumerable<int>`.
*/
abstract Type getElementType();
}

private class AddArrayType extends ParamsCollectionTypeImpl instanceof ArrayType {
override Type getElementType() { result = ArrayType.super.getElementType() }
}

private class AddCollectionTypes extends ParamsCollectionTypeImpl {
private ConstructedType base;

AddCollectionTypes() {
exists(UnboundGenericType unboundbase |
base = this.getABaseType*() and unboundbase = base.getUnboundGeneric()
|
unboundbase instanceof SystemCollectionsGenericIEnumerableTInterface or
unboundbase instanceof SystemCollectionsGenericICollectionInterface or
unboundbase instanceof SystemCollectionsGenericIListTInterface or
unboundbase instanceof SystemCollectionsGenericIReadOnlyCollectionTInterface or
unboundbase instanceof SystemCollectionsGenericIReadOnlyListTInterface or
unboundbase instanceof SystemSpanStruct or
unboundbase instanceof SystemReadOnlySpanStruct
)
}

override Type getElementType() { result = base.getTypeArgument(0) }
}

final class ParamsCollectionType = ParamsCollectionTypeImpl;

/** Holds if `t` is a collection type. */
predicate isCollectionType(ValueOrRefType t) {
t.getABaseType*() instanceof SystemCollectionsIEnumerableInterface and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {

/**
* Holds if `arg` is a `params` argument of `c`, for parameter `p`, and `arg` will
* be wrapped in an array by the C# compiler.
* be wrapped in an collection by the C# compiler.
*/
private predicate isParamsArg(Call c, Expr arg, Parameter p) {
exists(Callable target, int numArgs |
Expand Down Expand Up @@ -1645,7 +1645,7 @@ private module ArgumentNodes {
}

/**
* A data-flow node that represents the implicit array creation in a call to a
* A data-flow node that represents the implicit collection creation in a call to a
* callable with a `params` parameter. For example, there is an implicit array
* creation `new [] { "a", "b", "c" }` in
*
Expand Down Expand Up @@ -1684,7 +1684,7 @@ private module ArgumentNodes {

override Location getLocationImpl() { result = callCfn.getLocation() }

override string toStringImpl() { result = "[implicit array creation] " + callCfn }
override string toStringImpl() { result = "[implicit collection creation] " + callCfn }
}

private class SummaryArgumentNode extends FlowSummaryNode, ArgumentNodeImpl {
Expand Down
3 changes: 2 additions & 1 deletion csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import csharp
private import semmle.code.csharp.commons.Collections
private import RuntimeCallable

/** A call. */
Expand Down Expand Up @@ -1137,7 +1138,7 @@ private module Internal {
if p.isParams()
then (
j >= i and
paramType = p.getType().(ArrayType).getElementType()
paramType = p.getType().(ParamsCollectionType).getElementType()
) else (
i = j and
paramType = p.getType()
Expand Down
5 changes: 4 additions & 1 deletion csharp/ql/lib/semmle/code/csharp/frameworks/Format.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import csharp
private import semmle.code.csharp.commons.Collections
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.frameworks.system.Text

Expand Down Expand Up @@ -106,7 +107,9 @@ class StringFormatItemParameter extends Parameter {
}

private Type getParameterType(Parameter p) {
if p.isParams() then result = p.getType().(ArrayType).getElementType() else result = p.getType()
if p.isParams()
then result = p.getType().(ParamsCollectionType).getElementType()
else result = p.getType()
}

/** Regex for a valid insert. */
Expand Down
16 changes: 16 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/frameworks/System.qll
Original file line number Diff line number Diff line change
Expand Up @@ -755,3 +755,19 @@ class SystemNotImplementedExceptionClass extends SystemClass {
class SystemDateTimeStruct extends SystemStruct {
SystemDateTimeStruct() { this.hasName("DateTime") }
}

/** The `System.Span<T>` struct. */
class SystemSpanStruct extends SystemUnboundGenericStruct {
SystemSpanStruct() {
this.hasName("Span`1") and
this.getNumberOfTypeParameters() = 1
}
}

/** The `System.ReadOnlySpan<T>` struct. */
class SystemReadOnlySpanStruct extends SystemUnboundGenericStruct {
SystemReadOnlySpanStruct() {
this.hasName("ReadOnlySpan`1") and
this.getNumberOfTypeParameters() = 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,21 @@ class SystemCollectionsGenericIDictionaryInterface extends SystemCollectionsGene
this.getNumberOfTypeParameters() = 2
}
}

/** The ``System.Collections.Generic.IReadOnlyCollection`1`` interface. */
class SystemCollectionsGenericIReadOnlyCollectionTInterface extends SystemCollectionsGenericUnboundGenericInterface
{
SystemCollectionsGenericIReadOnlyCollectionTInterface() {
this.hasName("IReadOnlyCollection`1") and
this.getNumberOfTypeParameters() = 1
}
}

/** The ``System.Collections.Generic.IReadOnlyList`1`` interface. */
class SystemCollectionsGenericIReadOnlyListTInterface extends SystemCollectionsGenericUnboundGenericInterface
{
SystemCollectionsGenericIReadOnlyListTInterface() {
this.hasName("IReadOnlyList`1") and
this.getNumberOfTypeParameters() = 1
}
}
Loading
Loading