Skip to content

Commit 26469cd

Browse files
committed
Surface unknown PowerShell execution regions
1 parent 89a2e62 commit 26469cd

8 files changed

Lines changed: 597 additions & 20 deletions

File tree

IMPLEMENTATION_PLAN.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,16 @@ priorities.
359359
design-corpus categories are delivered. The shared projector,
360360
compatibility flattener, depth guard, decoded-wrapper cloning, and
361361
executable-corpus DTOs now preserve direct and command-owned regions in
362-
the locked substitution-host-region order. No parser emits a region yet,
363-
and automated execution-region oracle coverage remains in task 7.7.
362+
the locked substitution-host-region order. The PowerShell structural
363+
parser now emits command-argument script blocks as conservative unknown,
364+
incomplete regions, recursively exposes supported body commands, retains
365+
pure output expressions without inventing command occurrences, and fails
366+
atomically on unsupported execution-bearing expressions. Until a receiver
367+
contract proves scope and timing, an unknown region also poisons
368+
subsequent observing cwd, variable, and command-resolution facts so a
369+
continuation cannot reuse stale authorization evidence. Receiver-aware
370+
typed facts and shell-specific state flow remain in tasks 7.5b-7.5e, and
371+
automated execution-region oracle coverage remains in task 7.7.
364372
The PowerShell 7.6.4 receiver and parameter-binding catalog is now
365373
implemented with command-resolution proof as an explicit input. It pins
366374
aliases, supported module qualification, exact and abbreviated/inline

src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshCommandParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ private static bool NextIsAssignmentOperator(IReadOnlyList<PwshToken> tokens, in
413413
}
414414

415415
var v = tokens[j].Value;
416-
return v is "=" or "+=" or "-=" or "*=" or "/=" or "%=";
416+
return v is "=" or "+=" or "-=" or "*=" or "/=" or "%=" or "??=";
417417
}
418418

419419
return false;

src/ShellSyntaxTree/Internal/Pwsh/Parsing/PwshForEachValueAnalysis.cs

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,8 @@ internal sealed class PwshForEachValueAnalyzer
799799
new(ClauseReferenceComparer.Instance);
800800
private bool _isComplete = true;
801801
private int _remainingLoopAnalysisTransitions = MaxLoopAnalysisTransitions;
802+
private long _executionRegionEffectCount;
803+
private long _nonRegionStateMutationCount;
802804

803805
private PwshForEachValueAnalyzer(
804806
PwshParserOptions options,
@@ -861,9 +863,15 @@ private PwshFlowResult AnalyzeNode(ShellSyntaxNode node, AnalysisContext input)
861863
GroupSyntax group => AnalyzeGroup(group, input),
862864
ForEachSyntax forEach => AnalyzeForEach(forEach, input),
863865
CommandSubstitutionSyntax substitution => AnalyzeSubstitution(substitution, input),
864-
_ => PwshFlowResult.Both(input.Invalidate(unknownCwd: true)),
866+
_ => AnalyzeUnsupportedNode(input),
865867
};
866868

869+
private PwshFlowResult AnalyzeUnsupportedNode(AnalysisContext input)
870+
{
871+
_nonRegionStateMutationCount++;
872+
return PwshFlowResult.Both(input.Invalidate(unknownCwd: true));
873+
}
874+
867875
private PwshFlowResult AnalyzeBlock(ShellBlockSyntax block, AnalysisContext input)
868876
{
869877
var flow = PwshFlowResult.Both(input);
@@ -899,7 +907,8 @@ private PwshFlowResult AnalyzeSimple(SimpleCommandSyntax simple, AnalysisContext
899907
var effective = CreateEffectiveArguments(
900908
source.ValueProvenance,
901909
current,
902-
includeUnresolved: isForEachIncomplete);
910+
includeUnresolved: isForEachIncomplete ||
911+
current.CommandResolutionInvalidated);
903912
var mayPromote = current.CanPromote &&
904913
source.HasCompleteValueProvenance &&
905914
simple.Substitutions.Count == 0 &&
@@ -916,38 +925,64 @@ private PwshFlowResult AnalyzeSimple(SimpleCommandSyntax simple, AnalysisContext
916925
var location = AnalyzeSetLocation(simple, current);
917926
if (location is not null)
918927
{
928+
_nonRegionStateMutationCount++;
919929
if (PwshPersistentStateMutation.TryGetEffect(
920930
simple.Clause,
921931
effective,
922932
out var locationEffectUnknownCwd))
923933
{
924934
var flow = location.Value;
925-
return new PwshFlowResult(
935+
return ApplyExecutionRegionEffect(simple, new PwshFlowResult(
926936
flow.OnSuccess is AnalysisContext success
927937
? success.Invalidate(locationEffectUnknownCwd)
928938
: null,
929939
flow.OnFailure is AnalysisContext failure
930940
? failure.Invalidate(locationEffectUnknownCwd)
931-
: null);
941+
: null));
932942
}
933943

934-
return location.Value;
944+
return ApplyExecutionRegionEffect(simple, location.Value);
935945
}
936946

937947
if (simple.Clause.Verb.IsDynamic)
938948
{
939-
return PwshFlowResult.Both(current.Invalidate(unknownCwd: true));
949+
_nonRegionStateMutationCount++;
950+
return ApplyExecutionRegionEffect(
951+
simple,
952+
PwshFlowResult.Both(current.Invalidate(unknownCwd: true)));
940953
}
941954

942955
if (PwshPersistentStateMutation.TryGetEffect(
943956
simple.Clause,
944957
effective,
945958
out var unknownCwd))
946959
{
947-
return PwshFlowResult.Both(current.Invalidate(unknownCwd));
960+
_nonRegionStateMutationCount++;
961+
return ApplyExecutionRegionEffect(
962+
simple,
963+
PwshFlowResult.Both(current.Invalidate(unknownCwd)));
964+
}
965+
966+
return ApplyExecutionRegionEffect(simple, PwshFlowResult.Both(current));
967+
}
968+
969+
private PwshFlowResult ApplyExecutionRegionEffect(
970+
SimpleCommandSyntax simple,
971+
PwshFlowResult flow)
972+
{
973+
if (simple.ExecutionRegions.Count == 0)
974+
{
975+
return flow;
948976
}
949977

950-
return PwshFlowResult.Both(current);
978+
_executionRegionEffectCount++;
979+
return new PwshFlowResult(
980+
flow.OnSuccess is AnalysisContext success
981+
? success.Invalidate(unknownCwd: true)
982+
: null,
983+
flow.OnFailure is AnalysisContext failure
984+
? failure.Invalidate(unknownCwd: true)
985+
: null);
951986
}
952987

953988
private void RecordFacts(
@@ -1044,22 +1079,34 @@ private PwshFlowResult AnalyzeList(CommandListSyntax list, AnalysisContext input
10441079

10451080
private PwshFlowResult AnalyzePipeline(PipelineSyntax pipeline, AnalysisContext input)
10461081
{
1082+
var stageInput = input;
10471083
foreach (var stage in pipeline.Stages)
10481084
{
1049-
var stageFlow = AnalyzeNode(stage, input);
1085+
var executionRegionEffectsBefore = _executionRegionEffectCount;
1086+
var nonRegionMutationsBefore = _nonRegionStateMutationCount;
1087+
var stageFlow = AnalyzeNode(stage, stageInput);
10501088
if (stageFlow.JoinedState is not AnalysisContext stageExit)
10511089
{
10521090
return new PwshFlowResult(null, null);
10531091
}
10541092

1055-
if (!input.StateEquals(stageExit))
1093+
if (!stageInput.StateEquals(stageExit))
10561094
{
1057-
_isComplete = false;
1058-
return new PwshFlowResult(null, null);
1095+
var regionCausedTransition =
1096+
_executionRegionEffectCount > executionRegionEffectsBefore;
1097+
var unrelatedMutationCausedTransition =
1098+
_nonRegionStateMutationCount > nonRegionMutationsBefore;
1099+
if (!regionCausedTransition || unrelatedMutationCausedTransition)
1100+
{
1101+
_isComplete = false;
1102+
return new PwshFlowResult(null, null);
1103+
}
1104+
1105+
stageInput = stageInput.Invalidate(unknownCwd: true);
10591106
}
10601107
}
10611108

1062-
return PwshFlowResult.Both(input);
1109+
return PwshFlowResult.Both(stageInput);
10631110
}
10641111

10651112
private PwshFlowResult AnalyzeGroup(GroupSyntax group, AnalysisContext input)
@@ -1069,11 +1116,15 @@ private PwshFlowResult AnalyzeGroup(GroupSyntax group, AnalysisContext input)
10691116
return AnalyzeBlock(group.Body, input);
10701117
}
10711118

1119+
var executionRegionEffectCount = _executionRegionEffectCount;
1120+
var nonRegionStateMutationCount = _nonRegionStateMutationCount;
10721121
AnalyzeBlock(
10731122
group.Body,
10741123
input.WithoutBindings().Invalidate(
10751124
unknownCwd: false,
10761125
invalidateCommandResolution: false));
1126+
_executionRegionEffectCount = executionRegionEffectCount;
1127+
_nonRegionStateMutationCount = nonRegionStateMutationCount;
10771128
return PwshFlowResult.Both(input);
10781129
}
10791130

@@ -1086,6 +1137,7 @@ private PwshFlowResult AnalyzeSubstitution(
10861137

10871138
private PwshFlowResult AnalyzeForEach(ForEachSyntax forEach, AnalysisContext input)
10881139
{
1140+
_nonRegionStateMutationCount++;
10891141
var plan = _planFactory(forEach);
10901142
if (plan is null)
10911143
{
@@ -1446,6 +1498,16 @@ private SimpleCommandSyntax RewriteSimple(
14461498
facts);
14471499
}
14481500

1501+
var executionRegions = new ExecutionRegionSyntax[simple.ExecutionRegions.Count];
1502+
for (var index = 0; index < executionRegions.Length; index++)
1503+
{
1504+
var region = simple.ExecutionRegions[index];
1505+
executionRegions[index] = region with
1506+
{
1507+
Body = RewriteBlock(region.Body, facts),
1508+
};
1509+
}
1510+
14491511
var source = GetFacts(simple);
14501512
var clause = RewriteCwdCompatibility(
14511513
simple.Clause,
@@ -1465,6 +1527,7 @@ private SimpleCommandSyntax RewriteSimple(
14651527
{
14661528
Clause = clause,
14671529
Substitutions = substitutions,
1530+
ExecutionRegions = executionRegions,
14681531
};
14691532
}
14701533

0 commit comments

Comments
 (0)