diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 673420e..c0545d2 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -350,7 +350,7 @@ priorities. delivered for ordinary, adjacent, quoted, here-string, redirect, standalone, call-operator, dynamic-identity, and host-wrapper positions, with current-scope state propagation and bounded expression rejection pinned - by the 372-entry executable corpus. + by the 383-entry executable corpus. - [ ] Deliver typed PowerShell script-block execution regions before calling tasks 7.5-7.7 complete. The corrected contract adds an execution-region syntax node with independent origin, phase, timing, and cardinality rather than a @@ -425,8 +425,27 @@ priorities. proved child-process isolation while their body analysis stays fail closed; an explicit alternate `-PSVersion` remains visible but incomplete because it falls outside the pinned PowerShell 7 runtime model. - Child runspace jobs and parallel - blocks; deferred breakpoint/event/completion actions; then unknown + `ForEach-Object -Parallel` now publishes a concurrent per-input child- + runspace region, inherits the captured caller location, starts without + caller bindings or alias mutations, and isolates runspace-local child + exit from the host continuation, including `-AsJob`. Pooled-runspace command- + resolution mutation is joined across later activations; `-UseNewRunspace` + keeps activations independent. Live PowerShell probes pin both behaviors, + including the process-wide environment-provider exception: a possibly + escaping child mutation invalidates later host binding, command- + resolution, and location facts and also poisons later child activations + under `-UseNewRunspace`, which resets runspace-local but not process-wide + state. Runspace-global variables, functions, aliases, and location remain + isolated. The analyzer retains a bounded, case-insensitive set of exact + alias/function names whose identity changed and treats only matching later + invocations as possible process escapes; an ambiguous mutation or set + overflow fails closed to every unproved command name. This includes a + mutator such as `Set-Alias` being rebound to `Set-Item` before a later + authored `Set-Alias Env:...` invocation. + The generator-owned executable corpus now supports per-entry initial- + state mode and includes the promoted Parallel design case. Pinned child + runspace jobs and remote/session invocation remain before deferred + breakpoint/event/completion actions; then unknown receiver and nested/adversarial matrices. Preserve script blocks proved to be data as opaque values, expose ambiguous bodies with incomplete facts, and fail atomically when any potentially executable interior is diff --git a/SPEC.POWERSHELL.md b/SPEC.POWERSHELL.md index e19f21f..f59906d 100644 --- a/SPEC.POWERSHELL.md +++ b/SPEC.POWERSHELL.md @@ -529,7 +529,7 @@ The version-pinned PowerShell 7 catalog covers: | `ForEach-Object -Begin` | Begin | Synchronous | Once | current runspace | | `ForEach-Object -Process` / `-RemainingScripts` | Process, with binder-assigned Begin/End where applicable | Synchronous | OncePerInputObject | current runspace | | `ForEach-Object -End` | End | Synchronous | Once | current runspace | -| `ForEach-Object -Parallel` | Process | Concurrent | OncePerInputObject | child runspace; exit isolated | +| `ForEach-Object -Parallel` | Process | Concurrent | OncePerInputObject | child runspace; runspace-local exit isolated; process-wide effects conservative | | `Where-Object -FilterScript` | Filter | Synchronous | OncePerInputObject | current runspace | | in-process `Invoke-Command -ScriptBlock` | Main | Synchronous | Once | child scope unless `-NoNewScope`; shared location | | remote/session/SSH/VM/container `Invoke-Command` | Main | proved from complete parameter set | Unknown unless targets are proved | remote/child state; exit isolated | @@ -540,6 +540,22 @@ The version-pinned PowerShell 7 catalog covers: | `Set-PSBreakpoint -Action`, event `-Action` | Action | Deferred | ZeroOrMore | trigger-time state Unknown without proof | | `Register-ArgumentCompleter -ScriptBlock` | Completion | Deferred | ZeroOrMore | completion-time state Unknown without proof | +Parallel child runspaces do not inherit ordinary caller variables or aliases, +and their runspace-local variable and location exit state does not flow into the +host continuation. They do share process-wide state such as the environment +provider. A supported or unknown child mutation that may affect such state +invalidates later host binding, command-resolution, and location facts. It also +invalidates later child-activation facts even with `-UseNewRunspace`, because a +fresh runspace is not a fresh process. +Runspace `global:` variable, function, alias, and location state remains local +to that runspace and does not by itself invalidate the host. Once child command +resolution is mutated, however, an exact changed alias or function name is +retained in bounded case-insensitive state. A later matching invocation whose +identity is no longer proved is a possible process-wide mutation and receives +the same conservative invalidation unless its identity is independently proved. +An ambiguous changed name, wildcard, or candidate-set overflow collapses to all +unproved command names rather than guessing. + The optional inbox `Microsoft.PowerShell.ThreadJob\Start-ThreadJob` follows the initialization/main child-runspace model only when the caller's pinned module baseline proves that identity. Otherwise it follows the unknown diff --git a/openspec/changes/v0-3-structured-shell-analysis/design.md b/openspec/changes/v0-3-structured-shell-analysis/design.md index 6178460..32a7527 100644 --- a/openspec/changes/v0-3-structured-shell-analysis/design.md +++ b/openspec/changes/v0-3-structured-shell-analysis/design.md @@ -374,7 +374,7 @@ PowerShell 7.6.4 probes demonstrate that these are independent dimensions: | local `Invoke-Command { ... }` | child scope unless `-NoNewScope` | shared | synchronous / once | | `Measure-Command { ... }` / `Trace-Command -Expression { ... }` | shared current scope | shared | synchronous / once | | `Start-Job { ... }` | child process state | inherited initial location; exit isolated | concurrent / once | -| `ForEach-Object -Parallel { ... }` | child runspace state | inherited initial location; exit isolated | concurrent / once per input | +| `ForEach-Object -Parallel { ... }` | child runspace state; process-wide effects may escape | inherited initial location; runspace-local exit isolated | concurrent / once per input | | event, breakpoint, completion actions | trigger-time state | trigger-time state | deferred / zero or more | The shell-specific analyzer owns inbound state, exit propagation, phase @@ -383,6 +383,22 @@ Occurrence facts expose the effective result. This keeps the public structural API narrow while avoiding the false claim that variables and location always share one boundary. +An in-process child runspace is not a process boundary. Ordinary variables, +aliases, and PowerShell location are runspace-local, but environment-provider +mutation is process-wide and can affect the host and sibling runspaces. The +analyzer therefore invalidates later host binding, command-resolution, and +location facts after any possibly escaping child effect. The same invalidation +applies to later activations under `-UseNewRunspace`: the switch removes pooled +runspace-local state, but it does not create a separate process. +Runspace `global:` variables, functions, aliases, and location remain +runspace-local and do not themselves poison host facts. Exact alias/function +names changed inside a child are retained in a bounded, case-insensitive set; +only a matching subsequent invocation whose identity is not independently +proved is treated as a possible process-wide mutation. Ambiguous names, +wildcards, and set overflow collapse to all unproved command names. This +prevents both an alias to `Set-Item` and a rebound `Set-Alias` mutator from +hiding an environment-provider write without tainting unrelated names. + `SimpleCommandSyntax.ExecutionRegions` preserves authored script-block order. The analyzer separately applies semantic phase order. PowerShell's binder can assign Begin, Process, and End roles to multiple `ForEach-Object` blocks even diff --git a/openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md b/openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md index b530503..c9990b0 100644 --- a/openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md +++ b/openspec/changes/v0-3-structured-shell-analysis/specs/bounded-shell-analysis/spec.md @@ -668,7 +668,19 @@ from the complete parameter set. `Start-Job` executes initialization before its main block in a child process; `ForEach-Object -Parallel` and a proved `Start-ThreadJob` execute in child -runspaces. Their exit mutation does not flow into the containing continuation. +runspaces. Runspace-local variable and location exit mutation does not flow into +the containing continuation. In-process child runspaces share process-wide +state such as the environment provider, so a possibly escaping mutation SHALL +invalidate later host binding, command-resolution, and location facts. It SHALL +also invalidate later child-activation facts under `-UseNewRunspace`, which +creates a fresh runspace but not a fresh process. +Runspace `global:` variables, functions, aliases, and location SHALL remain +runspace-local and SHALL NOT by themselves invalidate host facts. After child +command resolution is mutated, each exact changed alias or function name SHALL +be retained in bounded case-insensitive state. A later matching invocation +whose identity is not independently proved SHALL be treated as a possible +process-wide mutation. An ambiguous name, wildcard, or candidate-set overflow +SHALL fail closed to every unproved command name. Breakpoint actions, event actions, and argument completers are deferred and may execute zero or more times; their trigger-time cwd and mutable state are Unknown unless independently proved. Deferred commands remain in the diff --git a/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs b/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs index 730c487..05c4686 100644 --- a/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs +++ b/src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs @@ -486,6 +486,581 @@ internal static bool MayEscapeChildScope( return !hasLocalProviderTarget; } + internal static bool MayEscapeChildRunspaceProcess( + Clause clause, + IReadOnlyList effectiveArguments) + { + var verb = GetCanonicalVerb(clause); + if (verb is null) + { + return true; + } + + if (IsInvokeExpression(verb) || + verb.Equals("Import-Module", StringComparison.OrdinalIgnoreCase) || + verb.Equals("New-Module", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Import-PSSession", StringComparison.OrdinalIgnoreCase)) + { + // These commands can execute module or expression code in-process. + return true; + } + + return IsProviderStateMutation( + verb, + clause, + effectiveArguments, + failOnUnproved: true) && + HasMutableOrUnprovedProviderTarget( + verb, + clause, + effectiveArguments, + failOnUnproved: true, + selection: StateProviderSelection.Environment); + } + + internal static bool TryGetCommandResolutionMutation( + Clause clause, + IReadOnlyList effectiveArguments, + out bool invalidatesAll, + out IReadOnlyList commandNames) + { + invalidatesAll = false; + commandNames = Array.Empty(); + var verb = GetCanonicalVerb(clause); + if (verb is null) + { + invalidatesAll = true; + return true; + } + + if (IsInvokeExpression(verb) || + verb.Equals("Import-Alias", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Import-Module", StringComparison.OrdinalIgnoreCase) || + verb.Equals("New-Module", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Remove-Module", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Import-PSSession", StringComparison.OrdinalIgnoreCase)) + { + invalidatesAll = true; + return true; + } + + if (verb.Equals("Set-Alias", StringComparison.OrdinalIgnoreCase) || + verb.Equals("New-Alias", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Remove-Alias", StringComparison.OrdinalIgnoreCase)) + { + if (!TryGetAliasMutationNames( + verb, + clause, + effectiveArguments, + out commandNames)) + { + invalidatesAll = true; + } + + return true; + } + + if (!IsProviderStateMutation( + verb, + clause, + effectiveArguments, + failOnUnproved: true)) + { + return false; + } + + if (verb.Equals("Rename-Item", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Move-Item", StringComparison.OrdinalIgnoreCase) || + verb.Equals("Copy-Item", StringComparison.OrdinalIgnoreCase)) + { + if (HasMutableOrUnprovedProviderTarget( + verb, + clause, + effectiveArguments, + failOnUnproved: true, + selection: StateProviderSelection.CommandResolution)) + { + // Both the source and destination can affect command lookup. + invalidatesAll = true; + return true; + } + + return false; + } + + return TryGetProviderMutationNames( + verb, + clause, + effectiveArguments, + out invalidatesAll, + out commandNames); + } + + private static bool TryGetAliasMutationNames( + string verb, + Clause clause, + IReadOnlyList effectiveArguments, + out IReadOnlyList commandNames) + { + var names = new List(); + var positionalIndex = 0; + var pendingParameter = AliasParameterRole.None; + foreach (var elementWithIndex in EnumerateArguments(clause)) + { + var element = elementWithIndex.Element; + var elementIndex = elementWithIndex.Index; + if (IsOpaqueSplat(element)) + { + commandNames = Array.Empty(); + return false; + } + + if (element.IsFlag) + { + if (!TryGetParameterName(element, out var parameter)) + { + commandNames = Array.Empty(); + return false; + } + + pendingParameter = ClassifyAliasParameter(parameter); + var separator = FindParameterValueSeparator(element.Value); + if (separator >= 0) + { + if (pendingParameter == AliasParameterRole.Name && + !TryAddInlineAliasCommandNames( + element, + elementIndex, + separator, + effectiveArguments, + names)) + { + commandNames = Array.Empty(); + return false; + } + + pendingParameter = AliasParameterRole.None; + } + + continue; + } + + var selectsName = pendingParameter == AliasParameterRole.Name || + pendingParameter == AliasParameterRole.None && + (positionalIndex == 0 || + verb.Equals("Remove-Alias", StringComparison.OrdinalIgnoreCase)); + if (pendingParameter == AliasParameterRole.Unknown || + selectsName && !TryAddCommandNames( + element, + elementIndex, + effectiveArguments, + names)) + { + commandNames = Array.Empty(); + return false; + } + + if (pendingParameter == AliasParameterRole.None) + { + positionalIndex++; + } + + pendingParameter = AliasParameterRole.None; + } + + if (pendingParameter is AliasParameterRole.Name or AliasParameterRole.Unknown || + names.Count == 0) + { + commandNames = Array.Empty(); + return false; + } + + commandNames = names.ToArray(); + return true; + } + + private static bool TryAddInlineAliasCommandNames( + ClauseElement element, + int elementIndex, + int separator, + IReadOnlyList effectiveArguments, + List names) + { + if (element.Kind is not (ArgKind.DynamicSkip or ArgKind.EnvVar)) + { + return TryAddCommandName( + element.Value.Substring(separator + 1), + names); + } + + if (!TryGetExactElementValues( + element, + elementIndex, + effectiveArguments, + out var values)) + { + return false; + } + + foreach (var value in values) + { + var valueSeparator = FindParameterValueSeparator(value); + var commandName = valueSeparator >= 0 + ? value.Substring(valueSeparator + 1) + : value; + if (!TryAddCommandName(commandName, names)) + { + return false; + } + } + + return true; + } + + private static bool TryGetProviderMutationNames( + string verb, + Clause clause, + IReadOnlyList effectiveArguments, + out bool invalidatesAll, + out IReadOnlyList commandNames) + { + invalidatesAll = false; + var names = new List(); + var positionalIndex = 0; + var pendingParameter = ProviderParameterRole.None; + foreach (var elementWithIndex in EnumerateArguments(clause)) + { + var element = elementWithIndex.Element; + var elementIndex = elementWithIndex.Index; + if (IsOpaqueSplat(element)) + { + invalidatesAll = true; + commandNames = Array.Empty(); + return true; + } + + if (element.IsFlag) + { + var separator = element.Value.IndexOf(':'); + var parameter = separator < 0 + ? element.Value + : element.Value.Substring(0, separator); + pendingParameter = ClassifyProviderParameter(parameter); + if (separator >= 0) + { + if (CanSelectProvider(pendingParameter) && + !TryAddProviderCommandNames( + element, + elementIndex, + effectiveArguments, + names, + out _)) + { + invalidatesAll = true; + commandNames = Array.Empty(); + return true; + } + + pendingParameter = ProviderParameterRole.None; + } + + continue; + } + + var couldSelectProvider = CanSelectProvider(pendingParameter) || + pendingParameter == ProviderParameterRole.None && + IsProviderTargetPosition(verb, positionalIndex); + if (couldSelectProvider && + !TryAddProviderCommandNames( + element, + elementIndex, + effectiveArguments, + names, + out _)) + { + invalidatesAll = true; + commandNames = Array.Empty(); + return true; + } + + if (pendingParameter == ProviderParameterRole.None) + { + positionalIndex++; + } + + pendingParameter = ProviderParameterRole.None; + } + + if (CanSelectProvider(pendingParameter)) + { + invalidatesAll = true; + commandNames = Array.Empty(); + return true; + } + + commandNames = names.ToArray(); + return names.Count > 0; + } + + private static bool TryAddProviderCommandNames( + ClauseElement element, + int elementIndex, + IReadOnlyList effectiveArguments, + List names, + out bool selectedCommandProvider) + { + selectedCommandProvider = false; + if (!TryGetExactElementValues( + element, + elementIndex, + effectiveArguments, + out var values)) + { + return false; + } + + foreach (var value in values) + { + if (!TryGetCommandNameFromProviderPath(value, out var commandName)) + { + continue; + } + + selectedCommandProvider = true; + if (!TryAddCommandName(commandName, names)) + { + return false; + } + } + + return true; + } + + private static bool TryAddCommandNames( + ClauseElement element, + int elementIndex, + IReadOnlyList effectiveArguments, + List names) + { + if (!TryGetExactElementValues( + element, + elementIndex, + effectiveArguments, + out var values)) + { + return false; + } + + foreach (var value in values) + { + if (!TryAddCommandName(value, names)) + { + return false; + } + } + + return true; + } + + private static bool TryGetExactElementValues( + ClauseElement element, + int elementIndex, + IReadOnlyList effectiveArguments, + out IReadOnlyList values) + { + foreach (var effective in effectiveArguments) + { + if (effective.ClauseElementIndex != elementIndex) + { + continue; + } + + if (effective.Value.Kind is not ( + ShellValueDomainKind.Exact or ShellValueDomainKind.FiniteSet) || + effective.Value.Values.Count == 0) + { + values = Array.Empty(); + return false; + } + + values = effective.Value.Values; + return true; + } + + if (element.Kind is ArgKind.DynamicSkip or ArgKind.EnvVar) + { + values = Array.Empty(); + return false; + } + + values = new[] { element.Value }; + return true; + } + + private static bool TryAddCommandName(string value, List names) + { + value = TrimMatchingQuotes(value); + if (value.Length == 0 || + value.IndexOfAny(new[] { '*', '?', '[', ']', ',', '/', '\\' }) >= 0) + { + return false; + } + + foreach (var existing in names) + { + if (existing.Equals(value, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + if (names.Count == ShellAnalysisLimits.MaxValueCandidates) + { + return false; + } + + names.Add(value); + return true; + } + + private static bool TryGetCommandNameFromProviderPath( + string value, + out string commandName) + { + value = TrimMatchingQuotes(value); + if (value.Length > 1 && value[0] == '-') + { + var separator = FindParameterValueSeparator(value); + if (separator < 0 || separator + 1 == value.Length) + { + commandName = string.Empty; + return false; + } + + value = TrimMatchingQuotes(value.Substring(separator + 1)); + } + + var providerPath = value; + if (!HasCommandResolutionStateProviderPrefix(providerPath)) + { + var providerStart = Math.Max( + value.LastIndexOf('\\'), + value.LastIndexOf('/')) + 1; + providerPath = value.Substring(providerStart); + } + + var isFunction = providerPath.StartsWith( + "Function:", + StringComparison.OrdinalIgnoreCase); + var prefixLength = providerPath.StartsWith( + "Alias:", + StringComparison.OrdinalIgnoreCase) + ? "Alias:".Length + : isFunction + ? "Function:".Length + : 0; + if (prefixLength == 0 || prefixLength == providerPath.Length) + { + commandName = string.Empty; + return false; + } + + commandName = providerPath.Substring(prefixLength); + if (commandName.Length > 0 && commandName[0] == ':') + { + // PowerShell accepts Provider::name as the provider-qualified form. + // Additional colons belong to the authored command name. + commandName = commandName.Substring(1); + } + + commandName = commandName.TrimStart('/', '\\'); + if (isFunction) + { + commandName = TrimFunctionScope(commandName); + } + + return true; + } + + private static string TrimFunctionScope(string commandName) + { + var separator = commandName.IndexOf(':'); + if (separator <= 0) + { + return commandName; + } + + var scope = commandName.Substring(0, separator); + return scope.Equals("Global", StringComparison.OrdinalIgnoreCase) || + scope.Equals("Script", StringComparison.OrdinalIgnoreCase) || + scope.Equals("Local", StringComparison.OrdinalIgnoreCase) || + scope.Equals("Private", StringComparison.OrdinalIgnoreCase) + ? commandName.Substring(separator + 1) + : commandName; + } + + private static int FindParameterValueSeparator(string value) + { + var colon = value.IndexOf(':', 1); + var equals = value.IndexOf('=', 1); + if (colon < 0) + { + return equals; + } + + return equals < 0 ? colon : Math.Min(colon, equals); + } + + private static AliasParameterRole ClassifyAliasParameter(string parameter) + { + if (IsAcceptedPrefix(parameter, "Name", minimumLength: 1)) + { + return AliasParameterRole.Name; + } + + if (IsAcceptedPrefix(parameter, "Value", minimumLength: 1) || + IsAcceptedPrefix(parameter, "Description", minimumLength: 1) || + IsAcceptedPrefix(parameter, "Option", minimumLength: 1) || + IsAcceptedPrefix(parameter, "Scope", minimumLength: 1) || + parameter.Equals("ErrorAction", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("ErrorVariable", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("InformationAction", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("InformationVariable", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("OutBuffer", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("OutVariable", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("PipelineVariable", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("ProgressAction", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("WarningAction", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("WarningVariable", StringComparison.OrdinalIgnoreCase)) + { + return AliasParameterRole.NonName; + } + + if (parameter.Equals("Force", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("PassThru", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("WhatIf", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("Confirm", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("Verbose", StringComparison.OrdinalIgnoreCase) || + parameter.Equals("Debug", StringComparison.OrdinalIgnoreCase)) + { + return AliasParameterRole.None; + } + + return AliasParameterRole.Unknown; + } + + private static IEnumerable<(ClauseElement Element, int Index)> EnumerateArguments( + Clause clause) + { + for (var index = 0; index < clause.Elements.Count; index++) + { + if (clause.Elements[index].Role == ClauseElementRole.Argument) + { + yield return (clause.Elements[index], index); + } + } + } + internal static bool HasVariableWritingArgument(Clause clause) { var verb = GetCanonicalVerb(clause); @@ -783,7 +1358,8 @@ private static bool HasMutableOrUnprovedProviderTarget( string verb, Clause clause, IReadOnlyList effectiveArguments, - bool failOnUnproved) + bool failOnUnproved, + StateProviderSelection selection = StateProviderSelection.AnyMutable) { var positionalIndex = 0; var pendingParameter = ProviderParameterRole.None; @@ -809,7 +1385,8 @@ private static bool HasMutableOrUnprovedProviderTarget( element, elementIndex, effectiveArguments, - failOnUnproved)) + failOnUnproved, + selection)) { return true; } @@ -828,7 +1405,8 @@ private static bool HasMutableOrUnprovedProviderTarget( element, elementIndex, effectiveArguments, - failOnUnproved)) + failOnUnproved, + selection)) { return true; } @@ -851,12 +1429,13 @@ private static bool IsMutableOrUnprovedProviderTarget( ClauseElement element, int elementIndex, IReadOnlyList effectiveArguments, - bool failOnUnproved) + bool failOnUnproved, + StateProviderSelection selection) { - if (IsMutableStateProviderPath(element.Value) || - IsMutableStateProviderPath(element.Raw) || + if (IsSelectedStateProviderPath(element.Value, selection) || + IsSelectedStateProviderPath(element.Raw, selection) || element.Resolved is not null && - IsMutableStateProviderPath(element.Resolved)) + IsSelectedStateProviderPath(element.Resolved, selection)) { return true; } @@ -882,7 +1461,7 @@ element.Resolved is not null && foreach (var value in effective.Value.Values) { - if (IsMutableStateProviderPath(value)) + if (IsSelectedStateProviderPath(value, selection)) { return true; } @@ -894,6 +1473,17 @@ element.Resolved is not null && return true; } + private static bool IsSelectedStateProviderPath( + string value, + StateProviderSelection selection) => selection switch + { + StateProviderSelection.Environment => + IsEnvironmentStateProviderPath(value), + StateProviderSelection.CommandResolution => + IsCommandResolutionStateProviderPath(value), + _ => IsMutableStateProviderPath(value), + }; + private static ProviderParameterRole ClassifyProviderParameter(string parameter) { if (parameter.Equals("-Path", StringComparison.OrdinalIgnoreCase) || @@ -972,6 +1562,72 @@ private static bool HasMutableStateProviderPrefix(string providerPath) => providerPath.StartsWith("Environment:", StringComparison.OrdinalIgnoreCase) || providerPath.StartsWith("Env:", StringComparison.OrdinalIgnoreCase); + private static bool IsEnvironmentStateProviderPath(string value) + { + value = TrimMatchingQuotes(value); + if (HasEnvironmentStateProviderPrefix(value)) + { + return true; + } + + if (value.Length > 1 && value[0] == '-') + { + var parameterSeparator = value.IndexOf(':'); + if (parameterSeparator > 1 && parameterSeparator + 1 < value.Length) + { + var inlineValue = TrimMatchingQuotes( + value.Substring(parameterSeparator + 1)); + if (HasEnvironmentStateProviderPrefix(inlineValue)) + { + return true; + } + } + } + + var providerStart = Math.Max( + value.LastIndexOf('\\'), + value.LastIndexOf('/')) + 1; + return providerStart > 0 && + HasEnvironmentStateProviderPrefix(value.Substring(providerStart)); + } + + private static bool HasEnvironmentStateProviderPrefix(string providerPath) => + providerPath.StartsWith("Environment:", StringComparison.OrdinalIgnoreCase) || + providerPath.StartsWith("Env:", StringComparison.OrdinalIgnoreCase); + + private static bool IsCommandResolutionStateProviderPath(string value) + { + value = TrimMatchingQuotes(value); + if (HasCommandResolutionStateProviderPrefix(value)) + { + return true; + } + + if (value.Length > 1 && value[0] == '-') + { + var parameterSeparator = value.IndexOf(':'); + if (parameterSeparator > 1 && parameterSeparator + 1 < value.Length) + { + var inlineValue = TrimMatchingQuotes( + value.Substring(parameterSeparator + 1)); + if (HasCommandResolutionStateProviderPrefix(inlineValue)) + { + return true; + } + } + } + + var providerStart = Math.Max( + value.LastIndexOf('\\'), + value.LastIndexOf('/')) + 1; + return providerStart > 0 && + HasCommandResolutionStateProviderPrefix(value.Substring(providerStart)); + } + + private static bool HasCommandResolutionStateProviderPrefix(string providerPath) => + providerPath.StartsWith("Alias:", StringComparison.OrdinalIgnoreCase) || + providerPath.StartsWith("Function:", StringComparison.OrdinalIgnoreCase); + private static string TrimMatchingQuotes(string value) => value.Length >= 2 && value[0] is '\'' or '"' && @@ -986,6 +1642,21 @@ private enum ProviderParameterRole NonTarget, Unknown, } + + private enum AliasParameterRole + { + None, + Name, + NonName, + Unknown, + } + + private enum StateProviderSelection + { + AnyMutable, + Environment, + CommandResolution, + } } internal sealed class PwshForEachValueAnalyzer @@ -1006,6 +1677,7 @@ private readonly Dictionary> private long _nonRegionStateMutationCount; private long _locationStateMutationCount; private long _childScopeEscapeRiskCount; + private long _childRunspaceProcessEscapeRiskCount; private bool _pipelineStageMayReceiveInput; private bool _pipelineStageEffectsMayReachRegionBodies; @@ -1043,6 +1715,9 @@ internal static bool TryAnalyze( canPromote: options.InitialStateMode == PwshInitialStateMode.IsolatedNonInteractiveNoProfile, commandResolutionInvalidated: false, + allRunspaceCommandResolutionMayReachProcessMutation: false, + Array.Empty(), + processWideStateInvalidated: false, new List())); if (!analyzer._isComplete) { @@ -1130,6 +1805,21 @@ private PwshFlowResult AnalyzeSimple(SimpleCommandSyntax simple, AnalysisContext isForEachIncomplete, mayPromote); + var hasCommandResolutionMutation = + PwshPersistentStateMutation.TryGetCommandResolutionMutation( + simple.Clause, + effective, + out var invalidatesAllCommandNames, + out var mutatedCommandNames); + if (simple.Clause.Verb.IsDynamic || + current.MayResolveCommandToProcessMutation(simple.Clause) && + !IsCommandIdentityProven(simple.Clause, current)) + { + // An invocation reached after alias/function/module mutation can be + // any in-process command, including one that writes Env: state. + _childRunspaceProcessEscapeRiskCount++; + } + var location = AnalyzeSetLocation(simple, current); if (location is not null) { @@ -1140,6 +1830,10 @@ private PwshFlowResult AnalyzeSimple(SimpleCommandSyntax simple, AnalysisContext effective, out var locationEffectUnknownCwd)) { + var mayEscapeChildRunspaceProcess = + PwshPersistentStateMutation.MayEscapeChildRunspaceProcess( + simple.Clause, + effective); if (PwshPersistentStateMutation.MayEscapeChildScope( simple.Clause, effective)) @@ -1147,13 +1841,30 @@ private PwshFlowResult AnalyzeSimple(SimpleCommandSyntax simple, AnalysisContext _childScopeEscapeRiskCount++; } + if (mayEscapeChildRunspaceProcess) + { + _childRunspaceProcessEscapeRiskCount++; + } + var flow = location.Value; return ApplyExecutionRegionEffect(simple, current, new PwshFlowResult( flow.OnSuccess is AnalysisContext success - ? success.Invalidate(locationEffectUnknownCwd) + ? ApplyPersistentStateInvalidation( + success, + locationEffectUnknownCwd, + hasCommandResolutionMutation, + invalidatesAllCommandNames, + mutatedCommandNames, + mayEscapeChildRunspaceProcess) : null, flow.OnFailure is AnalysisContext failure - ? failure.Invalidate(locationEffectUnknownCwd) + ? ApplyPersistentStateInvalidation( + failure, + locationEffectUnknownCwd, + hasCommandResolutionMutation, + invalidatesAllCommandNames, + mutatedCommandNames, + mayEscapeChildRunspaceProcess) : null)); } @@ -1164,10 +1875,15 @@ flow.OnFailure is AnalysisContext failure { _nonRegionStateMutationCount++; _childScopeEscapeRiskCount++; + _childRunspaceProcessEscapeRiskCount++; return ApplyExecutionRegionEffect( simple, current, - PwshFlowResult.Both(current.Invalidate(unknownCwd: true))); + PwshFlowResult.Both( + current + .Invalidate(unknownCwd: true) + .WithRunspaceCommandResolutionProcessRisk() + .WithProcessWideStateInvalidated())); } if (PwshPersistentStateMutation.TryGetEffect( @@ -1176,6 +1892,10 @@ flow.OnFailure is AnalysisContext failure out var unknownCwd)) { _nonRegionStateMutationCount++; + var mayEscapeChildRunspaceProcess = + PwshPersistentStateMutation.MayEscapeChildRunspaceProcess( + simple.Clause, + effective); if (PwshPersistentStateMutation.MayEscapeChildScope( simple.Clause, effective)) @@ -1183,15 +1903,47 @@ flow.OnFailure is AnalysisContext failure _childScopeEscapeRiskCount++; } + if (mayEscapeChildRunspaceProcess) + { + _childRunspaceProcessEscapeRiskCount++; + } + return ApplyExecutionRegionEffect( simple, current, - PwshFlowResult.Both(current.Invalidate(unknownCwd))); + PwshFlowResult.Both(ApplyPersistentStateInvalidation( + current, + unknownCwd, + hasCommandResolutionMutation, + invalidatesAllCommandNames, + mutatedCommandNames, + mayEscapeChildRunspaceProcess))); } return ApplyExecutionRegionEffect(simple, current, PwshFlowResult.Both(current)); } + private static AnalysisContext ApplyPersistentStateInvalidation( + AnalysisContext input, + bool unknownCwd, + bool hasCommandResolutionMutation, + bool invalidatesAllCommandNames, + IReadOnlyList mutatedCommandNames, + bool mayEscapeChildRunspaceProcess) + { + var invalidated = input.Invalidate(unknownCwd); + if (hasCommandResolutionMutation) + { + invalidated = invalidated.WithRunspaceCommandResolutionProcessRisk( + invalidatesAllCommandNames, + mutatedCommandNames); + } + + return mayEscapeChildRunspaceProcess + ? invalidated.WithProcessWideStateInvalidated() + : invalidated; + } + private PwshFlowResult ApplyExecutionRegionEffect( SimpleCommandSyntax simple, AnalysisContext receiverInput, @@ -1248,6 +2000,15 @@ flow.OnFailure is AnalysisContext failure flow); } + if (binding.ParameterSet == PwshExecutionRegionParameterSet.ForEachParallel) + { + return AnalyzeForEachParallel( + binding, + regions, + receiverInput, + flow); + } + if (binding.ParameterSet == PwshExecutionRegionParameterSet.InvokeInProcess) { return AnalyzeInProcessInvokeCommand( @@ -1330,6 +2091,8 @@ private PwshFlowResult AnalyzeStartJob( var nonRegionStateMutationCount = _nonRegionStateMutationCount; var locationStateMutationCount = _locationStateMutationCount; var childScopeEscapeRiskCount = _childScopeEscapeRiskCount; + var childRunspaceProcessEscapeRiskCount = + _childRunspaceProcessEscapeRiskCount; try { var child = CreateStartJobInput(simple, binding, receiverInput); @@ -1352,19 +2115,97 @@ private PwshFlowResult AnalyzeStartJob( _nonRegionStateMutationCount = nonRegionStateMutationCount; _locationStateMutationCount = locationStateMutationCount; _childScopeEscapeRiskCount = childScopeEscapeRiskCount; + _childRunspaceProcessEscapeRiskCount = + childRunspaceProcessEscapeRiskCount; } return hostFlow; } + private PwshFlowResult AnalyzeForEachParallel( + PwshExecutionRegionBindingResult binding, + IReadOnlyList regions, + AnalysisContext receiverInput, + PwshFlowResult hostFlow) + { + var executionRegionEffectCount = _executionRegionEffectCount; + var nonRegionStateMutationCount = _nonRegionStateMutationCount; + var locationStateMutationCount = _locationStateMutationCount; + var childScopeEscapeRiskCount = _childScopeEscapeRiskCount; + var childRunspaceProcessEscapeRiskCount = + _childRunspaceProcessEscapeRiskCount; + var childRunspaceProcessMayEscape = false; + try + { + var childInput = receiverInput.CreateChildRunspaceInput(); + if (TryAnalyzeRegionSequence(regions, childInput, out var childExit)) + { + var firstVisitMayEscape = + _childRunspaceProcessEscapeRiskCount > + childRunspaceProcessEscapeRiskCount; + if (firstVisitMayEscape || + !binding.HasUseNewRunspace && + childExit.HasRunspaceCommandResolutionProcessRisk) + { + // Pooled runspaces retain local command-resolution mutation; + // fresh runspaces still share process-wide child effects. + var repeatedInput = childInput + .Invalidate(unknownCwd: firstVisitMayEscape) + .WithConditionalProcessWideStateInvalidation( + firstVisitMayEscape) + .WithoutBindings(); + if (!binding.HasUseNewRunspace) + { + repeatedInput = repeatedInput + .WithRunspaceCommandResolutionProcessRisk(childExit); + } + + TryAnalyzeRegionSequence( + regions, + repeatedInput, + out _); + } + } + + childRunspaceProcessMayEscape = + _childRunspaceProcessEscapeRiskCount > + childRunspaceProcessEscapeRiskCount; + } + finally + { + _executionRegionEffectCount = executionRegionEffectCount + regions.Count; + _nonRegionStateMutationCount = nonRegionStateMutationCount; + _locationStateMutationCount = locationStateMutationCount; + _childScopeEscapeRiskCount = childScopeEscapeRiskCount; + _childRunspaceProcessEscapeRiskCount = + childRunspaceProcessEscapeRiskCount + + (childRunspaceProcessMayEscape ? 1 : 0); + } + + if (!childRunspaceProcessMayEscape) + { + return hostFlow; + } + + return new PwshFlowResult( + hostFlow.OnSuccess is AnalysisContext success + ? success + .Invalidate(unknownCwd: true) + .WithProcessWideStateInvalidated() + : null, + hostFlow.OnFailure is AnalysisContext failure + ? failure + .Invalidate(unknownCwd: true) + .WithProcessWideStateInvalidated() + : null); + } + private AnalysisContext CreateStartJobInput( SimpleCommandSyntax simple, PwshExecutionRegionBindingResult binding, AnalysisContext receiverInput) { - var child = receiverInput - .Invalidate(unknownCwd: false, invalidateCommandResolution: false) - .WithoutBindings(); + var child = receiverInput.CreateChildProcessInput(); if (binding.WorkingDirectoryElementIndex is not int elementIndex) { return child; @@ -1566,11 +2407,15 @@ private void AnalyzeUnreachableRegions( var nonRegionStateMutationCount = _nonRegionStateMutationCount; var locationStateMutationCount = _locationStateMutationCount; var childScopeEscapeRiskCount = _childScopeEscapeRiskCount; + var childRunspaceProcessEscapeRiskCount = + _childRunspaceProcessEscapeRiskCount; TryAnalyzeRegionSequence(regions, input, out _); _executionRegionEffectCount = executionRegionEffectCount; _nonRegionStateMutationCount = nonRegionStateMutationCount; _locationStateMutationCount = locationStateMutationCount; _childScopeEscapeRiskCount = childScopeEscapeRiskCount; + _childRunspaceProcessEscapeRiskCount = + childRunspaceProcessEscapeRiskCount; } private PwshFlowResult AnalyzeExecutionRegionBody( @@ -1672,6 +2517,10 @@ private static bool IsSupportedExecutionRegionReceiver( AllBindingsAreCompleteWithTiming( binding.Bindings, ExecutionRegionTiming.Concurrent), + PwshExecutionRegionParameterSet.ForEachParallel => + AllBindingsAreCompleteWithTiming( + binding.Bindings, + ExecutionRegionTiming.Concurrent), PwshExecutionRegionParameterSet.MeasureExpression or PwshExecutionRegionParameterSet.TraceExpression or PwshExecutionRegionParameterSet.InvokeInProcess or @@ -1944,6 +2793,7 @@ private static bool IsPipelineSensitiveExecutionRegionHost(SimpleCommandSyntax s commandIdentityProven: true); return binding.Status == PwshExecutionRegionBindingStatus.ProvedExecution && binding.ParameterSet is PwshExecutionRegionParameterSet.ForEachScriptBlock or + PwshExecutionRegionParameterSet.ForEachParallel or PwshExecutionRegionParameterSet.WhereScriptBlock or PwshExecutionRegionParameterSet.InvokeInProcess or PwshExecutionRegionParameterSet.MeasureExpression or @@ -2022,6 +2872,8 @@ private PwshFlowResult AnalyzeGroup(GroupSyntax group, AnalysisContext input) var nonRegionStateMutationCount = _nonRegionStateMutationCount; var locationStateMutationCount = _locationStateMutationCount; var childScopeEscapeRiskCount = _childScopeEscapeRiskCount; + var childRunspaceProcessEscapeRiskCount = + _childRunspaceProcessEscapeRiskCount; AnalyzeBlock( group.Body, input.WithoutBindings().Invalidate( @@ -2031,6 +2883,8 @@ private PwshFlowResult AnalyzeGroup(GroupSyntax group, AnalysisContext input) _nonRegionStateMutationCount = nonRegionStateMutationCount; _locationStateMutationCount = locationStateMutationCount; _childScopeEscapeRiskCount = childScopeEscapeRiskCount; + _childRunspaceProcessEscapeRiskCount = + childRunspaceProcessEscapeRiskCount; return PwshFlowResult.Both(input); } @@ -2095,6 +2949,18 @@ private PwshFlowResult AnalyzeExecutionRegion( var restored = childScopeMayEscape ? input.Invalidate(unknownCwd: false) : input; + if (childScopeMayEscape && + bodyExit.Value.HasRunspaceCommandResolutionProcessRisk) + { + restored = restored.WithRunspaceCommandResolutionProcessRisk( + bodyExit.Value); + } + + if (bodyExit.Value.ProcessWideStateInvalidated) + { + restored = restored.WithProcessWideStateInvalidated(); + } + if (!locationMutated && string.Equals( bodyExit.Value.WorkingDirectory, input.WorkingDirectory, @@ -3331,16 +4197,25 @@ internal BindingFrame(string name, ShellValueDomain domain) private readonly struct AnalysisContext { private readonly IReadOnlyList _bindings; + private readonly IReadOnlyList _runspaceProcessMutationCommandNames; internal AnalysisContext( string? workingDirectory, bool canPromote, bool commandResolutionInvalidated, + bool allRunspaceCommandResolutionMayReachProcessMutation, + IReadOnlyList runspaceProcessMutationCommandNames, + bool processWideStateInvalidated, IReadOnlyList bindings) { WorkingDirectory = workingDirectory; CanPromote = canPromote; CommandResolutionInvalidated = commandResolutionInvalidated; + AllRunspaceCommandResolutionMayReachProcessMutation = + allRunspaceCommandResolutionMayReachProcessMutation; + _runspaceProcessMutationCommandNames = + runspaceProcessMutationCommandNames; + ProcessWideStateInvalidated = processWideStateInvalidated; _bindings = bindings; } @@ -3350,6 +4225,41 @@ internal AnalysisContext( internal bool CommandResolutionInvalidated { get; } + internal bool AllRunspaceCommandResolutionMayReachProcessMutation { get; } + + internal bool ProcessWideStateInvalidated { get; } + + internal bool HasRunspaceCommandResolutionProcessRisk => + AllRunspaceCommandResolutionMayReachProcessMutation || + _runspaceProcessMutationCommandNames.Count > 0; + + internal bool MayResolveCommandToProcessMutation(Clause clause) + { + if (ProcessWideStateInvalidated || + AllRunspaceCommandResolutionMayReachProcessMutation) + { + return true; + } + + if (clause.Verb.Tokens.Count != 1) + { + return _runspaceProcessMutationCommandNames.Count > 0; + } + + var authoredName = clause.Verb.Tokens[0]; + foreach (var commandName in _runspaceProcessMutationCommandNames) + { + if (commandName.Equals( + authoredName, + StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + internal AnalysisContext Invalidate( bool unknownCwd, bool invalidateCommandResolution = true) @@ -3362,6 +4272,9 @@ internal AnalysisContext Invalidate( unknownCwd ? null : WorkingDirectory, false, commandResolutionInvalidated, + AllRunspaceCommandResolutionMayReachProcessMutation, + _runspaceProcessMutationCommandNames, + ProcessWideStateInvalidated, _bindings); } @@ -3377,14 +4290,106 @@ internal AnalysisContext Invalidate( unknownCwd ? null : WorkingDirectory, false, commandResolutionInvalidated, + AllRunspaceCommandResolutionMayReachProcessMutation, + _runspaceProcessMutationCommandNames, + ProcessWideStateInvalidated, unknown); } + internal AnalysisContext WithRunspaceCommandResolutionProcessRisk() => + new( + WorkingDirectory, + CanPromote, + commandResolutionInvalidated: true, + allRunspaceCommandResolutionMayReachProcessMutation: true, + Array.Empty(), + ProcessWideStateInvalidated, + _bindings); + + internal AnalysisContext WithRunspaceCommandResolutionProcessRisk( + bool invalidatesAll, + IReadOnlyList commandNames) + { + if (invalidatesAll) + { + return WithRunspaceCommandResolutionProcessRisk(); + } + + if (AllRunspaceCommandResolutionMayReachProcessMutation || + commandNames.Count == 0) + { + return this; + } + + var names = MergeCommandNames( + _runspaceProcessMutationCommandNames, + commandNames, + out var exceededLimit); + return exceededLimit + ? WithRunspaceCommandResolutionProcessRisk() + : new AnalysisContext( + WorkingDirectory, + CanPromote, + commandResolutionInvalidated: true, + allRunspaceCommandResolutionMayReachProcessMutation: false, + names, + ProcessWideStateInvalidated, + _bindings); + } + + internal AnalysisContext WithRunspaceCommandResolutionProcessRisk( + AnalysisContext source) => + source.AllRunspaceCommandResolutionMayReachProcessMutation + ? WithRunspaceCommandResolutionProcessRisk() + : WithRunspaceCommandResolutionProcessRisk( + invalidatesAll: false, + source._runspaceProcessMutationCommandNames); + + internal AnalysisContext WithProcessWideStateInvalidated() => + new( + WorkingDirectory, + CanPromote, + commandResolutionInvalidated: true, + AllRunspaceCommandResolutionMayReachProcessMutation, + _runspaceProcessMutationCommandNames, + processWideStateInvalidated: true, + _bindings); + + internal AnalysisContext WithConditionalProcessWideStateInvalidation( + bool invalidated) => invalidated + ? WithProcessWideStateInvalidated() + : this; + internal AnalysisContext WithoutBindings() => new( WorkingDirectory, CanPromote, CommandResolutionInvalidated, + AllRunspaceCommandResolutionMayReachProcessMutation, + _runspaceProcessMutationCommandNames, + ProcessWideStateInvalidated, + Array.Empty()); + + internal AnalysisContext CreateChildProcessInput() => + new( + WorkingDirectory, + canPromote: false, + commandResolutionInvalidated: ProcessWideStateInvalidated, + allRunspaceCommandResolutionMayReachProcessMutation: false, + Array.Empty(), + ProcessWideStateInvalidated, + Array.Empty()); + + internal AnalysisContext CreateChildRunspaceInput() => + // Caller aliases and ordinary variables do not initialize a Parallel + // child runspace; explicit $using: values remain conservatively unknown. + new( + WorkingDirectory, + CanPromote, + commandResolutionInvalidated: ProcessWideStateInvalidated, + allRunspaceCommandResolutionMayReachProcessMutation: false, + Array.Empty(), + ProcessWideStateInvalidated, Array.Empty()); internal AnalysisContext WithCwd(string? workingDirectory) => @@ -3392,6 +4397,9 @@ internal AnalysisContext WithCwd(string? workingDirectory) => workingDirectory, CanPromote, CommandResolutionInvalidated, + AllRunspaceCommandResolutionMayReachProcessMutation, + _runspaceProcessMutationCommandNames, + ProcessWideStateInvalidated, _bindings); internal AnalysisContext WithBinding( @@ -3414,6 +4422,9 @@ internal AnalysisContext WithBinding( WorkingDirectory, CanPromote, CommandResolutionInvalidated, + AllRunspaceCommandResolutionMayReachProcessMutation, + _runspaceProcessMutationCommandNames, + ProcessWideStateInvalidated, bindings); } @@ -3669,11 +4680,26 @@ internal bool StateEquals(AnalysisContext other) StringComparison.Ordinal) || CanPromote != other.CanPromote || CommandResolutionInvalidated != other.CommandResolutionInvalidated || + AllRunspaceCommandResolutionMayReachProcessMutation != + other.AllRunspaceCommandResolutionMayReachProcessMutation || + ProcessWideStateInvalidated != other.ProcessWideStateInvalidated || + _runspaceProcessMutationCommandNames.Count != + other._runspaceProcessMutationCommandNames.Count || _bindings.Count != other._bindings.Count) { return false; } + foreach (var commandName in _runspaceProcessMutationCommandNames) + { + if (!ContainsCommandName( + other._runspaceProcessMutationCommandNames, + commandName)) + { + return false; + } + } + foreach (var binding in _bindings) { var otherBinding = other.FindBinding(binding.Name); @@ -3721,6 +4747,14 @@ leftBinding is null || rightBinding is null : JoinDomains(leftBinding.Domain, rightBinding.Domain))); } + var commandNames = MergeCommandNames( + left._runspaceProcessMutationCommandNames, + right._runspaceProcessMutationCommandNames, + out var commandNameLimitExceeded); + var invalidatesAllCommandNames = + left.AllRunspaceCommandResolutionMayReachProcessMutation || + right.AllRunspaceCommandResolutionMayReachProcessMutation || + commandNameLimitExceeded; return new AnalysisContext( string.Equals( left.WorkingDirectory, @@ -3731,9 +4765,61 @@ leftBinding is null || rightBinding is null left.CanPromote && right.CanPromote, left.CommandResolutionInvalidated || right.CommandResolutionInvalidated, + invalidatesAllCommandNames, + invalidatesAllCommandNames + ? Array.Empty() + : commandNames, + left.ProcessWideStateInvalidated || + right.ProcessWideStateInvalidated, bindings); } + private static IReadOnlyList MergeCommandNames( + IReadOnlyList left, + IReadOnlyList right, + out bool exceededLimit) + { + exceededLimit = false; + var names = new List(left.Count + right.Count); + foreach (var commandName in left) + { + names.Add(commandName); + } + + foreach (var commandName in right) + { + if (ContainsCommandName(names, commandName)) + { + continue; + } + + if (names.Count == ShellAnalysisLimits.MaxValueCandidates) + { + exceededLimit = true; + return Array.Empty(); + } + + names.Add(commandName); + } + + return names.ToArray(); + } + + private static bool ContainsCommandName( + IReadOnlyList commandNames, + string expected) + { + foreach (var commandName in commandNames) + { + if (commandName.Equals(expected, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + internal static AnalysisContext Widen( AnalysisContext left, AnalysisContext right) => Join(left, right); diff --git a/src/ShellSyntaxTree/Internal/Pwsh/Verbs/PwshExecutionRegionBindingCatalog.cs b/src/ShellSyntaxTree/Internal/Pwsh/Verbs/PwshExecutionRegionBindingCatalog.cs index 11dfb04..295d63e 100644 --- a/src/ShellSyntaxTree/Internal/Pwsh/Verbs/PwshExecutionRegionBindingCatalog.cs +++ b/src/ShellSyntaxTree/Internal/Pwsh/Verbs/PwshExecutionRegionBindingCatalog.cs @@ -94,6 +94,8 @@ internal sealed record PwshExecutionRegionBindingResult internal bool HasNoNewScope { get; init; } + internal bool HasUseNewRunspace { get; init; } + internal int? WorkingDirectoryElementIndex { get; init; } internal int WorkingDirectoryValueOffset { get; init; } @@ -453,6 +455,8 @@ private static PwshExecutionRegionBindingResult BindReceiver( HasExplicitInputObject = arguments.HasNamed("InputObject"), HasNoNewScope = receiver == PwshExecutionRegionReceiver.InvokeCommand && arguments.IsSwitchEnabled("NoNewScope"), + HasUseNewRunspace = receiver == PwshExecutionRegionReceiver.ForEachObject && + arguments.IsSwitchEnabled("UseNewRunspace"), WorkingDirectoryElementIndex = receiver == PwshExecutionRegionReceiver.StartJob ? arguments.FirstNamedArgumentElementIndex("WorkingDirectory") : null, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/342_v03_foreach_literal_array.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/342_v03_foreach_literal_array.json index eb2800d..bad252a 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/342_v03_foreach_literal_array.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/342_v03_foreach_literal_array.json @@ -129,7 +129,9 @@ ], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/343_v03_foreach_pipeline_iterator.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/343_v03_foreach_pipeline_iterator.json index adfdcef..77324e2 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/343_v03_foreach_pipeline_iterator.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/343_v03_foreach_pipeline_iterator.json @@ -145,7 +145,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -190,7 +192,9 @@ ], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/344_v03_foreach_subexpression_iterator.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/344_v03_foreach_subexpression_iterator.json index 0a2311a..8d3517b 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/344_v03_foreach_subexpression_iterator.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/344_v03_foreach_subexpression_iterator.json @@ -176,7 +176,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -221,7 +223,9 @@ ], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/345_v03_foreach_body_pipeline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/345_v03_foreach_body_pipeline.json index ab37036..d16f77d 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/345_v03_foreach_body_pipeline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/345_v03_foreach_body_pipeline.json @@ -161,7 +161,9 @@ ], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -203,7 +205,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/346_v03_foreach_parenthesized_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/346_v03_foreach_parenthesized_alias.json index 46ece6b..d1d89a0 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/346_v03_foreach_parenthesized_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/346_v03_foreach_parenthesized_alias.json @@ -104,7 +104,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -132,7 +134,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/347_v03_foreach_call_operator_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/347_v03_foreach_call_operator_alias.json index 8f6ef47..d12c260 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/347_v03_foreach_call_operator_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/347_v03_foreach_call_operator_alias.json @@ -61,7 +61,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/348_v03_foreach_numeric_pipeline_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/348_v03_foreach_numeric_pipeline_alias.json index be3c565..7a26638 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/348_v03_foreach_numeric_pipeline_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/348_v03_foreach_numeric_pipeline_alias.json @@ -104,7 +104,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -132,7 +134,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/349_v03_foreach_numeric_call_operator_alias.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/349_v03_foreach_numeric_call_operator_alias.json index ed6c324..8d24461 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/349_v03_foreach_numeric_call_operator_alias.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/349_v03_foreach_numeric_call_operator_alias.json @@ -61,7 +61,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/350_v03_foreach_semicolon_boundary.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/350_v03_foreach_semicolon_boundary.json index 3bb0bc1..9762186 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/350_v03_foreach_semicolon_boundary.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/350_v03_foreach_semicolon_boundary.json @@ -156,7 +156,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -208,7 +210,9 @@ ], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -236,7 +240,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/351_v03_foreach_child_host_isolation.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/351_v03_foreach_child_host_isolation.json index bddcd53..330fbe4 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/351_v03_foreach_child_host_isolation.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/351_v03_foreach_child_host_isolation.json @@ -188,7 +188,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -216,7 +218,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/352_v03_foreach_following_and_or_pipeline.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/352_v03_foreach_following_and_or_pipeline.json index d436a38..3d35ce7 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/352_v03_foreach_following_and_or_pipeline.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/352_v03_foreach_following_and_or_pipeline.json @@ -180,7 +180,9 @@ ], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -208,7 +210,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } @@ -236,7 +240,9 @@ "effectiveArguments": [], "workingDirectory": { "kind": "Exact", - "values": ["C:/work"], + "values": [ + "C:/work" + ], "pattern": null, "coveringDirectory": null } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/362_v03_foreach_isolated_exact_scalar.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/362_v03_foreach_isolated_exact_scalar.json index 2c1c591..6b418fa 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/362_v03_foreach_isolated_exact_scalar.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/362_v03_foreach_isolated_exact_scalar.json @@ -1,39 +1,143 @@ { "name": "V03 foreach isolated exact scalar", - "input": "foreach ($f in 'a.txt') { Remove-Item -LiteralPath $f }", + "input": "foreach ($f in \u0027a.txt\u0027) { Remove-Item -LiteralPath $f }", "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ { "operator": "None", - "verb": ["Remove-Item"], + "verb": [ + "Remove-Item" + ], "args": [ - { "raw": "-LiteralPath", "kind": "Literal", "isPath": false }, - { "raw": "$f", "kind": "DynamicSkip", "isPath": false } + { + "raw": "-LiteralPath", + "kind": "Literal", + "isPath": false + }, + { + "raw": "$f", + "kind": "DynamicSkip", + "isPath": false + } ], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 55, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 55, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "bindingName": "f", + "bindingRaw": "$f", + "bindingSourceStart": 9, + "bindingSourceLength": 2, + "iterableRaw": "\u0027a.txt\u0027", + "iterableSourceStart": 15, + "iterableSourceLength": 7 + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "Iterator", + "childIndex": null, + "sourceStart": 15, + "sourceLength": 7, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 25, + "sourceLength": 29, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": 26, + "sourceLength": 27, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "LoopBody", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 55 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 55 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 25, "sourceLength": 29 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 55 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 55 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 25, + "sourceLength": 29 + } ], "effectiveArguments": [ { "clauseElementIndex": 2, "value": { "kind": "Exact", - "values": ["a.txt"] + "values": [ + "a.txt" + ], + "pattern": null, + "coveringDirectory": null } } - ] + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/363_v03_foreach_isolated_finite_array.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/363_v03_foreach_isolated_finite_array.json index 5245daa..f04993e 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/363_v03_foreach_isolated_finite_array.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/363_v03_foreach_isolated_finite_array.json @@ -1,38 +1,139 @@ { "name": "V03 foreach isolated finite array", - "input": "foreach ($f in @('a.txt', 'b.txt', 'a.txt')) { Write-Output $F }", + "input": "foreach ($f in @(\u0027a.txt\u0027, \u0027b.txt\u0027, \u0027a.txt\u0027)) { Write-Output $F }", "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ { "operator": "None", - "verb": ["Write-Output"], + "verb": [ + "Write-Output" + ], "args": [ - { "raw": "$F", "kind": "EnvVar", "isPath": false } + { + "raw": "$F", + "kind": "EnvVar", + "isPath": false + } ], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 64, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 64, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "bindingName": "f", + "bindingRaw": "$f", + "bindingSourceStart": 9, + "bindingSourceLength": 2, + "iterableRaw": "@(\u0027a.txt\u0027, \u0027b.txt\u0027, \u0027a.txt\u0027)", + "iterableSourceStart": 15, + "iterableSourceLength": 28 + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "Iterator", + "childIndex": null, + "sourceStart": 15, + "sourceLength": 28, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 46, + "sourceLength": 17, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": 47, + "sourceLength": 15, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "LoopBody", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 64 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 64 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 46, "sourceLength": 17 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 64 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 64 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 46, + "sourceLength": 17 + } ], "effectiveArguments": [ { "clauseElementIndex": 1, "value": { "kind": "FiniteSet", - "values": ["a.txt", "b.txt"] + "values": [ + "a.txt", + "b.txt" + ], + "pattern": null, + "coveringDirectory": null } } - ] + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/364_v03_foreach_isolated_nested_composition.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/364_v03_foreach_isolated_nested_composition.json index 87a366e..94db2e3 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/364_v03_foreach_isolated_nested_composition.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/364_v03_foreach_isolated_nested_composition.json @@ -1,40 +1,193 @@ { "name": "V03 foreach isolated nested composition", - "input": "foreach ($outer in 'left') { foreach ($inner in @('a','b')) { Write-Output \"$OUTER/$Inner\" } }", + "input": "foreach ($outer in \u0027left\u0027) { foreach ($inner in @(\u0027a\u0027,\u0027b\u0027)) { Write-Output \u0022$OUTER/$Inner\u0022 } }", "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ { "operator": "None", - "verb": ["Write-Output"], + "verb": [ + "Write-Output" + ], "args": [ - { "raw": "\"$OUTER/$Inner\"", "kind": "DynamicSkip", "isPath": false } + { + "raw": "\u0022$OUTER/$Inner\u0022", + "kind": "DynamicSkip", + "isPath": false + } ], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 94, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 94, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "bindingName": "outer", + "bindingRaw": "$outer", + "bindingSourceStart": 9, + "bindingSourceLength": 6, + "iterableRaw": "\u0027left\u0027", + "iterableSourceStart": 19, + "iterableSourceLength": 6 + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "Iterator", + "childIndex": null, + "sourceStart": 19, + "sourceLength": 6, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 28, + "sourceLength": 65, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 63, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "bindingName": "inner", + "bindingRaw": "$inner", + "bindingSourceStart": 38, + "bindingSourceLength": 6, + "iterableRaw": "@(\u0027a\u0027,\u0027b\u0027)", + "iterableSourceStart": 48, + "iterableSourceLength": 10 + }, + { + "kind": "Block", + "parentIndex": 4, + "region": "Iterator", + "childIndex": null, + "sourceStart": 48, + "sourceLength": 10, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 4, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 61, + "sourceLength": 30, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 62, + "sourceLength": 28, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "LoopBody", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 94 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 94 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 28, "sourceLength": 65 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 29, "sourceLength": 63 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 61, "sourceLength": 30 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 94 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 94 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 28, + "sourceLength": 65 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 29, + "sourceLength": 63 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 61, + "sourceLength": 30 + } ], "effectiveArguments": [ { "clauseElementIndex": 1, "value": { "kind": "FiniteSet", - "values": ["left/a", "left/b"] + "values": [ + "left/a", + "left/b" + ], + "pattern": null, + "coveringDirectory": null } } - ] + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/365_v03_foreach_pipeline_objects_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/365_v03_foreach_pipeline_objects_unknown.json index 32808d2..a14fe3b 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/365_v03_foreach_pipeline_objects_unknown.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/365_v03_foreach_pipeline_objects_unknown.json @@ -7,51 +7,193 @@ "clauses": [ { "operator": "None", - "verb": ["Get-ChildItem"], + "verb": [ + "Get-ChildItem" + ], "args": [ - { "raw": "C:\\input", "kind": "Literal", "isPath": true, "resolved": "C:/input" } + { + "raw": "C:\\input", + "kind": "Literal", + "isPath": true, + "resolved": "C:/input" + } ], "redirects": [] }, { "operator": "None", - "verb": ["Write-Output"], + "verb": [ + "Write-Output" + ], "args": [ - { "raw": "$f", "kind": "EnvVar", "isPath": false } + { + "raw": "$f", + "kind": "EnvVar", + "isPath": false + } ], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 58, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 58, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "bindingName": "f", + "bindingRaw": "$f", + "bindingSourceStart": 9, + "bindingSourceLength": 2, + "iterableRaw": "Get-ChildItem C:\\input", + "iterableSourceStart": 15, + "iterableSourceLength": 22 + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "Iterator", + "childIndex": null, + "sourceStart": 15, + "sourceLength": 22, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "Statement", + "childIndex": 0, + "sourceStart": 15, + "sourceLength": 22, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 40, + "sourceLength": 17, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 4, + "region": "Statement", + "childIndex": 0, + "sourceStart": 41, + "sourceLength": 15, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "Iterator", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 58 }, - { "ancestorKind": "ForEach", "region": "Iterator", "childIndex": null, "sourceStart": 0, "sourceLength": 58 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 15, "sourceLength": 22 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 58 + }, + { + "ancestorKind": "ForEach", + "region": "Iterator", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 58 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 15, + "sourceLength": 22 + } ], - "effectiveArguments": [] + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } }, { "clauseIndex": 1, "immediateRole": "LoopBody", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 58 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 58 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 40, "sourceLength": 17 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 58 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 58 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 40, + "sourceLength": 17 + } ], "effectiveArguments": [ { "clauseElementIndex": 1, "value": { "kind": "Unknown", - "values": [] + "values": [], + "pattern": null, + "coveringDirectory": null } } - ] + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/366_v03_foreach_candidate_overflow_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/366_v03_foreach_candidate_overflow_unknown.json index 95fb2f5..0968417 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/366_v03_foreach_candidate_overflow_unknown.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/366_v03_foreach_candidate_overflow_unknown.json @@ -1,38 +1,136 @@ { "name": "V03 foreach candidate overflow unknown", - "input": "foreach ($f in @('v01','v02','v03','v04','v05','v06','v07','v08','v09','v10','v11','v12','v13','v14','v15','v16','v17','v18','v19','v20','v21','v22','v23','v24','v25','v26','v27','v28','v29','v30','v31','v32','v33')) { Write-Output $f }", + "input": "foreach ($f in @(\u0027v01\u0027,\u0027v02\u0027,\u0027v03\u0027,\u0027v04\u0027,\u0027v05\u0027,\u0027v06\u0027,\u0027v07\u0027,\u0027v08\u0027,\u0027v09\u0027,\u0027v10\u0027,\u0027v11\u0027,\u0027v12\u0027,\u0027v13\u0027,\u0027v14\u0027,\u0027v15\u0027,\u0027v16\u0027,\u0027v17\u0027,\u0027v18\u0027,\u0027v19\u0027,\u0027v20\u0027,\u0027v21\u0027,\u0027v22\u0027,\u0027v23\u0027,\u0027v24\u0027,\u0027v25\u0027,\u0027v26\u0027,\u0027v27\u0027,\u0027v28\u0027,\u0027v29\u0027,\u0027v30\u0027,\u0027v31\u0027,\u0027v32\u0027,\u0027v33\u0027)) { Write-Output $f }", "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ { "operator": "None", - "verb": ["Write-Output"], + "verb": [ + "Write-Output" + ], "args": [ - { "raw": "$f", "kind": "EnvVar", "isPath": false } + { + "raw": "$f", + "kind": "EnvVar", + "isPath": false + } ], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 236, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 236, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "bindingName": "f", + "bindingRaw": "$f", + "bindingSourceStart": 9, + "bindingSourceLength": 2, + "iterableRaw": "@(\u0027v01\u0027,\u0027v02\u0027,\u0027v03\u0027,\u0027v04\u0027,\u0027v05\u0027,\u0027v06\u0027,\u0027v07\u0027,\u0027v08\u0027,\u0027v09\u0027,\u0027v10\u0027,\u0027v11\u0027,\u0027v12\u0027,\u0027v13\u0027,\u0027v14\u0027,\u0027v15\u0027,\u0027v16\u0027,\u0027v17\u0027,\u0027v18\u0027,\u0027v19\u0027,\u0027v20\u0027,\u0027v21\u0027,\u0027v22\u0027,\u0027v23\u0027,\u0027v24\u0027,\u0027v25\u0027,\u0027v26\u0027,\u0027v27\u0027,\u0027v28\u0027,\u0027v29\u0027,\u0027v30\u0027,\u0027v31\u0027,\u0027v32\u0027,\u0027v33\u0027)", + "iterableSourceStart": 15, + "iterableSourceLength": 200 + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "Iterator", + "childIndex": null, + "sourceStart": 15, + "sourceLength": 200, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 218, + "sourceLength": 17, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": 219, + "sourceLength": 15, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "LoopBody", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 236 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 236 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 218, "sourceLength": 17 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 236 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 236 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 218, + "sourceLength": 17 + } ], "effectiveArguments": [ { "clauseElementIndex": 1, "value": { "kind": "Unknown", - "values": [] + "values": [], + "pattern": null, + "coveringDirectory": null } } - ] + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/367_v03_foreach_automatic_binding_gated.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/367_v03_foreach_automatic_binding_gated.json index fb7af36..21077fd 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/367_v03_foreach_automatic_binding_gated.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/367_v03_foreach_automatic_binding_gated.json @@ -1,11 +1,11 @@ { "name": "V03 foreach automatic binding gated", - "input": "foreach ($HOME in @('x')) { Write-Output $HOME }", + "input": "foreach ($HOME in @(\u0027x\u0027)) { Write-Output $HOME }", "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": true, - "unparseableReasonContains": "built-in" + "unparseableReasonContains": "PowerShell foreach binding collides with a reserved or stateful built-in variable" }, - "oracleExpectation": "OutOfScope", - "notes": "A fresh host still has built-in automatic and read-only variables." + "notes": "A fresh host still has built-in automatic and read-only variables.", + "oracleExpectation": "OutOfScope" } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/368_v03_foreach_null_object_unknown.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/368_v03_foreach_null_object_unknown.json index 0b9f5db..9bca914 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/368_v03_foreach_null_object_unknown.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/368_v03_foreach_null_object_unknown.json @@ -7,32 +7,130 @@ "clauses": [ { "operator": "None", - "verb": ["Write-Output"], + "verb": [ + "Write-Output" + ], "args": [ - { "raw": "$f", "kind": "EnvVar", "isPath": false } + { + "raw": "$f", + "kind": "EnvVar", + "isPath": false + } ], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 44, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 44, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "bindingName": "f", + "bindingRaw": "$f", + "bindingSourceStart": 9, + "bindingSourceLength": 2, + "iterableRaw": "@($null)", + "iterableSourceStart": 15, + "iterableSourceLength": 8 + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "Iterator", + "childIndex": null, + "sourceStart": 15, + "sourceLength": 8, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 1, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 26, + "sourceLength": 17, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 3, + "region": "Statement", + "childIndex": 0, + "sourceStart": 27, + "sourceLength": 15, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "LoopBody", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 44 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 44 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 26, "sourceLength": 17 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 44 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 44 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 26, + "sourceLength": 17 + } ], "effectiveArguments": [ { "clauseElementIndex": 1, "value": { "kind": "Unknown", - "values": [] + "values": [], + "pattern": null, + "coveringDirectory": null } } - ] + ], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/369_v03_foreach_ordered_persistent_binding.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/369_v03_foreach_ordered_persistent_binding.json index 366603a..31abb4b 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/369_v03_foreach_ordered_persistent_binding.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/369_v03_foreach_ordered_persistent_binding.json @@ -1,61 +1,224 @@ { "name": "V03 foreach ordered persistent binding", - "input": "foreach ($f in @('a','b','a')) { Write-Output $f }; Write-Output $f", + "input": "foreach ($f in @(\u0027a\u0027,\u0027b\u0027,\u0027a\u0027)) { Write-Output $f }; Write-Output $f", "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ { "operator": "None", - "verb": ["Write-Output"], + "verb": [ + "Write-Output" + ], "args": [ - { "raw": "$f", "kind": "EnvVar", "isPath": false } + { + "raw": "$f", + "kind": "EnvVar", + "isPath": false + } ], "redirects": [] }, { "operator": "Sequence", - "verb": ["Write-Output"], + "verb": [ + "Write-Output" + ], "args": [ - { "raw": "$f", "kind": "EnvVar", "isPath": false } + { + "raw": "$f", + "kind": "EnvVar", + "isPath": false + } ], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 67, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 67, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 50, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None", + "bindingName": "f", + "bindingRaw": "$f", + "bindingSourceStart": 9, + "bindingSourceLength": 2, + "iterableRaw": "@(\u0027a\u0027,\u0027b\u0027,\u0027a\u0027)", + "iterableSourceStart": 15, + "iterableSourceLength": 14 + }, + { + "kind": "Block", + "parentIndex": 2, + "region": "Iterator", + "childIndex": null, + "sourceStart": 15, + "sourceLength": 14, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 2, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 32, + "sourceLength": 17, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 4, + "region": "Statement", + "childIndex": 0, + "sourceStart": 33, + "sourceLength": 15, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 52, + "sourceLength": 15, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "LoopBody", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 67 }, - { "ancestorKind": "CommandList", "region": "Statement", "childIndex": 0, "sourceStart": 0, "sourceLength": 67 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 50 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 32, "sourceLength": 17 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 67 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 67 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 50 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 32, + "sourceLength": 17 + } ], "effectiveArguments": [ { "clauseElementIndex": 1, - "value": { "kind": "FiniteSet", "values": ["a", "b"] } + "value": { + "kind": "FiniteSet", + "values": [ + "a", + "b" + ], + "pattern": null, + "coveringDirectory": null + } } ], - "workingDirectory": { "kind": "Exact", "values": ["C:/work"] } + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } }, { "clauseIndex": 1, "immediateRole": "Ordinary", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 67 }, - { "ancestorKind": "CommandList", "region": "Statement", "childIndex": 1, "sourceStart": 0, "sourceLength": 67 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 67 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 67 + } ], "effectiveArguments": [ { "clauseElementIndex": 1, - "value": { "kind": "Exact", "values": ["a"] } + "value": { + "kind": "Exact", + "values": [ + "a" + ], + "pattern": null, + "coveringDirectory": null + } } ], - "workingDirectory": { "kind": "Exact", "values": ["C:/work"] } + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/370_v03_foreach_empty_preserves_cwd.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/370_v03_foreach_empty_preserves_cwd.json index 62824e1..e237dc1 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/370_v03_foreach_empty_preserves_cwd.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/370_v03_foreach_empty_preserves_cwd.json @@ -7,43 +7,186 @@ "clauses": [ { "operator": "None", - "verb": ["Set-Location"], + "verb": [ + "Set-Location" + ], "args": [ - { "raw": "C:\\tmp", "kind": "Literal", "isPath": true, "resolved": "C:/tmp" } + { + "raw": "C:\\tmp", + "kind": "Literal", + "isPath": true, + "resolved": "C:/tmp" + } ], "redirects": [] }, { "operator": "Sequence", - "verb": ["Get-Location"], + "verb": [ + "Get-Location" + ], "args": [], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 57, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 57, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 43, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None", + "bindingName": "f", + "bindingRaw": "$f", + "bindingSourceStart": 9, + "bindingSourceLength": 2, + "iterableRaw": "@()", + "iterableSourceStart": 15, + "iterableSourceLength": 3 + }, + { + "kind": "Block", + "parentIndex": 2, + "region": "Iterator", + "childIndex": null, + "sourceStart": 15, + "sourceLength": 3, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 2, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 21, + "sourceLength": 21, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 4, + "region": "Statement", + "childIndex": 0, + "sourceStart": 22, + "sourceLength": 19, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 45, + "sourceLength": 12, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "LoopBody", "isComplete": false, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 57 }, - { "ancestorKind": "CommandList", "region": "Statement", "childIndex": 0, "sourceStart": 0, "sourceLength": 57 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 43 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 21, "sourceLength": 21 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 57 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 57 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 43 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 21, + "sourceLength": 21 + } ], "effectiveArguments": [], - "workingDirectory": { "kind": "Unknown", "values": [] } + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } }, { "clauseIndex": 1, "immediateRole": "Ordinary", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 57 }, - { "ancestorKind": "CommandList", "region": "Statement", "childIndex": 1, "sourceStart": 0, "sourceLength": 57 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 57 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 57 + } ], "effectiveArguments": [], - "workingDirectory": { "kind": "Exact", "values": ["C:/work"] } + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/371_v03_foreach_location_failure_join.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/371_v03_foreach_location_failure_join.json index 1697694..c35b9af 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/371_v03_foreach_location_failure_join.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/371_v03_foreach_location_failure_join.json @@ -1,57 +1,214 @@ { "name": "V03 foreach location failure join", - "input": "foreach ($d in @('C:\\a', 'C:\\b')) { Set-Location $d }; Get-ChildItem file.txt", + "input": "foreach ($d in @(\u0027C:\\a\u0027, \u0027C:\\b\u0027)) { Set-Location $d }; Get-ChildItem file.txt", "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", "expected": { "isUnparseable": false, "clauses": [ { "operator": "None", - "verb": ["Set-Location"], + "verb": [ + "Set-Location" + ], "args": [ - { "raw": "$d", "kind": "DynamicSkip", "isPath": false } + { + "raw": "$d", + "kind": "DynamicSkip", + "isPath": false + } ], "redirects": [] }, { "operator": "Sequence", - "verb": ["Get-ChildItem"], + "verb": [ + "Get-ChildItem" + ], "args": [ - { "raw": "file.txt", "kind": "DynamicSkip", "isPath": false }, - { "raw": "\u003Cdynamic-cwd\u003E", "kind": "DynamicSkip", "isPath": false, "isCwdAttribution": true } + { + "raw": "file.txt", + "kind": "DynamicSkip", + "isPath": false + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } ], "redirects": [] } ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 77, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 77, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ForEach", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 53, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None", + "bindingName": "d", + "bindingRaw": "$d", + "bindingSourceStart": 9, + "bindingSourceLength": 2, + "iterableRaw": "@(\u0027C:\\a\u0027, \u0027C:\\b\u0027)", + "iterableSourceStart": 15, + "iterableSourceLength": 17 + }, + { + "kind": "Block", + "parentIndex": 2, + "region": "Iterator", + "childIndex": null, + "sourceStart": 15, + "sourceLength": 17, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Block", + "parentIndex": 2, + "region": "LoopBody", + "childIndex": null, + "sourceStart": 35, + "sourceLength": 17, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 4, + "region": "Statement", + "childIndex": 0, + "sourceStart": 36, + "sourceLength": 15, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 55, + "sourceLength": 22, + "clauseIndex": 1, + "groupKind": null, + "listOperator": "Sequence" + } + ], "commands": [ { "clauseIndex": 0, "immediateRole": "LoopBody", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 77 }, - { "ancestorKind": "CommandList", "region": "Statement", "childIndex": 0, "sourceStart": 0, "sourceLength": 77 }, - { "ancestorKind": "ForEach", "region": "LoopBody", "childIndex": null, "sourceStart": 0, "sourceLength": 53 }, - { "ancestorKind": "Block", "region": "Statement", "childIndex": 0, "sourceStart": 35, "sourceLength": 17 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 77 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 77 + }, + { + "ancestorKind": "ForEach", + "region": "LoopBody", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 53 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 35, + "sourceLength": 17 + } ], "effectiveArguments": [ { "clauseElementIndex": 1, - "value": { "kind": "FiniteSet", "values": ["C:\\a", "C:\\b"] } + "value": { + "kind": "FiniteSet", + "values": [ + "C:\\a", + "C:\\b" + ], + "pattern": null, + "coveringDirectory": null + } } ], - "workingDirectory": { "kind": "Unknown", "values": [] } + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } }, { "clauseIndex": 1, "immediateRole": "Ordinary", "isComplete": true, "ancestry": [ - { "ancestorKind": "Block", "region": "Root", "childIndex": 0, "sourceStart": 0, "sourceLength": 77 }, - { "ancestorKind": "CommandList", "region": "Statement", "childIndex": 1, "sourceStart": 0, "sourceLength": 77 } + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 77 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 77 + } ], "effectiveArguments": [], - "workingDirectory": { "kind": "Unknown", "values": [] } + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } } ] }, diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/372_v03_foreach_transition_budget_gated.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/372_v03_foreach_transition_budget_gated.json index 904e83e..ed96e26 100644 --- a/tests/ShellSyntaxTree.Tests/Corpus/powershell/372_v03_foreach_transition_budget_gated.json +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/372_v03_foreach_transition_budget_gated.json @@ -1,10 +1,10 @@ { "name": "V03 foreach transition budget gated", - "input": "foreach ($a in @('a01','a02','a03','a04','a05','a06','a07','a08','a09','a10','a11','a12','a13','a14','a15','a16','a17')) { foreach ($b in @('b01','b02','b03','b04','b05','b06','b07','b08','b09','b10','b11','b12','b13','b14','b15','b16','b17')) { foreach ($c in @('c01','c02','c03','c04','c05','c06','c07','c08','c09','c10','c11','c12','c13','c14','c15')) { Write-Output \"$a$b$c\" } } }", + "input": "foreach ($a in @(\u0027a01\u0027,\u0027a02\u0027,\u0027a03\u0027,\u0027a04\u0027,\u0027a05\u0027,\u0027a06\u0027,\u0027a07\u0027,\u0027a08\u0027,\u0027a09\u0027,\u0027a10\u0027,\u0027a11\u0027,\u0027a12\u0027,\u0027a13\u0027,\u0027a14\u0027,\u0027a15\u0027,\u0027a16\u0027,\u0027a17\u0027)) { foreach ($b in @(\u0027b01\u0027,\u0027b02\u0027,\u0027b03\u0027,\u0027b04\u0027,\u0027b05\u0027,\u0027b06\u0027,\u0027b07\u0027,\u0027b08\u0027,\u0027b09\u0027,\u0027b10\u0027,\u0027b11\u0027,\u0027b12\u0027,\u0027b13\u0027,\u0027b14\u0027,\u0027b15\u0027,\u0027b16\u0027,\u0027b17\u0027)) { foreach ($c in @(\u0027c01\u0027,\u0027c02\u0027,\u0027c03\u0027,\u0027c04\u0027,\u0027c05\u0027,\u0027c06\u0027,\u0027c07\u0027,\u0027c08\u0027,\u0027c09\u0027,\u0027c10\u0027,\u0027c11\u0027,\u0027c12\u0027,\u0027c13\u0027,\u0027c14\u0027,\u0027c15\u0027)) { Write-Output \u0022$a$b$c\u0022 } } }", "expected": { "isUnparseable": true, - "unparseableReasonContains": "exceeded limits" + "unparseableReasonContains": "PowerShell structural syntax exceeded limits or contained invalid parser-owned facts" }, - "oracleExpectation": "OutOfScope", - "notes": "The valid PowerShell source exceeds the shared 4096 loop-body transition budget and fails atomically without a partial authorization projection." + "notes": "The valid PowerShell source exceeds the shared 4096 loop-body transition budget and fails atomically without a partial authorization projection.", + "oracleExpectation": "OutOfScope" } diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/373_v03_foreach_object_parallel_region.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/373_v03_foreach_object_parallel_region.json new file mode 100644 index 0000000..7293bd9 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/373_v03_foreach_object_parallel_region.json @@ -0,0 +1,322 @@ +{ + "name": "V03 foreach object parallel region", + "input": "Get-ChildItem | ForEach-Object -Parallel { Remove-Item $_ }", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "Get-ChildItem" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "Get-ChildItem", + "value": "Get-ChildItem", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 13, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Remove-Item $_ }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 16, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 31, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Remove-Item $_ }", + "value": "{ Remove-Item $_ }", + "role": "Argument", + "sourceStart": 41, + "sourceLength": 18, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Remove-Item" + ], + "args": [ + { + "raw": "$_", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Remove-Item", + "value": "Remove-Item", + "role": "Verb", + "sourceStart": 43, + "sourceLength": 11, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "$_", + "value": "$_", + "role": "Argument", + "sourceStart": 55, + "sourceLength": 2, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 59, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 59, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 13, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 16, + "sourceLength": 43, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 3, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 41, + "sourceLength": 18, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 42, + "sourceLength": 16, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 5, + "region": "Statement", + "childIndex": 0, + "sourceStart": 43, + "sourceLength": 14, + "clauseIndex": 2, + "groupKind": null, + "listOperator": null + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 59 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 59 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 59 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 59 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 59 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 59 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 41, + "sourceLength": 18 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 42, + "sourceLength": 16 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 1, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Parallel exposes a concurrent child-runspace region while the pipeline object remains unresolved." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/374_v03_foreach_object_parallel_process_state_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/374_v03_foreach_object_parallel_process_state_escape.json new file mode 100644 index 0000000..fc50a18 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/374_v03_foreach_object_parallel_process_state_escape.json @@ -0,0 +1,443 @@ +{ + "name": "V03 foreach object parallel process state escape", + "input": "1 | ForEach-Object -Parallel { Set-Item Env:SST_PARALLEL_PATH child }; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Item Env:SST_PARALLEL_PATH child }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Item Env:SST_PARALLEL_PATH child }", + "value": "{ Set-Item Env:SST_PARALLEL_PATH child }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 40, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Env:SST_PARALLEL_PATH", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:SST_PARALLEL_PATH", + "value": "Env:SST_PARALLEL_PATH", + "role": "Argument", + "sourceStart": 40, + "sourceLength": 21, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 62, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 71, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 80, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 88, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 88, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 65, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 40, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 38, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 36, + "clauseIndex": 2, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 71, + "sourceLength": 17, + "clauseIndex": 3, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 88 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 88 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 69 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 88 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 88 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 69 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 88 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 88 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 69 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 40 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 38 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 88 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 88 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A process-wide child provider mutation keeps every command visible and invalidates later host facts." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/375_v03_foreach_object_parallel_fresh_process_state_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/375_v03_foreach_object_parallel_fresh_process_state_escape.json new file mode 100644 index 0000000..83ca9ea --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/375_v03_foreach_object_parallel_fresh_process_state_escape.json @@ -0,0 +1,487 @@ +{ + "name": "V03 foreach object parallel fresh process state escape", + "input": "1,2 | ForEach-Object -Parallel { git status; Set-Item Env:PATH child } -ThrottleLimit 1 -UseNewRunspace", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1,2" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1,2", + "value": "1,2", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 3, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ git status; Set-Item Env:PATH child }", + "kind": "DynamicSkip", + "isPath": false + }, + { + "raw": "-ThrottleLimit", + "kind": "Literal", + "isPath": false + }, + { + "raw": "1", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-UseNewRunspace", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 6, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 21, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ git status; Set-Item Env:PATH child }", + "value": "{ git status; Set-Item Env:PATH child }", + "role": "Argument", + "sourceStart": 31, + "sourceLength": 39, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + }, + { + "raw": "-ThrottleLimit", + "value": "-ThrottleLimit", + "role": "Argument", + "sourceStart": 71, + "sourceLength": 14, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "1", + "value": "1", + "role": "Argument", + "sourceStart": 86, + "sourceLength": 1, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-UseNewRunspace", + "value": "-UseNewRunspace", + "role": "Argument", + "sourceStart": 88, + "sourceLength": 15, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "git", + "status" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "git", + "value": "git", + "role": "Verb", + "sourceStart": 33, + "sourceLength": 3, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "status", + "value": "status", + "role": "Verb", + "sourceStart": 37, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Env:PATH", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 45, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:PATH", + "value": "Env:PATH", + "role": "Argument", + "sourceStart": 54, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 63, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 103, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 103, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 3, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 6, + "sourceLength": 97, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 3, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 39, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 32, + "sourceLength": 37, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 5, + "region": "Statement", + "childIndex": 0, + "sourceStart": 33, + "sourceLength": 35, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 33, + "sourceLength": 10, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 6, + "region": "Statement", + "childIndex": 1, + "sourceStart": 45, + "sourceLength": 23, + "clauseIndex": 3, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 103 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 103 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 103 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 103 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 103 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 103 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 39 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 32, + "sourceLength": 37 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 33, + "sourceLength": 35 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 103 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 103 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 39 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 32, + "sourceLength": 37 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 33, + "sourceLength": 35 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Fresh child runspaces still join process-wide effects from earlier activations." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/376_v03_foreach_object_parallel_rebound_process_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/376_v03_foreach_object_parallel_rebound_process_escape.json new file mode 100644 index 0000000..f4ef36a --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/376_v03_foreach_object_parallel_rebound_process_escape.json @@ -0,0 +1,582 @@ +{ + "name": "V03 foreach object parallel rebound process escape", + "input": "1 | ForEach-Object -Parallel { Set-Alias sstSet Set-Item; sstSet Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F child }; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Alias sstSet Set-Item; sstSet Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F child }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Alias sstSet Set-Item; sstSet Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F child }", + "value": "{ Set-Alias sstSet Set-Item; sstSet Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F child }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 82, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "sstSet", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Set-Item", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "sstSet", + "value": "sstSet", + "role": "Argument", + "sourceStart": 41, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Argument", + "sourceStart": 48, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "sstSet" + ], + "args": [ + { + "raw": "Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "sstSet", + "value": "sstSet", + "role": "Verb", + "sourceStart": 58, + "sourceLength": 6, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F", + "value": "Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F", + "role": "Argument", + "sourceStart": 65, + "sourceLength": 38, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 104, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 113, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 122, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 130, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 111, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 107, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 82, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 80, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 78, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 25, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 1, + "sourceStart": 58, + "sourceLength": 51, + "clauseIndex": 3, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 113, + "sourceLength": 17, + "clauseIndex": 4, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 111 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 111 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 111 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 82 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 80 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 78 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 111 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 82 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 80 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 31, + "sourceLength": 78 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 4, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 130 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 130 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "A child invocation reached after command-resolution mutation may hide a process-wide environment write." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/377_v03_foreach_object_parallel_runspace_global_isolated.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/377_v03_foreach_object_parallel_runspace_global_isolated.json new file mode 100644 index 0000000..cc74b14 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/377_v03_foreach_object_parallel_runspace_global_isolated.json @@ -0,0 +1,475 @@ +{ + "name": "V03 foreach object parallel runspace global isolated", + "input": "1 | ForEach-Object -Parallel { Set-Variable sstChild child -Scope Global }; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Variable sstChild child -Scope Global }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Variable sstChild child -Scope Global }", + "value": "{ Set-Variable sstChild child -Scope Global }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 45, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Variable" + ], + "args": [ + { + "raw": "sstChild", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Scope", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Global", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Variable", + "value": "Set-Variable", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 12, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "sstChild", + "value": "sstChild", + "role": "Argument", + "sourceStart": 44, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 53, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Scope", + "value": "-Scope", + "role": "Argument", + "sourceStart": 59, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "Global", + "value": "Global", + "role": "Argument", + "sourceStart": 66, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/host.txt" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 76, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 85, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/host.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 93, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 93, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 74, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 70, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 45, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 43, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 41, + "clauseIndex": 2, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 76, + "sourceLength": 17, + "clauseIndex": 3, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 93 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 93 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 74 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 93 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 93 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 74 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 93 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 93 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 74 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 45 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 43 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 93 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 93 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Runspace-global variable state remains isolated from the host process continuation." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/378_v03_foreach_object_parallel_rebound_mutator_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/378_v03_foreach_object_parallel_rebound_mutator_escape.json new file mode 100644 index 0000000..8f6d15a --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/378_v03_foreach_object_parallel_rebound_mutator_escape.json @@ -0,0 +1,630 @@ +{ + "name": "V03 foreach object parallel rebound mutator escape", + "input": "1 | ForEach-Object -Parallel { Set-Alias -Name Set-Alias -Value Set-Item; Set-Alias Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F child } -UseNewRunspace; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Alias -Name Set-Alias -Value Set-Item; Set-Alias Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F child }", + "kind": "DynamicSkip", + "isPath": false + }, + { + "raw": "-UseNewRunspace", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Alias -Name Set-Alias -Value Set-Item; Set-Alias Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F child }", + "value": "{ Set-Alias -Name Set-Alias -Value Set-Item; Set-Alias Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F child }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 104, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + }, + { + "raw": "-UseNewRunspace", + "value": "-UseNewRunspace", + "role": "Argument", + "sourceStart": 134, + "sourceLength": 15, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "-Name", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Set-Alias", + "kind": "Literal", + "isPath": false + }, + { + "raw": "-Value", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Set-Item", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Name", + "value": "-Name", + "role": "Argument", + "sourceStart": 41, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Argument", + "sourceStart": 47, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Value", + "value": "-Value", + "role": "Argument", + "sourceStart": 57, + "sourceLength": 6, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Argument", + "sourceStart": 64, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 74, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F", + "value": "Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F", + "role": "Argument", + "sourceStart": 84, + "sourceLength": 41, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 126, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 151, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 160, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 168, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 149, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 145, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 104, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 102, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 100, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 41, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 1, + "sourceStart": 74, + "sourceLength": 57, + "clauseIndex": 3, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 151, + "sourceLength": 17, + "clauseIndex": 4, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 149 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 149 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 149 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 104 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 102 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 100 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 149 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 104 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 102 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 31, + "sourceLength": 100 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 4, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 168 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 168 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "An exact alias mutation can rebind a later authored mutator into a process-wide provider write within the same fresh child runspace." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/379_v03_foreach_object_parallel_unrelated_rebinding_isolated.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/379_v03_foreach_object_parallel_unrelated_rebinding_isolated.json new file mode 100644 index 0000000..a6d4ac4 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/379_v03_foreach_object_parallel_unrelated_rebinding_isolated.json @@ -0,0 +1,584 @@ +{ + "name": "V03 foreach object parallel unrelated rebinding isolated", + "input": "1 | ForEach-Object -Parallel { Set-Alias other Set-Item; Set-Alias untouched Write-Output }; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Alias other Set-Item; Set-Alias untouched Write-Output }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Alias other Set-Item; Set-Alias untouched Write-Output }", + "value": "{ Set-Alias other Set-Item; Set-Alias untouched Write-Output }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 62, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "other", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Set-Item", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "other", + "value": "other", + "role": "Argument", + "sourceStart": 41, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Argument", + "sourceStart": 47, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Set-Alias" + ], + "args": [ + { + "raw": "untouched", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Write-Output", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Alias", + "value": "Set-Alias", + "role": "Verb", + "sourceStart": 57, + "sourceLength": 9, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "untouched", + "value": "untouched", + "role": "Argument", + "sourceStart": 67, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Write-Output", + "value": "Write-Output", + "role": "Argument", + "sourceStart": 77, + "sourceLength": 12, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/host.txt" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 93, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 102, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/host.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 110, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 91, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 87, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 62, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 60, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 58, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 24, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 1, + "sourceStart": 57, + "sourceLength": 32, + "clauseIndex": 3, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 93, + "sourceLength": 17, + "clauseIndex": 4, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 91 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 91 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 91 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 62 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 60 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 58 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 91 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 62 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 60 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 31, + "sourceLength": 58 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 4, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 110 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 110 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Bounded exact-name tracking keeps unrelated runspace-local alias mutations from invalidating host process facts." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/380_v03_foreach_object_parallel_function_provider_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/380_v03_foreach_object_parallel_function_provider_escape.json new file mode 100644 index 0000000..2026454 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/380_v03_foreach_object_parallel_function_provider_escape.json @@ -0,0 +1,738 @@ +{ + "name": "V03 foreach object parallel function provider escape", + "input": "1 | ForEach-Object -Parallel { Set-Item Function:global:sstProviderSet { Set-Item @args }; sstProviderSet Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F child }; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Item Function:global:sstProviderSet { Set-Item @args }; sstProviderSet Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F child }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Item Function:global:sstProviderSet { Set-Item @args }; sstProviderSet Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F child }", + "value": "{ Set-Item Function:global:sstProviderSet { Set-Item @args }; sstProviderSet Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F child }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 126, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Function:global:sstProviderSet", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Item @args }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Function:global:sstProviderSet", + "value": "Function:global:sstProviderSet", + "role": "Argument", + "sourceStart": 40, + "sourceLength": 30, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Set-Item @args }", + "value": "{ Set-Item @args }", + "role": "Argument", + "sourceStart": 71, + "sourceLength": 18, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "@args", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 73, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "@args", + "value": "@args", + "role": "Argument", + "sourceStart": 82, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "sstProviderSet" + ], + "args": [ + { + "raw": "Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "sstProviderSet", + "value": "sstProviderSet", + "role": "Verb", + "sourceStart": 91, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F", + "value": "Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F", + "role": "Argument", + "sourceStart": 106, + "sourceLength": 41, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 148, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 157, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 166, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 174, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 151, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 126, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 124, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 122, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 58, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "ExecutionRegion", + "parentIndex": 8, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 71, + "sourceLength": 18, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Unknown", + "executionTiming": "Unknown", + "executionCardinality": "Unknown" + }, + { + "kind": "Block", + "parentIndex": 9, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 72, + "sourceLength": 16, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 10, + "region": "Statement", + "childIndex": 0, + "sourceStart": 73, + "sourceLength": 14, + "clauseIndex": 3, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 1, + "sourceStart": 91, + "sourceLength": 62, + "clauseIndex": 4, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 157, + "sourceLength": 17, + "clauseIndex": 5, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 126 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 124 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 122 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 126 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 124 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 122 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 71, + "sourceLength": 18 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 72, + "sourceLength": 16 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 4, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 126 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 124 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 31, + "sourceLength": 122 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 5, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 174 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Function-provider scope syntax is normalized to the callable name before checking whether a later child invocation can write process-wide state." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/381_v03_foreach_object_parallel_double_colon_alias_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/381_v03_foreach_object_parallel_double_colon_alias_escape.json new file mode 100644 index 0000000..2e3d9dd --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/381_v03_foreach_object_parallel_double_colon_alias_escape.json @@ -0,0 +1,582 @@ +{ + "name": "V03 foreach object parallel double colon alias escape", + "input": "1 | ForEach-Object -Parallel { Set-Item Alias::sstDoubleAlias Set-Item; sstDoubleAlias Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F child }; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Item Alias::sstDoubleAlias Set-Item; sstDoubleAlias Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F child }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Item Alias::sstDoubleAlias Set-Item; sstDoubleAlias Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F child }", + "value": "{ Set-Item Alias::sstDoubleAlias Set-Item; sstDoubleAlias Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F child }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 104, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Alias::sstDoubleAlias", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Set-Item", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Alias::sstDoubleAlias", + "value": "Alias::sstDoubleAlias", + "role": "Argument", + "sourceStart": 40, + "sourceLength": 21, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Argument", + "sourceStart": 62, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "sstDoubleAlias" + ], + "args": [ + { + "raw": "Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "sstDoubleAlias", + "value": "sstDoubleAlias", + "role": "Verb", + "sourceStart": 72, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F", + "value": "Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F", + "role": "Argument", + "sourceStart": 87, + "sourceLength": 38, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 126, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 135, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 144, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 152, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 133, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 129, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 104, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 102, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 100, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 39, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 1, + "sourceStart": 72, + "sourceLength": 59, + "clauseIndex": 3, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 135, + "sourceLength": 17, + "clauseIndex": 4, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 133 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 133 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 133 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 104 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 102 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 100 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 133 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 104 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 102 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 31, + "sourceLength": 100 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 4, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 152 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 152 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "The provider-qualified double-colon spelling mutates the unprefixed alias name and can hide a process-wide child write." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/382_v03_foreach_object_parallel_double_colon_function_escape.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/382_v03_foreach_object_parallel_double_colon_function_escape.json new file mode 100644 index 0000000..b8f098c --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/382_v03_foreach_object_parallel_double_colon_function_escape.json @@ -0,0 +1,738 @@ +{ + "name": "V03 foreach object parallel double colon function escape", + "input": "1 | ForEach-Object -Parallel { Set-Item Function::sstDoubleFunction { Set-Item @args }; sstDoubleFunction Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F child }; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Item Function::sstDoubleFunction { Set-Item @args }; sstDoubleFunction Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F child }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Item Function::sstDoubleFunction { Set-Item @args }; sstDoubleFunction Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F child }", + "value": "{ Set-Item Function::sstDoubleFunction { Set-Item @args }; sstDoubleFunction Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F child }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 126, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Function::sstDoubleFunction", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Item @args }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Function::sstDoubleFunction", + "value": "Function::sstDoubleFunction", + "role": "Argument", + "sourceStart": 40, + "sourceLength": 27, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "{ Set-Item @args }", + "value": "{ Set-Item @args }", + "role": "Argument", + "sourceStart": 68, + "sourceLength": 18, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "@args", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 70, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "@args", + "value": "@args", + "role": "Argument", + "sourceStart": 79, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "sstDoubleFunction" + ], + "args": [ + { + "raw": "Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "sstDoubleFunction", + "value": "sstDoubleFunction", + "role": "Verb", + "sourceStart": 88, + "sourceLength": 17, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F", + "value": "Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F", + "role": "Argument", + "sourceStart": 106, + "sourceLength": 41, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 148, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true + }, + { + "raw": "\u003Cdynamic-cwd\u003E", + "kind": "DynamicSkip", + "isPath": false, + "isCwdAttribution": true + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 157, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 166, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 174, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 151, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 126, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 124, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 122, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 55, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "ExecutionRegion", + "parentIndex": 8, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 68, + "sourceLength": 18, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Unknown", + "executionTiming": "Unknown", + "executionCardinality": "Unknown" + }, + { + "kind": "Block", + "parentIndex": 9, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 69, + "sourceLength": 16, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 10, + "region": "Statement", + "childIndex": 0, + "sourceStart": 70, + "sourceLength": 14, + "clauseIndex": 3, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 1, + "sourceStart": 88, + "sourceLength": 65, + "clauseIndex": 4, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 157, + "sourceLength": 17, + "clauseIndex": 5, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 126 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 124 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 122 + } + ], + "effectiveArguments": [ + { + "clauseElementIndex": 2, + "value": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 126 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 124 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 122 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 68, + "sourceLength": 18 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 69, + "sourceLength": 16 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 4, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 126 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 124 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 31, + "sourceLength": 122 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 5, + "immediateRole": "Ordinary", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 174 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 174 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Unknown", + "values": [], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "The provider-qualified double-colon spelling mutates the unprefixed function name and can hide a process-wide child write." +} diff --git a/tests/ShellSyntaxTree.Tests/Corpus/powershell/383_v03_foreach_object_parallel_provider_name_colon_precision.json b/tests/ShellSyntaxTree.Tests/Corpus/powershell/383_v03_foreach_object_parallel_provider_name_colon_precision.json new file mode 100644 index 0000000..cf6c014 --- /dev/null +++ b/tests/ShellSyntaxTree.Tests/Corpus/powershell/383_v03_foreach_object_parallel_provider_name_colon_precision.json @@ -0,0 +1,584 @@ +{ + "name": "V03 foreach object parallel provider name colon precision", + "input": "1 | ForEach-Object -Parallel { Set-Item Alias:::sstProviderSet Set-Item; sstProviderSet Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F child }; Get-Item host.txt", + "powerShellInitialStateMode": "IsolatedNonInteractiveNoProfile", + "expected": { + "isUnparseable": false, + "clauses": [ + { + "operator": "None", + "verb": [ + "1" + ], + "args": [], + "redirects": [], + "elements": [ + { + "raw": "1", + "value": "1", + "role": "Verb", + "sourceStart": 0, + "sourceLength": 1, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Pipe", + "verb": [ + "ForEach-Object" + ], + "args": [ + { + "raw": "-Parallel", + "kind": "Literal", + "isPath": false + }, + { + "raw": "{ Set-Item Alias:::sstProviderSet Set-Item; sstProviderSet Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F child }", + "kind": "DynamicSkip", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "ForEach-Object", + "value": "ForEach-Object", + "role": "Verb", + "sourceStart": 4, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "-Parallel", + "value": "-Parallel", + "role": "Argument", + "sourceStart": 19, + "sourceLength": 9, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": true, + "isPath": false + }, + { + "raw": "{ Set-Item Alias:::sstProviderSet Set-Item; sstProviderSet Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F child }", + "value": "{ Set-Item Alias:::sstProviderSet Set-Item; sstProviderSet Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F child }", + "role": "Argument", + "sourceStart": 29, + "sourceLength": 107, + "precedingVerbElementCount": 1, + "kind": "DynamicSkip", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "None", + "verb": [ + "Set-Item" + ], + "args": [ + { + "raw": "Alias:::sstProviderSet", + "kind": "Literal", + "isPath": false + }, + { + "raw": "Set-Item", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Verb", + "sourceStart": 31, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Alias:::sstProviderSet", + "value": "Alias:::sstProviderSet", + "role": "Argument", + "sourceStart": 40, + "sourceLength": 22, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Set-Item", + "value": "Set-Item", + "role": "Argument", + "sourceStart": 63, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "sstProviderSet" + ], + "args": [ + { + "raw": "Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F", + "kind": "Literal", + "isPath": false + }, + { + "raw": "child", + "kind": "Literal", + "isPath": false + } + ], + "redirects": [], + "elements": [ + { + "raw": "sstProviderSet", + "value": "sstProviderSet", + "role": "Verb", + "sourceStart": 73, + "sourceLength": 14, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F", + "value": "Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F", + "role": "Argument", + "sourceStart": 88, + "sourceLength": 40, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "child", + "value": "child", + "role": "Argument", + "sourceStart": 129, + "sourceLength": 5, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": false + } + ] + }, + { + "operator": "Sequence", + "verb": [ + "Get-Item" + ], + "args": [ + { + "raw": "host.txt", + "kind": "Literal", + "isPath": true, + "resolved": "C:/work/host.txt" + } + ], + "redirects": [], + "elements": [ + { + "raw": "Get-Item", + "value": "Get-Item", + "role": "Verb", + "sourceStart": 138, + "sourceLength": 8, + "precedingVerbElementCount": 0, + "kind": "Literal", + "isFlag": false, + "isPath": false + }, + { + "raw": "host.txt", + "value": "host.txt", + "role": "Argument", + "sourceStart": 147, + "sourceLength": 8, + "precedingVerbElementCount": 1, + "kind": "Literal", + "isFlag": false, + "isPath": true, + "resolved": "C:/work/host.txt" + } + ] + } + ], + "syntax": [ + { + "kind": "Block", + "parentIndex": null, + "region": "Unknown", + "childIndex": null, + "sourceStart": 0, + "sourceLength": 155, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 0, + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "Pipeline", + "parentIndex": 1, + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 136, + "clauseIndex": null, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 1, + "clauseIndex": 0, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 2, + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 4, + "sourceLength": 132, + "clauseIndex": 1, + "groupKind": null, + "listOperator": null + }, + { + "kind": "ExecutionRegion", + "parentIndex": 4, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 107, + "clauseIndex": null, + "groupKind": null, + "listOperator": null, + "executionOrigin": "CommandArgument", + "hostClauseElementIndex": 2, + "executionPhase": "Process", + "executionTiming": "Concurrent", + "executionCardinality": "OncePerInputObject" + }, + { + "kind": "Block", + "parentIndex": 5, + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 105, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "CommandList", + "parentIndex": 6, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 103, + "clauseIndex": null, + "groupKind": null, + "listOperator": null + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 40, + "clauseIndex": 2, + "groupKind": null, + "listOperator": "None" + }, + { + "kind": "SimpleCommand", + "parentIndex": 7, + "region": "Statement", + "childIndex": 1, + "sourceStart": 73, + "sourceLength": 61, + "clauseIndex": 3, + "groupKind": null, + "listOperator": "Sequence" + }, + { + "kind": "SimpleCommand", + "parentIndex": 1, + "region": "Statement", + "childIndex": 1, + "sourceStart": 138, + "sourceLength": 17, + "clauseIndex": 4, + "groupKind": null, + "listOperator": "Sequence" + } + ], + "commands": [ + { + "clauseIndex": 0, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 136 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 1, + "immediateRole": "PipelineStage", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 136 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 2, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 136 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 107 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 105 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 31, + "sourceLength": 103 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 3, + "immediateRole": "ExecutionRegion", + "isComplete": false, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "Pipeline", + "region": "PipelineStage", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 136 + }, + { + "ancestorKind": "ExecutionRegion", + "region": "ExecutionRegion", + "childIndex": 0, + "sourceStart": 29, + "sourceLength": 107 + }, + { + "ancestorKind": "Block", + "region": "Statement", + "childIndex": 0, + "sourceStart": 30, + "sourceLength": 105 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 31, + "sourceLength": 103 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + }, + { + "clauseIndex": 4, + "immediateRole": "Ordinary", + "isComplete": true, + "ancestry": [ + { + "ancestorKind": "Block", + "region": "Root", + "childIndex": 0, + "sourceStart": 0, + "sourceLength": 155 + }, + { + "ancestorKind": "CommandList", + "region": "Statement", + "childIndex": 1, + "sourceStart": 0, + "sourceLength": 155 + } + ], + "effectiveArguments": [], + "workingDirectory": { + "kind": "Exact", + "values": [ + "C:/work" + ], + "pattern": null, + "coveringDirectory": null + } + } + ] + }, + "notes": "Only the provider-qualification colon is removed; an additional authored colon remains part of the alias name and does not taint the unprefixed invocation." +} diff --git a/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionBindingCatalogTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionBindingCatalogTests.cs index b358c7d..6cf201d 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionBindingCatalogTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionBindingCatalogTests.cs @@ -282,6 +282,11 @@ public void Parallel_parameter_selects_concurrent_parameter_set() var binding = Assert.Single(result.Bindings); Assert.Equal(ExecutionRegionTiming.Concurrent, binding.Timing); Assert.Equal(ExecutionRegionCardinality.OncePerInputObject, binding.Cardinality); + Assert.False(result.HasUseNewRunspace); + + var fresh = Bind( + "ForEach-Object -Parallel { Get-Date } -UseNewRunspace"); + Assert.True(fresh.HasUseNewRunspace); } [Fact] diff --git a/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionStructuralTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionStructuralTests.cs index 9ab57b6..00400b2 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionStructuralTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/PwshExecutionRegionStructuralTests.cs @@ -179,6 +179,301 @@ public void In_process_invoke_command_joins_body_outcomes_before_host_continuati Assert.True(continuation.IsComplete); } + [Theory] + [InlineData("1 | ForEach-Object -Parallel { Get-Item child.txt }")] + [InlineData("1 | % -Parallel { Get-Item child.txt } -AsJob")] + [InlineData( + "1 | Microsoft.PowerShell.Core\\ForEach-Object " + + "-Parallel { Get-Item child.txt }")] + public void Parallel_publishes_a_concurrent_per_input_child_runspace_region( + string source) + { + var result = ParseIsolated(source); + + var pipeline = Assert.IsType(Assert.Single(result.Syntax.Statements)); + var host = Assert.IsType(pipeline.Stages[1]); + var region = Assert.Single(host.ExecutionRegions); + Assert.Equal(ExecutionRegionOrigin.CommandArgument, region.Origin); + Assert.Equal(ExecutionRegionPhase.Process, region.Phase); + Assert.Equal(ExecutionRegionTiming.Concurrent, region.Timing); + Assert.Equal(ExecutionRegionCardinality.OncePerInputObject, region.Cardinality); + Assert.Equal(3, result.Commands.Count); + Assert.All(result.Commands, command => Assert.True(command.IsComplete)); + } + + [Fact] + public void Parallel_inherits_location_but_not_bindings_and_isolates_child_exit() + { + var result = ParseIsolated( + "foreach ($x in 'outer') { }; 1 | ForEach-Object -Parallel { " + + "Write-Output $x; Get-Item child.txt; Set-Location /tmp; " + + "foreach ($x in 'inner') { } }; Write-Output $x; Get-Item host.txt"); + + var writes = result.Commands + .Where(command => command.Clause.Verb.Tokens[0] == "Write-Output") + .ToArray(); + Assert.Equal(2, writes.Length); + Assert.Empty(writes[0].EffectiveArguments); + Assert.Equal("$x", Assert.Single(writes[0].Clause.Args).Raw); + Assert.Equal( + new[] { "outer" }, + Assert.Single(writes[1].EffectiveArguments).Value.Values); + + var items = result.Commands + .Where(command => command.Clause.Verb.Tokens[0] == "Get-Item") + .ToArray(); + Assert.Equal(2, items.Length); + Assert.All(items, item => Assert.Equal( + new[] { "C:/work" }, + item.WorkingDirectory.Values)); + Assert.All(writes.Concat(items), command => Assert.True(command.IsComplete)); + } + + [Fact] + public void Parallel_process_wide_provider_mutation_invalidates_host_observations() + { + var result = ParseIsolated( + "1 | ForEach-Object -Parallel { " + + "Set-Item Env:SST_PARALLEL_PATH child }; Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.False(continuation.IsComplete); + Assert.Equal( + ShellValueDomainKind.Unknown, + continuation.WorkingDirectory.Kind); + } + + [Theory] + [InlineData("")] + [InlineData(" -UseNewRunspace")] + public void Parallel_unproved_child_command_can_escape_process_state( + string runspaceOption) + { + var result = ParseIsolated( + "1 | ForEach-Object -Parallel { Set-Alias sstSet Set-Item; " + + "sstSet Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F child }" + + runspaceOption + "; Get-Item host.txt"); + + var rebound = Assert.Single( + result.Commands, + command => command.Clause.Verb.Tokens[0] == "sstSet"); + Assert.False(rebound.IsComplete); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.False(continuation.IsComplete); + Assert.Equal( + ShellValueDomainKind.Unknown, + continuation.WorkingDirectory.Kind); + } + + [Theory] + [InlineData("")] + [InlineData(" -UseNewRunspace")] + public void Parallel_rebound_mutator_can_escape_process_state( + string runspaceOption) + { + var result = ParseIsolated( + "1 | ForEach-Object -Parallel { " + + "Set-Alias -Name Set-Alias -Value Set-Item; " + + "Set-Alias Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F child }" + + runspaceOption + "; Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.False(continuation.IsComplete); + Assert.Equal( + ShellValueDomainKind.Unknown, + continuation.WorkingDirectory.Kind); + } + + [Theory] + [InlineData("Set-Item Alias:sstProviderSet Set-Item")] + [InlineData("Set-Item Alias::sstProviderSet Set-Item")] + [InlineData( + "Set-Item Function:global:sstProviderSet { Set-Item @args }")] + [InlineData( + "Set-Item Function::sstProviderSet { Set-Item @args }")] + public void Parallel_provider_rebinding_can_escape_process_state( + string mutation) + { + var result = ParseIsolated( + $"1 | ForEach-Object -Parallel {{ {mutation}; " + + "sstProviderSet Env:SST_PARALLEL_PROVIDER_ESCAPE_91F62F1F child }; " + + "Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.False(continuation.IsComplete); + Assert.Equal( + ShellValueDomainKind.Unknown, + continuation.WorkingDirectory.Kind); + } + + [Fact] + public void Parallel_provider_qualification_preserves_additional_name_colons() + { + var result = ParseIsolated( + "1 | ForEach-Object -Parallel { " + + "Set-Item Alias:::sstProviderSet Set-Item; " + + "sstProviderSet Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F child }; " + + "Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.True(continuation.IsComplete); + Assert.Equal( + new[] { "C:/work" }, + continuation.WorkingDirectory.Values); + } + + [Fact] + public void Parallel_unproved_inline_alias_name_fails_closed() + { + var result = ParseIsolated( + "1 | ForEach-Object -Parallel { " + + "Set-Alias -Name:$n -Value Set-Item; " + + "Set-Alias Env:SST_PARALLEL_DYNAMIC_ALIAS_91F62F1F child }; " + + "Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.False(continuation.IsComplete); + Assert.Equal( + ShellValueDomainKind.Unknown, + continuation.WorkingDirectory.Kind); + } + + [Fact] + public void Parallel_exact_rebinding_does_not_taint_unrelated_mutator_names() + { + var result = ParseIsolated( + "1 | ForEach-Object -Parallel { " + + "Set-Alias other Set-Item; Set-Alias untouched Write-Output }; " + + "Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.True(continuation.IsComplete); + Assert.Equal( + new[] { "C:/work" }, + continuation.WorkingDirectory.Values); + } + + [Theory] + [InlineData("", ShellValueDomainKind.Unknown, false)] + [InlineData(" -UseNewRunspace", ShellValueDomainKind.Exact, true)] + public void Parallel_pooled_rebinding_can_escape_on_a_later_activation( + string runspaceOption, + ShellValueDomainKind expectedCwdKind, + bool expectedComplete) + { + var result = ParseIsolated( + "1,2 | ForEach-Object -Parallel { " + + "sstSet Env:SST_PARALLEL_LATER_ESCAPE_91F62F1F child; " + + "Set-Alias sstSet Set-Item } -ThrottleLimit 1" + + runspaceOption + "; Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.Equal(expectedComplete, continuation.IsComplete); + Assert.Equal(expectedCwdKind, continuation.WorkingDirectory.Kind); + } + + [Theory] + [InlineData("Set-Variable sstChild child -Scope Global")] + [InlineData("Remove-Item Function:sstChild")] + [InlineData("Remove-Item Alias:sstChild")] + public void Parallel_runspace_local_mutation_does_not_escape_to_host( + string mutation) + { + var result = ParseIsolated( + $"1 | ForEach-Object -Parallel {{ {mutation} }}; " + + "Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.True(continuation.IsComplete); + Assert.Equal( + new[] { "C:/work" }, + continuation.WorkingDirectory.Values); + } + + [Fact] + public void Parallel_child_local_provider_mutation_stays_isolated() + { + var result = ParseIsolated( + "1 | ForEach-Object -Parallel { " + + "Set-Item Variable:x child }; Get-Item host.txt"); + + var continuation = result.Commands.Last(); + Assert.Equal("Get-Item", continuation.Clause.Verb.Tokens[0]); + Assert.True(continuation.IsComplete); + Assert.Equal( + new[] { "C:/work" }, + continuation.WorkingDirectory.Values); + } + + [Fact] + public void Parallel_child_runspace_does_not_inherit_host_alias_mutation() + { + var result = ParseIsolated( + "Set-Alias Measure-Command Write-Output; 1 | " + + "Microsoft.PowerShell.Core\\ForEach-Object -Parallel { " + + "Measure-Command { Get-Date } }"); + + var childMeasure = Assert.Single( + result.Commands, + command => command.Clause.Verb.Tokens[0] == "Measure-Command"); + Assert.True(childMeasure.IsComplete); + Assert.True(result.Commands.Last().IsComplete); + } + + [Fact] + public void Parallel_pooled_runspace_command_mutation_joins_later_activations() + { + var result = ParseIsolated( + "1,2 | ForEach-Object -Parallel { " + + "Measure-Command { Get-Date }; " + + "Set-Alias Measure-Command Write-Output } -ThrottleLimit 1"); + + var measure = Assert.Single( + result.Commands, + command => command.Clause.Verb.Tokens[0] == "Measure-Command"); + Assert.False(measure.IsComplete); + } + + [Fact] + public void Parallel_use_new_runspace_keeps_activations_independent() + { + var result = ParseIsolated( + "1,2 | ForEach-Object -Parallel { " + + "Measure-Command { Get-Date }; " + + "Set-Alias Measure-Command Write-Output } " + + "-ThrottleLimit 1 -UseNewRunspace"); + + var measure = Assert.Single( + result.Commands, + command => command.Clause.Verb.Tokens[0] == "Measure-Command"); + Assert.True(measure.IsComplete); + Assert.True(result.Commands.Last().IsComplete); + } + + [Fact] + public void Parallel_use_new_runspace_joins_process_wide_child_effects() + { + var result = ParseIsolated( + "1,2 | ForEach-Object -Parallel { git status; " + + "Set-Item Env:PATH child } -ThrottleLimit 1 -UseNewRunspace"); + + var git = Assert.Single( + result.Commands, + command => command.Clause.Verb.Tokens[0] == "git"); + Assert.False(git.IsComplete); + Assert.Equal(ShellValueDomainKind.Unknown, git.WorkingDirectory.Kind); + } + [Theory] [InlineData("Start-Job -ScriptBlock { Get-Item child.txt }")] [InlineData("sajb { Get-Item child.txt }")] diff --git a/tests/ShellSyntaxTree.Tests/Parsing/ShellValueOracleTests.cs b/tests/ShellSyntaxTree.Tests/Parsing/ShellValueOracleTests.cs index d806806..1ee569c 100644 --- a/tests/ShellSyntaxTree.Tests/Parsing/ShellValueOracleTests.cs +++ b/tests/ShellSyntaxTree.Tests/Parsing/ShellValueOracleTests.cs @@ -1328,6 +1328,164 @@ public void Windows_start_job_psversion_selects_windows_powershell_51() Assert.StartsWith("5.1.", output, StringComparison.Ordinal); } + [Fact] + public void PowerShell_parallel_child_runspace_boundaries_match_the_model() + { + if (!IsAvailable("pwsh")) + { + return; + } + + var output = Run( + "pwsh", + "-NoProfile", + "-NonInteractive", + "-Command", + "$start=(Get-Location).Path; $x='outer'; " + + "$job=1 | ForEach-Object -Parallel { " + + "\"child-cwd=<$((Get-Location).Path -eq $using:start)>\"; " + + "\"child-x=<$x>\"; \"child-using=<$using:x>\"; " + + "Set-Item Env:SST_PARALLEL_BOUNDARY_91F62F1F child; " + + "Set-Location ([IO.Path]::GetPathRoot((Get-Location).Path)); " + + "$x='child'; exit 7 } -AsJob; " + + "Receive-Job -Job $job -Wait; " + + "\"host-cwd=<$((Get-Location).Path -eq $start)>\"; " + + "\"host-x=<$x>\"; " + + "\"host-env=<$env:SST_PARALLEL_BOUNDARY_91F62F1F>\"; " + + "\"job-state=<$($job.State)>\"; " + + "Remove-Job -Job $job"); + + Assert.Equal( + new[] + { + "child-cwd=", + "child-x=<>", + "child-using=", + "host-cwd=", + "host-x=", + "host-env=", + "job-state=", + }, + Lines(output)); + } + + [Fact] + public void PowerShell_parallel_runspace_reuse_matches_the_binding_switch() + { + if (!IsAvailable("pwsh")) + { + return; + } + + var output = Run( + "pwsh", + "-NoProfile", + "-NonInteractive", + "-Command", + "1,2 | ForEach-Object -Parallel { " + + "\"pooled-$_=<$([bool](Get-Alias sstParallelAlias " + + "-ErrorAction Ignore))>\"; " + + "Set-Alias sstParallelAlias Get-Date } -ThrottleLimit 1; " + + "1,2 | ForEach-Object -Parallel { " + + "\"fresh-$_=<$([bool](Get-Alias sstParallelAlias " + + "-ErrorAction Ignore))>\"; " + + "Set-Alias sstParallelAlias Get-Date } " + + "-ThrottleLimit 1 -UseNewRunspace; " + + "Remove-Item Env:SST_PARALLEL_FRESH_91F62F1F -ErrorAction Ignore; " + + "1,2 | ForEach-Object -Parallel { " + + "\"fresh-env-$_=<$env:SST_PARALLEL_FRESH_91F62F1F>\"; " + + "Set-Item Env:SST_PARALLEL_FRESH_91F62F1F child } " + + "-ThrottleLimit 1 -UseNewRunspace"); + + Assert.Equal( + new[] + { + "pooled-1=", + "pooled-2=", + "fresh-1=", + "fresh-2=", + "fresh-env-1=<>", + "fresh-env-2=", + }, + Lines(output)); + } + + [Fact] + public void PowerShell_parallel_process_escape_and_runspace_local_scope_match_the_model() + { + if (!IsAvailable("pwsh")) + { + return; + } + + var output = Run( + "pwsh", + "-NoProfile", + "-NonInteractive", + "-Command", + "Remove-Item Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F " + + "-ErrorAction Ignore; " + + "1 | ForEach-Object -Parallel { " + + "Set-Alias sstSet Set-Item; " + + "sstSet Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F child }; " + + "\"alias-env=<$env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F>\"; " + + "Remove-Item Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F " + + "-ErrorAction Ignore; " + + "1 | ForEach-Object -Parallel { " + + "Set-Alias -Name Set-Alias -Value Set-Item; " + + "Set-Alias Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F child } " + + "-UseNewRunspace; " + + "\"rebound-mutator-env=" + + "<$env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F>\"; " + + "Remove-Item Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F " + + "-ErrorAction Ignore; " + + "1 | ForEach-Object -Parallel { " + + "Set-Item Function:global:sstProviderSet { Set-Item @args }; " + + "sstProviderSet Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F child }; " + + "\"function-env=<$env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F>\"; " + + "Remove-Item Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F " + + "-ErrorAction Ignore; " + + "1 | ForEach-Object -Parallel { " + + "Set-Item Alias::sstDoubleAlias Set-Item; " + + "sstDoubleAlias Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F child }; " + + "\"double-alias-env=<$env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F>\"; " + + "Remove-Item Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F " + + "-ErrorAction Ignore; " + + "1 | ForEach-Object -Parallel { " + + "Set-Item Function::sstDoubleFunction { Set-Item @args }; " + + "sstDoubleFunction Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F child }; " + + "\"double-function-env=" + + "<$env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F>\"; " + + "$global:SST_PARALLEL_LOCAL_91F62F1F='host'; " + + "1 | ForEach-Object -Parallel { " + + "Set-Variable SST_PARALLEL_LOCAL_91F62F1F child -Scope Global }; " + + "\"global-var=<$global:SST_PARALLEL_LOCAL_91F62F1F>\"; " + + "Remove-Item Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F " + + "-ErrorAction Ignore; " + + "Remove-Item Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F " + + "-ErrorAction Ignore; " + + "Remove-Item Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F " + + "-ErrorAction Ignore; " + + "Remove-Item Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F " + + "-ErrorAction Ignore; " + + "Remove-Item Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F " + + "-ErrorAction Ignore; " + + "Remove-Variable SST_PARALLEL_LOCAL_91F62F1F -Scope Global " + + "-ErrorAction Ignore"); + + Assert.Equal( + new[] + { + "alias-env=", + "rebound-mutator-env=", + "function-env=", + "double-alias-env=", + "double-function-env=", + "global-var=", + }, + Lines(output)); + } + private static bool IsAvailable(string executable) { try diff --git a/tools/PwshCorpusTool/CorpusJson.cs b/tools/PwshCorpusTool/CorpusJson.cs index 0ab2f05..ac8a9bd 100644 --- a/tools/PwshCorpusTool/CorpusJson.cs +++ b/tools/PwshCorpusTool/CorpusJson.cs @@ -34,21 +34,28 @@ internal static string BuildEntry( bool includeElements, bool includeStructure, bool includeOptionalAssertions, - bool includeV03Assertions) + bool includeV03Assertions, + PwshInitialStateMode? powerShellInitialStateMode = null) { var obj = new JsonObject { ["name"] = name, ["input"] = input, - ["expected"] = BuildExpected( - parsed, - includeElements, - includeStructure, - includeOptionalAssertions, - includeV03Assertions), - ["notes"] = notes, }; + if (powerShellInitialStateMode is PwshInitialStateMode initialStateMode) + { + obj["powerShellInitialStateMode"] = initialStateMode.ToString(); + } + + obj["expected"] = BuildExpected( + parsed, + includeElements, + includeStructure, + includeOptionalAssertions, + includeV03Assertions); + obj["notes"] = notes; + if (parsed.IsUnparseable && outOfScope) { obj["oracleExpectation"] = "OutOfScope"; diff --git a/tools/PwshCorpusTool/CorpusManifest.cs b/tools/PwshCorpusTool/CorpusManifest.cs index 2757570..891b297 100644 --- a/tools/PwshCorpusTool/CorpusManifest.cs +++ b/tools/PwshCorpusTool/CorpusManifest.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Text; +using ShellSyntaxTree; namespace ShellSyntaxTree.Tools.PwshCorpus; @@ -27,7 +28,8 @@ internal sealed record ManifestEntry( bool IncludeStructure = false, bool IncludeOptionalAssertions = false, bool IncludeV03Assertions = false, - string? DisplayName = null) + string? DisplayName = null, + PwshInitialStateMode? PowerShellInitialStateMode = null) { /// Explicit display name when supplied; otherwise derived from the slug. public string Name => @@ -74,6 +76,31 @@ private static ManifestEntry V(string slug, string input, string notes) => IncludeStructure: true, IncludeV03Assertions: true); + private static ManifestEntry VI(string slug, string input, string notes) => + new( + slug, + input, + notes, + false, + ManifestTransform.None, + IncludeStructure: true, + IncludeV03Assertions: true, + PowerShellInitialStateMode: + PwshInitialStateMode.IsolatedNonInteractiveNoProfile); + + private static ManifestEntry VIE(string slug, string input, string notes) => + new( + slug, + input, + notes, + false, + ManifestTransform.None, + IncludeElements: true, + IncludeStructure: true, + IncludeV03Assertions: true, + PowerShellInitialStateMode: + PwshInitialStateMode.IsolatedNonInteractiveNoProfile); + private static ManifestEntry A(string slug, string name, string input, string notes) => new( slug, @@ -87,6 +114,16 @@ private static ManifestEntry A(string slug, string name, string input, string no private static ManifestEntry Oos(string slug, string input, string notes) => new(slug, input, notes, true, ManifestTransform.None); + private static ManifestEntry OosI(string slug, string input, string notes) => + new( + slug, + input, + notes, + true, + ManifestTransform.None, + PowerShellInitialStateMode: + PwshInitialStateMode.IsolatedNonInteractiveNoProfile); + private static ManifestEntry Enc(string slug, string decoded, string notes, bool oos = false) => new(slug, decoded, notes, oos, ManifestTransform.EncodeCommand); @@ -888,5 +925,71 @@ private static string NestIex(string inner, int depth) Oos("v03_foreach_provider_qualified_iterator_mutation_gated", "foreach ($x in $(Set-Item 'Microsoft.PowerShell.Core\\Alias::wipe' Remove-Item; wipe victim)) { }", "Provider-qualified iterator mutation cannot publish a later alias invocation as complete."), + VI("v03_foreach_isolated_exact_scalar", + "foreach ($f in 'a.txt') { Remove-Item -LiteralPath $f }", + "The explicit constrained-host contract permits one exact loop binding proof."), + VI("v03_foreach_isolated_finite_array", + "foreach ($f in @('a.txt', 'b.txt', 'a.txt')) { Write-Output $F }", + "PowerShell variable lookup is case-insensitive and the public finite domain removes duplicates."), + VI("v03_foreach_isolated_nested_composition", + "foreach ($outer in 'left') { foreach ($inner in @('a','b')) { Write-Output \"$OUTER/$Inner\" } }", + "Distinct active bindings compose without evaluating PowerShell expressions."), + VI("v03_foreach_pipeline_objects_unknown", + "foreach ($f in Get-ChildItem C:\\input) { Write-Output $f }", + "Pipeline output remains an object domain and is never guessed into strings."), + VI("v03_foreach_candidate_overflow_unknown", + "foreach ($f in @('v01','v02','v03','v04','v05','v06','v07','v08','v09','v10','v11','v12','v13','v14','v15','v16','v17','v18','v19','v20','v21','v22','v23','v24','v25','v26','v27','v28','v29','v30','v31','v32','v33')) { Write-Output $f }", + "The candidate cap collapses atomically to Unknown and never truncates the set."), + OosI("v03_foreach_automatic_binding_gated", + "foreach ($HOME in @('x')) { Write-Output $HOME }", + "A fresh host still has built-in automatic and read-only variables."), + VI("v03_foreach_null_object_unknown", + "foreach ($f in @($null)) { Write-Output $f }", + "An array containing null visits once but proves no string value."), + VI("v03_foreach_ordered_persistent_binding", + "foreach ($f in @('a','b','a')) { Write-Output $f }; Write-Output $f", + "The internal visit plan retains a,b,a while the body exposes the distinct finite set and the current-scope continuation sees final a."), + VI("v03_foreach_empty_preserves_cwd", + "foreach ($f in @()) { Set-Location C:\\tmp }; Get-Location", + "The unreachable body stays visible and incomplete, performs no state transition, and cannot leak parser-time cwd attribution into the continuation."), + VI("v03_foreach_location_failure_join", + "foreach ($d in @('C:\\a', 'C:\\b')) { Set-Location $d }; Get-ChildItem file.txt", + "Every candidate is evaluated, but Set-Location failure and divergent successful targets force the body-input join and continuation cwd to Unknown."), + Oos("v03_foreach_transition_budget_gated", + "foreach ($a in @('a01','a02','a03','a04','a05','a06','a07','a08','a09','a10','a11','a12','a13','a14','a15','a16','a17')) { foreach ($b in @('b01','b02','b03','b04','b05','b06','b07','b08','b09','b10','b11','b12','b13','b14','b15','b16','b17')) { foreach ($c in @('c01','c02','c03','c04','c05','c06','c07','c08','c09','c10','c11','c12','c13','c14','c15')) { Write-Output \"$a$b$c\" } } }", + "The valid PowerShell source exceeds the shared 4096 loop-body transition budget and fails atomically without a partial authorization projection."), + VIE("v03_foreach_object_parallel_region", + "Get-ChildItem | ForEach-Object -Parallel { Remove-Item $_ }", + "Parallel exposes a concurrent child-runspace region while the pipeline object remains unresolved."), + VIE("v03_foreach_object_parallel_process_state_escape", + "1 | ForEach-Object -Parallel { Set-Item Env:SST_PARALLEL_PATH child }; Get-Item host.txt", + "A process-wide child provider mutation keeps every command visible and invalidates later host facts."), + VIE("v03_foreach_object_parallel_fresh_process_state_escape", + "1,2 | ForEach-Object -Parallel { git status; Set-Item Env:PATH child } -ThrottleLimit 1 -UseNewRunspace", + "Fresh child runspaces still join process-wide effects from earlier activations."), + VIE("v03_foreach_object_parallel_rebound_process_escape", + "1 | ForEach-Object -Parallel { Set-Alias sstSet Set-Item; sstSet Env:SST_PARALLEL_ALIAS_ESCAPE_91F62F1F child }; Get-Item host.txt", + "A child invocation reached after command-resolution mutation may hide a process-wide environment write."), + VIE("v03_foreach_object_parallel_runspace_global_isolated", + "1 | ForEach-Object -Parallel { Set-Variable sstChild child -Scope Global }; Get-Item host.txt", + "Runspace-global variable state remains isolated from the host process continuation."), + VIE("v03_foreach_object_parallel_rebound_mutator_escape", + "1 | ForEach-Object -Parallel { Set-Alias -Name Set-Alias -Value Set-Item; Set-Alias Env:SST_PARALLEL_REBOUND_MUTATOR_91F62F1F child } -UseNewRunspace; Get-Item host.txt", + "An exact alias mutation can rebind a later authored mutator into a process-wide provider write within the same fresh child runspace."), + VIE("v03_foreach_object_parallel_unrelated_rebinding_isolated", + "1 | ForEach-Object -Parallel { Set-Alias other Set-Item; Set-Alias untouched Write-Output }; Get-Item host.txt", + "Bounded exact-name tracking keeps unrelated runspace-local alias mutations from invalidating host process facts."), + VIE("v03_foreach_object_parallel_function_provider_escape", + "1 | ForEach-Object -Parallel { Set-Item Function:global:sstProviderSet { Set-Item @args }; sstProviderSet Env:SST_PARALLEL_FUNCTION_ESCAPE_91F62F1F child }; Get-Item host.txt", + "Function-provider scope syntax is normalized to the callable name before checking whether a later child invocation can write process-wide state."), + VIE("v03_foreach_object_parallel_double_colon_alias_escape", + "1 | ForEach-Object -Parallel { Set-Item Alias::sstDoubleAlias Set-Item; sstDoubleAlias Env:SST_PARALLEL_DOUBLE_ALIAS_91F62F1F child }; Get-Item host.txt", + "The provider-qualified double-colon spelling mutates the unprefixed alias name and can hide a process-wide child write."), + VIE("v03_foreach_object_parallel_double_colon_function_escape", + "1 | ForEach-Object -Parallel { Set-Item Function::sstDoubleFunction { Set-Item @args }; sstDoubleFunction Env:SST_PARALLEL_DOUBLE_FUNCTION_91F62F1F child }; Get-Item host.txt", + "The provider-qualified double-colon spelling mutates the unprefixed function name and can hide a process-wide child write."), + VIE("v03_foreach_object_parallel_provider_name_colon_precision", + "1 | ForEach-Object -Parallel { Set-Item Alias:::sstProviderSet Set-Item; sstProviderSet Env:SST_PARALLEL_PROVIDER_COLON_91F62F1F child }; Get-Item host.txt", + "Only the provider-qualification colon is removed; an additional authored colon remains part of the alias name and does not taint the unprefixed invocation."), }; } diff --git a/tools/PwshCorpusTool/Program.cs b/tools/PwshCorpusTool/Program.cs index 09b807d..0b18c1c 100644 --- a/tools/PwshCorpusTool/Program.cs +++ b/tools/PwshCorpusTool/Program.cs @@ -20,12 +20,15 @@ // resolver settings as the executable corpus. // The corpus runner pins these resolver knobs; generation must match. -var options = new PwshParserOptions -{ - HomeDirectory = "C:/Users/user", - WorkingDirectory = "C:/work", -}; -var parser = new PwshParser(options); +static PwshParser CreatePwshParser(PwshInitialStateMode? initialStateMode = null) => + new(new PwshParserOptions + { + HomeDirectory = "C:/Users/user", + WorkingDirectory = "C:/work", + InitialStateMode = initialStateMode ?? PwshInitialStateMode.Unknown, + }); + +var parser = CreatePwshParser(); var bashParser = new BashParser(new BashParserOptions { HomeDirectory = "/home/test", @@ -68,7 +71,7 @@ int Generate(string outputDir) foreach (var entry in entries) { var input = entry.ResolveInput(); - var parsed = parser.Parse(input); + var parsed = CreatePwshParser(entry.PowerShellInitialStateMode).Parse(input); var json = CorpusJson.BuildEntry( entry.Name, input, @@ -78,7 +81,8 @@ int Generate(string outputDir) entry.IncludeElements, entry.IncludeStructure, entry.IncludeOptionalAssertions, - entry.IncludeV03Assertions); + entry.IncludeV03Assertions, + entry.PowerShellInitialStateMode); var fileName = $"{index:D3}_{entry.Slug}.json"; File.WriteAllText(Path.Combine(outputDir, fileName), json); index++;