/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/System.Management.Automation/engine/MergedCommandParameterMetadata.cs
717 строк
28 KB
Frode Flaten
Throw when too many parameter sets are defined (#17881)
22 авг 2022, 22:11
Не верифицирован
22 авг 2022, 22:11
a15bfcb
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Management.Automation.Language; using System.Text; namespace System.Management.Automation { internal class MergedCommandParameterMetadata { /// <summary> /// Replaces any existing metadata in this object with the metadata specified. /// /// Note that this method should NOT be called after a MergedCommandParameterMetadata /// instance is made read only by calling MakeReadOnly(). This is because MakeReadOnly() /// will turn 'bindableParameters', 'aliasedParameters' and 'parameterSetMap' into /// ReadOnlyDictionary and ReadOnlyCollection. /// </summary> /// <param name="metadata"> /// The metadata to replace in this object. /// </param> /// <returns> /// A list of the merged parameter metadata that was added. /// </returns> internal List<MergedCompiledCommandParameter> ReplaceMetadata(MergedCommandParameterMetadata metadata) { var result = new List<MergedCompiledCommandParameter>(); // Replace bindable parameters _bindableParameters.Clear(); foreach (KeyValuePair<string, MergedCompiledCommandParameter> entry in metadata.BindableParameters) { _bindableParameters.Add(entry.Key, entry.Value); result.Add(entry.Value); } _aliasedParameters.Clear(); foreach (KeyValuePair<string, MergedCompiledCommandParameter> entry in metadata.AliasedParameters) { _aliasedParameters.Add(entry.Key, entry.Value); } // Replace additional meta info _defaultParameterSetName = metadata._defaultParameterSetName; _nextAvailableParameterSetIndex = metadata._nextAvailableParameterSetIndex; _parameterSetMap.Clear(); var parameterSetMapInList = (List<string>)_parameterSetMap; parameterSetMapInList.AddRange(metadata._parameterSetMap); Diagnostics.Assert(ParameterSetCount == _nextAvailableParameterSetIndex, "After replacement with the metadata of the new parameters, ParameterSetCount should be equal to nextAvailableParameterSetIndex"); return result; } /// <summary> /// Merges the specified metadata with the other metadata already defined /// in this object. /// </summary> /// <param name="parameterMetadata"> /// The compiled metadata for the type to be merged. /// </param> /// <param name="binderAssociation"> /// The type of binder that the CommandProcessor will use to bind /// the parameters for <paramref name="parameterMetadata"/> /// </param> /// <returns> /// A collection of the merged parameter metadata that was added. /// </returns> /// <exception cref="MetadataException"> /// If a parameter name or alias described in the <paramref name="parameterMetadata"/> already /// exists. /// </exception> internal Collection<MergedCompiledCommandParameter> AddMetadataForBinder( InternalParameterMetadata parameterMetadata, ParameterBinderAssociation binderAssociation) { if (parameterMetadata == null) { throw PSTraceSource.NewArgumentNullException(nameof(parameterMetadata)); } Collection<MergedCompiledCommandParameter> result = new Collection<MergedCompiledCommandParameter>(); // Merge in the bindable parameters foreach (KeyValuePair<string, CompiledCommandParameter> bindableParameter in parameterMetadata.BindableParameters) { if (_bindableParameters.ContainsKey(bindableParameter.Key)) { MetadataException exception = new MetadataException( "ParameterNameAlreadyExistsForCommand", null, Metadata.ParameterNameAlreadyExistsForCommand, bindableParameter.Key); throw exception; } // NTRAID#Windows Out Of Band Releases-926371-2005/12/27-JonN if (_aliasedParameters.ContainsKey(bindableParameter.Key)) { MetadataException exception = new MetadataException( "ParameterNameConflictsWithAlias", null, Metadata.ParameterNameConflictsWithAlias, bindableParameter.Key, RetrieveParameterNameForAlias(bindableParameter.Key, _aliasedParameters)); throw exception; } MergedCompiledCommandParameter mergedParameter = new MergedCompiledCommandParameter(bindableParameter.Value, binderAssociation); _bindableParameters.Add(bindableParameter.Key, mergedParameter); result.Add(mergedParameter); // Merge in the aliases foreach (string aliasName in bindableParameter.Value.Aliases) { if (_aliasedParameters.ContainsKey(aliasName)) { MetadataException exception = new MetadataException( "AliasParameterNameAlreadyExistsForCommand", null, Metadata.AliasParameterNameAlreadyExistsForCommand, aliasName); throw exception; } // NTRAID#Windows Out Of Band Releases-926371-2005/12/27-JonN if (_bindableParameters.ContainsKey(aliasName)) { MetadataException exception = new MetadataException( "ParameterNameConflictsWithAlias", null, Metadata.ParameterNameConflictsWithAlias, RetrieveParameterNameForAlias(aliasName, _bindableParameters), bindableParameter.Value.Name); throw exception; } _aliasedParameters.Add(aliasName, mergedParameter); } } return result; } /// <summary> /// The next available parameter set bit. This number increments but the parameter /// set bit is really 1 shifted left this number of times. This number also acts /// as the index for the parameter set map. /// </summary> private uint _nextAvailableParameterSetIndex; /// <summary> /// The maximum number of parameter sets allowed. Limit is set by the use /// of a uint bitmask to store which parameter sets a parameter is included in. /// See <see cref="ParameterSetSpecificMetadata.ParameterSetFlag"/>. /// </summary> private const uint MaxParameterSetCount = 32; /// <summary> /// Gets the number of parameter sets that were declared for the command. /// </summary> internal int ParameterSetCount { get { return _parameterSetMap.Count; } } /// <summary> /// Gets a bit-field representing all valid parameter sets. /// </summary> internal uint AllParameterSetFlags { get { return (1u << ParameterSetCount) - 1; } } /// <summary> /// This is the parameter set map. The index is the number of times 1 gets shifted /// left to specify the bit field marker for the parameter set. /// The value is the parameter set name. /// New parameter sets are added at the nextAvailableParameterSetIndex. /// </summary> private IList<string> _parameterSetMap = new List<string>(); /// <summary> /// The name of the default parameter set. /// </summary> private string _defaultParameterSetName; /// <summary> /// Adds the parameter set name to the parameter set map and returns the /// index. If the parameter set name was already in the map, the index to /// the existing parameter set name is returned. /// </summary> /// <param name="parameterSetName"> /// The name of the parameter set to add. /// </param> /// <returns> /// The index of the parameter set name. If the name didn't already exist the /// name gets added and the new index is returned. If the name already exists /// the index of the existing name is returned. /// </returns> /// <remarks> /// The nextAvailableParameterSetIndex is incremented if the parameter set name /// is added. /// </remarks> /// <exception cref="ParsingMetadataException"> /// If more than uint.MaxValue parameter-sets are defined for the command. /// </exception> private int AddParameterSetToMap(string parameterSetName) { int index = -1; if (!string.IsNullOrEmpty(parameterSetName)) { index = _parameterSetMap.IndexOf(parameterSetName); // A parameter set name should only be added once if (index == -1) { if (_nextAvailableParameterSetIndex >= MaxParameterSetCount) { // Don't let the parameter set index overflow ParsingMetadataException parsingException = new ParsingMetadataException( "ParsingTooManyParameterSets", null, Metadata.ParsingTooManyParameterSets); throw parsingException; } _parameterSetMap.Add(parameterSetName); index = _parameterSetMap.IndexOf(parameterSetName); Diagnostics.Assert( index == _nextAvailableParameterSetIndex, "AddParameterSetToMap should always add the parameter set name to the map at the nextAvailableParameterSetIndex"); _nextAvailableParameterSetIndex++; } } return index; } /// <summary> /// Loops through all the parameters and retrieves the parameter set names. In the process /// it generates a mapping of parameter set names to the bits in the bit-field and sets /// the parameter set flags for the parameter. /// </summary> /// <param name="defaultParameterSetName"> /// The default parameter set name. /// </param> /// <returns> /// The bit flag for the default parameter set. /// </returns> /// <exception cref="ParsingMetadataException"> /// If more than uint.MaxValue parameter-sets are defined for the command. /// </exception> internal uint GenerateParameterSetMappingFromMetadata(string defaultParameterSetName) { // First clear the parameter set map _parameterSetMap.Clear(); _nextAvailableParameterSetIndex = 0; uint defaultParameterSetFlag = 0; if (!string.IsNullOrEmpty(defaultParameterSetName)) { _defaultParameterSetName = defaultParameterSetName; // Add the default parameter set to the parameter set map int index = AddParameterSetToMap(defaultParameterSetName); defaultParameterSetFlag = (uint)1 << index; } // Loop through all the parameters and then each parameter set for each parameter foreach (MergedCompiledCommandParameter parameter in BindableParameters.Values) { // For each parameter we need to generate a bit-field for the parameter sets // that the parameter is a part of. uint parameterSetBitField = 0; foreach (var keyValuePair in parameter.Parameter.ParameterSetData) { var parameterSetName = keyValuePair.Key; var parameterSetData = keyValuePair.Value; if (string.Equals(parameterSetName, ParameterAttribute.AllParameterSets, StringComparison.OrdinalIgnoreCase)) { // Don't add the parameter set name but assign the bit field zero and then mark the bool parameterSetData.ParameterSetFlag = 0; parameterSetData.IsInAllSets = true; parameter.Parameter.IsInAllSets = true; } else { // Add the parameter set name and/or get the index in the map int index = AddParameterSetToMap(parameterSetName); Diagnostics.Assert( index >= 0, "AddParameterSetToMap should always be able to add the parameter set name, if not it should throw"); // Calculate the bit for this parameter set uint parameterSetBit = (uint)1 << index; // Add the bit to the bit-field parameterSetBitField |= parameterSetBit; // Add the bit to the parameter set specific data parameterSetData.ParameterSetFlag = parameterSetBit; } } // Set the bit field in the parameter parameter.Parameter.ParameterSetFlags = parameterSetBitField; } return defaultParameterSetFlag; } /// <summary> /// Gets the parameter set name for the specified parameter set. /// </summary> /// <param name="parameterSet"> /// The parameter set to get the name for. /// </param> /// <returns> /// The name of the specified parameter set. /// </returns> internal string GetParameterSetName(uint parameterSet) { string result = _defaultParameterSetName; if (string.IsNullOrEmpty(result)) { result = ParameterAttribute.AllParameterSets; } if (parameterSet != uint.MaxValue && parameterSet != 0) { // Count the number of right shifts it takes to hit the parameter set // This is the index into the parameter set map. int index = 0; while (((parameterSet >> index) & 0x1) == 0) { ++index; } // Now check to see if there are any remaining sets passed this bit. // If so return string.Empty if (((parameterSet >> (index + 1)) & 0x1) == 0) { // Ensure that the bit found was within the map, if not return an empty string if (index < _parameterSetMap.Count) { result = _parameterSetMap[index]; } else { result = string.Empty; } } else { result = string.Empty; } } return result; } /// <summary> /// Helper function to retrieve the name of the parameter /// which defined an alias. /// </summary> /// <param name="key"></param> /// <param name="dict"></param> /// <returns></returns> private static string RetrieveParameterNameForAlias( string key, IDictionary<string, MergedCompiledCommandParameter> dict) { MergedCompiledCommandParameter mergedParam = dict[key]; if (mergedParam != null) { CompiledCommandParameter compiledParam = mergedParam.Parameter; if (compiledParam != null) { if (!string.IsNullOrEmpty(compiledParam.Name)) return compiledParam.Name; } } return string.Empty; } /// <summary> /// Gets the parameters by matching its name. /// </summary> /// <param name="name"> /// The name of the parameter. /// </param> /// <param name="throwOnParameterNotFound"> /// If true and a matching parameter is not found, an exception will be /// throw. If false and a matching parameter is not found, null is returned. /// </param> /// <param name="tryExactMatching"> /// If true we do exact matching, otherwise we do not. /// </param> /// <param name="invocationInfo"> /// The invocation information about the code being run. /// </param> /// <returns> /// The a collection of the metadata associated with the parameters that /// match the specified name. If no matches were found, an empty collection /// is returned. /// </returns> /// <exception cref="ArgumentException"> /// If <paramref name="name"/> is null or empty. /// </exception> internal MergedCompiledCommandParameter GetMatchingParameter( string name, bool throwOnParameterNotFound, bool tryExactMatching, InvocationInfo invocationInfo) { if (string.IsNullOrEmpty(name)) { throw PSTraceSource.NewArgumentException(nameof(name)); } Collection<MergedCompiledCommandParameter> matchingParameters = new Collection<MergedCompiledCommandParameter>(); // Skip the leading '-' if present if (name.Length > 0 && CharExtensions.IsDash(name[0])) { name = name.Substring(1); } // First try to match the bindable parameters foreach (string parameterName in _bindableParameters.Keys) { if (CultureInfo.InvariantCulture.CompareInfo.IsPrefix(parameterName, name, CompareOptions.IgnoreCase)) { // If it is an exact match then only return the exact match // as the result if (tryExactMatching && string.Equals(parameterName, name, StringComparison.OrdinalIgnoreCase)) { return _bindableParameters[parameterName]; } else { matchingParameters.Add(_bindableParameters[parameterName]); } } } // Now check the aliases foreach (string parameterName in _aliasedParameters.Keys) { if (CultureInfo.InvariantCulture.CompareInfo.IsPrefix(parameterName, name, CompareOptions.IgnoreCase)) { // If it is an exact match then only return the exact match // as the result if (tryExactMatching && string.Equals(parameterName, name, StringComparison.OrdinalIgnoreCase)) { return _aliasedParameters[parameterName]; } else { if (!matchingParameters.Contains(_aliasedParameters[parameterName])) { matchingParameters.Add(_aliasedParameters[parameterName]); } } } } if (matchingParameters.Count > 1) { // Prefer parameters in the cmdlet over common parameters Collection<MergedCompiledCommandParameter> filteredParameters = new Collection<MergedCompiledCommandParameter>(); foreach (MergedCompiledCommandParameter matchingParameter in matchingParameters) { if ((matchingParameter.BinderAssociation == ParameterBinderAssociation.DeclaredFormalParameters) || (matchingParameter.BinderAssociation == ParameterBinderAssociation.DynamicParameters)) { filteredParameters.Add(matchingParameter); } } if (tryExactMatching && filteredParameters.Count == 1) { matchingParameters = filteredParameters; } else { StringBuilder possibleMatches = new StringBuilder(); foreach (MergedCompiledCommandParameter matchingParameter in matchingParameters) { possibleMatches.Append(" -"); possibleMatches.Append(matchingParameter.Parameter.Name); } ParameterBindingException exception = new ParameterBindingException( ErrorCategory.InvalidArgument, invocationInfo, null, name, null, null, ParameterBinderStrings.AmbiguousParameter, "AmbiguousParameter", possibleMatches); throw exception; } } else if (matchingParameters.Count == 0) { if (throwOnParameterNotFound) { ParameterBindingException exception = new ParameterBindingException( ErrorCategory.InvalidArgument, invocationInfo, null, name, null, null, ParameterBinderStrings.NamedParameterNotFound, "NamedParameterNotFound"); throw exception; } } MergedCompiledCommandParameter result = null; if (matchingParameters.Count > 0) { result = matchingParameters[0]; } return result; } /// <summary> /// Gets a collection of all the parameters that are allowed in the parameter set. /// </summary> /// <param name="parameterSetFlag"> /// The bit representing the parameter set from which the parameters should be retrieved. /// </param> /// <returns> /// A collection of all the parameters in the specified parameter set. /// </returns> internal Collection<MergedCompiledCommandParameter> GetParametersInParameterSet(uint parameterSetFlag) { Collection<MergedCompiledCommandParameter> result = new Collection<MergedCompiledCommandParameter>(); foreach (MergedCompiledCommandParameter parameter in BindableParameters.Values) { if ((parameterSetFlag & parameter.Parameter.ParameterSetFlags) != 0 || parameter.Parameter.IsInAllSets) { result.Add(parameter); } } return result; } /// <summary> /// Gets a dictionary of the compiled parameter metadata for this Type. /// The dictionary keys are the names of the parameters and /// the values are the compiled parameter metadata. /// </summary> internal IDictionary<string, MergedCompiledCommandParameter> BindableParameters { get { return _bindableParameters; } } private IDictionary<string, MergedCompiledCommandParameter> _bindableParameters = new Dictionary<string, MergedCompiledCommandParameter>(StringComparer.OrdinalIgnoreCase); /// <summary> /// Gets a dictionary of the parameters that have been aliased to other names. The key is /// the alias name and the value is the MergedCompiledCommandParameter metadata. /// </summary> internal IDictionary<string, MergedCompiledCommandParameter> AliasedParameters { get { return _aliasedParameters; } } private IDictionary<string, MergedCompiledCommandParameter> _aliasedParameters = new Dictionary<string, MergedCompiledCommandParameter>(StringComparer.OrdinalIgnoreCase); internal void MakeReadOnly() { _bindableParameters = new ReadOnlyDictionary<string, MergedCompiledCommandParameter>(_bindableParameters); _aliasedParameters = new ReadOnlyDictionary<string, MergedCompiledCommandParameter>(_aliasedParameters); _parameterSetMap = new ReadOnlyCollection<string>(_parameterSetMap); } internal void ResetReadOnly() { _bindableParameters = new Dictionary<string, MergedCompiledCommandParameter>(_bindableParameters, StringComparer.OrdinalIgnoreCase); _aliasedParameters = new Dictionary<string, MergedCompiledCommandParameter>(_aliasedParameters, StringComparer.OrdinalIgnoreCase); } } /// <summary> /// Makes an association between a CompiledCommandParameter and the type /// of the parameter binder used to bind the parameter. /// </summary> internal class MergedCompiledCommandParameter { /// <summary> /// Constructs an association between the CompiledCommandParameter and the /// binder that should be used to bind it. /// </summary> /// <param name="parameter"> /// The metadata for a parameter. /// </param> /// <param name="binderAssociation"> /// The type of binder that should be used to bind the parameter. /// </param> internal MergedCompiledCommandParameter( CompiledCommandParameter parameter, ParameterBinderAssociation binderAssociation) { Diagnostics.Assert(parameter != null, "caller to verify parameter is not null"); this.Parameter = parameter; this.BinderAssociation = binderAssociation; } /// <summary> /// Gets the compiled command parameter for the association. /// </summary> internal CompiledCommandParameter Parameter { get; } /// <summary> /// Gets the type of binder that the compiled command parameter should be bound with. /// </summary> internal ParameterBinderAssociation BinderAssociation { get; } public override string ToString() { return Parameter.ToString(); } } /// <summary> /// This enum is used in the MergedCompiledCommandParameter class /// to associate a particular CompiledCommandParameter with the /// appropriate ParameterBinder. /// </summary> internal enum ParameterBinderAssociation { /// <summary> /// The parameter was declared as a formal parameter in the command type. /// </summary> DeclaredFormalParameters, /// <summary> /// The parameter was declared as a dynamic parameter for the command. /// </summary> DynamicParameters, /// <summary> /// The parameter is a common parameter found in the CommonParameters class. /// </summary> CommonParameters, /// <summary> /// The parameter is a ShouldProcess parameter found in the ShouldProcessParameters class. /// </summary> ShouldProcessParameters, /// <summary> /// The parameter is a transactions parameter found in the TransactionParameters class. /// </summary> TransactionParameters, /// <summary> /// The parameter is a Paging parameter found in the PagingParameters class. /// </summary> PagingParameters, } }