Compare commits

...

9 Commits

Author SHA1 Message Date
Alex Eyers-Taylor
2c3442d4eb Fix a bad join order on locations. 2025-04-25 15:09:41 +01:00
Alex Eyers-Taylor
29fcbe3c8f Swift extract predicates to improve TTE to avoid loop invariant code. 2025-04-25 15:09:41 +01:00
Alex Eyers-Taylor
5ba6904e91 Use late inline to deal with bad join orders from type tracking. 2025-04-25 15:09:40 +01:00
Alex Eyers-Taylor
1cf1acbbc6 Ruby: Avoid a forced CP. 2025-04-25 15:09:40 +01:00
Alex Eyers-Taylor
7096abe0a9 CPP: Add noinline so size estimates are better. 2025-04-25 15:09:40 +01:00
Alex Eyers-Taylor
15a26fd2ee Extarct to predicate for RTJO 2025-04-25 15:09:40 +01:00
Alex Eyers-Taylor
25c47922ec CPP: Pull rank into predicate for RTJO. 2025-04-25 15:09:39 +01:00
Alex Eyers-Taylor
672d896a80 CPP: Manually flip TC for RTJO. 2025-04-25 15:09:39 +01:00
Alex Eyers-Taylor
34ab6b3919 Reduce number of negations in some cases. 2025-04-25 15:09:39 +01:00
12 changed files with 132 additions and 76 deletions

View File

@@ -88,6 +88,7 @@ class Declaration extends Locatable, @declaration {
* *
* See the 3-argument `hasQualifiedName` for examples. * See the 3-argument `hasQualifiedName` for examples.
*/ */
pragma[noinline]
predicate hasQualifiedName(string namespaceQualifier, string baseName) { predicate hasQualifiedName(string namespaceQualifier, string baseName) {
this.hasQualifiedName(namespaceQualifier, "", baseName) this.hasQualifiedName(namespaceQualifier, "", baseName)
} }

View File

@@ -2011,13 +2011,23 @@ module ExprFlowCached {
localFlowStep(n1, n2) localFlowStep(n1, n2)
} }
/**
* Holds if `asExpr(n1)` doesn't have a result and `n1` flows to `n2` in a single
* dataflow step.
*
* i.e. this is the transpose of `localStepFromNonExpr`
*/
private predicate localStepFromNonExprR(Node n2, Node n1) {
localStepFromNonExpr(n1, n2)
}
/** /**
* Holds if `asExpr(n1)` doesn't have a result, `asExpr(n2) = e2` and * Holds if `asExpr(n1)` doesn't have a result, `asExpr(n2) = e2` and
* `n2` is the first node reachable from `n1` such that `asExpr(n2)` exists. * `n2` is the first node reachable from `n1` such that `asExpr(n2)` exists.
*/ */
pragma[nomagic] pragma[nomagic]
private predicate localStepsToExpr(Node n1, Node n2, Expr e2) { private predicate localStepsToExpr(Node n1, Node n2, Expr e2) {
localStepFromNonExpr*(n1, n2) and localStepFromNonExprR*(n2, n1) and
e2 = asExprInternal(n2) e2 = asExprInternal(n2)
} }

View File

@@ -815,10 +815,15 @@ private predicate isRelatableMemoryLocation(VariableMemoryLocation vml) {
vml.getStartBitOffset() != Ints::unknown() vml.getStartBitOffset() != Ints::unknown()
} }
pragma[noinline]
private int getRank(VirtualVariable vvar, IntValue offset) {
offset = rank[result](IntValue offset_ | isRelevantOffset(vvar, offset_))
}
private predicate isCoveredOffset(Allocation var, int offsetRank, VariableMemoryLocation vml) { private predicate isCoveredOffset(Allocation var, int offsetRank, VariableMemoryLocation vml) {
exists(int startRank, int endRank, VirtualVariable vvar | exists(int startRank, int endRank, VirtualVariable vvar |
vml.getStartBitOffset() = rank[startRank](IntValue offset_ | isRelevantOffset(vvar, offset_)) and startRank = getRank(vvar, vml.getStartBitOffset()) and
vml.getEndBitOffset() = rank[endRank](IntValue offset_ | isRelevantOffset(vvar, offset_)) and endRank = getRank(vvar, vml.getEndBitOffset()) and
var = vml.getAnAllocation() and var = vml.getAnAllocation() and
vvar = vml.getVirtualVariable() and vvar = vml.getVirtualVariable() and
isRelatableMemoryLocation(vml) and isRelatableMemoryLocation(vml) and

View File

@@ -117,27 +117,29 @@ class TranslatedStaticStorageDurationVarInit extends TranslatedRootElement,
* directly accessed by the function. * directly accessed by the function.
*/ */
final predicate hasUserVariable(Variable varUsed, CppType type) { final predicate hasUserVariable(Variable varUsed, CppType type) {
( this.hasUserVariable1(varUsed) and
(
varUsed instanceof GlobalOrNamespaceVariable
or
varUsed instanceof StaticLocalVariable
or
varUsed instanceof MemberVariable and not varUsed instanceof Field
) and
exists(VariableAccess access |
access.getTarget() = varUsed and
getEnclosingVariable(access) = var
)
or
var = varUsed
or
varUsed.(LocalScopeVariable).getEnclosingElement*() = var
or
varUsed.(Parameter).getCatchBlock().getEnclosingElement*() = var
) and
type = getTypeForPRValue(getVariableType(varUsed)) type = getTypeForPRValue(getVariableType(varUsed))
} }
private predicate hasUserVariable1(Variable varUsed) {
(
varUsed instanceof GlobalOrNamespaceVariable
or
varUsed instanceof StaticLocalVariable
or
varUsed instanceof MemberVariable and not varUsed instanceof Field
) and
exists(VariableAccess access |
access.getTarget() = varUsed and
getEnclosingVariable(access) = var
)
or
var = varUsed
or
varUsed.(LocalScopeVariable).getEnclosingElement*() = var
or
varUsed.(Parameter).getCatchBlock().getEnclosingElement*() = var
}
} }
TranslatedStaticStorageDurationVarInit getTranslatedVarInit(Variable var) { result.getAst() = var } TranslatedStaticStorageDurationVarInit getTranslatedVarInit(Variable var) { result.getAst() = var }

View File

@@ -44,6 +44,8 @@ class UsingWhileAfterWhile extends WhileStmt {
} }
} }
/** /**
* Using arithmetic in a condition. * Using arithmetic in a condition.
*/ */
@@ -55,15 +57,15 @@ class UsingArithmeticInComparison extends BinaryArithmeticOperation {
*/ */
UsingArithmeticInComparison() { UsingArithmeticInComparison() {
this.getParent*() instanceof IfStmt and this.getParent*() instanceof IfStmt and
not this.getAChild*().isConstant() and not (this.getAChild*().isConstant() or
not this.getParent*() instanceof Call and this.getParent*() instanceof Call or
not this.getParent*() instanceof AssignExpr and this.getParent*() instanceof AssignExpr or
not this.getParent*() instanceof ArrayExpr and this.getParent*() instanceof ArrayExpr or
not this.getParent*() instanceof RemExpr and this.getParent*() instanceof RemExpr or
not this.getParent*() instanceof AssignBitwiseOperation and this.getParent*() instanceof AssignBitwiseOperation or
not this.getParent*() instanceof AssignArithmeticOperation and this.getParent*() instanceof AssignArithmeticOperation or
not this.getParent*() instanceof EqualityOperation and this.getParent*() instanceof EqualityOperation or
not this.getParent*() instanceof RelationalOperation this.getParent*() instanceof RelationalOperation)
} }
/** Holds when the expression is inside the loop body. */ /** Holds when the expression is inside the loop body. */

View File

@@ -13,6 +13,39 @@ predicate duplicateChildren(Element e, int i) {
not e instanceof Constructor not e instanceof Constructor
} }
predicate allowableGap(Element e, int i) {
// Annotations are child 0 upwards, 'implements' are -2 downwards,
// and there may or may not be an 'extends' for child -1.
e instanceof ClassOrInterface and i = -1
or
// A class instance creation expression has the type as child -3,
// may or may not have a qualifier as child -2, and will never have
// a child -1.
e instanceof ClassInstanceExpr and i = [-2, -1]
or
// Type access have annotations from -2 down, and type
// arguments from 0 up, but may or may not have a qualifier
// at -1.
e instanceof TypeAccess and i = -1
or
// Try statements have their 'finally' clause as child 2,
// and that may or may not exist.
e instanceof TryStmt and i = -2
or
// For statements may or may not declare a new variable (child 0), or
// have a condition (child 1).
e instanceof ForStmt and i = [0, 1]
or
// Pattern case statements legitimately have a TypeAccess (-2) and a pattern (0) but not a rule (-1)
i = -1 and e instanceof PatternCase and not e.(PatternCase).isRule()
or
// Pattern case statements can have a gap at -3 when they have more than one pattern but no guard.
i = -3 and count(e.(PatternCase).getAPattern()) > 1 and not exists(e.(PatternCase).getGuard())
or
// Instanceof with a record pattern is not expected to have a type access in position 1
i = 1 and e.(InstanceOfExpr).getPattern() instanceof RecordPatternExpr
}
predicate gapInChildren(Element e, int i) { predicate gapInChildren(Element e, int i) {
exists(int left, int right | exists(int left, int right |
left = min(int l | exists(nthChildOf(e, l))) and left = min(int l | exists(nthChildOf(e, l))) and
@@ -20,48 +53,24 @@ predicate gapInChildren(Element e, int i) {
i in [left .. right] and i in [left .. right] and
not exists(nthChildOf(e, i)) not exists(nthChildOf(e, i))
) and ) and
// Annotations are child 0 upwards, 'implements' are -2 downwards, not allowableGap(e, i) and
// and there may or may not be an 'extends' for child -1. not (
not (e instanceof ClassOrInterface and i = -1) and // Pattern case statements may have some missing type accesses, depending on the nature of the direct child
// A class instance creation expression has the type as child -3, (i = -2 or i < -4) and
// may or may not have a qualifier as child -2, and will never have e instanceof PatternCase
// a child -1. ) and
not (e instanceof ClassInstanceExpr and i = [-2, -1]) and // RecordPatternExpr extracts type-accesses only for its LocalVariableDeclExpr children
// Type access have annotations from -2 down, and type not (i < 0 and e instanceof RecordPatternExpr) and
// arguments from 0 up, but may or may not have a qualifier
// at -1.
not (e instanceof TypeAccess and i = -1) and
// Try statements have their 'finally' clause as child 2,
// and that may or may not exist.
not (e instanceof TryStmt and i = -2) and
// For statements may or may not declare a new variable (child 0), or
// have a condition (child 1).
not (e instanceof ForStmt and i = [0, 1]) and
// TODO: Clarify situation with Kotlin and MethodCall.
// -1 can be skipped (type arguments from -2 down, no qualifier at -1,
// then arguments from 0).
// Can we also skip arguments, e.g. due to defaults for parameters?
not (e instanceof MethodCall and e.getFile().isKotlinSourceFile()) and
// Kotlin-extracted annotations can have missing children where a default // Kotlin-extracted annotations can have missing children where a default
// value should be, because kotlinc doesn't load annotation defaults and we // value should be, because kotlinc doesn't load annotation defaults and we
// want to leave a space for another extractor to fill in the default if it // want to leave a space for another extractor to fill in the default if it
// is able. // is able.
not e instanceof Annotation and not e instanceof Annotation and
// Pattern case statements legitimately have a TypeAccess (-2) and a pattern (0) but not a rule (-1) // TODO: Clarify situation with Kotlin and MethodCall.
not (i = -1 and e instanceof PatternCase and not e.(PatternCase).isRule()) and // -1 can be skipped (type arguments from -2 down, no qualifier at -1,
// Pattern case statements can have a gap at -3 when they have more than one pattern but no guard. // then arguments from 0).
not ( // Can we also skip arguments, e.g. due to defaults for parameters?
i = -3 and count(e.(PatternCase).getAPattern()) > 1 and not exists(e.(PatternCase).getGuard()) not (e instanceof MethodCall and e.getFile().isKotlinSourceFile())
) and
// Pattern case statements may have some missing type accesses, depending on the nature of the direct child
not (
(i = -2 or i < -4) and
e instanceof PatternCase
) and
// Instanceof with a record pattern is not expected to have a type access in position 1
not (i = 1 and e.(InstanceOfExpr).getPattern() instanceof RecordPatternExpr) and
// RecordPatternExpr extracts type-accesses only for its LocalVariableDeclExpr children
not (i < 0 and e instanceof RecordPatternExpr)
} }
predicate lateFirstChild(Element e, int i) { predicate lateFirstChild(Element e, int i) {

View File

@@ -1372,7 +1372,7 @@ module API {
exists(DataFlow::TypeBackTracker t, StepSummary summary, DataFlow::Node next | exists(DataFlow::TypeBackTracker t, StepSummary summary, DataFlow::Node next |
next = trackDefNode(nd, t) and next = trackDefNode(nd, t) and
StepSummary::step(prev, next, summary) and StepSummary::step(prev, next, summary) and
result = t.prepend(summary) result = t.prepend_cached(summary)
) )
} }

View File

@@ -224,8 +224,16 @@ class TypeBackTracker extends TTypeBackTracker {
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, prop) } TypeBackTracker() { this = MkTypeBackTracker(hasReturn, prop) }
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */ /** Gets the summary resulting from prepending `step` to this type-tracking summary. */
bindingset[this, step]
pragma[inline_late]
TypeBackTracker prepend(StepSummary step) { result = this.prepend_cached(step) }
/**
* INTERNAL: DO NOT USE
* Raw version of prepend.
*/
cached cached
TypeBackTracker prepend(StepSummary step) { TypeBackTracker prepend_cached(StepSummary step) {
Stages::TypeTracking::ref() and Stages::TypeTracking::ref() and
step = LevelStep() and step = LevelStep() and
result = this result = this

View File

@@ -236,7 +236,7 @@ module Stages {
or or
exists(any(DataFlow::TypeTracker t).append(_)) exists(any(DataFlow::TypeTracker t).append(_))
or or
exists(any(DataFlow::TypeBackTracker t).prepend(_)) exists(any(DataFlow::TypeBackTracker t).prepend_cached(_))
or or
DataFlow::functionForwardingStep(_, _) DataFlow::functionForwardingStep(_, _)
or or

View File

@@ -12,8 +12,11 @@
import javascript import javascript
from LabeledStmt l, Case c int labelInCaseStartColumn(Case c, LabeledStmt l) {
where
l = c.getAChildStmt+() and l = c.getAChildStmt+() and
l.getLocation().getStartColumn() = c.getLocation().getStartColumn() result = l.getLocation().getStartColumn()
}
from LabeledStmt l, Case c
where labelInCaseStartColumn(c, l) = c.getLocation().getStartColumn()
select l.getChildExpr(0), "Non-case labels in switch statements are confusing." select l.getChildExpr(0), "Non-case labels in switch statements are confusing."

View File

@@ -54,6 +54,14 @@ class NetHttpRequest extends Http::Client::Request::Range, DataFlow::CallNode {
override DataFlow::Node getAUrlPart() { override DataFlow::Node getAUrlPart() {
result = request.getArgument(0) result = request.getArgument(0)
or or
result = this.getAUrlPartFromConstructor()
}
/**
* Gets a node that contributes to the URL of the request
* indirectly, through the constructor.
*/
private DataFlow::Node getAUrlPartFromConstructor() {
// Net::HTTP.new(...).get(...) // Net::HTTP.new(...).get(...)
exists(API::Node new | exists(API::Node new |
new = API::getTopLevelMember("Net").getMember("HTTP").getInstance() and new = API::getTopLevelMember("Net").getMember("HTTP").getInstance() and

View File

@@ -186,9 +186,17 @@ module Stmts {
) )
} }
private Element getLastElement() {
result = ast.getLastElement()
}
private Element getAnElement() {
result = ast.getAnElement()
}
predicate lastInner(ControlFlowElement last, Completion c) { predicate lastInner(ControlFlowElement last, Completion c) {
// Normal exit and no defer statements // Normal exit and no defer statements
astLast(ast.getLastElement().getFullyUnresolved(), last, c) and astLast(this.getLastElement().getFullyUnresolved(), last, c) and
not exists(this.getFirstDeferStmtBody()) and not exists(this.getFirstDeferStmtBody()) and
c instanceof NormalCompletion c instanceof NormalCompletion
or or
@@ -198,7 +206,7 @@ module Stmts {
or or
// Abnormal exit without any defer statements // Abnormal exit without any defer statements
not c instanceof NormalCompletion and not c instanceof NormalCompletion and
astLast(ast.getAnElement().getFullyUnresolved(), last, c) and astLast(this.getAnElement().getFullyUnresolved(), last, c) and
not exists(this.getFirstDeferStmtBody()) not exists(this.getFirstDeferStmtBody())
} }