// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.Emit; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Test.Utilities { public class HostedRuntimeEnvironment : IDisposable { private static readonly Dictionary allModuleNames = new Dictionary(); private bool disposed; private AppDomain domain; private RuntimeAssemblyManager assemblyManager; private ImmutableArray lazyDiagnostics; private ModuleData mainModule; private ImmutableArray mainModulePdb; private List allModuleData; private readonly CompilationTestData testData = new CompilationTestData(); private readonly IEnumerable additionalDependencies; private bool executeRequested; private bool peVerifyRequested; public HostedRuntimeEnvironment(IEnumerable additionalDependencies = null) { this.additionalDependencies = additionalDependencies; } private void CreateAssemblyManager(IEnumerable compilationDependencies, ModuleData mainModule) { var allModules = compilationDependencies; if (additionalDependencies != null) { allModules = allModules.Concat(additionalDependencies); } // We need to add the main module so that it gets checked against already loaded assembly names. // If an assembly is loaded directly via PEVerify(image) another assembly of the same full name // can't be loaded as a dependency (via Assembly.ReflectionOnlyLoad) in the same domain. if (mainModule != null) { allModules = allModules.Concat(new[] { mainModule }); } allModules = allModules.ToArray(); string conflict = DetectNameCollision(allModules); if (conflict != null) { Type appDomainProxyType = typeof(RuntimeAssemblyManager); Assembly thisAssembly = appDomainProxyType.Assembly; AppDomain appDomain = null; RuntimeAssemblyManager manager; try { appDomain = AppDomain.CreateDomain("HostedRuntimeEnvironment", null, AppDomain.CurrentDomain.BaseDirectory, null, false); manager = (RuntimeAssemblyManager)appDomain.CreateInstanceAndUnwrap(thisAssembly.FullName, appDomainProxyType.FullName); } catch { if (appDomain != null) { AppDomain.Unload(appDomain); } throw; } this.domain = appDomain; this.assemblyManager = manager; } else { this.assemblyManager = new RuntimeAssemblyManager(); } this.assemblyManager.AddModuleData(allModules); if (mainModule != null) { this.assemblyManager.AddMainModuleMvid(mainModule.Mvid); } } // Determines if any of the given dependencies has the same name as already loaded assembly with different content. private static string DetectNameCollision(IEnumerable modules) { lock (allModuleNames) { foreach (var module in modules) { Guid mvid; if (allModuleNames.TryGetValue(module.FullName, out mvid)) { if (mvid != module.Mvid) { return module.FullName; } } } // only add new modules if there is no collision: foreach (var module in modules) { allModuleNames[module.FullName] = module.Mvid; } } return null; } private static void EmitDependentCompilation(Compilation compilation, List dependencies, DiagnosticBag diagnostics, bool usePdbForDebugging = false) { ImmutableArray assembly, pdb; if (EmitCompilation(compilation, null, dependencies, diagnostics, null, out assembly, out pdb)) { dependencies.Add(new ModuleData(compilation.Assembly.Identity, OutputKind.DynamicallyLinkedLibrary, assembly, pdb: usePdbForDebugging ? pdb : default(ImmutableArray), inMemoryModule: true)); } } internal static void EmitReferences(Compilation compilation, List dependencies, DiagnosticBag diagnostics) { if (compilation.PreviousSubmission != null) { EmitDependentCompilation(compilation.PreviousSubmission, dependencies, diagnostics); } foreach (MetadataReference r in compilation.References) { CompilationReference compilationRef; PortableExecutableReference peRef; if ((compilationRef = r as CompilationReference) != null) { EmitDependentCompilation(compilationRef.Compilation, dependencies, diagnostics); } else if ((peRef = r as PortableExecutableReference) != null) { var metadata = peRef.GetMetadata(); bool isManifestModule = peRef.Properties.Kind == MetadataImageKind.Assembly; foreach (var module in EnumerateModules(metadata)) { ImmutableArray bytes = module.Module.PEReaderOpt.GetEntireImage().GetContent(); if (isManifestModule) { dependencies.Add(new ModuleData(((AssemblyMetadata)metadata).GetAssembly().Identity, OutputKind.DynamicallyLinkedLibrary, bytes, pdb: default(ImmutableArray), inMemoryModule: true)); } else { dependencies.Add(new ModuleData(module.Name, bytes, pdb: default(ImmutableArray), inMemoryModule: true)); } isManifestModule = false; } } else { throw new InvalidOperationException(); } } } private static IEnumerable EnumerateModules(Metadata metadata) { return (metadata.Kind == MetadataImageKind.Assembly) ? ((AssemblyMetadata)metadata).GetModules().AsEnumerable() : SpecializedCollections.SingletonEnumerable((ModuleMetadata)metadata); } internal static bool EmitCompilation( Compilation compilation, IEnumerable manifestResources, List dependencies, DiagnosticBag diagnostics, CompilationTestData testData, out ImmutableArray assembly, out ImmutableArray pdb ) { assembly = default(ImmutableArray); pdb = default(ImmutableArray); EmitReferences(compilation, dependencies, diagnostics); using (var executableStream = new MemoryStream()) { MemoryStream pdbStream = new MemoryStream(); EmitResult result; try { result = compilation.Emit( executableStream, pdbStream: pdbStream, xmlDocumentationStream: null, win32Resources: null, manifestResources: manifestResources, options: EmitOptions.Default, testData: testData, cancellationToken: default(CancellationToken)); } finally { if (pdbStream != null) { pdb = pdbStream.ToImmutable(); pdbStream.Dispose(); } } diagnostics.AddRange(result.Diagnostics); assembly = executableStream.ToImmutable(); return result.Success; } } public void Emit( Compilation mainCompilation, IEnumerable manifestResources, bool usePdbForDebugging = false) { var diagnostics = DiagnosticBag.GetInstance(); var dependencies = new List(); testData.Methods.Clear(); ImmutableArray mainImage, mainPdb; bool succeeded = EmitCompilation(mainCompilation, manifestResources, dependencies, diagnostics, testData, out mainImage, out mainPdb); this.lazyDiagnostics = diagnostics.ToReadOnlyAndFree(); if (succeeded) { this.mainModule = new ModuleData(mainCompilation.Assembly.Identity, mainCompilation.Options.OutputKind, mainImage, pdb: usePdbForDebugging ? mainPdb : default(ImmutableArray), inMemoryModule: true); this.mainModulePdb = mainPdb; this.allModuleData = dependencies; this.allModuleData.Insert(0, mainModule); CreateAssemblyManager(dependencies, mainModule); } else { string dumpDir; RuntimeAssemblyManager.DumpAssemblyData(dependencies, out dumpDir); // This method MUST throw if compilation did not succeed. If compilation succeeded and there were errors, that is bad. // Please see KevinH if you intend to change this behavior as many tests expect the Exception to indicate failure. throw new EmitException(this.lazyDiagnostics, dumpDir); // ToArray for serializability. } } public int Execute(string moduleName, int expectedOutputLength, out string processOutput) { executeRequested = true; try { return this.assemblyManager.Execute(moduleName, expectedOutputLength, out processOutput); } catch (TargetInvocationException tie) { string dumpDir; assemblyManager.DumpAssemblyData(out dumpDir); throw new ExecutionException(tie.InnerException, dumpDir); } } public int Execute(string moduleName, string expectedOutput) { string actualOutput; int exitCode = Execute(moduleName, expectedOutput.Length, out actualOutput); if (expectedOutput.Trim() != actualOutput.Trim()) { string dumpDir; assemblyManager.DumpAssemblyData(out dumpDir); throw new ExecutionException(expectedOutput, actualOutput, dumpDir); } return exitCode; } internal ImmutableArray GetDiagnostics() { if (this.lazyDiagnostics.IsDefault) { throw new InvalidOperationException("You must call Emit before calling GetBuffer."); } return this.lazyDiagnostics; } public ImmutableArray GetMainImage() { if (mainModule == null) { throw new InvalidOperationException("You must call Emit before calling GetMainImage."); } return mainModule.Image; } public ImmutableArray GetMainPdb() { if (mainModule == null) { throw new InvalidOperationException("You must call Emit before calling GetMainPdb."); } return mainModulePdb; } internal IList GetAllModuleData() { if (allModuleData == null) { throw new InvalidOperationException("You must call Emit before calling GetAllModuleData."); } return allModuleData; } public void PeVerify() { peVerifyRequested = true; if (assemblyManager == null) { throw new InvalidOperationException("You must call Emit before calling PeVerify."); } this.assemblyManager.PeVerifyModules(new[] { this.mainModule.FullName }); } internal string[] PeVerifyModules(string[] modulesToVerify, bool throwOnError = true) { peVerifyRequested = true; if (assemblyManager == null) { CreateAssemblyManager(new ModuleData[0], null); } return assemblyManager.PeVerifyModules(modulesToVerify, throwOnError); } internal SortedSet GetMemberSignaturesFromMetadata(string fullyQualifiedTypeName, string memberName) { if (assemblyManager == null) { throw new InvalidOperationException("You must call Emit before calling GetMemberSignaturesFromMetadata."); } return assemblyManager.GetMemberSignaturesFromMetadata(fullyQualifiedTypeName, memberName); } // A workaround for known bug DevDiv 369979 - don't unload the AppDomain if we may have loaded a module private bool IsSafeToUnloadDomain { get { if (assemblyManager == null) { return true; } return !(assemblyManager.ContainsNetModules() && (peVerifyRequested || executeRequested)); } } void IDisposable.Dispose() { if (disposed) { return; } if (domain == null) { if (assemblyManager != null) { assemblyManager.Dispose(); assemblyManager = null; } } else { Debug.Assert(assemblyManager != null); assemblyManager.Dispose(); if (IsSafeToUnloadDomain) { AppDomain.Unload(domain); } assemblyManager = null; domain = null; } disposed = true; } internal CompilationTestData GetCompilationTestData() { if (testData.Module == null) { throw new InvalidOperationException("You must call Emit before calling GetCompilationTestData."); } return testData; } } internal sealed class RuntimeAssemblyManager : MarshalByRefObject, IDisposable { // Per-domain cache, contains all assemblies loaded to this app domain since the first manager was created. // The key is the manifest module MVID, which is unique for each distinct assembly. private static readonly ConcurrentDictionary domainAssemblyCache; private static readonly ConcurrentDictionary domainReflectionOnlyAssemblyCache; // Modules managed by this manager. All such modules must have unique simple name. private readonly Dictionary modules; // Assemblies loaded by this manager. private readonly HashSet loadedAssemblies; private readonly List mainMvids; private bool containsNetModules; static RuntimeAssemblyManager() { domainAssemblyCache = new ConcurrentDictionary(); domainReflectionOnlyAssemblyCache = new ConcurrentDictionary(); AppDomain.CurrentDomain.AssemblyLoad += DomainAssemblyLoad; } public RuntimeAssemblyManager() { modules = new Dictionary(StringComparer.OrdinalIgnoreCase); loadedAssemblies = new HashSet(); mainMvids = new List(); AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoad; CLRHelpers.ReflectionOnlyAssemblyResolve += ReflectionOnlyAssemblyResolve; } public void Dispose() { // clean up our handlers, so that they don't accumulate AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolve; AppDomain.CurrentDomain.AssemblyLoad -= AssemblyLoad; CLRHelpers.ReflectionOnlyAssemblyResolve -= ReflectionOnlyAssemblyResolve; foreach (var assembly in loadedAssemblies) { assembly.ModuleResolve -= ModuleResolve; } //EDMAURER Some RuntimeAssemblyManagers are created via reflection in an AppDomain of our creation. //Sometimes those AppDomains are not released. I don't fully understand how that appdomain roots //a RuntimeAssemblyManager, but according to heap dumps, it does. Even though the appdomain is not //unloaded, its RuntimeAssemblyManager is explicitly disposed. So make sure that it cleans up this //memory hog - the modules dictionary. modules.Clear(); } /// /// Adds given MVID into a list of module MVIDs that are considered owned by this manager. /// public void AddMainModuleMvid(Guid mvid) { mainMvids.Add(mvid); } /// /// True if given assembly is owned by this manager. /// private bool IsOwned(Assembly assembly) { return mainMvids.Count == 0 || mainMvids.Contains(assembly.ManifestModule.ModuleVersionId) || loadedAssemblies.Contains(assembly); } internal bool ContainsNetModules() { return containsNetModules; } public override object InitializeLifetimeService() { return null; } public void AddModuleData(IEnumerable modules) { foreach (var module in modules) { if (!this.modules.ContainsKey(module.FullName)) { if (module.Kind == OutputKind.NetModule) { containsNetModules = true; } this.modules.Add(module.FullName, module); } } } private ImmutableArray GetModuleBytesByName(string moduleName) { ModuleData data; if (!modules.TryGetValue(moduleName, out data)) { throw new KeyNotFoundException(String.Format("Could not find image for module '{0}'.", moduleName)); } return data.Image; } private static void DomainAssemblyLoad(object sender, AssemblyLoadEventArgs args) { // We need to add loaded assemblies to the cache in order to avoid loading them twice. // This is not just optimization. CLR isn't able to load the same assembly from multiple "locations". // Location for byte[] assemblies is the location of the assembly that invokes Assembly.Load. // PE verifier invokes load directly for the assembly being verified. If this assembly is also a dependency // of another assembly we verify our AssemblyResolve is invoked. If we didn't reuse the assembly already loaded // by PE verifier we would get an error from Assembly.Load. var assembly = args.LoadedAssembly; var cache = assembly.ReflectionOnly ? domainReflectionOnlyAssemblyCache : domainAssemblyCache; cache.TryAdd(assembly.ManifestModule.ModuleVersionId, assembly); } private void AssemblyLoad(object sender, AssemblyLoadEventArgs args) { var assembly = args.LoadedAssembly; // ModuleResolve needs to be hooked up for the main assembly once its loaded. // We won't get an AssemblyResolve event for the main assembly so we need to do it here. if (this.mainMvids.Contains(assembly.ManifestModule.ModuleVersionId) && loadedAssemblies.Add(assembly)) { assembly.ModuleResolve += ModuleResolve; } } private Assembly AssemblyResolve(object sender, ResolveEventArgs args) { return AssemblyResolve(args, reflectionOnly: false); } private Assembly ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args) { return AssemblyResolve(args, reflectionOnly: true); } private Assembly AssemblyResolve(ResolveEventArgs args, bool reflectionOnly) { // only respond to requests for dependencies of assemblies owned by this manager: if (IsOwned(args.RequestingAssembly)) { return GetAssembly(args.Name, reflectionOnly); } return null; } /// /// Loads given array of bytes as an assembly image using or . /// internal static Assembly LoadAsAssembly(ImmutableArray rawAssembly, bool reflectionOnly = false) { Debug.Assert(!rawAssembly.IsDefault); byte[] bytes = rawAssembly.ToArray(); if (reflectionOnly) { return System.Reflection.Assembly.ReflectionOnlyLoad(bytes); } else { return System.Reflection.Assembly.Load(bytes); } } internal Assembly GetAssembly(string fullName, bool reflectionOnly) { ModuleData data; if (!modules.TryGetValue(fullName, out data)) { return null; } ConcurrentDictionary cache = reflectionOnly ? domainReflectionOnlyAssemblyCache : domainAssemblyCache; var assembly = cache.GetOrAdd(data.Mvid, _ => LoadAsAssembly(data.Image, reflectionOnly)); assembly.ModuleResolve += ModuleResolve; loadedAssemblies.Add(assembly); return assembly; } private Module ModuleResolve(object sender, ResolveEventArgs args) { var assembly = args.RequestingAssembly; var rawModule = GetModuleBytesByName(args.Name); Debug.Assert(assembly != null); Debug.Assert(!rawModule.IsDefault); return assembly.LoadModule(args.Name, rawModule.ToArray()); } internal SortedSet GetMemberSignaturesFromMetadata(string fullyQualifiedTypeName, string memberName) { var signatures = new SortedSet(); foreach (var module in modules) // Check inside each assembly in the compilation { foreach (var signature in MetadataSignatureHelper.GetMemberSignatures(GetAssembly(module.Key, true), fullyQualifiedTypeName, memberName)) { signatures.Add(signature); } } return signatures; } internal SortedSet GetFullyQualifiedTypeNames(string assemblyName) { var typeNames = new SortedSet(); Assembly assembly = GetAssembly(assemblyName, true); foreach (var typ in assembly.GetTypes()) typeNames.Add(typ.FullName); return typeNames; } public int Execute(string moduleName, int expectedOutputLength, out string output) { ImmutableArray bytes = GetModuleBytesByName(moduleName); Assembly assembly = LoadAsAssembly(bytes); MethodInfo entryPoint = assembly.EntryPoint; Debug.Assert(entryPoint != null, "Attempting to execute an assembly that has no entrypoint; is your test trying to execute a DLL?"); object result = null; string stdOut, stdErr; ConsoleOutput.Capture(() => { result = entryPoint.Invoke(null, new object[entryPoint.GetParameters().Length]); }, expectedOutputLength, out stdOut, out stdErr); output = stdOut + stdErr; return result is int ? (int)result : 0; } public string DumpAssemblyData(out string dumpDirectory) { return DumpAssemblyData(modules.Values, out dumpDirectory); } public static string DumpAssemblyData(IEnumerable modules, out string dumpDirectory) { dumpDirectory = null; StringBuilder sb = new StringBuilder(); foreach (var module in modules) { if (module.InMemoryModule) { if (dumpDirectory == null) { dumpDirectory = Path.Combine(Path.GetTempPath(), "RoslynTestFailureDump", Guid.NewGuid().ToString()); Directory.CreateDirectory(dumpDirectory); } string fileName; if (module.Kind == OutputKind.NetModule) { fileName = module.FullName; } else { AssemblyIdentity identity; AssemblyIdentity.TryParseDisplayName(module.FullName, out identity); fileName = identity.Name; } string pePath = Path.Combine(dumpDirectory, fileName + module.Kind.GetDefaultExtension()); string pdbPath = (module.Pdb != null) ? pdbPath = Path.Combine(dumpDirectory, fileName + ".pdb") : null; try { module.Image.WriteToFile(pePath); if (pdbPath != null) { module.Pdb.WriteToFile(pdbPath); } } catch (IOException) { pePath = ""; if (pdbPath != null) { pdbPath = ""; } } sb.Append("PE(" + module.Kind + "): "); sb.AppendLine(pePath); if (pdbPath != null) { sb.Append("PDB: "); sb.AppendLine(pdbPath); } } } return sb.ToString(); } public string[] PeVerifyModules(string[] modulesToVerify, bool throwOnError = true) { // For Windows RT (ARM) THE CLRHelper.Peverify appears to not work and will exclude this // for ARM testing at present. StringBuilder errors = new StringBuilder(); List allOutput = new List(); #if !(ARM) foreach (var name in modulesToVerify) { var module = modules[name]; string[] output = CLRHelpers.PeVerify(module.Image); if (output.Length > 0) { if (modulesToVerify.Length > 1) { errors.AppendLine(); errors.AppendLine("<<" + name + ">>"); errors.AppendLine(); } foreach (var error in output) { errors.AppendLine(error); } } if (!throwOnError) { allOutput.AddRange(output); } } if (throwOnError && errors.Length > 0) { string dumpDir; DumpAssemblyData(this.modules.Values, out dumpDir); throw new PeVerifyException(errors.ToString(), dumpDir); } #endif return allOutput.ToArray(); } } public static class ModuleExtension { public static readonly string EXE = ".exe"; public static readonly string DLL = ".dll"; public static readonly string NETMODULE = ".netmodule"; } [Serializable, DebuggerDisplay("{GetDebuggerDisplay()}")] public sealed class ModuleData : ISerializable { // Simple assembly name ("foo") or module name ("bar.netmodule"). public readonly string FullName; public readonly OutputKind Kind; public readonly ImmutableArray Image; public readonly ImmutableArray Pdb; public readonly bool InMemoryModule; private Guid? mvid; public ModuleData(string netModuleName, ImmutableArray image, ImmutableArray pdb, bool inMemoryModule) { this.FullName = netModuleName; this.Kind = OutputKind.NetModule; this.Image = image; this.Pdb = pdb; this.InMemoryModule = inMemoryModule; } public ModuleData(AssemblyIdentity identity, OutputKind kind, ImmutableArray image, ImmutableArray pdb, bool inMemoryModule) { this.FullName = identity.GetDisplayName(); this.Kind = kind; this.Image = image; this.Pdb = pdb; this.InMemoryModule = inMemoryModule; } public Guid Mvid { get { if (mvid == null) { using (var metadata = ModuleMetadata.CreateFromImage(Image)) { mvid = metadata.GetModuleVersionId(); } } return mvid.Value; } } private string GetDebuggerDisplay() { return FullName + " {" + Mvid + "}"; } public void GetObjectData(SerializationInfo info, StreamingContext context) { //public readonly string FullName; info.AddValue("FullName", this.FullName); //public readonly OutputKind Kind; info.AddValue("kind", (int)this.Kind); //public readonly ImmutableArray Image; info.AddByteArray("Image", this.Image); //public readonly ImmutableArray PDB; info.AddByteArray("PDB", this.Pdb); //public readonly bool InMemoryModule; info.AddValue("InMemoryModule", this.InMemoryModule); //private Guid? mvid; info.AddValue("mvid", this.mvid, typeof(Guid?)); } private ModuleData(SerializationInfo info, StreamingContext context) { //public readonly string FullName; this.FullName = info.GetString("FullName"); //public readonly OutputKind Kind; this.Kind = (OutputKind)info.GetInt32("kind"); //public readonly ImmutableArray Image; this.Image = info.GetByteArray("Image"); //public readonly ImmutableArray PDB; this.Pdb = info.GetByteArray("PDB"); //public readonly bool InMemoryModule; this.InMemoryModule = info.GetBoolean("InMemoryModule"); //private Guid? mvid; this.mvid = (Guid?)info.GetValue("mvid", typeof(Guid?)); } } [Serializable] public class EmitException : Exception { public IEnumerable Diagnostics { get; private set; } protected EmitException(SerializationInfo info, StreamingContext context) : base(info, context) { } public EmitException(IEnumerable diagnostics, string directory) : base(GetMessageFromResult(diagnostics, directory)) { this.Diagnostics = diagnostics; } private static string GetMessageFromResult(IEnumerable diagnostics, string directory) { StringBuilder sb = new StringBuilder(); sb.AppendLine("Emit Failed, binaries saved to: "); sb.AppendLine(directory); foreach (var d in diagnostics) { sb.AppendLine(d.ToString()); } return sb.ToString(); } } [Serializable] public class PeVerifyException : Exception { protected PeVerifyException(SerializationInfo info, StreamingContext context) : base(info, context) { } public PeVerifyException(string output, string exePath) : base(GetMessageFromResult(output, exePath)) { } private static string GetMessageFromResult(string output, string exePath) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append("PeVerify failed for assembly '"); sb.Append(exePath); sb.AppendLine("':"); sb.AppendLine(output); return sb.ToString(); } } public class ExecutionException : Exception { public ExecutionException(string expectedOutput, string actualOutput, string exePath) : base(GetMessageFromResult(expectedOutput, actualOutput, exePath)) { } public ExecutionException(Exception innerException, string exePath) : base(GetMessageFromException(innerException, exePath), innerException) { } private static string GetMessageFromException(Exception executionException, string exePath) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append("Execution failed for assembly '"); sb.Append(exePath); sb.AppendLine("'."); sb.Append("Exception: " + executionException); return sb.ToString(); } private static string GetMessageFromResult(string expectedOutput, string actualOutput, string exePath) { StringBuilder sb = new StringBuilder(); sb.AppendLine(); sb.Append("Execution failed for assembly '"); sb.Append(exePath); sb.AppendLine("'."); if (expectedOutput != null) { sb.Append("Expected: "); sb.AppendLine(expectedOutput); sb.Append("Actual: "); sb.AppendLine(actualOutput); } else { sb.Append("Output: "); sb.AppendLine(actualOutput); } return sb.ToString(); } } }