Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust: Query for access to a dangling pointer #18300

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
11 changes: 11 additions & 0 deletions rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ module Impl {
)
}

/** Gets the block that encloses this node, if any. */
cached
BlockExpr getEnclosingBlock() {
exists(AstNode p | p = this.getParentNode() |
result = p
or
not p instanceof BlockExpr and
result = p.getEnclosingBlock()
)
}

/** Holds if this node is inside a macro expansion. */
predicate isInMacroExpansion() {
this = any(MacroCall mc).getExpanded()
Expand Down
4 changes: 4 additions & 0 deletions rust/ql/lib/codeql/rust/elements/internal/VariableImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ module Impl {
/** Gets an access to this variable. */
VariableAccess getAnAccess() { result.getVariable() = this }

/** Gets the block that encloses this variable, if any. */
cached
BlockExpr getEnclosingBlock() { result = definingNode.getEnclosingBlock() }

/** Gets the `self` parameter that declares this variable, if one exists. */
SelfParam getSelfParam() { variableDecl(definingNode, result, name) }

Expand Down
67 changes: 67 additions & 0 deletions rust/ql/lib/codeql/rust/security/DanglingPointerExtensions.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Provides classes and predicates for reasoning about dangling pointer
* accesses.
*/

import rust
private import codeql.rust.dataflow.DataFlow

/**
* Holds if `createsPointer` creates a pointer or reference pointing at `targetValue`.
*/
predicate createsPointer(DataFlow::Node createsPointer, DataFlow::Node targetValue) {
exists(RefExpr re |
re = createsPointer.asExpr().getExpr() and
re.getExpr() = targetValue.asExpr().getExpr()
)
}

/**
* Holds if `derefPointer` dereferences a pointer.
*/
predicate dereferencesPointer(DataFlow::Node derefPointer) {
exists(PrefixExpr pe |
pe.getOperatorName() = "*" and
pe.getExpr() = derefPointer.asExpr().getExpr()
)
}

/**
* A taint configuration for a pointer or reference that is created and later
* dereferenced.
*/
module PointerDereferenceConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { createsPointer(node, _) }

predicate isSink(DataFlow::Node node) { dereferencesPointer(node) }
}

/**
* Holds if `value` accesses a variable with scope `scope`.
*/
predicate valueScope(Expr value, BlockExpr scope) {
// variable access
scope = value.(VariableAccess).getVariable().getEnclosingBlock()
or
// field access
valueScope(value.(FieldExpr).getExpr(), scope)
}

/**
* Holds if block `a` contains block `b`, in the sense that a variable in
* `a` may be on the stack during execution of `b`. This is interprocedural,
* but is an overapproximation because we don't account for the actual call
* context that got us to `b` (for example if `f` and `g` both call `b`, then
* then depending on the caller a variable in `f` may or may-not be on the
* stack during `b`).
*/
predicate maybeOnStack(BlockExpr a, BlockExpr b) {
// `b` is a child of `a`
a = b.getEnclosingBlock*()
or
// propagage through function calls
exists(CallExprBase ce |
maybeOnStack(a, ce.getEnclosingBlock()) and
ce.getStaticTarget() = b.getEnclosingCallable()
)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An entirely different approach is possible, where having done the data flow we would check the control flow as well, all the way from the pointer creation to dereference, to see if it crosses a point where the objects lifetime ends. I ran into problems even defining these points, wasn't sure how best to implement that kind of flow effectively, and my attempts didn't get good results so I changed tactics. Nevertheless it could be more accurate in the sense of finding more results, and some of these issues might get easier as our libraries mature.

38 changes: 38 additions & 0 deletions rust/ql/src/queries/security/CWE-825/DanglingPointerAccess.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @name Access to dangling pointer.
* @description Accessing a pointer after the lifetime of it's target has ended is undefined behavior.
* @kind path-problem
* @problem.severity error
* @security-severity 9.8
* @precision high
* @id rust/dangling-ptr
* @tags security
* reliability
* correctness
* external/cwe/cwe-825
*/

import rust
import codeql.rust.dataflow.DataFlow
import codeql.rust.dataflow.TaintTracking
import codeql.rust.security.DanglingPointerExtensions

module PointerDereferenceFlow = TaintTracking::Global<PointerDereferenceConfig>;

import PointerDereferenceFlow::PathGraph

from
PointerDereferenceFlow::PathNode sourceNode, PointerDereferenceFlow::PathNode sinkNode,
DataFlow::Node targetValue, BlockExpr valueScope, BlockExpr accessScope
where
// flow from a pointer or reference to the dereference
PointerDereferenceFlow::flowPath(sourceNode, sinkNode) and
createsPointer(sourceNode.getNode(), targetValue) and
// check that the dereference is outside the lifetime of the target; in
// practice this is only possible for a pointer, and the dereference has to
// be in unsafe code, though we don't explicitly check for either.
valueScope(targetValue.asExpr().getExpr(), valueScope) and
accessScope = sinkNode.getNode().asExpr().getExpr().getEnclosingBlock() and
not maybeOnStack(valueScope, accessScope)
select sinkNode.getNode(), sourceNode, sinkNode,
"Access of a pointer to $@ after it's lifetime has ended.", targetValue, targetValue.toString()
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#select
| main.rs:77:13:77:14 | p1 | main.rs:29:9:29:18 | &my_local1 | main.rs:77:13:77:14 | p1 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:29:10:29:18 | my_local1 | my_local1 |
| main.rs:78:13:78:14 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:78:13:78:14 | p2 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:35:14:35:22 | my_local2 | my_local2 |
| main.rs:79:13:79:14 | p3 | main.rs:41:9:41:28 | &raw const my_local3 | main.rs:79:13:79:14 | p3 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:41:20:41:28 | my_local3 | my_local3 |
| main.rs:80:13:80:14 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:80:13:80:14 | p4 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:47:18:47:26 | my_local4 | my_local4 |
| main.rs:82:13:82:14 | p6 | main.rs:58:9:58:18 | &... | main.rs:82:13:82:14 | p6 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:58:10:58:18 | val.value | val.value |
| main.rs:83:13:83:14 | p7 | main.rs:71:8:71:27 | &raw const my_local7 | main.rs:83:13:83:14 | p7 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:71:19:71:27 | my_local7 | my_local7 |
| main.rs:84:4:84:5 | p2 | main.rs:35:9:35:22 | &mut my_local2 | main.rs:84:4:84:5 | p2 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:35:14:35:22 | my_local2 | my_local2 |
| main.rs:85:4:85:5 | p4 | main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:85:4:85:5 | p4 | Access of a pointer to $@ after it's lifetime has ended. | main.rs:47:18:47:26 | my_local4 | my_local4 |
| main.rs:200:13:200:15 | ptr | main.rs:215:12:215:22 | &my_local40 | main.rs:200:13:200:15 | ptr | Access of a pointer to $@ after it's lifetime has ended. | main.rs:215:13:215:22 | my_local40 | my_local40 |
| main.rs:613:13:613:18 | result | main.rs:598:26:598:27 | &x | main.rs:613:13:613:18 | result | Access of a pointer to $@ after it's lifetime has ended. | main.rs:598:27:598:27 | x | x |
edges
| main.rs:29:2:29:18 | return ... | main.rs:62:11:62:30 | get_local_dangling(...) | provenance | |
| main.rs:29:9:29:18 | &my_local1 | main.rs:29:2:29:18 | return ... | provenance | |
| main.rs:35:2:35:22 | return ... | main.rs:63:11:63:34 | get_local_dangling_mut(...) | provenance | |
| main.rs:35:9:35:22 | &mut my_local2 | main.rs:35:2:35:22 | return ... | provenance | |
| main.rs:41:2:41:28 | return ... | main.rs:64:11:64:40 | get_local_dangling_raw_const(...) | provenance | |
| main.rs:41:9:41:28 | &raw const my_local3 | main.rs:41:2:41:28 | return ... | provenance | |
| main.rs:47:2:47:26 | return ... | main.rs:65:11:65:38 | get_local_dangling_raw_mut(...) | provenance | |
| main.rs:47:9:47:26 | &raw mut my_local4 | main.rs:47:2:47:26 | return ... | provenance | |
| main.rs:51:2:51:15 | return ... | main.rs:66:11:66:31 | get_param_dangling(...) | provenance | |
| main.rs:51:9:51:15 | &param5 | main.rs:51:2:51:15 | return ... | provenance | |
| main.rs:58:2:58:18 | return ... | main.rs:67:11:67:36 | get_local_field_dangling(...) | provenance | |
| main.rs:58:9:58:18 | &... | main.rs:58:2:58:18 | return ... | provenance | |
| main.rs:62:11:62:30 | get_local_dangling(...) | main.rs:77:13:77:14 | p1 | provenance | |
| main.rs:63:11:63:34 | get_local_dangling_mut(...) | main.rs:78:13:78:14 | p2 | provenance | |
| main.rs:63:11:63:34 | get_local_dangling_mut(...) | main.rs:84:4:84:5 | p2 | provenance | |
| main.rs:64:11:64:40 | get_local_dangling_raw_const(...) | main.rs:79:13:79:14 | p3 | provenance | |
| main.rs:65:11:65:38 | get_local_dangling_raw_mut(...) | main.rs:80:13:80:14 | p4 | provenance | |
| main.rs:65:11:65:38 | get_local_dangling_raw_mut(...) | main.rs:85:4:85:5 | p4 | provenance | |
| main.rs:66:11:66:31 | get_param_dangling(...) | main.rs:81:13:81:14 | p5 | provenance | |
| main.rs:67:11:67:36 | get_local_field_dangling(...) | main.rs:82:13:82:14 | p6 | provenance | |
| main.rs:71:8:71:27 | &raw const my_local7 | main.rs:83:13:83:14 | p7 | provenance | |
| main.rs:99:17:99:30 | ...: ... | main.rs:107:13:107:14 | p1 | provenance | |
| main.rs:99:33:99:44 | ...: ... | main.rs:108:13:108:14 | p2 | provenance | |
| main.rs:99:33:99:44 | ...: ... | main.rs:110:4:110:5 | p2 | provenance | |
| main.rs:102:7:102:17 | &my_local10 | main.rs:109:13:109:14 | p3 | provenance | |
| main.rs:121:15:121:25 | &my_local11 | main.rs:99:17:99:30 | ...: ... | provenance | |
| main.rs:121:28:121:46 | &mut my_local_mut12 | main.rs:99:33:99:44 | ...: ... | provenance | |
| main.rs:129:2:129:24 | return ... | main.rs:141:11:141:21 | get_const(...) | provenance | |
| main.rs:129:9:129:24 | &MY_GLOBAL_CONST | main.rs:129:2:129:24 | return ... | provenance | |
| main.rs:136:3:136:30 | return ... | main.rs:142:11:142:26 | get_static_mut(...) | provenance | |
| main.rs:136:10:136:30 | &mut MY_GLOBAL_STATIC | main.rs:136:3:136:30 | return ... | provenance | |
| main.rs:141:11:141:21 | get_const(...) | main.rs:147:13:147:14 | p1 | provenance | |
| main.rs:142:11:142:26 | get_static_mut(...) | main.rs:148:13:148:14 | p2 | provenance | |
| main.rs:142:11:142:26 | get_static_mut(...) | main.rs:149:4:149:5 | p2 | provenance | |
| main.rs:161:23:161:32 | &... | main.rs:178:13:178:14 | p1 | provenance | |
| main.rs:164:23:164:32 | &... | main.rs:179:13:179:14 | p2 | provenance | |
| main.rs:169:23:169:32 | &... | main.rs:180:13:180:14 | p3 | provenance | |
| main.rs:189:17:189:31 | ...: ... | main.rs:192:13:192:15 | ptr | provenance | |
| main.rs:197:17:197:31 | ...: ... | main.rs:200:13:200:15 | ptr | provenance | |
| main.rs:205:17:205:31 | ...: ... | main.rs:208:13:208:15 | ptr | provenance | |
| main.rs:215:12:215:22 | &my_local40 | main.rs:217:15:217:17 | ptr | provenance | |
| main.rs:215:12:215:22 | &my_local40 | main.rs:218:15:218:17 | ptr | provenance | |
| main.rs:215:12:215:22 | &my_local40 | main.rs:220:2:220:11 | return ptr | provenance | |
| main.rs:217:15:217:17 | ptr | main.rs:189:17:189:31 | ...: ... | provenance | |
| main.rs:218:15:218:17 | ptr | main.rs:205:17:205:31 | ...: ... | provenance | |
| main.rs:220:2:220:11 | return ptr | main.rs:224:12:224:36 | access_and_get_dangling(...) | provenance | |
| main.rs:224:12:224:36 | access_and_get_dangling(...) | main.rs:228:15:228:17 | ptr | provenance | |
| main.rs:224:12:224:36 | access_and_get_dangling(...) | main.rs:229:15:229:17 | ptr | provenance | |
| main.rs:228:15:228:17 | ptr | main.rs:197:17:197:31 | ...: ... | provenance | |
| main.rs:229:15:229:17 | ptr | main.rs:205:17:205:31 | ...: ... | provenance | |
| main.rs:234:19:234:36 | ...: ... | main.rs:244:16:244:21 | ptr_up | provenance | |
| main.rs:236:17:236:29 | &my_local_rec | main.rs:239:33:239:40 | ptr_ours | provenance | |
| main.rs:236:17:236:29 | &my_local_rec | main.rs:245:18:245:25 | ptr_ours | provenance | |
| main.rs:236:17:236:29 | &my_local_rec | main.rs:253:2:253:16 | return ptr_ours | provenance | |
| main.rs:239:18:239:52 | access_ptr_rec(...) | main.rs:246:18:246:25 | ptr_down | provenance | |
| main.rs:239:33:239:40 | ptr_ours | main.rs:234:19:234:36 | ...: ... | provenance | |
| main.rs:253:2:253:16 | return ptr_ours | main.rs:239:18:239:52 | access_ptr_rec(...) | provenance | |
| main.rs:258:18:258:31 | &my_local_rec2 | main.rs:260:21:260:29 | ptr_start | provenance | |
| main.rs:260:21:260:29 | ptr_start | main.rs:234:19:234:36 | ...: ... | provenance | |
| main.rs:563:26:563:35 | &my_local1 | main.rs:573:14:573:18 | first | provenance | |
| main.rs:564:29:564:38 | &my_local1 | main.rs:575:14:575:17 | prev | provenance | |
| main.rs:568:26:568:35 | &my_local2 | main.rs:574:14:574:17 | ours | provenance | |
| main.rs:568:26:568:35 | &my_local2 | main.rs:575:14:575:17 | prev | provenance | |
| main.rs:598:26:598:27 | &x | main.rs:605:14:605:19 | result | provenance | |
| main.rs:598:26:598:27 | &x | main.rs:613:13:613:18 | result | provenance | |
nodes
| main.rs:29:2:29:18 | return ... | semmle.label | return ... |
| main.rs:29:9:29:18 | &my_local1 | semmle.label | &my_local1 |
| main.rs:35:2:35:22 | return ... | semmle.label | return ... |
| main.rs:35:9:35:22 | &mut my_local2 | semmle.label | &mut my_local2 |
| main.rs:41:2:41:28 | return ... | semmle.label | return ... |
| main.rs:41:9:41:28 | &raw const my_local3 | semmle.label | &raw const my_local3 |
| main.rs:47:2:47:26 | return ... | semmle.label | return ... |
| main.rs:47:9:47:26 | &raw mut my_local4 | semmle.label | &raw mut my_local4 |
| main.rs:51:2:51:15 | return ... | semmle.label | return ... |
| main.rs:51:9:51:15 | &param5 | semmle.label | &param5 |
| main.rs:58:2:58:18 | return ... | semmle.label | return ... |
| main.rs:58:9:58:18 | &... | semmle.label | &... |
| main.rs:62:11:62:30 | get_local_dangling(...) | semmle.label | get_local_dangling(...) |
| main.rs:63:11:63:34 | get_local_dangling_mut(...) | semmle.label | get_local_dangling_mut(...) |
| main.rs:64:11:64:40 | get_local_dangling_raw_const(...) | semmle.label | get_local_dangling_raw_const(...) |
| main.rs:65:11:65:38 | get_local_dangling_raw_mut(...) | semmle.label | get_local_dangling_raw_mut(...) |
| main.rs:66:11:66:31 | get_param_dangling(...) | semmle.label | get_param_dangling(...) |
| main.rs:67:11:67:36 | get_local_field_dangling(...) | semmle.label | get_local_field_dangling(...) |
| main.rs:71:8:71:27 | &raw const my_local7 | semmle.label | &raw const my_local7 |
| main.rs:77:13:77:14 | p1 | semmle.label | p1 |
| main.rs:78:13:78:14 | p2 | semmle.label | p2 |
| main.rs:79:13:79:14 | p3 | semmle.label | p3 |
| main.rs:80:13:80:14 | p4 | semmle.label | p4 |
| main.rs:81:13:81:14 | p5 | semmle.label | p5 |
| main.rs:82:13:82:14 | p6 | semmle.label | p6 |
| main.rs:83:13:83:14 | p7 | semmle.label | p7 |
| main.rs:84:4:84:5 | p2 | semmle.label | p2 |
| main.rs:85:4:85:5 | p4 | semmle.label | p4 |
| main.rs:99:17:99:30 | ...: ... | semmle.label | ...: ... |
| main.rs:99:33:99:44 | ...: ... | semmle.label | ...: ... |
| main.rs:102:7:102:17 | &my_local10 | semmle.label | &my_local10 |
| main.rs:107:13:107:14 | p1 | semmle.label | p1 |
| main.rs:108:13:108:14 | p2 | semmle.label | p2 |
| main.rs:109:13:109:14 | p3 | semmle.label | p3 |
| main.rs:110:4:110:5 | p2 | semmle.label | p2 |
| main.rs:121:15:121:25 | &my_local11 | semmle.label | &my_local11 |
| main.rs:121:28:121:46 | &mut my_local_mut12 | semmle.label | &mut my_local_mut12 |
| main.rs:129:2:129:24 | return ... | semmle.label | return ... |
| main.rs:129:9:129:24 | &MY_GLOBAL_CONST | semmle.label | &MY_GLOBAL_CONST |
| main.rs:136:3:136:30 | return ... | semmle.label | return ... |
| main.rs:136:10:136:30 | &mut MY_GLOBAL_STATIC | semmle.label | &mut MY_GLOBAL_STATIC |
| main.rs:141:11:141:21 | get_const(...) | semmle.label | get_const(...) |
| main.rs:142:11:142:26 | get_static_mut(...) | semmle.label | get_static_mut(...) |
| main.rs:147:13:147:14 | p1 | semmle.label | p1 |
| main.rs:148:13:148:14 | p2 | semmle.label | p2 |
| main.rs:149:4:149:5 | p2 | semmle.label | p2 |
| main.rs:161:23:161:32 | &... | semmle.label | &... |
| main.rs:164:23:164:32 | &... | semmle.label | &... |
| main.rs:169:23:169:32 | &... | semmle.label | &... |
| main.rs:178:13:178:14 | p1 | semmle.label | p1 |
| main.rs:179:13:179:14 | p2 | semmle.label | p2 |
| main.rs:180:13:180:14 | p3 | semmle.label | p3 |
| main.rs:189:17:189:31 | ...: ... | semmle.label | ...: ... |
| main.rs:192:13:192:15 | ptr | semmle.label | ptr |
| main.rs:197:17:197:31 | ...: ... | semmle.label | ...: ... |
| main.rs:200:13:200:15 | ptr | semmle.label | ptr |
| main.rs:205:17:205:31 | ...: ... | semmle.label | ...: ... |
| main.rs:208:13:208:15 | ptr | semmle.label | ptr |
| main.rs:215:12:215:22 | &my_local40 | semmle.label | &my_local40 |
| main.rs:217:15:217:17 | ptr | semmle.label | ptr |
| main.rs:218:15:218:17 | ptr | semmle.label | ptr |
| main.rs:220:2:220:11 | return ptr | semmle.label | return ptr |
| main.rs:224:12:224:36 | access_and_get_dangling(...) | semmle.label | access_and_get_dangling(...) |
| main.rs:228:15:228:17 | ptr | semmle.label | ptr |
| main.rs:229:15:229:17 | ptr | semmle.label | ptr |
| main.rs:234:19:234:36 | ...: ... | semmle.label | ...: ... |
| main.rs:236:17:236:29 | &my_local_rec | semmle.label | &my_local_rec |
| main.rs:239:18:239:52 | access_ptr_rec(...) | semmle.label | access_ptr_rec(...) |
| main.rs:239:33:239:40 | ptr_ours | semmle.label | ptr_ours |
| main.rs:244:16:244:21 | ptr_up | semmle.label | ptr_up |
| main.rs:245:18:245:25 | ptr_ours | semmle.label | ptr_ours |
| main.rs:246:18:246:25 | ptr_down | semmle.label | ptr_down |
| main.rs:253:2:253:16 | return ptr_ours | semmle.label | return ptr_ours |
| main.rs:258:18:258:31 | &my_local_rec2 | semmle.label | &my_local_rec2 |
| main.rs:260:21:260:29 | ptr_start | semmle.label | ptr_start |
| main.rs:563:26:563:35 | &my_local1 | semmle.label | &my_local1 |
| main.rs:564:29:564:38 | &my_local1 | semmle.label | &my_local1 |
| main.rs:568:26:568:35 | &my_local2 | semmle.label | &my_local2 |
| main.rs:573:14:573:18 | first | semmle.label | first |
| main.rs:574:14:574:17 | ours | semmle.label | ours |
| main.rs:575:14:575:17 | prev | semmle.label | prev |
| main.rs:598:26:598:27 | &x | semmle.label | &x |
| main.rs:605:14:605:19 | result | semmle.label | result |
| main.rs:613:13:613:18 | result | semmle.label | result |
subpaths
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
query: queries/security/CWE-825/DanglingPointerAccess.ql
postprocess:
- utils/PrettyPrintModels.ql
- utils/InlineExpectationsTestQuery.ql
Empty file.
25 changes: 25 additions & 0 deletions rust/ql/test/query-tests/security/CWE-825/PointerDerefs.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import rust
import codeql.rust.dataflow.DataFlow
import codeql.rust.dataflow.TaintTracking
import codeql.rust.security.DanglingPointerExtensions
import utils.InlineExpectationsTest

module PointerDereferenceFlow = TaintTracking::Global<PointerDereferenceConfig>;
Dismissed Show dismissed Hide dismissed

module PointerDereferenceTest implements TestSig {
string getARelevantTag() { result = "deref" }
Dismissed Show dismissed Hide dismissed

predicate hasActualResult(Location location, string element, string tag, string value) {
Dismissed Show dismissed Hide dismissed
exists(DataFlow::Node sourceNode, DataFlow::Node sinkNode, DataFlow::Node targetValue |
PointerDereferenceFlow::flow(sourceNode, sinkNode) and
createsPointer(sourceNode, targetValue) and
location = sinkNode.getLocation() and
location.getFile().getBaseName() != "" and
element = sinkNode.toString() and
tag = "deref" and
value = targetValue.toString()
)
}
}

import MakeTest<PointerDereferenceTest>
Loading
Loading