From c0974f364e69c163cfec7383152e6b741def9c6b Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 2 Jan 2025 14:43:34 +0100 Subject: [PATCH 01/14] C#: General type constraints tests. --- .../TypeParameterConstraints.cs | 19 ++++++++++++ .../typeParameterConstraints.expected | 20 ++++++++++++ .../typeParameterConstraints.ql | 31 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 csharp/ql/test/library-tests/typeparameterconstraints/TypeParameterConstraints.cs create mode 100644 csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected create mode 100644 csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql diff --git a/csharp/ql/test/library-tests/typeparameterconstraints/TypeParameterConstraints.cs b/csharp/ql/test/library-tests/typeparameterconstraints/TypeParameterConstraints.cs new file mode 100644 index 000000000000..a284d53713cb --- /dev/null +++ b/csharp/ql/test/library-tests/typeparameterconstraints/TypeParameterConstraints.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +public class TestClass +{ + public void M1(T1 x) where T1 : class { } + + public void M2(T2 x) where T2 : struct { } + + public void M3(T3 x) where T3 : unmanaged { } + + public void M4(T4 x) where T4 : new() { } + + public void M5(T5 x) where T5 : notnull { } + + public void M6(T6 x) where T6 : IList { } + + public void M7(T7 x) where T7 : allows ref struct { } +} diff --git a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected new file mode 100644 index 000000000000..3d594d077f0f --- /dev/null +++ b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected @@ -0,0 +1,20 @@ +typeParameterContraints +| TypeParameterConstraints.cs:6:20:6:21 | T1 | file://:0:0:0:0 | where T1: ... | +| TypeParameterConstraints.cs:8:20:8:21 | T2 | file://:0:0:0:0 | where T2: ... | +| TypeParameterConstraints.cs:10:20:10:21 | T3 | file://:0:0:0:0 | where T3: ... | +| TypeParameterConstraints.cs:12:20:12:21 | T4 | file://:0:0:0:0 | where T4: ... | +| TypeParameterConstraints.cs:14:20:14:21 | T5 | file://:0:0:0:0 | where T5: ... | +| TypeParameterConstraints.cs:16:20:16:21 | T6 | file://:0:0:0:0 | where T6: ... | +| TypeParameterConstraints.cs:18:20:18:21 | T7 | file://:0:0:0:0 | where T7: ... | +specificParameterConstraints +| TypeParameterConstraints.cs:16:20:16:21 | T6 | IList | +hasConstructorConstraint +| TypeParameterConstraints.cs:12:20:12:21 | T4 | file://:0:0:0:0 | where T4: ... | +hasRefTypeConstraint +| TypeParameterConstraints.cs:6:20:6:21 | T1 | file://:0:0:0:0 | where T1: ... | +hasValueTypeConstraint +| TypeParameterConstraints.cs:8:20:8:21 | T2 | file://:0:0:0:0 | where T2: ... | +| TypeParameterConstraints.cs:10:20:10:21 | T3 | file://:0:0:0:0 | where T3: ... | +hasUnmanagedTypeConstraint +| TypeParameterConstraints.cs:10:20:10:21 | T3 | file://:0:0:0:0 | where T3: ... | +hasNullableRefTypeConstraint diff --git a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql new file mode 100644 index 000000000000..c77f04d97b3c --- /dev/null +++ b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql @@ -0,0 +1,31 @@ +import csharp + +query predicate typeParameterContraints(TypeParameter tp, TypeParameterConstraints tpc) { + tp.fromSource() and tp.getConstraints() = tpc +} + +query predicate specificParameterConstraints(TypeParameter tp, string type) { + exists(TypeParameterConstraints tpc | + typeParameterContraints(tp, tpc) and type = tpc.getATypeConstraint().toStringWithTypes() + ) +} + +query predicate hasConstructorConstraint(TypeParameter tp, TypeParameterConstraints tpc) { + typeParameterContraints(tp, tpc) and tpc.hasConstructorConstraint() +} + +query predicate hasRefTypeConstraint(TypeParameter tp, TypeParameterConstraints tpc) { + typeParameterContraints(tp, tpc) and tpc.hasRefTypeConstraint() +} + +query predicate hasValueTypeConstraint(TypeParameter tp, TypeParameterConstraints tpc) { + typeParameterContraints(tp, tpc) and tpc.hasValueTypeConstraint() +} + +query predicate hasUnmanagedTypeConstraint(TypeParameter tp, TypeParameterConstraints tpc) { + typeParameterContraints(tp, tpc) and tpc.hasUnmanagedTypeConstraint() +} + +query predicate hasNullableRefTypeConstraint(TypeParameter tp, TypeParameterConstraints tpc) { + typeParameterContraints(tp, tpc) and tpc.hasNullableRefTypeConstraint() +} From 958d8f1f01b737728603bf9f03c181ac4e3b01ae Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 2 Jan 2025 14:49:13 +0100 Subject: [PATCH 02/14] C#: Add extractor support for the notnull general type parameter constraint. --- .../Entities/Types/TypeParameterConstraints.cs | 3 +++ csharp/ql/lib/semmle/code/csharp/Generics.qll | 3 +++ .../typeParameterConstraints.expected | 2 ++ .../typeparameterconstraints/typeParameterConstraints.ql | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameterConstraints.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameterConstraints.cs index 6f85759dee0f..cdf3a5054518 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameterConstraints.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameterConstraints.cs @@ -40,6 +40,9 @@ public override void Populate(TextWriter trapFile) if (Symbol.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated) trapFile.general_type_parameter_constraints(this, 5); + if (Symbol.HasNotNullConstraint) + trapFile.general_type_parameter_constraints(this, 6); + foreach (var abase in Symbol.GetAnnotatedTypeConstraints()) { var t = Type.Create(Context, abase.Symbol); diff --git a/csharp/ql/lib/semmle/code/csharp/Generics.qll b/csharp/ql/lib/semmle/code/csharp/Generics.qll index 4790c9637fc4..74b300d111f8 100644 --- a/csharp/ql/lib/semmle/code/csharp/Generics.qll +++ b/csharp/ql/lib/semmle/code/csharp/Generics.qll @@ -287,6 +287,9 @@ class TypeParameterConstraints extends Element, @type_parameter_constraints { /** Holds if these constraints include a nullable reference type constraint. */ predicate hasNullableRefTypeConstraint() { general_type_parameter_constraints(this, 5) } + /** Holds if these constraints include a not-null type constraint. */ + predicate hasNotNullTypeConstraint() { general_type_parameter_constraints(this, 6) } + /** Gets a textual representation of these constraints. */ override string toString() { result = "where " + this.getTypeParameter().getName() + ": ..." } diff --git a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected index 3d594d077f0f..79d70d524c12 100644 --- a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected +++ b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected @@ -18,3 +18,5 @@ hasValueTypeConstraint hasUnmanagedTypeConstraint | TypeParameterConstraints.cs:10:20:10:21 | T3 | file://:0:0:0:0 | where T3: ... | hasNullableRefTypeConstraint +hasNotNullConstraint +| TypeParameterConstraints.cs:14:20:14:21 | T5 | file://:0:0:0:0 | where T5: ... | diff --git a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql index c77f04d97b3c..7b6cd5513237 100644 --- a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql +++ b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql @@ -29,3 +29,7 @@ query predicate hasUnmanagedTypeConstraint(TypeParameter tp, TypeParameterConstr query predicate hasNullableRefTypeConstraint(TypeParameter tp, TypeParameterConstraints tpc) { typeParameterContraints(tp, tpc) and tpc.hasNullableRefTypeConstraint() } + +query predicate hasNotNullConstraint(TypeParameter tp, TypeParameterConstraints tpc) { + typeParameterContraints(tp, tpc) and tpc.hasNotNullTypeConstraint() +} From d9158c8cd5e3c9d95f84fc7e69581e54c0176a87 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 15:35:23 +0100 Subject: [PATCH 03/14] Fixup of second commit. --- csharp/ql/lib/semmle/code/csharp/Generics.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp/ql/lib/semmle/code/csharp/Generics.qll b/csharp/ql/lib/semmle/code/csharp/Generics.qll index 74b300d111f8..b169b1fb9d29 100644 --- a/csharp/ql/lib/semmle/code/csharp/Generics.qll +++ b/csharp/ql/lib/semmle/code/csharp/Generics.qll @@ -287,7 +287,7 @@ class TypeParameterConstraints extends Element, @type_parameter_constraints { /** Holds if these constraints include a nullable reference type constraint. */ predicate hasNullableRefTypeConstraint() { general_type_parameter_constraints(this, 5) } - /** Holds if these constraints include a not-null type constraint. */ + /** Holds if these constraints include a notnull type constraint. */ predicate hasNotNullTypeConstraint() { general_type_parameter_constraints(this, 6) } /** Gets a textual representation of these constraints. */ From 41dc4a5503e4ffdd586bd3776cc49a6d50c7c586 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 2 Jan 2025 15:02:14 +0100 Subject: [PATCH 04/14] C#: Add extractor support for the allows ref struct general type parameter constraint. --- .../Entities/Types/TypeParameterConstraints.cs | 3 +++ csharp/ql/lib/semmle/code/csharp/Generics.qll | 3 +++ .../typeParameterConstraints.expected | 2 ++ .../typeparameterconstraints/typeParameterConstraints.ql | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameterConstraints.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameterConstraints.cs index cdf3a5054518..c5dae364a898 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameterConstraints.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameterConstraints.cs @@ -43,6 +43,9 @@ public override void Populate(TextWriter trapFile) if (Symbol.HasNotNullConstraint) trapFile.general_type_parameter_constraints(this, 6); + if (Symbol.AllowsRefLikeType) + trapFile.general_type_parameter_constraints(this, 7); + foreach (var abase in Symbol.GetAnnotatedTypeConstraints()) { var t = Type.Create(Context, abase.Symbol); diff --git a/csharp/ql/lib/semmle/code/csharp/Generics.qll b/csharp/ql/lib/semmle/code/csharp/Generics.qll index b169b1fb9d29..81535fc1008a 100644 --- a/csharp/ql/lib/semmle/code/csharp/Generics.qll +++ b/csharp/ql/lib/semmle/code/csharp/Generics.qll @@ -290,6 +290,9 @@ class TypeParameterConstraints extends Element, @type_parameter_constraints { /** Holds if these constraints include a notnull type constraint. */ predicate hasNotNullTypeConstraint() { general_type_parameter_constraints(this, 6) } + /** Holds if these constraints include a `allows ref struct` constraint. */ + predicate hasAllowRefLikeTypeConstraint() { general_type_parameter_constraints(this, 7) } + /** Gets a textual representation of these constraints. */ override string toString() { result = "where " + this.getTypeParameter().getName() + ": ..." } diff --git a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected index 79d70d524c12..3565e727cd64 100644 --- a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected +++ b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.expected @@ -20,3 +20,5 @@ hasUnmanagedTypeConstraint hasNullableRefTypeConstraint hasNotNullConstraint | TypeParameterConstraints.cs:14:20:14:21 | T5 | file://:0:0:0:0 | where T5: ... | +hasAllowRefLikeTypeConstraint +| TypeParameterConstraints.cs:18:20:18:21 | T7 | file://:0:0:0:0 | where T7: ... | diff --git a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql index 7b6cd5513237..3c82c8bab14f 100644 --- a/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql +++ b/csharp/ql/test/library-tests/typeparameterconstraints/typeParameterConstraints.ql @@ -33,3 +33,7 @@ query predicate hasNullableRefTypeConstraint(TypeParameter tp, TypeParameterCons query predicate hasNotNullConstraint(TypeParameter tp, TypeParameterConstraints tpc) { typeParameterContraints(tp, tpc) and tpc.hasNotNullTypeConstraint() } + +query predicate hasAllowRefLikeTypeConstraint(TypeParameter tp, TypeParameterConstraints tpc) { + typeParameterContraints(tp, tpc) and tpc.hasAllowRefLikeTypeConstraint() +} From 9a2edc3d5cd2d3dac85cf9b8bfcf30faddaa1594 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 14:03:05 +0100 Subject: [PATCH 05/14] C#: Add ref struct boxing example (false positive). --- csharp/ql/test/library-tests/conversion/boxing/Boxing.cs | 3 +++ csharp/ql/test/library-tests/conversion/boxing/Boxing.expected | 3 +++ 2 files changed, 6 insertions(+) diff --git a/csharp/ql/test/library-tests/conversion/boxing/Boxing.cs b/csharp/ql/test/library-tests/conversion/boxing/Boxing.cs index a37d39aa2a04..dbfe4acac4b9 100644 --- a/csharp/ql/test/library-tests/conversion/boxing/Boxing.cs +++ b/csharp/ql/test/library-tests/conversion/boxing/Boxing.cs @@ -45,3 +45,6 @@ void M() x1 = x15; // not a boxing conversion } } + +// Ref structs can't be converted to a dynamic, object or valuetype. +ref struct S { } diff --git a/csharp/ql/test/library-tests/conversion/boxing/Boxing.expected b/csharp/ql/test/library-tests/conversion/boxing/Boxing.expected index 1d6fb24e3588..5ce47702bca9 100644 --- a/csharp/ql/test/library-tests/conversion/boxing/Boxing.expected +++ b/csharp/ql/test/library-tests/conversion/boxing/Boxing.expected @@ -117,6 +117,9 @@ | Nullable | Object | | Nullable | ValueType | | Nullable | dynamic | +| S | Object | +| S | ValueType | +| S | dynamic | | T1 | Object | | T1 | dynamic | | T3 | Object | From ef9f09ebfc20b99e6393a5c97d591e0bd3cb3047 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 14:05:18 +0100 Subject: [PATCH 06/14] C#: Do not consider ref struct as being convertible to object, dynamic and valuetype. --- csharp/ql/lib/semmle/code/csharp/Conversion.qll | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/Conversion.qll b/csharp/ql/lib/semmle/code/csharp/Conversion.qll index 636a48c09e26..a1ec1e44856b 100644 --- a/csharp/ql/lib/semmle/code/csharp/Conversion.qll +++ b/csharp/ql/lib/semmle/code/csharp/Conversion.qll @@ -649,11 +649,14 @@ predicate convBoxing(Type fromType, Type toType) { } private predicate convBoxingValueType(ValueType fromType, Type toType) { - toType instanceof ObjectType - or - toType instanceof DynamicType - or - toType instanceof SystemValueTypeClass + ( + toType instanceof ObjectType + or + toType instanceof DynamicType + or + toType instanceof SystemValueTypeClass + ) and + not fromType.(Struct).isRef() or toType = fromType.getABaseInterface+() } From b9fce5eb9d1e8bf358402064272a8726edfb542f Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 14:06:50 +0100 Subject: [PATCH 07/14] C#: Update boxing conversion expected output. --- csharp/ql/test/library-tests/conversion/boxing/Boxing.expected | 3 --- 1 file changed, 3 deletions(-) diff --git a/csharp/ql/test/library-tests/conversion/boxing/Boxing.expected b/csharp/ql/test/library-tests/conversion/boxing/Boxing.expected index 5ce47702bca9..1d6fb24e3588 100644 --- a/csharp/ql/test/library-tests/conversion/boxing/Boxing.expected +++ b/csharp/ql/test/library-tests/conversion/boxing/Boxing.expected @@ -117,9 +117,6 @@ | Nullable | Object | | Nullable | ValueType | | Nullable | dynamic | -| S | Object | -| S | ValueType | -| S | dynamic | | T1 | Object | | T1 | dynamic | | T3 | Object | From ef5ae3f1ae6f7b3fb85d20f623eacdd74a5849b5 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 14:34:26 +0100 Subject: [PATCH 08/14] C#: Add some unification and viable callable test cases. --- .../library-tests/dispatch/ViableCallable.cs | 30 +++++++++++++++++++ .../library-tests/unification/Unification.cs | 8 +++++ 2 files changed, 38 insertions(+) diff --git a/csharp/ql/test/library-tests/dispatch/ViableCallable.cs b/csharp/ql/test/library-tests/dispatch/ViableCallable.cs index 55a67b69e0d6..b28257010b3e 100644 --- a/csharp/ql/test/library-tests/dispatch/ViableCallable.cs +++ b/csharp/ql/test/library-tests/dispatch/ViableCallable.cs @@ -588,3 +588,33 @@ void Run(T c) where T : I3 c.M13(); } } + +public class C21 +{ + public interface I + { + void M(); + } + + public class A1 : I + { + public void M() { } + } + + public ref struct A2 : I + { + public void M() { } + } + + public void Run1(T t) where T : I + { + // Viable callable: A1.M() [also reports A2.M(); false positive] + t.M(); + } + + public void Run2(T t) where T : I, allows ref struct + { + // Viable callable: {A1, A2}.M() + t.M(); + } +} diff --git a/csharp/ql/test/library-tests/unification/Unification.cs b/csharp/ql/test/library-tests/unification/Unification.cs index e392911030c8..50aab4943569 100644 --- a/csharp/ql/test/library-tests/unification/Unification.cs +++ b/csharp/ql/test/library-tests/unification/Unification.cs @@ -48,3 +48,11 @@ public class NestedC { } Nested.NestedB.NestedC x5; Nested.NestedB.NestedC x6; } + +interface I2 { } +struct S3 : I2 { } +ref struct RS : I2 { } +class C7 : I2 { } + +class NormalConstraint where T : I2 { } // False positive: Allows T to be `RS`. +class NegativeConstraint where T : I2, allows ref struct { } From 33939a80416cd91195bc5e869705e499b506a1bb Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 14:42:50 +0100 Subject: [PATCH 09/14] C#: Update test expected output. --- .../dispatch/CallContext.expected | 2 + .../library-tests/dispatch/CallGraph.expected | 4 ++ .../dispatch/GetADynamicTarget.expected | 4 ++ .../unification/Unification.expected | 42 +++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/csharp/ql/test/library-tests/dispatch/CallContext.expected b/csharp/ql/test/library-tests/dispatch/CallContext.expected index 7234249cbff1..049464b8ca94 100644 --- a/csharp/ql/test/library-tests/dispatch/CallContext.expected +++ b/csharp/ql/test/library-tests/dispatch/CallContext.expected @@ -26,3 +26,5 @@ mayBenefitFromCallContext | ViableCallable.cs:576:18:576:22 | call to operator / | | ViableCallable.cs:579:26:579:30 | call to operator checked / | | ViableCallable.cs:585:9:585:15 | call to method M12 | +| ViableCallable.cs:612:9:612:13 | call to method M | +| ViableCallable.cs:618:9:618:13 | call to method M | diff --git a/csharp/ql/test/library-tests/dispatch/CallGraph.expected b/csharp/ql/test/library-tests/dispatch/CallGraph.expected index e8087b773612..192226d37b8f 100644 --- a/csharp/ql/test/library-tests/dispatch/CallGraph.expected +++ b/csharp/ql/test/library-tests/dispatch/CallGraph.expected @@ -259,3 +259,7 @@ | ViableCallable.cs:555:10:555:15 | Run`1 | ViableCallable.cs:550:40:550:40 | checked / | | ViableCallable.cs:555:10:555:15 | Run`1 | ViableCallable.cs:552:17:552:19 | M11 | | ViableCallable.cs:555:10:555:15 | Run`1 | ViableCallable.cs:553:17:553:19 | M12 | +| ViableCallable.cs:609:17:609:23 | Run1`1 | ViableCallable.cs:601:21:601:21 | M | +| ViableCallable.cs:609:17:609:23 | Run1`1 | ViableCallable.cs:606:21:606:21 | M | +| ViableCallable.cs:615:17:615:23 | Run2`1 | ViableCallable.cs:601:21:601:21 | M | +| ViableCallable.cs:615:17:615:23 | Run2`1 | ViableCallable.cs:606:21:606:21 | M | diff --git a/csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected b/csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected index 8849a785a1cb..e8609224d2db 100644 --- a/csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected +++ b/csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected @@ -505,3 +505,7 @@ | ViableCallable.cs:585:9:585:15 | call to method M12 | C20.M12() | | ViableCallable.cs:585:9:585:15 | call to method M12 | I3.M12() | | ViableCallable.cs:588:9:588:15 | call to method M13 | I3.M13() | +| ViableCallable.cs:612:9:612:13 | call to method M | C21+A1.M() | +| ViableCallable.cs:612:9:612:13 | call to method M | C21+A2.M() | +| ViableCallable.cs:618:9:618:13 | call to method M | C21+A1.M() | +| ViableCallable.cs:618:9:618:13 | call to method M | C21+A2.M() | diff --git a/csharp/ql/test/library-tests/unification/Unification.expected b/csharp/ql/test/library-tests/unification/Unification.expected index 7f837911baca..fac47754e195 100644 --- a/csharp/ql/test/library-tests/unification/Unification.expected +++ b/csharp/ql/test/library-tests/unification/Unification.expected @@ -7,6 +7,8 @@ constrainedTypeParameterSubsumes | Unification.cs:8:10:8:11 | T2 | Unification.cs:30:12:30:24 | (string, int) | | Unification.cs:8:10:8:11 | T2 | Unification.cs:31:12:31:23 | (string, T9) | | Unification.cs:8:10:8:11 | T2 | Unification.cs:32:12:32:19 | (T8, T9) | +| Unification.cs:8:10:8:11 | T2 | Unification.cs:53:8:53:9 | S3 | +| Unification.cs:8:10:8:11 | T2 | Unification.cs:54:12:54:13 | RS | | Unification.cs:9:10:9:11 | T3 | Unification.cs:1:11:1:12 | I1 | | Unification.cs:9:10:9:11 | T3 | Unification.cs:6:7:6:8 | C0 | | Unification.cs:9:10:9:11 | T3 | Unification.cs:7:7:7:12 | C1 | @@ -57,6 +59,10 @@ constrainedTypeParameterSubsumes | Unification.cs:9:10:9:11 | T3 | Unification.cs:41:22:41:33 | Nested+NestedB+NestedC | | Unification.cs:9:10:9:11 | T3 | Unification.cs:41:22:41:33 | Nested+NestedB+NestedC | | Unification.cs:9:10:9:11 | T3 | Unification.cs:41:22:41:33 | Nested`1+NestedB+NestedC | +| Unification.cs:9:10:9:11 | T3 | Unification.cs:52:11:52:12 | I2 | +| Unification.cs:9:10:9:11 | T3 | Unification.cs:55:7:55:8 | C7 | +| Unification.cs:9:10:9:11 | T3 | Unification.cs:57:7:57:25 | NormalConstraint | +| Unification.cs:9:10:9:11 | T3 | Unification.cs:58:7:58:27 | NegativeConstraint | | Unification.cs:10:10:10:11 | T4 | Unification.cs:7:7:7:12 | C1 | | Unification.cs:10:10:10:11 | T4 | Unification.cs:10:10:10:11 | T4 | | Unification.cs:11:10:11:11 | T5 | Unification.cs:8:7:8:12 | C2 | @@ -96,8 +102,22 @@ constrainedTypeParameterSubsumes | Unification.cs:12:25:12:27 | T6d | Unification.cs:30:12:30:24 | (string, int) | | Unification.cs:12:25:12:27 | T6d | Unification.cs:31:12:31:23 | (string, T9) | | Unification.cs:12:25:12:27 | T6d | Unification.cs:32:12:32:19 | (T8, T9) | +| Unification.cs:12:25:12:27 | T6d | Unification.cs:53:8:53:9 | S3 | +| Unification.cs:12:25:12:27 | T6d | Unification.cs:54:12:54:13 | RS | | Unification.cs:24:12:24:13 | Tm | Unification.cs:8:7:8:12 | C2 | | Unification.cs:24:12:24:13 | Tm | Unification.cs:24:12:24:13 | Tm | +| Unification.cs:57:24:57:24 | T | Unification.cs:52:11:52:12 | I2 | +| Unification.cs:57:24:57:24 | T | Unification.cs:53:8:53:9 | S3 | +| Unification.cs:57:24:57:24 | T | Unification.cs:54:12:54:13 | RS | +| Unification.cs:57:24:57:24 | T | Unification.cs:55:7:55:8 | C7 | +| Unification.cs:57:24:57:24 | T | Unification.cs:57:24:57:24 | T | +| Unification.cs:57:24:57:24 | T | Unification.cs:58:26:58:26 | T | +| Unification.cs:58:26:58:26 | T | Unification.cs:52:11:52:12 | I2 | +| Unification.cs:58:26:58:26 | T | Unification.cs:53:8:53:9 | S3 | +| Unification.cs:58:26:58:26 | T | Unification.cs:54:12:54:13 | RS | +| Unification.cs:58:26:58:26 | T | Unification.cs:55:7:55:8 | C7 | +| Unification.cs:58:26:58:26 | T | Unification.cs:57:24:57:24 | T | +| Unification.cs:58:26:58:26 | T | Unification.cs:58:26:58:26 | T | constrainedTypeParameterSubsumptionImpliesUnification constrainedTypeParameterUnifiable | Unification.cs:8:10:8:11 | T2 | Unification.cs:3:8:3:9 | S1 | @@ -108,6 +128,8 @@ constrainedTypeParameterUnifiable | Unification.cs:8:10:8:11 | T2 | Unification.cs:30:12:30:24 | (string, int) | | Unification.cs:8:10:8:11 | T2 | Unification.cs:31:12:31:23 | (string, T9) | | Unification.cs:8:10:8:11 | T2 | Unification.cs:32:12:32:19 | (T8, T9) | +| Unification.cs:8:10:8:11 | T2 | Unification.cs:53:8:53:9 | S3 | +| Unification.cs:8:10:8:11 | T2 | Unification.cs:54:12:54:13 | RS | | Unification.cs:9:10:9:11 | T3 | Unification.cs:1:11:1:12 | I1 | | Unification.cs:9:10:9:11 | T3 | Unification.cs:6:7:6:8 | C0 | | Unification.cs:9:10:9:11 | T3 | Unification.cs:7:7:7:12 | C1 | @@ -158,6 +180,10 @@ constrainedTypeParameterUnifiable | Unification.cs:9:10:9:11 | T3 | Unification.cs:41:22:41:33 | Nested+NestedB+NestedC | | Unification.cs:9:10:9:11 | T3 | Unification.cs:41:22:41:33 | Nested+NestedB+NestedC | | Unification.cs:9:10:9:11 | T3 | Unification.cs:41:22:41:33 | Nested`1+NestedB+NestedC | +| Unification.cs:9:10:9:11 | T3 | Unification.cs:52:11:52:12 | I2 | +| Unification.cs:9:10:9:11 | T3 | Unification.cs:55:7:55:8 | C7 | +| Unification.cs:9:10:9:11 | T3 | Unification.cs:57:7:57:25 | NormalConstraint | +| Unification.cs:9:10:9:11 | T3 | Unification.cs:58:7:58:27 | NegativeConstraint | | Unification.cs:10:10:10:11 | T4 | Unification.cs:7:7:7:12 | C1 | | Unification.cs:10:10:10:11 | T4 | Unification.cs:7:7:7:12 | C1 | | Unification.cs:10:10:10:11 | T4 | Unification.cs:7:7:7:12 | C1 | @@ -205,9 +231,23 @@ constrainedTypeParameterUnifiable | Unification.cs:12:25:12:27 | T6d | Unification.cs:30:12:30:24 | (string, int) | | Unification.cs:12:25:12:27 | T6d | Unification.cs:31:12:31:23 | (string, T9) | | Unification.cs:12:25:12:27 | T6d | Unification.cs:32:12:32:19 | (T8, T9) | +| Unification.cs:12:25:12:27 | T6d | Unification.cs:53:8:53:9 | S3 | +| Unification.cs:12:25:12:27 | T6d | Unification.cs:54:12:54:13 | RS | | Unification.cs:24:12:24:13 | Tm | Unification.cs:8:7:8:12 | C2 | | Unification.cs:24:12:24:13 | Tm | Unification.cs:8:7:8:12 | C2 | | Unification.cs:24:12:24:13 | Tm | Unification.cs:24:12:24:13 | Tm | +| Unification.cs:57:24:57:24 | T | Unification.cs:52:11:52:12 | I2 | +| Unification.cs:57:24:57:24 | T | Unification.cs:53:8:53:9 | S3 | +| Unification.cs:57:24:57:24 | T | Unification.cs:54:12:54:13 | RS | +| Unification.cs:57:24:57:24 | T | Unification.cs:55:7:55:8 | C7 | +| Unification.cs:57:24:57:24 | T | Unification.cs:57:24:57:24 | T | +| Unification.cs:57:24:57:24 | T | Unification.cs:58:26:58:26 | T | +| Unification.cs:58:26:58:26 | T | Unification.cs:52:11:52:12 | I2 | +| Unification.cs:58:26:58:26 | T | Unification.cs:53:8:53:9 | S3 | +| Unification.cs:58:26:58:26 | T | Unification.cs:54:12:54:13 | RS | +| Unification.cs:58:26:58:26 | T | Unification.cs:55:7:55:8 | C7 | +| Unification.cs:58:26:58:26 | T | Unification.cs:57:24:57:24 | T | +| Unification.cs:58:26:58:26 | T | Unification.cs:58:26:58:26 | T | subsumes | Unification.cs:7:7:7:12 | C1 | Unification.cs:7:7:7:12 | C1 | | Unification.cs:7:7:7:12 | C1 | Unification.cs:7:7:7:12 | C1 | @@ -312,6 +352,8 @@ subsumes | Unification.cs:41:22:41:33 | Nested`1+NestedB+NestedC | Unification.cs:41:22:41:33 | Nested+NestedB+NestedC | | Unification.cs:41:22:41:33 | Nested`1+NestedB+NestedC | Unification.cs:41:22:41:33 | Nested+NestedB+NestedC | | Unification.cs:41:22:41:33 | Nested`1+NestedB+NestedC | Unification.cs:41:22:41:33 | Nested`1+NestedB+NestedC | +| Unification.cs:57:7:57:25 | NormalConstraint | Unification.cs:57:7:57:25 | NormalConstraint | +| Unification.cs:58:7:58:27 | NegativeConstraint | Unification.cs:58:7:58:27 | NegativeConstraint | subsumptionImpliesUnification unifiable | Unification.cs:7:7:7:12 | C1 | Unification.cs:7:7:7:12 | C1 | From c439beb4b4806ec21547d75e042fb5e127be930d Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 14:57:30 +0100 Subject: [PATCH 10/14] C#: Introduce a class for ref structs. --- .../ql/lib/semmle/code/csharp/Conversion.qll | 2 +- csharp/ql/lib/semmle/code/csharp/Type.qll | 32 +++++++++++++++++-- .../dataflow/internal/DataFlowPrivate.qll | 2 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/Conversion.qll b/csharp/ql/lib/semmle/code/csharp/Conversion.qll index a1ec1e44856b..7a1314abe163 100644 --- a/csharp/ql/lib/semmle/code/csharp/Conversion.qll +++ b/csharp/ql/lib/semmle/code/csharp/Conversion.qll @@ -656,7 +656,7 @@ private predicate convBoxingValueType(ValueType fromType, Type toType) { or toType instanceof SystemValueTypeClass ) and - not fromType.(Struct).isRef() + not fromType.isRefLikeType() or toType = fromType.getABaseInterface+() } diff --git a/csharp/ql/lib/semmle/code/csharp/Type.qll b/csharp/ql/lib/semmle/code/csharp/Type.qll index e5a2c1b07c1e..784ae5c69782 100644 --- a/csharp/ql/lib/semmle/code/csharp/Type.qll +++ b/csharp/ql/lib/semmle/code/csharp/Type.qll @@ -48,6 +48,11 @@ class Type extends Member, TypeContainer, @type { /** Holds if this type is a value type, or a type parameter that is a value type. */ predicate isValueType() { none() } + + /** + * Holds if this type is a ref like type. + */ + predicate isRefLikeType() { none() } } pragma[nomagic] @@ -704,8 +709,12 @@ class Enum extends ValueType, @enum_type { * ``` */ class Struct extends ValueType, @struct_type { - /** Holds if this `struct` has a `ref` modifier. */ - predicate isRef() { this.hasModifier("ref") } + /** + * DEPRECATED: Use `instanceof RefStruct` instead. + * + * Holds if this `struct` has a `ref` modifier. + */ + deprecated predicate isRef() { this.hasModifier("ref") } /** Holds if this `struct` has a `readonly` modifier. */ predicate isReadonly() { this.hasModifier("readonly") } @@ -713,6 +722,25 @@ class Struct extends ValueType, @struct_type { override string getAPrimaryQlClass() { result = "Struct" } } +/** + * A `ref struct`, for example + * + * ```csharp + * ref struct S { + * ... + * } + * ``` + */ +class RefStruct extends Struct { + RefStruct() { this.hasModifier("ref") } + + override string getAPrimaryQlClass() { result = "RefStruct" } + + override predicate isValueType() { none() } + + override predicate isRefLikeType() { any() } +} + /** * A `record struct`, for example * ```csharp diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll index a66c7f3c5d83..e3e9ef4e86be 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll @@ -703,7 +703,7 @@ module LocalFlow { or t = any(TypeParameter tp | not tp.isValueType()) or - t.(Struct).isRef() + t.isRefLikeType() ) and not exists(getALastEvalNode(result)) } From 5ddc37867e35ddffb1dff2199fe6500eebdd13b9 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 15:23:52 +0100 Subject: [PATCH 11/14] C#: Update test expected output. --- csharp/ql/test/library-tests/csharp11/PrintAst.expected | 4 ++-- csharp/ql/test/library-tests/csharp7.2/PrintAst.expected | 4 ++-- csharp/ql/test/library-tests/csharp7.2/RefStructs.ql | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/csharp/ql/test/library-tests/csharp11/PrintAst.expected b/csharp/ql/test/library-tests/csharp11/PrintAst.expected index dc1927360e1b..b10e2096489b 100644 --- a/csharp/ql/test/library-tests/csharp11/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp11/PrintAst.expected @@ -847,7 +847,7 @@ RequiredMembers.cs: # 40| 0: [Parameter] value Scoped.cs: # 1| [Struct] S1 -# 2| [Struct] S2 +# 2| [RefStruct] S2 # 7| [Class] ScopedModifierTest # 9| 5: [Method] M1 # 9| -1: [TypeMention] int @@ -1402,7 +1402,7 @@ Strings.cs: Struct.cs: # 1| [NamespaceDeclaration] namespace ... { ... } # 3| 1: [Class] MyEmptyClass -# 5| 2: [Struct] RefStruct +# 5| 2: [RefStruct] RefStruct # 7| 5: [Field] MyInt # 7| -1: [TypeMention] int # 8| 6: [Field] MyByte diff --git a/csharp/ql/test/library-tests/csharp7.2/PrintAst.expected b/csharp/ql/test/library-tests/csharp7.2/PrintAst.expected index 31036ced693c..834b03b44715 100644 --- a/csharp/ql/test/library-tests/csharp7.2/PrintAst.expected +++ b/csharp/ql/test/library-tests/csharp7.2/PrintAst.expected @@ -30,8 +30,8 @@ csharp72.cs: # 26| 0: [FieldAccess] access to field s # 29| 7: [DelegateType] Del # 32| [Struct] ReadonlyStruct -# 36| [Struct] RefStruct -# 40| [Struct] ReadonlyRefStruct +# 36| [RefStruct] RefStruct +# 40| [RefStruct] ReadonlyRefStruct # 44| [Class] NumericLiterals # 46| 5: [Field] binaryValue # 46| -1: [TypeMention] int diff --git a/csharp/ql/test/library-tests/csharp7.2/RefStructs.ql b/csharp/ql/test/library-tests/csharp7.2/RefStructs.ql index 556a0b156d8f..4ad75b22bdfe 100644 --- a/csharp/ql/test/library-tests/csharp7.2/RefStructs.ql +++ b/csharp/ql/test/library-tests/csharp7.2/RefStructs.ql @@ -1,7 +1,5 @@ import csharp -from Struct s -where - s.fromSource() and - s.isRef() +from RefStruct s +where s.fromSource() select s From cac1e04de816b6ee8e4099dec3cb0fa7a807c185 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 15:00:42 +0100 Subject: [PATCH 12/14] C#: Improve unification logic to handle ref structs. --- .../ql/lib/semmle/code/csharp/Unification.qll | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/csharp/ql/lib/semmle/code/csharp/Unification.qll b/csharp/ql/lib/semmle/code/csharp/Unification.qll index 30a94efbe6fb..c8b78cd07a27 100644 --- a/csharp/ql/lib/semmle/code/csharp/Unification.qll +++ b/csharp/ql/lib/semmle/code/csharp/Unification.qll @@ -522,16 +522,21 @@ module Gvn { /** Provides definitions related to type unification. */ module Unification { - /** A type parameter that is compatible with any type. */ + /** A type parameter that is compatible with any type except `ref struct`. */ class UnconstrainedTypeParameter extends TypeParameter { - UnconstrainedTypeParameter() { not exists(getATypeConstraint(this)) } + UnconstrainedTypeParameter() { + not exists(getATypeConstraint(this)) and not exists(getANegativeTypeConstraint(this)) + } } /** A type parameter that is constrained. */ class ConstrainedTypeParameter extends TypeParameter { int constraintCount; - ConstrainedTypeParameter() { constraintCount = strictcount(getATypeConstraint(this)) } + ConstrainedTypeParameter() { + constraintCount = count(getATypeConstraint(this)) + count(getANegativeTypeConstraint(this)) and + constraintCount > 0 + } /** * Holds if this type parameter is unifiable with type `t`. @@ -559,7 +564,7 @@ module Unification { bindingset[this] pragma[inline_late] override predicate unifiable(Type t) { - exists(TTypeParameterConstraint ttc | ttc = getATypeConstraint(this) | + forall(TTypeParameterConstraint ttc | ttc = getATypeConstraint(this) | ttc = TRefTypeConstraint() and t.isRefType() or @@ -567,13 +572,14 @@ module Unification { t.isValueType() or typeConstraintUnifiable(ttc, t) - ) + ) and + (t.isRefLikeType() implies getANegativeTypeConstraint(this) = TAllowRefTypeConstraint()) } bindingset[this] pragma[inline_late] override predicate subsumes(Type t) { - exists(TTypeParameterConstraint ttc | ttc = getATypeConstraint(this) | + forall(TTypeParameterConstraint ttc | ttc = getATypeConstraint(this) | ttc = TRefTypeConstraint() and t.isRefType() or @@ -581,7 +587,8 @@ module Unification { t.isValueType() or typeConstraintSubsumes(ttc, t) - ) + ) and + (t.isRefLikeType() implies getANegativeTypeConstraint(this) = TAllowRefTypeConstraint()) } } @@ -603,7 +610,8 @@ module Unification { t.isValueType() or typeConstraintUnifiable(ttc, t) - ) + ) and + (t.isRefLikeType() implies getANegativeTypeConstraint(this) = TAllowRefTypeConstraint()) } bindingset[this] @@ -617,7 +625,8 @@ module Unification { t.isValueType() or typeConstraintSubsumes(ttc, t) - ) + ) and + (t.isRefLikeType() implies getANegativeTypeConstraint(this) = TAllowRefTypeConstraint()) } } @@ -632,6 +641,9 @@ module Unification { not t instanceof TypeParameter } + cached + newtype TTypeParameterNegativeConstraint = TAllowRefTypeConstraint() + cached TTypeParameterConstraint getATypeConstraint(TypeParameter tp) { exists(TypeParameterConstraints tpc | tpc = tp.getConstraints() | @@ -650,6 +662,14 @@ module Unification { ) } + cached + TTypeParameterNegativeConstraint getANegativeTypeConstraint(TypeParameter tp) { + exists(TypeParameterConstraints tpc | tpc = tp.getConstraints() | + tpc.hasAllowRefLikeTypeConstraint() and + result = TAllowRefTypeConstraint() + ) + } + cached predicate typeConstraintUnifiable(TTypeConstraint ttc, Type t) { exists(Type t0 | ttc = TTypeConstraint(t0) | implicitConversionRestricted(t, t0)) From ff32a382b0fdc8f90c6ab04b167ce87e1dfe0845 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 14:53:28 +0100 Subject: [PATCH 13/14] C#: Update test expected output. --- csharp/ql/test/library-tests/dispatch/CallContext.expected | 1 - csharp/ql/test/library-tests/dispatch/CallGraph.expected | 1 - .../test/library-tests/dispatch/GetADynamicTarget.expected | 1 - csharp/ql/test/library-tests/dispatch/ViableCallable.cs | 2 +- csharp/ql/test/library-tests/unification/Unification.cs | 2 +- .../ql/test/library-tests/unification/Unification.expected | 6 ------ 6 files changed, 2 insertions(+), 11 deletions(-) diff --git a/csharp/ql/test/library-tests/dispatch/CallContext.expected b/csharp/ql/test/library-tests/dispatch/CallContext.expected index 049464b8ca94..b5f75b4f265c 100644 --- a/csharp/ql/test/library-tests/dispatch/CallContext.expected +++ b/csharp/ql/test/library-tests/dispatch/CallContext.expected @@ -26,5 +26,4 @@ mayBenefitFromCallContext | ViableCallable.cs:576:18:576:22 | call to operator / | | ViableCallable.cs:579:26:579:30 | call to operator checked / | | ViableCallable.cs:585:9:585:15 | call to method M12 | -| ViableCallable.cs:612:9:612:13 | call to method M | | ViableCallable.cs:618:9:618:13 | call to method M | diff --git a/csharp/ql/test/library-tests/dispatch/CallGraph.expected b/csharp/ql/test/library-tests/dispatch/CallGraph.expected index 192226d37b8f..f1fc555f5203 100644 --- a/csharp/ql/test/library-tests/dispatch/CallGraph.expected +++ b/csharp/ql/test/library-tests/dispatch/CallGraph.expected @@ -260,6 +260,5 @@ | ViableCallable.cs:555:10:555:15 | Run`1 | ViableCallable.cs:552:17:552:19 | M11 | | ViableCallable.cs:555:10:555:15 | Run`1 | ViableCallable.cs:553:17:553:19 | M12 | | ViableCallable.cs:609:17:609:23 | Run1`1 | ViableCallable.cs:601:21:601:21 | M | -| ViableCallable.cs:609:17:609:23 | Run1`1 | ViableCallable.cs:606:21:606:21 | M | | ViableCallable.cs:615:17:615:23 | Run2`1 | ViableCallable.cs:601:21:601:21 | M | | ViableCallable.cs:615:17:615:23 | Run2`1 | ViableCallable.cs:606:21:606:21 | M | diff --git a/csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected b/csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected index e8609224d2db..c31cd923d9d6 100644 --- a/csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected +++ b/csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected @@ -506,6 +506,5 @@ | ViableCallable.cs:585:9:585:15 | call to method M12 | I3.M12() | | ViableCallable.cs:588:9:588:15 | call to method M13 | I3.M13() | | ViableCallable.cs:612:9:612:13 | call to method M | C21+A1.M() | -| ViableCallable.cs:612:9:612:13 | call to method M | C21+A2.M() | | ViableCallable.cs:618:9:618:13 | call to method M | C21+A1.M() | | ViableCallable.cs:618:9:618:13 | call to method M | C21+A2.M() | diff --git a/csharp/ql/test/library-tests/dispatch/ViableCallable.cs b/csharp/ql/test/library-tests/dispatch/ViableCallable.cs index b28257010b3e..49210d992afc 100644 --- a/csharp/ql/test/library-tests/dispatch/ViableCallable.cs +++ b/csharp/ql/test/library-tests/dispatch/ViableCallable.cs @@ -608,7 +608,7 @@ public void M() { } public void Run1(T t) where T : I { - // Viable callable: A1.M() [also reports A2.M(); false positive] + // Viable callable: A1.M() t.M(); } diff --git a/csharp/ql/test/library-tests/unification/Unification.cs b/csharp/ql/test/library-tests/unification/Unification.cs index 50aab4943569..65d06e9c1397 100644 --- a/csharp/ql/test/library-tests/unification/Unification.cs +++ b/csharp/ql/test/library-tests/unification/Unification.cs @@ -54,5 +54,5 @@ struct S3 : I2 { } ref struct RS : I2 { } class C7 : I2 { } -class NormalConstraint where T : I2 { } // False positive: Allows T to be `RS`. +class NormalConstraint where T : I2 { } class NegativeConstraint where T : I2, allows ref struct { } diff --git a/csharp/ql/test/library-tests/unification/Unification.expected b/csharp/ql/test/library-tests/unification/Unification.expected index fac47754e195..9849938a4a68 100644 --- a/csharp/ql/test/library-tests/unification/Unification.expected +++ b/csharp/ql/test/library-tests/unification/Unification.expected @@ -8,7 +8,6 @@ constrainedTypeParameterSubsumes | Unification.cs:8:10:8:11 | T2 | Unification.cs:31:12:31:23 | (string, T9) | | Unification.cs:8:10:8:11 | T2 | Unification.cs:32:12:32:19 | (T8, T9) | | Unification.cs:8:10:8:11 | T2 | Unification.cs:53:8:53:9 | S3 | -| Unification.cs:8:10:8:11 | T2 | Unification.cs:54:12:54:13 | RS | | Unification.cs:9:10:9:11 | T3 | Unification.cs:1:11:1:12 | I1 | | Unification.cs:9:10:9:11 | T3 | Unification.cs:6:7:6:8 | C0 | | Unification.cs:9:10:9:11 | T3 | Unification.cs:7:7:7:12 | C1 | @@ -103,12 +102,10 @@ constrainedTypeParameterSubsumes | Unification.cs:12:25:12:27 | T6d | Unification.cs:31:12:31:23 | (string, T9) | | Unification.cs:12:25:12:27 | T6d | Unification.cs:32:12:32:19 | (T8, T9) | | Unification.cs:12:25:12:27 | T6d | Unification.cs:53:8:53:9 | S3 | -| Unification.cs:12:25:12:27 | T6d | Unification.cs:54:12:54:13 | RS | | Unification.cs:24:12:24:13 | Tm | Unification.cs:8:7:8:12 | C2 | | Unification.cs:24:12:24:13 | Tm | Unification.cs:24:12:24:13 | Tm | | Unification.cs:57:24:57:24 | T | Unification.cs:52:11:52:12 | I2 | | Unification.cs:57:24:57:24 | T | Unification.cs:53:8:53:9 | S3 | -| Unification.cs:57:24:57:24 | T | Unification.cs:54:12:54:13 | RS | | Unification.cs:57:24:57:24 | T | Unification.cs:55:7:55:8 | C7 | | Unification.cs:57:24:57:24 | T | Unification.cs:57:24:57:24 | T | | Unification.cs:57:24:57:24 | T | Unification.cs:58:26:58:26 | T | @@ -129,7 +126,6 @@ constrainedTypeParameterUnifiable | Unification.cs:8:10:8:11 | T2 | Unification.cs:31:12:31:23 | (string, T9) | | Unification.cs:8:10:8:11 | T2 | Unification.cs:32:12:32:19 | (T8, T9) | | Unification.cs:8:10:8:11 | T2 | Unification.cs:53:8:53:9 | S3 | -| Unification.cs:8:10:8:11 | T2 | Unification.cs:54:12:54:13 | RS | | Unification.cs:9:10:9:11 | T3 | Unification.cs:1:11:1:12 | I1 | | Unification.cs:9:10:9:11 | T3 | Unification.cs:6:7:6:8 | C0 | | Unification.cs:9:10:9:11 | T3 | Unification.cs:7:7:7:12 | C1 | @@ -232,13 +228,11 @@ constrainedTypeParameterUnifiable | Unification.cs:12:25:12:27 | T6d | Unification.cs:31:12:31:23 | (string, T9) | | Unification.cs:12:25:12:27 | T6d | Unification.cs:32:12:32:19 | (T8, T9) | | Unification.cs:12:25:12:27 | T6d | Unification.cs:53:8:53:9 | S3 | -| Unification.cs:12:25:12:27 | T6d | Unification.cs:54:12:54:13 | RS | | Unification.cs:24:12:24:13 | Tm | Unification.cs:8:7:8:12 | C2 | | Unification.cs:24:12:24:13 | Tm | Unification.cs:8:7:8:12 | C2 | | Unification.cs:24:12:24:13 | Tm | Unification.cs:24:12:24:13 | Tm | | Unification.cs:57:24:57:24 | T | Unification.cs:52:11:52:12 | I2 | | Unification.cs:57:24:57:24 | T | Unification.cs:53:8:53:9 | S3 | -| Unification.cs:57:24:57:24 | T | Unification.cs:54:12:54:13 | RS | | Unification.cs:57:24:57:24 | T | Unification.cs:55:7:55:8 | C7 | | Unification.cs:57:24:57:24 | T | Unification.cs:57:24:57:24 | T | | Unification.cs:57:24:57:24 | T | Unification.cs:58:26:58:26 | T | From caaf29115cec76d8dbda10e813c2e6555ccd9c4a Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Jan 2025 15:30:58 +0100 Subject: [PATCH 14/14] C#: Add change note. --- csharp/ql/lib/change-notes/2025-01-03-allow-ref-struct.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 csharp/ql/lib/change-notes/2025-01-03-allow-ref-struct.md diff --git a/csharp/ql/lib/change-notes/2025-01-03-allow-ref-struct.md b/csharp/ql/lib/change-notes/2025-01-03-allow-ref-struct.md new file mode 100644 index 000000000000..c9a7234fa202 --- /dev/null +++ b/csharp/ql/lib/change-notes/2025-01-03-allow-ref-struct.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* C# 13: Added extractor support and call dispatch logic (data flow) for the (negative) type parameter constraint `allows ref struct`. Added extractor support for the type parameter constraint `notnull`.