Skip to content

Commit 12a958d

Browse files
authored
Fix a few GCChecker issues (#51)
This PR fixes a few issues in the GCChecker. See the new test cases for what are fixed in the PR.
1 parent b1f61bb commit 12a958d

File tree

6 files changed

+277
-30
lines changed

6 files changed

+277
-30
lines changed

src/clangsa/GCChecker.cpp

Lines changed: 99 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
// Assumptions for pinning:
44
// * args need to be pinned
55
// * JL_ROOTING_ARGUMENT and JL_ROOTED_ARGUMENT will propagate pinning state as well.
6+
// * The checker may not consider alias for derived pointers in some cases.
7+
// * if f(x) returns a derived pointer from x, a = f(x); b = f(x); PTR_PIN(a); The checker will NOT find b as pinned.
8+
// * a = x->y; b = x->y; PTR_PIN(a); The checker will find b as pinned.
9+
// * Need to see if this affects correctness.
10+
// * The checker may report some vals as moved even if there is a new load for the val after safepoint.
11+
// * f(x->a); jl_safepoint(); f(x->a); x->a is loaded after a safepoint, but the checker may report errors. This seems fine, as the compiler may hoist the load.
12+
// * a = x->a; f(a); jl_safepoint(); f(a); a may be moved in a safepoint, and the checker will report errors.
613

714
#include "clang/Frontend/FrontendActions.h"
815
#include "clang/StaticAnalyzer/Checkers/SValExplainer.h"
@@ -96,6 +103,7 @@ class GCChecker
96103
llvm::dbgs() << ((P == TransitivelyPinned) ? "TransitivelyPinned"
97104
: (P == Pinned) ? "Pinned"
98105
: (P == NotPinned) ? "NotPinned"
106+
: (P == Moved) ? "Moved"
99107
: "Error");
100108
llvm::dbgs() << ",";
101109
if (S == Rooted)
@@ -193,8 +201,13 @@ class GCChecker
193201
VS.FD = FD;
194202
return VS;
195203
}
196-
// Assume arguments are pinned
197-
return getRooted(nullptr, ValueState::Pinned, -1);
204+
bool require_tpin = declHasAnnotation(PVD, "julia_require_tpin");
205+
if (require_tpin) {
206+
return getRooted(nullptr, ValueState::TransitivelyPinned, -1);
207+
} else {
208+
// Assume arguments are pinned
209+
return getRooted(nullptr, ValueState::Pinned, -1);
210+
}
198211
}
199212
};
200213

@@ -230,7 +243,6 @@ class GCChecker
230243
: "Error");
231244
llvm::dbgs() << ",Depth=";
232245
llvm::dbgs() << RootedAtDepth;
233-
llvm::dbgs() << "\n";
234246
}
235247
};
236248

@@ -325,6 +337,7 @@ class GCChecker
325337
SymbolRef getSymbolForResult(const Expr *Result, const ValueState *OldValS,
326338
ProgramStateRef &State, CheckerContext &C) const;
327339
void validateValue(const GCChecker::ValueState* VS, CheckerContext &C, SymbolRef Sym, const char *message) const;
340+
void validateValueRootnessOnly(const GCChecker::ValueState* VS, CheckerContext &C, SymbolRef Sym, const char *message) const;
328341
void validateValue(const GCChecker::ValueState* VS, CheckerContext &C, SymbolRef Sym, const char *message, SourceRange range) const;
329342
int validateValueInner(const GCChecker::ValueState* VS) const;
330343
GCChecker::ValueState getRootedFromRegion(const MemRegion *Region, const PinState *PS, int Depth) const;
@@ -401,13 +414,16 @@ SymbolRef GCChecker::walkToRoot(callback f, const ProgramStateRef &State,
401414
const MemRegion *Region) {
402415
if (!Region)
403416
return nullptr;
417+
logWithDump("- walkToRoot, Region", Region);
404418
while (true) {
405419
const SymbolicRegion *SR = Region->getSymbolicBase();
406420
if (!SR) {
407421
return nullptr;
408422
}
409423
SymbolRef Sym = SR->getSymbol();
410424
const ValueState *OldVState = State->get<GCValueMap>(Sym);
425+
logWithDump("- walkToRoot, Sym", Sym);
426+
logWithDump("- walkToRoot, OldVState", OldVState);
411427
if (f(Sym, OldVState)) {
412428
if (const SymbolRegionValue *SRV = dyn_cast<SymbolRegionValue>(Sym)) {
413429
Region = SRV->getRegion();
@@ -468,6 +484,15 @@ void GCChecker::validateValue(const ValueState* VS, CheckerContext &C, SymbolRef
468484
}
469485
}
470486

487+
void GCChecker::validateValueRootnessOnly(const ValueState* VS, CheckerContext &C, SymbolRef Sym, const char *message) const {
488+
int v = validateValueInner(VS);
489+
if (v == FREED) {
490+
GCChecker::report_value_error(C, Sym, (std::string(message) + " GCed").c_str());
491+
} else if (v == MOVED) {
492+
// We don't care if it is moved
493+
}
494+
}
495+
471496
void GCChecker::validateValue(const ValueState* VS, CheckerContext &C, SymbolRef Sym, const char *message) const {
472497
int v = validateValueInner(VS);
473498
if (v == FREED) {
@@ -717,6 +742,8 @@ PDP GCChecker::GCValueBugVisitor::VisitNode(const ExplodedNode *N,
717742
return MakePDP(Pos, "Root was released here.");
718743
} else if (NewValueState->RootDepth != OldValueState->RootDepth) {
719744
return MakePDP(Pos, "Rooting Depth changed here.");
745+
} else if (NewValueState->isMoved() && !OldValueState->isMoved()) {
746+
return MakePDP(Pos, "Value was moved here.");
720747
}
721748
return nullptr;
722749
}
@@ -833,6 +860,7 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const {
833860
const auto *FD = dyn_cast<FunctionDecl>(LCtx->getDecl());
834861
if (!FD)
835862
return;
863+
logWithDump("checkBeginFunction", FD);
836864
ProgramStateRef State = C.getState();
837865
bool Change = false;
838866
if (C.inTopFrame()) {
@@ -861,7 +889,10 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const {
861889
auto Param = State->getLValue(P, LCtx);
862890
const MemRegion *Root = State->getSVal(Param).getAsRegion();
863891
State = State->set<GCRootMap>(Root, RootState::getRoot(-1));
864-
State = State->set<GCPinMap>(Root, PinState::getPin(-1));
892+
if (declHasAnnotation(P, "julia_require_tpin"))
893+
State = State->set<GCPinMap>(Root, PinState::getTransitivePin(-1));
894+
else
895+
State = State->set<GCPinMap>(Root, PinState::getTransitivePin(-1));
865896
} else if (isGCTrackedType(P->getType())) {
866897
auto Param = State->getLValue(P, LCtx);
867898
SymbolRef AssignedSym = State->getSVal(Param).getAsSymbol();
@@ -880,6 +911,7 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const {
880911

881912
void GCChecker::checkEndFunction(const clang::ReturnStmt *RS,
882913
CheckerContext &C) const {
914+
log("checkEndFunction");
883915
ProgramStateRef State = C.getState();
884916

885917
if (RS && gcEnabledHere(C) && RS->getRetValue() && isGCTracked(RS->getRetValue())) {
@@ -1104,7 +1136,11 @@ bool GCChecker::processPotentialSafepoint(const CallEvent &Call,
11041136
if (RetSym == I.getKey())
11051137
continue;
11061138
if (I.getData().isNotPinned()) {
1107-
State = State->set<GCValueMap>(I.getKey(), ValueState::getMoved(I.getData()));
1139+
logWithDump("- move unpinned values, Sym", I.getKey());
1140+
logWithDump("- move unpinned values, VS", I.getData());
1141+
auto NewVS = ValueState::getMoved(I.getData());
1142+
State = State->set<GCValueMap>(I.getKey(), NewVS);
1143+
logWithDump("- move unpinned values, NewVS", NewVS);
11081144
DidChange = true;
11091145
}
11101146
}
@@ -1153,7 +1189,7 @@ bool GCChecker::processArgumentRooting(const CallEvent &Call, CheckerContext &C,
11531189
const ValueState *CurrentVState = State->get<GCValueMap>(RootedSymbol);
11541190
ValueState NewVState = *OldVState;
11551191
// If the old state is pinned, the new state is not pinned.
1156-
if (OldVState->isPinned() && ((CurrentVState && CurrentVState->isPinnedByAnyway()) || !CurrentVState)) {
1192+
if (OldVState->isPinned() && ((CurrentVState && !CurrentVState->isPinnedByAnyway()) || !CurrentVState)) {
11571193
NewVState = ValueState::getNotPinned(*OldVState);
11581194
}
11591195
logWithDump("- Rooted set to", NewVState);
@@ -1176,6 +1212,8 @@ bool GCChecker::processAllocationOfResult(const CallEvent &Call,
11761212
Call.getOriginExpr(), C.getLocationContext(), QT, C.blockCount());
11771213
State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), S);
11781214
Sym = S.getAsSymbol();
1215+
logWithDump("- conjureSymbolVal, S", S);
1216+
logWithDump("- conjureSymbolVal, Sym", Sym);
11791217
}
11801218
if (isGloballyRootedType(QT))
11811219
State = State->set<GCValueMap>(Sym, ValueState::getRooted(nullptr, ValueState::pinState(isGloballyTransitivelyPinnedType(QT)), -1));
@@ -1229,8 +1267,11 @@ bool GCChecker::processAllocationOfResult(const CallEvent &Call,
12291267
const MemRegion *Region = Test.getAsRegion();
12301268
const ValueState *OldVState =
12311269
getValStateForRegion(C.getASTContext(), State, Region);
1232-
if (OldVState)
1233-
NewVState = *OldVState;
1270+
if (OldVState) {
1271+
NewVState = ValueState::inheritState(*OldVState);
1272+
logWithDump("- jl_propagates_root, OldVState", *OldVState);
1273+
logWithDump("- jl_propagates_root, NewVState", NewVState);
1274+
}
12341275
break;
12351276
}
12361277
}
@@ -1245,8 +1286,11 @@ bool GCChecker::processAllocationOfResult(const CallEvent &Call,
12451286
void GCChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const {
12461287
logWithDump("checkPostCall", Call);
12471288
ProgramStateRef State = C.getState();
1289+
log("- processArgmentRooting");
12481290
bool didChange = processArgumentRooting(Call, C, State);
1291+
log("- processPotentialsafepoint");
12491292
didChange |= processPotentialSafepoint(Call, C, State);
1293+
log("- processAllocationOfResult");
12501294
didChange |= processAllocationOfResult(Call, C, State);
12511295
if (didChange)
12521296
C.addTransition(State);
@@ -1255,6 +1299,7 @@ void GCChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const {
12551299
// Implicitly root values that were casted to globally rooted values
12561300
void GCChecker::checkPostStmt(const CStyleCastExpr *CE,
12571301
CheckerContext &C) const {
1302+
logWithDump("checkpostStmt(CStyleCastExpr)", CE);
12581303
if (!isGloballyRootedType(CE->getTypeAsWritten()))
12591304
return;
12601305
SymbolRef Sym = C.getSVal(CE).getAsSymbol();
@@ -1354,6 +1399,7 @@ void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent,
13541399
SymbolRef OldSym = ParentVal.getAsSymbol(true);
13551400
const MemRegion *Region = C.getSVal(Parent).getAsRegion();
13561401
const ValueState *OldValS = OldSym ? State->get<GCValueMap>(OldSym) : nullptr;
1402+
logWithDump("- Region", Region);
13571403
logWithDump("- OldSym", OldSym);
13581404
logWithDump("- OldValS", OldValS);
13591405
SymbolRef NewSym = getSymbolForResult(Result, OldValS, State, C);
@@ -1363,6 +1409,7 @@ void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent,
13631409
logWithDump("- NewSym", NewSym);
13641410
// NewSym might already have a better root
13651411
const ValueState *NewValS = State->get<GCValueMap>(NewSym);
1412+
logWithDump("- NewValS", NewValS);
13661413
if (Region) {
13671414
const VarRegion *VR = Region->getAs<VarRegion>();
13681415
bool inheritedState = false;
@@ -1412,8 +1459,9 @@ void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent,
14121459
validateValue(OldValS, C, OldSym, "Creating derivative of value that may have been");
14131460
if (!OldValS->isPotentiallyFreed() && ResultTracked) {
14141461
logWithDump("- Set as OldValS, Sym", NewSym);
1415-
logWithDump("- Set as OldValS, VS", OldValS);
1416-
C.addTransition(State->set<GCValueMap>(NewSym, *OldValS));
1462+
auto InheritVS = ValueState::inheritState(*OldValS);
1463+
logWithDump("- Set as OldValS, InheritVS", InheritVS);
1464+
C.addTransition(State->set<GCValueMap>(NewSym, InheritVS));
14171465
return;
14181466
}
14191467
}
@@ -1481,6 +1529,7 @@ void GCChecker::checkPostStmt(const MemberExpr *ME, CheckerContext &C) const {
14811529

14821530
void GCChecker::checkPostStmt(const UnaryOperator *UO,
14831531
CheckerContext &C) const {
1532+
logWithDump("checkPostStmt(UnaryOperator)", UO);
14841533
if (UO->getOpcode() == UO_Deref) {
14851534
checkDerivingExpr(UO, UO->getSubExpr(), true, C);
14861535
}
@@ -1590,6 +1639,11 @@ void GCChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
15901639
report_value_error(C, Sym, "Passing non-pinned value as argument to function that may GC", range);
15911640
}
15921641
}
1642+
if (FD && idx < FD->getNumParams() && declHasAnnotation(FD->getParamDecl(idx), "julia_require_tpin")) {
1643+
if (!ValState->isTransitivelyPinned()) {
1644+
report_value_error(C, Sym, "Passing non-tpinned argument to function that requires a tpin argument.");
1645+
}
1646+
}
15931647
}
15941648
}
15951649

@@ -1732,6 +1786,13 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
17321786
report_error(C, "Can not understand this pin.");
17331787
return true;
17341788
}
1789+
1790+
const ValueState *OldVS = C.getState()->get<GCValueMap>(Sym);
1791+
if (OldVS && OldVS->isMoved()) {
1792+
report_error(C, "Attempt to PIN a value that is already moved.");
1793+
return true;
1794+
}
1795+
17351796
auto MRV = Arg.getAs<loc::MemRegionVal>();
17361797
if (!MRV) {
17371798
report_error(C, "PTR_PIN with something other than a local variable");
@@ -1740,7 +1801,6 @@ bool GCChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
17401801
const MemRegion *Region = MRV->getRegion();
17411802
auto State = C.getState()->set<GCPinMap>(Region, PinState::getPin(CurrentDepth));
17421803
logWithDump("- Pin region", Region);
1743-
const ValueState *OldVS = State->get<GCValueMap>(Sym);
17441804
State = State->set<GCValueMap>(Sym, ValueState::getPinned(*OldVS));
17451805
logWithDump("- Pin value", Sym);
17461806
C.addTransition(State);
@@ -1885,16 +1945,21 @@ void GCChecker::checkBind(SVal LVal, SVal RVal, const clang::Stmt *S,
18851945
log("- No Sym");
18861946
return;
18871947
}
1948+
logWithDump("- Sym", Sym);
18881949
const auto *RootState = State->get<GCRootMap>(R);
18891950
logWithDump("- R", R);
18901951
logWithDump("- RootState for R", RootState);
18911952
if (!RootState) {
18921953
const ValueState *ValSP = nullptr;
18931954
ValueState ValS;
18941955
if (rootRegionIfGlobal(R->getBaseRegion(), State, C, &ValS)) {
1956+
logWithDump("- rootRegionIfGlobal, base", R->getBaseRegion());
18951957
ValSP = &ValS;
1958+
logWithDump("- rootRegionIfGlobal ValSP", ValSP);
18961959
} else {
1960+
logWithDump("- getValStateForRegion", R);
18971961
ValSP = getValStateForRegion(C.getASTContext(), State, R);
1962+
logWithDump("- getValStateForRegion", ValSP);
18981963
}
18991964
if (ValSP && ValSP->isRooted()) {
19001965
logWithDump("- Found base region that is rooted", ValSP);
@@ -1903,8 +1968,9 @@ void GCChecker::checkBind(SVal LVal, SVal RVal, const clang::Stmt *S,
19031968
RValState->RootDepth < ValSP->RootDepth) {
19041969
logWithDump("- No need to set ValState, current ValState", RValState);
19051970
} else {
1906-
logWithDump("- Set ValState, current ValState", ValSP);
1907-
C.addTransition(State->set<GCValueMap>(Sym, *ValSP));
1971+
auto InheritVS = ValueState::inheritState(*ValSP);
1972+
logWithDump("- Set ValState, InheritVS", InheritVS);
1973+
C.addTransition(State->set<GCValueMap>(Sym, InheritVS));
19081974
}
19091975
}
19101976
} else {
@@ -1929,7 +1995,7 @@ void GCChecker::checkBind(SVal LVal, SVal RVal, const clang::Stmt *S,
19291995
} else {
19301996
logWithDump("- Found ValState for Sym", RValState);
19311997
validateValue(RValState, C, Sym, "Trying to root value which may have been");
1932-
if (!RValState->isRooted() ||
1998+
if (!RValState->isRooted() || !RValState->isPinnedByAnyway() ||
19331999
RValState->RootDepth > RootState->RootedAtDepth) {
19342000
auto NewVS = getRootedFromRegion(R, State->get<GCPinMap>(R), RootState->RootedAtDepth);
19352001
logWithDump("- getRootedFromRegion", NewVS);
@@ -1994,48 +2060,62 @@ bool GCChecker::rootRegionIfGlobal(const MemRegion *R, ProgramStateRef &State,
19942060

19952061
void GCChecker::checkLocation(SVal SLoc, bool IsLoad, const Stmt *S,
19962062
CheckerContext &C) const {
2063+
logWithDump("checkLocation", SLoc);
19972064
ProgramStateRef State = C.getState();
19982065
bool DidChange = false;
19992066
const RootState *RS = nullptr;
20002067
// Loading from a root produces a rooted symbol. TODO: Can we do something
20012068
// better than this.
20022069
if (IsLoad && (RS = State->get<GCRootMap>(SLoc.getAsRegion()))) {
2070+
logWithDump("- IsLoad, RS", RS);
20032071
SymbolRef LoadedSym =
20042072
State->getSVal(SLoc.getAs<Loc>().getValue()).getAsSymbol();
20052073
if (LoadedSym) {
20062074
const ValueState *ValS = State->get<GCValueMap>(LoadedSym);
2007-
if (!ValS || !ValS->isRooted() || ValS->RootDepth > RS->RootedAtDepth) {
2075+
logWithDump("- IsLoad, LoadedSym", LoadedSym);
2076+
logWithDump("- IsLoad, ValS", ValS);
2077+
if (!ValS || !ValS->isRooted() || !ValS->isPinnedByAnyway() || ValS->RootDepth > RS->RootedAtDepth) {
2078+
auto NewVS = getRootedFromRegion(SLoc.getAsRegion(), State->get<GCPinMap>(SLoc.getAsRegion()), RS->RootedAtDepth);
2079+
logWithDump("- IsLoad, NewVS", NewVS);
20082080
DidChange = true;
2009-
State = State->set<GCValueMap>(
2010-
LoadedSym,
2011-
getRootedFromRegion(SLoc.getAsRegion(), State->get<GCPinMap>(SLoc.getAsRegion()), RS->RootedAtDepth));
2081+
State = State->set<GCValueMap>(LoadedSym, NewVS);
20122082
}
20132083
}
20142084
}
2085+
logWithDump("- getAsRegion()", SLoc.getAsRegion());
20152086
// If it's just the symbol by itself, let it be. We allow dead pointer to be
20162087
// passed around, so long as they're not accessed. However, we do want to
20172088
// start tracking any globals that may have been accessed.
20182089
if (rootRegionIfGlobal(SLoc.getAsRegion(), State, C)) {
20192090
C.addTransition(State);
2091+
log("- rootRegionIfGlobal");
20202092
return;
20212093
}
20222094
SymbolRef SymByItself = SLoc.getAsSymbol(false);
2095+
logWithDump("- SymByItself", SymByItself);
20232096
if (SymByItself) {
20242097
DidChange &&C.addTransition(State);
20252098
return;
20262099
}
20272100
// This will walk backwards until it finds the base symbol
20282101
SymbolRef Sym = SLoc.getAsSymbol(true);
2102+
logWithDump("- Sym", Sym);
20292103
if (!Sym) {
20302104
DidChange &&C.addTransition(State);
20312105
return;
20322106
}
20332107
const ValueState *VState = State->get<GCValueMap>(Sym);
2108+
logWithDump("- VState", VState);
20342109
if (!VState) {
20352110
DidChange &&C.addTransition(State);
20362111
return;
20372112
}
2038-
validateValue(VState, C, Sym, "Trying to access value which may have been");
2113+
// If this is the sym, we verify both rootness and pinning. Otherwise, it may be the parent sym and we only care about the rootness.
2114+
if (SymByItself == Sym) {
2115+
validateValue(VState, C, Sym, "Trying to access value which may have been");
2116+
} else {
2117+
validateValueRootnessOnly(VState, C, Sym, "Trying to access value which may have been");
2118+
}
20392119
DidChange &&C.addTransition(State);
20402120
}
20412121

src/julia.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,12 +1871,12 @@ JL_DLLEXPORT void JL_NORETURN jl_exceptionf(jl_datatype_t *ty,
18711871
JL_DLLEXPORT void JL_NORETURN jl_too_few_args(const char *fname, int min);
18721872
JL_DLLEXPORT void JL_NORETURN jl_too_many_args(const char *fname, int max);
18731873
JL_DLLEXPORT void JL_NORETURN jl_type_error(const char *fname,
1874-
jl_value_t *expected JL_MAYBE_UNROOTED,
1875-
jl_value_t *got JL_MAYBE_UNROOTED);
1874+
jl_value_t *expected JL_MAYBE_UNROOTED JL_MAYBE_UNPINNED,
1875+
jl_value_t *got JL_MAYBE_UNROOTED JL_MAYBE_UNPINNED);
18761876
JL_DLLEXPORT void JL_NORETURN jl_type_error_rt(const char *fname,
18771877
const char *context,
1878-
jl_value_t *ty JL_MAYBE_UNROOTED,
1879-
jl_value_t *got JL_MAYBE_UNROOTED);
1878+
jl_value_t *ty JL_MAYBE_UNROOTED JL_MAYBE_UNPINNED,
1879+
jl_value_t *got JL_MAYBE_UNROOTED JL_MAYBE_UNPINNED);
18801880
JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var);
18811881
JL_DLLEXPORT void JL_NORETURN jl_has_no_field_error(jl_sym_t *type_name, jl_sym_t *var);
18821882
JL_DLLEXPORT void JL_NORETURN jl_atomic_error(char *str);

0 commit comments

Comments
 (0)