Skip to content

Commit b633a92

Browse files
authored
Do not save/restore a pointer across an in-place realloc. (#1925)
For `p = realloc(p, sz)`, VisitBinaryOperator saved the pre-realloc pointer -- both the primal p and its adjoint _d_p -- to restore it in the reverse sweep. realloc frees the old block, so the restore installed a dangling pointer that the sweep then read and wrote, and the cleanup free() double-freed it. Valgrind reports the Invalid read/write and Invalid free in Gradient/Pointers.C once the clang false positives are suppressed. A growing or same-size realloc preserves the reused contents, so the reallocated pointer stays valid for every reverse access. Detect an in-place realloc -- the LHS is realloc's own pointer argument -- and skip both the primal and the derivative store/restore, leaving p and _d_p in place. A shrinking in-place realloc stays unhandled (its reverse sweep may read the freed tail out of bounds, as before); no test exercises it.
1 parent 959f0be commit b633a92

2 files changed

Lines changed: 28 additions & 15 deletions

File tree

lib/Differentiator/ReverseModeVisitor.cpp

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3146,9 +3146,30 @@ Expr* ReverseModeVisitor::getStdInitListSizeExpr(const Expr* E) {
31463146
Lblock.erase(Lblock.begin());
31473147
}
31483148

3149+
// For an in-place `p = realloc(p, sz)`, realloc frees the old block, so
3150+
// saving p to restore it in the reverse sweep would dangle (and be
3151+
// double-freed at cleanup). Keep the reallocated pointer instead, for
3152+
// both p and its adjoint _d_p. This is sound for a growing or same-size
3153+
// realloc, where every reverse access stays in bounds. A shrinking
3154+
// in-place realloc is not handled -- its reverse sweep may read the
3155+
// freed tail out of bounds, as it did before this change; no test
3156+
// exercises it.
3157+
bool isReallocAssignment = false;
3158+
if (opCode == BO_Assign)
3159+
if (auto* RCall = dyn_cast<CallExpr>(R->IgnoreParenCasts()))
3160+
if (const FunctionDecl* RFD = RCall->getDirectCallee())
3161+
if (RFD->getNameAsString() == "realloc" && RCall->getNumArgs()) {
3162+
// Only in-place: the LHS must be realloc's own pointer argument.
3163+
const auto* LDRE = dyn_cast<DeclRefExpr>(L->IgnoreParenCasts());
3164+
const auto* ArgDRE =
3165+
dyn_cast<DeclRefExpr>(RCall->getArg(0)->IgnoreParenCasts());
3166+
isReallocAssignment =
3167+
LDRE && ArgDRE && LDRE->getDecl() == ArgDRE->getDecl();
3168+
}
3169+
31493170
// Store the value of the LHS of the assignment in the forward pass
31503171
// and restore it in the reverse pass
3151-
if (m_DiffReq.shouldBeRecorded(L)) {
3172+
if (m_DiffReq.shouldBeRecorded(L) && !isReallocAssignment) {
31523173
// Clone: LCloned is also consumed by the forward reconstruction, so the
31533174
// store/restore must not share it.
31543175
StmtDiff pushPop = StoreAndRestore(CloneNode(LCloned));
@@ -3167,8 +3188,8 @@ Expr* ReverseModeVisitor::getStdInitListSizeExpr(const Expr* E) {
31673188
if (!ResultRef)
31683189
return Clone(BinOp);
31693190
// We need to store values of derivative pointer variables in forward pass
3170-
// and restore them in reverse pass.
3171-
if (isPointerOp) {
3191+
// and restore them in reverse pass (except across a realloc, see above).
3192+
if (isPointerOp && !isReallocAssignment) {
31723193
// Ldiff.getExpr_dx() is reused below (ResultRef, the derivative op);
31733194
// clone it for the store/restore so the node is not shared.
31743195
StmtDiff pushPop = StoreAndRestore(CloneNode(Ldiff.getExpr_dx()));

test/Gradient/Pointers.C

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// RUN: ./Pointers.out | %filecheck_exec %s
33
// RUN: %cladclang -Xclang -plugin-arg-clad -Xclang -disable-tbr -Xclang -plugin-arg-clad -Xclang -enable-va %s -I%S/../../include -oPointers.out
44
// RUN: ./Pointers.out | %filecheck_exec %s
5-
// FIXME: real realloc use-after-free in reverse mode; drop valgrind when fixed.
6-
// XFAIL: target={{i586.*}}, valgrind
75

86
#include "clad/Differentiator/Differentiator.h"
97

@@ -338,32 +336,26 @@ double cStyleMemoryAlloc(double x, size_t n) {
338336
// CHECK-NEXT: *p = x;
339337
// CHECK-NEXT: double _d_res = 0.;
340338
// CHECK-NEXT: double res = t->x + *p;
341-
// CHECK-NEXT: double *_t2 = p;
342-
// CHECK-NEXT: double *_t3 = _d_p;
343339
// CHECK-NEXT: _d_p = (double *)realloc(_d_p, 2 * sizeof(double));
344340
// CHECK-NEXT: memset(_d_p, 0, 2 * sizeof(double));
345341
// CHECK-NEXT: p = (double *)realloc(p, 2 * sizeof(double));
346-
// CHECK-NEXT: double _t4 = p[1];
342+
// CHECK-NEXT: double _t2 = p[1];
347343
// CHECK-NEXT: p[1] = 2 * x;
348-
// CHECK-NEXT: double _t5 = res;
344+
// CHECK-NEXT: double _t3 = res;
349345
// CHECK-NEXT: res += p[1];
350346
// CHECK-NEXT: _d_res += 1;
351347
// CHECK-NEXT: {
352-
// CHECK-NEXT: res = _t5;
348+
// CHECK-NEXT: res = _t3;
353349
// CHECK-NEXT: double _r_d3 = _d_res;
354350
// CHECK-NEXT: _d_p[1] += _r_d3;
355351
// CHECK-NEXT: }
356352
// CHECK-NEXT: {
357-
// CHECK-NEXT: p[1] = _t4;
353+
// CHECK-NEXT: p[1] = _t2;
358354
// CHECK-NEXT: double _r_d2 = _d_p[1];
359355
// CHECK-NEXT: _d_p[1] = 0.;
360356
// CHECK-NEXT: *_d_x += 2 * _r_d2;
361357
// CHECK-NEXT: }
362358
// CHECK-NEXT: {
363-
// CHECK-NEXT: p = _t2;
364-
// CHECK-NEXT: _d_p = _t3;
365-
// CHECK-NEXT: }
366-
// CHECK-NEXT: {
367359
// CHECK-NEXT: _d_t->x += _d_res;
368360
// CHECK-NEXT: *_d_p += _d_res;
369361
// CHECK-NEXT: }

0 commit comments

Comments
 (0)