diff --git a/csharp/ql/src/Bad Practices/VirtualCallInConstructorOrDestructor.ql b/csharp/ql/src/Bad Practices/VirtualCallInConstructorOrDestructor.ql index a29b39d33a10..5effa2f27650 100644 --- a/csharp/ql/src/Bad Practices/VirtualCallInConstructorOrDestructor.ql +++ b/csharp/ql/src/Bad Practices/VirtualCallInConstructorOrDestructor.ql @@ -19,6 +19,7 @@ predicate virtualCallToSelfInConstructor(Expr e) { (c instanceof Constructor or c instanceof Destructor) and t = c.getDeclaringType() and virtualAccessWithThisQualifier(e, d) and + not e = any(NameOfExpr ne).getAccess().getAChildExpr*() and t.getABaseType*() = d.getDeclaringType() and not t.isSealed() and not overriddenSealed(t.getABaseType*(), d) @@ -34,17 +35,15 @@ predicate overriddenSealed(RefType t, Virtualizable d) { } predicate virtualAccessWithThisQualifier(Expr e, Member d) { - exists(VirtualMethodCall c | - c = e and c.getTarget() = d and c.hasThisQualifier() and not c.isImplicit() - ) + e = any(VirtualMethodCall c | c.getTarget() = d and c.hasThisQualifier() and not c.isImplicit()) or - exists(VirtualMethodAccess c | c = e and c.getTarget() = d and c.hasThisQualifier()) + e = any(VirtualMethodAccess c | c.getTarget() = d and c.hasThisQualifier()) or - exists(VirtualPropertyAccess c | c = e and c.getTarget() = d and c.hasThisQualifier()) + e = any(VirtualPropertyAccess c | c.getTarget() = d and c.hasThisQualifier()) or - exists(VirtualIndexerAccess c | c = e and c.getTarget() = d and c.hasThisQualifier()) + e = any(VirtualIndexerAccess c | c.getTarget() = d and c.hasThisQualifier()) or - exists(VirtualEventAccess c | c = e and c.getTarget() = d and c.hasThisQualifier()) + e = any(VirtualEventAccess c | c.getTarget() = d and c.hasThisQualifier()) } from Expr e diff --git a/csharp/ql/src/change-notes/2026-07-16-virtual-call-in-constructor.md b/csharp/ql/src/change-notes/2026-07-16-virtual-call-in-constructor.md new file mode 100644 index 000000000000..bd1f5f99a4f5 --- /dev/null +++ b/csharp/ql/src/change-notes/2026-07-16-virtual-call-in-constructor.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The query `cs/virtual-call-in-constructor` has been improved. Uses of virtual members in `nameof` expressions are no longer reported, since they are not calls. diff --git a/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.cs b/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.cs index 6db005e00cef..f654c1c0a659 100644 --- a/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.cs +++ b/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.cs @@ -15,6 +15,7 @@ public void f_nonvirtual() { } public void f_interface() { } public virtual int p_virtual { get { return 0; } } + public virtual string p_virtual_string { get { return ""; } } public virtual int p_sealed { get { return 0; } } public int p_nonvirtual { get { return 0; } } @@ -55,11 +56,14 @@ class C : B a = f_sealed; // GOOD a = f_nonvirtual; // GOOD a = f_interface; // GOOD + var f_name = nameof(f_virtual); // GOOD // Property access int i = p_virtual; // $ Alert // BAD i = p_sealed; // GOOD i = p_nonvirtual; // GOOD + var p_name = nameof(p_virtual_string); // GOOD + var p_length_name = nameof(p_virtual_string.Length); // GOOD // Indexer access i = this[0]; // $ Alert // BAD diff --git a/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.expected b/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.expected index 8a97e61bf8cf..e2925c9cbd7b 100644 --- a/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.expected +++ b/csharp/ql/test/query-tests/Bad Practices/VirtualCallInConstructorOrDestructor/VirtualCallInConstructorOrDestructor.expected @@ -1,5 +1,5 @@ -| VirtualCallInConstructorOrDestructor.cs:45:13:45:23 | call to method f_virtual | Avoid virtual calls in a constructor or destructor. | -| VirtualCallInConstructorOrDestructor.cs:54:17:54:25 | access to method f_virtual | Avoid virtual calls in a constructor or destructor. | -| VirtualCallInConstructorOrDestructor.cs:60:21:60:29 | access to property p_virtual | Avoid virtual calls in a constructor or destructor. | -| VirtualCallInConstructorOrDestructor.cs:65:17:65:23 | access to indexer | Avoid virtual calls in a constructor or destructor. | -| VirtualCallInConstructorOrDestructor.cs:70:13:70:21 | access to event e_virtual | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:46:13:46:23 | call to method f_virtual | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:55:17:55:25 | access to method f_virtual | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:62:21:62:29 | access to property p_virtual | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:69:17:69:23 | access to indexer | Avoid virtual calls in a constructor or destructor. | +| VirtualCallInConstructorOrDestructor.cs:74:13:74:21 | access to event e_virtual | Avoid virtual calls in a constructor or destructor. |