Skip to content
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
127 changes: 93 additions & 34 deletions src/System.Management.Automation/engine/CoreAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1983,13 +1983,39 @@ internal static void DoBoxingIfNecessary(ILGenerator generator, Type type)

#endregion base
}

/// <summary>
/// The abstract cache entry type.
/// All specific cache entry types should derive from it.
/// </summary>
internal abstract class CacheEntry
{
/// <summary>
/// Gets the boolean value to indicate if the member is hidden.
/// </summary>
/// <remarks>
/// Currently, we only check the 'HiddenAttribute' declared for properties and methods,
/// because it can be done for them through the 'hidden' keyword in PowerShell Class.
///
/// We can't currently write a parameterized property in a PowerShell class so it's not too important
/// to check for the 'HiddenAttribute' for parameterized properties. But if someone added the attribute
/// to their C#, it'd be good to set this property correctly.
/// </remarks>
internal virtual bool IsHidden => false;
}

/// <summary>
/// Ordered and case insensitive hashtable.
/// </summary>
internal class CacheTable
{
/// <summary>
/// An object collection is used to help make populating method cache table more efficient
/// <see cref="DotNetAdapter.PopulateMethodReflectionTable(Type, CacheTable, BindingFlags)"/>.
/// </summary>
internal Collection<object> memberCollection;
private Dictionary<string, int> _indexes;

internal CacheTable()
{
memberCollection = new Collection<object>();
Expand All @@ -2012,17 +2038,30 @@ internal object this[string name]
return null;
}

return this.memberCollection[indexObj];
return memberCollection[indexObj];
}
}

/// <summary>
/// Get the first non-hidden member that satisfies the predicate.
/// </summary>
/// <remarks>
/// Hidden members are not returned for any fuzzy searches (searching by 'match' or enumerating a collection).
/// A hidden member is returned only if the member name is explicitly looked for.
/// </remarks>
internal object GetFirstOrDefault(MemberNamePredicate predicate)
{
foreach (var entry in _indexes)
{
if (predicate(entry.Key))
{
return this.memberCollection[entry.Value];
object member = memberCollection[entry.Value];
if (member is CacheEntry cacheEntry && cacheEntry.IsHidden)
{
continue;
}

return member;
}
}

Expand Down Expand Up @@ -2565,9 +2604,9 @@ private static readonly Dictionary<Type, Dictionary<string, EventCacheEntry>> s_
private static readonly Dictionary<Type, Dictionary<string, EventCacheEntry>> s_staticEventCacheTable
= new Dictionary<Type, Dictionary<string, EventCacheEntry>>();

internal class MethodCacheEntry
internal class MethodCacheEntry : CacheEntry
{
internal MethodInformation[] methodInformationStructures;
internal readonly MethodInformation[] methodInformationStructures;
/// <summary>
/// Cache delegate to the ctor of PSMethod&lt;&gt; with a template parameter derived from the methodInformationStructures.
/// </summary>
Expand All @@ -2585,9 +2624,33 @@ internal MethodInformation this[int i]
return methodInformationStructures[i];
}
}

private bool? _isHidden;
internal override bool IsHidden
{
get
{
if (_isHidden == null)
{
bool hasHiddenAttribute = false;
foreach (var method in methodInformationStructures)
{
if (method.method.GetCustomAttributes(typeof(HiddenAttribute), inherit: false).Length != 0)
{
hasHiddenAttribute = true;
break;
}
}

_isHidden = hasHiddenAttribute;
}

return _isHidden.Value;
}
}
}

internal class EventCacheEntry
internal class EventCacheEntry : CacheEntry
{
internal EventInfo[] events;

Expand All @@ -2597,7 +2660,7 @@ internal EventCacheEntry(EventInfo[] events)
}
}

internal class ParameterizedPropertyCacheEntry
internal class ParameterizedPropertyCacheEntry : CacheEntry
{
internal MethodInformation[] getterInformation;
internal MethodInformation[] setterInformation;
Expand Down Expand Up @@ -2676,7 +2739,7 @@ internal ParameterizedPropertyCacheEntry(List<PropertyInfo> properties)
}
}

internal class PropertyCacheEntry
internal class PropertyCacheEntry : CacheEntry
{
internal delegate object GetterDelegate(object instance);
internal delegate void SetterDelegate(object instance, object setValue);
Expand Down Expand Up @@ -2916,6 +2979,20 @@ internal SetterDelegate setterDelegate
internal bool isStatic;
internal Type propertyType;

private bool? _isHidden;
internal override bool IsHidden
{
get
{
if (_isHidden == null)
{
_isHidden = member.GetCustomAttributes(typeof(HiddenAttribute), inherit: false).Length != 0;
}

return _isHidden.Value;
}
}

private AttributeCollection _attributes;
internal AttributeCollection Attributes
{
Expand Down Expand Up @@ -3578,8 +3655,7 @@ private T GetDotNetPropertyImpl<T>(object obj, string propertyName, MemberNamePr
case null:
return null;
case PropertyCacheEntry cacheEntry when lookingForProperties:
var isHidden = cacheEntry.member.GetCustomAttributes(typeof(HiddenAttribute), false).Any();
return new PSProperty(cacheEntry.member.Name, this, obj, cacheEntry) { IsHidden = isHidden } as T;
return new PSProperty(cacheEntry.member.Name, this, obj, cacheEntry) { IsHidden = cacheEntry.IsHidden } as T;
case ParameterizedPropertyCacheEntry paramCacheEntry when lookingForParameterizedProperties:

// TODO: check for HiddenAttribute
Expand Down Expand Up @@ -3612,17 +3688,8 @@ private T GetDotNetMethodImpl<T>(object obj, string methodName, MemberNamePredic

var isCtor = methods[0].method is ConstructorInfo;
bool isSpecial = !isCtor && methods[0].method.IsSpecialName;
bool isHidden = false;
foreach (var method in methods.methodInformationStructures)
{
if (method.method.GetCustomAttributes(typeof(HiddenAttribute), false).Any())
{
isHidden = true;
break;
}
}

return PSMethod.Create(methods[0].method.Name, this, obj, methods, isSpecial, isHidden) as T;
return PSMethod.Create(methods[0].method.Name, this, obj, methods, isSpecial, methods.IsHidden) as T;
}

internal T GetDotNetProperty<T>(object obj, string propertyName) where T : PSMemberInfo
Expand Down Expand Up @@ -3712,10 +3779,12 @@ internal void AddAllProperties<T>(object obj, PSMemberInfoInternalCollection<T>
{
if (!ignoreDuplicates || (members[propertyEntry.member.Name] == null))
{
var isHidden = propertyEntry.member.GetCustomAttributes(typeof(HiddenAttribute), false).Any();
members.Add(new PSProperty(propertyEntry.member.Name, this,
obj, propertyEntry)
{ IsHidden = isHidden } as T);
members.Add(
new PSProperty(
name: propertyEntry.member.Name,
adapter: this,
baseObject: obj,
adapterData: propertyEntry) { IsHidden = propertyEntry.IsHidden } as T);
}
}
}
Expand Down Expand Up @@ -3754,17 +3823,7 @@ internal void AddAllMethods<T>(object obj, PSMemberInfoInternalCollection<T> mem
if (!ignoreDuplicates || (members[name] == null))
{
bool isSpecial = !isCtor && method[0].method.IsSpecialName;
bool isHidden = false;
foreach (var m in method.methodInformationStructures)
{
if (m.method.GetCustomAttributes(typeof(HiddenAttribute), false).Any())
{
isHidden = true;
break;
}
}

members.Add(PSMethod.Create(name, this, obj, method, isSpecial, isHidden) as T);
members.Add(PSMethod.Create(name, this, obj, method, isSpecial, method.IsHidden) as T);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal abstract class BaseWMIAdapter : Adapter
/// by Get-Member cmdlet, original MethodData and computed method information such
/// as whether a method is static etc.
/// </summary>
internal class WMIMethodCacheEntry
internal class WMIMethodCacheEntry : CacheEntry
{
public string Name { get; }

Expand Down
53 changes: 53 additions & 0 deletions test/powershell/engine/Formatting/BugFix.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

Describe "Hidden properties should not be returned by the 'FirstOrDefault' primitive" -Tag CI {

It "Formatting for an object with no property/field should use 'ToString'" {
class Empty {
[String]ToString() { return 'MyString' }
}

$outstring = [Empty]::new() | Out-String
$outstring.Trim() | Should -BeExactly "MyString"

class Empty2 { }

$outstring = [Empty2]::new() | Out-String
$outstring.Trim() | Should -BeLike "*.Empty2"
}

It "Formatting for an object with only hidden property should use 'ToString'" {
class Hidden {
hidden $Param = 'Foo'
[String]ToString() { return 'MyString' }
}

$outstring = [Hidden]::new() | Out-String
$outstring.Trim() | Should -BeExactly "MyString"

class Hidden2 {
hidden $Param = 'Foo'
}

$outstring = [Hidden2]::new() | Out-String
$outstring.Trim() | Should -BeLike "*.Hidden2"
}

It 'Formatting for an object with no-hidden property should use the default view' {
class Params {
$Param = 'Foo'
[String]ToString() { return 'MyString' }
}

$outstring = [Params]::new() | Out-String
$outstring.Trim() | Should -BeExactly "Param$([System.Environment]::NewLine)-----$([System.Environment]::NewLine)Foo"

class Params2 {
$Param = 'Foo'
}

$outstring = [Params2]::new() | Out-String
$outstring.Trim() | Should -BeExactly "Param$([System.Environment]::NewLine)-----$([System.Environment]::NewLine)Foo"
}
}