@@ -417,56 +417,34 @@ 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+ /**
422+ * Holds if the `indirectionIndex`'th dereference of a value of type
423+ * `cppType` is a type that can be modified (either by modifying the value
424+ * itself or one of its fields if it's a class type).
425+ *
426+ * For example, a value of type `const int* const` cannot be modified
427+ * at any indirection index (because it's a constant pointer to constant
428+ * data), and a value of type `int *const *` is modifiable at indirection index
429+ * 2 only.
430+ *
431+ * A value of type `const S2* s2` where `s2` is
432+ * ```cpp
433+ * struct S { int x; }
434+ * ```
435+ * can be modified at indirection index 1. This is to ensure that we generate
436+ * a `PostUpdateNode` for the argument corresponding to the `s2` parameter in
437+ * an example such as:
438+ * ```cpp
439+ * void set_field(const S2* s2)
440+ * {
441+ * s2->s->x = 42;
442+ * }
443+ * ```
444+ */
445+ bindingset [ cppType, indirectionIndex]
446+ pragma [ inline_late]
447+ private predicate impl ( CppType cppType , int indirectionIndex ) {
470448 exists ( Type pointerType , Type base , Type t |
471449 pointerType = t .getUnderlyingType ( ) and
472450 pointerType = any ( Indirection ind ) .getUnderlyingType ( ) and
@@ -480,29 +458,115 @@ private predicate isModifiableAtImpl(CppType cppType, int indirectionIndex) {
480458 // one of the members was modified.
481459 exists ( base .stripType ( ) .( Cpp:: Class ) .getAField ( ) )
482460 )
461+ }
462+
463+ /**
464+ * Holds if `cppType` is modifiable with an indirection index of at least 1.
465+ *
466+ * This predicate factored out into a separate predicate for two reasons:
467+ * - This predicate needs to be recursive because, if a type is modifiable
468+ * at indirection `i`, then it's also modifiable at indirection index `i+1`
469+ * (because the pointer could be completely re-assigned at indirection `i`).
470+ * - We special-case indirection index `0` so that pointer arguments that can
471+ * be modified at some index always have a `PostUpdateNode` at indiretion
472+ * index 0 even though the 0'th indirection can never be modified by a
473+ * callee.
474+ */
475+ private predicate isModifiableAtImplAtLeast1 ( CppType cppType , int indirectionIndex ) {
476+ indirectionIndex = [ 1 .. countIndirectionsForCppType ( cppType ) ] and
477+ (
478+ impl ( cppType , indirectionIndex )
479+ or
480+ // If the `indirectionIndex`'th dereference of a type can be modified
481+ // then so can the `indirectionIndex + 1`'th dereference.
482+ isModifiableAtImplAtLeast1 ( cppType , indirectionIndex - 1 )
483+ )
484+ }
485+
486+ /**
487+ * Holds if `cppType` is modifiable at indirection index 0.
488+ *
489+ * In reality, the 0'th indirection of a pointer (i.e., the pointer itself)
490+ * can never be modified by a callee, but it is sometimes useful to be able
491+ * to specify the value of the pointer, as its coming out of a function, as
492+ * a source of dataflow since the shared library's reverse-read mechanism
493+ * then ensures that field-flow is accounted for.
494+ */
495+ private predicate isModifiableAtImplAt0 ( CppType cppType ) { impl ( cppType , 0 ) }
496+
497+ /**
498+ * Holds if `t` is a pointer or reference type that supports at least
499+ * `indirectionIndex` number of indirections, and the `indirectionIndex`
500+ * indirection cannot be modfiied by passing a value of `t` to a function.
501+ */
502+ private predicate isModifiableAtImpl ( CppType cppType , int indirectionIndex ) {
503+ isModifiableAtImplAtLeast1 ( cppType , indirectionIndex )
483504 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- }
505+ indirectionIndex = 0 and
506+ isModifiableAtImplAt0 ( cppType )
507+ }
489508
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- )
509+ /**
510+ * Holds if `t` is a type with at least `indirectionIndex` number of
511+ * indirections, and the `indirectionIndex` indirection can be modified by
512+ * passing a value of type `t` to a function function.
513+ */
514+ bindingset [ indirectionIndex]
515+ predicate isModifiableAt ( CppType cppType , int indirectionIndex ) {
516+ isModifiableAtImpl ( cppType , indirectionIndex )
517+ or
518+ exists ( PointerWrapper pw , Type t |
519+ cppType .hasType ( t , _) and
520+ t .stripType ( ) = pw and
521+ not pw .pointsToConst ( )
522+ )
523+ }
524+
525+ /**
526+ * Holds if the value pointed to by `operand` can potentially be
527+ * modified be the caller.
528+ */
529+ predicate isModifiableByCall ( ArgumentOperand operand , int indirectionIndex ) {
530+ exists ( CallInstruction call , int index , CppType type |
531+ indirectionIndex = [ 0 .. countIndirectionsForCppType ( type ) ] and
532+ type = getLanguageType ( operand ) and
533+ call .getArgumentOperand ( index ) = operand and
534+ if index = - 1
535+ then
536+ // A qualifier is "modifiable" if:
537+ // 1. the member function is not const specified, or
538+ // 2. the member function is `const` specified, but returns a pointer or reference
539+ // type that is non-const.
540+ //
541+ // To see why this is necessary, consider the following function:
542+ // ```
543+ // struct C {
544+ // void* data_;
545+ // void* data() const { return data; }
546+ // };
547+ // ...
548+ // C c;
549+ // memcpy(c.data(), source, 16)
550+ // ```
551+ // the data pointed to by `c.data_` is potentially modified by the call to `memcpy` even though
552+ // `C::data` has a const specifier. So we further place the restriction that the type returned
553+ // by `call` should not be of the form `const T*` (for some deeply const type `T`).
554+ if call .getStaticCallTarget ( ) instanceof Cpp:: ConstMemberFunction
555+ then
556+ exists ( PointerOrArrayOrReferenceType resultType |
557+ resultType = call .getResultType ( ) and
558+ not resultType .isDeeplyConstBelow ( )
559+ )
560+ else any ( )
561+ else
562+ // An argument is modifiable if it's a non-const pointer or reference type.
563+ isModifiableAt ( type , indirectionIndex )
564+ )
565+ }
504566}
505567
568+ import IsModifiableAtImpl
569+
506570abstract class BaseSourceVariableInstruction extends Instruction {
507571 /** Gets the base source variable accessed by this instruction. */
508572 abstract BaseSourceVariable getBaseSourceVariable ( ) ;
0 commit comments