Skip to content

Commit 337132b

Browse files
authored
Merge pull request #270 from microsoft/better-api-graphs
PS: Better usability when working with nested classes
2 parents f8bdfa4 + e96e464 commit 337132b

File tree

9 files changed

+704
-185
lines changed

9 files changed

+704
-185
lines changed

powershell/ql/lib/qlpack.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ dependencies:
1616
dataExtensions:
1717
- semmle/code/powershell/frameworks/**/*.model.yml
1818
- semmle/code/powershell/frameworks/**/*.typemodel.yml
19+
- semmle/code/powershell/frameworks/data/internal/cmdlet.model.yml
20+
- semmle/code/powershell/frameworks/data/internal/alias.model.yml
1921
warnOnImplicitThis: true

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

Lines changed: 106 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private import semmle.code.powershell.dataflow.DataFlow
1010
private import semmle.code.powershell.typetracking.ApiGraphShared
1111
private import semmle.code.powershell.typetracking.internal.TypeTrackingImpl
1212
private import semmle.code.powershell.controlflow.Cfg
13+
private import frameworks.data.internal.ApiGraphModels
1314
private import frameworks.data.internal.ApiGraphModelsExtensions as Extensions
1415
private import frameworks.data.internal.ApiGraphModelsSpecific as Specific
1516
private import semmle.code.powershell.dataflow.internal.DataFlowPrivate as DataFlowPrivate
@@ -204,18 +205,6 @@ module API {
204205
Impl::positionalParameterOrArgumentEdge(this.getAnEpsilonSuccessor(), n, result)
205206
}
206207

207-
/**
208-
* Gets the given keyword parameter of this callable, or keyword argument to this call.
209-
*
210-
* Note: for historical reasons, this predicate may refer to an argument of a call, but this may change in the future.
211-
* When referring to an argument, it is recommended to use `getKeywordArgument(n)` instead.
212-
*/
213-
pragma[inline]
214-
Node getKeywordParameter(string name) {
215-
// This predicate is currently not 'inline_late' because 'name' can be an input or output
216-
Impl::keywordParameterOrArgumentEdge(this.getAnEpsilonSuccessor(), name, result)
217-
}
218-
219208
/**
220209
* Gets the argument passed in argument position `pos` at this call.
221210
*/
@@ -260,15 +249,6 @@ module API {
260249
result = this.getContent(contents.getAReadContent())
261250
}
262251

263-
/**
264-
* Gets a representative for the instance field of the given `name`.
265-
*/
266-
pragma[inline]
267-
Node getField(string name) {
268-
// This predicate is currently not 'inline_late' because 'name' can be an input or output
269-
Impl::fieldEdge(this.getAnEpsilonSuccessor(), name, result)
270-
}
271-
272252
/**
273253
* Gets a representative for an arbitrary element of this collection.
274254
*/
@@ -283,8 +263,7 @@ module API {
283263
this = Impl::MkMethodAccessNode(result) or
284264
this = Impl::MkBackwardNode(result, _) or
285265
this = Impl::MkForwardNode(result, _) or
286-
this = Impl::MkSinkNode(result) or
287-
this = Impl::MkNamespaceOfTypeNameNode(result)
266+
this = Impl::MkSinkNode(result)
288267
}
289268

290269
/** Gets the location of this node. */
@@ -293,6 +272,10 @@ module API {
293272
or
294273
this instanceof RootNode and
295274
result instanceof EmptyLocation
275+
or
276+
not this instanceof RootNode and
277+
not exists(this.getInducingNode()) and
278+
result instanceof EmptyLocation
296279
}
297280

298281
/**
@@ -352,20 +335,84 @@ module API {
352335
override string toString() { result = "SinkNode(" + this.getInducingNode() + ")" }
353336
}
354337

355-
private class UsingNode extends Node, Impl::MkUsingNode {
356-
UsingStmt using; // TODO: This should really be the cfg node, I think
338+
abstract private class AbstractTypeNameNode extends Node {
339+
string prefix;
340+
341+
bindingset[prefix]
342+
AbstractTypeNameNode() { any() }
343+
344+
override string toString() { result = "TypeNameNode(" + this.getTypeName() + ")" }
345+
346+
string getComponent() {
347+
exists(int n |
348+
result = prefix.splitAt(".", n) and
349+
not exists(prefix.splitAt(".", n + 1))
350+
)
351+
}
352+
353+
string getTypeName() { result = prefix }
354+
355+
abstract Node getSuccessor(string name);
356+
357+
Node memberEdge(string name) { none() }
357358

358-
UsingNode() { this = Impl::MkUsingNode(using) }
359+
Node methodEdge(string name) { none() }
359360

360-
override string toString() { result = "UsingNode(" + using + ")" }
361+
final predicate isImplicit() { not this.isExplicit(_) }
362+
363+
predicate isExplicit(DataFlow::TypeNameNode typeName) { none() }
361364
}
362365

363-
private class NamespaceOfTypeNameNode extends Node, Impl::MkNamespaceOfTypeNameNode {
364-
DataFlow::QualifiedTypeNameNode typeName;
366+
final class TypeNameNode = AbstractTypeNameNode;
367+
368+
private class ExplicitTypeNameNode extends AbstractTypeNameNode, Impl::MkExplicitTypeNameNode {
369+
ExplicitTypeNameNode() { this = Impl::MkExplicitTypeNameNode(prefix) }
370+
371+
final override Node getSuccessor(string name) {
372+
exists(ExplicitTypeNameNode succ |
373+
succ = Impl::MkExplicitTypeNameNode(prefix + "." + name) and
374+
result = succ
375+
)
376+
or
377+
exists(DataFlow::TypeNameNode typeName, int n, string lowerCaseName |
378+
Specific::needsExplicitTypeNameNode(typeName, prefix) and
379+
lowerCaseName = typeName.getLowerCaseName() and
380+
name = lowerCaseName.splitAt(".", n) and
381+
not lowerCaseName.matches("%.%") and
382+
result = getForwardStartNode(typeName)
383+
)
384+
}
385+
386+
final override predicate isExplicit(DataFlow::TypeNameNode typeName) {
387+
Specific::needsExplicitTypeNameNode(typeName, prefix)
388+
}
389+
}
365390

366-
NamespaceOfTypeNameNode() { this = Impl::MkNamespaceOfTypeNameNode(typeName) }
391+
private string getAnAlias(string cmdlet) { Specific::aliasModel(cmdlet, result) }
367392

368-
override string toString() { result = "NamespaceOfTypeNameNode(" + typeName + ")" }
393+
predicate implicitCmdlet(string mod, string cmdlet) {
394+
exists(string cmdlet0 |
395+
Specific::cmdletModel(mod, cmdlet0) and
396+
cmdlet = [cmdlet0, getAnAlias(cmdlet0)]
397+
)
398+
}
399+
400+
private class ImplicitTypeNameNode extends AbstractTypeNameNode, Impl::MkImplicitTypeNameNode {
401+
ImplicitTypeNameNode() { this = Impl::MkImplicitTypeNameNode(prefix) }
402+
403+
final override Node getSuccessor(string name) {
404+
result = Impl::MkImplicitTypeNameNode(prefix + "." + name)
405+
}
406+
407+
final override Node memberEdge(string name) { result = this.methodEdge(name) }
408+
409+
final override Node methodEdge(string name) {
410+
exists(DataFlow::CallNode call |
411+
result = Impl::MkMethodAccessNode(call) and
412+
name = call.getLowerCaseName() and
413+
implicitCmdlet(prefix, name)
414+
)
415+
}
369416
}
370417

371418
/**
@@ -405,13 +452,6 @@ module API {
405452
/** Gets the root node. */
406453
Node root() { result instanceof RootNode }
407454

408-
bindingset[name]
409-
pragma[inline_late]
410-
Node namespace(string name) {
411-
// This predicate is currently not 'inline_late' because 'n' can be an input or output
412-
Impl::namespace(name, result)
413-
}
414-
415455
pragma[inline]
416456
Node getTopLevelMember(string name) { Impl::topLevelMember(name, result) }
417457

@@ -466,8 +506,8 @@ module API {
466506
MkRoot() or
467507
/** The method accessed at `call`, synthetically treated as a separate object. */
468508
MkMethodAccessNode(DataFlow::CallNode call) or
469-
MkUsingNode(UsingStmt using) or
470-
MkNamespaceOfTypeNameNode(DataFlow::QualifiedTypeNameNode typeName) or
509+
MkExplicitTypeNameNode(string prefix) { Specific::needsExplicitTypeNameNode(_, prefix) } or
510+
MkImplicitTypeNameNode(string prefix) { Specific::needsImplicitTypeNameNode(prefix) } or
471511
MkForwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
472512
/** Intermediate node for following backward data flow. */
473513
MkBackwardNode(DataFlow::LocalSourceNode node, TypeTracker t) { isReachable(node, t) } or
@@ -483,27 +523,8 @@ module API {
483523
node = any(EntryPoint e).getASink()
484524
}
485525

486-
bindingset[e]
487-
pragma[inline_late]
488-
private DataFlow::Node getNodeFromExpr(Expr e) { result.asExpr().getExpr() = e }
489-
490526
private import frameworks.data.ModelsAsData
491527

492-
cached
493-
predicate namespace(string name, Node node) {
494-
exists(DataFlow::QualifiedTypeNameNode typeName |
495-
typeName.getNamespace() = name and
496-
node = MkNamespaceOfTypeNameNode(typeName)
497-
)
498-
or
499-
exists(UsingStmt using |
500-
using.getName().toLowerCase() = name and
501-
node = MkUsingNode(using)
502-
)
503-
or
504-
node = ModelOutput::getATypeNode(name)
505-
}
506-
507528
cached
508529
predicate topLevelMember(string name, Node node) { memberEdge(root(), name, node) }
509530

@@ -516,83 +537,55 @@ module API {
516537
)
517538
}
518539

519-
cached
520-
predicate callEdge(Node pred, string name, Node succ) {
521-
exists(DataFlow::CallNode call |
522-
// from receiver to method call node
523-
pred = getForwardEndNode(getALocalSourceStrict(call.getQualifier())) and
524-
succ = MkMethodAccessNode(call) and
525-
name = call.getLowerCaseName()
526-
)
527-
}
528-
529-
bindingset[name]
530-
private string memberOrMethodReturnValue(string name) {
531-
// This predicate is a bit ad-hoc, but it's okay for now.
532-
// We can delete it once we no longer use the typeModel and summaryModel
533-
// tables to represent implicit root members.
534-
result = "Method[" + name + "]"
535-
or
536-
result = "Method[" + name + "].ReturnValue"
537-
or
538-
result = "Member[" + name + "]"
539-
}
540-
541-
private Node getAnImplicitRootMember(string name) {
542-
exists(DataFlow::CallNode call |
543-
Extensions::typeModel(_, Specific::getAnImplicitImport(), memberOrMethodReturnValue(name))
544-
or
545-
Extensions::summaryModel(Specific::getAnImplicitImport(), memberOrMethodReturnValue(name),
546-
_, _, _, _)
547-
or
548-
Extensions::sourceModel(Specific::getAnImplicitImport(), memberOrMethodReturnValue(name), _,
549-
_)
550-
|
551-
result = MkMethodAccessNode(call) and
552-
name = call.getLowerCaseName()
553-
)
554-
}
555-
556540
cached
557541
predicate memberEdge(Node pred, string name, Node succ) {
558542
pred = API::root() and
559543
(
560-
exists(StringConstExpr read |
561-
succ = getForwardStartNode(getNodeFromExpr(read)) and
562-
name = read.getValueString()
563-
)
544+
succ.(TypeNameNode).getTypeName() = name
564545
or
565546
exists(DataFlow::AutomaticVariableNode automatic |
566547
automatic.getLowerCaseName() = name and
567548
succ = getForwardStartNode(automatic)
568549
)
569-
or
570-
succ = getAnImplicitRootMember(name)
571550
)
572551
or
573-
exists(DataFlow::QualifiedTypeNameNode typeName |
574-
typeName.getLowerCaseName() = name and
575-
pred = MkNamespaceOfTypeNameNode(typeName) and
576-
succ = getForwardStartNode(typeName)
552+
exists(TypeNameNode typeName | pred = typeName |
553+
typeName.getSuccessor(name) = succ
554+
or
555+
typeName.memberEdge(name) = succ
577556
)
578557
or
579-
exists(MemberExprReadAccess read |
580-
read.getLowerCaseMemberName().toLowerCase() = name and
581-
pred = getForwardEndNode(getALocalSourceStrict(getNodeFromExpr(read.getQualifier()))) and
582-
succ = getForwardStartNode(getNodeFromExpr(read))
558+
exists(DataFlow::Node qualifier | pred = getForwardEndNode(getALocalSourceStrict(qualifier)) |
559+
exists(CfgNodes::ExprNodes::MemberExprReadAccessCfgNode read |
560+
read.getQualifier() = qualifier.asExpr() and
561+
read.getLowerCaseMemberName() = name and
562+
succ = getForwardStartNode(DataFlow::exprNode(read))
563+
)
564+
or
565+
exists(DataFlow::CallNode call |
566+
call.getLowerCaseName() = name and
567+
call.getQualifier() = qualifier and
568+
succ = MkMethodAccessNode(call)
569+
)
583570
)
584571
}
585572

586573
cached
587574
predicate methodEdge(Node pred, string name, Node succ) {
588575
exists(DataFlow::CallNode call |
589-
succ = MkMethodAccessNode(call) and name = call.getLowerCaseName()
590-
|
576+
succ = MkMethodAccessNode(call) and
577+
name = call.getLowerCaseName() and
591578
pred = getForwardEndNode(getALocalSourceStrict(call.getQualifier()))
592579
)
593580
or
581+
pred.(TypeNameNode).methodEdge(name) = succ
582+
or
594583
pred = API::root() and
595-
succ = getAnImplicitRootMember(name)
584+
exists(DataFlow::CallNode call |
585+
not exists(call.getQualifier()) and
586+
succ = MkMethodAccessNode(call) and
587+
name = call.getLowerCaseName()
588+
)
596589
}
597590

598591
cached
@@ -617,11 +610,6 @@ module API {
617610
)
618611
}
619612

620-
cached
621-
predicate fieldEdge(Node pred, string name, Node succ) {
622-
Impl::contentEdge(pred, DataFlowPrivate::TFieldContent(name), succ)
623-
}
624-
625613
cached
626614
predicate elementEdge(Node pred, Node succ) {
627615
contentEdge(pred, any(DataFlow::ContentSet set | set.isAnyElement()).getAReadContent(), succ)
@@ -665,24 +653,13 @@ module API {
665653
), succ)
666654
}
667655

668-
private predicate keywordParameterEdge(Node pred, string name, Node succ) {
669-
parameterEdge(pred, any(DataFlowDispatch::ParameterPosition pos | pos.isKeyword(name)), succ)
670-
}
671-
672656
cached
673657
predicate positionalParameterOrArgumentEdge(Node pred, int n, Node succ) {
674658
positionalArgumentEdge(pred, n, succ)
675659
or
676660
positionalParameterEdge(pred, n, succ)
677661
}
678662

679-
cached
680-
predicate keywordParameterOrArgumentEdge(Node pred, string name, Node succ) {
681-
keywordArgumentEdge(pred, name, succ)
682-
or
683-
keywordParameterEdge(pred, name, succ)
684-
}
685-
686663
cached
687664
predicate instanceEdge(Node pred, Node succ) {
688665
// TODO: Also model parameters with a given type here

powershell/ql/lib/semmle/code/powershell/dataflow/FlowSummary.qll

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,3 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari
4747
)
4848
}
4949
}
50-
51-
/**
52-
* A callable with a flow summary, identified by a unique string, where all
53-
* calls to a method with the same name are considered relevant.
54-
*/
55-
abstract class SimpleSummarizedCallable extends SummarizedCallable {
56-
CallExpr c;
57-
58-
bindingset[this]
59-
SimpleSummarizedCallable() { c.getLowerCaseName() = this }
60-
61-
final override CallExpr getACall() { result = c }
62-
63-
final override CallExpr getACallSimple() { result = c }
64-
}

0 commit comments

Comments
 (0)