Skip to content

Commit 5bb2144

Browse files
committed
C++: Add field-flow through addresses of fields
1 parent 30c67ba commit 5bb2144

File tree

2 files changed

+117
-79
lines changed

2 files changed

+117
-79
lines changed

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private newtype TIRDataFlowNode =
3939
} or
4040
TPostUpdateNodeImpl(Operand operand, int indirectionIndex) {
4141
operand = any(FieldAddress fa).getObjectAddressOperand() and
42-
indirectionIndex = [1 .. Ssa::countIndirectionsForCppType(Ssa::getLanguageType(operand))]
42+
indirectionIndex = [0 .. Ssa::countIndirectionsForCppType(Ssa::getLanguageType(operand))]
4343
or
4444
Ssa::isModifiableByCall(operand, indirectionIndex)
4545
} or
@@ -436,7 +436,12 @@ class Node extends TIRDataFlowNode {
436436
* `x.set(taint())` is a partial definition of `x`, and `transfer(&x, taint())` is
437437
* a partial definition of `&x`).
438438
*/
439-
Expr asPartialDefinition() { result = this.(PartialDefinitionNode).getDefinedExpr() }
439+
Expr asPartialDefinition() {
440+
exists(PartialDefinitionNode pdn | this = pdn |
441+
pdn.getIndirectionIndex() > 0 and
442+
result = pdn.getDefinedExpr()
443+
)
444+
}
440445

441446
/**
442447
* Gets an upper bound on the type of this node.
@@ -563,11 +568,17 @@ private class PostUpdateNodeImpl extends PartialDefinitionNode, TPostUpdateNodeI
563568
Operand getOperand() { result = operand }
564569

565570
/** Gets the indirection index associated with this node. */
566-
int getIndirectionIndex() { result = indirectionIndex }
571+
override int getIndirectionIndex() { result = indirectionIndex }
567572

568573
override Location getLocationImpl() { result = operand.getLocation() }
569574

570-
final override Node getPreUpdateNode() { hasOperandAndIndex(result, operand, indirectionIndex) }
575+
final override Node getPreUpdateNode() {
576+
indirectionIndex > 0 and
577+
hasOperandAndIndex(result, operand, indirectionIndex)
578+
or
579+
indirectionIndex = 0 and
580+
result.asOperand() = operand
581+
}
571582

572583
final override Expr getDefinedExpr() {
573584
result = operand.getDef().getUnconvertedResultExpression()
@@ -836,12 +847,14 @@ class IndirectArgumentOutNode extends PostUpdateNodeImpl {
836847
Function getStaticCallTarget() { result = this.getCallInstruction().getStaticCallTarget() }
837848

838849
override string toStringImpl() {
839-
// This string should be unique enough to be helpful but common enough to
840-
// avoid storing too many different strings.
841-
result = this.getStaticCallTarget().getName() + " output argument"
842-
or
843-
not exists(this.getStaticCallTarget()) and
844-
result = "output argument"
850+
exists(string prefix | if indirectionIndex > 0 then prefix = "" else prefix = "pointer to " |
851+
// This string should be unique enough to be helpful but common enough to
852+
// avoid storing too many different strings.
853+
result = prefix + this.getStaticCallTarget().getName() + " output argument"
854+
or
855+
not exists(this.getStaticCallTarget()) and
856+
result = prefix + "output argument"
857+
)
845858
}
846859
}
847860

@@ -1699,6 +1712,10 @@ abstract class PostUpdateNode extends Node {
16991712
* ```
17001713
*/
17011714
abstract private class PartialDefinitionNode extends PostUpdateNode {
1715+
/** Gets the indirection index of this node. */
1716+
abstract int getIndirectionIndex();
1717+
1718+
/** Gets the expression that is partially defined by this node. */
17021719
abstract Expr getDefinedExpr();
17031720
}
17041721

@@ -1713,6 +1730,8 @@ abstract private class PartialDefinitionNode extends PostUpdateNode {
17131730
* `getVariableAccess()` equal to `x`.
17141731
*/
17151732
class DefinitionByReferenceNode extends IndirectArgumentOutNode {
1733+
DefinitionByReferenceNode() { this.getIndirectionIndex() > 0 }
1734+
17161735
/** Gets the unconverted argument corresponding to this node. */
17171736
Expr getArgument() { result = this.getAddressOperand().getDef().getUnconvertedResultExpression() }
17181737

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternalsCommon.qll

Lines changed: 88 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -417,56 +417,10 @@ class BaseCallVariable extends AbstractBaseSourceVariable, TBaseCallVariable {
417417
override CppType getLanguageType() { result = getResultLanguageType(call) }
418418
}
419419

420-
/**
421-
* Holds if the value pointed to by `operand` can potentially be
422-
* modified be the caller.
423-
*/
424-
predicate isModifiableByCall(ArgumentOperand operand, int indirectionIndex) {
425-
exists(CallInstruction call, int index, CppType type |
426-
indirectionIndex = [1 .. countIndirectionsForCppType(type)] and
427-
type = getLanguageType(operand) and
428-
call.getArgumentOperand(index) = operand and
429-
if index = -1
430-
then
431-
// A qualifier is "modifiable" if:
432-
// 1. the member function is not const specified, or
433-
// 2. the member function is `const` specified, but returns a pointer or reference
434-
// type that is non-const.
435-
//
436-
// To see why this is necessary, consider the following function:
437-
// ```
438-
// struct C {
439-
// void* data_;
440-
// void* data() const { return data; }
441-
// };
442-
// ...
443-
// C c;
444-
// memcpy(c.data(), source, 16)
445-
// ```
446-
// the data pointed to by `c.data_` is potentially modified by the call to `memcpy` even though
447-
// `C::data` has a const specifier. So we further place the restriction that the type returned
448-
// by `call` should not be of the form `const T*` (for some deeply const type `T`).
449-
if call.getStaticCallTarget() instanceof Cpp::ConstMemberFunction
450-
then
451-
exists(PointerOrArrayOrReferenceType resultType |
452-
resultType = call.getResultType() and
453-
not resultType.isDeeplyConstBelow()
454-
)
455-
else any()
456-
else
457-
// An argument is modifiable if it's a non-const pointer or reference type.
458-
isModifiableAt(type, indirectionIndex)
459-
)
460-
}
461-
462-
/**
463-
* Holds if `t` is a pointer or reference type that supports at least `indirectionIndex` number
464-
* of indirections, and the `indirectionIndex` indirection cannot be modfiied by passing a
465-
* value of `t` to a function.
466-
*/
467-
private predicate isModifiableAtImpl(CppType cppType, int indirectionIndex) {
468-
indirectionIndex = [1 .. countIndirectionsForCppType(cppType)] and
469-
(
420+
private module IsModifiableAtImpl {
421+
bindingset[cppType, indirectionIndex]
422+
pragma[inline_late]
423+
private predicate impl(CppType cppType, int indirectionIndex) {
470424
exists(Type pointerType, Type base, Type t |
471425
pointerType = t.getUnderlyingType() and
472426
pointerType = any(Indirection ind).getUnderlyingType() and
@@ -480,29 +434,94 @@ private predicate isModifiableAtImpl(CppType cppType, int indirectionIndex) {
480434
// one of the members was modified.
481435
exists(base.stripType().(Cpp::Class).getAField())
482436
)
437+
}
438+
439+
private predicate isModifiableAtImplAtLeast1(CppType cppType, int indirectionIndex) {
440+
indirectionIndex = [1 .. countIndirectionsForCppType(cppType)] and
441+
(
442+
impl(cppType, indirectionIndex)
443+
or
444+
// If the `indirectionIndex`'th dereference of a type can be modified
445+
// then so can the `indirectionIndex + 1`'th dereference.
446+
isModifiableAtImplAtLeast1(cppType, indirectionIndex - 1)
447+
)
448+
}
449+
450+
private predicate isModifiableAtImplAt0(CppType cppType) { impl(cppType, 0) }
451+
452+
/**
453+
* Holds if `t` is a pointer or reference type that supports at least `indirectionIndex` number
454+
* of indirections, and the `indirectionIndex` indirection cannot be modfiied by passing a
455+
* value of `t` to a function.
456+
*/
457+
private predicate isModifiableAtImpl(CppType cppType, int indirectionIndex) {
458+
isModifiableAtImplAtLeast1(cppType, indirectionIndex)
483459
or
484-
// If the `indirectionIndex`'th dereference of a type can be modified
485-
// then so can the `indirectionIndex + 1`'th dereference.
486-
isModifiableAtImpl(cppType, indirectionIndex - 1)
487-
)
488-
}
460+
indirectionIndex = 0 and
461+
isModifiableAtImplAt0(cppType)
462+
}
489463

490-
/**
491-
* Holds if `t` is a type with at least `indirectionIndex` number of indirections,
492-
* and the `indirectionIndex` indirection can be modified by passing a value of
493-
* type `t` to a function function.
494-
*/
495-
bindingset[indirectionIndex]
496-
predicate isModifiableAt(CppType cppType, int indirectionIndex) {
497-
isModifiableAtImpl(cppType, indirectionIndex)
498-
or
499-
exists(PointerWrapper pw, Type t |
500-
cppType.hasType(t, _) and
501-
t.stripType() = pw and
502-
not pw.pointsToConst()
503-
)
464+
/**
465+
* Holds if `t` is a type with at least `indirectionIndex` number of indirections,
466+
* and the `indirectionIndex` indirection can be modified by passing a value of
467+
* type `t` to a function function.
468+
*/
469+
bindingset[indirectionIndex]
470+
predicate isModifiableAt(CppType cppType, int indirectionIndex) {
471+
isModifiableAtImpl(cppType, indirectionIndex)
472+
or
473+
exists(PointerWrapper pw, Type t |
474+
cppType.hasType(t, _) and
475+
t.stripType() = pw and
476+
not pw.pointsToConst()
477+
)
478+
}
479+
480+
/**
481+
* Holds if the value pointed to by `operand` can potentially be
482+
* modified be the caller.
483+
*/
484+
predicate isModifiableByCall(ArgumentOperand operand, int indirectionIndex) {
485+
exists(CallInstruction call, int index, CppType type |
486+
indirectionIndex = [0 .. countIndirectionsForCppType(type)] and
487+
type = getLanguageType(operand) and
488+
call.getArgumentOperand(index) = operand and
489+
if index = -1
490+
then
491+
// A qualifier is "modifiable" if:
492+
// 1. the member function is not const specified, or
493+
// 2. the member function is `const` specified, but returns a pointer or reference
494+
// type that is non-const.
495+
//
496+
// To see why this is necessary, consider the following function:
497+
// ```
498+
// struct C {
499+
// void* data_;
500+
// void* data() const { return data; }
501+
// };
502+
// ...
503+
// C c;
504+
// memcpy(c.data(), source, 16)
505+
// ```
506+
// the data pointed to by `c.data_` is potentially modified by the call to `memcpy` even though
507+
// `C::data` has a const specifier. So we further place the restriction that the type returned
508+
// by `call` should not be of the form `const T*` (for some deeply const type `T`).
509+
if call.getStaticCallTarget() instanceof Cpp::ConstMemberFunction
510+
then
511+
exists(PointerOrArrayOrReferenceType resultType |
512+
resultType = call.getResultType() and
513+
not resultType.isDeeplyConstBelow()
514+
)
515+
else any()
516+
else
517+
// An argument is modifiable if it's a non-const pointer or reference type.
518+
isModifiableAt(type, indirectionIndex)
519+
)
520+
}
504521
}
505522

523+
import IsModifiableAtImpl
524+
506525
abstract class BaseSourceVariableInstruction extends Instruction {
507526
/** Gets the base source variable accessed by this instruction. */
508527
abstract BaseSourceVariable getBaseSourceVariable();

0 commit comments

Comments
 (0)