Add IsCollectible property to Memberinfo and MethodInfo (#21155)
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / src / System / Reflection / RuntimeMethodInfo.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.Security;
9 using System.Text;
10 using System.Threading;
11 using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache;
12
13 namespace System.Reflection
14 {
15     internal sealed class RuntimeMethodInfo : MethodInfo, IRuntimeMethodInfo
16     {
17         #region Private Data Members
18         private IntPtr m_handle;
19         private RuntimeTypeCache m_reflectedTypeCache;
20         private string m_name;
21         private string m_toString;
22         private ParameterInfo[] m_parameters;
23         private ParameterInfo m_returnParameter;
24         private BindingFlags m_bindingFlags;
25         private MethodAttributes m_methodAttributes;
26         private Signature m_signature;
27         private RuntimeType m_declaringType;
28         private object m_keepalive;
29         private INVOCATION_FLAGS m_invocationFlags;
30
31         internal INVOCATION_FLAGS InvocationFlags
32         {
33             get
34             {
35                 if ((m_invocationFlags & INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED) == 0)
36                 {
37                     INVOCATION_FLAGS invocationFlags = INVOCATION_FLAGS.INVOCATION_FLAGS_UNKNOWN;
38
39                     Type declaringType = DeclaringType;
40
41                     //
42                     // first take care of all the NO_INVOKE cases. 
43                     if (ContainsGenericParameters ||
44                          IsDisallowedByRefType(ReturnType) ||
45                          (declaringType != null && declaringType.ContainsGenericParameters) ||
46                          ((CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs))
47                     {
48                         // We don't need other flags if this method cannot be invoked
49                         invocationFlags = INVOCATION_FLAGS.INVOCATION_FLAGS_NO_INVOKE;
50                     }
51                     else
52                     {
53                         // Check for byref-like types
54                         if ((declaringType != null && declaringType.IsByRefLike) || ReturnType.IsByRefLike)
55                             invocationFlags |= INVOCATION_FLAGS.INVOCATION_FLAGS_CONTAINS_STACK_POINTERS;
56                     }
57
58                     m_invocationFlags = invocationFlags | INVOCATION_FLAGS.INVOCATION_FLAGS_INITIALIZED;
59                 }
60
61                 return m_invocationFlags;
62             }
63         }
64
65         private bool IsDisallowedByRefType(Type type)
66         {
67             if (!type.IsByRef)
68                 return false;
69
70             Type elementType = type.GetElementType();
71             return elementType.IsByRefLike || elementType == typeof(void);
72         }
73         #endregion
74
75         #region Constructor
76         internal RuntimeMethodInfo(
77             RuntimeMethodHandleInternal handle, RuntimeType declaringType,
78             RuntimeTypeCache reflectedTypeCache, MethodAttributes methodAttributes, BindingFlags bindingFlags, object keepalive)
79         {
80             Debug.Assert(!handle.IsNullHandle());
81             Debug.Assert(methodAttributes == RuntimeMethodHandle.GetAttributes(handle));
82
83             m_bindingFlags = bindingFlags;
84             m_declaringType = declaringType;
85             m_keepalive = keepalive;
86             m_handle = handle.Value;
87             m_reflectedTypeCache = reflectedTypeCache;
88             m_methodAttributes = methodAttributes;
89         }
90         #endregion
91
92         #region Private Methods
93         RuntimeMethodHandleInternal IRuntimeMethodInfo.Value
94         {
95             get
96             {
97                 return new RuntimeMethodHandleInternal(m_handle);
98             }
99         }
100
101         private RuntimeType ReflectedTypeInternal
102         {
103             get
104             {
105                 return m_reflectedTypeCache.GetRuntimeType();
106             }
107         }
108
109         private ParameterInfo[] FetchNonReturnParameters()
110         {
111             if (m_parameters == null)
112                 m_parameters = RuntimeParameterInfo.GetParameters(this, this, Signature);
113
114             return m_parameters;
115         }
116
117         private ParameterInfo FetchReturnParameter()
118         {
119             if (m_returnParameter == null)
120                 m_returnParameter = RuntimeParameterInfo.GetReturnParameter(this, this, Signature);
121
122             return m_returnParameter;
123         }
124         #endregion
125
126         #region Internal Members
127         internal override bool CacheEquals(object o)
128         {
129             RuntimeMethodInfo m = o as RuntimeMethodInfo;
130
131             if ((object)m == null)
132                 return false;
133
134             return m.m_handle == m_handle;
135         }
136
137         internal Signature Signature
138         {
139             get
140             {
141                 if (m_signature == null)
142                     m_signature = new Signature(this, m_declaringType);
143
144                 return m_signature;
145             }
146         }
147
148         internal BindingFlags BindingFlags { get { return m_bindingFlags; } }
149
150         internal RuntimeMethodInfo GetParentDefinition()
151         {
152             if (!IsVirtual || m_declaringType.IsInterface)
153                 return null;
154
155             RuntimeType parent = (RuntimeType)m_declaringType.BaseType;
156
157             if (parent == null)
158                 return null;
159
160             int slot = RuntimeMethodHandle.GetSlot(this);
161
162             if (RuntimeTypeHandle.GetNumVirtuals(parent) <= slot)
163                 return null;
164
165             return (RuntimeMethodInfo)RuntimeType.GetMethodBase(parent, RuntimeTypeHandle.GetMethodAt(parent, slot));
166         }
167
168         // Unlike DeclaringType, this will return a valid type even for global methods
169         internal RuntimeType GetDeclaringTypeInternal()
170         {
171             return m_declaringType;
172         }
173
174         internal sealed override int GenericParameterCount => RuntimeMethodHandle.GetGenericParameterCount(this);
175         #endregion
176
177         #region Object Overrides
178         public override string ToString()
179         {
180             if (m_toString == null)
181             {
182                 var sbName = new ValueStringBuilder(MethodNameBufferSize);
183
184                 sbName.Append(ReturnType.FormatTypeName());
185                 sbName.Append(' ');
186                 sbName.Append(Name);
187
188                 if (IsGenericMethod)
189                     sbName.Append(RuntimeMethodHandle.ConstructInstantiation(this, TypeNameFormatFlags.FormatBasic));
190
191                 sbName.Append('(');
192                 AppendParameters(ref sbName, GetParameterTypes(), CallingConvention);
193                 sbName.Append(')');
194
195                 m_toString = sbName.ToString();
196             }
197
198             return m_toString;
199         }
200
201         public override int GetHashCode()
202         {
203             // See RuntimeMethodInfo.Equals() below.
204             if (IsGenericMethod)
205                 return ValueType.GetHashCodeOfPtr(m_handle);
206             else
207                 return base.GetHashCode();
208         }
209
210         public override bool Equals(object obj)
211         {
212             if (!IsGenericMethod)
213                 return obj == (object)this;
214
215             // We cannot do simple object identity comparisons for generic methods.
216             // Equals will be called in CerHashTable when RuntimeType+RuntimeTypeCache.GetGenericMethodInfo()
217             // retrieve items from and insert items into s_methodInstantiations which is a CerHashtable.
218
219             RuntimeMethodInfo mi = obj as RuntimeMethodInfo;
220
221             if (mi == null || !mi.IsGenericMethod)
222                 return false;
223
224             // now we know that both operands are generic methods
225
226             IRuntimeMethodInfo handle1 = RuntimeMethodHandle.StripMethodInstantiation(this);
227             IRuntimeMethodInfo handle2 = RuntimeMethodHandle.StripMethodInstantiation(mi);
228             if (handle1.Value.Value != handle2.Value.Value)
229                 return false;
230
231             Type[] lhs = GetGenericArguments();
232             Type[] rhs = mi.GetGenericArguments();
233
234             if (lhs.Length != rhs.Length)
235                 return false;
236
237             for (int i = 0; i < lhs.Length; i++)
238             {
239                 if (lhs[i] != rhs[i])
240                     return false;
241             }
242
243             if (DeclaringType != mi.DeclaringType)
244                 return false;
245
246             if (ReflectedType != mi.ReflectedType)
247                 return false;
248
249             return true;
250         }
251         #endregion
252
253         #region ICustomAttributeProvider
254         public override object[] GetCustomAttributes(bool inherit)
255         {
256             return CustomAttribute.GetCustomAttributes(this, typeof(object) as RuntimeType, inherit);
257         }
258
259         public override object[] GetCustomAttributes(Type attributeType, bool inherit)
260         {
261             if (attributeType == null)
262                 throw new ArgumentNullException(nameof(attributeType));
263
264             RuntimeType attributeRuntimeType = attributeType.UnderlyingSystemType as RuntimeType;
265
266             if (attributeRuntimeType == null)
267                 throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));
268
269             return CustomAttribute.GetCustomAttributes(this, attributeRuntimeType, inherit);
270         }
271
272         public override bool IsDefined(Type attributeType, bool inherit)
273         {
274             if (attributeType == null)
275                 throw new ArgumentNullException(nameof(attributeType));
276
277             RuntimeType attributeRuntimeType = attributeType.UnderlyingSystemType as RuntimeType;
278
279             if (attributeRuntimeType == null)
280                 throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));
281
282             return CustomAttribute.IsDefined(this, attributeRuntimeType, inherit);
283         }
284
285         public override IList<CustomAttributeData> GetCustomAttributesData()
286         {
287             return CustomAttributeData.GetCustomAttributesInternal(this);
288         }
289         #endregion
290
291         #region MemberInfo Overrides
292         public override string Name
293         {
294             get
295             {
296                 if (m_name == null)
297                     m_name = RuntimeMethodHandle.GetName(this);
298
299                 return m_name;
300             }
301         }
302
303         public override Type DeclaringType
304         {
305             get
306             {
307                 if (m_reflectedTypeCache.IsGlobal)
308                     return null;
309
310                 return m_declaringType;
311             }
312         }
313
314         public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeMethodInfo>(other);
315
316         public override Type ReflectedType
317         {
318             get
319             {
320                 if (m_reflectedTypeCache.IsGlobal)
321                     return null;
322
323                 return m_reflectedTypeCache.GetRuntimeType();
324             }
325         }
326
327         public override MemberTypes MemberType { get { return MemberTypes.Method; } }
328         public override int MetadataToken
329         {
330             get { return RuntimeMethodHandle.GetMethodDef(this); }
331         }
332         public override Module Module { get { return GetRuntimeModule(); } }
333         internal RuntimeType GetRuntimeType() { return m_declaringType; }
334         internal RuntimeModule GetRuntimeModule() { return m_declaringType.GetRuntimeModule(); }
335         internal RuntimeAssembly GetRuntimeAssembly() { return GetRuntimeModule().GetRuntimeAssembly(); }
336
337         public override bool IsSecurityCritical
338         {
339             get { return true; }
340         }
341         public override bool IsSecuritySafeCritical
342         {
343             get { return false; }
344         }
345         public override bool IsSecurityTransparent
346         {
347             get { return false; }
348         }
349         #endregion
350
351         #region MethodBase Overrides
352         internal override ParameterInfo[] GetParametersNoCopy()
353         {
354             FetchNonReturnParameters();
355
356             return m_parameters;
357         }
358
359         public override ParameterInfo[] GetParameters()
360         {
361             FetchNonReturnParameters();
362
363             if (m_parameters.Length == 0)
364                 return m_parameters;
365
366             ParameterInfo[] ret = new ParameterInfo[m_parameters.Length];
367
368             Array.Copy(m_parameters, ret, m_parameters.Length);
369
370             return ret;
371         }
372
373         public override MethodImplAttributes GetMethodImplementationFlags()
374         {
375             return RuntimeMethodHandle.GetImplAttributes(this);
376         }
377
378         public override RuntimeMethodHandle MethodHandle
379         {
380             get
381             {
382                 return new RuntimeMethodHandle(this);
383             }
384         }
385
386         public override MethodAttributes Attributes { get { return m_methodAttributes; } }
387
388         public override CallingConventions CallingConvention
389         {
390             get
391             {
392                 return Signature.CallingConvention;
393             }
394         }
395
396         public override MethodBody GetMethodBody()
397         {
398             RuntimeMethodBody mb = RuntimeMethodHandle.GetMethodBody(this, ReflectedTypeInternal);
399             if (mb != null)
400                 mb._methodBase = this;
401             return mb;
402         }
403         #endregion
404
405         #region Invocation Logic(On MemberBase)
406         private void CheckConsistency(object target)
407         {
408             // only test instance methods
409             if ((m_methodAttributes & MethodAttributes.Static) != MethodAttributes.Static)
410             {
411                 if (!m_declaringType.IsInstanceOfType(target))
412                 {
413                     if (target == null)
414                         throw new TargetException(SR.RFLCT_Targ_StatMethReqTarg);
415                     else
416                         throw new TargetException(SR.RFLCT_Targ_ITargMismatch);
417                 }
418             }
419         }
420
421         private void ThrowNoInvokeException()
422         {
423             // method is on a class that contains stack pointers
424             if ((InvocationFlags & INVOCATION_FLAGS.INVOCATION_FLAGS_CONTAINS_STACK_POINTERS) != 0)
425             {
426                 throw new NotSupportedException();
427             }
428             // method is vararg
429             else if ((CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs)
430             {
431                 throw new NotSupportedException();
432             }
433             // method is generic or on a generic class
434             else if (DeclaringType.ContainsGenericParameters || ContainsGenericParameters)
435             {
436                 throw new InvalidOperationException(SR.Arg_UnboundGenParam);
437             }
438             // method is abstract class
439             else if (IsAbstract)
440             {
441                 throw new MemberAccessException();
442             }
443             else if (ReturnType.IsByRef)
444             {
445                 Type elementType = ReturnType.GetElementType();
446                 if (elementType.IsByRefLike)
447                     throw new NotSupportedException(SR.NotSupported_ByRefToByRefLikeReturn);    
448                 if (elementType == typeof(void))
449                     throw new NotSupportedException(SR.NotSupported_ByRefToVoidReturn);
450             }
451
452             throw new TargetException();
453         }
454
455         [DebuggerStepThroughAttribute]
456         [Diagnostics.DebuggerHidden]
457         public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
458         {
459             object[] arguments = InvokeArgumentsCheck(obj, invokeAttr, binder, parameters, culture);
460
461             bool wrapExceptions = (invokeAttr & BindingFlags.DoNotWrapExceptions) == 0;
462             if (arguments == null || arguments.Length == 0)
463                 return RuntimeMethodHandle.InvokeMethod(obj, null, Signature, false, wrapExceptions);
464             else
465             {
466                 object retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, false, wrapExceptions);
467
468                 // copy out. This should be made only if ByRef are present.
469                 for (int index = 0; index < arguments.Length; index++)
470                     parameters[index] = arguments[index];
471
472                 return retValue;
473             }
474         }
475
476         [DebuggerStepThroughAttribute]
477         [Diagnostics.DebuggerHidden]
478         private object[] InvokeArgumentsCheck(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
479         {
480             Signature sig = Signature;
481
482             // get the signature 
483             int formalCount = sig.Arguments.Length;
484             int actualCount = (parameters != null) ? parameters.Length : 0;
485
486             INVOCATION_FLAGS invocationFlags = InvocationFlags;
487
488             // INVOCATION_FLAGS_CONTAINS_STACK_POINTERS means that the struct (either the declaring type or the return type)
489             // contains pointers that point to the stack. This is either a ByRef or a TypedReference. These structs cannot
490             // be boxed and thus cannot be invoked through reflection which only deals with boxed value type objects.
491             if ((invocationFlags & (INVOCATION_FLAGS.INVOCATION_FLAGS_NO_INVOKE | INVOCATION_FLAGS.INVOCATION_FLAGS_CONTAINS_STACK_POINTERS)) != 0)
492                 ThrowNoInvokeException();
493
494             // check basic method consistency. This call will throw if there are problems in the target/method relationship
495             CheckConsistency(obj);
496
497             if (formalCount != actualCount)
498                 throw new TargetParameterCountException(SR.Arg_ParmCnt);
499
500             if (actualCount != 0)
501                 return CheckArguments(parameters, binder, invokeAttr, culture, sig);
502             else
503                 return null;
504         }
505
506         #endregion
507
508         #region MethodInfo Overrides
509         public override Type ReturnType
510         {
511             get { return Signature.ReturnType; }
512         }
513
514         public override ICustomAttributeProvider ReturnTypeCustomAttributes
515         {
516             get { return ReturnParameter; }
517         }
518
519         public override ParameterInfo ReturnParameter
520         {
521             get
522             {
523                 FetchReturnParameter();
524                 return m_returnParameter as ParameterInfo;
525             }
526         }
527
528         public override bool IsCollectible => RuntimeMethodHandle.GetIsCollectible(new RuntimeMethodHandleInternal(m_handle));
529
530         public override MethodInfo GetBaseDefinition()
531         {
532             if (!IsVirtual || IsStatic || m_declaringType == null || m_declaringType.IsInterface)
533                 return this;
534
535             int slot = RuntimeMethodHandle.GetSlot(this);
536             RuntimeType declaringType = (RuntimeType)DeclaringType;
537             RuntimeType baseDeclaringType = declaringType;
538             RuntimeMethodHandleInternal baseMethodHandle = new RuntimeMethodHandleInternal();
539
540             do
541             {
542                 int cVtblSlots = RuntimeTypeHandle.GetNumVirtuals(declaringType);
543
544                 if (cVtblSlots <= slot)
545                     break;
546
547                 baseMethodHandle = RuntimeTypeHandle.GetMethodAt(declaringType, slot);
548                 baseDeclaringType = declaringType;
549
550                 declaringType = (RuntimeType)declaringType.BaseType;
551             } while (declaringType != null);
552
553             return (MethodInfo)RuntimeType.GetMethodBase(baseDeclaringType, baseMethodHandle);
554         }
555
556         public override Delegate CreateDelegate(Type delegateType)
557         {
558             // This API existed in v1/v1.1 and only expected to create closed
559             // instance delegates. Constrain the call to BindToMethodInfo to
560             // open delegates only for backwards compatibility. But we'll allow
561             // relaxed signature checking and open static delegates because
562             // there's no ambiguity there (the caller would have to explicitly
563             // pass us a static method or a method with a non-exact signature
564             // and the only change in behavior from v1.1 there is that we won't
565             // fail the call).
566             return CreateDelegateInternal(
567                 delegateType,
568                 null,
569                 DelegateBindingFlags.OpenDelegateOnly | DelegateBindingFlags.RelaxedSignature);
570         }
571
572         public override Delegate CreateDelegate(Type delegateType, object target)
573         {
574             // This API is new in Whidbey and allows the full range of delegate
575             // flexability (open or closed delegates binding to static or
576             // instance methods with relaxed signature checking). The delegate
577             // can also be closed over null. There's no ambiguity with all these
578             // options since the caller is providing us a specific MethodInfo.
579             return CreateDelegateInternal(
580                 delegateType,
581                 target,
582                 DelegateBindingFlags.RelaxedSignature);
583         }
584
585         private Delegate CreateDelegateInternal(Type delegateType, object firstArgument, DelegateBindingFlags bindingFlags)
586         {
587             // Validate the parameters.
588             if (delegateType == null)
589                 throw new ArgumentNullException(nameof(delegateType));
590
591             RuntimeType rtType = delegateType as RuntimeType;
592             if (rtType == null)
593                 throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(delegateType));
594
595             if (!rtType.IsDelegate())
596                 throw new ArgumentException(SR.Arg_MustBeDelegate, nameof(delegateType));
597
598             Delegate d = Delegate.CreateDelegateInternal(rtType, this, firstArgument, bindingFlags);
599             if (d == null)
600             {
601                 throw new ArgumentException(SR.Arg_DlgtTargMeth);
602             }
603
604             return d;
605         }
606
607         #endregion
608
609         #region Generics
610         public override MethodInfo MakeGenericMethod(params Type[] methodInstantiation)
611         {
612             if (methodInstantiation == null)
613                 throw new ArgumentNullException(nameof(methodInstantiation));
614
615             RuntimeType[] methodInstantionRuntimeType = new RuntimeType[methodInstantiation.Length];
616
617             if (!IsGenericMethodDefinition)
618                 throw new InvalidOperationException(
619                     SR.Format(SR.Arg_NotGenericMethodDefinition, this));
620
621             for (int i = 0; i < methodInstantiation.Length; i++)
622             {
623                 Type methodInstantiationElem = methodInstantiation[i];
624
625                 if (methodInstantiationElem == null)
626                     throw new ArgumentNullException();
627
628                 RuntimeType rtMethodInstantiationElem = methodInstantiationElem as RuntimeType;
629
630                 if (rtMethodInstantiationElem == null)
631                 {
632                     Type[] methodInstantiationCopy = new Type[methodInstantiation.Length];
633                     for (int iCopy = 0; iCopy < methodInstantiation.Length; iCopy++)
634                         methodInstantiationCopy[iCopy] = methodInstantiation[iCopy];
635                     methodInstantiation = methodInstantiationCopy;
636                     return System.Reflection.Emit.MethodBuilderInstantiation.MakeGenericMethod(this, methodInstantiation);
637                 }
638
639                 methodInstantionRuntimeType[i] = rtMethodInstantiationElem;
640             }
641
642             RuntimeType[] genericParameters = GetGenericArgumentsInternal();
643
644             RuntimeType.SanityCheckGenericArguments(methodInstantionRuntimeType, genericParameters);
645
646             MethodInfo ret = null;
647
648             try
649             {
650                 ret = RuntimeType.GetMethodBase(ReflectedTypeInternal,
651                     RuntimeMethodHandle.GetStubIfNeeded(new RuntimeMethodHandleInternal(m_handle), m_declaringType, methodInstantionRuntimeType)) as MethodInfo;
652             }
653             catch (VerificationException e)
654             {
655                 RuntimeType.ValidateGenericArguments(this, methodInstantionRuntimeType, e);
656                 throw;
657             }
658
659             return ret;
660         }
661
662         internal RuntimeType[] GetGenericArgumentsInternal()
663         {
664             return RuntimeMethodHandle.GetMethodInstantiationInternal(this);
665         }
666
667         public override Type[] GetGenericArguments()
668         {
669             Type[] types = RuntimeMethodHandle.GetMethodInstantiationPublic(this);
670
671             if (types == null)
672             {
673                 types = Array.Empty<Type>();
674             }
675             return types;
676         }
677
678         public override MethodInfo GetGenericMethodDefinition()
679         {
680             if (!IsGenericMethod)
681                 throw new InvalidOperationException();
682
683             return RuntimeType.GetMethodBase(m_declaringType, RuntimeMethodHandle.StripMethodInstantiation(this)) as MethodInfo;
684         }
685
686         public override bool IsGenericMethod
687         {
688             get { return RuntimeMethodHandle.HasMethodInstantiation(this); }
689         }
690
691         public override bool IsGenericMethodDefinition
692         {
693             get { return RuntimeMethodHandle.IsGenericMethodDefinition(this); }
694         }
695
696         public override bool ContainsGenericParameters
697         {
698             get
699             {
700                 if (DeclaringType != null && DeclaringType.ContainsGenericParameters)
701                     return true;
702
703                 if (!IsGenericMethod)
704                     return false;
705
706                 Type[] pis = GetGenericArguments();
707                 for (int i = 0; i < pis.Length; i++)
708                 {
709                     if (pis[i].ContainsGenericParameters)
710                         return true;
711                 }
712
713                 return false;
714             }
715         }
716         #endregion
717
718         #region Legacy Internal
719         internal static MethodBase InternalGetCurrentMethod(ref StackCrawlMark stackMark)
720         {
721             IRuntimeMethodInfo method = RuntimeMethodHandle.GetCurrentMethod(ref stackMark);
722
723             if (method == null)
724                 return null;
725
726             return RuntimeType.GetMethodBase(method);
727         }
728         #endregion
729     }
730 }