Skip to content

Commit

Permalink
Merge pull request #18329 from michaelnebel/csharp/params
Browse files Browse the repository at this point in the history
C# 13: params modifier on collection types.
  • Loading branch information
michaelnebel authored Jan 3, 2025
2 parents f23e56b + fe4ec59 commit 7cdaa79
Show file tree
Hide file tree
Showing 30 changed files with 2,234 additions and 1,752 deletions.
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 ParamsArrayType extends ParamsCollectionTypeImpl instanceof ArrayType {
override Type getElementType() { result = ArrayType.super.getElementType() }
}

private class ParamsConstructedCollectionTypes extends ParamsCollectionTypeImpl {
private ConstructedType base;

ParamsConstructedCollectionTypes() {
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

0 comments on commit 7cdaa79

Please sign in to comment.