protected override MethodILData CreateValueFromKey(MethodDesc key) { MethodIL methodIL = ILProvider.GetMethodIL(key); if (methodIL == null && key.IsPInvoke && _compilationModuleGroup.GeneratesPInvoke(key)) { methodIL = PInvokeILEmitter.EmitIL(key); } return(new MethodILData() { Method = key, MethodIL = methodIL }); }
protected override MethodILData CreateValueFromKey(MethodDesc key) { MethodIL methodIL = ILProvider.GetMethodIL(key); if (methodIL == null && key.IsPInvoke && _compilationModuleGroup.GeneratesPInvoke(key)) { // TODO: enable when IL Stubs are fixed to be non-shared // methodIL = PInvokeILEmitter.EmitIL(key); } return(new MethodILData() { Method = key, MethodIL = methodIL }); }
protected override void ComputeDependencyNodeDependencies(List <DependencyNodeCore <NodeFactory> > obj) { foreach (DependencyNodeCore <NodeFactory> dependency in obj) { var methodCodeNodeNeedingCode = dependency as MethodCodeNode; if (methodCodeNodeNeedingCode == null) { // To compute dependencies of the shadow method that tracks dictionary // dependencies we need to ensure there is code for the canonical method body. var dependencyMethod = (ShadowConcreteMethodNode)dependency; methodCodeNodeNeedingCode = (MethodCodeNode)dependencyMethod.CanonicalMethodNode; } // We might have already compiled this method. if (methodCodeNodeNeedingCode.StaticDependenciesAreComputed) { continue; } MethodDesc method = methodCodeNodeNeedingCode.Method; if (Logger.IsVerbose) { string methodName = method.ToString(); Logger.Writer.WriteLine("Compiling " + methodName); } try { _corInfo.CompileMethod(methodCodeNodeNeedingCode); } catch (TypeSystemException ex) { // TODO: fail compilation if a switch was passed // Try to compile the method again, but with a throwing method body this time. MethodIL throwingIL = TypeSystemThrowingILEmitter.EmitIL(method, ex); _corInfo.CompileMethod(methodCodeNodeNeedingCode, throwingIL); // TODO: Log as a warning. For now, just log to the logger; but this needs to // have an error code, be supressible, the method name/sig needs to be properly formatted, etc. // https://github.com/dotnet/corert/issues/72 Logger.Writer.WriteLine($"Warning: Method `{method}` will always throw because: {ex.Message}"); } } }
/// <summary> /// Skips over a "foo == typeof(Bar)" or "typeof(Foo) == typeof(Bar)" sequence. /// </summary> private static bool IsTypeEqualityTest(MethodIL methodIL, ILReader reader, out ILReader afterTest) { afterTest = default; if (reader.ReadILOpcode() != ILOpcode.call) { return(false); } MethodDesc method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method == null || method.Name != "GetTypeFromHandle" && !method.OwningType.IsSystemType()) { return(false); } ILOpcode opcode = reader.ReadILOpcode(); if (opcode == ILOpcode.ldtoken) { reader.ReadILToken(); opcode = reader.ReadILOpcode(); if (opcode != ILOpcode.call) { return(false); } method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method == null || method.Name != "GetTypeFromHandle" && !method.OwningType.IsSystemType()) { return(false); } opcode = reader.ReadILOpcode(); } if (opcode != ILOpcode.call) { return(false); } method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method == null || method.Name != "op_Equality" && !method.OwningType.IsSystemType()) { return(false); } afterTest = reader; return(true); }
public ReflectionPatternContext( Logger logger, bool reportingEnabled, TypeSystemEntity source, Origin memberWithRequirements) { _logger = logger; ReportingEnabled = reportingEnabled; Source = source; MemberWithRequirements = memberWithRequirements; _ilOffset = 0; _sourceIL = null; #if DEBUG _patternAnalysisAttempted = false; _patternReported = false; #endif }
protected override void GetDependenciesDueToMethodCodePresence(ref DependencyNodeCore <NodeFactory> .DependencyList dependencies, NodeFactory factory, MethodDesc method) { if (_ilProvider != null) { MethodIL methodIL = _ilProvider.GetMethodIL(method); if (methodIL != null) { try { ReflectionMethodBodyScanner.ScanMarshalOnly(ref dependencies, factory, methodIL); } catch (TypeSystemException) { // A problem with the IL - we just don't scan it... } } } }
protected override void GetDependenciesDueToMethodCodePresenceInternal(ref DependencyList dependencies, NodeFactory factory, MethodDesc method) { if ((_generationOptions & UsageBasedMetadataGenerationOptions.ILScanning) != 0) { MethodIL methodIL = _ilProvider.GetMethodIL(method); if (methodIL != null) { try { ReflectionMethodBodyScanner.Scan(ref dependencies, factory, methodIL); } catch (TypeSystemException) { // A problem with the IL - we just don't scan it... } } } }
protected override void ComputeDependencyNodeDependencies(List <DependencyNodeCore <NodeFactory> > obj) { foreach (DependencyNodeCore <NodeFactory> dependency in obj) { var methodCodeNodeNeedingCode = dependency as MethodCodeNode; if (methodCodeNodeNeedingCode == null) { // To compute dependencies of the shadow method that tracks dictionary // dependencies we need to ensure there is code for the canonical method body. var dependencyMethod = (ShadowConcreteMethodNode <MethodCodeNode>)dependency; methodCodeNodeNeedingCode = dependencyMethod.CanonicalMethodNode; } // We might have already compiled this method. if (methodCodeNodeNeedingCode.StaticDependenciesAreComputed) { continue; } MethodDesc method = methodCodeNodeNeedingCode.Method; if (Logger.IsVerbose) { string methodName = method.ToString(); Logger.Writer.WriteLine("Compiling " + methodName); } try { _corInfo.CompileMethod(methodCodeNodeNeedingCode); } catch (TypeSystemException ex) { // TODO: fail compilation if a switch was passed // Try to compile the method again, but with a throwing method body this time. MethodIL throwingIL = TypeSystemThrowingILEmitter.EmitIL(method, ex); _corInfo.CompileMethod(methodCodeNodeNeedingCode, throwingIL); // TODO: Log as a warning } } }
private void CompileSingleMethod(CorInfoImpl corInfo, MethodCodeNode methodCodeNodeNeedingCode) { MethodDesc method = methodCodeNodeNeedingCode.Method; TypeSystemException exception = _methodImportationErrorProvider.GetCompilationError(method); // If we previously failed to import the method, do not try to import it again and go // directly to the error path. if (exception == null) { try { corInfo.CompileMethod(methodCodeNodeNeedingCode); } catch (TypeSystemException ex) { exception = ex; } } if (exception != null) { // Try to compile the method again, but with a throwing method body this time. MethodIL throwingIL = TypeSystemThrowingILEmitter.EmitIL(method, exception); corInfo.CompileMethod(methodCodeNodeNeedingCode, throwingIL); if (exception is TypeSystemException.InvalidProgramException && method.OwningType is MetadataType mdOwningType && mdOwningType.HasCustomAttribute("System.Runtime.InteropServices", "ClassInterfaceAttribute")) { Logger.LogWarning(method, DiagnosticId.COMInteropNotSupportedInFullAOT); } if ((_compilationOptions & RyuJitCompilationOptions.UseResilience) != 0) { Logger.LogMessage($"Method '{method}' will always throw because: {exception.Message}"); } else { Logger.LogError($"Method will always throw because: {exception.Message}", 1005, method, MessageSubCategory.AotAnalysis); } } }
private void CompileSingleMethod(CorInfoImpl corInfo, MethodCodeNode methodCodeNodeNeedingCode) { try { MethodDesc method = methodCodeNodeNeedingCode.Method; TypeSystemException exception = _methodImportationErrorProvider.GetCompilationError(method); // If we previously failed to import the method, do not try to import it again and go // directly to the error path. if (exception == null) { try { corInfo.CompileMethod(methodCodeNodeNeedingCode); } catch (TypeSystemException ex) { exception = ex; } } if (exception != null) { // TODO: fail compilation if a switch was passed // Try to compile the method again, but with a throwing method body this time. MethodIL throwingIL = TypeSystemThrowingILEmitter.EmitIL(method, exception); corInfo.CompileMethod(methodCodeNodeNeedingCode, throwingIL); if (exception is TypeSystemException.InvalidProgramException && method.OwningType is MetadataType mdOwningType && mdOwningType.HasCustomAttribute("System.Runtime.InteropServices", "ClassInterfaceAttribute")) { Logger.LogWarning("COM interop is not supported with full ahead of time compilation", 3052, method, MessageSubCategory.AotAnalysis); } else { Logger.LogWarning($"Method will always throw because: {exception.Message}", 1005, method, MessageSubCategory.AotAnalysis); } } }
private void CompileSingleMethod(ScannedMethodNode methodCodeNodeNeedingCode) { MethodDesc method = methodCodeNodeNeedingCode.Method; try { var importer = new ILImporter(this, method); methodCodeNodeNeedingCode.InitializeDependencies(_nodeFactory, importer.Import()); } catch (TypeSystemException ex) { // Try to compile the method again, but with a throwing method body this time. MethodIL throwingIL = TypeSystemThrowingILEmitter.EmitIL(method, ex); var importer = new ILImporter(this, method, throwingIL); methodCodeNodeNeedingCode.InitializeDependencies(_nodeFactory, importer.Import(), ex); } catch (Exception ex) { throw new CodeGenerationFailedException(method, ex); } }
private StackSlot PopUnknown(Stack <StackSlot> stack, int count, MethodIL method, int ilOffset) { if (count < 1) { throw new InvalidOperationException(); } StackSlot topOfStack = default; CheckForInvalidStack(stack, count, method, ilOffset); for (int i = 0; i < count; ++i) { StackSlot slot = stack.Pop(); if (i == 0) { topOfStack = slot; } } return(topOfStack); }
public override MethodIL GetMethodIL(MethodDesc method) { BodySubstitution substitution = GetSubstitution(method); if (substitution != null) { return(substitution.EmitIL(method)); } // BEGIN TEMPORARY WORKAROUND // // The following lines should just be: // return _nestedILProvider.GetMethodIL(method); // But we want to allow this to be used as a general-purpose IL provider. // // Rewriting all IL has compilation throughput hit we don't want. MethodIL result = _nestedILProvider.GetMethodIL(method); if (result != null) { var resultDef = result.GetMethodILDefinition(); if (resultDef != result) { MethodIL newBodyDef = GetMethodILWithInlinedSubstitutions(resultDef); // If we didn't rewrite the body, we can keep the existing result. if (newBodyDef != resultDef) { result = new InstantiatedMethodIL(method, newBodyDef); } } else { result = GetMethodILWithInlinedSubstitutions(result); } } return(result); // END TEMPORARY WORKAROUND }
public MessageOrigin(MethodIL origin, int ilOffset) { string document = null; int? lineNumber = null; IEnumerable <ILSequencePoint> sequencePoints = origin.GetDebugInfo()?.GetSequencePoints(); if (sequencePoints != null) { foreach (var sequencePoint in sequencePoints) { if (sequencePoint.Offset <= ilOffset) { document = sequencePoint.Document; lineNumber = sequencePoint.LineNumber; } } } FileName = document; MemberDefinition = origin.OwningMethod; SourceLine = lineNumber; SourceColumn = null; }
private void CompileSingleMethod(ScannedMethodNode methodCodeNodeNeedingCode) { MethodDesc method = methodCodeNodeNeedingCode.Method; try { var importer = new ILImporter(this, method); methodCodeNodeNeedingCode.InitializeDependencies(_nodeFactory, importer.Import()); } catch (TypeSystemException ex) { // Try to compile the method again, but with a throwing method body this time. MethodIL throwingIL = TypeSystemThrowingILEmitter.EmitIL(method, ex); var importer = new ILImporter(this, method, throwingIL); methodCodeNodeNeedingCode.InitializeDependencies(_nodeFactory, importer.Import()); } finally { if (_compilationCountdown != null) { _compilationCountdown.Signal(); } } }
public static HashSet <int> ComputeBranchTargets(this MethodIL methodBody) { HashSet <int> branchTargets = new HashSet <int>(); var reader = new ILReader(methodBody.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); if (opcode >= ILOpcode.br_s && opcode <= ILOpcode.blt_un) { branchTargets.Add(reader.ReadBranchDestination(opcode)); } else if (opcode == ILOpcode.switch_) { uint count = reader.ReadILUInt32(); int jmpBase = reader.Offset + (int)(4 * count); for (uint i = 0; i < count; i++) { branchTargets.Add((int)reader.ReadILUInt32() + jmpBase); } } else { reader.Skip(opcode); } } foreach (ILExceptionRegion einfo in methodBody.GetExceptionRegions()) { if (einfo.Kind == ILExceptionRegionKind.Filter) { branchTargets.Add(einfo.FilterOffset); } branchTargets.Add(einfo.HandlerOffset); } return(branchTargets); }
private PerMethodInfo GetOrCreateInfo(MethodDesc md) { if (!_methodInf.TryGetValue(md, out PerMethodInfo pmi)) { MethodIL il = md switch { EcmaMethod em => EcmaMethodIL.Create(em), _ => new InstantiatedMethodIL(md, EcmaMethodIL.Create((EcmaMethod)md.GetTypicalMethodDefinition())), }; if (il == null) { return(null); } _methodInf.Add(md, pmi = new PerMethodInfo()); pmi.IL = il; pmi.FlowGraph = FlowGraph.Create(il); pmi.Profile = new SampleProfile(pmi.IL, pmi.FlowGraph); } return(pmi); }
/// <summary> /// Subset of <see cref="Scan(ref DependencyList, NodeFactory, MethodIL)"/> that only deals with Marshal.SizeOf. /// </summary> public static void ScanMarshalOnly(ref DependencyList list, NodeFactory factory, MethodIL methodIL) { ILReader reader = new ILReader(methodIL.GetILBytes()); Tracker tracker = new Tracker(methodIL); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldtoken: tracker.TrackLdTokenToken(reader.ReadILToken()); break; case ILOpcode.call: var method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method != null && method.Name == "SizeOf" && IsMarshalSizeOf(method)) { TypeDesc type = tracker.GetLastType(); if (IsTypeEligibleForMarshalSizeOfTracking(type)) { list = list ?? new DependencyList(); list.Add(factory.StructMarshallingData((DefType)type), "Marshal.SizeOf"); } } break; default: reader.Skip(opcode); break; } } }
private static void HandleCall(ref DependencyList list, NodeFactory factory, MethodIL methodIL, MethodDesc methodCalled, ref Tracker tracker) { switch (methodCalled.Name) { // Enum.GetValues(Type) needs array of that type case "GetValues" when methodCalled.OwningType == factory.TypeSystemContext.GetWellKnownType(WellKnownType.Enum): { TypeDesc type = tracker.GetLastType(); if (type != null && type.IsEnum && !type.IsGenericDefinition /* generic enums! */) { // Type could be something weird like MyEnum<object, __Canon> - normalize it type = type.NormalizeInstantiation(); list = list ?? new DependencyList(); list.Add(factory.ConstructedTypeSymbol(type.MakeArrayType()), "Enum.GetValues"); } } break; // Type.GetType(string...) needs the type with the given name case "GetType" when methodCalled.OwningType.IsSystemType() && methodCalled.Signature.Length > 0: { string name = tracker.GetLastString(); if (name != null && methodIL.OwningMethod.OwningType is MetadataType mdType && ResolveType(name, mdType.Module, out TypeDesc type, out ModuleDesc referenceModule) && !factory.MetadataManager.IsReflectionBlocked(type)) { const string reason = "Type.GetType"; list = list ?? new DependencyList(); list.Add(factory.MaximallyConstructableType(type), reason); // Also add module metadata in case this reference was through a type forward if (factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) { list.Add(factory.ModuleMetadata(referenceModule), reason); } // Opportunistically remember the type so that it flows to Type.GetMethod if needed. tracker.TrackType(type); } } break; // Type.GetMethod(string...) case "GetMethod" when methodCalled.OwningType.IsSystemType(): { string name = tracker.GetLastString(); TypeDesc type = tracker.GetLastType(); if (name != null && type != null && !factory.MetadataManager.IsReflectionBlocked(type)) { if (type.IsGenericDefinition) { Instantiation inst = TypeExtensions.GetInstantiationThatMeetsConstraints(type.Instantiation, allowCanon: false); if (inst.IsNull) { break; } type = ((MetadataType)type).MakeInstantiatedType(inst); list = list ?? new DependencyList(); list.Add(factory.MaximallyConstructableType(type), "Type.GetMethod"); } else { // Type could be something weird like SomeType<object, __Canon> - normalize it type = type.NormalizeInstantiation(); } MethodDesc reflectedMethod = type.GetMethod(name, null); if (reflectedMethod != null && !factory.MetadataManager.IsReflectionBlocked(reflectedMethod)) { if (reflectedMethod.HasInstantiation) { // Don't want to accidentally get Foo<__Canon>.Bar<object>() if (reflectedMethod.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any)) { break; } Instantiation inst = TypeExtensions.GetInstantiationThatMeetsConstraints(reflectedMethod.Instantiation, allowCanon: false); if (inst.IsNull) { break; } reflectedMethod = reflectedMethod.MakeInstantiatedMethod(inst); } const string reason = "Type.GetMethod"; list = list ?? new DependencyList(); if (reflectedMethod.IsVirtual) { RootVirtualMethodForReflection(ref list, factory, reflectedMethod, reason); } if (!reflectedMethod.IsAbstract) { list.Add(factory.CanonicalEntrypoint(reflectedMethod), reason); if (reflectedMethod.HasInstantiation && reflectedMethod != reflectedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)) { list.Add(factory.MethodGenericDictionary(reflectedMethod), reason); } } } } } break; case "SizeOf" when IsMarshalSizeOf(methodCalled): { TypeDesc type = tracker.GetLastType(); if (IsTypeEligibleForMarshalSizeOfTracking(type)) { list = list ?? new DependencyList(); list.Add(factory.StructMarshallingData((DefType)type), "Marshal.SizeOf"); } } break; } }
public MethodDebugInformation GetDebugInfo(MethodIL methodIL) { return(_debugInformationProvider.GetDebugInfo(methodIL)); }
private MetadataLoadedInfo LoadMetadata() { HashSet <ModuleDesc> metadataModules = new HashSet <ModuleDesc>(); MetadataType typeWithMetadataMappings = (MetadataType)_metadataDescribingModule.GetTypeByCustomAttributeTypeName(MetadataMappingTypeName); MethodDesc fullMetadataMethod = typeWithMetadataMappings.GetMethod("Metadata", null); MethodDesc weakMetadataMethod = typeWithMetadataMappings.GetMethod("WeakMetadata", null); ILProvider ilProvider = new ILProvider(null); MetadataLoadedInfo result = new MetadataLoadedInfo(); if (fullMetadataMethod != null) { MethodIL fullMethodIL = ilProvider.GetMethodIL(fullMetadataMethod); ReadMetadataMethod(fullMethodIL, ref result.AllTypeMappings, ref result.MethodMappings, ref result.FieldMappings, ref metadataModules); foreach (var mapping in result.AllTypeMappings) { result.TypesWithStrongMetadataMappings.Add(mapping.Key); } } if (weakMetadataMethod != null) { MethodIL weakMethodIL = ilProvider.GetMethodIL(weakMetadataMethod); Dictionary <MethodDesc, int> weakMethodMappings = new Dictionary <MethodDesc, int>(); Dictionary <FieldDesc, int> weakFieldMappings = new Dictionary <FieldDesc, int>(); ReadMetadataMethod(weakMethodIL, ref result.AllTypeMappings, ref weakMethodMappings, ref weakFieldMappings, ref metadataModules); if ((weakMethodMappings.Count > 0) || (weakFieldMappings.Count > 0)) { // the format does not permit weak field/method mappings throw new BadImageFormatException(); } } result.MetadataModules = ImmutableArray.CreateRange(metadataModules); ImmutableArray <ModuleDesc> .Builder externalMetadataModulesBuilder = ImmutableArray.CreateBuilder <ModuleDesc>(); ImmutableArray <ModuleDesc> .Builder localMetadataModulesBuilder = ImmutableArray.CreateBuilder <ModuleDesc>(); foreach (ModuleDesc module in result.MetadataModules) { if (!_compilationModules.Contains(module)) { externalMetadataModulesBuilder.Add(module); } else { localMetadataModulesBuilder.Add(module); } } result.ExternalMetadataModules = externalMetadataModulesBuilder.ToImmutable(); result.LocalMetadataModules = localMetadataModulesBuilder.ToImmutable(); // TODO! Replace with something more complete that capture the generic instantiations that the pre-analysis // indicates should have been present foreach (var pair in result.MethodMappings) { MethodDesc reflectableMethod = pair.Key; if (reflectableMethod.HasInstantiation) { continue; } if (reflectableMethod.OwningType.HasInstantiation) { continue; } MethodDesc typicalDynamicInvokeStub; if (!_dynamicInvokeStubs.Value.TryGetValue(reflectableMethod, out typicalDynamicInvokeStub)) { continue; } MethodDesc instantiatiatedDynamicInvokeStub = InstantiateDynamicInvokeMethodForMethod(typicalDynamicInvokeStub, reflectableMethod); result.DynamicInvokeCompiledMethods.Add(instantiatiatedDynamicInvokeStub.GetCanonMethodTarget(CanonicalFormKind.Specific)); } return(result); }
public ILStreamReader(MethodIL methodIL) { _methodIL = methodIL; _ilBytes = methodIL.GetILBytes(); _currentOffset = 0; }
public ILStreamReader(MethodIL methodIL) { _methodIL = methodIL; _reader = new ILReader(methodIL.GetILBytes()); }
public MethodDebugInformation GetDebugInfo(MethodIL methodIL) { // This method looks odd right now, but it's an extensibility point that lets us generate // fake debugging information for things that don't have physical symbols. return(methodIL.GetDebugInfo()); }
private static void HandleCall(ref DependencyList list, NodeFactory factory, MethodIL methodIL, MethodDesc methodCalled, ref Tracker tracker, ScanModes modes) { bool scanningReflection = (modes & ScanModes.Reflection) != 0; bool scanningInterop = (modes & ScanModes.Interop) != 0; switch (methodCalled.Name) { // Enum.GetValues(Type) needs array of that type case "GetValues" when scanningReflection && methodCalled.OwningType == factory.TypeSystemContext.GetWellKnownType(WellKnownType.Enum): { TypeDesc type = tracker.GetLastType(); if (type != null && type.IsEnum && !type.IsGenericDefinition /* generic enums! */) { // Type could be something weird like MyEnum<object, __Canon> - normalize it type = type.NormalizeInstantiation(); list = list ?? new DependencyList(); list.Add(factory.ConstructedTypeSymbol(type.MakeArrayType()), "Enum.GetValues"); } } break; // Type.GetType(string...) needs the type with the given name case "GetType" when scanningReflection && methodCalled.OwningType.IsSystemType() && methodCalled.Signature.Length > 0: { string name = tracker.GetLastString(); if (name != null && methodIL.OwningMethod.OwningType is MetadataType mdType && ResolveType(name, mdType.Module, out TypeDesc type, out ModuleDesc referenceModule) && !factory.MetadataManager.IsReflectionBlocked(type)) { const string reason = "Type.GetType"; list = list ?? new DependencyList(); list.Add(factory.MaximallyConstructableType(type), reason); // Also add module metadata in case this reference was through a type forward if (factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) { list.Add(factory.ModuleMetadata(referenceModule), reason); } // Opportunistically remember the type so that it flows to Type.GetMethod if needed. tracker.TrackType(type); } } break; // Type.GetMethod(string...) case "GetMethod" when scanningReflection && methodCalled.OwningType.IsSystemType(): { string name = tracker.GetLastString(); TypeDesc type = tracker.GetLastType(); if (name != null && type != null) { HandleTypeGetMethod(ref list, factory, type, name, "Type.GetMethod"); } } break; // Type.GetProperty(string...) case "GetProperty" when scanningReflection && methodCalled.OwningType.IsSystemType(): { string name = tracker.GetLastString(); TypeDesc type = tracker.GetLastType(); if (name != null && type != null) { // Just do the easy thing and assume C# naming conventions HandleTypeGetMethod(ref list, factory, type, "get_" + name, "Type.GetProperty"); HandleTypeGetMethod(ref list, factory, type, "set_" + name, "Type.GetProperty"); } } break; case "SizeOf" when scanningInterop && IsMarshalSizeOf(methodCalled): { TypeDesc type = tracker.GetLastType(); if (IsTypeEligibleForMarshalSizeOfTracking(type)) { list = list ?? new DependencyList(); list.Add(factory.StructMarshallingData((DefType)type), "Marshal.SizeOf"); } } break; } }
public static void Scan(ref DependencyList list, NodeFactory factory, MethodIL methodIL, ScanModes modes) { ILReader reader = new ILReader(methodIL.GetILBytes()); Tracker tracker = new Tracker(methodIL); // The algorithm here is really primitive: we scan the IL forward in a single pass, remembering // the last type/string/token we saw. // // We then intrinsically recognize a couple methods that consume this information. // // This has obvious problems since we don't have exact knowledge of the parameters passed // (something being in front of a call doesn't mean it's a parameter to the call). But since // this is a heuristic, it's okay. We want this to be as fast as possible. // // The main purposes of this scanner is to make following patterns work: // // * Enum.GetValues(typeof(Foo)) - this is very common and we need to make sure Foo[] is compiled. // * Type.GetType("Foo, Bar").GetMethod("Blah") - framework uses this to work around layering problems. // * typeof(Foo<>).MakeGenericType(arg).GetMethod("Blah") - used in e.g. LINQ expressions implementation // * typeof(Foo<>).GetProperty("Blah") - used in e.g. LINQ expressions implementation // * Marshal.SizeOf(typeof(Foo)) - very common and we need to make sure interop data is generated while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldstr: tracker.TrackStringToken(reader.ReadILToken()); break; case ILOpcode.ldtoken: int token = reader.ReadILToken(); if (IsTypeEqualityTest(methodIL, reader, out ILReader newReader)) { reader = newReader; } else { tracker.TrackLdTokenToken(token); TypeDesc type = methodIL.GetObject(token) as TypeDesc; if (type != null && !type.IsCanonicalSubtype(CanonicalFormKind.Any)) { list = list ?? new DependencyList(); list.Add(factory.MaximallyConstructableType(type), "Unknown LDTOKEN use"); } } break; case ILOpcode.call: case ILOpcode.callvirt: var method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method != null) { HandleCall(ref list, factory, methodIL, method, ref tracker, modes); } break; default: reader.Skip(opcode); break; } } }
private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int argIndex, out int constant) { if ((flags[offset] & OpcodeFlags.BasicBlockStart) != 0) { constant = 0; return(false); } for (int currentOffset = offset - 1; currentOffset >= 0; currentOffset--) { if ((flags[currentOffset] & OpcodeFlags.InstructionStart) == 0) { continue; } ILReader reader = new ILReader(body, currentOffset); ILOpcode opcode = reader.ReadILOpcode(); if (opcode == ILOpcode.call || opcode == ILOpcode.callvirt) { MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken()); if (argIndex == 0) { BodySubstitution substitution = GetSubstitution(method); if (substitution != null && substitution.Value is int && (opcode != ILOpcode.callvirt || !method.IsVirtual)) { constant = (int)substitution.Value; return(true); } else { constant = 0; return(false); } } argIndex--; if (method.Signature.Length > 0 || !method.Signature.IsStatic) { // We don't know how to skip over the parameters break; } } else if (opcode == ILOpcode.ldsfld) { FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken()); if (argIndex == 0) { object substitution = GetSubstitution(field); if (substitution is int) { constant = (int)substitution; return(true); } else { constant = 0; return(false); } } argIndex--; } else if (opcode >= ILOpcode.ldc_i4_0 && opcode <= ILOpcode.ldc_i4_8) { if (argIndex == 0) { constant = opcode - ILOpcode.ldc_i4_0; return(true); } argIndex--; } else if (opcode == ILOpcode.ldc_i4) { if (argIndex == 0) { constant = (int)reader.ReadILUInt32(); return(true); } argIndex--; } else if (opcode == ILOpcode.ldc_i4_s) { if (argIndex == 0) { constant = (int)(sbyte)reader.ReadILByte(); return(true); } argIndex--; } else if ((opcode == ILOpcode.ldloc || opcode == ILOpcode.ldloc_s || (opcode >= ILOpcode.ldloc_0 && opcode <= ILOpcode.ldloc_3)) && ((flags[currentOffset] & OpcodeFlags.BasicBlockStart) == 0)) { // Paired stloc/ldloc that the C# compiler generates in debug code? int locIndex = opcode switch { ILOpcode.ldloc => reader.ReadILUInt16(), ILOpcode.ldloc_s => reader.ReadILByte(), _ => opcode - ILOpcode.ldloc_0, }; for (int potentialStlocOffset = currentOffset - 1; potentialStlocOffset >= 0; potentialStlocOffset--) { if ((flags[potentialStlocOffset] & OpcodeFlags.InstructionStart) == 0) { continue; } ILReader nestedReader = new ILReader(body, potentialStlocOffset); ILOpcode otherOpcode = nestedReader.ReadILOpcode(); if ((otherOpcode == ILOpcode.stloc || otherOpcode == ILOpcode.stloc_s || (otherOpcode >= ILOpcode.stloc_0 && otherOpcode <= ILOpcode.stloc_3)) && otherOpcode switch { ILOpcode.stloc => nestedReader.ReadILUInt16(), ILOpcode.stloc_s => nestedReader.ReadILByte(), _ => otherOpcode - ILOpcode.stloc_0, } == locIndex)
public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) { // This attempts to find all basic blocks that are unreachable after applying the substitutions. // // On a high level, we first find all the basic blocks and instruction boundaries in the IL stream. // This is tracked in a sidecar `flags` array that has flags for each byte of the IL stream. // // Once we have all the basic blocks and instruction boundaries, we do a marking phase to mark // the reachable blocks. We use substitutions to tell us what's unreachable. We consider conditional // branches "interesting" and whenever we see one, we seek backwards in the IL instruction stream // to find the instruction that feeds it. We make sure we don't cross the basic block boundary while // doing that. If the conditional instruction is fed by known values (either through the substitutions // or because it's an IL constant), we simulate the result of the comparison and only mark // the taken branch. We also mark any associated EH regions. // // The "seek backwards to find what feeds the comparison" only works for a couple known instructions // (load constant, call). It can't e.g. skip over arguments to the call. // // Last step is a sweep - we replace the tail of all unreachable blocks with "br $-2" // and nop out the rest. If the basic block is smaller than 2 bytes, we don't touch it. // We also eliminate any EH records that correspond to the stubbed out basic block. Debug.Assert(method.GetMethodILDefinition() == method); ILExceptionRegion[] ehRegions = method.GetExceptionRegions(); byte[] methodBytes = method.GetILBytes(); OpcodeFlags[] flags = new OpcodeFlags[methodBytes.Length]; // Offset 0 is the first basic block Stack <int> offsetsToVisit = new Stack <int>(); offsetsToVisit.Push(0); // Basic blocks also start around EH regions foreach (ILExceptionRegion ehRegion in ehRegions) { if (ehRegion.Kind == ILExceptionRegionKind.Filter) { offsetsToVisit.Push(ehRegion.FilterOffset); } offsetsToVisit.Push(ehRegion.HandlerOffset); } // Identify basic blocks and instruction boundaries while (offsetsToVisit.TryPop(out int offset)) { // If this was already visited, we're done if (flags[offset] != 0) { // Also mark as basic block start in case this was a target of a backwards branch. flags[offset] |= OpcodeFlags.BasicBlockStart; continue; } flags[offset] |= OpcodeFlags.BasicBlockStart; // Read until we reach the end of the basic block ILReader reader = new ILReader(methodBytes, offset); while (reader.HasNext) { offset = reader.Offset; flags[offset] |= OpcodeFlags.InstructionStart; ILOpcode opcode = reader.ReadILOpcode(); if (opcode >= ILOpcode.br_s && opcode <= ILOpcode.blt_un || opcode == ILOpcode.leave || opcode == ILOpcode.leave_s) { int destination = reader.ReadBranchDestination(opcode); offsetsToVisit.Push(destination); if (opcode != ILOpcode.leave && opcode != ILOpcode.leave_s && opcode != ILOpcode.br && opcode != ILOpcode.br_s) { // Branches not tested for above are conditional and the flow falls through. offsetsToVisit.Push(reader.Offset); } flags[offset] |= OpcodeFlags.EndBasicBlock; } else if (opcode == ILOpcode.ret || opcode == ILOpcode.endfilter || opcode == ILOpcode.endfinally || opcode == ILOpcode.throw_ || opcode == ILOpcode.rethrow || opcode == ILOpcode.jmp) { // Ends basic block. flags[offset] |= OpcodeFlags.EndBasicBlock; reader.Skip(opcode); } else if (opcode == ILOpcode.switch_) { uint count = reader.ReadILUInt32(); int jmpBase = reader.Offset + (int)(4 * count); for (uint i = 0; i < count; i++) { int destination = (int)reader.ReadILUInt32() + jmpBase; offsetsToVisit.Push(destination); } // We fall through to the next basic block. offsetsToVisit.Push(reader.Offset); flags[offset] |= OpcodeFlags.EndBasicBlock; } else { reader.Skip(opcode); } if ((flags[offset] & OpcodeFlags.EndBasicBlock) != 0) { if (reader.HasNext) { // If the bytes following this basic block are not reachable from anywhere, // the sweeping step would consider them to be part of the last instruction // of the current basic block because of how instruction boundaries are identified. // We wouldn't NOP them out if the current basic block is reachable. // // That's a problem for RyuJIT because RyuJIT looks at these bytes for... reasons. // // We can just do the same thing as RyuJIT and consider those a basic block. offsetsToVisit.Push(reader.Offset); } break; } } } // Mark all reachable basic blocks // // We also do another round of basic block marking to mark beginning of visible basic blocks // after dead branch elimination. This allows us to limit the number of potential small basic blocks // that are not interesting (because no code jumps to them anymore), but could prevent us from // finishing the process. Unreachable basic blocks smaller than 2 bytes abort the substitution // inlining process because we can't neutralize them (turn them into an infinite loop). offsetsToVisit.Push(0); while (offsetsToVisit.TryPop(out int offset)) { // Mark as a basic block visible after constant propagation. flags[offset] |= OpcodeFlags.VisibleBasicBlockStart; // If this was already marked, we're done. if ((flags[offset] & OpcodeFlags.Mark) != 0) { continue; } ILReader reader = new ILReader(methodBytes, offset); while (reader.HasNext) { offset = reader.Offset; flags[offset] |= OpcodeFlags.Mark; ILOpcode opcode = reader.ReadILOpcode(); // Mark any applicable EH blocks foreach (ILExceptionRegion ehRegion in ehRegions) { int delta = offset - ehRegion.TryOffset; if (delta >= 0 && delta < ehRegion.TryLength) { if (ehRegion.Kind == ILExceptionRegionKind.Filter) { offsetsToVisit.Push(ehRegion.FilterOffset); } offsetsToVisit.Push(ehRegion.HandlerOffset); // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. int handlerEnd = ehRegion.HandlerOffset + ehRegion.HandlerLength; if (handlerEnd < flags.Length) { flags[handlerEnd] |= OpcodeFlags.VisibleBasicBlockStart; } } } // All branches are relevant to basic block tracking if (opcode == ILOpcode.brfalse || opcode == ILOpcode.brfalse_s || opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s) { int destination = reader.ReadBranchDestination(opcode); if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int constant)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); offsetsToVisit.Push(reader.Offset); } else if ((constant == 0 && (opcode == ILOpcode.brfalse || opcode == ILOpcode.brfalse_s)) || (constant != 0 && (opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s))) { // Only the "branch taken" is live. // The fallthrough marks the beginning of a visible (but not live) basic block. offsetsToVisit.Push(destination); flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } else { // Only fallthrough is live. // The "brach taken" marks the beginning of a visible (but not live) basic block. flags[destination] |= OpcodeFlags.VisibleBasicBlockStart; offsetsToVisit.Push(reader.Offset); } } else if (opcode == ILOpcode.beq || opcode == ILOpcode.beq_s || opcode == ILOpcode.bne_un || opcode == ILOpcode.bne_un_s) { int destination = reader.ReadBranchDestination(opcode); if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int left) || !TryGetConstantArgument(method, methodBytes, flags, offset, 1, out int right)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); offsetsToVisit.Push(reader.Offset); } else if ((left == right && (opcode == ILOpcode.beq || opcode == ILOpcode.beq_s) || (left != right) && (opcode == ILOpcode.bne_un || opcode == ILOpcode.bne_un_s))) { // Only the "branch taken" is live. // The fallthrough marks the beginning of a visible (but not live) basic block. offsetsToVisit.Push(destination); flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } else { // Only fallthrough is live. // The "brach taken" marks the beginning of a visible (but not live) basic block. flags[destination] |= OpcodeFlags.VisibleBasicBlockStart; offsetsToVisit.Push(reader.Offset); } } else if (opcode >= ILOpcode.br_s && opcode <= ILOpcode.blt_un || opcode == ILOpcode.leave || opcode == ILOpcode.leave_s) { int destination = reader.ReadBranchDestination(opcode); offsetsToVisit.Push(destination); if (opcode != ILOpcode.leave && opcode != ILOpcode.leave_s && opcode != ILOpcode.br && opcode != ILOpcode.br_s) { // Branches not tested for above are conditional and the flow falls through. offsetsToVisit.Push(reader.Offset); } else { // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. if (reader.HasNext) { flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } } } else if (opcode == ILOpcode.switch_) { uint count = reader.ReadILUInt32(); int jmpBase = reader.Offset + (int)(4 * count); for (uint i = 0; i < count; i++) { int destination = (int)reader.ReadILUInt32() + jmpBase; offsetsToVisit.Push(destination); } offsetsToVisit.Push(reader.Offset); } else if (opcode == ILOpcode.ret || opcode == ILOpcode.endfilter || opcode == ILOpcode.endfinally || opcode == ILOpcode.throw_ || opcode == ILOpcode.rethrow || opcode == ILOpcode.jmp) { reader.Skip(opcode); // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. if (reader.HasNext) { flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } } else { reader.Skip(opcode); } if ((flags[offset] & OpcodeFlags.EndBasicBlock) != 0) { break; } } } // Now sweep unreachable basic blocks by replacing them with nops bool hasUnmarkedIntructions = false; foreach (var flag in flags) { if ((flag & OpcodeFlags.InstructionStart) != 0 && (flag & OpcodeFlags.Mark) == 0) { hasUnmarkedIntructions = true; } } if (!hasUnmarkedIntructions) { return(method); } byte[] newBody = (byte[])methodBytes.Clone(); int position = 0; while (position < newBody.Length) { Debug.Assert((flags[position] & OpcodeFlags.InstructionStart) != 0); Debug.Assert((flags[position] & OpcodeFlags.VisibleBasicBlockStart) != 0); bool erase = (flags[position] & OpcodeFlags.Mark) == 0; int basicBlockStart = position; do { if (erase) { newBody[position] = (byte)ILOpCode.Nop; } position++; } while (position < newBody.Length && (flags[position] & OpcodeFlags.VisibleBasicBlockStart) == 0); // If we had to nop out this basic block, we need to neutralize it by appending // an infinite loop ("br $-2"). // We append instead of prepend because RyuJIT's importer has trouble with junk unreachable bytes. if (erase) { if (position - basicBlockStart < 2) { // We cannot neutralize the basic block, so better leave the method alone. // The control would fall through to the next basic block. return(method); } newBody[position - 2] = (byte)ILOpCode.Br_s; newBody[position - 1] = unchecked ((byte)-2); } } // EH regions with unmarked handlers belong to unmarked basic blocks // Need to eliminate them because they're not usable. ArrayBuilder <ILExceptionRegion> newEHRegions = new ArrayBuilder <ILExceptionRegion>(); foreach (ILExceptionRegion ehRegion in ehRegions) { if ((flags[ehRegion.HandlerOffset] & OpcodeFlags.Mark) != 0) { newEHRegions.Add(ehRegion); } } // Existing debug information might not match new instruction boundaries (plus there's little point // in generating debug information for NOPs) - generate new debug information by filtering // out the sequence points associated with nopped out instructions. MethodDebugInformation debugInfo = method.GetDebugInfo(); IEnumerable <ILSequencePoint> oldSequencePoints = debugInfo?.GetSequencePoints(); if (oldSequencePoints != null) { ArrayBuilder <ILSequencePoint> sequencePoints = new ArrayBuilder <ILSequencePoint>(); foreach (var sequencePoint in oldSequencePoints) { if (sequencePoint.Offset < flags.Length && (flags[sequencePoint.Offset] & OpcodeFlags.Mark) != 0) { sequencePoints.Add(sequencePoint); } } debugInfo = new SubstitutedDebugInformation(debugInfo, sequencePoints.ToArray()); } return(new SubstitutedMethodIL(method, newBody, newEHRegions.ToArray(), debugInfo)); }
private MetadataLoadedInfo LoadMetadata() { HashSet <ModuleDesc> metadataModules = new HashSet <ModuleDesc>(); MetadataType typeWithMetadataMappings = (MetadataType)_metadataDescribingModule.GetTypeByCustomAttributeTypeName(MetadataMappingTypeName); MethodDesc fullMetadataMethod = typeWithMetadataMappings.GetMethod("Metadata", null); MethodDesc weakMetadataMethod = typeWithMetadataMappings.GetMethod("WeakMetadata", null); MethodDesc requiredGenericTypesMethod = typeWithMetadataMappings.GetMethod("RequiredGenericTypes", null); MethodDesc requiredGenericMethodsMethod = typeWithMetadataMappings.GetMethod("RequiredGenericMethods", null); MethodDesc requiredGenericFieldsMethod = typeWithMetadataMappings.GetMethod("RequiredGenericFields", null); MethodDesc requiredTemplatesMethod = typeWithMetadataMappings.GetMethod("CompilerDeterminedInstantiations", null); ILProvider ilProvider = new ILProvider(null); MetadataLoadedInfo result = new MetadataLoadedInfo(); if (fullMetadataMethod != null) { MethodIL fullMethodIL = ilProvider.GetMethodIL(fullMetadataMethod); ReadMetadataMethod(fullMethodIL, result.AllTypeMappings, result.MethodMappings, result.FieldMappings, metadataModules); foreach (var mapping in result.AllTypeMappings) { result.TypesWithStrongMetadataMappings.Add(mapping.Key); } } if (weakMetadataMethod != null) { MethodIL weakMethodIL = ilProvider.GetMethodIL(weakMetadataMethod); Dictionary <MethodDesc, int> weakMethodMappings = new Dictionary <MethodDesc, int>(); Dictionary <FieldDesc, int> weakFieldMappings = new Dictionary <FieldDesc, int>(); ReadMetadataMethod(weakMethodIL, result.AllTypeMappings, weakMethodMappings, weakFieldMappings, metadataModules); if ((weakMethodMappings.Count > 0) || (weakFieldMappings.Count > 0)) { // the format does not permit weak field/method mappings throw new BadImageFormatException(); } } if (requiredGenericTypesMethod != null) { foreach (var type in ReadRequiredGenericsEntities(ilProvider.GetMethodIL(requiredGenericTypesMethod))) { Debug.Assert(type is DefType); result.RequiredGenericTypes.Add((TypeDesc)type); } } if (requiredGenericMethodsMethod != null) { foreach (var method in ReadRequiredGenericsEntities(ilProvider.GetMethodIL(requiredGenericMethodsMethod))) { result.RequiredGenericMethods.Add((MethodDesc)method); } } if (requiredGenericFieldsMethod != null) { foreach (var field in ReadRequiredGenericsEntities(ilProvider.GetMethodIL(requiredGenericFieldsMethod))) { result.RequiredGenericFields.Add((FieldDesc)field); } } if (requiredTemplatesMethod != null) { ReadRequiredTemplates(ilProvider.GetMethodIL(requiredTemplatesMethod), result.RequiredTemplateTypes, result.RequiredTemplateMethods, result.RequiredTemplateFields); } result.MetadataModules = ImmutableArray.CreateRange(metadataModules); ImmutableArray <ModuleDesc> .Builder externalMetadataModulesBuilder = ImmutableArray.CreateBuilder <ModuleDesc>(); ImmutableArray <ModuleDesc> .Builder localMetadataModulesBuilder = ImmutableArray.CreateBuilder <ModuleDesc>(); foreach (ModuleDesc module in result.MetadataModules) { if (!_compilationModules.Contains(module)) { externalMetadataModulesBuilder.Add(module); } else { localMetadataModulesBuilder.Add(module); } } result.ExternalMetadataModules = externalMetadataModulesBuilder.ToImmutable(); result.LocalMetadataModules = localMetadataModulesBuilder.ToImmutable(); foreach (var pair in result.MethodMappings) { MethodDesc reflectableMethod = pair.Key; if (reflectableMethod.HasInstantiation) { continue; } if (reflectableMethod.OwningType.HasInstantiation) { continue; } MethodDesc typicalDynamicInvokeStub; if (!_dynamicInvokeStubs.Value.TryGetValue(reflectableMethod, out typicalDynamicInvokeStub)) { continue; } MethodDesc instantiatiatedDynamicInvokeStub = InstantiateCanonicalDynamicInvokeMethodForMethod(typicalDynamicInvokeStub, reflectableMethod); result.DynamicInvokeCompiledMethods.Add(instantiatiatedDynamicInvokeStub); } foreach (var reflectableMethod in result.RequiredGenericMethods) { MethodDesc typicalDynamicInvokeStub; if (!_dynamicInvokeStubs.Value.TryGetValue(reflectableMethod.GetTypicalMethodDefinition(), out typicalDynamicInvokeStub)) { continue; } MethodDesc instantiatiatedDynamicInvokeStub = InstantiateCanonicalDynamicInvokeMethodForMethod(typicalDynamicInvokeStub, reflectableMethod); result.DynamicInvokeCompiledMethods.Add(instantiatiatedDynamicInvokeStub); } return(result); }
private IEnumerable <VerificationResult> VerifyMethod(EcmaModule module, MethodIL methodIL, MethodDefinitionHandle methodHandle) { var builder = new ArrayBuilder <VerificationResult>(); MethodDesc method = methodIL.OwningMethod; try { var importer = new ILImporter(method, methodIL); importer.ReportVerificationError = (args) => { var codeResource = _stringResourceManager.Value.GetString(args.Code.ToString(), CultureInfo.InvariantCulture); builder.Add(new VerificationResult() { Method = methodHandle, Error = args, Message = string.IsNullOrEmpty(codeResource) ? args.Code.ToString() : codeResource }); }; importer.Verify(); } catch (VerificationException) { // a result was reported already (before aborting) } catch (BadImageFormatException) { builder.Add(new VerificationResult() { Method = methodHandle, Message = "Unable to resolve token" }); } catch (NotImplementedException e) { reportException(e); } catch (InvalidProgramException e) { reportException(e); } catch (PlatformNotSupportedException e) { reportException(e); } catch (VerifierException e) { reportException(e); } catch (TypeSystemException e) { reportException(e); } return(builder.ToArray()); void reportException(Exception e) { builder.Add(new VerificationResult() { Method = methodHandle, Message = e.Message }); } }