Skip to content

Commit cfde677

Browse files
committed
PS: AST and control-flow additions required for MaD and Api graphs.
1 parent 68c729f commit cfde677

File tree

10 files changed

+100
-10
lines changed

10 files changed

+100
-10
lines changed

powershell/ql/lib/powershell.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,5 @@ import semmle.code.powershell.HashTable
8181
import semmle.code.powershell.SplitExpr
8282
import semmle.code.powershell.CommentEntity
8383
import semmle.code.powershell.Variable
84-
import semmle.code.powershell.internal.Internal::Public
84+
import semmle.code.powershell.internal.Internal::Public
85+
import semmle.code.powershell.ModuleManifest

powershell/ql/lib/semmle/code/powershell/Command.qll

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
import powershell
2+
private predicate parseCommandName(Cmd cmd, string namespace, string name) {
3+
exists(string qualified | command(cmd, qualified, _, _, _) |
4+
namespace = qualified.regexpCapture("([^\\\\]+)\\\\([^\\\\]+)", 1) and
5+
name = qualified.regexpCapture("([^\\\\]+)\\\\([^\\\\]+)", 2)
6+
or
7+
// Not a qualified name
8+
not exists(qualified.indexOf("\\")) and
9+
namespace = "" and
10+
name = qualified
11+
)
12+
}
213

314
class Cmd extends @command, CmdBase {
4-
override string toString() { result = this.getCommandName() }
15+
override string toString() { result = this.getQualifiedCommandName() }
516

617
override SourceLocation getLocation() { command_location(this, result) }
718

8-
string getCommandName() { command(this, result, _, _, _) }
19+
/** Gets the name of the command without any qualifiers. */
20+
string getCommandName() { parseCommandName(this, _, result) }
21+
22+
/** Holds if the command is qualified. */
23+
predicate isQualified() { parseCommandName(this, any(string s | s != ""), _) }
24+
25+
/** Gets the namespace qualifier of this command, if any. */
26+
string getNamespaceQualifier() { parseCommandName(this, result, _) }
27+
28+
/** Gets the (possibly qualified) name of this command. */
29+
string getQualifiedCommandName() { command(this, result, _, _, _) }
930

1031
int getKind() { command(this, _, result, _, _) }
1132

@@ -15,8 +36,10 @@ class Cmd extends @command, CmdBase {
1536

1637
CmdElement getElement(int i) { command_command_element(this, i, result) }
1738

39+
/** Gets the expression that determines the command to invoke. */
1840
Expr getCommand() { result = this.getElement(0) }
1941

42+
/** Gets the name of this command, if this is statically known. */
2043
StringConstExpr getCmdName() { result = this.getElement(0) }
2144

2245
/** Gets any argument to this command. */

powershell/ql/lib/semmle/code/powershell/HashTable.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ class HashTableExpr extends @hash_table, Expr {
77

88
Stmt getElement(Expr key) { hash_table_key_value_pairs(this, _, key, result) } // TODO: Change @ast to @expr in db scheme
99

10+
Stmt getElementFromConstant(string key) {
11+
result = this.getElement(any(StringConstExpr sc | sc.getValue().getValue() = key))
12+
}
13+
1014
predicate hasKey(Expr key) { exists(this.getElement(key)) }
1115

1216
Stmt getAnElement() { result = this.getElement(_) }

powershell/ql/lib/semmle/code/powershell/InvokeMemberExpression.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class InvokeMemberExpr extends @invoke_member_expression, MemberExprBase {
99

1010
CmdElement getMember() { invoke_member_expression(this, _, result) }
1111

12+
string getMemberName() { result = this.getMember().(StringConstExpr).getValue().getValue() }
13+
1214
Expr getArgument(int i) { invoke_member_expression_argument(this, i, result) }
1315

1416
Expr getAnArgument() { invoke_member_expression_argument(this, _, result) }

powershell/ql/lib/semmle/code/powershell/MemberExpr.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import powershell
33
class MemberExpr extends @member_expression, MemberExprBase {
44
final override Location getLocation() { member_expression_location(this, result) }
55

6-
Expr getBase() { member_expression(this, result, _, _, _) }
6+
Expr getQualifier() { member_expression(this, result, _, _, _) }
77

88
CmdElement getMember() { member_expression(this, _, result, _, _) }
99

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import powershell
2+
3+
class ModuleManifestFile extends File {
4+
ModuleManifestFile() { this.getExtension() = "psd1" }
5+
}
6+
7+
private Expr getEntry(HashTableExpr ht, string key) {
8+
result = ht.getElementFromConstant(key).(CmdExpr).getExpr() and
9+
not result instanceof ArrayLiteral
10+
}
11+
12+
private Expr getAnEntry(HashTableExpr ht, string key) {
13+
exists(Expr e | e = ht.getElementFromConstant(key).(CmdExpr).getExpr() |
14+
not e instanceof ArrayLiteral and result = e
15+
or
16+
result = e.(ArrayLiteral).getAnElement()
17+
)
18+
}
19+
20+
class ModuleManifest extends HashTableExpr {
21+
string moduleVersion;
22+
23+
ModuleManifest() {
24+
// The hash table is in a .psd1 file
25+
this.getLocation().getFile() instanceof ModuleManifestFile and
26+
// It's at the top level of the file
27+
this.getParent().(CmdExpr).getParent().(NamedBlock).getParent() instanceof TopLevel and
28+
// It has a `ModuleVersion` entry. The only required field is ModuleVersion.
29+
// https://learn.microsoft.com/en-us/powershell/scripting/developer/module/how-to-write-a-powershell-module-manifest?view=powershell-7.4#to-create-and-use-a-module-manifest
30+
moduleVersion = getEntry(this, "ModuleVersion").getValue().asString()
31+
}
32+
33+
string getModuleVersion() { result = moduleVersion }
34+
35+
string getModuleName() {
36+
result + ".psd1" = this.getLocation().getFile().getBaseName()
37+
}
38+
39+
string getAFunctionToExport() {
40+
result = getAnEntry(this, "FunctionsToExport").getValue().asString()
41+
}
42+
43+
string getACmdLetToExport() { result = getAnEntry(this, "CmdletsToExport").getValue().asString() }
44+
}

powershell/ql/lib/semmle/code/powershell/UsingStmt.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,18 @@ class UsingStmt extends @using_statement, Stmt {
44
override SourceLocation getLocation() { using_statement_location(this, result) }
55

66
override string toString() { result = "using ..." }
7+
8+
string getName() {
9+
exists(StringConstExpr const |
10+
using_statement_name(this, const) and // TODO: Change dbscheme
11+
result = const.getValue().getValue()
12+
)
13+
}
14+
15+
string getAlias() {
16+
exists(StringConstExpr const |
17+
using_statement_alias(this, const) and // TODO: Change dbscheme
18+
result = const.getValue().getValue()
19+
)
20+
}
721
}

powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ abstract private class AbstractCallCfgNode extends AstCfgNode {
159159
* Gets the expression that provides the call target of this call, if any.
160160
*/
161161
abstract ExprCfgNode getCommand();
162+
163+
int getNumberOfArguments() { result = count(this.getAnArgument()) }
162164
}
163165

164166
final class CallCfgNode = AbstractCallCfgNode;
@@ -371,7 +373,7 @@ module ExprNodes {
371373
}
372374

373375
class MemberChildMapping extends ExprChildMapping, MemberExpr {
374-
override predicate relevantChild(Ast n) { n = this.getBase() or n = this.getMember() }
376+
override predicate relevantChild(Ast n) { n = this.getQualifier() or n = this.getMember() }
375377
}
376378

377379
/** A control-flow node that wraps a `MemberExpr` expression. */
@@ -382,7 +384,7 @@ module ExprNodes {
382384

383385
final override MemberExpr getExpr() { result = super.getExpr() }
384386

385-
final ExprCfgNode getBase() { e.hasCfgChild(e.getBase(), this, result) }
387+
final ExprCfgNode getQualifier() { e.hasCfgChild(e.getQualifier(), this, result) }
386388

387389
final string getMemberName() { result = e.getMemberName() }
388390

powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ module Trees {
468468

469469
class MemberExprTree extends StandardPostOrderTree instanceof MemberExpr {
470470
override AstNode getChildNode(int i) {
471-
i = 0 and result = super.getBase()
471+
i = 0 and result = super.getQualifier()
472472
or
473473
i = 1 and result = super.getMember()
474474
}

powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ private module Cached {
186186
n instanceof CfgNodes::ExprNodes::QualifierCfgNode
187187
or
188188
exists(CfgNodes::ExprNodes::MemberCfgNode member |
189-
n = member.getBase() and
189+
n = member.getQualifier() and
190190
not member.isStatic()
191191
)
192192
or
@@ -769,7 +769,7 @@ predicate jumpStep(Node pred, Node succ) {
769769
*/
770770
predicate storeStep(Node node1, ContentSet c, Node node2) {
771771
exists(CfgNodes::ExprNodes::MemberCfgWriteAccessNode var, Content::FieldContent fc |
772-
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = var.getBase() and
772+
node2.(PostUpdateNode).getPreUpdateNode().asExpr() = var.getQualifier() and
773773
node1.asStmt() = var.getAssignStmt().getRightHandSide() and
774774
fc.getName() = var.getMemberName() and
775775
c.isSingleton(fc)
@@ -838,7 +838,7 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
838838
predicate readStep(Node node1, ContentSet c, Node node2) {
839839
exists(CfgNodes::ExprNodes::MemberCfgReadAccessNode var, Content::FieldContent fc |
840840
node2.asExpr() = var and
841-
node1.asExpr() = var.getBase() and
841+
node1.asExpr() = var.getQualifier() and
842842
fc.getName() = var.getMemberName() and
843843
c.isSingleton(fc)
844844
)

0 commit comments

Comments
 (0)