Skip to content

Commit a0eddde

Browse files
shaobo-heclaude
andcommitted
Resolve derived source expressions by field offset
The n-th LLVM struct element is not the n-th DW_TAG_member, so naming a field by its ordinal position reported the wrong member whenever the two orderings diverged: bitfields share a storage unit, clang inserts padding, and a C++ base class occupies a field without being a member. A store to 'b->after' was recorded as 'b->f2', and 'p->d0' as 'p->d1' -- a wrong name in a counterexample is worse than no name at all. Look the member up by the byte offset of the field being indexed instead, and decline to name a bitfield, a static or artificial member, an ambiguous match, or a union, where a field index cannot pick a member out. Fix three more ways a name could describe the wrong object: - A pointer value owes a dereference that only the member branch spent, so '(*pa)[2]' came out as 'pa[2]', a different address. Spend it in the subscript branch too. - Subscripting the address of a non-array named 'x[1]' for a scalar x. Require the debug type to really be an array. - A cast that changes the type no longer describes the same object, so neither it nor the stripPointerCastsAndAliases fallback should carry a source expression across one. Consume one DWARF array dimension per subscript, since DWARF folds every dimension into a single node; multi-dimensional element expressions were dropped entirely before and now resolve. Restrict the recorded value to scalars -- vectors reached the recorder through the vector load and store paths -- and drop the parameter name shadowing llvm::Value. Move the test into its own folder pinned to Corral: source expressions are only rendered into a counterexample by /printDataValues, so @checkout could never match under Boogie and the c/data job went red. Add regressions for the bitfield, cast, and array cases; the absence assertions use awk rather than grep -v, which exits 0 whenever any line differs and so asserts nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 497c60f commit a0eddde

7 files changed

Lines changed: 228 additions & 42 deletions

File tree

‎include/smack/SmackInstGenerator.h‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
#include <unordered_set>
1313
#include <vector>
1414

15+
namespace llvm {
16+
class DIType;
17+
}
18+
1519
namespace smack {
1620

1721
class Naming;
@@ -57,9 +61,12 @@ class SmackInstGenerator : public llvm::InstVisitor<SmackInstGenerator> {
5761
void rememberSourceExpr(const llvm::Value *v, const SourceExpr &expr);
5862
const std::vector<SourceExpr> *findSourceExprs(const llvm::Value *v) const;
5963
std::vector<SourceExpr> sourceLValues(const llvm::Value *pointer) const;
60-
void rememberLoadSourceExpr(llvm::LoadInst &i);
64+
void rememberLoadSourceExpr(llvm::LoadInst &i,
65+
const std::vector<SourceExpr> &lvalues);
6166
void rememberGEPSourceExpr(llvm::GetElementPtrInst &i);
6267
void rememberCastSourceExpr(llvm::CastInst &i);
68+
void recordDerivedLValues(const std::vector<SourceExpr> &lvalues,
69+
const llvm::Value *value);
6370
void recordSourceLValues(const llvm::Value *pointer,
6471
const llvm::Value *value);
6572

‎lib/smack/SmackInstGenerator.cpp‎

Lines changed: 146 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
#include "smack/VectorOperations.h"
1212
#include "llvm/Analysis/LoopInfo.h"
1313
#include "llvm/BinaryFormat/Dwarf.h"
14+
#include "llvm/IR/DataLayout.h"
1415
#include "llvm/IR/DebugInfo.h"
1516
#include "llvm/IR/DebugInfoMetadata.h"
1617
#include "llvm/IR/GetElementPtrTypeIterator.h"
1718
#include "llvm/IR/InstVisitor.h"
1819
#include "llvm/Support/GraphWriter.h"
20+
#include <algorithm>
1921
#include <sstream>
2022

2123
#include "llvm/Support/raw_ostream.h"
@@ -90,20 +92,71 @@ const DIType *dereferenceDIType(const DIType *T) {
9092
}
9193
}
9294

93-
const DIDerivedType *getDIMember(const DIType *T, unsigned index) {
95+
// The n-th LLVM struct element is not the n-th DW_TAG_member: bitfields share
96+
// a storage unit, clang inserts padding, and a base class occupies a field
97+
// without being a member. Matching on the byte offset instead keeps the name
98+
// tied to the field whose value is actually recorded.
99+
const DIDerivedType *getDIMemberAtOffset(const DIType *T,
100+
uint64_t offsetInBits) {
94101
auto *CT = dyn_cast_or_null<DICompositeType>(stripDIAliases(T));
95102
if (!CT)
96103
return nullptr;
97104

98-
unsigned memberIndex = 0;
105+
switch (CT->getTag()) {
106+
case dwarf::DW_TAG_structure_type:
107+
case dwarf::DW_TAG_class_type:
108+
break;
109+
default:
110+
// Every union member sits at offset zero, so a field index cannot pick one
111+
// out; arrays and enumerations have no members to pick at all.
112+
return nullptr;
113+
}
114+
115+
const DIDerivedType *found = nullptr;
99116
for (DINode *N : CT->getElements()) {
100117
auto *Member = dyn_cast<DIDerivedType>(N);
101118
if (!Member || Member->getTag() != dwarf::DW_TAG_member)
102119
continue;
103-
if (memberIndex++ == index)
104-
return Member;
120+
// A static member has no storage in the object, and an artificial one
121+
// (a vtable pointer) is not something the source can name.
122+
if (Member->isStaticMember() || Member->isArtificial())
123+
continue;
124+
if (Member->getOffsetInBits() != offsetInBits)
125+
continue;
126+
// Several bitfields share one storage unit, so the recorded value is not
127+
// the value of any single one of them.
128+
if (Member->isBitField())
129+
return nullptr;
130+
if (found)
131+
return nullptr;
132+
found = Member;
105133
}
106-
return nullptr;
134+
return found;
135+
}
136+
137+
unsigned countDIArrayDims(const DICompositeType *CT) {
138+
unsigned dims = 0;
139+
for (DINode *N : CT->getElements())
140+
if (isa<DISubrange>(N))
141+
++dims;
142+
return dims;
143+
}
144+
145+
// DWARF folds every dimension of a multi-dimensional array into one node, so
146+
// consuming a subscript may leave the remaining dimensions in place.
147+
const DIType *stepIntoDIArray(const DIType *T, unsigned &dimsLeft) {
148+
auto *CT = dyn_cast_or_null<DICompositeType>(stripDIAliases(T));
149+
if (!CT || CT->getTag() != dwarf::DW_TAG_array_type)
150+
return nullptr;
151+
152+
if (dimsLeft == 0)
153+
dimsLeft = countDIArrayDims(CT);
154+
if (dimsLeft == 0)
155+
return nullptr;
156+
157+
if (--dimsLeft > 0)
158+
return T;
159+
return stripDIAliases(CT->getBaseType());
107160
}
108161

109162
std::string sourceIndex(const Value *V) {
@@ -142,8 +195,11 @@ SmackInstGenerator::findSourceExprs(const llvm::Value *V) const {
142195
if (I != sourceExprs.end())
143196
return &I->second;
144197

198+
// Only follow a strip that preserves the pointee type; a bitcast to another
199+
// pointee would let the next GEP resolve field indices against the wrong
200+
// debug type.
145201
const Value *Stripped = V->stripPointerCastsAndAliases();
146-
if (Stripped != V) {
202+
if (Stripped != V && Stripped->getType() == V->getType()) {
147203
I = sourceExprs.find(Stripped);
148204
if (I != sourceExprs.end())
149205
return &I->second;
@@ -178,8 +234,9 @@ SmackInstGenerator::sourceLValues(const llvm::Value *Pointer) const {
178234
return Results;
179235
}
180236

181-
void SmackInstGenerator::rememberLoadSourceExpr(llvm::LoadInst &I) {
182-
for (const auto &E : sourceLValues(I.getPointerOperand()))
237+
void SmackInstGenerator::rememberLoadSourceExpr(
238+
llvm::LoadInst &I, const std::vector<SourceExpr> &LValues) {
239+
for (const auto &E : LValues)
183240
rememberSourceExpr(&I, {E.name, E.type, false, E.isDerived});
184241
}
185242

@@ -188,34 +245,55 @@ void SmackInstGenerator::rememberGEPSourceExpr(llvm::GetElementPtrInst &I) {
188245
if (!Bases)
189246
return;
190247

191-
for (const auto &Base : *Bases) {
248+
// Copy the bases: recording a new expression for this instruction may grow
249+
// the map, and we would otherwise be iterating a vector inside it.
250+
const std::vector<SourceExpr> Sources = *Bases;
251+
252+
for (const auto &Base : Sources) {
192253
std::string Name = Base.name;
193254
const DIType *DITy = Base.isAddress ? stripDIAliases(Base.type)
194255
: dereferenceDIType(Base.type);
195256
Type *IRTy = I.getSourceElementType();
196-
bool UseArrow = !Base.isAddress;
257+
// A pointer value still owes a dereference: the first member access spends
258+
// it as '->', and the first subscript spends it as '(*p)[i]'.
259+
bool PendingDeref = !Base.isAddress;
197260
bool Derived = Base.isDerived;
198261
bool Valid = DITy != nullptr;
262+
unsigned Dims = 0;
199263
unsigned Position = 0;
200264

201265
for (auto Index = I.idx_begin(), End = I.idx_end(); Valid && Index != End;
202266
++Index, ++Position) {
203267
Value *IndexValue = Index->get();
204268

205269
// The first index steps through the pointer operand. A zero index names
206-
// the same source object and leaves '.' versus '->' to the first member.
270+
// the same source object and leaves the dereference to the next index.
207271
if (Position == 0) {
208272
auto *CI = dyn_cast<ConstantInt>(IndexValue);
209-
if (!CI || !CI->isZero()) {
210-
std::string IndexName = sourceIndex(IndexValue);
211-
if (IndexName.empty()) {
273+
if (CI && CI->isZero())
274+
continue;
275+
276+
std::string IndexName = sourceIndex(IndexValue);
277+
if (IndexName.empty()) {
278+
Valid = false;
279+
break;
280+
}
281+
282+
if (Base.isAddress) {
283+
// Subscripting the address of an object only names an element when
284+
// that object is an array; otherwise this is pointer arithmetic
285+
// running past it, and 'x[1]' would name something that x is not.
286+
const DIType *ElemDITy = stepIntoDIArray(DITy, Dims);
287+
if (!ElemDITy) {
212288
Valid = false;
213289
break;
214290
}
215-
Name += "[" + IndexName + "]";
216-
UseArrow = false;
217-
Derived = true;
291+
DITy = ElemDITy;
218292
}
293+
294+
Name += "[" + IndexName + "]";
295+
PendingDeref = false;
296+
Derived = true;
219297
continue;
220298
}
221299

@@ -227,17 +305,22 @@ void SmackInstGenerator::rememberGEPSourceExpr(llvm::GetElementPtrInst &I) {
227305
}
228306

229307
unsigned Field = CI->getZExtValue();
230-
const DIDerivedType *Member = getDIMember(DITy, Field);
308+
const DIDerivedType *Member =
309+
getDIMemberAtOffset(DITy, I.getModule()
310+
->getDataLayout()
311+
.getStructLayout(ST)
312+
->getElementOffsetInBits(Field));
231313
if (!Member || Member->getName().empty()) {
232314
Valid = false;
233315
break;
234316
}
235317

236-
Name += (UseArrow ? "->" : ".") + Member->getName().str();
237-
UseArrow = false;
318+
Name += (PendingDeref ? "->" : ".") + Member->getName().str();
319+
PendingDeref = false;
238320
Derived = true;
239321
IRTy = ST->getElementType(Field);
240322
DITy = stripDIAliases(Member->getBaseType());
323+
Dims = 0;
241324
continue;
242325
}
243326

@@ -246,21 +329,27 @@ void SmackInstGenerator::rememberGEPSourceExpr(llvm::GetElementPtrInst &I) {
246329
Valid = false;
247330
break;
248331
}
249-
Name += "[" + IndexName + "]";
250-
UseArrow = false;
251-
Derived = true;
252332

253-
if (auto *AT = dyn_cast<ArrayType>(IRTy)) {
254-
IRTy = AT->getElementType();
255-
auto *CT = dyn_cast_or_null<DICompositeType>(stripDIAliases(DITy));
256-
DITy = CT ? stripDIAliases(CT->getBaseType()) : nullptr;
257-
} else if (auto *VT = dyn_cast<VectorType>(IRTy)) {
258-
IRTy = VT->getElementType();
259-
auto *CT = dyn_cast_or_null<DICompositeType>(stripDIAliases(DITy));
260-
DITy = CT ? stripDIAliases(CT->getBaseType()) : nullptr;
261-
} else {
333+
Type *ElemIRTy = nullptr;
334+
if (auto *AT = dyn_cast<ArrayType>(IRTy))
335+
ElemIRTy = AT->getElementType();
336+
else if (auto *VT = dyn_cast<FixedVectorType>(IRTy))
337+
ElemIRTy = VT->getElementType();
338+
339+
const DIType *ElemDITy = stepIntoDIArray(DITy, Dims);
340+
if (!ElemIRTy || !ElemDITy) {
262341
Valid = false;
342+
break;
263343
}
344+
345+
// '*p' subscripted is '(*p)[i]'; 'p[i]' would name a different address.
346+
if (PendingDeref)
347+
Name = "(*" + Name + ")";
348+
Name += "[" + IndexName + "]";
349+
PendingDeref = false;
350+
Derived = true;
351+
IRTy = ElemIRTy;
352+
DITy = ElemDITy;
264353
}
265354

266355
if (Valid)
@@ -269,26 +358,42 @@ void SmackInstGenerator::rememberGEPSourceExpr(llvm::GetElementPtrInst &I) {
269358
}
270359

271360
void SmackInstGenerator::rememberCastSourceExpr(llvm::CastInst &I) {
361+
// A cast that changes the type no longer describes the same source object:
362+
// the pointee of a bitcast decides which struct the next GEP resolves its
363+
// field indices against, and an integer cast changes the value outright.
364+
if (I.getSrcTy() != I.getDestTy())
365+
return;
366+
272367
auto *Exprs = findSourceExprs(I.getOperand(0));
273368
if (!Exprs)
274369
return;
275-
for (const auto &E : *Exprs)
370+
const std::vector<SourceExpr> Sources = *Exprs;
371+
for (const auto &E : Sources)
276372
rememberSourceExpr(&I, E);
277373
}
278374

279-
void SmackInstGenerator::recordSourceLValues(const llvm::Value *Pointer,
280-
const llvm::Value *Value) {
281-
if (!SmackOptions::SourceLocSymbols || Value->getType()->isAggregateType())
375+
void SmackInstGenerator::recordDerivedLValues(
376+
const std::vector<SourceExpr> &LValues, const llvm::Value *V) {
377+
// boogie_si_record_* models a single scalar; an aggregate or a vector has no
378+
// counterexample value to print.
379+
llvm::Type *T = V->getType();
380+
if (!SmackOptions::SourceLocSymbols || T->isAggregateType() ||
381+
T->isVectorTy() || T->isVoidTy())
282382
return;
283383

284384
std::set<std::string> Recorded;
285-
for (const auto &E : sourceLValues(Pointer)) {
385+
for (const auto &E : LValues) {
286386
if (!E.isDerived || !Recorded.insert(E.name).second)
287387
continue;
288-
emit(recordProcedureCall(Value, {Attr::attr("cexpr", E.name)}));
388+
emit(recordProcedureCall(V, {Attr::attr("cexpr", E.name)}));
289389
}
290390
}
291391

392+
void SmackInstGenerator::recordSourceLValues(const llvm::Value *Pointer,
393+
const llvm::Value *V) {
394+
recordDerivedLValues(sourceLValues(Pointer), V);
395+
}
396+
292397
const Stmt *
293398
SmackInstGenerator::recordProcedureCall(const llvm::Value *V,
294399
std::list<const Attr *> attrs) {
@@ -725,8 +830,9 @@ void SmackInstGenerator::visitLoadInst(llvm::LoadInst &li) {
725830
}
726831

727832
emit(Stmt::assign(rep->expr(&li), E));
728-
rememberLoadSourceExpr(li);
729-
recordSourceLValues(P, &li);
833+
auto LValues = sourceLValues(P);
834+
rememberLoadSourceExpr(li, LValues);
835+
recordDerivedLValues(LValues, &li);
730836

731837
if (SmackOptions::MemoryModelDebug) {
732838
emit(Stmt::call(Naming::REC_MEM_OP, {Expr::id(Naming::MEM_OP_VAL)}));
@@ -1474,7 +1580,6 @@ void SmackInstGenerator::visitIntrinsicInst(llvm::IntrinsicInst &ii) {
14741580
{llvm::Intrinsic::ctlz, ctlz},
14751581
{llvm::Intrinsic::ctpop, ctpop},
14761582
{llvm::Intrinsic::cttz, cttz},
1477-
{llvm::Intrinsic::dbg_declare, ignore},
14781583
{llvm::Intrinsic::dbg_label, ignore},
14791584
{llvm::Intrinsic::copysign, copysign},
14801585
{llvm::Intrinsic::expect, identity},

‎test/c/data/source-expr/config.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Recorded source expressions are only rendered into a counterexample by
2+
# Corral's /printDataValues, so @checkout cannot match under Boogie.
3+
verifiers: [corral]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include "smack.h"
2+
3+
// @expect verified
4+
// @checkbpl grep -F 'call {:cexpr "(*pa)[2]"} boogie_si_record_i32'
5+
// @checkbpl awk '/cexpr "pa\[2\]"/ { exit 1 }'
6+
// @checkbpl awk '/cexpr "x\[1\]"/ { exit 1 }'
7+
// @checkbpl grep -F 'call {:cexpr "m[1][2]"} boogie_si_record_i32'
8+
9+
// (*pa)[2] and pa[2] are different addresses, and a scalar's address is not an
10+
// array; both used to be recorded under the wrong name.
11+
int main(void) {
12+
int x = 1;
13+
int arr[4];
14+
int(*pa)[4] = &arr;
15+
int m[3][4];
16+
17+
(*pa)[2] = 2;
18+
m[1][2] = 3;
19+
assert((*pa)[2] == 2 && m[1][2] == 3 && x == 1);
20+
return 0;
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include "smack.h"
2+
3+
// @expect verified
4+
// @checkbpl grep -F 'call {:cexpr "b->after"} boogie_si_record_i32'
5+
// @checkbpl awk '/cexpr "b->f1"/ { exit 1 }'
6+
// @checkbpl awk '/cexpr "b->f2"/ { exit 1 }'
7+
8+
// Two bitfields share one LLVM storage unit, so the third member sits at LLVM
9+
// field index 1 while it is the third DW_TAG_member. Naming it by ordinal
10+
// position reported it as f2.
11+
struct bits {
12+
unsigned f1 : 3;
13+
unsigned f2 : 5;
14+
int after;
15+
};
16+
17+
int main(void) {
18+
struct bits s;
19+
struct bits *b = &s;
20+
b->after = 42;
21+
assert(b->after == 42);
22+
return 0;
23+
}

0 commit comments

Comments
 (0)