10000 Push locals of automatic variables to 'DottedScopes' when dotting scr… · PowerShell/PowerShell@41f12b1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 41f12b1

Browse files
authored
Push locals of automatic variables to 'DottedScopes' when dotting script cmdlets (#4709)
When dotting a script cmdlet, the locals of automatic variables from the `PSScriptCmdlet` is not set up in the current scope before parameter binding. The fix is to push the locals in `CommandProcessor.OnSetCurrentScope` and pop them in `CommandProcessor.OnRestorePreviousScope`, which will be called from `SetCurrentScopeToExecutionScope` and `RestorePreviousScope` respectively. Summary of changes: 1. When a new local scope is used, currently we set the locals for `CommandProcessor` right before parameter binding (in `BindCommandLineParametersNoValidation`); we set the locals for `ScriptCommandProcessor` in `Prepare`. I moved both to the constructor, right after the new scope is created so that the code is more consistent. 2. In `CmdletParameterBinderController.cs`, we set up the `PSBoundParameters` and `MyInvocation` variables in `HandleCommandLineDynamicParameters` again, which I think is unnecessary because this method is only called 10000 from `BindCommandLineParametersNoValidation`, where the setup is done for the first time. 3. Currently, the locals will be set for dotted script cmdlet in `EnterScope()` and `ExitScope()`. Now, that logic is moved to `OnSetCurrentScope` and `OnRestorePreviousScope` of `CommandProcessor`. This not only makes sure that locals are set before parameter binding, but also is consistent with the `ScriptCommandProcessor`.
1 parent 955e01d commit 41f12b1

File tree

5 files changed

+116
-36
lines changed

5 files changed

+116
-36
lines changed

src/System.Management.Automation/engine/CmdletParameterBinderController.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,7 @@ internal void BindCommandLineParametersNoValidation(Collection<CommandParameterI
215215
var psCompiledScriptCmdlet = this.Command as PSScriptCmdlet;
216216
if (psCompiledScriptCmdlet != null)
217217
{
218-
psCompiledScriptCmdlet.PrepareForBinding(
219-
((ScriptParameterBinder)this.DefaultParameterBinder).LocalScope,
220-
this.CommandLineParameters);
218+
psCompiledScriptCmdlet.PrepareForBinding(this.CommandLineParameters);
221219
}
222220

223221
// Add the passed in arguments to the unboundArguments collection
@@ -1759,14 +1757,6 @@ private void HandleCommandLineDynamicParameters(out ParameterBindingException ou
17591757
{
17601758
s_tracer.WriteLine("Getting the bindable object from the Cmdlet");
17611759

1762-
var psCompiledScriptCmdlet = this.Command as PSScriptCmdlet;
1763-
if (psCompiledScriptCmdlet != null)
1764-
{
1765-
psCompiledScriptCmdlet.PrepareForBinding(
1766-
((ScriptParameterBinder)this.DefaultParameterBinder).LocalScope,
1767-
this.CommandLineParameters);
1768-
}
1769-
17701760
// Now get the dynamic parameter bindable object.
17711761
object dynamicParamBindableObject;
17721762

src/System.Management.Automation/engine/CommandProcessor.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,28 @@ internal override void Prepare(IDictionary psDefaultParameterValues)
225225
BindCommandLineParameters();
226226
}
227227

228+
protected override void OnSetCurrentScope()
229+
{
230+
// When dotting a script cmdlet, push the locals of automatic variables to
231+
// the 'DottedScopes' of the current scope.
232+
PSScriptCmdlet scriptCmdlet = this.Command as PSScriptCmdlet;
233+
if (scriptCmdlet != null && !UseLocalScope)
234+
{
235+
scriptCmdlet.PushDottedScope(CommandSessionState.CurrentScope);
236+
}
237+
}
238+
239+
protected override void OnRestorePreviousScope()
240+
{
241+
// When dotting a script cmdlet, pop the locals of automatic variables from
242+
// the 'DottedScopes' of the current scope.
243+
PSScriptCmdlet scriptCmdlet = this.Command as PSScriptCmdlet;
244+
if (scriptCmdlet != null && !UseLocalScope)
245+
{
246+
scriptCmdlet.PopDottedScope(CommandSessionState.CurrentScope);
247+
}
248+
}
249+
228250
/// <summary>
229251
/// Execute BeginProcessing part of command
230252
/// </summary>
@@ -731,14 +753,18 @@ private void Init(CmdletInfo cmdletInformation)
731753

732754
private void Init(IScriptCommandInfo scriptCommandInfo)
733755
{
734-
InternalCommand scriptCmdlet =
735-
new PSScriptCmdlet(scriptCommandInfo.ScriptBlock, _useLocalScope, FromScriptFile, _context);
736-
756+
var scriptCmdlet = new PSScriptCmdlet(scriptCommandInfo.ScriptBlock, UseLocalScope, FromScriptFile, _context);
737757
this.Command = scriptCmdlet;
738-
this.CommandScope = _useLocalScope
758+
this.CommandScope = UseLocalScope
739759
? this.CommandSessionState.NewScope(_fromScriptFile)
740760
: this.CommandSessionState.CurrentScope;
741761

762+
if (UseLocalScope)
763+
{
764+
// Set the 'LocalsTuple' of the new scope to that of the scriptCmdlet
765+
scriptCmdlet.SetLocalsTupleForNewScope(CommandScope);
766+
}
767+
742768
InitCommon();
743769

744770
// If the script has been dotted, throw an error if it's from a different language mode.

src/System.Management.Automation/engine/ScriptCommandProcessor.cs

Lines changed: 10 additions & 6 deletions
F438
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ private void Init()
269269
_obsoleteAttribute = _scriptBlock.ObsoleteAttribute;
270270
_runOptimizedCode = _scriptBlock.Compile(optimized: _context._debuggingMode > 0 ? false : UseLocalScope);
271271
_localsTuple = _scriptBlock.MakeLocalsTuple(_runOptimizedCode);
272+
273+
if (UseLocalScope)
274+
{
275+
Diagnostics.Assert(CommandScope.LocalsTuple == null, "a newly created scope shouldn't have it's tuple set.");
276+
CommandScope.LocalsTuple = _localsTuple;
277+
}
272278
}
273279

274280
/// <summary>
@@ -282,12 +288,6 @@ internal override ObsoleteAttribute ObsoleteAttribute
282288

283289
internal override void Prepare(IDictionary psDefaultParameterValues)
284290
{
285-
if (UseLocalScope)
286-
{
287-
Diagnostics.Assert(CommandScope.LocalsTuple == null, "a newly created scope shouldn't have it's tuple set.");
288-
CommandScope.LocalsTuple = _localsTuple;
289-
}
290-
291291
_localsTuple.SetAutomaticVariable(AutomaticVariable.MyInvocation, this.Command.MyInvocation, _context);
292292
_scriptBlock.SetPSScriptRootAndPSCommandPath(_localsTuple, _context);
293293
_functionContext = new FunctionContext
@@ -585,6 +585,8 @@ private void EnterScope()
585585

586586
protected override void OnSetCurrentScope()
587587
{
588+
// When dotting a script, push the locals of automatic variables to
589+
// the 'DottedScopes' of the current scope.
588590
if (!UseLocalScope)
589591
{
590592
CommandSessionState.CurrentScope.DottedScopes.Push(_localsTuple);
@@ -593,6 +595,8 @@ protected override void OnSetCurrentScope()
593595

594596
protected override void OnRestorePreviousScope()
595597
{
598+
// When dotting a script, pop the locals of automatic variables from
599+
// the 'DottedScopes' of the current scope.
596600
if (!UseLocalScope)
597601
{
598602
CommandSessionState.CurrentScope.DottedScopes.Pop();

src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,21 +1864,11 @@ internal override void DoEndProcessing()
18641864
private void EnterScope()
18651865
{
18661866
_commandRuntime.SetVariableListsInPipe();
1867-
1868-
if (!_useLocalScope)
1869-
{
1870-
this.Context.SessionState.Internal.CurrentScope.DottedScopes.Push(_localsTuple);
1871-
}
18721867
}
18731868

18741869
private void ExitScope()
18751870
{
18761871
_commandRuntime.RemoveVariableListsInPipe();
1877-
1878-
if (!_useLocalScope)
1879-
{
1880-
this.Context.SessionState.Internal.CurrentScope.DottedScopes.Pop();
1881-
}
18821872
}
18831873

18841874
private void RunClause(Action<FunctionContext> clause, object dollarUnderbar, object inputToProcess)
@@ -1986,12 +1976,33 @@ public object GetDynamicParameters()
19861976
return null;
19871977
}
19881978

1989-
public void PrepareForBinding(SessionStateScope scope, CommandLineParameters commandLineParameters)
1979+
/// <summary>
1980+
/// If the script cmdlet will run in a new local scope, this method is used to set the locals to the newly created scope.
1981+
/// </summary>
1982+
internal void SetLocalsTupleForNewScope(SessionStateScope scope)
1983+
{
1984+
Diagnostics.Assert(scope.LocalsTuple == null, "a newly created scope shouldn't have it's tuple set.");
1985+
scope.LocalsTuple = _localsTuple;
1986+
}
1987+
1988+
/// <summary>
1989+
/// If the script cmdlet is dotted, this method is used to push the locals to the 'DottedScopes' of the current scope.
1990+
/// </summary>
1991+
internal void PushDottedScope(SessionStateScope scope)
1992+
{
1993+
scope.DottedScopes.Push(_localsTuple);
1994+
}
1995+
1996+
/// <summary>
1997+
/// If the script cmdlet is dotted, this method is used to pop the locals from the 'DottedScopes' of the current scope.
1998+
/// </summary>
1999+
internal void PopDottedScope(SessionStateScope scope)
2000+
{
2001+
scope.DottedScopes.Pop();
2002+
}
2003+
2004+
internal void PrepareForBinding(CommandLineParameters commandLineParameters)
19902005
{
1991-
if (_useLocalScope && scope.LocalsTuple == null)
1992-
{
1993-
scope.LocalsTuple = _localsTuple;
1994-
}
19952006
_localsTuple.SetAutomaticVariable(AutomaticVariable.PSBoundParameters,
19962007
commandLineParameters.GetValueToBindToPSBoundParameters(), this.Context);
19972008
_localsTuple.SetAutomaticVariable(AutomaticVariable.MyInvocation, MyInvocation, this.Context);

test/powershell/engine/ParameterBinding/ParameterBinding.Tests.ps1

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,53 @@
280280
$_.Exception.Message | should match "Parameter2"
281281
}
282282
}
283+
284+
Context "Use automatic variables as default value for parameters" {
285+
BeforeAll {
286+
## Explicit use of 'CmdletBinding' make it a script cmdlet
287+
$test1 = @'
288+
[CmdletBinding()]
289+
param ($Root = $PSScriptRoot)
290+
"[$Root]"
291+
'@
292+
## Use of 'Parameter' implicitly make it a script cmdlet
293+
$test2 = @'
294+
param (
295+
[Parameter()]
296+
$Root = $PSScriptRoot
297+
)
298+
"[$Root]"
299+
'@
300+
$tempDir = Join-Path -Path $TestDrive -ChildPath "DefaultValueTest"
301+
$test1File = Join-Path -Path $tempDir -ChildPath "test1.ps1"
302+
$test2File = Join-Path -Path $tempDir -ChildPath "test2.ps1"
303+
304+
$expected = "[$tempDir]"
305+
$psPath = "$PSHOME\powershell"
306+
307+
$null = New-Item -Path $tempDir -ItemType Directory -Force
308+
Set-Content -Path $test1File -Value $test1 -Force
309+
Set-Content -Path $test2File -Value $test2 -Force
310+
}
< AFFA /code>311+
312+
AfterAll {
313+
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
314+
}
315+
316+
It "Test dot-source should evaluate '`$PSScriptRoot' for parameter default value" {
317+
$result = . $test1File
318+
$result | Should Be $expected
319+
320+
$result = . $test2File
321+
$result | Should Be $expected
322+
}
323+
324+
It "Test 'powershell -File' should evaluate '`$PSScriptRoot' for parameter default value" {
325+
$result = & $psPath -NoProfile -File $test1File
326+
$result | Should Be $expected
327+
328+
$result = & $psPath -NoProfile -File $test2File
329+
$result | Should Be $expected
330+
}
331+
}
283332
}

0 commit comments

Comments
 (0)
0