diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll index 2b48b36c5022..bcf7c24180f9 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/ExternalFlow.qll @@ -1142,16 +1142,14 @@ private predicate interpretForwardsModelType( * at calls to `forwarder`. */ private predicate interpretForwardsModel( - Function forwarder, Constructor constructor, int start, string output, string provenance, - string model + Function forwarder, Class c, int start, string output, string provenance, string model ) { - interpretForwardsModelType(forwarder, constructor.getDeclaringType(), start, output, provenance, - model) + interpretForwardsModelType(forwarder, c, start, output, provenance, model) } /** Holds if `forwarder` forwards its arguments starting at `start` to `constructor`. */ -predicate forwards(Function forwarder, Constructor constructor, int start) { - interpretForwardsModel(forwarder, constructor, start, _, _, _) +predicate forwards(Function forwarder, Class c, int start) { + interpretForwardsModel(forwarder, c, start, _, _, _) } private int referenceIndirection(Type unspecified) { @@ -1166,6 +1164,388 @@ private Type stripReference(Type unspecified) { result = unspecified } +/** + * Encapsulates predicates used to compute which constructor a perfect + * forwarding function targets. + */ +module ConstructorForwarding { + private import codeql.util.Boolean + + private class ConvertingConstructor extends Constructor { + Type fromType; + + ConvertingConstructor() { + not this.isFromUninstantiatedTemplate(_) and + not this.isExplicit() and + not this.isDeleted() and + not this instanceof CopyConstructor and + not this instanceof MoveConstructor and + fromType = this.getParameter(0).getUnderlyingType() and + forall(int index | index > 0 and exists(this.getParameter(index)) | + this.getParameter(index).hasInitializer() + ) + } + + Type getUnderlyingFromType() { result = fromType } + } + + private Type getForwardedArgumentType(Function forwarder, int start, int i) { + forwards(forwarder, _, start) and + i = [0 .. forwarder.getNumberOfParameters() - start - 1] and + result = forwarder.getParameter(start + i).getUnderlyingType() + } + + private Type getConstructorParameterType(Cpp::Constructor constructor, int i) { + forwards(_, constructor.getDeclaringType(), _) and + result = constructor.getParameter(i).getUnderlyingType() + } + + private newtype ValueCategory = + LValue() or + XValue() or + PRValue() + + bindingset[t1, t2] + pragma[inline_late] + private predicate preservesQualifiers(Type t1, Type t2) { + (t1.isConst() implies t2.isConst()) and + (t1.isVolatile() implies t2.isVolatile()) + } + + private predicate baseTypeCompatible(Type arg, Type param) { + param.stripTopLevelSpecifiers() = arg.stripTopLevelSpecifiers().(Class).getABaseClass+() and + preservesQualifiers(arg, param) + } + + private predicate voidType(Type t) { t.stripTopLevelSpecifiers() instanceof VoidType } + + private predicate routineType(Type t) { t.stripTopLevelSpecifiers() instanceof RoutineType } + + private predicate pointerConversion(Cpp::PointerType arg, Cpp::PointerType param) { + exists(Type argBase, Type paramBase | + argBase = arg.getBaseType() and + paramBase = param.getBaseType() + | + baseTypeCompatible(argBase, paramBase) + or + voidType(paramBase) and + not routineType(argBase) and + preservesQualifiers(argBase, paramBase) + ) + } + + private predicate nullPointerConversion(Type t) { + t instanceof Cpp::PointerType or t instanceof FunctionPointerType + } + + private predicate booleanConversion(Type t) { + t instanceof Cpp::PointerType + or + t instanceof FunctionPointerType + or + t instanceof Cpp::ArrayType + or + t instanceof RoutineType + } + + private predicate referenceAcceptsCategory(Cpp::ReferenceType t, ValueCategory category) { + if t instanceof Cpp::LValueReferenceType + then + category = LValue() + or + exists(Type base | + base = t.getBaseType() and + base.isConst() and + not base.isVolatile() + ) + else category != LValue() + } + + /** + * Tracks the ordering of standard conversions. All phases allow parameter matching + * and, if none has been used, a user-defined conversion that resets to `Initial()`. + */ + private newtype Phase = + // Allows array/function decay, a value or base-class conversion, or pointer qualification. + Initial() or + // Allows a value or base-class conversion or pointer qualification, but no further decay. + AfterTransformation() or + // Allows only pointer qualification among the remaining conversion steps. + AfterValueConversion() or + // Allows no further conversion steps. + AfterQualification() + + private predicate isUnderlyingType(Type type) { type = type.getUnderlyingType() } + + private newtype TTypeState = + MkTypeState(Type type, ValueCategory category, Boolean conversionUsed, Phase phase) { + not type instanceof Cpp::ReferenceType and + not type instanceof FunctionReferenceType and + isUnderlyingType(type) + } + + private ValueCategory getCategoryForRef(Cpp::ReferenceType reference) { + reference instanceof Cpp::LValueReferenceType and result = LValue() + or + reference instanceof Cpp::RValueReferenceType and result = XValue() + } + + private Type getValueType(Type t, ValueCategory category) { + result = t.(FunctionReferenceType).getBaseType().getUnderlyingType() and + category = LValue() + or + result = t.(Cpp::ReferenceType).getBaseType().getUnderlyingType() and + category = getCategoryForRef(t) + or + not t instanceof FunctionReferenceType and + not t instanceof Cpp::ReferenceType and + result = t and + category = PRValue() + } + + bindingset[arg, param] + pragma[inline_late] + private predicate qualificationCompatible(Type arg, Type param) { + arg.stripTopLevelSpecifiers() = param.stripTopLevelSpecifiers() and + preservesQualifiers(arg, param) + } + + private class TypeState extends TTypeState { + Type getType() { this = MkTypeState(result, _, _, _) } + + ValueCategory getCategory() { this = MkTypeState(_, result, _, _) } + + Phase getPhase() { this = MkTypeState(_, _, _, result) } + + /** Holds if this state's phase permits a value or base-class conversion. */ + predicate canConvertValue() { + this.getPhase() = Initial() or this.getPhase() = AfterTransformation() + } + + predicate hasNotUsedConversion() { this = MkTypeState(_, _, false, _) } + + string toString() { result = this.getType().toString() } + + predicate isSource(Type argType) { + argType = getForwardedArgumentType(_, _, _) and + exists(ValueCategory category | + this = MkTypeState(getValueType(argType, category), category, false, Initial()) + ) + } + + predicate matchesParameter(Type paramType) { + exists(Type base | base = paramType.(Cpp::ReferenceType).getBaseType() | + qualificationCompatible(this.getType(), base) and + referenceAcceptsCategory(paramType, this.getCategory()) + ) + or + not paramType instanceof Cpp::ReferenceType and + this.getType().stripTopLevelSpecifiers() = paramType.stripTopLevelSpecifiers() + } + + predicate isSink(Type paramType, Cpp::Constructor constructor) { + paramType = getConstructorParameterType(constructor, _) and + this.matchesParameter(paramType) + } + } + + private Cpp::PointerType pointerType(Type base) { + result.getBaseType() = base.getUnderlyingType() + } + + private predicate arrayToPointerStep(TypeState argState, TypeState paramState) { + exists(Cpp::ArrayType array, boolean conversionUsed | + argState = MkTypeState(array, _, conversionUsed, Initial()) and + paramState = + MkTypeState(pointerType(array.getBaseType()), PRValue(), conversionUsed, + AfterTransformation()) + ) + } + + private predicate functionToPointerStep(TypeState argState, TypeState paramState) { + exists(RoutineType routine, FunctionPointerType pointer, boolean conversionUsed | + argState = MkTypeState(routine, _, conversionUsed, Initial()) and + routine = pointer.getBaseType().getUnderlyingType() and + paramState = MkTypeState(pointer, PRValue(), conversionUsed, AfterTransformation()) + ) + } + + private predicate arithmeticConversion(Type t) { + t instanceof ArithmeticType + or + t instanceof Enum and not t instanceof ScopedEnum + } + + private predicate hasNoTopLevelSpecifiers(Type type) { type = type.stripTopLevelSpecifiers() } + + private predicate valueConversionStep(TypeState argState, TypeState paramState) { + exists(Type argType, Type paramType, boolean conversionUsed | + argState = MkTypeState(_, _, conversionUsed, _) and + argState.canConvertValue() and + argType = argState.getType().stripTopLevelSpecifiers() and + hasNoTopLevelSpecifiers(paramType) and + argType != paramType and + paramState = MkTypeState(paramType, PRValue(), conversionUsed, AfterValueConversion()) + | + paramType instanceof ArithmeticType and + arithmeticConversion(argType) + or + pointerConversion(argType, paramType) + or + argType instanceof NullPointerType and + nullPointerConversion(paramType) + or + booleanConversion(argType) and + paramType instanceof BoolType + ) + } + + private newtype PtrKind = + NormalPtrKind() or + FunPtrKind() + + private Type pointerBase(Type pointer, PtrKind k) { + result = pointer.(Cpp::PointerType).getBaseType() and + k = NormalPtrKind() + or + result = pointer.(FunctionPointerType).getBaseType() and + k = FunPtrKind() + } + + private predicate pointerQualificationStep(TypeState argState, TypeState paramState) { + exists(Type argType, Type paramType, boolean conversionUsed, PtrKind k | + argState = MkTypeState(_, _, conversionUsed, _) and + argState.getPhase() != AfterQualification() and + argType = argState.getType().stripTopLevelSpecifiers() and + hasNoTopLevelSpecifiers(paramType) and + argType != paramType and + qualificationCompatible(pointerBase(argType, k), pointerBase(paramType, k)) and + paramState = MkTypeState(paramType, PRValue(), conversionUsed, AfterQualification()) + ) + } + + private predicate baseClassStep(TypeState argState, TypeState paramState) { + exists(Type arg, Type param, ValueCategory category, boolean conversionUsed | + argState = MkTypeState(arg, category, conversionUsed, _) and + argState.canConvertValue() and + baseTypeCompatible(arg, param) and + paramState = MkTypeState(param, category, conversionUsed, AfterValueConversion()) + ) + } + + private predicate convertingConstructorStep(TypeState argState, TypeState paramState) { + exists(ConvertingConstructor constructor | + argState.hasNotUsedConversion() and + argState.matchesParameter(constructor.getUnderlyingFromType()) and + paramState = MkTypeState(constructor.getDeclaringType(), PRValue(), true, Initial()) + ) + } + + private predicate conversionOperatorStep(TypeState argState, TypeState paramState) { + exists(ConversionOperator conversion, ValueCategory category | + not conversion.isFromUninstantiatedTemplate(_) and + not conversion.isExplicit() and + not conversion.isDeleted() and + argState.hasNotUsedConversion() and + conversion.getSourceType() = + argState.getType().stripTopLevelSpecifiers().(Class).getABaseClass*() and + paramState = + MkTypeState(getValueType(conversion.getDestType().getUnderlyingType(), category), category, + true, Initial()) + ) + } + + private predicate step(TypeState argState, TypeState paramState) { + arrayToPointerStep(argState, paramState) + or + functionToPointerStep(argState, paramState) + or + valueConversionStep(argState, paramState) + or + pointerQualificationStep(argState, paramState) + or + baseClassStep(argState, paramState) + or + convertingConstructorStep(argState, paramState) + or + conversionOperatorStep(argState, paramState) + } + + private predicate typeFwd(TypeState state) { + state.isSource(_) + or + exists(TypeState previous | + typeFwd(previous) and + step(previous, state) + ) + } + + private predicate typeRev(TypeState state, Cpp::Constructor constructor) { + typeFwd(state) and + ( + state.isSink(_, constructor) + or + exists(TypeState next | + typeRev(next, constructor) and + step(state, next) + ) + ) + } + + private predicate prunedStep(TypeState argState, TypeState paramState) { + exists(Cpp::Constructor constructor | + typeRev(argState, constructor) and + typeRev(paramState, constructor) and + step(argState, paramState) + ) + } + + private predicate compatible(Function forwarder, int start, int i, Cpp::Constructor constructor) { + exists(TypeState argState, TypeState paramState | + argState.isSource(getForwardedArgumentType(forwarder, start, i)) and + paramState.isSink(getConstructorParameterType(constructor, i), constructor) and + prunedStep*(argState, paramState) + ) + } + + /** + * Gets the `Constructor` that should be invoked with arguments `0 .. n` + * when `forwarder` is called with arguments `start + 0 .. start + n`. + */ + Cpp::Constructor getForwardingConstructor(Function forwarder, int start) { + exists(int numberOfForwardedArguments | + numberOfForwardedArguments <= result.getNumberOfParameters() + or + result.isVarargs() + | + forwards(forwarder, result.getDeclaringType(), start) and + forwarder.getNumberOfParameters() = start + numberOfForwardedArguments and + forex(int i | i = [0 .. result.getNumberOfParameters() - 1] | + // If we are still processing the forwarded arguments then we need to + // check that the argument types match the parameter types. + // Functions that perform perfect forwarding are always written as: + // ``` + // template void emplace(Args&&... args) { ... } + // ``` + // and so all the arguments will be reference typed (lvalue or rvalued). + // However, the constructor may not specify all the arguments by + // reference. + i < numberOfForwardedArguments and + compatible(forwarder, start, i, result) + or + // If the constructor has a default argument and we have processed all + // the forwarded arguments then we don't need to check the types. + i >= numberOfForwardedArguments and result.getParameter(i).hasInitializer() + ) + ) + } + + /** Holds if `call` is a call that forwards arguments to a constructor call. */ + predicate isForwarderConstructorArgumentNodeImpl(CallInstruction call) { + exists(getForwardingConstructor(call.getStaticCallTarget(), _)) + } +} + /** * In order to support flow summaries for functions that perform "perfect * forwarding" we interpret a call such as: @@ -1189,12 +1569,14 @@ private Type stripReference(Type unspecified) { private predicate interpretForwardingSummary( Function forwarder, string input, string output, string provenance, string model ) { - exists(Constructor constructor, int start, string constructorOutput | - interpretForwardsModel(forwarder, constructor, start, constructorOutput, provenance, model) + exists(Class c, int start, string constructorOutput | + interpretForwardsModel(forwarder, c, start, constructorOutput, provenance, model) | // Generate the (1) summary - exists(int index, Parameter arg, Parameter p, int indirection | + exists(int index, Parameter arg, Parameter p, int indirection, Cpp::Constructor constructor | arg = forwarder.getParameter(start + index) and + constructor = ConstructorForwarding::getForwardingConstructor(forwarder, start) and + constructor.getDeclaringType() = c and p = constructor.getParameter(index) and indirection = [0 .. SsaImpl::getMaxIndirectionsForPRType(p.getUnspecifiedType())] and input = diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll index b493ba001559..9dbf7f8e73c5 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowNodes.qll @@ -3,6 +3,7 @@ private import semmle.code.cpp.ir.ValueNumbering private import semmle.code.cpp.ir.IR private import semmle.code.cpp.models.interfaces.DataFlow private import semmle.code.cpp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +private import semmle.code.cpp.dataflow.ExternalFlow as External private import DataFlowPrivate private import DataFlowUtil private import ModelUtil @@ -192,7 +193,7 @@ private module Cached { TSsaSynthNode(SsaImpl::SynthNode n) or TSsaIteratorNode(IteratorFlow::IteratorFlowNode n) or TForwarderConstructorArgumentNode(CallInstruction call) { - isForwarderConstructorArgumentNodeImpl(call) + External::ConstructorForwarding::isForwarderConstructorArgumentNodeImpl(call) } or TRawIndirectOperand0(Node0Impl node, int indirectionIndex) { SsaImpl::hasRawIndirectOperand(node.asOperand(), indirectionIndex) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index 7faa8bb8681c..29f1a16bf58e 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -593,52 +593,6 @@ private class SideEffectArgumentNode extends ArgumentNode, SideEffectOperandNode } } -/** - * Gets `unspecifiedType`, but with the outermost `ReferenceType` removed, if any. - */ -private Type stripReferences(Type unspecifiedType) { - result = unspecifiedType.(Cpp::ReferenceType).getBaseType().getUnspecifiedType() - or - not unspecifiedType instanceof Cpp::ReferenceType and - result = unspecifiedType -} - -predicate forwardingCallTargetsConstructor( - CallInstruction call, Cpp::Constructor constructor, int start -) { - exists(int numberOfForwardedArguments | - numberOfForwardedArguments <= constructor.getNumberOfParameters() - or - constructor.isVarargs() - | - External::forwards(call.getStaticCallTarget(), constructor, start) and - call.getNumberOfPositionalArguments() = start + numberOfForwardedArguments and - forall(int i | i = [0 .. constructor.getNumberOfParameters() - 1] | - // If we are still processing the forwarded arguments then we need to - // check that the argument types match the parameter types. - // Functions that perform perfect forwarding are always written as: - // ``` - // template void emplace(Args&&... args) { ... } - // ``` - // and so all the arguments will be reference typed (lvalue or rvalued). - // However, the constructor may not specify all the arguments by - // reference. - i < numberOfForwardedArguments and - stripReferences(call.getPositionalArgument(start + i).getResultType()) = - stripReferences(constructor.getParameter(i).getUnspecifiedType()) - or - // If the constructor has a default argument and we have processed all - // the forwarded arguments then we don't need to check the types. - i >= numberOfForwardedArguments and constructor.getParameter(i).hasInitializer() - ) - ) -} - -/** Holds if `call` is a call that forwards arguments to a constructor call. */ -predicate isForwarderConstructorArgumentNodeImpl(CallInstruction call) { - forwardingCallTargetsConstructor(call, _, _) -} - /** * In order to implement a MaD summary for a flow such as: * ``` @@ -679,7 +633,10 @@ private class ForwarderConstructorArgumentNode extends ArgumentNode, /** * Gets a constructor which may be targeted by this forwarding call. */ - Cpp::Constructor getAConstructor() { forwardingCallTargetsConstructor(call, result, _) } + Cpp::Constructor getAConstructor() { + result = + External::ConstructorForwarding::getForwardingConstructor(call.getStaticCallTarget(), _) + } override DataFlowCallable getEnclosingCallable() { result.asSourceCallable() = this.getFunction() diff --git a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected index 21aeeb32e3a1..ee0d3e830d00 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/flow.expected +++ b/cpp/ql/test/library-tests/dataflow/external-models/flow.expected @@ -682,22 +682,40 @@ edges | test.cpp:362:15:362:23 | call to ymlSource | test.cpp:362:15:362:25 | call to ymlSource | provenance | Src:MaD:48 | | test.cpp:362:15:362:25 | call to ymlSource | test.cpp:363:15:363:15 | *x | provenance | | | test.cpp:363:5:363:5 | forward output argument [s] | test.cpp:365:30:365:30 | *f [s] | provenance | | +| test.cpp:363:5:363:5 | forward output argument [ul] | test.cpp:365:30:365:30 | *f [ul] | provenance | | | test.cpp:363:15:363:15 | *x | test.cpp:341:30:341:32 | arg | provenance | | +| test.cpp:363:15:363:15 | *x | test.cpp:345:38:345:40 | arg | provenance | | | test.cpp:363:15:363:15 | *x | test.cpp:363:5:363:5 | forward output argument [s] | provenance | | +| test.cpp:363:15:363:15 | *x | test.cpp:363:5:363:5 | forward output argument [ul] | provenance | | | test.cpp:365:30:365:30 | *f [s] | test.cpp:365:32:365:34 | call to get [s] | provenance | MaD:88 | +| test.cpp:365:30:365:30 | *f [ul] | test.cpp:365:32:365:34 | call to get [ul] | provenance | MaD:88 | | test.cpp:365:32:365:34 | call to get [s] | test.cpp:365:32:365:34 | call to get [s] | provenance | | | test.cpp:365:32:365:34 | call to get [s] | test.cpp:366:13:366:13 | *c [s] | provenance | | +| test.cpp:365:32:365:34 | call to get [ul] | test.cpp:365:32:365:34 | call to get [ul] | provenance | | +| test.cpp:365:32:365:34 | call to get [ul] | test.cpp:367:13:367:13 | *c [ul] | provenance | | | test.cpp:366:13:366:13 | *c [s] | test.cpp:366:13:366:15 | s | provenance | | | test.cpp:366:13:366:13 | *c [s] | test.cpp:366:15:366:15 | s | provenance | Sink:MaD:3 | | test.cpp:366:13:366:15 | s | test.cpp:366:15:366:15 | s | provenance | Sink:MaD:3 | +| test.cpp:367:13:367:13 | *c [ul] | test.cpp:367:13:367:16 | ul | provenance | | +| test.cpp:367:13:367:13 | *c [ul] | test.cpp:367:15:367:16 | ul | provenance | Sink:MaD:3 | +| test.cpp:367:13:367:16 | ul | test.cpp:367:15:367:16 | ul | provenance | Sink:MaD:3 | | test.cpp:371:24:371:32 | call to ymlSource | test.cpp:371:24:371:34 | call to ymlSource | provenance | Src:MaD:48 | | test.cpp:371:24:371:34 | call to ymlSource | test.cpp:372:15:372:16 | *ul | provenance | | +| test.cpp:372:5:372:5 | forward output argument [s] | test.cpp:374:30:374:30 | *f [s] | provenance | | | test.cpp:372:5:372:5 | forward output argument [ul] | test.cpp:374:30:374:30 | *f [ul] | provenance | | +| test.cpp:372:15:372:16 | *ul | test.cpp:341:30:341:32 | arg | provenance | | | test.cpp:372:15:372:16 | *ul | test.cpp:345:38:345:40 | arg | provenance | | +| test.cpp:372:15:372:16 | *ul | test.cpp:372:5:372:5 | forward output argument [s] | provenance | | | test.cpp:372:15:372:16 | *ul | test.cpp:372:5:372:5 | forward output argument [ul] | provenance | | +| test.cpp:374:30:374:30 | *f [s] | test.cpp:374:32:374:34 | call to get [s] | provenance | MaD:88 | | test.cpp:374:30:374:30 | *f [ul] | test.cpp:374:32:374:34 | call to get [ul] | provenance | MaD:88 | +| test.cpp:374:32:374:34 | call to get [s] | test.cpp:374:32:374:34 | call to get [s] | provenance | | +| test.cpp:374:32:374:34 | call to get [s] | test.cpp:375:13:375:13 | *c [s] | provenance | | | test.cpp:374:32:374:34 | call to get [ul] | test.cpp:374:32:374:34 | call to get [ul] | provenance | | | test.cpp:374:32:374:34 | call to get [ul] | test.cpp:376:13:376:13 | *c [ul] | provenance | | +| test.cpp:375:13:375:13 | *c [s] | test.cpp:375:13:375:15 | s | provenance | | +| test.cpp:375:13:375:13 | *c [s] | test.cpp:375:15:375:15 | s | provenance | Sink:MaD:3 | +| test.cpp:375:13:375:15 | s | test.cpp:375:15:375:15 | s | provenance | Sink:MaD:3 | | test.cpp:376:13:376:13 | *c [ul] | test.cpp:376:13:376:16 | ul | provenance | | | test.cpp:376:13:376:13 | *c [ul] | test.cpp:376:15:376:16 | ul | provenance | Sink:MaD:3 | | test.cpp:376:13:376:16 | ul | test.cpp:376:15:376:16 | ul | provenance | Sink:MaD:3 | @@ -1550,20 +1568,34 @@ nodes | test.cpp:362:15:362:23 | call to ymlSource | semmle.label | call to ymlSource | | test.cpp:362:15:362:25 | call to ymlSource | semmle.label | call to ymlSource | | test.cpp:363:5:363:5 | forward output argument [s] | semmle.label | forward output argument [s] | +| test.cpp:363:5:363:5 | forward output argument [ul] | semmle.label | forward output argument [ul] | | test.cpp:363:15:363:15 | *x | semmle.label | *x | | test.cpp:365:30:365:30 | *f [s] | semmle.label | *f [s] | +| test.cpp:365:30:365:30 | *f [ul] | semmle.label | *f [ul] | | test.cpp:365:32:365:34 | call to get [s] | semmle.label | call to get [s] | | test.cpp:365:32:365:34 | call to get [s] | semmle.label | call to get [s] | +| test.cpp:365:32:365:34 | call to get [ul] | semmle.label | call to get [ul] | +| test.cpp:365:32:365:34 | call to get [ul] | semmle.label | call to get [ul] | | test.cpp:366:13:366:13 | *c [s] | semmle.label | *c [s] | | test.cpp:366:13:366:15 | s | semmle.label | s | | test.cpp:366:15:366:15 | s | semmle.label | s | +| test.cpp:367:13:367:13 | *c [ul] | semmle.label | *c [ul] | +| test.cpp:367:13:367:16 | ul | semmle.label | ul | +| test.cpp:367:15:367:16 | ul | semmle.label | ul | | test.cpp:371:24:371:32 | call to ymlSource | semmle.label | call to ymlSource | | test.cpp:371:24:371:34 | call to ymlSource | semmle.label | call to ymlSource | +| test.cpp:372:5:372:5 | forward output argument [s] | semmle.label | forward output argument [s] | | test.cpp:372:5:372:5 | forward output argument [ul] | semmle.label | forward output argument [ul] | | test.cpp:372:15:372:16 | *ul | semmle.label | *ul | +| test.cpp:374:30:374:30 | *f [s] | semmle.label | *f [s] | | test.cpp:374:30:374:30 | *f [ul] | semmle.label | *f [ul] | +| test.cpp:374:32:374:34 | call to get [s] | semmle.label | call to get [s] | +| test.cpp:374:32:374:34 | call to get [s] | semmle.label | call to get [s] | | test.cpp:374:32:374:34 | call to get [ul] | semmle.label | call to get [ul] | | test.cpp:374:32:374:34 | call to get [ul] | semmle.label | call to get [ul] | +| test.cpp:375:13:375:13 | *c [s] | semmle.label | *c [s] | +| test.cpp:375:13:375:15 | s | semmle.label | s | +| test.cpp:375:15:375:15 | s | semmle.label | s | | test.cpp:376:13:376:13 | *c [ul] | semmle.label | *c [ul] | | test.cpp:376:13:376:16 | ul | semmle.label | ul | | test.cpp:376:15:376:16 | ul | semmle.label | ul | @@ -1862,6 +1894,8 @@ subpaths | test.cpp:32:41:32:41 | x | test.cpp:7:47:7:52 | value2 | test.cpp:7:5:7:30 | *ymlStepGenerated_with_body | test.cpp:32:11:32:36 | call to ymlStepGenerated_with_body | | test.cpp:172:51:172:51 | x | test.cpp:164:34:164:34 | x | test.cpp:164:7:164:7 | *templateFunction3 | test.cpp:172:13:172:44 | call to templateFunction3 | | test.cpp:363:15:363:15 | *x | test.cpp:341:30:341:32 | arg | test.cpp:341:3:341:22 | *this [Return] [s] | test.cpp:363:5:363:5 | forward output argument [s] | +| test.cpp:363:15:363:15 | *x | test.cpp:345:38:345:40 | arg | test.cpp:345:3:345:22 | *this [Return] [ul] | test.cpp:363:5:363:5 | forward output argument [ul] | +| test.cpp:372:15:372:16 | *ul | test.cpp:341:30:341:32 | arg | test.cpp:341:3:341:22 | *this [Return] [s] | test.cpp:372:5:372:5 | forward output argument [s] | | test.cpp:372:15:372:16 | *ul | test.cpp:345:38:345:40 | arg | test.cpp:345:3:345:22 | *this [Return] [ul] | test.cpp:372:5:372:5 | forward output argument [ul] | | test.cpp:443:18:443:18 | *x | test.cpp:435:34:435:38 | first | test.cpp:435:3:435:28 | *this [Return] [x] | test.cpp:443:5:443:5 | emplace output argument [element, x] | | test.cpp:453:21:453:21 | *x | test.cpp:436:39:436:44 | second | test.cpp:436:3:436:28 | *this [Return] [x] | test.cpp:453:5:453:5 | emplace output argument [element, x] | diff --git a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp index ae4c4f657aba..bfc7c9ed1148 100644 --- a/cpp/ql/test/library-tests/dataflow/external-models/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/external-models/test.cpp @@ -364,7 +364,7 @@ void forward_test() { ConstructableFromInt c = f.get(); ymlSink(c.s); // $ ir - ymlSink(c.ul); // clean + ymlSink(c.ul); // $ SPURIOUS: ir } { Forwarder f; @@ -372,7 +372,7 @@ void forward_test() { f.forward(ul); ConstructableFromInt c = f.get(); - ymlSink(c.s); // clean + ymlSink(c.s); // $ SPURIOUS: ir ymlSink(c.ul); // $ ir } } diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp new file mode 100644 index 000000000000..ac225d6c997b --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.cpp @@ -0,0 +1,531 @@ +template +struct Container { + template + void emplace(Args&&... args); +}; + +struct Element { + int x; + Element(int); // element_int + Element(short); // element_short + Element(unsigned long); // element_ul + Element(const char*, int x); // element_const_char_ptr_int + Element(char*, int x); // element_char_ptr_int +}; + +struct RefElement { + RefElement(int&& x); // element_ref_int_rref + RefElement(int& x); // element_ref_int_lref + RefElement(const int& x); // element_ref_const_int_ref + RefElement(const char* const&, int x); // element_ref_const_char_ptr_const_ref_int + RefElement(const char* const volatile&, int x); // element_ref_const_char_ptr_const_volatile_ref_int + RefElement(const char*&&, int, int); // element_ref_pointer_rref + RefElement(const char*&, int, int, int); // element_ref_pointer_lref +}; + +struct ElementWithDefaultArgument { + int x; + ElementWithDefaultArgument(int x, int = 0); // element_default_int +}; + +struct ElementWithOverloadedArity { + int x; + ElementWithOverloadedArity(int first); // element_overload_arith_1 + ElementWithOverloadedArity(int, int second); // element_overload_arith_2 +}; + +struct ElementFromMutablePointer { + ElementFromMutablePointer(char*, int); // element_from_mutable_ptr +}; + +void test() { + { + Container c; + c.emplace(42); // $ targets=element_int SPURIOUS: targets=element_short targets=element_ul + } + { + Container c; + c.emplace(42); // $ targets=element_default_int + c.emplace(42, 1); // $ targets=element_default_int + } + { + Container c; + c.emplace(42); // $ targets=element_overload_arith_1 + c.emplace(42, 1); // $ targets=element_overload_arith_2 + } + { + Container c; + c.emplace("abc", 42); // $ targets=element_const_char_ptr_int + c.emplace((char*)nullptr, 42); // $ targets=element_char_ptr_int SPURIOUS: targets=element_const_char_ptr_int + } + { + Container c; + Container cr; + { + const int x = 42; + c.emplace(x); // $ targets=element_int SPURIOUS: targets=element_short targets=element_ul + cr.emplace(x); // $ targets=element_ref_const_int_ref + } + { + int x = 42; + c.emplace(x); // $ targets=element_int SPURIOUS: targets=element_short targets=element_ul + cr.emplace(x); // $ targets=element_ref_int_lref SPURIOUS: targets=element_ref_const_int_ref + } + c.emplace(42); // $ targets=element_int SPURIOUS: targets=element_short targets=element_ul + cr.emplace(42); // $ targets=element_ref_int_rref SPURIOUS: targets=element_ref_const_int_ref + cr.emplace("abc", 42); // $ targets=element_ref_const_char_ptr_const_ref_int + const char buffer[] = "abc"; + cr.emplace(buffer, 42); // $ targets=element_ref_const_char_ptr_const_ref_int + } + { + Container container; + short shortValue = 42; + unsigned long longValue = 42; + container.emplace(shortValue); // $ targets=element_short SPURIOUS: targets=element_int targets=element_ul + container.emplace(longValue); // $ targets=element_ul SPURIOUS: targets=element_int targets=element_short + } +} + + +void test_invalid_pointer_conversion() { + Container container; + container.emplace("abc", 42); // no targets +} + +void test_volatile_reference_binding() { + Container container; + const char* volatile pointer = "abc"; + container.emplace(pointer, 42); // $ targets=element_ref_const_char_ptr_const_volatile_ref_int + container.emplace("abc", 42); // $ targets=element_ref_const_char_ptr_const_ref_int +} + +struct ImplicitConversion { + ImplicitConversion(int = 0, int = 0); + ImplicitConversion(const char* const&); +}; + +struct ExplicitConversion { + explicit ExplicitConversion(int); +}; + +struct DeletedConversion { + DeletedConversion(int) = delete; +}; + +struct RequiredArgumentConversion { + RequiredArgumentConversion(int, int); +}; + +struct LvalueConversion { + LvalueConversion(int&); +}; + +struct RvalueConversion { + RvalueConversion(int&&); +}; + +struct ChainedConversion { + ChainedConversion(ImplicitConversion); +}; + +struct ElementFromImplicitConversion { + explicit ElementFromImplicitConversion(ImplicitConversion); // element_conversion_value + ElementFromImplicitConversion(const ImplicitConversion&, int); // element_conversion_const_ref + ElementFromImplicitConversion(ImplicitConversion&&, int, int); // element_conversion_rref + ElementFromImplicitConversion(ImplicitConversion&, int, int, int); // element_conversion_lref + ElementFromImplicitConversion(const volatile ImplicitConversion&, int, int, int, int); // element_conversion_const_volatile_ref +}; + +struct ElementFromUnavailableConversion { + ElementFromUnavailableConversion(ExplicitConversion); // element_explicit_conversion + ElementFromUnavailableConversion(DeletedConversion, int); // element_deleted_conversion + ElementFromUnavailableConversion(RequiredArgumentConversion, int, int); // element_required_argument_conversion +}; + +struct ElementFromReferenceConversion { + ElementFromReferenceConversion(LvalueConversion); // element_lvalue_conversion + ElementFromReferenceConversion(RvalueConversion, int); // element_rvalue_conversion +}; + +struct ElementFromChainedConversion { + ElementFromChainedConversion(ChainedConversion); // element_chained_conversion + ElementFromChainedConversion(ImplicitConversion, ImplicitConversion); // element_two_conversions +}; + +void test_implicit_conversion() { + Container container; + container.emplace(42); // $ targets=element_conversion_value + container.emplace("abc"); // $ targets=element_conversion_value + container.emplace(42, 0); // $ targets=element_conversion_const_ref + container.emplace(42, 0, 0); // $ targets=element_conversion_rref + container.emplace(42, 0, 0, 0); // no targets + container.emplace(42, 0, 0, 0, 0); // no targets +} + +void test_unavailable_implicit_conversion() { + Container container; + container.emplace(42); // no targets + container.emplace(42, 0); // no targets + container.emplace(42, 0, 0); // no targets +} + +void test_implicit_conversion_input_references() { + Container container; + int value = 42; + const int constValue = 42; + volatile int volatileValue = 42; + container.emplace(value); // $ targets=element_lvalue_conversion + container.emplace(42); // no targets + container.emplace(constValue); // no targets + container.emplace(volatileValue); // no targets + container.emplace(42, 0); // $ targets=element_rvalue_conversion + container.emplace(value, 0); // no targets + container.emplace(static_cast(constValue), 0); // no targets + container.emplace(static_cast(volatileValue), 0); // no targets +} + +void test_implicit_conversion_limit() { + Container container; + container.emplace(42); // no targets + ImplicitConversion converted(42); + container.emplace(converted); // $ targets=element_chained_conversion + container.emplace(42, 42); // $ targets=element_two_conversions + + Container references; + references.emplace(converted, 0, 0); // no target + const ImplicitConversion constConverted(42); + references.emplace(constConverted, 0, 0, 0); // no target +} + +struct ValueConversionOperator { + operator int(); +}; + +struct LvalueConversionOperator { + operator int&() &; +}; + +struct RvalueConversionOperator { + operator int&&() &&; +}; + +struct ConstConversionOperator { + operator const int&() const &; +}; + +struct VolatileConversionOperator { + operator const volatile int&() const volatile; +}; + +struct ExplicitConversionOperator { + explicit operator int(); +}; + +struct DeletedConversionOperator { + operator int() = delete; +}; + +struct ChainedConversionOperator { + operator ValueConversionOperator&(); +}; + +struct ArrayConversionOperator { + using Buffer = const char[4]; + operator Buffer&() const; +}; + +struct ElementFromConversionOperator { + explicit ElementFromConversionOperator(int); // element_operator_value + ElementFromConversionOperator(int&, int); // element_operator_lref + ElementFromConversionOperator(const int&, int, int); // element_operator_const_ref + ElementFromConversionOperator(int&&, int, int, int); // element_operator_rref + ElementFromConversionOperator(const volatile int&, int, int, int, int); // element_operator_const_volatile_ref +}; + +void test_value_conversion_operator() { + Container container; + ValueConversionOperator value; + const ValueConversionOperator constValue; + volatile ValueConversionOperator volatileValue; + container.emplace(value); // $ targets=element_operator_value + container.emplace(ValueConversionOperator()); // $ targets=element_operator_value + container.emplace(constValue); // $ SPURIOUS: targets=element_operator_value + container.emplace(volatileValue); // $ SPURIOUS: targets=element_operator_value + container.emplace(value, 0); // no targets + container.emplace(value, 0, 0); // $ targets=element_operator_const_ref + container.emplace(value, 0, 0, 0); // $ targets=element_operator_rref + container.emplace(value, 0, 0, 0, 0); // no targets +} + +void test_lvalue_conversion_operator() { + Container container; + LvalueConversionOperator value; + const LvalueConversionOperator constValue; + volatile LvalueConversionOperator volatileValue; + container.emplace(value); // $ targets=element_operator_value + container.emplace(value, 0); // $ targets=element_operator_lref + container.emplace(value, 0, 0); // $ targets=element_operator_const_ref + container.emplace(value, 0, 0, 0); // no targets + container.emplace(value, 0, 0, 0, 0); // $ targets=element_operator_const_volatile_ref + container.emplace(value, ValueConversionOperator()); // $ targets=element_operator_lref + container.emplace(LvalueConversionOperator()); // $ SPURIOUS: targets=element_operator_value + container.emplace(constValue); // $ SPURIOUS: targets=element_operator_value + container.emplace(volatileValue); // $ SPURIOUS: targets=element_operator_value +} + +void test_rvalue_conversion_operator() { + Container container; + RvalueConversionOperator value; + const RvalueConversionOperator constValue; + container.emplace(value); // $ SPURIOUS: targets=element_operator_value + container.emplace(static_cast(constValue)); // $ SPURIOUS: targets=element_operator_value + container.emplace(RvalueConversionOperator()); // $ targets=element_operator_value + container.emplace(RvalueConversionOperator(), 0); // no targets + container.emplace(RvalueConversionOperator(), 0, 0); // $ targets=element_operator_const_ref + container.emplace(RvalueConversionOperator(), 0, 0, 0); // $ targets=element_operator_rref + container.emplace(RvalueConversionOperator(), 0, 0, 0, 0); // no targets +} + +void test_conversion_operator_qualification() { + Container container; + const ConstConversionOperator constValue; + volatile ConstConversionOperator volatileValue; + container.emplace(constValue, 0, 0); // $ targets=element_operator_const_ref + container.emplace(ConstConversionOperator(), 0, 0); // $ targets=element_operator_const_ref + container.emplace(constValue, 0); // no targets + container.emplace(constValue, 0, 0, 0); // no targets + container.emplace(volatileValue); // $ targets=element_operator_value + + const volatile VolatileConversionOperator constVolatileValue; + container.emplace(constVolatileValue, 0, 0, 0, 0); // $ targets=element_operator_const_volatile_ref + container.emplace(constVolatileValue, 0, 0); // no targets + container.emplace(constVolatileValue, 0); // no targets +} + +void test_conversion_operator_limit() { + Container container; + container.emplace(ExplicitConversionOperator()); // no targets + container.emplace(DeletedConversionOperator()); // no targets + container.emplace(ChainedConversionOperator()); // no targets + + Container converted; + converted.emplace(ValueConversionOperator()); // no targets +} + +void test_array_conversion_operator() { + Container container; + container.emplace(ArrayConversionOperator(), 42); // $ targets=element_ref_const_char_ptr_const_ref_int + container.emplace(ArrayConversionOperator(), 0, 0); // $ SPURIOUS: targets=element_ref_pointer_rref + container.emplace(ArrayConversionOperator(), 0, 0, 0); // no targets +} + +void test_pointer_value_categories() { + Container container; + const char buffer[] = "abc"; + const char* pointer = buffer; + const char* const constPointer = buffer; + container.emplace(buffer, 0, 0); // $ targets=element_ref_pointer_rref + container.emplace(pointer, 0, 0); // no target + container.emplace(static_cast(pointer), 0, 0); // $ targets=element_ref_pointer_rref + container.emplace(static_cast(constPointer), 0, 0); // no target + container.emplace(buffer, 0, 0, 0); // no targets + container.emplace(pointer, 0, 0, 0); // $ targets=element_ref_pointer_lref + container.emplace(constPointer, 0, 0, 0); // no target + container.emplace(static_cast(pointer), 0, 0, 0); // no target +} + +enum ArithmeticEnum { arithmeticValue = 42 }; +enum class ScopedArithmeticEnum { value = 42 }; + +struct ElementFromArithmetic { + ElementFromArithmetic(long); // element_arithmetic_long + ElementFromArithmetic(double, int); // element_arithmetic_double + ElementFromArithmetic(bool, int, int); // element_arithmetic_bool + ElementFromArithmetic(const long&, int, int, int); // element_arithmetic_const_ref + ElementFromArithmetic(long&&, int, int, int, int); // element_arithmetic_rref +}; + +void test_arithmetic_conversions() { + Container container; + short value = 42; + long longValue = 42; + container.emplace(value); // $ targets=element_arithmetic_long + container.emplace(1.5f, 0); // $ targets=element_arithmetic_double + container.emplace(arithmeticValue); // $ targets=element_arithmetic_long + container.emplace(42, 0, 0); // $ targets=element_arithmetic_bool + container.emplace(value, 0, 0, 0); // $ targets=element_arithmetic_const_ref + container.emplace(value, 0, 0, 0, 0); // $ targets=element_arithmetic_rref + container.emplace(longValue, 0, 0, 0, 0); // no target + container.emplace(ScopedArithmeticEnum::value); // no targets +} + +void test_standard_and_user_defined_conversions() { + short value = 42; + Container before; + before.emplace(value); // $ targets=element_conversion_value + Container after; + after.emplace(ValueConversionOperator()); // $ targets=element_arithmetic_long + after.emplace(ValueConversionOperator(), 0, 0, 0); // $ targets=element_arithmetic_const_ref +} + +struct ElementFromQualifiedPointer { + ElementFromQualifiedPointer(const char*); // element_qualified_pointer + ElementFromQualifiedPointer(const char* const&, int); // element_qualified_pointer_const_ref + ElementFromQualifiedPointer(const char*&&, int, int); // element_qualified_pointer_rref + ElementFromQualifiedPointer(const char*&, int, int, int); // element_qualified_pointer_lref + ElementFromQualifiedPointer(const char**, int, int, int, int); // element_qualified_double_pointer + ElementFromQualifiedPointer(const char* const*, int, int, int, int, int); // element_qualified_const_double_pointer +}; + +void test_pointer_qualification_conversions() { + Container container; + char buffer[] = "abc"; + char* pointer = buffer; + char** doublePointer = &pointer; + const char* constPointer = buffer; + container.emplace(buffer); // $ targets=element_qualified_pointer + container.emplace(pointer); // $ targets=element_qualified_pointer + container.emplace(pointer, 0); // $ targets=element_qualified_pointer_const_ref + container.emplace(buffer, 0, 0); // $ targets=element_qualified_pointer_rref + container.emplace(static_cast(pointer), 0, 0); // $ targets=element_qualified_pointer_rref + container.emplace(pointer, 0, 0, 0); // no target + container.emplace(doublePointer, 0, 0, 0, 0); // no target + container.emplace(doublePointer, 0, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_const_double_pointer + Container mutableContainer; + mutableContainer.emplace(constPointer, 42); // no target +} + +struct ConversionBase { + operator int() const; +}; + +struct ConversionDerived : ConversionBase {}; +struct ConversionFurtherDerived : ConversionDerived {}; + +struct ElementFromBase { + ElementFromBase(ConversionBase); // element_base_value + ElementFromBase(ConversionBase&, int); // element_base_lref + ElementFromBase(const ConversionBase&, int, int); // element_base_const_ref + ElementFromBase(ConversionBase&&, int, int, int); // element_base_rref + ElementFromBase(ConversionBase*, int, int, int, int); // element_base_pointer + ElementFromBase(const ConversionBase*, int, int, int, int, int); // element_const_base_pointer +}; + +struct ElementFromDerived { + ElementFromDerived(ConversionDerived&); // element_derived_lref + ElementFromDerived(ConversionDerived*, int); // element_derived_pointer +}; + +void test_inheritance_conversions() { + ConversionFurtherDerived derived; + const ConversionDerived constDerived{}; + ConversionBase base; + Container container; + container.emplace(derived); // $ targets=element_base_value + container.emplace(derived, 0); // $ targets=element_base_lref + container.emplace(constDerived, 0, 0); // $ targets=element_base_const_ref + container.emplace(ConversionDerived(), 0, 0, 0); // $ targets=element_base_rref + container.emplace(derived, 0, 0, 0); // no targets + container.emplace(constDerived, 0); // no targets + container.emplace(&derived, 0, 0, 0, 0); // $ targets=element_base_pointer + container.emplace(&constDerived, 0, 0, 0, 0); // no targets + container.emplace(&constDerived, 0, 0, 0, 0, 0); // $ targets=element_const_base_pointer + Container downcast; + downcast.emplace(base); // no targets + downcast.emplace(&base, 0); // no targets + Container inherited; + inherited.emplace(derived); // $ targets=element_operator_value +} + +struct ElementFromStandardPointer { + ElementFromStandardPointer(void*); // element_void_pointer + ElementFromStandardPointer(const void*, int); // element_const_void_pointer + ElementFromStandardPointer(int*, int, int); // element_int_pointer + ElementFromStandardPointer(bool, int, int, int, int); // element_pointer_bool +}; + +void test_standard_pointer_conversions() { + Container container; + int value = 42; + const int constValue = 42; + container.emplace(&value); // $ targets=element_void_pointer + container.emplace(&constValue); // no targets + container.emplace(&constValue, 0); // $ targets=element_const_void_pointer + container.emplace(nullptr); // $ targets=element_void_pointer + container.emplace(nullptr, 0, 0); // $ targets=element_int_pointer + container.emplace(0, 0, 0); // no targets + container.emplace(&value, 0, 0, 0, 0); // $ targets=element_pointer_bool + container.emplace(nullptr, 0, 0, 0, 0); // no targets + void* opaque = &value; + container.emplace(opaque, 0, 0); // no targets +} + +using Callback = int (*)(int); +using NothrowCallback = int (*)(int) noexcept; +int callback(int); +int nothrowCallback(int) noexcept; +double differentCallback(int); + +struct ElementFromCallback { + ElementFromCallback(Callback); // element_callback + ElementFromCallback(NothrowCallback, int); // element_nothrow_callback + ElementFromCallback(const Callback&, int, int); // element_callback_const_ref +}; + +void test_function_conversions() { + Container container; + Callback pointer = callback; + NothrowCallback nothrowPointer = nothrowCallback; + container.emplace(callback); // $ targets=element_callback + container.emplace(nothrowCallback); // $ targets=element_callback + container.emplace(nothrowPointer); // $ targets=element_callback + container.emplace(nothrowCallback, 0); // $ targets=element_nothrow_callback + container.emplace(pointer, 0); // $ SPURIOUS: targets=element_nothrow_callback + container.emplace(callback, 0); // $ SPURIOUS: targets=element_nothrow_callback + container.emplace(callback, 0, 0); // $ targets=element_callback_const_ref + container.emplace(nullptr); // $ targets=element_callback + container.emplace(differentCallback); // no targets + Container boolean; + boolean.emplace(callback, 0, 0, 0, 0); // $ targets=element_pointer_bool + boolean.emplace(callback); // no targets +} + +void test_standard_conversion_phases() { + Container arithmetic; + long value = 42; + arithmetic.emplace(value, 0, 0, 0, 0); // no targets + arithmetic.emplace(&value); // no targets + arithmetic.emplace(nullptr, 0, 0); // no targets + arithmetic.emplace(ValueConversionOperator(), 0, 0, 0, 0); // $ targets=element_arithmetic_rref + + Container converted; + char buffer[] = "abc"; + converted.emplace(buffer); // $ targets=element_conversion_value + converted.emplace(&value); // no targets + converted.emplace(ScopedArithmeticEnum::value); // no targets +} + +void test_pointer_aliases() { + using Character = const char; + using Pointer = Character*; + using NestedPointer = Pointer*; + Pointer pointer = "abc"; + NestedPointer nested = &pointer; + Pointer const constPointer = pointer; + Pointer volatile volatilePointer = pointer; + Container container; + container.emplace(pointer); // $ targets=element_qualified_pointer + container.emplace(nested, 0, 0, 0, 0); // $ targets=element_qualified_double_pointer + container.emplace(&constPointer, 0, 0, 0, 0, 0); // $ targets=element_qualified_const_double_pointer + container.emplace(&constPointer, 0, 0, 0, 0); // no targets + container.emplace(&volatilePointer, 0, 0, 0, 0); // no targets + container.emplace(&volatilePointer, 0, 0, 0, 0, 0); // $ MISSING: targets=element_qualified_const_double_pointer + + using CallbackAlias = Callback; + CallbackAlias callbackPointer = callback; + Container callbacks; + callbacks.emplace(callbackPointer); // $ targets=element_callback + callbacks.emplace(callbackPointer, 0, 0); // $ targets=element_callback_const_ref + Container pointers; + pointers.emplace(callbackPointer); // no targets +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.expected b/cpp/ql/test/library-tests/dataflow/forwarding/test.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.ext.yml b/cpp/ql/test/library-tests/dataflow/forwarding/test.ext.yml new file mode 100644 index 000000000000..44ba9a609803 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.ext.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/cpp-all + extensible: forwardsModel + data: # namespace, type, subtypes, name, signature, ext, start, constructor, output, provenance + - ["", "Container", True, "emplace", "(Args &&)", "", "0", "T", "Argument[-1].Element", "manual"] diff --git a/cpp/ql/test/library-tests/dataflow/forwarding/test.ql b/cpp/ql/test/library-tests/dataflow/forwarding/test.ql new file mode 100644 index 000000000000..e625769b0224 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/forwarding/test.ql @@ -0,0 +1,31 @@ +import cpp +import utils.test.InlineExpectationsTest +import semmle.code.cpp.dataflow.ExternalFlow +import semmle.code.cpp.ir.IR + +bindingset[s] +string quote(string s) { if s.matches("% %") then result = "\"" + s + "\"" else result = s } + +string getConstructorId(Constructor constructor) { + exists(CppStyleComment comment, string filepath, int startline, int endline | + comment.getLocation().hasLocationInfo(filepath, startline, _, endline, _) and + constructor.getLocation().hasLocationInfo(filepath, startline, _, endline, _) and + result = comment.getContents().suffix(2).trim() + ) +} + +module AsDefinitionTest implements TestSig { + string getARelevantTag() { result = "targets" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(CallInstruction call, Constructor constructor | + constructor = ConstructorForwarding::getForwardingConstructor(call.getStaticCallTarget(), _) and + element = call.toString() and + tag = "targets" and + value = quote(getConstructorId(constructor)) and + location = call.getLocation() + ) + } +} + +import MakeTest