From 294def7b11c87c0a16b728b5848c984c98bcf6c1 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 15 Mar 2018 15:09:38 -0700 Subject: [PATCH] Fix 'PropertyOnlyAdapter' to allow calling base methods (#6394) For a PropertyOnlyAdapter, the property may come from various sources, but methods, including parameterized properties, still come from DotNetAdapter. So, the binder can optimize on method calls for objects that map to a custom PropertyOnlyAdapter. --- .../engine/CoreAdapter.cs | 23 ++++++++-- .../engine/runtime/Binding/Binders.cs | 13 +++--- test/powershell/engine/ETS/Adapter.Tests.ps1 | 46 +++++++++++++++++++ 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index 4fbb5f662..1092fa290 100644 --- a/src/System.Management.Automation/engine/CoreAdapter.cs +++ b/src/System.Management.Automation/engine/CoreAdapter.cs @@ -46,7 +46,7 @@ namespace System.Management.Automation #region member - internal virtual bool SiteBinderCanOptimize { get { return false; } } + internal virtual bool CanSiteBinderOptimize(MemberTypes typeToOperateOn) { return false; } protected static IEnumerable GetDotNetTypeNameHierarchy(Type type) { @@ -3519,7 +3519,7 @@ namespace System.Management.Automation #region member - internal override bool SiteBinderCanOptimize { get { return true; } } + internal override bool CanSiteBinderOptimize(MemberTypes typeToOperateOn) { return true; } private static ConcurrentDictionary s_typeToTypeNameDictionary = new ConcurrentDictionary(); @@ -4545,7 +4545,24 @@ namespace System.Management.Automation /// internal abstract class PropertyOnlyAdapter : DotNetAdapter { - internal override bool SiteBinderCanOptimize { get { return false; } } + /// + /// For a PropertyOnlyAdapter, the property may come from various sources, + /// but methods, including parameterized properties, still come from DotNetAdapter. + /// So, the binder can optimize on method calls for objects that map to a + /// custom PropertyOnlyAdapter. + /// + internal override bool CanSiteBinderOptimize(MemberTypes typeToOperateOn) + { + switch (typeToOperateOn) + { + case MemberTypes.Property: + return false; + case MemberTypes.Method: + return true; + default: + throw new InvalidOperationException("Should be unreachable. Update code if other member types need to be handled here."); + } + } protected override ConsolidatedString GetInternedTypeNameHierarchy(object obj) { diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index d35d139ed..3d376e525 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -5053,7 +5053,7 @@ namespace System.Management.Automation.Language bool canOptimize; Type aliasConversionType; - memberInfo = GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType); + memberInfo = GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Property); if (!canOptimize) { @@ -5415,8 +5415,8 @@ namespace System.Management.Automation.Language return null; } - PSMemberInfo result = binder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, aliases, aliasRestrictions); - + PSMemberInfo result = binder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, + MemberTypes.Property, aliases, aliasRestrictions); return result; } @@ -5424,6 +5424,7 @@ namespace System.Management.Automation.Language out BindingRestrictions restrictions, out bool canOptimize, out Type aliasConversionType, + MemberTypes memberTypeToOperateOn, HashSet aliases = null, List aliasRestrictions = null) { @@ -5493,7 +5494,7 @@ namespace System.Management.Automation.Language var adapterSet = PSObject.GetMappedAdapter(value, typeTable); if (memberInfo == null) { - canOptimize = adapterSet.OriginalAdapter.SiteBinderCanOptimize; + canOptimize = adapterSet.OriginalAdapter.CanSiteBinderOptimize(memberTypeToOperateOn); // Don't bother looking for the member if we're not going to use it. if (canOptimize) { @@ -5969,7 +5970,7 @@ namespace System.Management.Automation.Language BindingRestrictions restrictions; bool canOptimize; Type aliasConversionType; - memberInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType); + memberInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Property); restrictions = restrictions.Merge(value.PSGetTypeRestriction()); @@ -6464,7 +6465,7 @@ namespace System.Management.Automation.Language BindingRestrictions restrictions; bool canOptimize; Type aliasConversionType; - var methodInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType) as PSMethodInfo; + var methodInfo = _getMemberBinder.GetPSMemberInfo(target, out restrictions, out canOptimize, out aliasConversionType, MemberTypes.Method) as PSMethodInfo; restrictions = args.Aggregate(restrictions, (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); // If the process has ever used ConstrainedLanguage, then we need to add the language mode diff --git a/test/powershell/engine/ETS/Adapter.Tests.ps1 b/test/powershell/engine/ETS/Adapter.Tests.ps1 index c8a955142..ac1c23c0c 100644 --- a/test/powershell/engine/ETS/Adapter.Tests.ps1 +++ b/test/powershell/engine/ETS/Adapter.Tests.ps1 @@ -315,3 +315,49 @@ Describe "DataRow and DataRowView Adapter tests" -tags "CI" { } } } + +Describe "Base method call on object mapped to PropertyOnlyAdapter should work" -tags "CI" { + It "Base method call on object of a subclass of 'XmlDocument' -- Add-Type" { + $code =@' +namespace BaseMethodCallTest.OnXmlDocument { + public class Foo : System.Xml.XmlDocument { + public string MyName { get; set; } + public override void LoadXml(string content) { + MyName = content; + } + } +} +'@ + try { + $null = [BaseMethodCallTest.OnXmlDocument.Foo] + } catch { + Add-Type -TypeDefinition $code + } + + $foo = [BaseMethodCallTest.OnXmlDocument.Foo]::new() + $foo.LoadXml('bar') + $foo.MyName | Should -BeExactly 'bar' + $foo.ChildNodes.Count | Should -Be 0 + + ([System.Xml.XmlDocument]$foo).LoadXml('bar') + $foo.test | Should -BeExactly 'bar' + $foo.ChildNodes.Count | Should -Be 1 + } + + It "Base method call on object of a subclass of 'XmlDocument' -- PowerShell Class" { + class XmlDocChild : System.Xml.XmlDocument { + [string] $MyName + [void] LoadXml([string]$content) { + $this.MyName = $content + # Try to call the base type's .LoadXml() method. + ([System.Xml.XmlDocument] $this).LoadXml($content) + } + } + + $child = [XmlDocChild]::new() + $child.LoadXml('bar') + $child.MyName | Should -BeExactly 'bar' + $child.test | Should -BeExactly 'bar' + $child.ChildNodes.Count | Should -Be 1 + } +}