param( [string]$IlFile = "runs/offline-evidence-intake/zyl-qqfile-20260709/metadata/c28-il/IMPlatformClient.exe.il", [string]$OutDir = "runs/offline-chat-window" ) $ErrorActionPreference = "Stop" $repo = Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..") function Resolve-RepoPath([string]$PathValue) { if ([System.IO.Path]::IsPathRooted($PathValue)) { return $PathValue } return (Join-Path $repo $PathValue) } $ilFull = Resolve-RepoPath $IlFile $outFull = Resolve-RepoPath $OutDir if (-not (Test-Path -LiteralPath $ilFull)) { throw "IL file not found: $ilFull" } New-Item -ItemType Directory -Force -Path $outFull | Out-Null $lines = Get-Content -LiteralPath $ilFull $endIndex = $null for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match 'end of method frmP2PChat::\.ctor') { $endIndex = $i break } } if ($null -eq $endIndex) { throw "frmP2PChat::.ctor end marker not found" } $startIndex = $null for ($i = $endIndex; $i -ge 0; $i--) { if ($lines[$i] -match '^\s*\.method\s+public\s+hidebysig\s+specialname\s+rtspecialname') { $startIndex = $i break } } if ($null -eq $startIndex) { throw "frmP2PChat::.ctor method start not found before line $($endIndex + 1)" } $ctorIlFile = Join-Path $outFull "frmP2PChat-ctor.il.txt" $riskCsvFile = Join-Path $outFull "frmP2PChat-ctor-null-risk.csv" $riskMdFile = Join-Path $outFull "frmP2PChat-ctor-null-risk.md" $ctorLines = New-Object System.Collections.Generic.List[string] for ($i = $startIndex; $i -le $endIndex; $i++) { $ctorLines.Add(("{0}: {1}" -f ($i + 1), $lines[$i])) } $ctorLines | Set-Content -LiteralPath $ctorIlFile -Encoding UTF8 $riskRows = New-Object System.Collections.Generic.List[object] foreach ($entry in $ctorLines) { if ($entry -notmatch '\b(callvirt|ldfld)\b') { continue } $offset = $null if ($entry -match '(IL_[0-9a-fA-F]{4})') { $offset = $matches[1].ToLowerInvariant().Replace("il_", "IL_") } $op = if ($entry -match '\b(callvirt|ldfld)\b') { $matches[1] } else { $null } $target = ($entry -replace '^.*?\b(callvirt|ldfld)\b\s+', '').Trim() $riskRows.Add([pscustomobject]@{ source_line = [int]($entry -replace '^(\d+):.*$', '$1') il_offset = $offset opcode = $op target = $target null_risk = if ($op -eq "callvirt") { "receiver_null_possible" } else { "instance_field_owner_null_possible" } evidence = $entry.Trim() }) } $riskRows | Sort-Object source_line | Export-Csv -LiteralPath $riskCsvFile -NoTypeInformation -Encoding UTF8 $topEvidence = $riskRows | Where-Object { $_.target -match 'Connection::add_OnConnectError|Connection::add_OnReConnectOk|P2PChat::get_OtherJid|Jid::getBareJid|IMPPManager::get_UserInfo|UCChatSendMessageBox::GetChatRichTextBox|UCChatSendMessageBox::GetReceiptCheckBox|MessageCenter::add_OnTcpMessageArrived|RosterManager::UpdateStrangerStatus|BaseConfig::VisualPhoneEnable' } | Select-Object -First 40 $md = New-Object System.Collections.Generic.List[string] $md.Add("# frmP2PChat constructor IL NullRisk") $md.Add("") $md.Add("- Source IL: $ilFull") $md.Add("- Constructor source lines: $($startIndex + 1)-$($endIndex + 1)") $md.Add("- Constructor line count: $($ctorLines.Count)") $md.Add("- callvirt / ldfld risk rows: $($riskRows.Count)") $md.Add("") $md.Add("## High-signal risks") $md.Add("") $md.Add("| source_line | il_offset | opcode | target |") $md.Add("|---:|---|---|---|") foreach ($row in $topEvidence) { $target = ($row.target -replace '\|', '\\|') $md.Add("| $($row.source_line) | $($row.il_offset) | $($row.opcode) | $target |") } $md.Add("") $md.Add("## Files") $md.Add("") $md.Add("- Full constructor IL: $ctorIlFile") $md.Add("- Full NullRisk CSV: $riskCsvFile") $md | Set-Content -LiteralPath $riskMdFile -Encoding UTF8 [pscustomobject]@{ ok = $true il_file = $ilFull ctor_line_start = $startIndex + 1 ctor_line_end = $endIndex + 1 ctor_line_count = $ctorLines.Count risk_count = $riskRows.Count ctor_il_file = $ctorIlFile risk_csv_file = $riskCsvFile risk_md_file = $riskMdFile } | ConvertTo-Json -Compress