// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Management.Automation.Language; namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { /// /// This class is used to analyze variable based on data flow /// public class VariableAnalysis : AnalysisBase { /// /// Constructor /// /// public VariableAnalysis(IFlowGraph decorated) : base(decorated) { } private Dictionary _variables; private readonly List _loopTargets = new List(); private Dictionary VariablesDictionary; private Dictionary InternalVariablesDictionary; /// /// Return parameters of a functionmemberast or functiondefinitionast /// /// /// /// private IEnumerable FindParameters(Ast ast, Type type) { IEnumerable parameters = ast.FindAll(item => item is ParameterAst, true); foreach (ParameterAst parameter in parameters) { Ast parent = parameter.Parent; while (parent != null) { if (parent.GetType() == type) { if (parent != ast) { break; } yield return parameter; } parent = parent.Parent; } } } private void ProcessParameters(IEnumerable parameters) { foreach (var parameter in parameters) { var variablePath = parameter.Name.VariablePath; bool isSwitchOrMandatory = false; Type type = null; foreach (var paramAst in parameter.Attributes) { if (paramAst is TypeConstraintAst) { if (type == null) { type = paramAst.TypeName.GetReflectionType(); } if (paramAst.TypeName.GetReflectionType() == typeof(System.Management.Automation.SwitchParameter)) { isSwitchOrMandatory = true; } } else if (paramAst is AttributeAst) { var args = (paramAst as AttributeAst).NamedArguments; if (args != null) { foreach (NamedAttributeArgumentAst arg in args) { if (String.Equals(arg.ArgumentName, "mandatory", StringComparison.OrdinalIgnoreCase)) { // check for the case mandatory=$true and just mandatory if (arg.ExpressionOmitted || (!arg.ExpressionOmitted && String.Equals(arg.Argument.Extent.Text, "$true", StringComparison.OrdinalIgnoreCase))) { isSwitchOrMandatory = true; } } } } } } var varName = AssignmentTarget.GetUnaliasedVariableName(variablePath); var details = _variables[varName]; details.Type = type ?? details.Type ?? typeof(object); if (parameter.DefaultValue != null) { var assignTarget = new AssignmentTarget(varName, type); if (parameter.DefaultValue is ConstantExpressionAst) { assignTarget.Constant = (parameter.DefaultValue as ConstantExpressionAst).Value; assignTarget.Type = assignTarget.Constant == null ? typeof(object) : assignTarget.Constant.GetType(); } Entry.AddAst(assignTarget); } else if (isSwitchOrMandatory) { // Consider switch or mandatory parameter as already initialized Entry.AddAst(new AssignmentTarget(varName, type)); } else { VariableTarget varTarget = new VariableTarget(parameter.Name); varTarget.Type = details.Type; Entry.AddAst(varTarget); } } } /// /// Used to analyze scriptblock, functionmemberast or functiondefinitionast /// /// /// public void AnalyzeImpl(Ast ast, VariableAnalysis outerAnalysis) { if (!(ast is ScriptBlockAst || ast is FunctionMemberAst || ast is FunctionDefinitionAst)) { return; } _variables = FindAllVariablesVisitor.Visit(ast); Init(); if (ast is FunctionMemberAst || ast is FunctionDefinitionAst) { IEnumerable parameters = FindParameters(ast, ast.GetType()); if (parameters != null) { ProcessParameters(parameters); } } else { ScriptBlockAst sbAst = ast as ScriptBlockAst; if (sbAst != null && sbAst.ParamBlock != null && sbAst.ParamBlock.Parameters != null) { ProcessParameters(sbAst.ParamBlock.Parameters); } } if (ast is FunctionMemberAst) { (ast as FunctionMemberAst).Body.Visit(this.Decorator); } else if (ast is FunctionDefinitionAst) { (ast as FunctionDefinitionAst).Body.Visit(this.Decorator); } else { ast.Visit(this.Decorator); } Ast parent = ast; while (parent.Parent != null) { parent = parent.Parent; } List classes = parent.FindAll(item => item is TypeDefinitionAst && (item as TypeDefinitionAst).IsClass, true) .Cast().ToList(); if (outerAnalysis != null) { // Initialize the variables from outside var outerDictionary = outerAnalysis.InternalVariablesDictionary; foreach (var details in outerDictionary.Values) { if (details.DefinedBlock != null) { var assignTarget = new AssignmentTarget(details.RealName, details.Type); assignTarget.Constant = details.Constant; if (!_variables.ContainsKey(assignTarget.Name)) { _variables.Add(assignTarget.Name, new VariableAnalysisDetails { Name = assignTarget.Name, RealName = assignTarget.Name, Type = assignTarget.Type }); } Entry.AddFirstAst(assignTarget); } } foreach (var key in _variables.Keys) { if (outerDictionary.ContainsKey(key)) { var outerItem = outerDictionary[key]; var innerItem = _variables[key]; innerItem.Constant = outerItem.Constant; innerItem.Name = outerItem.Name; innerItem.RealName = outerItem.RealName; innerItem.Type = outerItem.Type; } } } var dictionaries = Block.SparseSimpleConstants(_variables, Entry, classes); VariablesDictionary = dictionaries.Item1; InternalVariablesDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var KVP in dictionaries.Item2) { var analysis = KVP.Value; if (analysis == null) { continue; } if (!InternalVariablesDictionary.ContainsKey(analysis.RealName)) { InternalVariablesDictionary.Add(analysis.RealName, analysis); } else { InternalVariablesDictionary[analysis.RealName] = analysis; } } } /// /// Updates the variablesdictionary of the outeranalysis based on that of the inneranalysis /// /// /// internal static void UpdateOuterAnalysis(VariableAnalysis OuterAnalysis, VariableAnalysis InnerAnalysis) { if (OuterAnalysis == null || InnerAnalysis == null) { return; } foreach (var key in InnerAnalysis.VariablesDictionary.Keys) { if (OuterAnalysis.VariablesDictionary.ContainsKey(key)) { OuterAnalysis.VariablesDictionary[key] = InnerAnalysis.VariablesDictionary[key]; } else { OuterAnalysis.VariablesDictionary.Add(key, InnerAnalysis.VariablesDictionary[key]); } } } /// /// Return variableanalysisdetails for VarTarget. /// This function should only be called after Block.SparseSimpleConstants are called. /// /// /// public VariableAnalysisDetails GetVariableAnalysis(VariableExpressionAst varTarget) { if (varTarget == null) { return null; } string key = AnalysisDictionaryKey(varTarget); if (!VariablesDictionary.ContainsKey(key)) { return null; } return VariablesDictionary[key]; } internal static string AnalysisDictionaryKey(VariableExpressionAst varExprAst) { if (varExprAst == null) { return String.Empty; } return String.Format(CultureInfo.CurrentCulture, "{0}s{1}e{2}", AssignmentTarget.GetUnaliasedVariableName(varExprAst.VariablePath), varExprAst.Extent.StartOffset, varExprAst.Extent.EndOffset ); } /// /// Returns true if the variable is initialized. /// This function should only be called after SparseSimpleConstants are called. /// /// /// public bool IsUninitialized(VariableExpressionAst varTarget) { if (varTarget == null) { return false; } var analysis = GetVariableAnalysis(varTarget); if (analysis == null) { return false; } return analysis.DefinedBlock == null && !(SpecialVars.InitializedVariables.Contains(analysis.Name, StringComparer.OrdinalIgnoreCase) || SpecialVars.InitializedVariables.Contains(analysis.RealName, StringComparer.OrdinalIgnoreCase)) && !IsGlobalOrEnvironment(varTarget); } /// /// Returns true if the variable is not a global variable nor environment variable /// /// /// public static bool IsGlobalOrEnvironment(VariableExpressionAst varTarget) { if (varTarget != null) { return (varTarget.VariablePath.IsGlobal || String.Equals(varTarget.VariablePath.DriveName, "env", StringComparison.OrdinalIgnoreCase)); } return false; } /// /// Get assignment targets from expressionast /// /// /// internal static IEnumerable GetAssignmentTargets(ExpressionAst expressionAst) { var parenExpr = expressionAst as ParenExpressionAst; if (parenExpr != null) { foreach (var e in GetAssignmentTargets(parenExpr.Pipeline.GetPureExpression())) { yield return e; } } else { var arrayLiteral = expressionAst as ArrayLiteralAst; if (arrayLiteral != null) { foreach (var e in arrayLiteral.Elements.SelectMany(GetAssignmentTargets)) { yield return e; } } else { yield return expressionAst; } } } /// /// Visit assignment statement /// /// /// public override object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { if (assignmentStatementAst == null) { return null; } base.VisitAssignmentStatement(assignmentStatementAst); foreach (var assignTarget in GetAssignmentTargets(assignmentStatementAst.Left)) { var leftAst = assignTarget; while (leftAst is AttributedExpressionAst) { leftAst = ((AttributedExpressionAst)leftAst).Child; } if (leftAst is VariableExpressionAst) { var varPath = ((VariableExpressionAst)leftAst).VariablePath; if (_variables.ContainsKey(AssignmentTarget.GetUnaliasedVariableName(varPath))) { var details = _variables[AssignmentTarget.GetUnaliasedVariableName(varPath)]; details.AssignedBlocks.Add(Current); } Current.AddAst(new AssignmentTarget(assignmentStatementAst)); } else { // We skip things like $a.test = 3. In this case we will just test // for variable $a assignTarget.Visit(this.Decorator); } } return null; } /// /// Visit variable expression ast /// /// /// public override object VisitVariableExpression(VariableExpressionAst variableExpressionAst) { if (variableExpressionAst == null) return null; var varPath = variableExpressionAst.VariablePath; if (_variables.ContainsKey(AssignmentTarget.GetUnaliasedVariableName(varPath))) { var details = _variables[AssignmentTarget.GetUnaliasedVariableName(varPath)]; Current.AddAst(new VariableTarget(variableExpressionAst)); details.AssociatedAsts.Add(variableExpressionAst); } return base.VisitVariableExpression(variableExpressionAst); } } }