Skip to content

Conversation

@daxian-dbw
Copy link
Member

@daxian-dbw daxian-dbw commented Nov 13, 2017

Fix #5417

  • For Mandatory parameter, ValidateNotNull and ValidateNotNullOrEmpty attributes, skip null-element check if the collection's element type is value type.
  • For ValidateNotNull and ValidateNotNullOrEmpty, they are changed to do null/empty check only on collections (as defined in ParameterCollectionTypeInformation) and hashtable. They currently enumerate elements of any object that implements IEnumerable and also enumerate elements if the argument is an enumerator. I think this behavior is not right for the following reasons:
    • The argument may be an object that talks to Azure to retrieve elements back. It doesn't seem right to have the ValidateNotNull/ValidateNotNullOrEmpty attributes wait on that.
    • On Windows, COM object may be enumerable, but the GetEnumerator() method is not supported in .NET Core on COM object. (See the COM example below)
    • When the argument is an enumerator, the enumerator may not be useful after the validate attribute walks through all its elements. (See the enumerator example below)

COM Example

function bar {
   param ([ValidateNotNull()] $object)
}
> $shell = New-Object -ComObject Shell.Application
> $folder = $shell.NameSpace("F:\tmp\dd\")
> bar -object ($folder.Items())
bar : Cannot validate argument on parameter 'object'. Could not load type 'System.Runtime.InteropServices.ComTypes.IEnumerable' from assembly 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'.
At line:1 char:13
+ bar -object ($folder.Items())
+             ~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidData: (:) [bar], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,bar

Enumerator Example

function bar {
   param ([ValidateNotNull()] $object)
   "MoveNext:  $($object.MoveNext())"
}

> bar -object @(1,2,3).GetEnumerator()
MoveNext:  False

@daxian-dbw daxian-dbw added the Breaking-Change breaking change that may affect users label Nov 13, 2017
build.psm1 Outdated
$ReleaseVersion = $ReleaseTagToUse
} else {
$ReleaseVersion = (Get-PSCommitId) -replace '^v'
$ReleaseVersion = (Get-PSCommitId -WarningAction SilentlyContinue) -replace '^v'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change does not seem related to this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will revert it.

$expected = $baseline + 20

if ($IsWindows) {
$null = New-Item -Path $TESTDRIVE/file1 -ItemType File
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-ItemType File is not necessary anymore. The default itemtype is file

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I will remove it.

param(
[ValidateNotNullOrEmpty()]
$Value,
[string] $TestType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a parameter like: [System.Nullable``1[[System.Int32]]] $nullableInt

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A [System.Nullable[int]] parameter is not useful in this case because the type is not a collection. Hence ValidateNotNullOrEmpty attribute won't validate it as a collection.

Copy link
Collaborator

@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave a comment

isEmpty = false;
// If the element of the collection is of value type, then no need to check for null
// because a value-type value cannot be null.
if (isElementValueType) { break; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we can move this before while line 1997 - we check first element in ParameterCollectionTypeInformation in IsArgumentCollection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't check the elements of arguments in IsArgumentCollection. The argument passed in is the type of arguments, not arguments itself.

Copy link
Collaborator

@iSazonov iSazonov Nov 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I see that no element check is in ParameterCollectionTypeInformation.

But the code is not clear. Maybe:

# Check if the collection is empty.
if (ienum.MoveNext()) isEmpty = false;

# Check every element in the collection only if the element type of the collection is NOT a value type. 
if (!isElementValueType && !isEmpty)
{
    do
    {
        ...
        object element = ienum.Current; 
        ...
    } while(ienum.MoveNext()))
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the logic is clear -- if the element type of the collection is a value type, then skip checking each element. The isEmpty check is needed in either case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is right but the code is slightly tricky for me. I added your comments in my sample above. Also we do two actions that can be carried before the cycle - if we are thinking about performance it is important - I think Measure-Object show a delay starting with 10000 elements.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get your point. I will update as you suggested. Thanks!

}
}
else if ((ienum = arguments as IEnumerable) != null)
else if (IsArgumentCollection(arguments.GetType(), out bool isElementValueType))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is in the context isEmpty = !isElementValueType ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the check of element type doesn't involve inspecting the elements of the collection. You can see that the type of arguments is passed in instead of the arguments itself.
Please see the constructor of ParameterCollectionTypeInformation for details.

isEmpty = false;
// If the element of the collection is of value type, then no need to check for null
// because a value-type value cannot be null.
if (isElementValueType) { break; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why if we calculate this before the cycle?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as above. The element type is resolved using the collection type.

@daxian-dbw
Copy link
Member Author

@iSazonov You comments have been addressed. Please take another look when you have time. Thanks!

@daxian-dbw daxian-dbw added the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Nov 20, 2017
Copy link
Collaborator

@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with two minor comments.

Context "ValidateNotNull, ValidateNotNullOrEmpty and Not-Null-Or-Empty check for Mandatory parameter" {

BeforeAll {
function MandType {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please use more nice name? MandatoryType?

#region NULL validation attributes

/// <summary>
/// Base type of Null Validation attributes
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add final dot in public comments?

@SteveL-MSFT SteveL-MSFT added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Nov 29, 2017
@SteveL-MSFT SteveL-MSFT added this to the 6.0.0-GA milestone Nov 29, 2017
@SteveL-MSFT
Copy link
Member

@PowerShell/powershell-committee reviewed this and approves with this change

@adityapatwardhan
Copy link
Member

@daxian-dbw restarted AppVeyor build. Failed due to #5567

@adityapatwardhan adityapatwardhan merged commit b5f84c2 into PowerShell:master Nov 30, 2017
@daxian-dbw daxian-dbw deleted the perf branch November 30, 2017 23:18
@TravisEz13 TravisEz13 modified the milestones: 6.0.0-GA, 6.0.0-RC.2 Dec 2, 2017
TravisEz13 pushed a commit to TravisEz13/PowerShell that referenced this pull request Dec 2, 2017
PowerShell#5432)

* Fix NotNullOrEmpty check logic

* Fix a test issue on Unix
TravisEz13 pushed a commit that referenced this pull request Dec 2, 2017
#5432)

* Fix NotNullOrEmpty check logic

* Fix a test issue on Unix
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Breaking-Change breaking change that may affect users Committee-Reviewed PS-Committee has reviewed this and made a decision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants