Merge pull request #8576 from asgerf/js/decorated-method-or-class

JS: Add decorator edges in API graphs and corresponding MaD tokens
This commit is contained in:
Asger F
2022-04-04 12:49:28 +02:00
committed by GitHub
7 changed files with 413 additions and 4 deletions

View File

@@ -230,6 +230,72 @@ module API {
result = this.getASuccessor(Label::promisedError())
}
/**
* Gets any class that has this value as a decorator.
*
* For example:
* ```js
* import { D } from "foo";
*
* // moduleImport("foo").getMember("D").getADecoratedClass()
* @D
* class C1 {}
*
* // moduleImport("foo").getMember("D").getReturn().getADecoratedClass()
* @D()
* class C2 {}
* ```
*/
cached
Node getADecoratedClass() { result = this.getASuccessor(Label::decoratedClass()) }
/**
* Gets any method, field, or accessor that has this value as a decorator.
*
* In the case of an accessor, this gets the return value of a getter, or argument to a setter.
*
* For example:
* ```js
* import { D } from "foo";
*
* class C {
* // moduleImport("foo").getMember("D").getADecoratedMember()
* @D m1() {}
* @D f;
* @D get g() { return this.x; }
*
* // moduleImport("foo").getMember("D").getReturn().getADecoratedMember()
* @D() m2() {}
* @D() f2;
* @D() get g2() { return this.x; }
* }
* ```
*/
cached
Node getADecoratedMember() { result = this.getASuccessor(Label::decoratedMember()) }
/**
* Gets any parameter that has this value as a decorator.
*
* For example:
* ```js
* import { D } from "foo";
*
* class C {
* method(
* // moduleImport("foo").getMember("D").getADecoratedParameter()
* @D
* param1,
* // moduleImport("foo").getMember("D").getReturn().getADecoratedParameter()
* @D()
* param2
* ) {}
* }
* ```
*/
cached
Node getADecoratedParameter() { result = this.getASuccessor(Label::decoratedParameter()) }
/**
* Gets a string representation of the lexicographically least among all shortest access paths
* from the root to this node.
@@ -570,6 +636,15 @@ module API {
lbl = Label::memberFromRef(pw)
)
)
or
decoratorDualEdge(base, lbl, rhs)
or
decoratorRhsEdge(base, lbl, rhs)
or
exists(DataFlow::PropWrite write |
decoratorPropEdge(base, lbl, write) and
rhs = write.getRhs()
)
}
/**
@@ -699,6 +774,98 @@ module API {
lbl = Label::parameter(1) and
ref = awaited(call)
)
or
decoratorDualEdge(base, lbl, ref)
or
decoratorUseEdge(base, lbl, ref)
or
// for fields and accessors, mark the reads as use-nodes
decoratorPropEdge(base, lbl, ref.(DataFlow::PropRead))
)
}
/** Holds if `base` is a use-node that flows to the decorator expression of the given decorator. */
pragma[nomagic]
private predicate useNodeFlowsToDecorator(TApiNode base, Decorator decorator) {
exists(DataFlow::SourceNode decoratorSrc |
use(base, decoratorSrc) and
trackUseNode(decoratorSrc).flowsToExpr(decorator.getExpression())
)
}
/**
* Holds if `ref` corresponds to both a use and def-node that should have an incoming edge from `base` labelled `lbl`.
*
* This happens because the decorated value escapes into the decorator function, and is then replaced
* by the function's return value. In the JS analysis we generally assume decorators return their input,
* but library models may want to find the return value.
*/
private predicate decoratorDualEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) {
exists(ClassDefinition cls |
useNodeFlowsToDecorator(base, cls.getADecorator()) and
lbl = Label::decoratedClass() and
ref = DataFlow::valueNode(cls)
)
or
exists(MethodDefinition method |
useNodeFlowsToDecorator(base, method.getADecorator()) and
not method instanceof AccessorMethodDefinition and
lbl = Label::decoratedMember() and
ref = DataFlow::valueNode(method.getBody())
)
}
/** Holds if `ref` is a use that should have an incoming edge from `base` labelled `lbl`, induced by a decorator. */
private predicate decoratorUseEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node ref) {
exists(SetterMethodDefinition accessor |
useNodeFlowsToDecorator(base,
[accessor.getADecorator(), accessor.getCorrespondingGetter().getADecorator()]) and
lbl = Label::decoratedMember() and
ref = DataFlow::parameterNode(accessor.getBody().getParameter(0))
)
or
exists(Parameter param |
useNodeFlowsToDecorator(base, param.getADecorator()) and
lbl = Label::decoratedParameter() and
ref = DataFlow::parameterNode(param)
)
}
/** Holds if `rhs` is a def node that should have an incoming edge from `base` labelled `lbl`, induced by a decorator. */
private predicate decoratorRhsEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::Node rhs) {
exists(GetterMethodDefinition accessor |
useNodeFlowsToDecorator(base,
[accessor.getADecorator(), accessor.getCorrespondingSetter().getADecorator()]) and
lbl = Label::decoratedMember() and
rhs = DataFlow::valueNode(accessor.getBody().getAReturnedExpr())
)
}
/**
* Holds if `ref` is a reference to a field/accessor that should have en incoming edge from base labelled `lbl`.
*
* Since fields do not have their own data-flow nodes, we generate a node for each read or write.
* For property writes, the right-hand side becomes a def-node and property reads become use-nodes.
*
* For accessors this predicate computes each use of the accessor.
* The return value inside the accessor is computed by the `decoratorRhsEdge` predicate.
*/
private predicate decoratorPropEdge(TApiNode base, Label::ApiLabel lbl, DataFlow::PropRef ref) {
exists(MemberDefinition fieldLike, DataFlow::ClassNode cls |
fieldLike instanceof FieldDefinition
or
fieldLike instanceof AccessorMethodDefinition
|
useNodeFlowsToDecorator(base, fieldLike.getADecorator()) and
lbl = Label::decoratedMember() and
cls = fieldLike.getDeclaringClass().flow() and
(
fieldLike.isStatic() and
ref = cls.getAClassReference().getAPropertyReference(fieldLike.getName())
or
not fieldLike.isStatic() and
ref = cls.getAnInstanceReference().getAPropertyReference(fieldLike.getName())
)
)
}
@@ -1106,6 +1273,15 @@ module API {
/** Gets the `promisedError` edge label connecting a promise to its rejected value. */
LabelPromisedError promisedError() { any() }
/** Gets the label for an edge leading from a value `D` to any class that has `D` as a decorator. */
LabelDecoratedClass decoratedClass() { any() }
/** Gets the label for an edge leading from a value `D` to any method, field, or accessor that has `D` as a decorator. */
LabelDecoratedMethod decoratedMember() { any() }
/** Gets the label for an edge leading from a value `D` to any parameter that has `D` as a decorator. */
LabelDecoratedParameter decoratedParameter() { any() }
/** Gets an entry-point label for the entry-point `e`. */
LabelEntryPoint entryPoint(API::EntryPoint e) { result.getEntryPoint() = e }
@@ -1140,6 +1316,9 @@ module API {
MkLabelReturn() or
MkLabelPromised() or
MkLabelPromisedError() or
MkLabelDecoratedClass() or
MkLabelDecoratedMember() or
MkLabelDecoratedParameter() or
MkLabelEntryPoint(API::EntryPoint e)
/** A label for an entry-point. */
@@ -1229,6 +1408,21 @@ module API {
class LabelReceiver extends ApiLabel, MkLabelReceiver {
override string toString() { result = "receiver" }
}
/** A label for a class decorated by the current value. */
class LabelDecoratedClass extends ApiLabel, MkLabelDecoratedClass {
override string toString() { result = "decorated-class" }
}
/** A label for a method, field, or accessor decorated by the current value. */
class LabelDecoratedMethod extends ApiLabel, MkLabelDecoratedMember {
override string toString() { result = "decorated-member" }
}
/** A label for a parameter decorated by the current value. */
class LabelDecoratedParameter extends ApiLabel, MkLabelDecoratedParameter {
override string toString() { result = "decorated-parameter" }
}
}
}
}

View File

@@ -895,7 +895,14 @@ class SyntheticConstructor extends Function {
* }
* ```
*/
abstract class AccessorMethodDeclaration extends MethodDeclaration { }
abstract class AccessorMethodDeclaration extends MethodDeclaration {
/** Get the corresponding getter (if this is a setter) or setter (if this is a getter). */
AccessorMethodDeclaration getOtherAccessor() {
getterSetterPair(this, result)
or
getterSetterPair(result, this)
}
}
/**
* A concrete accessor method definition in a class, that is, an accessor method with a function body.
@@ -958,6 +965,9 @@ abstract class AccessorMethodSignature extends MethodSignature, AccessorMethodDe
*/
class GetterMethodDeclaration extends AccessorMethodDeclaration, @property_getter {
override string getAPrimaryQlClass() { result = "GetterMethodDeclaration" }
/** Gets the correspinding setter declaration, if any. */
SetterMethodDeclaration getCorrespondingSetter() { getterSetterPair(this, result) }
}
/**
@@ -1020,6 +1030,9 @@ class GetterMethodSignature extends GetterMethodDeclaration, AccessorMethodSigna
*/
class SetterMethodDeclaration extends AccessorMethodDeclaration, @property_setter {
override string getAPrimaryQlClass() { result = "SetterMethodDeclaration" }
/** Gets the correspinding getter declaration, if any. */
GetterMethodDeclaration getCorrespondingGetter() { getterSetterPair(result, this) }
}
/**
@@ -1263,3 +1276,25 @@ class IndexSignature extends @index_signature, MemberSignature {
override string getAPrimaryQlClass() { result = "IndexSignature" }
}
private boolean getStaticness(AccessorMethodDefinition member) {
member.isStatic() and result = true
or
not member.isStatic() and result = false
}
pragma[nomagic]
private AccessorMethodDefinition getAnAccessorFromClass(
ClassDefinition cls, string name, boolean static
) {
result = cls.getMember(name) and
static = getStaticness(result)
}
pragma[nomagic]
private predicate getterSetterPair(GetterMethodDeclaration getter, SetterMethodDeclaration setter) {
exists(ClassDefinition cls, string name, boolean static |
getter = getAnAccessorFromClass(cls, name, static) and
setter = getAnAccessorFromClass(cls, name, static)
)
}

View File

@@ -124,6 +124,15 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
token.getName() = "Parameter" and
token.getAnArgument() = "this" and
result = node.getReceiver()
or
token.getName() = "DecoratedClass" and
result = node.getADecoratedClass()
or
token.getName() = "DecoratedMember" and
result = node.getADecoratedMember()
or
token.getName() = "DecoratedParameter" and
result = node.getADecoratedParameter()
}
/**
@@ -210,7 +219,11 @@ InvokeNode getAnInvocationOf(API::Node node) { result = node.getAnInvocation() }
*/
bindingset[name]
predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
name = ["Member", "Instance", "Awaited", "ArrayElement", "Element", "MapValue", "NewCall", "Call"]
name =
[
"Member", "Instance", "Awaited", "ArrayElement", "Element", "MapValue", "NewCall", "Call",
"DecoratedClass", "DecoratedMember", "DecoratedParameter"
]
}
/**
@@ -218,7 +231,11 @@ predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
* in an identifying access path.
*/
predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
name = ["Instance", "Awaited", "ArrayElement", "Element", "MapValue", "NewCall", "Call"]
name =
[
"Instance", "Awaited", "ArrayElement", "Element", "MapValue", "NewCall", "Call",
"DecoratedClass", "DecoratedMember", "DecoratedParameter"
]
}
/**