Skip to content

Commit 13959ab

Browse files
authored
Merge pull request #21335 from michaelnebel/csharp14/partialconstrucstors
C# 14: Support for partial constructor declarations.
2 parents e695477 + 06a8fd0 commit 13959ab

18 files changed

+266
-191
lines changed

csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.IO;
@@ -12,16 +13,38 @@ namespace Semmle.Extraction.CSharp.Entities
1213
internal class Constructor : Method
1314
{
1415
private readonly List<SyntaxNode> declaringReferenceSyntax;
15-
16+
private readonly Lazy<ConstructorDeclarationSyntax?> ordinaryConstructorSyntaxLazy;
17+
private readonly Lazy<TypeDeclarationSyntax?> primaryConstructorSyntaxLazy;
18+
private readonly Lazy<PrimaryConstructorBaseTypeSyntax?> primaryBaseLazy;
1619
private Constructor(Context cx, IMethodSymbol init)
1720
: base(cx, init)
1821
{
1922
declaringReferenceSyntax =
2023
Symbol.DeclaringSyntaxReferences
2124
.Select(r => r.GetSyntax())
2225
.ToList();
26+
ordinaryConstructorSyntaxLazy = new Lazy<ConstructorDeclarationSyntax?>(() =>
27+
declaringReferenceSyntax
28+
.OfType<ConstructorDeclarationSyntax>()
29+
.FirstOrDefault());
30+
primaryConstructorSyntaxLazy = new Lazy<TypeDeclarationSyntax?>(() =>
31+
declaringReferenceSyntax
32+
.OfType<TypeDeclarationSyntax>()
33+
.FirstOrDefault(t => t is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax));
34+
primaryBaseLazy = new Lazy<PrimaryConstructorBaseTypeSyntax?>(() =>
35+
PrimaryConstructorSyntax?
36+
.BaseList?
37+
.Types
38+
.OfType<PrimaryConstructorBaseTypeSyntax>()
39+
.FirstOrDefault());
2340
}
2441

42+
private ConstructorDeclarationSyntax? OrdinaryConstructorSyntax => ordinaryConstructorSyntaxLazy.Value;
43+
44+
private TypeDeclarationSyntax? PrimaryConstructorSyntax => primaryConstructorSyntaxLazy.Value;
45+
46+
private PrimaryConstructorBaseTypeSyntax? PrimaryBase => primaryBaseLazy.Value;
47+
2548
public override void Populate(TextWriter trapFile)
2649
{
2750
PopulateMethod(trapFile);
@@ -176,23 +199,6 @@ private void ExtractSourceInitializer(TextWriter trapFile, ITypeSymbol? type, IM
176199
init.PopulateArguments(trapFile, arguments, 0);
177200
}
178201

179-
private ConstructorDeclarationSyntax? OrdinaryConstructorSyntax =>
180-
declaringReferenceSyntax
181-
.OfType<ConstructorDeclarationSyntax>()
182-
.FirstOrDefault();
183-
184-
private TypeDeclarationSyntax? PrimaryConstructorSyntax =>
185-
declaringReferenceSyntax
186-
.OfType<TypeDeclarationSyntax>()
187-
.FirstOrDefault(t => t is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax);
188-
189-
private PrimaryConstructorBaseTypeSyntax? PrimaryBase =>
190-
PrimaryConstructorSyntax?
191-
.BaseList?
192-
.Types
193-
.OfType<PrimaryConstructorBaseTypeSyntax>()
194-
.FirstOrDefault();
195-
196202
private bool IsPrimary => PrimaryConstructorSyntax is not null;
197203

198204
// This is a default constructor in a class or struct declared in source.
@@ -223,7 +229,7 @@ Symbol.ContainingType.TypeKind is TypeKind.Class or TypeKind.Struct &&
223229
{
224230
case MethodKind.StaticConstructor:
225231
case MethodKind.Constructor:
226-
return ConstructorFactory.Instance.CreateEntityFromSymbol(cx, constructor);
232+
return ConstructorFactory.Instance.CreateEntityFromSymbol(cx, constructor.GetBodyDeclaringSymbol());
227233
default:
228234
throw new InternalError(constructor, "Attempt to create a Constructor from a symbol that isn't a constructor");
229235
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* C# 14: Added support for partial constructors.

csharp/ql/test/library-tests/dataflow/constructors/ConstructorFlow.expected

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ edges
122122
| Constructors.cs:143:29:143:30 | access to local variable o2 : Object | Constructors.cs:143:18:143:31 | object creation of type R1 : R1 [property Obj2] : Object | provenance | |
123123
| Constructors.cs:144:14:144:15 | access to local variable r1 : R1 [property Obj1] : Object | Constructors.cs:144:14:144:20 | access to property Obj1 | provenance | |
124124
| Constructors.cs:145:14:145:15 | access to local variable r1 : R1 [property Obj2] : Object | Constructors.cs:145:14:145:20 | access to property Obj2 | provenance | |
125+
| Constructors.cs:157:40:157:40 | o : Object | Constructors.cs:157:52:157:52 | access to parameter o : Object | provenance | |
126+
| Constructors.cs:157:46:157:48 | [post] this access : CPartial [property Obj] : Object | Constructors.cs:157:24:157:31 | this [Return] : CPartial [property Obj] : Object | provenance | |
127+
| Constructors.cs:157:52:157:52 | access to parameter o : Object | Constructors.cs:157:46:157:48 | [post] this access : CPartial [property Obj] : Object | provenance | |
128+
| Constructors.cs:162:13:162:13 | access to local variable o : Object | Constructors.cs:163:37:163:37 | access to local variable o : Object | provenance | |
129+
| Constructors.cs:162:17:162:34 | call to method Source<Object> : Object | Constructors.cs:162:13:162:13 | access to local variable o : Object | provenance | |
130+
| Constructors.cs:163:13:163:20 | access to local variable cPartial : CPartial [property Obj] : Object | Constructors.cs:164:14:164:21 | access to local variable cPartial : CPartial [property Obj] : Object | provenance | |
131+
| Constructors.cs:163:24:163:38 | object creation of type CPartial : CPartial [property Obj] : Object | Constructors.cs:163:13:163:20 | access to local variable cPartial : CPartial [property Obj] : Object | provenance | |
132+
| Constructors.cs:163:37:163:37 | access to local variable o : Object | Constructors.cs:157:40:157:40 | o : Object | provenance | |
133+
| Constructors.cs:163:37:163:37 | access to local variable o : Object | Constructors.cs:163:24:163:38 | object creation of type CPartial : CPartial [property Obj] : Object | provenance | |
134+
| Constructors.cs:164:14:164:21 | access to local variable cPartial : CPartial [property Obj] : Object | Constructors.cs:164:14:164:25 | access to property Obj | provenance | |
125135
nodes
126136
| Constructors.cs:3:18:3:26 | [post] this access : C_no_ctor [field s1] : Object | semmle.label | [post] this access : C_no_ctor [field s1] : Object |
127137
| Constructors.cs:5:24:5:25 | [post] this access : C_no_ctor [field s1] : Object | semmle.label | [post] this access : C_no_ctor [field s1] : Object |
@@ -255,6 +265,17 @@ nodes
255265
| Constructors.cs:144:14:144:20 | access to property Obj1 | semmle.label | access to property Obj1 |
256266
| Constructors.cs:145:14:145:15 | access to local variable r1 : R1 [property Obj2] : Object | semmle.label | access to local variable r1 : R1 [property Obj2] : Object |
257267
| Constructors.cs:145:14:145:20 | access to property Obj2 | semmle.label | access to property Obj2 |
268+
| Constructors.cs:157:24:157:31 | this [Return] : CPartial [property Obj] : Object | semmle.label | this [Return] : CPartial [property Obj] : Object |
269+
| Constructors.cs:157:40:157:40 | o : Object | semmle.label | o : Object |
270+
| Constructors.cs:157:46:157:48 | [post] this access : CPartial [property Obj] : Object | semmle.label | [post] this access : CPartial [property Obj] : Object |
271+
| Constructors.cs:157:52:157:52 | access to parameter o : Object | semmle.label | access to parameter o : Object |
272+
| Constructors.cs:162:13:162:13 | access to local variable o : Object | semmle.label | access to local variable o : Object |
273+
| Constructors.cs:162:17:162:34 | call to method Source<Object> : Object | semmle.label | call to method Source<Object> : Object |
274+
| Constructors.cs:163:13:163:20 | access to local variable cPartial : CPartial [property Obj] : Object | semmle.label | access to local variable cPartial : CPartial [property Obj] : Object |
275+
| Constructors.cs:163:24:163:38 | object creation of type CPartial : CPartial [property Obj] : Object | semmle.label | object creation of type CPartial : CPartial [property Obj] : Object |
276+
| Constructors.cs:163:37:163:37 | access to local variable o : Object | semmle.label | access to local variable o : Object |
277+
| Constructors.cs:164:14:164:21 | access to local variable cPartial : CPartial [property Obj] : Object | semmle.label | access to local variable cPartial : CPartial [property Obj] : Object |
278+
| Constructors.cs:164:14:164:25 | access to property Obj | semmle.label | access to property Obj |
258279
subpaths
259280
| Constructors.cs:44:18:44:19 | this access : C2 [parameter o21param] : Object | Constructors.cs:46:23:46:27 | this access : C2 [parameter o21param] : Object | Constructors.cs:46:23:46:27 | [post] this access : C2 [field Obj21] : Object | Constructors.cs:44:18:44:19 | [post] this access : C2 [field Obj21] : Object |
260281
| Constructors.cs:64:37:64:37 | access to parameter o : Object | Constructors.cs:57:54:57:55 | o2 : Object | Constructors.cs:59:13:59:14 | access to parameter o1 : Object | Constructors.cs:64:27:64:34 | access to parameter o22param : Object |
@@ -273,6 +294,7 @@ subpaths
273294
| Constructors.cs:132:29:132:30 | access to local variable o2 : Object | Constructors.cs:121:38:121:40 | oc2 : Object | Constructors.cs:121:16:121:17 | this [Return] : C4 [property Obj2] : Object | Constructors.cs:132:18:132:31 | object creation of type C4 : C4 [property Obj2] : Object |
274295
| Constructors.cs:143:25:143:26 | access to local variable o1 : Object | Constructors.cs:137:29:137:32 | Obj1 : Object | Constructors.cs:137:19:137:20 | this [Return] : R1 [property Obj1] : Object | Constructors.cs:143:18:143:31 | object creation of type R1 : R1 [property Obj1] : Object |
275296
| Constructors.cs:143:29:143:30 | access to local variable o2 : Object | Constructors.cs:137:42:137:45 | Obj2 : Object | Constructors.cs:137:19:137:20 | this [Return] : R1 [property Obj2] : Object | Constructors.cs:143:18:143:31 | object creation of type R1 : R1 [property Obj2] : Object |
297+
| Constructors.cs:163:37:163:37 | access to local variable o : Object | Constructors.cs:157:40:157:40 | o : Object | Constructors.cs:157:24:157:31 | this [Return] : CPartial [property Obj] : Object | Constructors.cs:163:24:163:38 | object creation of type CPartial : CPartial [property Obj] : Object |
276298
testFailures
277299
#select
278300
| Constructors.cs:15:18:15:19 | access to field s1 | Constructors.cs:5:29:5:45 | call to method Source<Object> : Object | Constructors.cs:15:18:15:19 | access to field s1 | $@ | Constructors.cs:5:29:5:45 | call to method Source<Object> : Object | call to method Source<Object> : Object |
@@ -288,3 +310,4 @@ testFailures
288310
| Constructors.cs:134:14:134:20 | access to property Obj2 | Constructors.cs:131:18:131:34 | call to method Source<Object> : Object | Constructors.cs:134:14:134:20 | access to property Obj2 | $@ | Constructors.cs:131:18:131:34 | call to method Source<Object> : Object | call to method Source<Object> : Object |
289311
| Constructors.cs:144:14:144:20 | access to property Obj1 | Constructors.cs:141:18:141:34 | call to method Source<Object> : Object | Constructors.cs:144:14:144:20 | access to property Obj1 | $@ | Constructors.cs:141:18:141:34 | call to method Source<Object> : Object | call to method Source<Object> : Object |
290312
| Constructors.cs:145:14:145:20 | access to property Obj2 | Constructors.cs:142:18:142:35 | call to method Source<Object> : Object | Constructors.cs:145:14:145:20 | access to property Obj2 | $@ | Constructors.cs:142:18:142:35 | call to method Source<Object> : Object | call to method Source<Object> : Object |
313+
| Constructors.cs:164:14:164:25 | access to property Obj | Constructors.cs:162:17:162:34 | call to method Source<Object> : Object | Constructors.cs:164:14:164:25 | access to property Obj | $@ | Constructors.cs:162:17:162:34 | call to method Source<Object> : Object | call to method Source<Object> : Object |

csharp/ql/test/library-tests/dataflow/constructors/Constructors.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,25 @@ public void M7()
145145
Sink(r1.Obj2); // $ hasValueFlow=10
146146
}
147147

148+
public partial class CPartial
149+
{
150+
public object Obj { get; }
151+
152+
public partial CPartial(object o);
153+
}
154+
155+
public partial class CPartial
156+
{
157+
public partial CPartial(object o) => Obj = o;
158+
}
159+
160+
public void M8()
161+
{
162+
var o = Source<object>(11);
163+
var cPartial = new CPartial(o);
164+
Sink(cPartial.Obj); // $ hasValueFlow=11
165+
}
166+
148167
public static void Sink(object o) { }
149168

150169
public static T Source<T>(object source) => throw null;

csharp/ql/test/library-tests/dispatch/CallGraph.expected

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,10 @@
270270
| ViableCallable.cs:679:17:679:20 | Run3 | ViableCallable.cs:637:21:637:21 | M |
271271
| ViableCallable.cs:679:17:679:20 | Run3 | ViableCallable.cs:646:21:646:21 | M |
272272
| ViableCallable.cs:679:17:679:20 | Run3 | ViableCallable.cs:648:21:648:21 | M |
273-
| ViableCallable.cs:709:17:709:20 | Run1 | ViableCallable.cs:703:42:703:44 | get_Property |
274-
| ViableCallable.cs:709:17:709:20 | Run1 | ViableCallable.cs:703:63:703:65 | set_Property |
275-
| ViableCallable.cs:709:17:709:20 | Run1 | ViableCallable.cs:705:49:705:51 | get_Item |
276-
| ViableCallable.cs:709:17:709:20 | Run1 | ViableCallable.cs:705:70:705:72 | set_Item |
277-
| ViableCallable.cs:709:17:709:20 | Run1 | ViableCallable.cs:706:51:706:53 | add_Event |
278-
| ViableCallable.cs:709:17:709:20 | Run1 | ViableCallable.cs:706:59:706:64 | remove_Event |
273+
| ViableCallable.cs:711:17:711:20 | Run1 | ViableCallable.cs:704:24:704:31 | Partial1 |
274+
| ViableCallable.cs:711:17:711:20 | Run1 | ViableCallable.cs:705:42:705:44 | get_Property |
275+
| ViableCallable.cs:711:17:711:20 | Run1 | ViableCallable.cs:705:63:705:65 | set_Property |
276+
| ViableCallable.cs:711:17:711:20 | Run1 | ViableCallable.cs:707:49:707:51 | get_Item |
277+
| ViableCallable.cs:711:17:711:20 | Run1 | ViableCallable.cs:707:70:707:72 | set_Item |
278+
| ViableCallable.cs:711:17:711:20 | Run1 | ViableCallable.cs:708:51:708:53 | add_Event |
279+
| ViableCallable.cs:711:17:711:20 | Run1 | ViableCallable.cs:708:59:708:64 | remove_Event |

csharp/ql/test/library-tests/dispatch/GetADynamicTarget.expected

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,10 @@
518518
| ViableCallable.cs:683:9:683:16 | call to method M | C22+TestOverloadResolution2<System.Int32>.M(Int32[]) |
519519
| ViableCallable.cs:687:9:687:16 | call to method M | C22+TestOverloadResolution1<System.Int32>.M(List<int>) |
520520
| ViableCallable.cs:687:9:687:16 | call to method M | C22+TestOverloadResolution2<System.Int32>.M(List<int>) |
521-
| ViableCallable.cs:714:9:714:18 | access to property Property | C23+Partial1.set_Property(object) |
522-
| ViableCallable.cs:717:13:717:22 | access to property Property | C23+Partial1.get_Property() |
523-
| ViableCallable.cs:720:9:720:12 | access to indexer | C23+Partial1.set_Item(int, object) |
524-
| ViableCallable.cs:723:13:723:16 | access to indexer | C23+Partial1.get_Item(int) |
525-
| ViableCallable.cs:726:9:726:15 | access to event Event | C23+Partial1.add_Event(EventHandler) |
526-
| ViableCallable.cs:729:9:729:15 | access to event Event | C23+Partial1.remove_Event(EventHandler) |
521+
| ViableCallable.cs:716:9:716:18 | access to property Property | C23+Partial1.set_Property(object) |
522+
| ViableCallable.cs:719:13:719:22 | access to property Property | C23+Partial1.get_Property() |
523+
| ViableCallable.cs:722:9:722:12 | access to indexer | C23+Partial1.set_Item(int, object) |
524+
| ViableCallable.cs:725:13:725:16 | access to indexer | C23+Partial1.get_Item(int) |
525+
| ViableCallable.cs:728:9:728:15 | access to event Event | C23+Partial1.add_Event(EventHandler) |
526+
| ViableCallable.cs:731:9:731:15 | access to event Event | C23+Partial1.remove_Event(EventHandler) |
527+
| ViableCallable.cs:734:18:734:43 | object creation of type Partial1 | C23+Partial1.Partial1(object) |

csharp/ql/test/library-tests/dispatch/ViableCallable.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ public class C23
692692
{
693693
public partial class Partial1
694694
{
695+
public partial Partial1(object obj);
695696
public partial object Property { get; set; }
696697

697698
public partial object this[int index] { get; set; }
@@ -700,6 +701,7 @@ public partial class Partial1
700701

701702
public partial class Partial1
702703
{
704+
public partial Partial1(object obj) { }
703705
public partial object Property { get { return null; } set { } }
704706

705707
public partial object this[int index] { get { return null; } set { } }
@@ -727,5 +729,8 @@ public void Run1(Partial1 p)
727729

728730
// Viable callable: Partial1.remove_Event
729731
p.Event -= (sender, e) => { };
732+
733+
// Viable callable: Partial1.Partial1(object)
734+
var p0 = new Partial1(new object());
730735
}
731736
}
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
| Partial.cs:7:18:7:42 | PartialMethodWithoutBody1 | true |
2-
| Partial.cs:8:17:8:23 | Method2 | false |
3-
| Partial.cs:19:18:19:39 | PartialMethodWithBody1 | true |
4-
| Partial.cs:20:27:20:48 | PartialMethodWithBody2 | true |
5-
| Partial.cs:24:17:24:23 | Method3 | false |
6-
| Partial.cs:46:18:46:42 | PartialMethodWithoutBody2 | true |
7-
| Partial.cs:47:17:47:23 | Method4 | false |
8-
| Partial.cs:52:17:52:23 | Method5 | false |
1+
| Partial.cs:9:18:9:42 | PartialMethodWithoutBody1 | true |
2+
| Partial.cs:10:17:10:23 | Method2 | false |
3+
| Partial.cs:23:18:23:39 | PartialMethodWithBody1 | true |
4+
| Partial.cs:24:27:24:48 | PartialMethodWithBody2 | true |
5+
| Partial.cs:28:17:28:23 | Method3 | false |
6+
| Partial.cs:50:18:50:42 | PartialMethodWithoutBody2 | true |
7+
| Partial.cs:51:17:51:23 | Method4 | false |
8+
| Partial.cs:57:17:57:23 | Method5 | false |

csharp/ql/test/library-tests/partial/Partial.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
partial class TwoPartClass
44
{
5+
// Declaring declaration.
6+
public partial TwoPartClass(object obj);
57
partial void PartialMethodWithBody1();
68
public partial object PartialMethodWithBody2(object obj);
79
partial void PartialMethodWithoutBody1();
@@ -16,6 +18,8 @@ public void Method2() { }
1618

1719
partial class TwoPartClass
1820
{
21+
// Implementation declaration.
22+
public partial TwoPartClass(object obj) { }
1923
partial void PartialMethodWithBody1() { }
2024
public partial object PartialMethodWithBody2(object obj)
2125
{
@@ -49,6 +53,7 @@ public void Method4() { }
4953

5054
class NonPartialClass
5155
{
56+
public NonPartialClass(object obj) { }
5257
public void Method5() { }
5358
public object Property { get; set; }
5459
public object this[int index]
Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
| Partial.cs:3:15:3:26 | TwoPartClass |
2-
| Partial.cs:7:18:7:42 | PartialMethodWithoutBody1 |
3-
| Partial.cs:17:15:17:26 | TwoPartClass |
4-
| Partial.cs:19:18:19:39 | PartialMethodWithBody1 |
5-
| Partial.cs:20:27:20:48 | PartialMethodWithBody2 |
6-
| Partial.cs:27:27:27:42 | PartialProperty1 |
7-
| Partial.cs:29:9:29:11 | get_PartialProperty1 |
8-
| Partial.cs:30:9:30:11 | set_PartialProperty1 |
9-
| Partial.cs:34:27:34:30 | Item |
10-
| Partial.cs:36:9:36:11 | get_Item |
11-
| Partial.cs:37:9:37:11 | set_Item |
12-
| Partial.cs:41:39:41:51 | PartialEvent1 |
13-
| Partial.cs:41:55:41:57 | add_PartialEvent1 |
14-
| Partial.cs:41:63:41:68 | remove_PartialEvent1 |
15-
| Partial.cs:44:15:44:33 | OnePartPartialClass |
16-
| Partial.cs:46:18:46:42 | PartialMethodWithoutBody2 |
2+
| Partial.cs:9:18:9:42 | PartialMethodWithoutBody1 |
3+
| Partial.cs:19:15:19:26 | TwoPartClass |
4+
| Partial.cs:22:20:22:31 | TwoPartClass |
5+
| Partial.cs:23:18:23:39 | PartialMethodWithBody1 |
6+
| Partial.cs:24:27:24:48 | PartialMethodWithBody2 |
7+
| Partial.cs:31:27:31:42 | PartialProperty1 |
8+
| Partial.cs:33:9:33:11 | get_PartialProperty1 |
9+
| Partial.cs:34:9:34:11 | set_PartialProperty1 |
10+
| Partial.cs:38:27:38:30 | Item |
11+
| Partial.cs:40:9:40:11 | get_Item |
12+
| Partial.cs:41:9:41:11 | set_Item |
13+
| Partial.cs:45:39:45:51 | PartialEvent1 |
14+
| Partial.cs:45:55:45:57 | add_PartialEvent1 |
15+
| Partial.cs:45:63:45:68 | remove_PartialEvent1 |
16+
| Partial.cs:48:15:48:33 | OnePartPartialClass |
17+
| Partial.cs:50:18:50:42 | PartialMethodWithoutBody2 |
1718
| PartialMultipleFiles1.cs:1:22:1:41 | PartialMultipleFiles |
1819
| PartialMultipleFiles2.cs:1:22:1:41 | PartialMultipleFiles |

0 commit comments

Comments
 (0)