Skip to content

Commit abd88e7

Browse files
author
quoctruong
committed
Merge pull request #8 from PowerShell/working
Implement checking for correct return types of functions in DSC Class
2 parents 2728193 + 26a8091 commit abd88e7

8 files changed

+870
-158
lines changed

Engine/Helper.cs

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,23 @@ public bool IsVariableGlobalOrEnvironment(VariableExpressionAst varAst, Ast ast)
380380
return VariableAnalysisDictionary[ast].IsGlobalOrEnvironment(varAst);
381381
}
382382

383+
/// <summary>
384+
/// Checks whether all the code path of ast returns.
385+
/// Runs InitializeVariableAnalysis before calling this method
386+
/// </summary>
387+
/// <param name="ast"></param>
388+
/// <returns></returns>
389+
public bool AllCodePathReturns(Ast ast)
390+
{
391+
if (!VariableAnalysisDictionary.ContainsKey(ast))
392+
{
393+
return true;
394+
}
395+
396+
var analysis = VariableAnalysisDictionary[ast];
397+
return analysis.Exit._predecessors.All(block => block._returns || block._unreachable || block._throws);
398+
}
399+
383400
/// <summary>
384401
/// Initialize Variable Analysis on Ast ast
385402
/// </summary>
@@ -400,6 +417,93 @@ public void InitializeVariableAnalysis(Ast ast)
400417
catch { }
401418
}
402419

420+
/// <summary>
421+
/// Get the return type of ret, which is used in function funcAst in scriptAst ast
422+
/// This function assumes that initialize variable analysis is already run on funcast
423+
/// It also assumes that the pipeline of ret is not null
424+
/// </summary>
425+
/// <param name="funcAst"></param>
426+
/// <param name="ret"></param>
427+
/// <param name="classes"></param>
428+
/// <param name="scriptAst"></param>
429+
/// <returns></returns>
430+
public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret, IEnumerable<TypeDefinitionAst> classes, Ast scriptAst)
431+
{
432+
if (ret == null)
433+
{
434+
return String.Empty;
435+
}
436+
437+
PipelineAst pipe = ret.Pipeline as PipelineAst;
438+
439+
// Handle the case with 1 pipeline element first
440+
if (pipe != null && pipe.PipelineElements.Count == 1)
441+
{
442+
CommandExpressionAst cmAst = pipe.PipelineElements[0] as CommandExpressionAst;
443+
if (cmAst != null)
444+
{
445+
if (cmAst.Expression.StaticType != typeof(object))
446+
{
447+
return cmAst.Expression.StaticType.FullName;
448+
}
449+
450+
VariableExpressionAst varAst = cmAst.Expression as VariableExpressionAst;
451+
452+
if (varAst != null)
453+
{
454+
return GetVariableTypeFromAnalysis(varAst, funcAst);
455+
}
456+
457+
MemberExpressionAst memAst = cmAst.Expression as MemberExpressionAst;
458+
459+
if (memAst != null)
460+
{
461+
VariableAnalysisDetails details = null;
462+
TypeDefinitionAst psClass = null;
463+
464+
if (memAst.Expression is VariableExpressionAst && VariableAnalysisDictionary.ContainsKey(scriptAst))
465+
{
466+
VariableAnalysis VarTypeAnalysis = VariableAnalysisDictionary[scriptAst];
467+
details = VarTypeAnalysis.GetVariableAnalysis(memAst.Expression as VariableExpressionAst);
468+
469+
if (details != null && classes != null)
470+
{
471+
psClass = classes.FirstOrDefault(item => String.Equals(item.Name, details.Type.FullName, StringComparison.OrdinalIgnoreCase));
472+
}
473+
}
474+
475+
return GetTypeFromMemberExpressionAst(memAst, psClass, details);
476+
}
477+
478+
}
479+
}
480+
481+
return String.Empty;
482+
}
483+
484+
/// <summary>
485+
/// Retrieves the type from member expression ast
486+
/// </summary>
487+
/// <param name="memberAst"></param>
488+
/// <param name="psClass"></param>
489+
/// <param name="analysisDetails"></param>
490+
/// <returns></returns>
491+
public string GetTypeFromMemberExpressionAst(MemberExpressionAst memberAst, TypeDefinitionAst psClass, VariableAnalysisDetails analysisDetails)
492+
{
493+
Type result = AssignmentTarget.GetTypeFromMemberExpressionAst(memberAst);
494+
495+
if (result == null && psClass != null && analysisDetails != null)
496+
{
497+
result = AssignmentTarget.GetTypeFromMemberExpressionAst(memberAst, analysisDetails, psClass);
498+
}
499+
500+
if (result != null)
501+
{
502+
return result.FullName;
503+
}
504+
505+
return String.Empty;
506+
}
403507

404508
/// <summary>
405509
/// Get type of variable from the variable analysis
@@ -421,7 +525,8 @@ public string GetVariableTypeFromAnalysis(VariableExpressionAst varAst, Ast ast)
421525
return "";
422526
}
423527
}
424-
catch {
528+
catch
529+
{
425530
return "";
426531
}
427532
}
@@ -440,8 +545,6 @@ public bool HasSpecialVars(string varName)
440545
return false;
441546
}
442547

443-
444-
445548
#endregion
446549
}
447550

Engine/SpecialVars.cs

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Microsoft.Windows.Powershell.ScriptAnalyzer
1616
{
1717
internal class SpecialVars
1818
{
19+
internal static object ThisVariable = new object();
1920
internal const string @foreach = "foreach";
2021
internal const string @switch = "switch";
2122
internal const string Question = "?";
@@ -100,28 +101,28 @@ static SpecialVars()
100101
/* ConfirmPreference */ typeof(ConfirmImpact),
101102
};
102103

103-
internal enum AutomaticVariable
104-
{
105-
Underbar = 0,
106-
Args = 1,
107-
This = 2,
108-
Input = 3,
109-
PSCmdlet = 4,
110-
PSBoundParameters = 5,
111-
MyInvocation = 6,
112-
PSScriptRoot = 7,
113-
PSCommandPath = 8,
104+
internal enum AutomaticVariable
105+
{
106+
Underbar = 0,
107+
Args = 1,
108+
This = 2,
109+
Input = 3,
110+
PSCmdlet = 4,
111+
PSBoundParameters = 5,
112+
MyInvocation = 6,
113+
PSScriptRoot = 7,
114+
PSCommandPath = 8,
114115
NumberOfAutomaticVariables // 1 + the last, used to initialize global scope.
115-
}
116-
117-
internal enum PreferenceVariable
118-
{
119-
Debug = 9,
120-
Verbose = 10,
121-
Error = 11,
122-
WhatIf = 12,
123-
Warning = 13,
124-
Confirm = 14,
116+
}
117+
118+
internal enum PreferenceVariable
119+
{
120+
Debug = 9,
121+
Verbose = 10,
122+
Error = 11,
123+
WhatIf = 12,
124+
Warning = 13,
125+
Confirm = 14,
125126
}
126127

127128
internal const string HistorySize = "MaximumHistoryCount";

Engine/VariableAnalysis.cs

Lines changed: 19 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,6 @@ public VariableAnalysis(IFlowGraph decorated) : base(decorated) { }
3131
private readonly List<LoopGotoTargets> _loopTargets = new List<LoopGotoTargets>();
3232
private Dictionary<string, VariableAnalysisDetails> VariablesDictionary;
3333

34-
/// <summary>
35-
/// Used to analyze scriptbloct, functionmemberast or functiondefinitionast
36-
/// </summary>
37-
/// <param name="ast"></param>
38-
/// <returns></returns>
39-
public static void Analyze(Ast ast)
40-
{
41-
if (ast == null) return;
42-
43-
(new VariableAnalysis(new FlowGraph())).AnalyzeImpl(ast);
44-
}
45-
46-
/// <summary>
47-
/// Analyze a member function, marking variable references as "dynamic" (so they can be reported as errors)
48-
/// and also analyze the control flow to make sure every block returns (or throws)
49-
/// </summary>
50-
/// <param name="ast"></param>
51-
/// <returns></returns>
52-
public static bool AnalyzeMemberFunction(Ast ast)
53-
{
54-
VariableAnalysis va = (new VariableAnalysis(new FlowGraph()));
55-
va.AnalyzeImpl(ast);
56-
return va.Exit._predecessors.All(b => b._returns || b._throws || b._unreachable);
57-
}
58-
5934
/// <summary>
6035
/// Return parameters of a functionmemberast or functiondefinitionast
6136
/// </summary>
@@ -86,10 +61,8 @@ private IEnumerable<ParameterAst> FindParameters(Ast ast, Type type)
8661
}
8762
}
8863

89-
private Dictionary<String, VariableTarget> ProcessParameters(IEnumerable<ParameterAst> parameters)
64+
private void ProcessParameters(IEnumerable<ParameterAst> parameters)
9065
{
91-
Dictionary<String, VariableTarget> varTargets = new Dictionary<String, VariableTarget>();
92-
9366
foreach (var parameter in parameters)
9467
{
9568
var variablePath = parameter.Name.VariablePath;
@@ -128,7 +101,7 @@ private Dictionary<String, VariableTarget> ProcessParameters(IEnumerable<Paramet
128101

129102
var varName = AssignmentTarget.GetUnaliasedVariableName(variablePath);
130103
var details = _variables[varName];
131-
type = type ?? details.Type ?? typeof(object);
104+
details.Type = type ?? details.Type ?? typeof(object);
132105

133106
if (parameter.DefaultValue != null)
134107
{
@@ -147,24 +120,13 @@ private Dictionary<String, VariableTarget> ProcessParameters(IEnumerable<Paramet
147120
// Consider switch or mandatory parameter as already initialized
148121
Entry.AddAst(new AssignmentTarget(varName, type));
149122
}
150-
else if (type != typeof(object))
123+
else
151124
{
152125
VariableTarget varTarget = new VariableTarget(parameter.Name);
153-
varTarget.Type = type;
154-
if (!varTargets.ContainsKey(varTarget.Name))
155-
{
156-
varTargets.Add(varTarget.Name, varTarget);
157-
}
158-
126+
varTarget.Type = details.Type;
159127
Entry.AddAst(varTarget);
160128
}
161-
else
162-
{
163-
Entry.AddAst(new VariableTarget(parameter.Name));
164-
}
165129
}
166-
167-
return varTargets;
168130
}
169131

170132
/// <summary>
@@ -181,24 +143,22 @@ public void AnalyzeImpl(Ast ast)
181143

182144
_variables = FindAllVariablesVisitor.Visit(ast);
183145

184-
Dictionary<String, VariableTarget> varTargets = null;
185-
186146
Init();
187147

188148
if (ast is FunctionMemberAst || ast is FunctionDefinitionAst)
189149
{
190150
IEnumerable<ParameterAst> parameters = FindParameters(ast, ast.GetType());
191151
if (parameters != null)
192152
{
193-
varTargets = ProcessParameters(parameters);
153+
ProcessParameters(parameters);
194154
}
195155
}
196156
else
197157
{
198158
ScriptBlockAst sbAst = ast as ScriptBlockAst;
199159
if (sbAst != null && sbAst.ParamBlock != null && sbAst.ParamBlock.Parameters != null)
200160
{
201-
varTargets = ProcessParameters(sbAst.ParamBlock.Parameters);
161+
ProcessParameters(sbAst.ParamBlock.Parameters);
202162
}
203163
}
204164

@@ -215,29 +175,18 @@ public void AnalyzeImpl(Ast ast)
215175
ast.Visit(this.Decorator);
216176
}
217177

218-
VariablesDictionary = Block.SparseSimpleConstants(_variables, Entry);
178+
Ast parent = ast;
219179

220-
// Update the type of variables in VariablesDictionary based on the param block
221-
foreach (var entry in VariablesDictionary)
180+
while (parent.Parent != null)
222181
{
223-
var analysisDetails = entry.Value;
224-
if (analysisDetails.Type != typeof(Unreached))
225-
{
226-
continue;
227-
}
228-
229-
// This regex is used to extracts the variable name from entry.Key. The key is of the form [varname]s[so]e[eo]
230-
// where [varname] is name of variable [so] is the start offset number and [eo] is the end offset number
231-
var result = Regex.Match(entry.Key, "^(.+)s[0-9]+e[0-9]+$");
232-
if (result.Success && result.Groups.Count == 2)
233-
{
234-
string varName = result.Groups[1].Value;
235-
if (varTargets.ContainsKey(varName))
236-
{
237-
analysisDetails.Type = varTargets[varName].Type;
238-
}
239-
}
182+
parent = parent.Parent;
240183
}
184+
185+
List<TypeDefinitionAst> classes = parent.FindAll(item =>
186+
item is TypeDefinitionAst && (item as TypeDefinitionAst).IsClass, true)
187+
.Cast<TypeDefinitionAst>().ToList();
188+
189+
VariablesDictionary = Block.SparseSimpleConstants(_variables, Entry, classes);
241190
}
242191

243192
/// <summary>
@@ -292,7 +241,7 @@ public bool IsUninitialized(VariableExpressionAst varTarget)
292241
}
293242

294243
var analysis = GetVariableAnalysis(varTarget);
295-
244+
296245
if (analysis == null)
297246
{
298247
return false;
@@ -315,9 +264,9 @@ public bool IsGlobalOrEnvironment(VariableExpressionAst varTarget)
315264
return (varTarget.VariablePath.IsGlobal
316265
|| String.Equals(varTarget.VariablePath.DriveName, "env", StringComparison.OrdinalIgnoreCase));
317266
}
318-
267+
319268
return false;
320-
269+
321270
}
322271

323272
/// <summary>
@@ -391,7 +340,7 @@ public override object VisitAssignmentStatement(AssignmentStatementAst assignmen
391340
// We skip things like $a.test = 3. In this case we will just test
392341
// for variable $a
393342
assignTarget.Visit(this.Decorator);
394-
}
343+
}
395344
}
396345

397346
return null;

0 commit comments

Comments
 (0)