MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Buffers;5using System.Collections;6using System.Collections.Concurrent;7using System.Collections.Generic;8using System.Collections.Specialized;9using System.Data;10using System.Dynamic;11using System.Globalization;12using System.Linq;13using System.Linq.Expressions;14using System.Management.Automation.Internal;15using System.Management.Automation.Runspaces;16using System.Management.Automation.Security;17using System.Reflection;18using System.Runtime.CompilerServices;19using System.Runtime.InteropServices;20using System.Text;21using System.Text.RegularExpressions;22using System.Threading;23using System.Xml;24 25namespace System.Management.Automation.Language26{27 // Item1 - member name28 // Item2 - class containing dynamic site (for protected/private member access)29 // Item3 - static (true) or instance (false)30 // Item4 - enumerating (true) or not (false)31 using PSGetMemberBinderKeyType = Tuple<string, Type, bool, bool>;32 33 // Item1 - member name34 // Item2 - class containing dynamic site (for protected/private member access)35 // Item3 - enumerating (true) or not (false)36 using PSSetMemberBinderKeyType = Tuple<string, Type, bool>;37 38 // Item1 - member name39 // Item2 - callinfo (# of args and (not used) named arguments)40 // Item3 - property setter (true) or not (false)41 // Item4 - enumerating (true) or not (false)42 // Item5 - invocation constraints (casts used in the invocation expression used to guide overload resolution)43 // Item6 - static (true) or instance (false)44 // Item7 - class containing dynamic site (for protected/private member access)45 using PSInvokeMemberBinderKeyType = Tuple<string, CallInfo, bool, bool, PSMethodInvocationConstraints, bool, Type>;46 47 // Item1 - callinfo (# of args and (not used) named arguments)48 // Item2 - invocation constraints (casts used in the invocation expression used to guide overload resolution)49 // Item3 - property setter (true) or not (false)50 // Item4 - static (true) or instance (false)51 // Item5 - class containing dynamic site (for protected/private member access)52 using PSInvokeDynamicMemberBinderKeyType = Tuple<CallInfo, PSMethodInvocationConstraints, bool, bool, Type>;53 54 // Item1 - class containing dynamic site (for protected/private member access)55 // Item2 - static (true) or instance (false)56 using PSGetOrSetDynamicMemberBinderKeyType = Tuple<Type, bool>;57 58 /// <summary>59 /// Extension methods for DynamicMetaObject. Some of these extensions help handle PSObject, both in terms of60 /// getting the type or value from a DynamicMetaObject, but also help to generate binding restrictions that61 /// account for values that are optionally wrapped in PSObject.62 /// </summary>63 internal static class DynamicMetaObjectExtensions64 {65 internal static readonly DynamicMetaObject FakeError66 = new DynamicMetaObject(ExpressionCache.NullConstant, BindingRestrictions.Empty);67 68 internal static DynamicMetaObject WriteToDebugLog(this DynamicMetaObject obj, DynamicMetaObjectBinder binder)69 {70#if ENABLE_BINDER_DEBUG_LOGGING71 if (obj != FakeError)72 {73 System.Diagnostics.Debug.WriteLine("Binder: {0}\r\n Restrictions: {2}\r\n Target: {1}",74 binder.ToString(),75 obj.Expression.ToDebugString(),76 obj.Restrictions.ToDebugString());77 }78#endif79 return obj;80 }81 82 internal static BindingRestrictions GetSimpleTypeRestriction(this DynamicMetaObject obj)83 {84 if (obj.Value == null)85 {86 return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value);87 }88 89 return BindingRestrictions.GetTypeRestriction(obj.Expression, obj.Value.GetType());90 }91 92 internal static BindingRestrictions PSGetMethodArgumentRestriction(this DynamicMetaObject obj)93 {94 var baseValue = PSObject.Base(obj.Value);95 if (baseValue != null && baseValue.GetType() == typeof(object[]))96 {97 var effectiveArgType = Adapter.EffectiveArgumentType(obj.Value);98 var methodInfo = effectiveArgType != typeof(object[])99 ? CachedReflectionInfo.PSInvokeMemberBinder_IsHomogeneousArray.MakeGenericMethod(effectiveArgType.GetElementType())100 : CachedReflectionInfo.PSInvokeMemberBinder_IsHeterogeneousArray;101 102 BindingRestrictions restrictions;103 Expression test;104 if (obj.Value != baseValue)105 {106 // Need PSObject...107 restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, typeof(PSObject));108 109 var temp = Expression.Variable(typeof(object[]));110 test = Expression.Block(111 new[] { temp },112 Expression.Assign(temp, Expression.TypeAs(Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression), typeof(object[]))),113 Expression.AndAlso(114 Expression.NotEqual(temp, ExpressionCache.NullObjectArray),115 Expression.Call(methodInfo, temp)));116 }117 else118 {119 restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, typeof(object[]));120 var arrayExpr = obj.Expression.Cast(typeof(object[]));121 test = Expression.Call(methodInfo, arrayExpr);122 }123 124 return restrictions.Merge(BindingRestrictions.GetExpressionRestriction(test));125 }126 127 return obj.PSGetTypeRestriction();128 }129 130 internal static BindingRestrictions PSGetStaticMemberRestriction(this DynamicMetaObject obj)131 {132 if (obj.Restrictions != BindingRestrictions.Empty)133 {134 return obj.Restrictions;135 }136 137 if (obj.Value == null)138 {139 return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value);140 }141 142 var baseValue = PSObject.Base(obj.Value);143 if (baseValue == null)144 {145 Diagnostics.Assert(obj.Value == AutomationNull.Value, "PSObject.Base should only return null for AutomationNull.Value");146 return BindingRestrictions.GetExpressionRestriction(Expression.Equal(obj.Expression, Expression.Constant(AutomationNull.Value)));147 }148 149 BindingRestrictions restrictions;150 151 if (baseValue is Type)152 {153 if (obj.Value == baseValue)154 {155 // newObj == oldObj (if not wrapped in PSObject) or156 restrictions = BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value);157 }158 else159 {160 // newObj.GetType() == typeof(PSObject) && PSObject.Base(newObj) == oldObj161 restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType);162 restrictions = restrictions.Merge(163 BindingRestrictions.GetInstanceRestriction(164 Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression),165 baseValue));166 }167 }168 else if (obj.Value != baseValue)169 {170 // Binding restriction will look like:171 // newObj.GetType() == typeof(PSObject) && PSObject.Base(newObj).GetType() == typeof(oldType)172 173 restrictions = BindingRestrictions.GetTypeRestriction(174 Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression),175 baseValue.GetType());176 }177 else178 {179 restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType);180 }181 182 return restrictions;183 }184 185 internal static BindingRestrictions PSGetTypeRestriction(this DynamicMetaObject obj)186 {187 if (obj.Restrictions != BindingRestrictions.Empty)188 {189 return obj.Restrictions;190 }191 192 if (obj.Value == null)193 {194 return BindingRestrictions.GetInstanceRestriction(obj.Expression, obj.Value);195 }196 197 var baseValue = PSObject.Base(obj.Value);198 if (baseValue == null)199 {200 Diagnostics.Assert(obj.Value == AutomationNull.Value, "PSObject.Base should only return null for AutomationNull.Value");201 return BindingRestrictions.GetExpressionRestriction(Expression.Equal(obj.Expression, Expression.Constant(AutomationNull.Value)));202 }203 204 // The default restriction is a simple type test. We use this type test even if the object is a PSObject,205 // this way we can avoid calling PSObject.Base in all restriction checks.206 var restrictions = BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType);207 208 if (obj.Value != baseValue)209 {210 // Binding restriction will look like:211 // newObj.GetType() == typeof(PSObject) && PSObject.Base(newObj).GetType() == typeof(oldType)212 213 restrictions = restrictions.Merge(214 BindingRestrictions.GetTypeRestriction(Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression),215 baseValue.GetType()));216 }217 else if (baseValue is PSObject)218 {219 // We have an empty custom object. The restrictions must check this explicitly, otherwise we have220 // a simple type test on PSObject, which obviously tests true for many objects.221 // So we end up with:222 // newObj.GetType() == typeof(PSObject) && PSObject.Base(newObj) == newObj223 224 restrictions = restrictions.Merge(225 BindingRestrictions.GetExpressionRestriction(226 Expression.Equal(Expression.Call(CachedReflectionInfo.PSObject_Base, obj.Expression), obj.Expression)));227 }228 229 return restrictions;230 }231 232 internal static BindingRestrictions CombineRestrictions(this DynamicMetaObject target, params DynamicMetaObject[] args)233 {234 var restrictions = target.Restrictions == BindingRestrictions.Empty ? target.PSGetTypeRestriction() : target.Restrictions;235 236 for (int index = 0; index < args.Length; index++)237 {238 var r = args[index];239 restrictions =240 restrictions.Merge(r.Restrictions == BindingRestrictions.Empty241 ? r.PSGetTypeRestriction()242 : r.Restrictions);243 }244 245 return restrictions;246 }247 248 internal static Expression CastOrConvertMethodArgument(this DynamicMetaObject target,249 Type parameterType,250 string parameterName,251 string methodName,252 bool allowCastingToByRefLikeType,253 List<ParameterExpression> temps,254 List<Expression> initTemps)255 {256 if (target.Value == AutomationNull.Value)257 {258 return Expression.Constant(null, parameterType);259 }260 261 var argType = target.LimitType;262 if (parameterType == typeof(object) && argType == typeof(PSObject))263 {264 return Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression.Cast(typeof(PSObject)));265 }266 267 // If the conversion can't fail, skip wrapping the conversion in try/catch so we generate less code.268 if (parameterType.IsAssignableFrom(argType))269 {270 return target.Expression.Cast(parameterType);271 }272 273 ConversionRank? rank = null;274 if (parameterType.IsByRefLike && allowCastingToByRefLikeType)275 {276 var conversionResult = PSConvertBinder.ConvertToByRefLikeTypeViaCasting(target, parameterType);277 if (conversionResult != null)278 {279 return conversionResult;280 }281 282 rank = ConversionRank.None;283 }284 285 var exceptionParam = Expression.Variable(typeof(Exception));286 var targetTemp = Expression.Variable(target.Expression.Type);287 bool debase = false;288 289 // ConstrainedLanguage note - calls to this conversion are covered by the method resolution algorithm290 // (which ignores method arguments with disallowed types)291 var conversion = rank == ConversionRank.None292 ? LanguagePrimitives.NoConversion293 : LanguagePrimitives.FigureConversion(target.Value, parameterType, out debase);294 var invokeConverter = PSConvertBinder.InvokeConverter(conversion, targetTemp, parameterType, debase, ExpressionCache.InvariantCulture);295 var expr =296 Expression.Block(new[] { targetTemp },297 Expression.TryCatch(298 Expression.Block(299 Expression.Assign(targetTemp, target.Expression),300 invokeConverter),301 Expression.Catch(exceptionParam,302 Expression.Block(303 Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_ConvertToArgumentConversionException,304 exceptionParam,305 Expression.Constant(parameterName),306 targetTemp.Cast(typeof(object)),307 Expression.Constant(methodName),308 Expression.Constant(parameterType, typeof(Type))),309 Expression.Default(invokeConverter.Type)))));310 var tmp = Expression.Variable(expr.Type);311 temps.Add(tmp);312 initTemps.Add(Expression.Assign(tmp, expr));313 return tmp;314 }315 316 internal static Expression CastOrConvert(this DynamicMetaObject target, Type type)317 {318 if (target.LimitType == type)319 {320 return target.Expression.Cast(type);321 }322 323 bool debase;324 325 // ConstrainedLanguage note - calls to this conversion are done by:326 // Switch statements (always to Object), method invocation (protected by InvokeMember binder),327 // and hard-coded casts to integral types.328 var conversion = LanguagePrimitives.FigureConversion(target.Value, type, out debase);329 return PSConvertBinder.InvokeConverter(conversion, target.Expression, type, debase, ExpressionCache.InvariantCulture);330 }331 332 internal static DynamicMetaObject ThrowRuntimeError(this DynamicMetaObject target, DynamicMetaObject[] args,333 BindingRestrictions moreTests, string errorID,334 string resourceString, params Expression[] exceptionArgs)335 {336 return new DynamicMetaObject(Compiler.ThrowRuntimeError(errorID, resourceString, exceptionArgs),337 target.CombineRestrictions(args).Merge(moreTests));338 }339 340 internal static DynamicMetaObject ThrowRuntimeError(this DynamicMetaObject target, BindingRestrictions bindingRestrictions,341 string errorID, string resourceString, params Expression[] exceptionArgs)342 {343 return new DynamicMetaObject(Compiler.ThrowRuntimeError(errorID, resourceString, exceptionArgs),344 bindingRestrictions);345 }346 347#if ENABLE_BINDER_DEBUG_LOGGING348 internal static string ToDebugString(this BindingRestrictions restrictions)349 {350 return restrictions.ToExpression().ToDebugString();351 }352#endif353 }354 355 internal static class DynamicMetaObjectBinderExtensions356 {357 internal static DynamicMetaObject DeferForPSObject(this DynamicMetaObjectBinder binder, DynamicMetaObject target, bool targetIsComObject = false)358 {359 Diagnostics.Assert(target.Value is PSObject, "target must be a psobject");360 361 BindingRestrictions restrictions = BindingRestrictions.Empty;362 Expression expr = ProcessOnePSObject(target, ref restrictions, argIsComObject: targetIsComObject);363 return new DynamicMetaObject(DynamicExpression.Dynamic(binder, binder.ReturnType, expr), restrictions);364 }365 366 internal static DynamicMetaObject DeferForPSObject(this DynamicMetaObjectBinder binder, DynamicMetaObject target, DynamicMetaObject arg, bool targetIsComObject = false)367 {368 Diagnostics.Assert(target.Value is PSObject || arg.Value is PSObject, "At least one arg must be a psobject");369 370 BindingRestrictions restrictions = BindingRestrictions.Empty;371 Expression expr1 = ProcessOnePSObject(target, ref restrictions, argIsComObject: targetIsComObject);372 Expression expr2 = ProcessOnePSObject(arg, ref restrictions, argIsComObject: false);373 return new DynamicMetaObject(DynamicExpression.Dynamic(binder, binder.ReturnType, expr1, expr2), restrictions);374 }375 376 internal static DynamicMetaObject DeferForPSObject(this DynamicMetaObjectBinder binder, DynamicMetaObject[] args, bool targetIsComObject = false)377 {378 Diagnostics.Assert(args != null && args.Length > 0, "args should not be null or empty");379 Diagnostics.Assert(args.Any(mo => mo.Value is PSObject), "At least one arg must be a psobject");380 381 Expression[] exprs = new Expression[args.Length];382 BindingRestrictions restrictions = BindingRestrictions.Empty;383 384 // Target maps to arg[0] of the binder.385 exprs[0] = ProcessOnePSObject(args[0], ref restrictions, targetIsComObject);386 for (int i = 1; i < args.Length; i++)387 {388 exprs[i] = ProcessOnePSObject(args[i], ref restrictions, argIsComObject: false);389 }390 391 return new DynamicMetaObject(DynamicExpression.Dynamic(binder, binder.ReturnType, exprs), restrictions);392 }393 394 private static Expression ProcessOnePSObject(DynamicMetaObject arg, ref BindingRestrictions restrictions, bool argIsComObject = false)395 {396 Expression expr = null;397 object baseValue = PSObject.Base(arg.Value);398 if (baseValue != arg.Value)399 {400 expr = Expression.Call(CachedReflectionInfo.PSObject_Base, arg.Expression.Cast(typeof(object)));401 402 if (argIsComObject)403 {404 // The 'base' is a COM object, so bake that in the rule.405 restrictions = restrictions406 .Merge(arg.GetSimpleTypeRestriction())407 .Merge(BindingRestrictions.GetExpressionRestriction(Expression.Call(CachedReflectionInfo.Utils_IsComObject, expr)));408 }409 else410 {411 // Use a more general condition for the rule: 'arg' is a PSObject and 'base != arg'.412 restrictions = restrictions413 .Merge(arg.GetSimpleTypeRestriction())414 .Merge(BindingRestrictions.GetExpressionRestriction(Expression.NotEqual(expr, arg.Expression)));415 }416 }417 else418 {419 expr = arg.Expression;420 restrictions = restrictions.Merge(arg.PSGetTypeRestriction());421 }422 423 return expr;424 }425 426 internal static DynamicMetaObject UpdateComRestrictionsForPsObject(this DynamicMetaObject binder, DynamicMetaObject[] args)427 {428 // Add a restriction that prevents PSObject arguments (so that they get based)429 BindingRestrictions newRestrictions = binder.Restrictions;430 newRestrictions = args.Aggregate(newRestrictions, (current, arg) =>431 {432 if (arg.LimitType.IsValueType)433 {434 return current.Merge(arg.GetSimpleTypeRestriction());435 }436 else437 {438 return current.Merge(BindingRestrictions.GetExpressionRestriction(439 Expression.Equal(440 Expression.Call(CachedReflectionInfo.PSObject_Base, arg.Expression),441 arg.Expression)));442 }443 });444 445 return new DynamicMetaObject(binder.Expression, newRestrictions);446 }447 }448 449 internal static class BinderUtils450 {451 internal static BindingRestrictions GetVersionCheck(DynamicMetaObjectBinder binder, int expectedVersionNumber)452 {453 return BindingRestrictions.GetExpressionRestriction(454 Expression.Equal(Expression.Field(Expression.Constant(binder), "_version"),455 ExpressionCache.Constant(expectedVersionNumber)));456 }457 458 internal static BindingRestrictions GetLanguageModeCheckIfHasEverUsedConstrainedLanguage()459 {460 // Also add a language mode check to detect toggling between language modes461 if (ExecutionContext.HasEverUsedConstrainedLanguage)462 {463 var context = LocalPipeline.GetExecutionContextFromTLS();464 465 var tmp = Expression.Variable(typeof(ExecutionContext));466 var langModeFromContext = Expression.Property(tmp, CachedReflectionInfo.ExecutionContext_LanguageMode);467 var constrainedLanguageMode = Expression.Constant(PSLanguageMode.ConstrainedLanguage);468 469 // Execution context might be null if we're called from a thread with no runspace (e.g. a PSObject470 // is used in some C# w/ dynamic). This is sometimes fine, we don't always need a runspace to access471 // properties.472 Expression test = context?.LanguageMode == PSLanguageMode.ConstrainedLanguage473 ? Expression.AndAlso(474 Expression.NotEqual(tmp, ExpressionCache.NullExecutionContext),475 Expression.Equal(langModeFromContext, constrainedLanguageMode))476 : Expression.OrElse(477 Expression.Equal(tmp, ExpressionCache.NullExecutionContext),478 Expression.NotEqual(langModeFromContext, constrainedLanguageMode));479 480 return BindingRestrictions.GetExpressionRestriction(481 Expression.Block(482 new[] { tmp },483 Expression.Assign(tmp, ExpressionCache.GetExecutionContextFromTLS),484 test));485 }486 487 return BindingRestrictions.Empty;488 }489 490 internal static BindingRestrictions GetOptionalVersionAndLanguageCheckForType(DynamicMetaObjectBinder binder, Type targetType, int expectedVersionNumber)491 {492 BindingRestrictions additionalBindingRestrictions = BindingRestrictions.Empty;493 494 // If this uses a potentially unsafe type, we also need a version check495 if (!CoreTypes.Contains(targetType))496 {497 if (expectedVersionNumber != -1)498 {499 additionalBindingRestrictions = additionalBindingRestrictions.Merge(BinderUtils.GetVersionCheck(binder, expectedVersionNumber));500 }501 502 additionalBindingRestrictions = additionalBindingRestrictions.Merge(GetLanguageModeCheckIfHasEverUsedConstrainedLanguage());503 }504 505 return additionalBindingRestrictions;506 }507 }508 509 #region PowerShell non-standard language binders510 511 /// <summary>512 /// Some classes that implement IEnumerable are not considered as enumerable from the perspective of pipelines,513 /// this binder implements those semantics.514 ///515 /// The standard interop ConvertBinder is used to allow third party dynamic objects to get the first chance516 /// at the conversion in case they do support enumeration, but do not implement IEnumerable directly.517 /// </summary>518 internal sealed class PSEnumerableBinder : ConvertBinder519 {520 private static readonly PSEnumerableBinder s_binder = new PSEnumerableBinder();521 522 internal static PSEnumerableBinder Get()523 {524 return s_binder;525 }526 527 private PSEnumerableBinder()528 : base(typeof(IEnumerator), false)529 {530 CacheTarget((Func<CallSite, object, IEnumerator>)(PSObjectStringRule));531 CacheTarget((Func<CallSite, object, IEnumerator>)(ArrayRule));532 CacheTarget((Func<CallSite, object, IEnumerator>)(StringRule));533 CacheTarget((Func<CallSite, object, IEnumerator>)(NotEnumerableRule));534 CacheTarget((Func<CallSite, object, IEnumerator>)(PSObjectNotEnumerableRule));535 CacheTarget((Func<CallSite, object, IEnumerator>)(AutomationNullRule));536 }537 538 public override string ToString()539 {540 return "ToEnumerable";541 }542 543 internal static BindingRestrictions GetRestrictions(DynamicMetaObject target)544 {545 return (target.Value is PSObject)546 ? BindingRestrictions.GetTypeRestriction(target.Expression, target.Value.GetType())547 : target.PSGetTypeRestriction();548 }549 550 private DynamicMetaObject NullResult(DynamicMetaObject target)551 {552 // The object is not enumerable from PowerShell's perspective. Rather than raise an exception, we let the553 // caller check for null and take the appropriate action.554 return new DynamicMetaObject(555 MaybeDebase(this, static e => ExpressionCache.NullEnumerator, target),556 GetRestrictions(target));557 }558 559 internal static Expression MaybeDebase(DynamicMetaObjectBinder binder, Func<Expression, Expression> generator, DynamicMetaObject target)560 {561 if (target.Value is not PSObject)562 {563 return generator(target.Expression);564 }565 566 object targetValue = PSObject.Base(target.Value);567 568 var tmp = Expression.Parameter(typeof(object), "value");569 return Expression.Block(570 new ParameterExpression[] { tmp },571 Expression.Assign(tmp, Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression)),572 Expression.Condition(573 targetValue == null ?574 (Expression)Expression.AndAlso(Expression.Equal(tmp, ExpressionCache.NullConstant),575 Expression.Not(Expression.Equal(target.Expression, ExpressionCache.AutomationNullConstant)))576 : Expression.TypeEqual(tmp, targetValue.GetType()),577 generator(tmp), binder.GetUpdateExpression(binder.ReturnType)));578 }579 580 public override DynamicMetaObject FallbackConvert(DynamicMetaObject target, DynamicMetaObject errorSuggestion)581 {582 if (!target.HasValue)583 {584 return Defer(target).WriteToDebugLog(this);585 }586 587 if (target.Value == AutomationNull.Value)588 {589 return new DynamicMetaObject(590 Expression.Call(Expression.Constant(Array.Empty<object>()), typeof(Array).GetMethod("GetEnumerator")),591 BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value)).WriteToDebugLog(this);592 }593 594 var targetValue = PSObject.Base(target.Value);595 596 if (targetValue == null || targetValue is string || targetValue is PSObject)597 {598 return (errorSuggestion ?? NullResult(target)).WriteToDebugLog(this);599 }600 601 if (targetValue.GetType().IsArray)602 {603 return (new DynamicMetaObject(604 MaybeDebase(this, static e => Expression.Call(Expression.Convert(e, typeof(Array)), typeof(Array).GetMethod("GetEnumerator")),605 target),606 GetRestrictions(target))).WriteToDebugLog(this);607 }608 609 if (targetValue is IDictionary || targetValue is XmlNode)610 {611 return (errorSuggestion ?? NullResult(target)).WriteToDebugLog(this);612 }613 614 if (targetValue is DataTable)615 {616 // Generate:617 //618 // DataRowCollection rows;619 // DataTable table = (DataTable)obj;620 // if ((rows = table.Rows) != null)621 // return rows.GetEnumerator();622 // else623 // return null;624 625 return (new DynamicMetaObject(626 MaybeDebase(this, e =>627 {628 var table = Expression.Parameter(typeof(DataTable), "table");629 var rows = Expression.Parameter(typeof(DataRowCollection), "rows");630 return Expression.Block(new ParameterExpression[] { table, rows },631 Expression.Assign(table, e.Cast(typeof(DataTable))),632 Expression.Condition(633 Expression.NotEqual(Expression.Assign(rows, Expression.Property(table, "Rows")), ExpressionCache.NullConstant),634 Expression.Call(rows, typeof(DataRowCollection).GetMethod("GetEnumerator")),635 ExpressionCache.NullEnumerator));636 },637 target),638 GetRestrictions(target))).WriteToDebugLog(this);639 }640 641 if (Marshal.IsComObject(targetValue))642 {643 // Pretend that all com objects are enumerable, even if they aren't. We do this because it's technically impossible644 // to know if a com object is enumerable without just trying to cast it to IEnumerable. We could generate a rule like:645 //646 // if (IsComObject(obj)) { return obj as IEnumerable; } else { UpdateSite; }647 //648 // But code that calls PSEnumerableBinder.IsEnumerable and generate code based on the true/false result of that649 // function wouldn't work properly. Instead, we'll fix things up after the binding decisions are made, see650 // EnumerableOps.NonEnumerableObjectEnumerator for more comments on how this works.651 652 var bindingRestrictions = BindingRestrictions.GetExpressionRestriction(653 Expression.Call(CachedReflectionInfo.Utils_IsComObject,654 Expression.Call(CachedReflectionInfo.PSObject_Base, target.Expression)));655 return new DynamicMetaObject(656 Expression.Call(CachedReflectionInfo.EnumerableOps_GetCOMEnumerator, target.Expression), bindingRestrictions).WriteToDebugLog(this);657 }658 659 var enumerable = targetValue as IEnumerable;660 if (enumerable != null)661 {662 // Normally it's safe to just call IEnumerable.GetEnumerator, but in some rare cases, the663 // non-generic implementation throws or returns null, so we'll just avoid that problem and664 // call the generic version if it exists.665 666 foreach (var i in targetValue.GetType().GetInterfaces())667 {668 if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))669 {670 return (new DynamicMetaObject(671 MaybeDebase(this, e => Expression.Call(672 CachedReflectionInfo.EnumerableOps_GetGenericEnumerator.MakeGenericMethod(i.GetGenericArguments()[0]), Expression.Convert(e, i)),673 target),674 GetRestrictions(target))).WriteToDebugLog(this);675 }676 }677 678 return (new DynamicMetaObject(679 MaybeDebase(this, static e => Expression.Call(CachedReflectionInfo.EnumerableOps_GetEnumerator, Expression.Convert(e, typeof(IEnumerable))),680 target),681 GetRestrictions(target))).WriteToDebugLog(this);682 }683 684 var enumerator = targetValue as IEnumerator;685 if (enumerator != null)686 {687 return (new DynamicMetaObject(688 MaybeDebase(this, static e => e.Cast(typeof(IEnumerator)), target),689 GetRestrictions(target))).WriteToDebugLog(this);690 }691 692 return (errorSuggestion ?? NullResult(target)).WriteToDebugLog(this);693 }694 695 /// <summary>696 /// Check if the statically known type is potentially enumerable. We can avoid some dynamic sites if we know the697 /// type is never enumerable.698 /// </summary>699 internal static bool IsStaticTypePossiblyEnumerable(Type type)700 {701 if (type == typeof(object) || type == typeof(PSObject) || type.IsArray)702 {703 return true;704 }705 706 if (type == typeof(string) || typeof(IDictionary).IsAssignableFrom(type) || typeof(XmlNode).IsAssignableFrom(type))707 {708 return false;709 }710 711 if (type.IsSealed && !typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IEnumerator).IsAssignableFrom(type))712 {713 return false;714 }715 716 return true;717 }718 719 // Binders normally cannot return null, but we want a way to detect if something is enumerable,720 // so we return null if the target is not enumerable.721 internal static DynamicMetaObject IsEnumerable(DynamicMetaObject target)722 {723 var binder = PSEnumerableBinder.Get();724 var result = binder.FallbackConvert(target, DynamicMetaObjectExtensions.FakeError);725 return (result == DynamicMetaObjectExtensions.FakeError) ? null : result;726 }727 728 private static IEnumerator AutomationNullRule(CallSite site, object obj)729 {730 return obj == AutomationNull.Value731 ? Array.Empty<object>().GetEnumerator()732 : ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);733 }734 735 private static IEnumerator NotEnumerableRule(CallSite site, object obj)736 {737 if (obj == null)738 {739 return null;740 }741 742 if (obj is not PSObject743 && obj is not IEnumerable744 && obj is not IEnumerator745 && obj is not DataTable746 && !Marshal.IsComObject(obj))747 {748 return null;749 }750 751 return ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);752 }753 754 private static IEnumerator PSObjectNotEnumerableRule(CallSite site, object obj)755 {756 var psobj = obj as PSObject;757 return psobj != null && obj != AutomationNull.Value758 ? NotEnumerableRule(site, PSObject.Base(obj))759 : ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);760 }761 762 private static IEnumerator ArrayRule(CallSite site, object obj)763 {764 var array = obj as Array;765 if (array != null) return array.GetEnumerator();766 return ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);767 }768 769 private static IEnumerator StringRule(CallSite site, object obj)770 {771 return obj is string ? null : ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);772 }773 774 private static IEnumerator PSObjectStringRule(CallSite site, object obj)775 {776 var psobj = obj as PSObject;777 if (psobj != null && PSObject.Base(psobj) is string) return null;778 return ((CallSite<Func<CallSite, object, IEnumerator>>)site).Update(site, obj);779 }780 }781 782 /// <summary>783 /// This binder is used for the @() operator.784 /// </summary>785 internal sealed class PSToObjectArrayBinder : DynamicMetaObjectBinder786 {787 private static readonly PSToObjectArrayBinder s_binder = new PSToObjectArrayBinder();788 789 internal static PSToObjectArrayBinder Get()790 {791 return s_binder;792 }793 794 private PSToObjectArrayBinder()795 {796 }797 798 public override string ToString()799 {800 return "ToObjectArray";801 }802 803 public override Type ReturnType { get { return typeof(object[]); } }804 805 public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)806 {807 if (!target.HasValue)808 {809 return Defer(target, args);810 }811 812 if (target.Value == AutomationNull.Value)813 {814 return new DynamicMetaObject(Expression.Constant(Array.Empty<object>()),815 BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value)).WriteToDebugLog(this);816 }817 818 var enumerable = PSEnumerableBinder.IsEnumerable(target);819 if (enumerable == null)820 {821 return new DynamicMetaObject(822 Expression.NewArrayInit(typeof(object), target.Expression.Cast(typeof(object))),823 target.PSGetTypeRestriction()).WriteToDebugLog(this);824 }825 826 var value = PSObject.Base(target.Value);827 if (value is List<object>)828 {829 return new DynamicMetaObject(830 Expression.Call(PSEnumerableBinder.MaybeDebase(this, static e => e.Cast(typeof(List<object>)), target), CachedReflectionInfo.ObjectList_ToArray),831 PSEnumerableBinder.GetRestrictions(target)).WriteToDebugLog(this);832 }833 834 // It's enumerable, but not an List<object>. Call EnumerableOps.ToArray835 return new DynamicMetaObject(836 Expression.Call(CachedReflectionInfo.EnumerableOps_ToArray, enumerable.Expression),837 target.PSGetTypeRestriction()).WriteToDebugLog(this);838 }839 }840 841 internal sealed class PSPipeWriterBinder : DynamicMetaObjectBinder842 {843 private static readonly PSPipeWriterBinder s_binder = new PSPipeWriterBinder();844 845 internal static PSPipeWriterBinder Get()846 {847 return s_binder;848 }849 850 private PSPipeWriterBinder()851 {852 CacheTarget((Action<CallSite, object, Pipe, ExecutionContext>)StringRule);853 CacheTarget((Action<CallSite, object, Pipe, ExecutionContext>)AutomationNullRule);854 CacheTarget((Action<CallSite, object, Pipe, ExecutionContext>)BoolRule);855 CacheTarget((Action<CallSite, object, Pipe, ExecutionContext>)IntRule);856 }857 858 public override string ToString()859 {860 return "PipelineWriter";861 }862 863 public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)864 {865 // args[0] is the pipe866 // args[1] is the execution context, only used if we're enumerating867 868 if (!target.HasValue)869 {870 return Defer(target, args);871 }872 873 if (target.Value == AutomationNull.Value)874 {875 return (new DynamicMetaObject(876 Expression.Block(typeof(void), Expression.Call(CachedReflectionInfo.PipelineOps_Nop)),877 BindingRestrictions.GetInstanceRestriction(target.Expression, AutomationNull.Value))).WriteToDebugLog(this);878 }879 880 var enumerable = PSEnumerableBinder.IsEnumerable(target);881 if (enumerable == null)882 {883 var bindingResult = PSVariableAssignmentBinder.Get().Bind(target, Array.Empty<DynamicMetaObject>());884 var restrictions = target.LimitType.IsValueType885 ? bindingResult.Restrictions886 : target.PSGetTypeRestriction();887 return (new DynamicMetaObject(888 Expression.Call(args[0].Expression,889 CachedReflectionInfo.Pipe_Add,890 bindingResult.Expression.Cast(typeof(object))),891 restrictions)).WriteToDebugLog(this);892 }893 894 bool needsToDispose = PSObject.Base(target.Value) is not IEnumerator;895 return (new DynamicMetaObject(896 Expression.Call(CachedReflectionInfo.EnumerableOps_WriteEnumerableToPipe,897 enumerable.Expression,898 args[0].Expression,899 args[1].Expression,900 ExpressionCache.Constant(needsToDispose)),901 enumerable.Restrictions)).WriteToDebugLog(this);902 }903 904 private static void BoolRule(CallSite site, object obj, Pipe pipe, ExecutionContext context)905 {906 if (obj is bool) { pipe.Add(obj); }907 else { ((CallSite<Action<CallSite, object, Pipe, ExecutionContext>>)site).Update(site, obj, pipe, context); }908 }909 910 private static void IntRule(CallSite site, object obj, Pipe pipe, ExecutionContext context)911 {912 if (obj is int) { pipe.Add(obj); }913 else { ((CallSite<Action<CallSite, object, Pipe, ExecutionContext>>)site).Update(site, obj, pipe, context); }914 }915 916 private static void StringRule(CallSite site, object obj, Pipe pipe, ExecutionContext context)917 {918 if (obj is string) { pipe.Add(obj); }919 else { ((CallSite<Action<CallSite, object, Pipe, ExecutionContext>>)site).Update(site, obj, pipe, context); }920 }921 922 private static void AutomationNullRule(CallSite site, object obj, Pipe pipe, ExecutionContext context)923 {924 if (obj != AutomationNull.Value) { ((CallSite<Action<CallSite, object, Pipe, ExecutionContext>>)site).Update(site, obj, pipe, context); }925 }926 }927 928 /// <summary>929 /// This binder creates the collection we use to do multi-assignments, e.g.:930 /// $x,$y = $z931 /// $x,$y,$z = 1,2,3,4,5932 /// The target in this binder is the RHS, the result expression is an IList where the Count matches the933 /// number of values assigned (_elements) on the left hand side of the assign.934 /// </summary>935 internal sealed class PSArrayAssignmentRHSBinder : DynamicMetaObjectBinder936 {937 private static readonly List<PSArrayAssignmentRHSBinder> s_binders = new List<PSArrayAssignmentRHSBinder>();938 private readonly int _elements;939 940 internal static PSArrayAssignmentRHSBinder Get(int i)941 {942 lock (s_binders)943 {944 while (s_binders.Count <= i)945 {946 s_binders.Add(null);947 }948 949 return s_binders[i] ?? (s_binders[i] = new PSArrayAssignmentRHSBinder(i));950 }951 }952 953 private PSArrayAssignmentRHSBinder(int elements)954 {955 _elements = elements;956 }957 958 public override string ToString()959 {960 return string.Create(CultureInfo.InvariantCulture, $"MultiAssignRHSBinder {_elements}");961 }962 963 public override Type ReturnType { get { return typeof(IList); } }964 965 public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)966 {967 Diagnostics.Assert(args.Length == 0, "binder doesn't expect any arguments");968 969 if (!target.HasValue)970 {971 return Defer(target).WriteToDebugLog(this);972 }973 974 if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))975 {976 return this.DeferForPSObject(target).WriteToDebugLog(this);977 }978 979 var iList = target.Value as IList;980 if (iList != null)981 {982 // 3 possibilities - too few, exact, or too many elements.983 984 var getListCountExpr = Expression.Property(target.Expression.Cast(typeof(ICollection)), CachedReflectionInfo.ICollection_Count);985 986 var restrictions = target.PSGetTypeRestriction().Merge(987 BindingRestrictions.GetExpressionRestriction(Expression.Equal(getListCountExpr,988 ExpressionCache.Constant(iList.Count))));989 990 if (iList.Count == _elements)991 {992 // Exact, we can use the value as is.993 return new DynamicMetaObject(target.Expression.Cast(typeof(IList)), restrictions).WriteToDebugLog(this);994 }995 996 int i;997 Expression[] newArrayElements = new Expression[_elements];998 var temp = Expression.Variable(typeof(IList));999 if (iList.Count < _elements)1000 {1001 // Too few, create an array with the correct number assigned, fill in null for the extras1002 1003 for (i = 0; i < iList.Count; ++i)1004 {1005 newArrayElements[i] =1006 Expression.Call(temp, CachedReflectionInfo.IList_get_Item, ExpressionCache.Constant(i));1007 }1008 1009 for (; i < _elements; ++i)1010 {1011 newArrayElements[i] = ExpressionCache.NullConstant;1012 }1013 }1014 else1015 {1016 // Too many, create an array with the correct number assigned, and the last element contains1017 // all the extras.1018 for (i = 0; i < _elements - 1; ++i)1019 {1020 newArrayElements[i] =1021 Expression.Call(temp, CachedReflectionInfo.IList_get_Item, ExpressionCache.Constant(i));1022 }1023 1024 newArrayElements[_elements - 1] =1025 Expression.Call(CachedReflectionInfo.EnumerableOps_GetSlice, temp, ExpressionCache.Constant(_elements - 1)).Cast(typeof(object));1026 }1027 1028 return (new DynamicMetaObject(1029 Expression.Block(1030 new[] { temp },1031 Expression.Assign(temp, target.Expression.Cast(typeof(IList))),1032 Expression.NewArrayInit(typeof(object), newArrayElements)),1033 restrictions)).WriteToDebugLog(this);1034 }1035 1036 // We have a single element, the rest must be null.1037 return (new DynamicMetaObject(1038 Expression.NewArrayInit(typeof(object), Enumerable.Repeat(ExpressionCache.NullConstant, _elements - 1)1039 .Prepend(target.Expression.Cast(typeof(object)))),1040 target.PSGetTypeRestriction())).WriteToDebugLog(this);1041 }1042 }1043 1044 /// <summary>1045 /// This binder is used to convert objects to string in specific circumstances, including:1046 ///1047 /// * The LHS of a format expression. The arguments (the RHS objects) of the format1048 /// expression are not converted to string here, that is deferred to string.Format which1049 /// may have some custom formatting to apply.1050 /// * The objects passed to the format expression as part of an expandable string. In this1051 /// case, the format string is generated by the parser, so we know that there is no custom1052 /// formatting to consider.1053 /// </summary>1054 internal sealed class PSToStringBinder : DynamicMetaObjectBinder1055 {1056 private static readonly PSToStringBinder s_binder = new PSToStringBinder();1057 1058 internal static PSToStringBinder Get()1059 {1060 return s_binder;1061 }1062 1063 public override Type ReturnType { get { return typeof(string); } }1064 1065 private PSToStringBinder()1066 {1067 }1068 1069 public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)1070 {1071 // target is the object to convert1072 // args[0] is the ExecutionContext, needed to get $OFS, and possibly the type table.1073 1074 if (!target.HasValue || !args[0].HasValue)1075 {1076 return Defer(target, args).WriteToDebugLog(this);1077 }1078 1079 if (target.Value is PSObject && (PSObject.Base(target.Value) != target.Value))1080 {1081 return this.DeferForPSObject(target, args[0]).WriteToDebugLog(this);1082 }1083 1084 var restrictions = target.PSGetTypeRestriction();1085 if (target.LimitType == typeof(string))1086 {1087 return (new DynamicMetaObject(1088 target.Expression.Cast(typeof(string)),1089 restrictions)).WriteToDebugLog(this);1090 }1091 1092 // PSObject.ToStringParser will handle everything, but if you want to speed up conversion to string,1093 // add special cases here.1094 1095 return (new DynamicMetaObject(1096 InvokeToString(args[0].Expression, target.Expression),1097 restrictions)).WriteToDebugLog(this);1098 }1099 1100 internal static Expression InvokeToString(Expression context, Expression target)1101 {1102 if (target.Type == typeof(string))1103 {1104 return target;1105 }1106 1107 // PSObject.ToStringParser will handle everything, but if you want to speed up conversion to string,1108 // add special cases here.1109 1110 return Expression.Call(CachedReflectionInfo.PSObject_ToStringParser,1111 context.Cast(typeof(ExecutionContext)),1112 target.Cast(typeof(object)));1113 }1114 }1115 1116 /// <summary>1117 /// This binder is used to optimize the conversion of the result.1118 /// </summary>1119 internal sealed class PSPipelineResultToBoolBinder : DynamicMetaObjectBinder1120 {1121 private static readonly PSPipelineResultToBoolBinder s_binder = new PSPipelineResultToBoolBinder();1122 1123 internal static PSPipelineResultToBoolBinder Get()1124 {1125 return s_binder;1126 }1127 1128 private PSPipelineResultToBoolBinder()1129 {1130 }1131 1132 public override Type ReturnType { get { return typeof(bool); } }1133 1134 public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args)1135 {1136 if (!target.HasValue)1137 {1138 return Defer(target).WriteToDebugLog(this);1139 }1140 1141 IList pipelineResult = target.Value as IList;1142 Diagnostics.Assert(pipelineResult != null, "Pipeline result is always an IList");1143 1144 var ilistExpr = target.Expression;1145 if (typeof(IList) != ilistExpr.Type)1146 {1147 ilistExpr = Expression.Convert(ilistExpr, typeof(IList));1148 }1149 1150 var countExpr = Expression.Property(1151 Expression.Convert(ilistExpr, typeof(ICollection)), CachedReflectionInfo.ICollection_Count);1152 1153 Expression result;1154 BindingRestrictions restrictions;1155 switch (pipelineResult.Count)1156 {1157 case 0:1158 result = ExpressionCache.Constant(false);1159 restrictions = BindingRestrictions.GetExpressionRestriction(1160 Expression.Equal(countExpr, ExpressionCache.Constant(0)));1161 break;1162 case 1:1163 result = Expression.Call(ilistExpr,1164 CachedReflectionInfo.IList_get_Item,1165 ExpressionCache.Constant(0)).Convert(typeof(bool));1166 restrictions = BindingRestrictions.GetExpressionRestriction(1167 Expression.Equal(countExpr, ExpressionCache.Constant(1)));1168 break;1169 default:1170 result = ExpressionCache.Constant(true);1171 restrictions = BindingRestrictions.GetExpressionRestriction(1172 Expression.GreaterThan(countExpr, ExpressionCache.Constant(1)));1173 break;1174 }1175 1176 return (new DynamicMetaObject(result, restrictions)).WriteToDebugLog(this);1177 }1178 }1179 1180 internal sealed class PSInvokeDynamicMemberBinder : DynamicMetaObjectBinder1181 {1182 private sealed class KeyComparer : IEqualityComparer<PSInvokeDynamicMemberBinderKeyType>1183 {1184 public bool Equals(PSInvokeDynamicMemberBinderKeyType x, PSInvokeDynamicMemberBinderKeyType y)1185 {1186 return x.Item1.Equals(y.Item1) &&1187 ((x.Item2 == null) ? y.Item2 == null : x.Item2.Equals(y.Item2)) &&1188 x.Item3 == y.Item3 &&1189 x.Item4 == y.Item4 &&1190 x.Item5 == y.Item5;1191 }1192 1193 public int GetHashCode(PSInvokeDynamicMemberBinderKeyType obj)1194 {1195 return obj.GetHashCode();1196 }1197 }1198 1199 private static readonly1200 Dictionary<PSInvokeDynamicMemberBinderKeyType, PSInvokeDynamicMemberBinder> s_binderCache