Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions powershell/misc/GenerateFromDB.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* This query generates type models for PowerShell from a C# database.
*
* It is not meant to be run manually. Instead, it is executed
* by `typemodelgenFromDB.py`.
*/
import csharp
private import semmle.code.csharp.commons.QualifiedName

private module FullyQualifiedNameInput implements QualifiedNameInputSig {
string getUnboundGenericSuffix(UnboundGeneric ug) { exists(ug) and result = "" }
}

predicate hasFullyQualifiedNameImpl(Declaration d, string namespace, string type, string name) {
QualifiedName<FullyQualifiedNameInput>::hasQualifiedName(d, namespace, type, name)
}

predicate hasFullyQualifiedNameImpl(Declaration d, string qualifier, string name) {
QualifiedName<FullyQualifiedNameInput>::hasQualifiedName(d, qualifier, name)
}

predicate hasFullyQualifiedName(Callable callable, string namespace, string type, string name) {
hasFullyQualifiedNameImpl(callable.(Method).getUnboundDeclaration(), namespace, type, name)
}

Type getReturnType(Callable c) {
result = c.(Method).getReturnType()
}

bindingset[t]
Type remap(Type t) {
if hasFullyQualifiedNameImpl(t, "System", "ReadOnlySpan")
then hasFullyQualifiedNameImpl(result, "System", "String")
else (
if t instanceof TupleType
then hasFullyQualifiedNameImpl(result, "System", "ValueTuple")
else result = t
)
}

from Callable m, Type t, string namespace, string type, string name, string qualifier, string return
where
hasFullyQualifiedName(m, namespace, type, name) and
not type.matches("%+%") and
not return.matches("%+%") and
getReturnType(m) = t and
not t instanceof VoidType and
hasFullyQualifiedNameImpl(remap(t.getUnboundDeclaration()), qualifier, return) and
qualifier != "" and
m.fromSource() and
exists(string relative |
relative = m.getLocation().getFile().getRelativePath() and
not relative.toLowerCase().matches("%/tests/%")
)
select qualifier.toLowerCase() + "." + return.toLowerCase(),
namespace.toLowerCase() + "." + type.toLowerCase(), name.toLowerCase()
13 changes: 12 additions & 1 deletion powershell/misc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Type information about .NET and PowerShell SDK methods are obtained by generatin

The type models are located here: https://github.com/microsoft/codeql/blob/main/powershell/ql/lib/semmle/code/powershell/frameworks/

## dotnet/dotnet-api-docs and MicrosoftDocs/powershell-docs-sdk-dotnet

Follow the steps below in order to generate new type models:
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).
2.
Expand All @@ -20,4 +22,13 @@ Run the following commands
```
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/.

Note: Care must be taken to ensure that manually modified versions aren't overwritten.
## dotnet/SqlClient

The type models for this is generated via a CodeQL query. Following these steps to generate these type models

1. Download a C# DB for `dotnet/SqlClient` (for instance using VSCode's `CodeQL: Download Database from GitHub`)
2. Run the following command (note: your current working directory is assumed to be CodeQL):
```
python .\powershell\misc\typemodelgenFromDB.py . powershell/ql/lib/semmle/code/powershell/frameworks/generated/dotnet.sqlclient.typemodel.yml path/to/the/db/you/downloaded/above
```
This will generate the file `dotnet.sqlclient.typemodel.yml` inside the right folder.
6 changes: 6 additions & 0 deletions powershell/misc/qlpack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: microsoft-sdl/powershell-typegen
version: 0.0.1
extractor: csharp
dependencies:
codeql/csharp-all: "*"
warnOnImplicitThis: true
84 changes: 84 additions & 0 deletions powershell/misc/typemodelgenFromDB.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Generate a typemodel.yml file from a CodeQL BQRS query result."""

import argparse
import csv
import subprocess
import sys
import tempfile
from pathlib import Path

HEADER = """\
# THIS FILE IS AN AUTO-GENERATED MODELS AS DATA FILE. DO NOT EDIT.
extensions:
- addsTo:
pack: microsoft/powershell-all
extensible: typeModel
data:
"""


def generate_type_models(output: str, codeql: str, db: str) -> None:
"""Decode a BQRS file to CSV and convert it into a typemodel YAML file."""
with tempfile.TemporaryDirectory() as tmpdir:
bqrs_path = Path(tmpdir) / "out.bqrs"
query_path = Path(codeql) / "powershell" / "misc" / "GenerateFromDB.ql"
subprocess.run(
[
"codeql", "query", "run", str(query_path),
"--additional-packs", str(codeql),
"-d", str(db),
"-o", str(bqrs_path),
],
check=True,
)

csv_path = Path(tmpdir) / "results.csv"
subprocess.run(
[
"codeql", "bqrs", "decode",
str(bqrs_path),
"--no-titles",
"--format=csv",
"-o", str(csv_path),
],
check=True,
)

rows = _read_csv(csv_path)

output_path = Path(output)
output_path.parent.mkdir(parents=True, exist_ok=True)

lines = [
f' - ["{t}", "{d}", "Method[{m}].ReturnValue"]'
for t, d, m in rows
]
content = (HEADER + "\n".join(lines) + "\n") if lines else HEADER
output_path.write_text(content, newline="\n")

def _read_csv(path: Path) -> list[tuple[str, str, str]]:
"""Read the decoded CSV and return valid (type, declaring_type, member) triples."""
with path.open(newline="") as f:
rows = []
for row in csv.reader(f):
if len(row) != 3:
print(f"Skipping malformed row: {row}", file=sys.stderr)
continue
rows.append(tuple(row))
return rows


def main() -> None:
parser = argparse.ArgumentParser(
description="Generate a typemodel.yml from a CodeQL BQRS file."
)
parser.add_argument("codeql", help="Path to the CodeQL checkout")
parser.add_argument("output", help="Output path")
parser.add_argument("db", help="Path to the CodeQL database")
args = parser.parse_args()

generate_type_models(args.output, args.codeql, args.db)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class AttributeBaseCfgNode extends AstCfgNode {

private class AttributeChildMapping extends AttributeBaseChildMapping, Attribute {
override predicate relevantChild(Ast child) {
this.relevantChild(child)
super.relevantChild(child)
or
child = this.getANamedArgument()
or
Expand Down Expand Up @@ -576,7 +576,10 @@ module ExprNodes {
}

private class ObjectCreationChildMapping extends CallExprChildMapping instanceof ObjectCreation {
override predicate relevantChild(Ast child) { child = super.getConstructedTypeExpr() }
override predicate relevantChild(Ast child) {
super.relevantChild(child) or
child = super.getConstructedTypeExpr()
}
}

class ObjectCreationCfgNode extends CallExprCfgNode {
Expand Down
Loading