From 6056cb3c7dfa476032efe032831273f96d13537e Mon Sep 17 00:00:00 2001 From: Jose Perez Rodriguez Date: Tue, 23 Jun 2020 09:21:50 -0700 Subject: [PATCH 1/3] Add support for shared source in linker test code --- eng/testing/linker/trimmingTests.targets | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/eng/testing/linker/trimmingTests.targets b/eng/testing/linker/trimmingTests.targets index 8446128ce5b303..8fdc0f9b82816a 100644 --- a/eng/testing/linker/trimmingTests.targets +++ b/eng/testing/linker/trimmingTests.targets @@ -57,6 +57,10 @@ <_projectSourceFile>%(TestConsoleApps.ProjectCompileItems) + + <_additionalProjectSourceFiles Include="%(TestConsoleApps.AdditionalSourceFiles)" /> + + + From 6fd123036459268d1dec039cb20bf721c5188e41 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Fri, 19 Jun 2020 17:42:51 -0700 Subject: [PATCH 2/3] Clean up DynamicDependencyAttribute usages --- .../System/ComponentModel/TypeDescriptor.cs | 3 +- .../src/System/Data/SQLTypes/SqlXml.cs | 1 - .../DiagnosticSourceEventSource.cs | 11 +- .../EventSourcePropertyFetchTest.cs | 200 +++++++++++++++ ...ostics.DiagnosticSource.TrimmingTests.proj | 5 + .../Interpreter/CallInstruction.Generated.cs | 14 +- .../tests/TrimmingTests/GetHelperTypeTests.cs | 36 +++ ...System.Linq.Expressions.TrimmingTests.proj | 6 + .../System/Net/Windows/CookieExtensions.cs | 4 - .../CookieExtensionsTest.Clone.cs | 33 +++ .../CookieExtensionsTest.Helper.cs | 239 ++++++++++++++++++ .../CookieExtensionsTest.InternalAdd.cs | 39 +++ .../CookieExtensionsTest.ToServerString.cs | 52 ++++ ...System.Net.HttpListener.TrimmingTests.proj | 18 ++ 14 files changed, 639 insertions(+), 22 deletions(-) create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/EventSourcePropertyFetchTest.cs create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.TrimmingTests.proj create mode 100644 src/libraries/System.Linq.Expressions/tests/TrimmingTests/GetHelperTypeTests.cs create mode 100644 src/libraries/System.Linq.Expressions/tests/TrimmingTests/System.Linq.Expressions.TrimmingTests.proj create mode 100644 src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.Clone.cs create mode 100644 src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.Helper.cs create mode 100644 src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.InternalAdd.cs create mode 100644 src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.ToServerString.cs create mode 100644 src/libraries/System.Net.HttpListener/tests/TrimmingTests/System.Net.HttpListener.TrimmingTests.proj diff --git a/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/TypeDescriptor.cs b/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/TypeDescriptor.cs index ae6c63b09c38a3..a7c831032a62e2 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/TypeDescriptor.cs +++ b/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/TypeDescriptor.cs @@ -85,8 +85,7 @@ private TypeDescriptor() [EditorBrowsable(EditorBrowsableState.Advanced)] public static Type InterfaceType { - // TODO: replace this with DynamicallyAccessedMembersAttribute (https://github.com/dotnet/runtime/issues/37837) - [DynamicDependency("#ctor", typeof(TypeDescriptorInterface))] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] get => typeof(TypeDescriptorInterface); } diff --git a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SqlXml.cs b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SqlXml.cs index a1f4c409c3b1bb..a6f168d101aeaf 100644 --- a/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SqlXml.cs +++ b/src/libraries/System.Data.Common/src/System/Data/SQLTypes/SqlXml.cs @@ -128,7 +128,6 @@ private static Func Crea private static MethodInfo CreateSqlReaderMethodInfo { - [DynamicDependency("CreateSqlReader", typeof(System.Xml.XmlReader))] get { if (s_createSqlReaderMethodInfo == null) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs index 0c3d619900a3e0..081a51da1b47e4 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs @@ -892,9 +892,6 @@ public PropertyFetch(Type? type) /// /// Create a property fetcher for a propertyName /// - [DynamicDependency("#ctor(System.Type)", typeof(EnumeratePropertyFetch<>))] - [DynamicDependency("#ctor(System.Type,System.Reflection.PropertyInfo)", typeof(RefTypedFetchProperty<,>))] - [DynamicDependency("#ctor(System.Type,System.Reflection.PropertyInfo)", typeof(ValueTypedFetchProperty<,>))] public static PropertyFetch FetcherForProperty(Type? type, string propertyName) { if (propertyName == null) @@ -949,6 +946,12 @@ public static PropertyFetch FetcherForProperty(Type? type, string propertyName) Logger.Message($"Property {propertyName} not found on {type}"); return new PropertyFetch(type); } + // Delegate creation below is incompatible with static properties. + else if (propertyInfo.GetMethod?.IsStatic == true || propertyInfo.SetMethod?.IsStatic == true) + { + Logger.Message($"Property {propertyName} is static."); + return new PropertyFetch(type); + } Type typedPropertyFetcher = typeInfo.IsValueType ? typeof(ValueTypedFetchProperty<,>) : typeof(RefTypedFetchProperty<,>); Type instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( @@ -966,6 +969,8 @@ public static PropertyFetch FetcherForProperty(Type? type, string propertyName) private sealed class RefTypedFetchProperty : PropertyFetch { + [DynamicDependency("get_TraceStateString", typeof(Activity))] + [DynamicDependency("get_IsAllDataRequested", typeof(Activity))] public RefTypedFetchProperty(Type type, PropertyInfo property) : base(type) { Debug.Assert(typeof(TObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/EventSourcePropertyFetchTest.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/EventSourcePropertyFetchTest.cs new file mode 100644 index 00000000000000..0531fffb3ec1b6 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/EventSourcePropertyFetchTest.cs @@ -0,0 +1,200 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Text; + +/// +/// Tests that the System.Diagnostics.DiagnosticSourceEventSource.TransformSpec.PropertyFetch.FetcherForProperty +/// method works as expected when used in a trimmed application. +/// +internal class Program +{ + public static int Main() + { + using var eventListener = new DiagnosticSourceEventListener(); + using var diagnosticListener = new DiagnosticListener("MySource"); + string activityProps = + "-DummyProp" + + ";ActivityId=*Activity.Id" + + ";ActivityStartTime=*Activity.StartTimeUtc.Ticks" + + ";ActivityDuration=*Activity.Duration.Ticks" + + ";ActivityOperationName=*Activity.OperationName" + + ";ActivityIdFormat=*Activity.IdFormat" + + ";ActivityParentId=*Activity.ParentId" + + ";ActivityTags=*Activity.Tags.*Enumerate" + + ";ActivityTraceId=*Activity.TraceId" + + ";ActivitySpanId=*Activity.SpanId" + + ";ActivityTraceStateString=*Activity.TraceStateString" + + ";ActivityParentSpanId=*Activity.ParentSpanId" + + ";ActivityRootId=*Activity.RootId" + + ";ActivityTags=*Activity.Tags" + + ";ActivityLinks=*Activity.Links" + + ";ActivityIsAllDataRequested=*Activity.IsAllDataRequested" + + ";ActivityKind=*Activity.Kind" + + ";ActivityDisplayName=*Activity.DisplayName" + + ";ActivitySource=*Activity.Source" + + ";ActivityParent=*Activity.Parent" + + ";ActivityId=*Activity.Id" + + ";ActivityRecorded=*Activity.Recorded" + + ";ActivityEvents=*Activity.Events" + + ";ActivityBaggage=*Activity.Baggage" + + ";ActivityContext=*Activity.Context" + + ";ActivityActivityTraceFlags=*Activity.ActivityTraceFlags"; + eventListener.Enable( + "MySource/TestActivity1.Start@Activity1Start:" + activityProps + "\r\n" + + "MySource/TestActivity1.Stop@Activity1Stop:" + activityProps + "\r\n" + + "MySource/TestActivity2.Start@Activity2Start:" + activityProps + "\r\n" + + "MySource/TestActivity2.Stop@Activity2Stop:" + activityProps + "\r\n" + ); + + Activity activity1 = new Activity("TestActivity1"); + activity1.SetIdFormat(ActivityIdFormat.W3C); + activity1.TraceStateString = "hi_there"; + activity1.AddTag("one", "1"); + activity1.AddTag("two", "2"); + + var obj = new { DummyProp = "val" }; + + diagnosticListener.StartActivity(activity1, obj); + return eventListener.EventCount == 1 ? 100 : -1; + } + + /// + /// A helper class that listens to Diagnostic sources and send events to the 'EventWritten' callback. + /// + internal class DiagnosticSourceEventListener : EventListener + { + public DiagnosticSourceEventListener() + { + EventWritten += UpdateLastEvent; + } + + public int EventCount; + public DiagnosticSourceEvent LastEvent; + + /// + /// Will be called when a DiagnosticSource event is fired. + /// + public new event Action EventWritten; + + /// + /// It is possible that there are other events besides those that are being forwarded from + /// the DiagnosticSources. These come here. + /// + public event Action OtherEventWritten; + + public void Enable(string filterAndPayloadSpecs, EventKeywords keywords = EventKeywords.All) + { + var args = new Dictionary(); + if (filterAndPayloadSpecs != null) + args.Add("FilterAndPayloadSpecs", filterAndPayloadSpecs); + EnableEvents(_diagnosticSourceEventSource, EventLevel.Verbose, keywords, args); + } + + /// + /// Cleans this class up. Among other things disables the DiagnosticSources being listened to. + /// + public override void Dispose() + { + if (_diagnosticSourceEventSource != null) + { + DisableEvents(_diagnosticSourceEventSource); + _diagnosticSourceEventSource = null; + } + } + + #region private + private void UpdateLastEvent(DiagnosticSourceEvent anEvent) + { + EventCount++; + LastEvent = anEvent; + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + bool wroteEvent = false; + var eventWritten = EventWritten; + if (eventWritten != null) + { + if (eventData.Payload.Count == 3 && (eventData.EventName == "Event" || eventData.EventName.Contains("Activity"))) + { + Debug.Assert(eventData.PayloadNames[0] == "SourceName"); + Debug.Assert(eventData.PayloadNames[1] == "EventName"); + Debug.Assert(eventData.PayloadNames[2] == "Arguments"); + + var anEvent = new DiagnosticSourceEvent + { + SourceName = eventData.Payload[0].ToString(), + EventName = eventData.Payload[1].ToString(), + Arguments = new Dictionary() + }; + + var asKeyValueList = eventData.Payload[2] as IEnumerable; + if (asKeyValueList != null) + { + foreach (IDictionary keyvalue in asKeyValueList) + { + keyvalue.TryGetValue("Key", out object key); + keyvalue.TryGetValue("Value", out object value); + if (key != null && value != null) + anEvent.Arguments[key.ToString()] = value.ToString(); + } + } + eventWritten(anEvent); + wroteEvent = true; + } + } + + if (eventData.EventName == "EventSourceMessage" && 0 < eventData.Payload.Count) + Debug.WriteLine("EventSourceMessage: " + eventData.Payload[0].ToString()); + + var otherEventWritten = OtherEventWritten; + if (otherEventWritten != null && !wroteEvent) + otherEventWritten(eventData); + } + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Microsoft-Diagnostics-DiagnosticSource") + _diagnosticSourceEventSource = eventSource; + } + + EventSource _diagnosticSourceEventSource; + #endregion + } + + /// + /// Represents a single DiagnosticSource event. + /// + internal sealed class DiagnosticSourceEvent + { + public string SourceName; + public string EventName; + public Dictionary Arguments; + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("{"); + sb.Append(" SourceName: \"").Append(SourceName ?? "").Append("\",").AppendLine(); + sb.Append(" EventName: \"").Append(EventName ?? "").Append("\",").AppendLine(); + sb.Append(" Arguments: ").Append("[").AppendLine(); + bool first = true; + foreach (var keyValue in Arguments) + { + if (!first) + sb.Append(",").AppendLine(); + first = false; + sb.Append(" ").Append(keyValue.Key).Append(": \"").Append(keyValue.Value).Append("\""); + } + sb.AppendLine().AppendLine(" ]"); + sb.AppendLine("}"); + return sb.ToString(); + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.TrimmingTests.proj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.TrimmingTests.proj new file mode 100644 index 00000000000000..da4a46f2ae141a --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.TrimmingTests.proj @@ -0,0 +1,5 @@ + + + + + diff --git a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.Generated.cs b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.Generated.cs index ed2ee838c56bc2..b58ef2ff8656c4 100644 --- a/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.Generated.cs +++ b/src/libraries/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/CallInstruction.Generated.cs @@ -4,10 +4,10 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.Dynamic.Utils; using System.Reflection; -using System.Diagnostics.CodeAnalysis; using System.Threading; namespace System.Linq.Expressions.Interpreter @@ -160,17 +160,7 @@ private static CallInstruction FastCreate(MethodInfo target, ParameterIn #endif #if FEATURE_DLG_INVOKE - // TODO: replace these with DynamicallyAccessedMembersAttribute (https://github.com/dotnet/runtime/issues/37837) - [DynamicDependency("#ctor", typeof(ActionCallInstruction))] - [DynamicDependency("#ctor", typeof(ActionCallInstruction<>))] - [DynamicDependency("#ctor", typeof(ActionCallInstruction<,>))] - [DynamicDependency("#ctor", typeof(ActionCallInstruction<,,>))] - [DynamicDependency("#ctor", typeof(ActionCallInstruction<,,,>))] - [DynamicDependency("#ctor", typeof(FuncCallInstruction<>))] - [DynamicDependency("#ctor", typeof(FuncCallInstruction<,>))] - [DynamicDependency("#ctor", typeof(FuncCallInstruction<,,>))] - [DynamicDependency("#ctor", typeof(FuncCallInstruction<,,,>))] - [DynamicDependency("#ctor", typeof(FuncCallInstruction<,,,,>))] + [return: DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes.PublicConstructors)] private static Type GetHelperType(MethodInfo info, Type[] arrTypes) { Type t; diff --git a/src/libraries/System.Linq.Expressions/tests/TrimmingTests/GetHelperTypeTests.cs b/src/libraries/System.Linq.Expressions/tests/TrimmingTests/GetHelperTypeTests.cs new file mode 100644 index 00000000000000..57ab775e6561f5 --- /dev/null +++ b/src/libraries/System.Linq.Expressions/tests/TrimmingTests/GetHelperTypeTests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +/// +/// Tests that the System.Linq.Expressions.Interpreter.CallInstruction.GetHelperType +/// method works as expected when used in a trimmed application. +/// +internal class Program +{ + static int Main(string[] args) + { + for (int rank = 1; rank < 6; rank++) + { + Array arrayObj = Array.CreateInstance(typeof(string), Enumerable.Repeat(1, rank).ToArray()); + arrayObj.SetValue("solitary value", Enumerable.Repeat(0, rank).ToArray()); + ConstantExpression array = Expression.Constant(arrayObj); + IEnumerable indices = Enumerable.Repeat(Expression.Default(typeof(int)), rank); + // This code path for the Compile call excercises the method being tested. + Func func = Expression.Lambda>( + Expression.ArrayAccess(array, indices)).Compile(preferInterpretation: true); + + if (func() != "solitary value") + { + return -1; + } + } + + return 100; + } +} diff --git a/src/libraries/System.Linq.Expressions/tests/TrimmingTests/System.Linq.Expressions.TrimmingTests.proj b/src/libraries/System.Linq.Expressions/tests/TrimmingTests/System.Linq.Expressions.TrimmingTests.proj new file mode 100644 index 00000000000000..e5b98c5414a7f9 --- /dev/null +++ b/src/libraries/System.Linq.Expressions/tests/TrimmingTests/System.Linq.Expressions.TrimmingTests.proj @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/CookieExtensions.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/CookieExtensions.cs index 3543dffbfbc126..69c48a053fcac6 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/CookieExtensions.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/CookieExtensions.cs @@ -13,7 +13,6 @@ internal static class CookieExtensions { private static Func s_toServerStringFunc; - [DynamicDependency("ToServerString", typeof(Cookie))] public static string ToServerString(this Cookie cookie) { s_toServerStringFunc ??= (Func)typeof(Cookie).GetMethod("ToServerString", BindingFlags.Instance | BindingFlags.NonPublic).CreateDelegate(typeof(Func)); @@ -23,7 +22,6 @@ public static string ToServerString(this Cookie cookie) private static Func s_cloneFunc; - [DynamicDependency("Clone", typeof(Cookie))] public static Cookie Clone(this Cookie cookie) { s_cloneFunc ??= (Func)typeof(Cookie).GetMethod("Clone", BindingFlags.Instance | BindingFlags.NonPublic).CreateDelegate(typeof(Func)); @@ -42,7 +40,6 @@ private enum CookieVariant private static Func s_getVariantFunc; - [DynamicDependency("get_Variant", typeof(Cookie))] public static bool IsRfc2965Variant(this Cookie cookie) { s_getVariantFunc ??= (Func)typeof(Cookie).GetProperty("Variant", BindingFlags.Instance | BindingFlags.NonPublic).GetGetMethod(true).CreateDelegate(typeof(Func)); @@ -55,7 +52,6 @@ internal static class CookieCollectionExtensions { private static Func s_internalAddFunc; - [DynamicDependency("InternalAdd", typeof(CookieCollection))] public static int InternalAdd(this CookieCollection cookieCollection, Cookie cookie, bool isStrict) { s_internalAddFunc ??= (Func)typeof(CookieCollection).GetMethod("InternalAdd", BindingFlags.Instance | BindingFlags.NonPublic).CreateDelegate(typeof(Func)); diff --git a/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.Clone.cs b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.Clone.cs new file mode 100644 index 00000000000000..a4f375e5baf8ca --- /dev/null +++ b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.Clone.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net; +using System.Threading.Tasks; + +namespace CookieExtensionsTest +{ + /// + /// Tests that the System.Net.CookieExtensions.Clone() + /// method works as expected when used in a trimmed application. + /// + internal class Program + { + static async Task Main(string[] args) + { + var helper = new TestHelper(); + HttpListenerResponse response = await helper.GetResponse(); + var cookie = new Cookie("name", "value"); + response.SetCookie(cookie); + + // Cookies are cloned. + cookie.Value = "value3"; + if (response.Cookies[0].Value != "value") + { + return -1; + } + + return 100; + } + } +} diff --git a/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.Helper.cs b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.Helper.cs new file mode 100644 index 00000000000000..a537eedf416c2f --- /dev/null +++ b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.Helper.cs @@ -0,0 +1,239 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace CookieExtensionsTest +{ + internal class TestHelper + { + private readonly HttpListenerFactory _factory; + private readonly Socket _client; + + public TestHelper() + { + _factory = new HttpListenerFactory(); + _client = _factory.GetConnectedSocket(); + } + + public async Task GetRequest(string requestType, string[] headers) + { + _client.Send(_factory.GetContent("1.1", requestType, query: null, text: "Text\r\n", headers, true)); + + HttpListener listener = _factory.GetListener(); + return (await listener.GetContextAsync()).Request; + } + + public async Task GetResponse() + { + _client.Send(_factory.GetContent(httpVersion: "1.1", "POST", query: null, "Give me a context, please", headers: null, headerOnly: false)); + HttpListenerContext context = await _factory.GetListener().GetContextAsync(); + return context.Response; + } + + public string GetClientResponse(int expectedLength) + { + byte[] buffer = new byte[expectedLength]; + + int totalReceived = 0; + while (totalReceived < expectedLength) + { + int bytesReceived = _client.Receive(buffer, totalReceived, buffer.Length - totalReceived, SocketFlags.None); + if (bytesReceived == 0) + { + throw new Exception($"Unexpected early end of response: received {totalReceived} bytes, expected {expectedLength}"); + } + totalReceived += bytesReceived; + } + + return Encoding.UTF8.GetString(buffer, 0, totalReceived); + } + } + + // Utilities for generating URL prefixes for HttpListener + internal class HttpListenerFactory : IDisposable + { + const int StartPort = 1025; + const int MaxStartAttempts = IPEndPoint.MaxPort - StartPort + 1; + private static readonly object s_nextPortLock = new object(); + private static int s_nextPort = StartPort; + + private readonly HttpListener _processPrefixListener; + private readonly Exception _processPrefixException; + private readonly string _processPrefix; + private readonly string _hostname; + private readonly int _port; + + public HttpListenerFactory() + { + // Find a URL prefix that is not in use on this machine *and* uses a port that's not in use. + // Once we find this prefix, keep a listener on it for the duration of the process, so other processes + // can't steal it. + _hostname = "localhost"; + string path = Guid.NewGuid().ToString("N"); + string pathComponent = $"{path}/"; + + for (int attempt = 0; attempt < MaxStartAttempts; attempt++) + { + int port = GetNextPort(); + string prefix = $"http://{_hostname}:{port}/{pathComponent}"; + + var listener = new HttpListener(); + try + { + listener.Prefixes.Add(prefix); + listener.Start(); + + _processPrefixListener = listener; + _processPrefix = prefix; + _port = port; + + _processPrefixException = null; + Socket socket = GetConnectedSocket(); + socket.Close(); + + break; + } + catch (Exception e) + { + // can't use this prefix + listener.Close(); + + // Remember the exception for later + _processPrefixException = e; + + if (e is HttpListenerException listenerException) + { + // If we can't access the host (e.g. if it is '+' or '*' and the current user is the administrator) + // then throw. + const int ERROR_ACCESS_DENIED = 5; + if (listenerException.ErrorCode == ERROR_ACCESS_DENIED && (_hostname == "*" || _hostname == "+")) + { + throw new InvalidOperationException($"Access denied for host {_hostname}"); + } + } + else if (!(e is SocketException)) + { + // If this is not an HttpListenerException or SocketException, something very wrong has happened, and there's no point + // in trying again. + break; + } + } + } + + // At this point, either we've reserved a prefix, or we've tried everything and failed. If we failed, + // we've saved the exception for later. We'll defer actually *throwing* the exception until a test + // asks for the prefix, because dealing with a type initialization exception is not nice in xunit. + } + + public int Port + { + get + { + if (_port == 0) + { + throw new Exception("Could not reserve a port for HttpListener", _processPrefixException); + } + + return _port; + } + } + + public string ListeningUrl + { + get + { + if (_processPrefix == null) + { + throw new Exception("Could not reserve a port for HttpListener", _processPrefixException); + } + + return _processPrefix; + } + } + + public HttpListener GetListener() => _processPrefixListener ?? throw new Exception("Could not reserve a port for HttpListener", _processPrefixException); + + public void Dispose() => _processPrefixListener?.Close(); + + public Socket GetConnectedSocket() + { + + if (_processPrefixException != null) + { + throw new Exception("Could not create HttpListener", _processPrefixException); + } + + string hostname = _hostname == "*" || _hostname == "+" ? "localhost" : _hostname; + + // Some platforms or distributions require IPv6 sockets if the OS supports IPv6. Others (e.g. Ubuntu) don't. + try + { + AddressFamily addressFamily = Socket.OSSupportsIPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork; + Socket socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp); + socket.Connect(hostname, Port); + return socket; + } + catch + { + Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.Connect(hostname, Port); + return socket; + } + } + + public byte[] GetContent(string httpVersion, string requestType, string query, string text, IEnumerable headers, bool headerOnly) + { + headers ??= Enumerable.Empty(); + + Uri listeningUri = new Uri(ListeningUrl); + string rawUrl = listeningUri.PathAndQuery; + if (query != null) + { + rawUrl += query; + } + + string content = $"{requestType} {rawUrl} HTTP/{httpVersion}\r\n"; + if (!headers.Any(header => header.ToLower().StartsWith("host:"))) + { + content += $"Host: { listeningUri.Host}\r\n"; + } + if (text != null && !headers.Any(header => header.ToLower().StartsWith("content-length:"))) + { + content += $"Content-Length: {text.Length}\r\n"; + } + foreach (string header in headers) + { + content += header + "\r\n"; + } + content += "\r\n"; + + if (!headerOnly && text != null) + { + content += text; + } + + return Encoding.UTF8.GetBytes(content); + } + + private static int GetNextPort() + { + lock (s_nextPortLock) + { + int port = s_nextPort++; + if (s_nextPort > IPEndPoint.MaxPort) + { + s_nextPort = StartPort; + } + return port; + } + } + } +} diff --git a/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.InternalAdd.cs b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.InternalAdd.cs new file mode 100644 index 00000000000000..67232ae6b9b24f --- /dev/null +++ b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.InternalAdd.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net; +using System.Threading.Tasks; + +namespace CookieExtensionsTest +{ + /// + /// Tests that the System.Net.CookieExtensions.InternalAdd() + /// method works as expected when used in a trimmed application. + /// + internal class Program + { + static async Task Main(string[] args) + { + var helper = new TestHelper(); + + string cookieString = "cookie: name=value"; + Cookie expected = new Cookie("name", "value"); + + HttpListenerRequest request = await helper.GetRequest("POST", new[] { cookieString }); + Cookie actual = request.Cookies[0]; + + if (request.Cookies.Count != 1 || + actual.Name != expected.Name || + actual.Value != expected.Value || + actual.Port != expected.Port || + actual.Path != expected.Path || + actual.Domain != expected.Domain) + { + return -1; + } + + return 100; + } + } +} diff --git a/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.ToServerString.cs b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.ToServerString.cs new file mode 100644 index 00000000000000..3a7dab2d9ee072 --- /dev/null +++ b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/CookieExtensionsTest.ToServerString.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net; +using System.Threading.Tasks; + +namespace CookieExtensionsTest +{ + /// + /// Tests that the System.Net.CookieExtensions.ToServerString() + /// method works as expected when used in a trimmed application. + /// + internal class Program + { + static async Task Main(string[] args) + { + var helper = new TestHelper(); + + CookieCollection cookies = new CookieCollection + { + new Cookie("name1", "value1"), + new Cookie("name2", "value2") { Port = "\"300\"" } + }; + + int expectedBytes = 196; + string expectedSetCookie = "Set-Cookie: name1=value1"; + string expectedSetCookie2 = "Set-Cookie2: name2=value2; Port=\"300\"; Version=1"; + + HttpListenerResponse response = await helper.GetResponse(); + response.Cookies = cookies; + + response.Close(); + + if (expectedSetCookie.Replace("Set-Cookie: ", "") != response.Headers["Set-Cookie"] || + expectedSetCookie2.Replace("Set-Cookie2: ", "") != response.Headers["Set-Cookie2"]) + { + return -1; + } + + string clientResponse = helper.GetClientResponse(expectedBytes); + + if (!clientResponse.Contains($"\r\n{expectedSetCookie}\r\n") || + !clientResponse.Contains($"\r\n{expectedSetCookie2}\r\n")) + { + return -1; + } + + return 100; + } + } +} diff --git a/src/libraries/System.Net.HttpListener/tests/TrimmingTests/System.Net.HttpListener.TrimmingTests.proj b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/System.Net.HttpListener.TrimmingTests.proj new file mode 100644 index 00000000000000..a9e9e0a944267f --- /dev/null +++ b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/System.Net.HttpListener.TrimmingTests.proj @@ -0,0 +1,18 @@ + + + + + + + CookieExtensionsTest.Helper.cs + + + CookieExtensionsTest.Helper.cs + + + CookieExtensionsTest.Helper.cs + + + + + From f56754e68ca0d46fc995b87baf873fb767701d11 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Thu, 25 Jun 2020 17:28:10 -0700 Subject: [PATCH 3/3] Clean up DynamicDependencyAttribute: review feedback + another test --- .../tests/TrimmingTests/InterfaceTypeTest.cs | 22 ++ .../DiagnosticSourceEventSource.cs | 11 +- .../EventSourcePropertyFetchTest.cs | 200 ------------------ ...ostics.DiagnosticSource.TrimmingTests.proj | 5 - ...System.Linq.Expressions.TrimmingTests.proj | 1 - ...System.Net.HttpListener.TrimmingTests.proj | 1 - 6 files changed, 25 insertions(+), 215 deletions(-) create mode 100644 src/libraries/System.ComponentModel.TypeConverter/tests/TrimmingTests/InterfaceTypeTest.cs delete mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/EventSourcePropertyFetchTest.cs delete mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.TrimmingTests.proj diff --git a/src/libraries/System.ComponentModel.TypeConverter/tests/TrimmingTests/InterfaceTypeTest.cs b/src/libraries/System.ComponentModel.TypeConverter/tests/TrimmingTests/InterfaceTypeTest.cs new file mode 100644 index 00000000000000..85f5d7f419a505 --- /dev/null +++ b/src/libraries/System.ComponentModel.TypeConverter/tests/TrimmingTests/InterfaceTypeTest.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel; + +/// +/// Tests that the System.ComponentModel.TypeDescriptor.InterfaceType +/// property works as expected when used in a trimmed application. +/// +class Program +{ + static int Main(string[] args) + { + Type type = TypeDescriptor.InterfaceType; + + // Tests that the ctor for System.ComponentModel.TypeDescriptor+TypeDescriptorInterface is not trimmed out. + object obj = Activator.CreateInstance(type); + string expectedObjTypeNamePrefix = "System.ComponentModel.TypeDescriptor+TypeDescriptorInterface, System.ComponentModel.TypeConverter, Version="; + + return obj != null && obj.GetType().AssemblyQualifiedName.StartsWith(expectedObjTypeNamePrefix) + ? 100 + : -1; + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs index 081a51da1b47e4..0c3d619900a3e0 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs @@ -892,6 +892,9 @@ public PropertyFetch(Type? type) /// /// Create a property fetcher for a propertyName /// + [DynamicDependency("#ctor(System.Type)", typeof(EnumeratePropertyFetch<>))] + [DynamicDependency("#ctor(System.Type,System.Reflection.PropertyInfo)", typeof(RefTypedFetchProperty<,>))] + [DynamicDependency("#ctor(System.Type,System.Reflection.PropertyInfo)", typeof(ValueTypedFetchProperty<,>))] public static PropertyFetch FetcherForProperty(Type? type, string propertyName) { if (propertyName == null) @@ -946,12 +949,6 @@ public static PropertyFetch FetcherForProperty(Type? type, string propertyName) Logger.Message($"Property {propertyName} not found on {type}"); return new PropertyFetch(type); } - // Delegate creation below is incompatible with static properties. - else if (propertyInfo.GetMethod?.IsStatic == true || propertyInfo.SetMethod?.IsStatic == true) - { - Logger.Message($"Property {propertyName} is static."); - return new PropertyFetch(type); - } Type typedPropertyFetcher = typeInfo.IsValueType ? typeof(ValueTypedFetchProperty<,>) : typeof(RefTypedFetchProperty<,>); Type instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( @@ -969,8 +966,6 @@ public static PropertyFetch FetcherForProperty(Type? type, string propertyName) private sealed class RefTypedFetchProperty : PropertyFetch { - [DynamicDependency("get_TraceStateString", typeof(Activity))] - [DynamicDependency("get_IsAllDataRequested", typeof(Activity))] public RefTypedFetchProperty(Type type, PropertyInfo property) : base(type) { Debug.Assert(typeof(TObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/EventSourcePropertyFetchTest.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/EventSourcePropertyFetchTest.cs deleted file mode 100644 index 0531fffb3ec1b6..00000000000000 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/EventSourcePropertyFetchTest.cs +++ /dev/null @@ -1,200 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Tracing; -using System.Text; - -/// -/// Tests that the System.Diagnostics.DiagnosticSourceEventSource.TransformSpec.PropertyFetch.FetcherForProperty -/// method works as expected when used in a trimmed application. -/// -internal class Program -{ - public static int Main() - { - using var eventListener = new DiagnosticSourceEventListener(); - using var diagnosticListener = new DiagnosticListener("MySource"); - string activityProps = - "-DummyProp" + - ";ActivityId=*Activity.Id" + - ";ActivityStartTime=*Activity.StartTimeUtc.Ticks" + - ";ActivityDuration=*Activity.Duration.Ticks" + - ";ActivityOperationName=*Activity.OperationName" + - ";ActivityIdFormat=*Activity.IdFormat" + - ";ActivityParentId=*Activity.ParentId" + - ";ActivityTags=*Activity.Tags.*Enumerate" + - ";ActivityTraceId=*Activity.TraceId" + - ";ActivitySpanId=*Activity.SpanId" + - ";ActivityTraceStateString=*Activity.TraceStateString" + - ";ActivityParentSpanId=*Activity.ParentSpanId" + - ";ActivityRootId=*Activity.RootId" + - ";ActivityTags=*Activity.Tags" + - ";ActivityLinks=*Activity.Links" + - ";ActivityIsAllDataRequested=*Activity.IsAllDataRequested" + - ";ActivityKind=*Activity.Kind" + - ";ActivityDisplayName=*Activity.DisplayName" + - ";ActivitySource=*Activity.Source" + - ";ActivityParent=*Activity.Parent" + - ";ActivityId=*Activity.Id" + - ";ActivityRecorded=*Activity.Recorded" + - ";ActivityEvents=*Activity.Events" + - ";ActivityBaggage=*Activity.Baggage" + - ";ActivityContext=*Activity.Context" + - ";ActivityActivityTraceFlags=*Activity.ActivityTraceFlags"; - eventListener.Enable( - "MySource/TestActivity1.Start@Activity1Start:" + activityProps + "\r\n" + - "MySource/TestActivity1.Stop@Activity1Stop:" + activityProps + "\r\n" + - "MySource/TestActivity2.Start@Activity2Start:" + activityProps + "\r\n" + - "MySource/TestActivity2.Stop@Activity2Stop:" + activityProps + "\r\n" - ); - - Activity activity1 = new Activity("TestActivity1"); - activity1.SetIdFormat(ActivityIdFormat.W3C); - activity1.TraceStateString = "hi_there"; - activity1.AddTag("one", "1"); - activity1.AddTag("two", "2"); - - var obj = new { DummyProp = "val" }; - - diagnosticListener.StartActivity(activity1, obj); - return eventListener.EventCount == 1 ? 100 : -1; - } - - /// - /// A helper class that listens to Diagnostic sources and send events to the 'EventWritten' callback. - /// - internal class DiagnosticSourceEventListener : EventListener - { - public DiagnosticSourceEventListener() - { - EventWritten += UpdateLastEvent; - } - - public int EventCount; - public DiagnosticSourceEvent LastEvent; - - /// - /// Will be called when a DiagnosticSource event is fired. - /// - public new event Action EventWritten; - - /// - /// It is possible that there are other events besides those that are being forwarded from - /// the DiagnosticSources. These come here. - /// - public event Action OtherEventWritten; - - public void Enable(string filterAndPayloadSpecs, EventKeywords keywords = EventKeywords.All) - { - var args = new Dictionary(); - if (filterAndPayloadSpecs != null) - args.Add("FilterAndPayloadSpecs", filterAndPayloadSpecs); - EnableEvents(_diagnosticSourceEventSource, EventLevel.Verbose, keywords, args); - } - - /// - /// Cleans this class up. Among other things disables the DiagnosticSources being listened to. - /// - public override void Dispose() - { - if (_diagnosticSourceEventSource != null) - { - DisableEvents(_diagnosticSourceEventSource); - _diagnosticSourceEventSource = null; - } - } - - #region private - private void UpdateLastEvent(DiagnosticSourceEvent anEvent) - { - EventCount++; - LastEvent = anEvent; - } - - protected override void OnEventWritten(EventWrittenEventArgs eventData) - { - bool wroteEvent = false; - var eventWritten = EventWritten; - if (eventWritten != null) - { - if (eventData.Payload.Count == 3 && (eventData.EventName == "Event" || eventData.EventName.Contains("Activity"))) - { - Debug.Assert(eventData.PayloadNames[0] == "SourceName"); - Debug.Assert(eventData.PayloadNames[1] == "EventName"); - Debug.Assert(eventData.PayloadNames[2] == "Arguments"); - - var anEvent = new DiagnosticSourceEvent - { - SourceName = eventData.Payload[0].ToString(), - EventName = eventData.Payload[1].ToString(), - Arguments = new Dictionary() - }; - - var asKeyValueList = eventData.Payload[2] as IEnumerable; - if (asKeyValueList != null) - { - foreach (IDictionary keyvalue in asKeyValueList) - { - keyvalue.TryGetValue("Key", out object key); - keyvalue.TryGetValue("Value", out object value); - if (key != null && value != null) - anEvent.Arguments[key.ToString()] = value.ToString(); - } - } - eventWritten(anEvent); - wroteEvent = true; - } - } - - if (eventData.EventName == "EventSourceMessage" && 0 < eventData.Payload.Count) - Debug.WriteLine("EventSourceMessage: " + eventData.Payload[0].ToString()); - - var otherEventWritten = OtherEventWritten; - if (otherEventWritten != null && !wroteEvent) - otherEventWritten(eventData); - } - - protected override void OnEventSourceCreated(EventSource eventSource) - { - if (eventSource.Name == "Microsoft-Diagnostics-DiagnosticSource") - _diagnosticSourceEventSource = eventSource; - } - - EventSource _diagnosticSourceEventSource; - #endregion - } - - /// - /// Represents a single DiagnosticSource event. - /// - internal sealed class DiagnosticSourceEvent - { - public string SourceName; - public string EventName; - public Dictionary Arguments; - - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("{"); - sb.Append(" SourceName: \"").Append(SourceName ?? "").Append("\",").AppendLine(); - sb.Append(" EventName: \"").Append(EventName ?? "").Append("\",").AppendLine(); - sb.Append(" Arguments: ").Append("[").AppendLine(); - bool first = true; - foreach (var keyValue in Arguments) - { - if (!first) - sb.Append(",").AppendLine(); - first = false; - sb.Append(" ").Append(keyValue.Key).Append(": \"").Append(keyValue.Value).Append("\""); - } - sb.AppendLine().AppendLine(" ]"); - sb.AppendLine("}"); - return sb.ToString(); - } - } -} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.TrimmingTests.proj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.TrimmingTests.proj deleted file mode 100644 index da4a46f2ae141a..00000000000000 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/TrimmingTests/System.Diagnostics.DiagnosticSource.TrimmingTests.proj +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/libraries/System.Linq.Expressions/tests/TrimmingTests/System.Linq.Expressions.TrimmingTests.proj b/src/libraries/System.Linq.Expressions/tests/TrimmingTests/System.Linq.Expressions.TrimmingTests.proj index e5b98c5414a7f9..da4a46f2ae141a 100644 --- a/src/libraries/System.Linq.Expressions/tests/TrimmingTests/System.Linq.Expressions.TrimmingTests.proj +++ b/src/libraries/System.Linq.Expressions/tests/TrimmingTests/System.Linq.Expressions.TrimmingTests.proj @@ -1,4 +1,3 @@ - diff --git a/src/libraries/System.Net.HttpListener/tests/TrimmingTests/System.Net.HttpListener.TrimmingTests.proj b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/System.Net.HttpListener.TrimmingTests.proj index a9e9e0a944267f..2ab8b1999b438e 100644 --- a/src/libraries/System.Net.HttpListener/tests/TrimmingTests/System.Net.HttpListener.TrimmingTests.proj +++ b/src/libraries/System.Net.HttpListener/tests/TrimmingTests/System.Net.HttpListener.TrimmingTests.proj @@ -1,4 +1,3 @@ -