Skip to content

Commit 817b5b2

Browse files
[CI Example Analyzer] Fix some bugs (Azure#18871)
* fix bug for mismatched parameter value type * fix some bugs * fix bugs * Apply suggestions from code review Co-authored-by: Beisi Zhou <[email protected]> * adjust indent Co-authored-by: Beisi Zhou <[email protected]>
1 parent 6f20c36 commit 817b5b2

File tree

3 files changed

+161
-71
lines changed

3 files changed

+161
-71
lines changed

tools/StaticAnalysis/ExampleAnalyzer/AnalyzeRules/ParameterNameAndValue.psm1

Lines changed: 128 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ enum RuleNames {
1515
Mismatched_Parameter_Value_Type
1616
}
1717

18+
$global:UtilityOutputTypePair = @{"ConvertTo-Json" = [string]; "ConvertFrom-Json" = [hashtable]}
19+
1820
<#
1921
.SYNOPSIS
2022
Gets the actual name of the parameter, not alias.
@@ -35,20 +37,40 @@ function Get-ParameterNameNotAlias {
3537
Gets the final actual value from ast.
3638
#>
3739
function Get-FinalVariableValue {
38-
param([System.Management.Automation.Language.Ast]$CommandElementAst)
39-
40+
param([System.Management.Automation.Language.Ast]$CommandElementAst,
41+
[System.Management.Automation.Language.VariableExpressionAst]$VariableExpressionAst = $null)
4042
while ($true) {
4143
if ($null -ne $CommandElementAst.Expression) {
4244
$CommandElementAst = $CommandElementAst.Expression
4345
}
46+
elseif ($null -ne $CommandElementAst.Left) {
47+
if($CommandElementAst.Left -eq $VariableExpressionAst){
48+
if($CommandElementAst.Right -eq $VariableExpressionAst){
49+
$CommandElementAst = $null
50+
}
51+
else{
52+
$CommandElementAst = $CommandElementAst.Right
53+
}
54+
}
55+
else{
56+
$CommandElementAst = $CommandElementAst.Left
57+
}
58+
}
4459
elseif ($null -ne $CommandElementAst.Target) {
4560
$CommandElementAst = $CommandElementAst.Target
4661
}
4762
elseif ($null -ne $CommandElementAst.Pipeline) {
4863
$CommandElementAst = $CommandElementAst.Pipeline
4964
}
5065
elseif ($null -ne $CommandElementAst.PipelineElements) {
51-
$CommandElementAst = $CommandElementAst.PipelineElements[-1]
66+
$LastElement = $CommandElementAst.PipelineElements[-1].Extent.Text
67+
# If the LastElement contains "where" or "sort", then the type isnot changed.
68+
if($LastElement -match "where" -or $LastElement -match "sort"){
69+
$CommandElementAst = $CommandElementAst.PipelineElements[0]
70+
}
71+
else{
72+
$CommandElementAst = $CommandElementAst.PipelineElements[-1]
73+
}
5274
}
5375
elseif($null -ne $CommandElementAst.Elements){
5476
$CommandElementAst = $CommandElementAst.Elements[0]
@@ -98,12 +120,23 @@ function Get-RecoveredValueType{
98120
}
99121
}
100122
else{
123+
if($Items[$j].Value -eq "new"){
124+
return $Type
125+
}
101126
$Member = $Type.GetMembers() | Where-Object {$_.Name -eq $Items[$j]}
102-
if($Member -is [array]){
103-
$Member = $Member[0]
127+
if($null -eq $Member -and $null -ne $Type.ImplementedInterfaces){
128+
for($i = 0; $i -lt $Type.ImplementedInterfaces.Length; $i++){
129+
$Member = $Type.ImplementedInterfaces[$i].GetMembers() | Where-Object {$_.Name -eq $Items[$j]}
130+
if($null -ne $Member){
131+
break
132+
}
133+
}
104134
}
105135
if($null -eq $Member){
106-
return $null
136+
return $null
137+
}
138+
if($Member -is [array]){
139+
$Member = $Member[0]
107140
}
108141
if($null -ne $Member.PropertyType){
109142
$Type = $Member.PropertyType
@@ -119,6 +152,36 @@ function Get-RecoveredValueType{
119152
return $Type
120153
}
121154

155+
<#
156+
.SYNOPSIS
157+
Measure whether the actual type matches the expected type.
158+
#>
159+
function Measure-IsTypeMatched{
160+
param (
161+
[System.Reflection.TypeInfo]$ExpectedType,
162+
[System.Reflection.TypeInfo]$ActualType
163+
)
164+
if($ActualType.IsArray) {
165+
$ActualType = $ActualType.GetElementType()
166+
}
167+
if($ActualType.IsGenericType){
168+
$ActualType = $ActualType.GetGenericArguments()[0]
169+
}
170+
$Converter = [System.ComponentModel.TypeDescriptor]::GetConverter($ExpectedType)
171+
if ($ActualType -eq $ExpectedType -or
172+
$ActualType.GetInterfaces().Contains($ExpectedType) -or
173+
$ExpectedType.GetInterfaces().Contains($ActualType) -or
174+
$ActualType.IsSubclassOf($ExpectedType) -or
175+
$Converter.CanConvertFrom($ActualType)) {
176+
return $true
177+
}
178+
return $false
179+
}
180+
181+
<#
182+
.SYNOPSIS
183+
Gets the expression's actual value and type, if the parameter is assigned with a value.
184+
#>
122185
function Get-AssignedParameterExpression {
123186
param (
124187
[System.Management.Automation.CommandInfo]$GetCommand,
@@ -134,24 +197,45 @@ function Get-AssignedParameterExpression {
134197
break
135198
}
136199
# Get the actual value
137-
$CommandElement_Copy = Get-FinalVariableValue $global:AssignmentLeftAndRight.($CommandElement_Copy.Extent.Text)
200+
$CommandElement_Copy = Get-FinalVariableValue $global:AssignmentLeftAndRight.($CommandElement_Copy.Extent.Text) $CommandElement_Copy
138201
if ($null -eq $CommandElement_Copy) {
139202
# Variable is not assigned with a value.
140203
# Unassigned_Variable
141204
$ExpressionToParameter = $CommandElement.Extent.Text + " is a null-valued parameter value."
142205
return $ExpressionToParameter
143206
}
144207
}
208+
if($CommandElement_Copy.Extent.Text -match "foreach" -or $CommandElement_Copy.Extent.Text -match "select"){
209+
Write-Debug "The CommandElement contains 'foreach' or 'select'. This situation can not be handled now."
210+
return $null
211+
}
212+
$ExpectedType = $GetCommand.Parameters.$ParameterNameNotAlias.ParameterType
213+
if($CommandElement_Copy -is [System.Management.Automation.Language.HashtableAst]){
214+
# If ExpectedType is ValueType, then it cannot be created by Hashtable.
215+
if($ExpectedType.IsValueType){
216+
# Mismatched_Parameter_Value_Type
217+
$ExpressionToParameter = "$($CommandElement.Extent.Text)-#-$ExpectedType, created by hashtable but is value type."
218+
return $ExpressionToParameter
219+
}
220+
return $null
221+
}
222+
while ($ExpectedType.IsArray) {
223+
$ExpectedType = $ExpectedType.GetElementType()
224+
}
225+
if($ExpectedType.IsGenericType){
226+
$ExpectedType = $ExpectedType.GetGenericArguments()[0]
227+
}
145228
if ($CommandElement_Copy -is [System.Management.Automation.Language.CommandAst]) {
146229
# Value is an command
147230
# If the value is created by "New-Object", then get the type behind "New-Object".
148231
if($CommandElement_Copy.CommandElements[0].Extent.Text -eq "New-Object"){
149232
if($CommandElement_Copy.CommandElements[1].Extent.Text -eq "-TypeName"){
150-
$OutputType = $CommandElement_Copy.CommandElements[2].Extent.Text -as [Type]
233+
$TypeName = $CommandElement_Copy.CommandElements[2].Extent.Text -replace "`""
151234
}
152235
else{
153-
$OutputType = $CommandElement_Copy.CommandElements[1].Extent.Text -as [Type]
236+
$TypeName = $CommandElement_Copy.CommandElements[1].Extent.Text
154237
}
238+
$OutputType = $TypeName -as [Type]
155239
$OutputTypes = @() + $OutputType
156240
}
157241
else{
@@ -162,10 +246,16 @@ function Get-AssignedParameterExpression {
162246
return $null
163247
}
164248
$OutputTypes = @()
165-
$j = 0
166-
while($GetElementCommand.OutputType[$j]){
167-
$OutputTypes += $GetElementCommand.OutputType[$j].Type
168-
$j++
249+
if($global:UtilityOutputTypePair.ContainsKey($GetElementCommand.Name)){
250+
$OutputType = $global:UtilityOutputTypePair.($GetElementCommand.Name)
251+
$OutputTypes += $OutputType
252+
}
253+
else{
254+
$j = 0
255+
while($GetElementCommand.OutputType[$j]){
256+
$OutputTypes += $GetElementCommand.OutputType[$j].Type
257+
$j++
258+
}
169259
}
170260
}
171261
$flag = $true
@@ -174,63 +264,43 @@ function Get-AssignedParameterExpression {
174264
$ReturnType = $OutputTypes[$j]
175265
$j++
176266
$ActualType = Get-RecoveredValueType $CommandElement $ReturnType
177-
$ExpectedType = $GetCommand.Parameters.$ParameterNameNotAlias.ParameterType
178267
if($null -eq $ActualType){
179268
Continue
180269
}
181-
if ($ExpectedType.IsArray) {
182-
$ExpectedType = $ExpectedType.GetElementType()
183-
}
184-
if($ActualType.IsArray) {
185-
$ActualType = $ActualType.GetElementType()
186-
}
187-
if($ActualType.IsGenericType){
188-
$ActualType = $ActualType.GetGenericArguments()[0]
189-
}
190-
if($ExpectedType.IsGenericType){
191-
$ExpectedType = $ExpectedType.GetGenericArguments()[0]
192-
}
193-
if ($ActualType -eq $ExpectedType -or $ActualType -is $ExpectedType -or
194-
$ActualType.GetInterfaces().Contains($ExpectedType) -or $ExpectedType.GetInterfaces().Contains($ActualType)) {
270+
if(Measure-IsTypeMatched $ExpectedType $ActualType){
195271
$flag = $false
272+
break
196273
}
197274
}
198275
if($flag){
199276
# Mismatched_Parameter_Value_Type
200-
$ExpressionToParameter = "$($CommandElement.Extent.Text)-#-$ExpectedType"
277+
$ExpressionToParameter = "$($CommandElement.Extent.Text)-#-$ExpectedType. Now the type is $ActualType.(Command)"
201278
return $ExpressionToParameter
202279
}
203-
204280
}
205-
elseif($CommandElement_Copy -is [System.Management.Automation.Language.HashtableAst]){
206-
# If ExpectedType is ValueType, then it cannot be created by Hashtable.
207-
if($ExpectedType.IsValueType){
281+
elseif($CommandElement_Copy -is [System.Management.Automation.Language.TypeExpressionAst] -or
282+
$CommandElement_Copy -is [System.Management.Automation.Language.TypeConstraintAst]){
283+
$ReturnType = $CommandElement_Copy.TypeName.ToString() -as [Type]
284+
$ActualType = Get-RecoveredValueType $CommandElement $ReturnType
285+
if (!(Measure-IsTypeMatched $ExpectedType $ActualType)) {
208286
# Mismatched_Parameter_Value_Type
209-
$ExpressionToParameter = "$($CommandElement.Extent.Text)-#-$ExpectedType"
287+
$ExpressionToParameter = "$($CommandElement.Extent.Text)-#-$ExpectedType. Now the type is $ActualType.(Type)"
210288
return $ExpressionToParameter
211289
}
212290
}
213291
elseif($CommandElement_Copy -is [System.Management.Automation.Language.ExpressionAst]) {
214-
# Value is a constant expression
215-
$ExpectedType = $GetCommand.Parameters.$ParameterNameNotAlias.ParameterType
292+
# Value is a constant expression
216293
$ConvertedObject = $CommandElement_Copy.Extent.text -as $ExpectedType
217-
$StaticType = $CommandElement_Copy.StaticType
218-
if($ExpectedType.IsGenericType){
219-
$ExpectedType = $ExpectedType.GetGenericArguments()[0]
220-
}
221-
if($StaticType.IsGenericType){
222-
$StaticType = $StaticType.GetGenericArguments()[0]
223-
}
224-
if ($ExpectedType.IsArray){
225-
$ExpectedType = $ExpectedType.GetElementType()
226-
}
227-
if($StaticType.IsArray){
228-
$StaticType = $StaticType.GetElementType()
294+
if($null -eq $ConvertedObject){
295+
if($null -ne (Get-Variable | Where-Object {$_.Name -eq $CommandElement_Copy.VariablePath})){
296+
$value = (Get-Variable | Where-Object {$_.Name -eq $CommandElement_Copy.VariablePath}).Value
297+
}
298+
$ConvertedObject = $value -as $ExpectedType
229299
}
230-
if ($StaticType -ne $ExpectedType -and $null -eq $ConvertedObject -and
231-
!$StaticType.GetInterfaces().Contains($ExpectedType) -and !$ExpectedType.GetInterfaces().Contains($StaticType)) {
300+
$StaticType = $CommandElement_Copy.StaticType
301+
if (!(Measure-IsTypeMatched $ExpectedType $StaticType) -and $null -eq $ConvertedObject) {
232302
# Mismatched_Parameter_Value_Type
233-
$ExpressionToParameter = "$($CommandElement.Extent.Text)-#-$ExpectedType"
303+
$ExpressionToParameter = "$($CommandElement.Extent.Text)-#-$ExpectedType. Now the type is $StaticType.(Static)"
234304
return $ExpressionToParameter
235305
}
236306
}
@@ -272,7 +342,12 @@ function Measure-ParameterNameAndValue {
272342

273343
if ($Ast -is [System.Management.Automation.Language.AssignmentStatementAst]) {
274344
[System.Management.Automation.Language.AssignmentStatementAst]$AssignmentStatementAst = $Ast
275-
$global:AssignmentLeftAndRight.($AssignmentStatementAst.Left.Extent.Text) = $AssignmentStatementAst.Right
345+
if($AssignmentStatementAst.Left -is [System.Management.Automation.Language.ConvertExpressionAst]){
346+
$global:AssignmentLeftAndRight.($AssignmentStatementAst.Left.Child.Extent.Text) = $AssignmentStatementAst.Left.Type
347+
}
348+
elseif($AssignmentStatementAst.Left -is [System.Management.Automation.Language.VariableExpressionAst]){
349+
$global:AssignmentLeftAndRight.($AssignmentStatementAst.Left.Extent.Text) = $AssignmentStatementAst.Right
350+
}
276351
}
277352

278353
if ($Ast -is [System.Management.Automation.Language.CommandElementAst] -and $Ast.Parent -is [System.Management.Automation.Language.CommandAst]) {
@@ -669,4 +744,4 @@ function Measure-ParameterNameAndValue {
669744
}
670745
}
671746

672-
Export-ModuleMember -Function Measure-*
747+
Export-ModuleMember -Function Measure-*

tools/StaticAnalysis/ExampleAnalyzer/utils.ps1

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,21 @@ function Get-ExamplesDetailsFromMd {
7575
$indexOfExamples = $fileContent.IndexOf($EXAMPLES_HEADING)
7676
$indexOfParameters = $fileContent.IndexOf($PARAMETERS_HEADING)
7777

78-
$exampleNumber = 0
78+
$exampleNumber = -1
7979
$examplesProperties = @()
8080
$examplesContent = $fileContent.Substring($indexOfExamples, $indexOfParameters - $indexOfExamples)
8181
$examplesTitles = ($examplesContent | Select-String -Pattern $SINGLE_EXAMPLE_TITLE_HEADING_REGEX -AllMatches).Matches
8282
$examplesContentWithoutTitle = $examplesContent -split $SINGLE_EXAMPLE_TITLE_HEADING_REGEX | Select-Object -Skip 1
8383
foreach ($exampleContent in $examplesContentWithoutTitle) {
84+
$exampleNumber++
8485
# Skip the autogenerated example
8586
if($exampleContent -match "\(autogenerated\)"){
8687
continue
8788
}
89+
# Skip the example whose output can not be splitted from code
90+
if($exampleContent -match "<!-- Skip.*-->"){
91+
continue
92+
}
8893
$exampleTitle = ($examplesTitles[$exampleNumber].Value -split $SINGLE_EXAMPLE_HEADING_REGEX)[1].Trim()
8994
$exampleCodes = @()
9095
$exampleOutputs = @()
@@ -323,26 +328,28 @@ function Measure-SectionMissingAndOutputScript {
323328
$examplesDetails = Get-ExamplesDetailsFromMd $MarkdownPath
324329
# If no examples
325330
if ($examplesDetails.Count -eq 0) {
326-
$missingExampleTitle++
327-
$missingExampleCode++
328-
$missingExampleOutput++
329-
$missingExampleDescription++
330-
$result = [AnalysisOutput]@{
331-
Module = $Module
332-
Cmdlet = $Cmdlet
333-
Example = ""
334-
Description = "Example is missing."
335-
RuleName = "MissingExample"
336-
Severity = $missingSeverity
337-
Extent = "$Module\help\$Cmdlet.md"
338-
ProblemID = 5042
339-
Remediation = "Add Example. Remove any placeholders."
331+
if($fileContent -notmatch "\(autogenerated\)" -and $fileContent -notmatch "<!-- Skip.*-->"){
332+
$missingExampleTitle++
333+
$missingExampleCode++
334+
$missingExampleOutput++
335+
$missingExampleDescription++
336+
$result = [AnalysisOutput]@{
337+
Module = $Module
338+
Cmdlet = $Cmdlet
339+
Example = ""
340+
Description = "Example is missing."
341+
RuleName = "MissingExample"
342+
Severity = $missingSeverity
343+
Extent = "$Module\help\$Cmdlet.md"
344+
ProblemID = 5042
345+
Remediation = "Add Example. Remove any placeholders."
346+
}
347+
$results += $result
340348
}
341-
$results += $result
342349
}
343350
else {
344351
foreach ($exampleDetails in $examplesDetails) {
345-
$exampleNumber++
352+
$exampleNumber = $exampleDetails.Num
346353
$_missingExampleTitle = ($exampleDetails.Title | Select-String -Pattern "{{[A-Za-z ]*}}").Count
347354
$_missingExampleCode = ($exampleDetails.Codes | Select-String -Pattern "{{[A-Za-z ]*}}").Count
348355
$_missingExampleOutput = ($exampleDetails.Outputs | Select-String -Pattern "{{[A-Za-z ]*}}").Count
@@ -435,7 +442,7 @@ function Measure-SectionMissingAndOutputScript {
435442
RuleName = "NeedDeleting"
436443
Severity = $missingSeverity
437444
Extent = "$Module\help\$Cmdlet.md"
438-
ProblemID = 5051
445+
ProblemID = 5052
439446
Remediation = "Delete the prompt of example."
440447
}
441448
$results += $result
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"Module","Cmdlet","Example","RuleName","ProblemID","Severity","Description","Extent","Remediation"
2+
"Accounts","Disconnect-AzAccount","2","Unbinded_Expression","5014","2","Get-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter."
3+
"Accounts","Remove-AzContext","1","Invalid_Parameter_Name","5011","2","Remove-AzContext -Name is not a valid parameter name.","-Name","Check validity of the parameter Name."
4+
"Accounts","Rename-AzContext","1","Invalid_Parameter_Name","5011","2","Rename-AzContext -SourceName is not a valid parameter name.","-SourceName","Check validity of the parameter SourceName."
5+
"Accounts","Rename-AzContext","1","Invalid_Parameter_Name","5011","2","Rename-AzContext -TargetName is not a valid parameter name.","-TargetName","Check validity of the parameter TargetName."
6+
"Accounts","Rename-AzContext","2","Unbinded_Expression","5014","2","Rename-AzContext 'My context' is not explicitly assigned to a parameter.","'My context'","Assign 'My context' explicitly to the parameter."
7+
"Accounts","Rename-AzContext","2","Unbinded_Expression","5014","2","Rename-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter."
8+
"Accounts","Select-AzContext","1","Unbinded_Expression","5014","2","Select-AzContext 'Work' is not explicitly assigned to a parameter.","'Work'","Assign 'Work' explicitly to the parameter."

0 commit comments

Comments
 (0)