Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[d16-3] [registar] Search the entire interface hierarchy for protocols. Fixes #6493. #6525

Merged
merged 1 commit into from
Jul 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/ObjCRuntime/Registrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ internal class ObjCType {
public CategoryAttribute CategoryAttribute;
public TType Type;
public ObjCType BaseType;
// This only contains the leaf protocols implemented by this type.
public ObjCType [] Protocols;
public string [] AdoptedProtocols;
public bool IsModel;
Expand All @@ -159,6 +160,44 @@ internal class ObjCType {

public bool IsCategory { get { return CategoryAttribute != null; } }

#if MTOUCH || MMP
HashSet<ObjCType> all_protocols;
// This contains all protocols in the type hierarchy.
// Given a type T that implements a protocol with super protocols:
// class T : NSObject, IProtocol2 {}
// [Protocol]
// interface IP1 {}
// [Protocol]
// interface IP2 : IP1 {}
// This property will contain both IP1 and IP2. The Protocols property only contains IP2.
public IEnumerable<ObjCType> AllProtocols {
get {
if (Protocols == null || Protocols.Length == 0)
return null;

if (all_protocols == null) {
var queue = new Queue<ObjCType> (Protocols);
var rv = new HashSet<ObjCType> ();
while (queue.Count > 0) {
var type = queue.Dequeue ();
if (rv.Add (type)) {
foreach (var iface in type.Type.Resolve ().Interfaces) {
if (!Registrar.Types.TryGetValue (iface.InterfaceType, out var superIface)) {
// This is not an interface that corresponds to a protocol.
continue;
}
queue.Enqueue (superIface);
}
}
}
all_protocols = rv;
}

return all_protocols;
}
}
#endif

public void VerifyRegisterAttribute (ref List<Exception> exceptions)
{
if (RegisterAttribute == null)
Expand Down
138 changes: 138 additions & 0 deletions tests/monotouch-test/Foundation/UrlProtocolTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

#if XAMCORE_2_0
using Foundation;
#if MONOMAC
Expand Down Expand Up @@ -66,5 +69,140 @@ public void Task ()
}
}
#endif

#if !__WATCHOS__
[Test]
public void RegistrarTest ()
{
Exception ex = null;
var done = new ManualResetEvent (false);
var success = false;

Task.Run (async () => {
try {
var config = NSUrlSessionConfiguration.DefaultSessionConfiguration;
config.WeakProtocolClasses = NSArray.FromNSObjects (new Class (typeof (CustomUrlProtocol)));
var session = NSUrlSession.FromConfiguration (config);
var custom_url = new NSUrl ("foo://server");
using (var task = await session.CreateDownloadTaskAsync (custom_url)) {
success = true;
}
} catch (Exception e) {
ex = e;
} finally {
done.Set ();
}
});

Assert.IsTrue (TestRuntime.RunAsync (DateTime.Now.AddSeconds (10), () => { }, () => done.WaitOne (0)), "Timed out");
Assert.IsNull (ex, "Exception");
Assert.IsTrue (custom_url_protocol_instance.Called_DidCompleteWithError, "DidCompleteWithError");
// if DidReceiveChallenge is called or not seems to vary between test runs, so we can't assert it.
//Assert.IsFalse (custom_url_protocol_instance.Called_DidReceiveChallenge, "DidReceiveChallenge");
Assert.IsTrue (custom_url_protocol_instance.Called_DidReceiveData, "DidReceiveData");
Assert.IsTrue (custom_url_protocol_instance.Called_DidReceiveResponse, "DidReceiveResponse");
Assert.IsTrue (custom_url_protocol_instance.Called_StartLoading, "StartLoading");
Assert.IsTrue (custom_url_protocol_instance.Called_StopLoading, "StopLoading");
Assert.IsTrue (custom_url_protocol_instance.Called_WillPerformHttpRedirection, "WillPerformHttpRedirection");

Assert.IsTrue (CustomUrlProtocol.Called_CanInitWithRequest, "CanInitWithRequest");
Assert.IsTrue (CustomUrlProtocol.Called_GetCanonicalRequest, "GetCanonicalRequest");

Assert.IsTrue (success, "Success");
}

static CustomUrlProtocol custom_url_protocol_instance;

public class CustomUrlProtocol : NSUrlProtocol, INSUrlSessionDelegate, INSUrlSessionTaskDelegate, INSUrlSessionDataDelegate {
[Export ("canInitWithRequest:")]
public static new bool CanInitWithRequest (NSUrlRequest request)
{
Called_CanInitWithRequest = true;
return true;
}
public static bool Called_CanInitWithRequest;

[Export ("canonicalRequestForRequest:")]
public static new NSUrlRequest GetCanonicalRequest (NSUrlRequest request)
{
Called_GetCanonicalRequest = true;
return request;
}
public static bool Called_GetCanonicalRequest;

[Export ("initWithRequest:cachedResponse:client:")]
public CustomUrlProtocol (NSUrlRequest request, NSCachedUrlResponse cachedResponse, INSUrlProtocolClient client)
: base (request, cachedResponse, client)
{
custom_url_protocol_instance = this;
}

[Export ("startLoading")]
public override void StartLoading ()
{
Called_StartLoading = true;
var config = NSUrlSession.SharedSession.Configuration;
var session = NSUrlSession.FromConfiguration (config, this, new NSOperationQueue ());

var task = session.CreateDataTask (new NSUrlRequest (new NSUrl ("https://microsoft.com")));
task.Resume ();
}
public bool Called_StartLoading;

[Export ("stopLoading")]
public override void StopLoading ()
{
Called_StopLoading = true;
}
public bool Called_StopLoading;

//NSURLSessionTaskDelegate
[Export ("URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:")]
public virtual void WillPerformHttpRedirection (NSUrlSession session, NSUrlSessionTask task, NSHttpUrlResponse response, NSUrlRequest newRequest, Action<NSUrlRequest> completionHandler)
{
Called_WillPerformHttpRedirection = true;
completionHandler (newRequest);
}
public bool Called_WillPerformHttpRedirection;

[Export ("URLSession:task:didReceiveChallenge:completionHandler:")]
public virtual void DidReceiveChallenge (NSUrlSession session, NSUrlSessionTask task, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
Called_DidReceiveChallenge = true;
completionHandler (NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, null);
}
public bool Called_DidReceiveChallenge;

//NSURLSessionDataDelegate
[Export ("URLSession:dataTask:didReceiveResponse:completionHandler:")]
public virtual void DidReceiveResponse (NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action<NSUrlSessionResponseDisposition> completionHandler)
{
Called_DidReceiveResponse = true;
completionHandler (NSUrlSessionResponseDisposition.Allow);
this.Client.ReceivedResponse (this, response, NSUrlCacheStoragePolicy.Allowed);
}
public bool Called_DidReceiveResponse;

[Export ("URLSession:dataTask:didReceiveData:")]
public virtual void DidReceiveData (NSUrlSession session, NSUrlSessionDataTask dataTask, NSData data)
{
Called_DidReceiveData = true;
this.Client.DataLoaded (this, data);
}
public bool Called_DidReceiveData;

[Export ("URLSession:task:didCompleteWithError:")]
public virtual void DidCompleteWithError (NSUrlSession session, NSUrlSessionTask task, NSError error)
{
Called_DidCompleteWithError = true;
if (error != null) {
this.Client.FailedWithError (this, error);
} else {
this.Client.FinishedLoading (this);
}
}
public bool Called_DidCompleteWithError;
}
#endif // !__WATCHOS__
}
}
10 changes: 6 additions & 4 deletions tools/common/StaticRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4129,10 +4129,11 @@ TypeDefinition GetDelegateProxyType (ObjCMethod obj_method)
}

// Might be an implementation of an optional protocol member.
if (obj_method.DeclaringType.Protocols != null) {
var allProtocols = obj_method.DeclaringType.AllProtocols;
if (allProtocols != null) {
string selector = null;

foreach (var proto in obj_method.DeclaringType.Protocols) {
foreach (var proto in allProtocols) {
// We store the DelegateProxy type in the ProtocolMemberAttribute, so check those.
if (selector == null)
selector = obj_method.Selector ?? string.Empty;
Expand Down Expand Up @@ -4175,10 +4176,11 @@ MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int parameter)
}

// Might be an implementation of an optional protocol member.
if (obj_method.DeclaringType.Protocols != null) {
var allProtocols = obj_method.DeclaringType.AllProtocols;
if (allProtocols != null) {
string selector = null;

foreach (var proto in obj_method.DeclaringType.Protocols) {
foreach (var proto in allProtocols) {
// We store the BlockProxy type in the ProtocolMemberAttribute, so check those.
// We may run into binding assemblies built with earlier versions of the generator,
// which means we can't rely on finding the BlockProxy attribute in the ProtocolMemberAttribute.
Expand Down