Skip to content
Draft
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
16 changes: 14 additions & 2 deletions unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ module Public {
}
}

final class PotentialLocalNameAccess = PotentialLocalNameAccessImpl;

/**
* A name node that is possibly a reference to a local name, but could also refer to a member
* visible through imports or inheritance.
Expand All @@ -493,17 +495,27 @@ module Public {
* }
* ```
*/
class PotentialLocalNameAccess extends IdentifierExpr {
abstract private class PotentialLocalNameAccessImpl extends AstNode {
/** Gets the representative for the local name being accessed. */
LocalName getLocalName() { result = this.(LocalNameBindingOutput::LocalAccess).getLocal() }

/** Gets the name being accessed. */
string getName() { result = this.getValue() }
abstract string getName();

/** Holds if this is one of the binding sites for a name, such as the `x` in `let x = 123`. */
predicate isBindingSite() { this instanceof NameBinding }
}

private class IdentifierPotentialLocalNameAccess extends PotentialLocalNameAccessImpl,
IdentifierExpr
{
override string getName() { result = this.getValue() }
}

private class SuperPotentialLocalNameAccess extends PotentialLocalNameAccessImpl, SuperExpr {
override string getName() { result = any(NameBindingPlugin p).getSuperLocalName() }
}

/** Gets the implicitly-declared variable through which the given callable refers to its receiver. */
LocalVariable getImplicitReceiverVariable(Callable callable) {
exists(string name |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ class NameBindingPlugin extends Unit {
/** Gets the name of the implicit receiver parameter in `callable`, if it has one. */
string getImplicitReceiverParameterName(Callable callable) { none() }

/**
* Gets the name to be used when resolving `super` expressions, for example
* `this` or `self`.
*/
string getSuperLocalName() { none() }

/**
* Gets the name through which static members of the enclosing class `cls` can be
* accessed, for example `Self` in Swift.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class NameBindingPluginSwift extends NameBindingPlugin {
result = "self"
}

override string getSuperLocalName() { result = "self" }

bindingset[cls]
override string getStaticSelfName(ClassLikeDeclaration cls) { exists(cls) and result = "Self" }
}
Expand Down
55 changes: 53 additions & 2 deletions unified/ql/lib/codeql/unified/internal/dataflow/Content.qll
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
private import unified
private import AllDataFlow
private import codeql.unified.internal.typeinference.TypeInference as T
private import codeql.unified.internal.typeinference.TypeInferencePlugin as TP

/**
* Holds if a MaD access path token of form `name[arg]` exists.
Expand All @@ -8,8 +10,20 @@ predicate isAccessPathTokenPresent(string name, string arg) {
arg = any(FlowSummaryOutput::Private::AccessPathToken tok).getAnArgument(name)
}

private class ResolvedField extends VariableDeclaration {
ResolvedField() { this = T::resolveFieldAccess(_) }

string getName() {
result = super.getName() and
not this = any(TP::TupleType tt).getField(_)
or
this = any(TP::TupleType tt).getField(result)
}
}

private newtype TContent =
TArrayElement() or
TFieldContent(ResolvedField field) or
TNamedMember(string name) {
name = any(Identifier id).getValue()
or
Expand All @@ -24,23 +38,35 @@ private newtype TContent =
TCapturedVariable(CaptureSsaInput::CapturedVariable v)

class Content extends TContent {
ResolvedField asField() { this = TFieldContent(result) }

string asNamedMember() { this = TNamedMember(result) }

predicate isArrayElement() { this = TArrayElement() }

string toString() {
result = this.asNamedMember()
or
result = this.asField().getName()
or
this.isArrayElement() and result = "ArrayElement"
or
result = this.asCapturedVariable().toString()
}

LocalVariable asCapturedVariable() { this = TCapturedVariable(result) }

Location getLocation() { result = this.asCapturedVariable().getLocation() }
Location getLocation() {
result = this.asCapturedVariable().getLocation()
or
result = this.asField().getLocation()
}
}

private class TContentApprox = TArrayElement or TNamedMember or TCapturedVariable;

class ContentApprox extends Content, TContentApprox { }

private newtype TContentSet = TSingleton(Content content)

class ContentSet extends TContentSet {
Expand All @@ -52,10 +78,35 @@ class ContentSet extends TContentSet {

Content getAStoreContent() { result = this.asSingleton() }

Content getAReadContent() { result = this.asSingleton() }
Content getAReadContent() {
result = this.asSingleton()
or
result.asNamedMember() = this.asSingleton().asField().getName()
or
result.asField().getName() = this.asSingleton().asNamedMember()
Comment thread
hvitved marked this conversation as resolved.
Resolved
}
}

module ContentSet {
/**
* Gets the resolved field content for `access`, falling back to name-based content when resolution
* is missing.
*/
ContentSet member(Expr access) {
result.asSingleton().asField() = T::resolveFieldAccess(access)
or
not exists(T::resolveFieldAccess(access)) and
not exists(T::resolveAccessorAccess(access)) and
exists(string name |
name =
[
access.(MemberAccessExpr).getMemberName(),
access.(UnqualifiedMemberAccess).getMemberName()
] and
result = namedMember(name)
)
}

ContentSet namedMember(string name) { result.asSingleton().asNamedMember() = name }

ContentSet arrayElement() { result.asSingleton().isArrayElement() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ predicate step(BuilderNode node1, Step step, BuilderNode node2) {
or
exists(UnqualifiedMemberAccess access | access.isInstanceAccess() |
node1.isLocalVariableRead(access, access.getImplicitQualifierVariable()) and
step.readName(access.getMemberName()) and
step.read(ContentSet::member(access)) and
node2.isResultValue(access)
or
(node1.isIncomingValue(access) or node1.isPostUpdate(access)) and
step.storeName(access.getMemberName()) and
step.store(ContentSet::member(access)) and
node2.isLocalVariablePostUpdate(access, access.getImplicitQualifierVariable())
or
// Pass the receiver along to the getter/setter invoked by this expression
Expand Down Expand Up @@ -129,11 +129,11 @@ predicate step(BuilderNode node1, Step step, BuilderNode node2) {
or
exists(MemberAccessExpr expr |
node1.isResultValue(expr.getBase()) and
step.readName(expr.getMemberName()) and
step.read(ContentSet::member(expr)) and
node2.isResultValue(expr)
or
(node1.isIncomingValue(expr) or node1.isPostUpdate(expr)) and
step.storeName(expr.getMemberName()) and
step.store(ContentSet::member(expr)) and
node2.isPostUpdate(expr.getBase())
or
// Pass the receiver along to the getter/setter invoked by this expression
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ module DataFlowInput implements InputSig<Location> {

class ContentSet = D::ContentSet;

class ContentApprox = Content; // TODO
class ContentApprox = D::ContentApprox;

ContentApprox getContentApprox(Content c) { result = c } // TODO
ContentApprox getContentApprox(Content c) {
result = c
or
result.asNamedMember() = c.asField().getName()
}

//
// Parameter, argument, return, and out nodes and their positions/kinds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,11 @@ module Input3 implements InputSig3 {
}

class Variable extends LocalNameBindingOutput::Local {
Expr getAnAccess() { result = super.getAnAccess() }
Expr getAnAccess() {
result = super.getAnAccess() and
// for type inference, we want super expressions to have the type of the base class
not result instanceof SuperExpr
}
}

final class VariableDeclaration = VariableDeclarationImpl;
Expand Down
89 changes: 89 additions & 0 deletions unified/ql/test/library-tests/dataflow/fields.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
func source(_ s: String) -> String { return s }

@discardableResult
func sink(_ s: Any) -> String { return "" }

class FieldBox {
var field: String = ""
}

class OtherFieldBox {
var field: String = ""
}

class InheritedFieldBox: FieldBox {}

func resolvedFields() {
let box = FieldBox()
box.field = source("fields.resolved")
sink(box.field) // $ hasValueFlow=fields.resolved
sink((box as! OtherFieldBox).field) // no flow
}

func inheritedFields() {
let box = InheritedFieldBox()
box.field = source("fields.inherited")
sink((box as! FieldBox).field) // $ hasValueFlow=fields.inherited
}

func unresolvedFieldRead() {
let box = FieldBox()
box.field = source("fields.unresolved-read")
sink((box as! UnknownFieldBox).field) // $ hasValueFlow=fields.unresolved-read
}

func unresolvedFieldStore() {
let box = FieldBox()
(box as! UnknownFieldBox).field = source("fields.unresolved-store")
sink(box.field) // $ hasValueFlow=fields.unresolved-store
}

func summarizedFieldRead() {
let box = FieldBox()
box.field = source("fields.summary")
sink(TestType.readField(box)) // $ hasValueFlow=fields.summary
}

class BaseBox {
var field: String = ""
}

class OverridingBox: BaseBox {
override var field: String {
get { super.field }
set { super.field = newValue }
}
}

func overriddenFieldRead() {
let base: BaseBox = OverridingBox()
base.field = source("fields.override-read")
let sub = base as! OverridingBox
sink(sub.field) // $ hasValueFlow=fields.override-read
}

protocol FieldProtocol {
var field: String { get set }
}

class ProtocolBox: FieldProtocol {
var field: String = ""
}

func protocolFieldRead() {
let box = ProtocolBox()
box.field = source("fields.protocol")
sink(box.field) // $ hasValueFlow=fields.protocol
let p = box as FieldProtocol
sink(p.field) // $ MISSING: hasValueFlow=fields.protocol -- missing dispatch from `FieldProtocol.get_field` to `ProtocolBox.field`
}

func protocolFieldWrite() {
let box = ProtocolBox()
var p = box as FieldProtocol
p.field = source("fields.protocol")
sink(p.field) // $ MISSING: hasValueFlow=fields.protocol -- missing dispatch from `FieldProtocol.{get,set}_field` to `ProtocolBox.field`
sink(box.field) // $ MISSING: hasValueFlow=fields.protocol (aliasing)
let box2 = p as! OtherFieldBox
sink(box2.field) // no flow
}
Loading
Loading