Skip to content

Commit 3dbe7f4

Browse files
committed
PS: Add the type model generation script and add a short readme.
1 parent 6ef0941 commit 3dbe7f4

File tree

2 files changed

+199
-0
lines changed

2 files changed

+199
-0
lines changed

powershell/misc/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
# Type model generation
3+
4+
Type information about .NET and PowerShell SDK methods are obtained by generating data extension files that populate the `typeModel` extensible predicate.
5+
6+
The type models are located here: https://github.com/microsoft/codeql/blob/main/powershell/ql/lib/semmle/code/powershell/frameworks/
7+
8+
Follow the steps below in order to generate new type models:
9+
1. Join the `MicrosoftDocs` organisation to ensure that you can access https://github.com/MicrosoftDocs/powershell-docs-sdk-dotnet/tree/main/dotnet/xml (if you haven't already).
10+
2.
11+
Run the following commands
12+
13+
```
14+
# Clone dotnet/dotnet-api-docs
15+
git clone https://github.com/dotnet/dotnet-api-docs
16+
# Clone MicrosoftDocs/powershell-docs-sdk-dotnet
17+
git clone [email protected]:MicrosoftDocs/powershell-docs-sdk-dotnet.git
18+
# Generate data extensions
19+
python3 misc/typemodelgen.py dotnet-api-docs/xml/ powershell-docs-sdk-dotnet/dotnet/xml
20+
```
21+
This will generate 600+ folders that need to be copied into https://github.com/microsoft/codeql/blob/main/powershell/ql/lib/semmle/code/powershell/frameworks/.
22+
23+
Note: Care must be taken to ensure that manually modified versions aren't overwritten.

powershell/misc/typemodelgen.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import xml.etree.ElementTree as ET
2+
from pathlib import Path
3+
import sys
4+
import os
5+
from collections import defaultdict
6+
7+
8+
def fixup(t):
9+
"""Sometimes the docs specify a type that doesn't align with what
10+
PowerShell reports. This function fixes up those types so that it aligns with PowerShell.
11+
"""
12+
if t.startswith("System.ReadOnlySpan<"):
13+
return "System.String"
14+
return t
15+
16+
17+
def isStatic(member):
18+
"""Returns True if the member is static, False otherwise."""
19+
for child in member:
20+
if child.tag == "MemberSignature" and "static" in child.attrib["Value"]:
21+
return True
22+
return False
23+
24+
25+
def isA(x):
26+
"""Returns True if member is an `x`."""
27+
for child in member:
28+
if child.tag == "MemberType" and child.text == x:
29+
return True
30+
return False
31+
32+
33+
def isMethod(member):
34+
"""Returns True if the member is a method, False otherwise."""
35+
return isA(member, "Method")
36+
37+
38+
def isField(member):
39+
"""Returns True if the member is a field, False otherwise."""
40+
return isA(member, "Field")
41+
42+
43+
def isProperty(member):
44+
"""Returns True if the member is a property, False otherwise."""
45+
return isA(member, "Property")
46+
47+
48+
def isEvent(member):
49+
"""Returns True if the member is an event, False otherwise."""
50+
return isA(member, "Event")
51+
52+
53+
def isAttachedProperty(member):
54+
"""Returns True if the member is an attached property, False otherwise."""
55+
return isA(member, "AttachedProperty")
56+
57+
58+
def isAttachedEvent(member):
59+
"""Returns True if the member is an attached event, False otherwise."""
60+
return isA(member, "AttachedEvent")
61+
62+
63+
def isConstructor(member):
64+
"""Returns True if the member is a constructor, False otherwise."""
65+
return isA(member, "Constructor")
66+
67+
68+
# A map from filenames to a set of type models to be stored in the file
69+
summaries = defaultdict(set)
70+
71+
72+
def generateTypeModels(arg):
73+
"""Generates type models for the given XML file."""
74+
folder_path = Path(arg)
75+
76+
for file_path in folder_path.rglob("*"):
77+
try:
78+
if not file_path.name.endswith(".xml"):
79+
continue
80+
81+
if not file_path.is_file():
82+
continue
83+
84+
tree = ET.parse(str(file_path))
85+
root = tree.getroot()
86+
if not root.tag == "Type":
87+
continue
88+
89+
thisType = root.attrib["FullName"]
90+
91+
if "`" in file_path.stem or "+" in file_path.stem:
92+
continue # Skip generics (and nested types?) for now
93+
94+
folderName = file_path.parent.name.replace(".", "")
95+
filename = folderName + "/model.yml"
96+
s = set()
97+
for elem in root.findall(".//Members/Member"):
98+
name = elem.attrib["MemberName"]
99+
if name == ".ctor":
100+
continue
101+
102+
staticMarker = ""
103+
if isStatic(elem):
104+
staticMarker = "!"
105+
106+
startSelectorMarker = ""
107+
endSelectorMarker = ""
108+
if isField(elem):
109+
startSelectorMarker = "Field"
110+
endSelectorMarker = ""
111+
if isProperty(elem):
112+
startSelectorMarker = "Property"
113+
endSelectorMarker = ""
114+
if isMethod(elem):
115+
startSelectorMarker = "Method"
116+
endSelectorMarker = ".ReturnValue"
117+
118+
if isEvent(elem):
119+
continue # What are these?
120+
if isAttachedProperty(elem):
121+
continue # What are these?
122+
if isAttachedEvent(elem):
123+
continue # What are these?
124+
if isConstructor(elem):
125+
continue # No need to model the type information for constructors
126+
if startSelectorMarker == "":
127+
print(f"Error: Unknown type for {thisType}.{name}")
128+
continue
129+
130+
if elem.find(".//ReturnValue/ReturnType") is None:
131+
print(f"Error: {name} has no return type!")
132+
continue
133+
134+
returnType = elem.find(".//ReturnValue/ReturnType").text
135+
if returnType == "System.Void":
136+
continue # Don't generate type summaries for void methods
137+
s.add(
138+
f' - ["{fixup(returnType)}", "{thisType + staticMarker}", "{startSelectorMarker}[{name}]{endSelectorMarker}"]\n'
139+
)
140+
141+
summaries[filename].update(s)
142+
143+
except ET.ParseError as e:
144+
print(f"Error parsing XML: {e}")
145+
except Exception as e:
146+
print(f"An error occurred: {repr(e)}")
147+
148+
149+
def writeModels():
150+
"""Writes the type models to disk."""
151+
for filename, s in summaries.items():
152+
if len(s) == 0:
153+
continue
154+
os.makedirs(os.path.dirname(filename), exist_ok=True)
155+
with open(filename, "x") as file:
156+
file.write("extensions:\n")
157+
file.write(" - addsTo:\n")
158+
file.write(" pack: microsoft-sdl/powershell-all\n")
159+
file.write(" extensible: typeModel\n")
160+
file.write(" data:\n")
161+
for summary in s:
162+
for x in summary:
163+
file.write(x)
164+
165+
166+
if __name__ == "__main__":
167+
if len(sys.argv) < 2:
168+
print("Usage: python parse_xml.py <xml_file1> <xml_file2> ... <xml_fileN>")
169+
sys.exit(1)
170+
171+
for arg in sys.argv[1:]:
172+
print(f"Processing {arg}...")
173+
generateTypeModels(arg)
174+
175+
print("Writing models...")
176+
writeModels()

0 commit comments

Comments
 (0)