JS: introduce implicit this uses in general

This commit is contained in:
Asger F
2024-09-23 14:11:51 +02:00
parent 8dc0505f84
commit d31499d727
6 changed files with 115 additions and 50 deletions

View File

@@ -26,6 +26,7 @@ private import internal.AnalyzedParameters
private import internal.PreCallGraphStep
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.dataflow.internal.DataFlowPrivate as Private
private import semmle.javascript.dataflow.internal.VariableOrThis
module DataFlow {
/**
@@ -729,9 +730,7 @@ module DataFlow {
private class ParameterFieldAsPropWrite extends PropWrite, PropNode {
override ParameterField prop;
override Node getBase() {
thisNode(result, prop.getDeclaringClass().getConstructor().getBody())
}
override Node getBase() { result = TImplicitThisUse(prop, false) }
override Expr getPropertyNameExpr() {
none() // The parameter value is not the name of the field
@@ -758,9 +757,7 @@ module DataFlow {
exists(prop.getInit())
}
override Node getBase() {
thisNode(result, prop.getDeclaringClass().getConstructor().getBody())
}
override Node getBase() { result = TImplicitThisUse(prop, false) }
override Expr getPropertyNameExpr() { result = prop.getNameExpr() }
@@ -1045,12 +1042,12 @@ module DataFlow {
}
/**
* A node representing the value passed as `this` argument in a `new` call or a `super` call.
* A node representing the value passed as `this` argument in a `new` call.
*/
class ConstructorThisArgumentNode extends TConstructorThisArgumentNode, DataFlow::Node {
private InvokeExpr expr;
class NewCallThisArgumentNode extends TNewCallThisArgument, DataFlow::Node {
private NewExpr expr;
ConstructorThisArgumentNode() { this = TConstructorThisArgumentNode(expr) }
NewCallThisArgumentNode() { this = TNewCallThisArgument(expr) }
override string toString() { result = "implicit 'this' argument of " + expr }
@@ -1060,18 +1057,23 @@ module DataFlow {
}
/**
* A node representing the post-update node corresponding to implicit uses of `this` in a constructor.
* A node representing an implicit use of `this` or its post-update node.
*/
private class ConstructorThisPostUpdateNode extends TConstructorThisPostUpdate, DataFlow::Node {
private Function constructor;
private class ImplicitThisUseNode extends TImplicitThisUse, DataFlow::Node {
private ImplicitThisUse use;
private boolean isPost;
ConstructorThisPostUpdateNode() { this = TConstructorThisPostUpdate(constructor) }
ImplicitThisUseNode() { this = TImplicitThisUse(use, isPost) }
override string toString() { result = "[post-update] 'this' parameter of " + constructor }
override string toString() {
if isPost = false
then result = "implicit 'this'"
else result = "[post-update] implicit 'this'"
}
override StmtContainer getContainer() { result = constructor }
override StmtContainer getContainer() { result = use.getUseContainer() }
override Location getLocation() { result = constructor.getLocation() }
override Location getLocation() { result = use.getLocation() }
}
/**
@@ -1682,6 +1684,12 @@ module DataFlow {
pred = TReflectiveCallNode(call, _) and
succ = TValueNode(call)
)
or
// Pass 'this' into implicit uses of 'this'
exists(ImplicitThisUse use |
pred = TThisNode(use.getBindingContainer()) and
succ = TImplicitThisUse(use, false)
)
}
pragma[nomagic]
@@ -1772,12 +1780,6 @@ module DataFlow {
pred = TReflectiveParametersNode(f) and
succ = TValueNode(f.getArgumentsVariable().getAnAccess())
)
or
// Pass 'this' into super calls
exists(SuperCall call |
pred = TThisNode(call.getBinder()) and
succ = TConstructorThisArgumentNode(call)
)
}
private class ReflectiveParamsStep extends LegacyPreCallGraphStep {

View File

@@ -5,6 +5,7 @@
*/
private import javascript
private import codeql.util.Boolean
private import semmle.javascript.dataflow.internal.AdditionalFlowInternal
private import semmle.javascript.dataflow.internal.Contents::Private
private import semmle.javascript.dataflow.internal.sharedlib.DataFlowImplCommon as DataFlowImplCommon
@@ -13,6 +14,7 @@ private import semmle.javascript.dataflow.internal.DataFlowPrivate as DataFlowPr
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as FlowSummaryImpl
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate as FlowSummaryPrivate
private import semmle.javascript.dataflow.internal.VariableCapture as VariableCapture
private import semmle.javascript.dataflow.internal.VariableOrThis
cached
private module Cached {
@@ -31,7 +33,7 @@ private module Cached {
/** An SSA node from the legacy SSA library */
TSsaDefNode(SsaDefinition d) or
/** Use of a variable or 'this', with flow from a post-update node (from an earlier use) */
TSsaUseNode(Expr use) { use = any(Ssa2::SsaConfig::SourceVariable v).getAnAccess() } or
TSsaUseNode(ControlFlowNode use) { use = any(Ssa2::SsaConfig::SourceVariable v).getAUse() } or
/** Phi-read node (new SSA library). Ordinary phi nodes are represented by TSsaDefNode. */
TSsaPhiReadNode(Ssa2::PhiReadNode phi) or
/** Input to a phi node (new SSA library) */
@@ -88,8 +90,8 @@ private module Cached {
// The RHS of an assignment can be an argument to a setter-call, so it needs a post-update node
e = any(Assignment asn | asn.getTarget() instanceof PropAccess).getRhs()
} or
TConstructorThisArgumentNode(InvokeExpr e) { e instanceof NewExpr or e instanceof SuperCall } or
TConstructorThisPostUpdate(Constructor ctor) or
TNewCallThisArgument(NewExpr e) or
TImplicitThisUse(ImplicitThisUse use, Boolean isPost) or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TFlowSummaryDynamicParameterArrayNode(FlowSummaryImpl::Public::SummarizedCallable callable) or
TFlowSummaryIntermediateAwaitStoreNode(FlowSummaryImpl::Private::SummaryNode sn) {
@@ -130,8 +132,9 @@ private class TEarlyStageNode =
TFunctionSelfReferenceNode or TDestructuredModuleImportNode or THtmlAttributeNode or
TFunctionReturnNode or TExceptionalFunctionReturnNode or TExceptionalInvocationReturnNode or
TGlobalAccessPathRoot or TTemplatePlaceholderTag or TReflectiveParametersNode or
TExprPostUpdateNode or TConstructorThisArgumentNode or TStaticArgumentArrayNode or
TDynamicArgumentArrayNode or TStaticParameterArrayNode or TDynamicParameterArrayNode;
TExprPostUpdateNode or TNewCallThisArgument or TStaticArgumentArrayNode or
TDynamicArgumentArrayNode or TStaticParameterArrayNode or TDynamicParameterArrayNode or
TImplicitThisUse;
/**
* A data-flow node that is not a flow summary node.

View File

@@ -21,7 +21,7 @@ private class Node = DataFlow::Node;
class PostUpdateNode = DataFlow::PostUpdateNode;
class SsaUseNode extends DataFlow::Node, TSsaUseNode {
private Expr expr;
private ControlFlowNode expr;
SsaUseNode() { this = TSsaUseNode(expr) }
@@ -333,18 +333,13 @@ predicate postUpdatePair(Node pre, Node post) {
)
or
exists(NewExpr expr |
pre = TConstructorThisArgumentNode(expr) and
pre = TNewCallThisArgument(expr) and
post = TValueNode(expr)
)
or
exists(SuperCall expr |
pre = TConstructorThisArgumentNode(expr) and
post = TConstructorThisPostUpdate(expr.getBinder())
)
or
exists(Function constructor |
pre = TThisNode(constructor) and
post = TConstructorThisPostUpdate(constructor)
exists(ImplicitThisUse use |
pre = TImplicitThisUse(use, false) and
post = TImplicitThisUse(use, true)
)
or
FlowSummaryImpl::Private::summaryPostUpdateNode(post.(FlowSummaryNode).getSummaryNode(),
@@ -473,7 +468,7 @@ private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition
pos.isThis()
)
or
pos.isThis() and n = TConstructorThisArgumentNode(call.asOrdinaryCall().asExpr())
pos.isThis() and n = TNewCallThisArgument(call.asOrdinaryCall().asExpr())
or
// receiver of accessor call
pos.isThis() and n = call.asAccessorCall().getBase()
@@ -1068,6 +1063,11 @@ private Node getNodeFromSsa2(Ssa2::Node node) {
or
result = TExprPostUpdateNode(node.(Ssa2::ExprPostUpdateNode).getExpr())
or
exists(ImplicitThisUse use |
node.(Ssa2::ExprPostUpdateNode).getExpr() = use and
result = TImplicitThisUse(use, true)
)
or
result = TSsaPhiReadNode(node.(Ssa2::SsaDefinitionExtNode).getDefinitionExt())
or
result = TSsaInputNode(node.(Ssa2::SsaInputNode))
@@ -1091,6 +1091,11 @@ private predicate useUseFlow(Node node1, Node node2) {
node1 = TSsaUseNode(use) and
node2 = TValueNode(use)
)
or
exists(ImplicitThisUse use |
node1 = TSsaUseNode(use) and
node2 = TImplicitThisUse(use, false)
)
}
predicate simpleLocalFlowStep(Node node1, Node node2, string model) {
@@ -1298,11 +1303,16 @@ private Node getPostUpdateForStore(Node base) {
expr instanceof VarAccess or
expr instanceof ThisExpr
)
or
exists(ImplicitThisUse use |
base = TImplicitThisUse(use, false) and
result = TImplicitThisUse(use, true)
)
}
/** Gets node to target with a store to the given `base` object.. */
pragma[inline]
private Node getStoreTarget(Node base) {
private Node getStoreTarget(DataFlow::Node base) {
result = getPostUpdateForStore(base)
or
not exists(getPostUpdateForStore(base)) and

View File

@@ -1,4 +1,5 @@
private import javascript
private import DataFlowNode
cached
private newtype TLocalVariableOrThis =
@@ -44,14 +45,17 @@ class LocalVariableOrThis extends TLocalVariableOrThis {
result = this.asThisContainer()
}
/** Gets an access to `this` represented by this value. */
ThisExpr getAThisAccess() { result.getBindingContainer() = this.asThisContainer() }
/** Gets an explicit access to `this` represented by this value. */
ThisExpr getAThisExpr() { result.getBindingContainer() = this.asThisContainer() }
/** Gets an access to variable or `this`. */
Expr getAnAccess() {
/** Gets an implicit or explicit use of the `this` represented by this value. */
ThisUse getAThisUse() { result.getBindingContainer() = this.asThisContainer() }
/** Gets an expression that accesses this variable or `this`. */
ControlFlowNode getAUse() {
result = this.asLocalVariable().getAnAccess()
or
result = this.getAThisAccess()
result = this.getAThisUse()
}
}
@@ -74,3 +78,50 @@ module LocalVariableOrThis {
/** Gets the representation of `this` in the given container. */
LocalVariableOrThis thisInContainer(StmtContainer c) { result = TThis(c) }
}
/**
* An explicit or implicit use of `this`.
*
* Implicit uses include `super()` calls and instance field initializers (which includes TypeScript parameter fields).
*/
abstract class ThisUse instanceof ControlFlowNode {
/** Gets the container binding the `this` being accessed */
abstract StmtContainer getBindingContainer();
abstract StmtContainer getUseContainer();
string toString() { result = super.toString() }
DbLocation getLocation() { result = super.getLocation() }
}
private predicate implicitThisUse(ControlFlowNode node, StmtContainer thisBinder) {
thisBinder = node.(SuperExpr).getBinder()
or
exists(FieldDefinition field |
not field.isStatic() and
node = field and
thisBinder = field.getDeclaringClass().getConstructor().getBody()
)
}
class ImplicitThisUse extends ThisUse {
ImplicitThisUse() { implicitThisUse(this, _) }
override StmtContainer getBindingContainer() { implicitThisUse(this, result) }
override StmtContainer getUseContainer() {
// The following differs from FieldDefinition.getContainer() which returns the container enclosing
// the class, not the class constructor.
// TODO: consider changing this in FieldDefinition.getContainer()
result = this.(FieldDefinition).getDeclaringClass().getConstructor().getBody()
or
result = this.(SuperExpr).getContainer()
}
}
private class ExplicitThisUse extends ThisUse instanceof ThisExpr {
override StmtContainer getBindingContainer() { result = ThisExpr.super.getBindingContainer() }
override StmtContainer getUseContainer() { result = ThisExpr.super.getContainer() }
}

View File

@@ -42,8 +42,7 @@ module SsaConfig implements InputSig<js::DbLocation> {
bb.useAt(i, v.asLocalVariable(), _) and certain = true
or
certain = true and
bb.getNode(i) = v.getAThisAccess()
// TODO: also account for: super() and field initialisers
bb.getNode(i).(ThisUse).getBindingContainer() = v.asThisContainer()
}
predicate getImmediateBasicBlockDominator = BasicBlockInternal::immediateDominator/1;
@@ -55,8 +54,8 @@ module SsaConfig implements InputSig<js::DbLocation> {
import Make<js::DbLocation, SsaConfig>
private module SsaDataflowInput implements DataFlowIntegrationInputSig {
class Expr extends js::Expr {
Expr() { this = any(SsaConfig::SourceVariable v).getAnAccess() }
class Expr extends js::ControlFlowNode {
Expr() { this = any(SsaConfig::SourceVariable v).getAUse() }
predicate hasCfgNode(js::BasicBlock bb, int i) { this = bb.getNode(i) }
}

View File

@@ -60,5 +60,5 @@ function t5() {
}
}
const c = new C();
sink(c.field); // $ MISSING: hasValueFlow=t5.1
sink(c.field); // $ hasValueFlow=t5.1
}