-
-
Save nikki93/2bb11237bf76fceb0bf687d6d9eef1b3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
typedef struct { | |
int i; | |
} Foo; | |
typedef struct { | |
int count; | |
} FooArray; // Dummy array type, real version of this would store some `Foo`s | |
void FooArrayDealloc(FooArray *arr); | |
FooArray FooArrayClone(FooArray *arr); | |
void FooArrayReplace(FooArray *arr, FooArray replacement); | |
Foo *FooArrayPush(FooArray *arr); | |
void ForgotDealloc() { | |
FooArray arr = {}; | |
arr.count = 42; | |
} | |
void GoodDealloc() { | |
FooArray arr = {}; | |
arr.count = 42; | |
FooArrayDealloc(&arr); | |
} | |
void ForgotInitialize() { | |
FooArray arr; | |
arr.count = 42; | |
FooArrayDealloc(&arr); | |
} | |
void UseAfterDealloc() { | |
FooArray arr = {}; | |
FooArrayDealloc(&arr); | |
arr.count = 42; | |
} | |
void UseAfterDeallocThroughPtr() { | |
FooArray arr = {}; | |
FooArray *parr = &arr; | |
FooArrayDealloc(&arr); | |
parr->count = 42; | |
} | |
void SkipDeallocInIf() { | |
FooArray arr = {}; | |
if (0) { | |
FooArrayDealloc(&arr); | |
} | |
} | |
void SkipDeallocDueToReturn() { | |
FooArray arr = {}; | |
if (1) { | |
return; | |
} | |
FooArrayDealloc(&arr); | |
} | |
void ForgotClone() { | |
FooArray arr = {}; | |
FooArray arrCopy = arr; | |
arrCopy.count = 42; | |
FooArrayDealloc(&arr); | |
} | |
void GoodClone() { | |
FooArray arr = {}; | |
FooArray arrClone = FooArrayClone(&arr); // `TClone` should deep copy | |
arrClone.count = 42; | |
FooArrayDealloc(&arr); | |
FooArrayDealloc(&arrClone); | |
} | |
void ForgotReplace() { | |
FooArray arr = {}; | |
arr.count = 42; | |
arr = (FooArray) {}; // Forgot to dealloc the old value | |
FooArrayDealloc(&arr); | |
} | |
void GoodReplace() { | |
FooArray arr = {}; | |
arr.count = 42; | |
FooArrayReplace(&arr, (FooArray) {}); // `TReplace` should dealloc the old value, and then replace | |
FooArrayDealloc(&arr); | |
} | |
void UseAfterPush() { | |
FooArray arr = {}; | |
Foo *foo1 = FooArrayPush(&arr); | |
foo1->i = 1; | |
Foo *foo2 = FooArrayPush(&arr); // This could cause a `realloc` | |
foo2->i = foo1->i; // `foo1` could now refer to invalid memory after `realloc` | |
FooArrayDealloc(&arr); | |
} | |
#define FooArrayEach(v, arr) \ | |
Foo *v = FooArrayIterator(arr); \ | |
FooArrayIteratorNext(arr, &v); \ | |
(void)0 | |
Foo *FooArrayIterator(FooArray *arr); | |
_Bool FooArrayIteratorNext(FooArray *arr, Foo **v); | |
void PushWhileIterating() { | |
FooArray arr = {}; | |
for (FooArrayEach(foo, &arr)) { | |
FooArrayPush(&arr); | |
// The push above could cause a `realloc`, and the `foo` iteration ptr is now invalidated | |
} | |
FooArrayDealloc(&arr); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/Users/nikki/Development/cx/tests/demo.c:15:12: error: missed dealloc | |
FooArray arr = {}; | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:26:12: error: variable not initialized | |
FooArray arr; | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:34:3: error: use after dealloc | |
arr.count = 42; | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:40:20: error: this use conflicts with later use through a borrow | |
FooArrayDealloc(&arr); | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:41:3: error: this is the later use through a borrow | |
parr->count = 42; | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:47:5: error: dealloc call at inner scope, needs to be at same scope as variable declaration | |
FooArrayDealloc(&arr); | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:54:5: error: statement may jump over dealloc of heavy type | |
return; | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:61:22: error: copy of heavy type | |
FooArray arrCopy = arr; | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:61:12: error: missed dealloc | |
FooArray arrCopy = arr; | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:77:3: error: assignment to heavy type | |
arr = (FooArray) {}; // Forgot to dealloc the old value | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:92:29: error: this use conflicts with later use through a borrow | |
Foo *foo2 = FooArrayPush(&arr); // This could cause a `realloc` | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:93:13: error: this is the later use through a borrow | |
foo2->i = foo1->i; // `foo1` could now refer to invalid memory after `realloc` | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:107:19: error: this use conflicts with later use through a borrow | |
FooArrayPush(&arr); | |
^ | |
/Users/nikki/Development/cx/tests/demo.c:106:21: error: this is the later use through a borrow | |
for (FooArrayEach(foo, &arr)) { | |
^ | |
14 errors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <ctype.h> | |
#include <stdbool.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include "clang-c/CXCompilationDatabase.h" | |
#include "clang-c/Index.h" | |
// | |
// Utilities | |
// | |
typedef enum CXChildVisitResult CXChildVisitResult; | |
typedef enum CXCursorKind CXCursorKind; | |
typedef enum CXUnaryOperatorKind CXUnaryOperatorKind; | |
typedef enum CXBinaryOperatorKind CXBinaryOperatorKind; | |
bool spellingHasSuffix(CXString spelling, const char *suffix) { | |
const char *spellingCStr = clang_getCString(spelling); | |
int spellingLength = strlen(spellingCStr); | |
int suffixLength = strlen(suffix); | |
return spellingLength > suffixLength | |
&& !strncmp(spellingCStr + spellingLength - suffixLength, suffix, suffixLength); | |
} | |
void printIndent(int depth) { | |
for (int i = 0; i < depth; i++) { | |
printf(" │ "); | |
} | |
} | |
void printCursor(CXCursor cursor) { | |
{ | |
CXString cursorKindSpelling = clang_getCursorKindSpelling(clang_getCursorKind(cursor)); | |
printf("%s :: ", clang_getCString(cursorKindSpelling)); | |
clang_disposeString(cursorKindSpelling); | |
} | |
{ | |
CXString cursorSpelling = clang_getCursorSpelling(cursor); | |
printf("%s :: ", clang_getCString(cursorSpelling)); | |
clang_disposeString(cursorSpelling); | |
} | |
{ | |
CXString typeSpelling = clang_getTypeSpelling(clang_getCursorType(cursor)); | |
printf("%s", clang_getCString(typeSpelling)); | |
clang_disposeString(typeSpelling); | |
} | |
{ | |
CXCursor referenced = clang_getCursorReferenced(cursor); | |
if (!clang_Cursor_isNull(referenced)) { | |
printf(" :: ^"); | |
} | |
} | |
printf("\n"); | |
} | |
void printSourceCodeLine(CXTranslationUnit unit, CXFile file, int line, int column) { | |
const char *contents = clang_getFileContents(unit, file, NULL); | |
const char *lineStart = contents; | |
for (int currentLine = 1; currentLine < line && *lineStart != '\0'; lineStart++) { | |
if (*lineStart == '\n') { | |
currentLine++; | |
} | |
} | |
const char *lineEnd = lineStart; | |
while (*lineEnd != '\0' && *lineEnd != '\n') { | |
lineEnd++; | |
} | |
fprintf(stdout, "%.*s\n", (int)(lineEnd - lineStart), lineStart); | |
fprintf(stdout, "%*s^\n", column - 1, ""); | |
} | |
#define ARRAY_MAX_COUNT(array) sizeof(array) / sizeof(array[0]) | |
CXCursor errorCursors[500]; | |
int errorCount = 0; | |
#define ERROR(cursor, message, ...) \ | |
if (errorCount < ARRAY_MAX_COUNT(errorCursors)) { \ | |
bool found = false; \ | |
for (int i = errorCount - 1; i >= 0; i--) { \ | |
if (clang_equalCursors(errorCursors[i], cursor)) { \ | |
found = true; \ | |
break; \ | |
} \ | |
} \ | |
if (!found) { \ | |
errorCursors[errorCount++] = cursor; \ | |
CXSourceLocation location = clang_getCursorLocation(cursor); \ | |
CXFile file = NULL; \ | |
unsigned int line = 0; \ | |
unsigned int column = 0; \ | |
clang_getFileLocation(location, &file, &line, &column, NULL); \ | |
if (file != NULL) { \ | |
CXString path = clang_File_tryGetRealPathName(file); \ | |
const char *pathCStr = clang_getCString(path); \ | |
if (!(pathCStr != NULL && *pathCStr != '\0')) { \ | |
clang_disposeString(path); \ | |
path = clang_getFileName(file); \ | |
pathCStr = clang_getCString(path); \ | |
} \ | |
if (pathCStr != NULL && *pathCStr != '\0') { \ | |
fprintf(stdout, "%s:%u:%u: error: " message "\n", pathCStr, line, column, ##__VA_ARGS__); \ | |
printSourceCodeLine(clang_Cursor_getTranslationUnit(cursor), file, line, column); \ | |
} \ | |
clang_disposeString(path); \ | |
} \ | |
} \ | |
} | |
#define ERROR_FATAL(...) \ | |
ERROR(__VA_ARGS__); \ | |
exit(1); | |
// | |
// Visit | |
// | |
typedef struct { | |
CXType canonicalType; | |
bool heavy; | |
} Type; | |
typedef struct { | |
char text[128]; | |
} VariablePath; | |
typedef struct { | |
CXCursor referringExpr; | |
VariablePath path; | |
int variableSequenceId; | |
bool nextCall; | |
} VariableUse; | |
typedef struct { | |
bool active; | |
CXCursor target; | |
VariablePath path; | |
bool firstParameter; | |
bool reborrow; | |
CXCursor reborrowTarget; | |
} Borrow; | |
typedef struct { | |
CXCursor decl; | |
CXCursor scope; | |
bool deallocCalledDeclScope; | |
bool deallocCalledInnerScope; | |
Borrow borrow; | |
bool borrowErrored; | |
VariableUse uses[512]; | |
int useCount; | |
int sequenceId; | |
} Variable; | |
typedef struct { | |
CXCursor cursor; | |
int currentChildNum; | |
int currentParamNum; | |
int currentLoopIteration; | |
Borrow borrow; | |
bool borrowReturn; | |
bool nextCall; | |
bool deallocCall; | |
int variableCountBefore; | |
} Ancestor; | |
typedef struct { | |
Ancestor ancestors[128]; | |
int ancestorCount; | |
Type types[512]; | |
int typeCount; | |
Variable variables[64]; | |
int variableCount; | |
int variableSequenceId; | |
int uncheckedAncestorCount; | |
const char *mainFileName; | |
} VisitContext; | |
void pathAppend(VariablePath *path, const char *str, CXCursor cursor) { | |
while (*str != '\0' && *str == '.') { | |
str++; | |
} | |
if (*str == '\0') { | |
return; | |
} | |
int strLen = strlen(str); | |
int pathLen = strlen(path->text); | |
if (pathLen + 1 + strLen + 1 > ARRAY_MAX_COUNT(path->text)) { | |
ERROR_FATAL(cursor, "path too long!"); | |
} | |
path->text[pathLen] = '.'; | |
strcpy(&path->text[pathLen + 1], str); | |
} | |
bool pathHasPrefix(VariablePath *path, const char *prefix) { | |
int pathLen = strlen(path->text); | |
int prefixLen = strlen(prefix); | |
if (pathLen >= prefixLen) { | |
return !strncmp(path->text, prefix, prefixLen); | |
} | |
return false; | |
} | |
bool cursorIsHeavyType(CXCursor cursor, VisitContext *ctx) { | |
{ | |
CXString typeSpelling = clang_getTypeSpelling(clang_getCursorType(cursor)); | |
bool arraySuffix = spellingHasSuffix(typeSpelling, "Array"); // TODO: Generalize this | |
clang_disposeString(typeSpelling); | |
if (arraySuffix) { | |
return true; | |
} | |
} | |
CXType canonicalType = clang_getCanonicalType(clang_getCursorType(cursor)); | |
for (int typeI = 0; typeI < ctx->typeCount; typeI++) { | |
Type *type = &ctx->types[typeI]; | |
if (clang_equalTypes(type->canonicalType, canonicalType)) { | |
return type->heavy; | |
} | |
} | |
return false; | |
} | |
bool variableNeedsDealloc(Variable *variable, VisitContext *ctx) { | |
return cursorIsHeavyType(variable->decl, ctx) && clang_getCursorKind(variable->decl) != CXCursor_ParmDecl | |
&& !(variable->deallocCalledDeclScope || variable->deallocCalledInnerScope); | |
} | |
Variable *addVariable(CXCursor cursor, VisitContext *ctx) { | |
if (ctx->variableCount >= ARRAY_MAX_COUNT(ctx->variables)) { | |
ERROR_FATAL(cursor, "too many variables!"); | |
} | |
Variable *variable = &ctx->variables[ctx->variableCount++]; | |
variable->decl = cursor; | |
variable->sequenceId = ctx->variableSequenceId; | |
return variable; | |
} | |
Ancestor visitChildren(CXCursor cursor, VisitContext *ctx) { | |
if (ctx->ancestorCount >= ARRAY_MAX_COUNT(ctx->ancestors)) { | |
ERROR_FATAL(cursor, "too many ancestors!"); | |
} | |
Ancestor *ancestor = &ctx->ancestors[ctx->ancestorCount++]; | |
ancestor->cursor = cursor; | |
ancestor->variableCountBefore = ctx->variableCount; | |
int iterations = 1; | |
CXCursorKind cursorKind = clang_getCursorKind(cursor); | |
if (cursorKind == CXCursor_ForStmt) { | |
iterations = 3; | |
} | |
if (cursorKind == CXCursor_DoStmt || cursorKind == CXCursor_WhileStmt) { | |
iterations = 2; | |
} | |
for (ancestor->currentLoopIteration = 0; ancestor->currentLoopIteration < iterations; | |
ancestor->currentLoopIteration++) { | |
ancestor->currentChildNum = 0; | |
CXChildVisitResult visit(CXCursor cursor, CXCursor parentUnused, CXClientData clientData); | |
clang_visitChildren(cursor, visit, ctx); | |
} | |
ctx->ancestorCount--; | |
Ancestor result = ctx->ancestors[ctx->ancestorCount]; | |
ctx->ancestors[ctx->ancestorCount] = (Ancestor) {}; | |
return result; | |
} | |
CXChildVisitResult visit(CXCursor cursor, CXCursor parentUnused, CXClientData clientData) { | |
// Get context | |
VisitContext *ctx = clientData; | |
// Get kind | |
CXCursorKind cursorKind = clang_getCursorKind(cursor); | |
if (clang_isUnexposed(cursorKind)) { | |
return CXChildVisit_Recurse; | |
} | |
// Skip if not in main file | |
{ | |
CXSourceLocation location = clang_getCursorLocation(cursor); | |
CXFile file = NULL; | |
clang_getFileLocation(location, &file, NULL, NULL, NULL); | |
if (file == NULL) { | |
return CXChildVisit_Continue; | |
} | |
CXString filename = clang_getFileName(file); | |
bool skip = strcmp(clang_getCString(filename), ctx->mainFileName) != 0; | |
clang_disposeString(filename); | |
if (skip) { | |
return CXChildVisit_Continue; | |
} | |
} | |
// If unchecked -- pop back up to checked ancestor | |
if (ctx->uncheckedAncestorCount > 0) { | |
if (ctx->ancestorCount >= ctx->uncheckedAncestorCount) { | |
return CXChildVisit_Recurse; | |
} else { | |
ctx->uncheckedAncestorCount = 0; | |
} | |
} | |
// Increment child and parameter index in parent | |
if (ctx->ancestorCount >= 1) { | |
ctx->ancestors[ctx->ancestorCount - 1].currentChildNum++; | |
if (cursorKind == CXCursor_ParmDecl) { | |
ctx->ancestors[ctx->ancestorCount - 1].currentParamNum++; | |
} | |
} | |
// Skip for statement children based on iteration | |
if (ctx->ancestorCount >= 1 | |
&& clang_getCursorKind(ctx->ancestors[ctx->ancestorCount - 1].cursor) == CXCursor_ForStmt) { | |
int iteration = ctx->ancestors[ctx->ancestorCount - 1].currentLoopIteration; | |
int childNum = ctx->ancestors[ctx->ancestorCount - 1].currentChildNum; | |
if (iteration == 0 && childNum == 3) { | |
return CXChildVisit_Continue; | |
} | |
if (iteration == 1 && (childNum == 1 || childNum == 2 || childNum == 4)) { | |
return CXChildVisit_Continue; | |
} | |
if (iteration == 2 && (childNum == 1 || childNum == 3)) { | |
return CXChildVisit_Continue; | |
} | |
} | |
/* | |
if (ctx->ancestorCount == 0) { | |
printf("\n\n\n\n"); | |
} | |
printIndent(ctx->ancestorCount); | |
printCursor(cursor); | |
//*/ | |
// Disallow copies of heavy types | |
if (clang_isExpression(cursorKind) && cursorIsHeavyType(cursor, ctx)) { | |
bool allowed = false; | |
if (cursorKind == CXCursor_InitListExpr || cursorKind == CXCursor_CompoundLiteralExpr) { | |
allowed = true; // Temporary from literal | |
} | |
if (cursorKind == CXCursor_CallExpr) { | |
allowed = true; // Temporary from function call | |
} | |
if (cursorKind == CXCursor_ConditionalOperator) { | |
allowed = true; // Temporary from ternary operator | |
} | |
if (cursorKind == CXCursor_ParenExpr) { | |
allowed = true; // Temporary from parens | |
} | |
if (ctx->ancestorCount >= 1) { | |
CXCursor parent = ctx->ancestors[ctx->ancestorCount - 1].cursor; | |
if (clang_getCursorKind(parent) == CXCursor_CompoundStmt) { | |
allowed = true; // Value not used | |
if (clang_getCursorBinaryOperatorKind(cursor) != CXBinaryOperator_Assign) { | |
ERROR(cursor, "temporary heavy value is ignored, needs a dealloc call"); | |
} | |
// TODO: Handle GNU statement expressions? | |
} | |
if (clang_getCursorKind(parent) == CXCursor_MemberRefExpr) { | |
allowed = true; // Subscripting with a field | |
} | |
if (clang_getCursorUnaryOperatorKind(parent) == CXUnaryOperator_AddrOf) { | |
allowed = true; // Taking address | |
} | |
// TODO: Allow use in `sizeof(...)` etc.? | |
if (clang_getCursorBinaryOperatorKind(parent) == CXBinaryOperator_Assign | |
&& ctx->ancestors[ctx->ancestorCount - 1].currentChildNum == 1) { | |
allowed = true; // Assignment LHS -- handled as a separate case below | |
} | |
if (clang_getCursorBinaryOperatorKind(parent) == CXBinaryOperator_Comma) { | |
allowed = true; | |
ERROR(cursor, "heavy value not allowed in comma expression"); | |
} | |
} | |
if (!allowed) { | |
ERROR(cursor, "copy of heavy type"); | |
} | |
} | |
// Disallow copies of pointers | |
if (clang_isExpression(cursorKind) && clang_getCursorType(cursor).kind == CXType_Pointer) { | |
bool allowed = false; | |
if (clang_getCursorUnaryOperatorKind(cursor) == CXUnaryOperator_AddrOf) { | |
allowed = true; // Temporary from address of | |
} | |
if (cursorKind == CXCursor_CallExpr) { | |
allowed = true; // Temporary from function call | |
} | |
if (ctx->ancestorCount >= 1) { | |
CXCursor parent = ctx->ancestors[ctx->ancestorCount - 1].cursor; | |
if (clang_getCursorKind(parent) == CXCursor_CompoundStmt) { | |
allowed = true; // Value not used | |
// TODO: Handle GNU statement expressions? | |
} | |
if (clang_getCursorUnaryOperatorKind(parent) == CXUnaryOperator_Deref) { | |
allowed = true; // Dereferencing | |
} | |
if (clang_getCursorUnaryOperatorKind(parent) == CXUnaryOperator_AddrOf) { | |
allowed = true; // Taking address | |
} | |
if (clang_getCursorKind(parent) == CXCursor_MemberRefExpr) { | |
allowed = true; // Subscripting with a field | |
} | |
if (clang_getCursorKind(parent) == CXCursor_ArraySubscriptExpr) { | |
allowed = true; // Subscripting with index -- handled as a separate case below | |
} | |
if (clang_getCursorKind(parent) == CXCursor_CallExpr) { | |
allowed = true; // Function call | |
} | |
if (clang_getCursorKind(parent) == CXCursor_CStyleCastExpr) { | |
allowed = true; // Cast | |
} | |
if (clang_getCursorKind(parent) == CXCursor_ParenExpr) { | |
allowed = true; // Parens | |
} | |
CXBinaryOperatorKind binOpKind = clang_getCursorBinaryOperatorKind(parent); | |
if (binOpKind == CXBinaryOperator_EQ || binOpKind == CXBinaryOperator_NE) { | |
allowed = true; // Comparison | |
} | |
if (binOpKind == CXBinaryOperator_Assign && ctx->ancestors[ctx->ancestorCount - 1].currentChildNum == 1) { | |
allowed = true; // Assignment LHS -- handled as a separate case below | |
} | |
if (clang_getCursorKind(parent) == CXCursor_ReturnStmt) { | |
allowed = true; // Return -- handled through borrows | |
ctx->ancestors[ctx->ancestorCount - 1].borrowReturn = true; | |
} | |
} | |
if (!allowed) { | |
ERROR(cursor, "copy of pointer"); | |
} | |
} | |
// Disallow `return`, `break`, `continue` over dealloc of heavy types | |
if (cursorKind == CXCursor_ReturnStmt || cursorKind == CXCursor_BreakStmt | |
|| cursorKind == CXCursor_ContinueStmt) { | |
Ancestor *scope = NULL; | |
for (int ancestorI = ctx->ancestorCount - 1; ancestorI >= 0; ancestorI--) { | |
Ancestor *ancestor = &ctx->ancestors[ancestorI]; | |
CXCursorKind ancestorKind = clang_getCursorKind(ancestor->cursor); | |
if (cursorKind == CXCursor_BreakStmt | |
&& (ancestorKind == CXCursor_SwitchStmt || ancestorKind == CXCursor_DoStmt | |
|| ancestorKind == CXCursor_ForStmt || ancestorKind == CXCursor_WhileStmt)) { | |
scope = ancestor; | |
break; | |
} | |
if (cursorKind == CXCursor_ContinueStmt | |
&& (ancestorKind == CXCursor_DoStmt || ancestorKind == CXCursor_ForStmt | |
|| ancestorKind == CXCursor_WhileStmt)) { | |
scope = ancestor; | |
break; | |
} | |
} | |
for (int variableI = ctx->variableCount - 1; variableI >= 0; variableI--) { | |
if (scope != NULL && variableI < scope->variableCountBefore) { | |
break; | |
} | |
if (variableNeedsDealloc(&ctx->variables[variableI], ctx)) { | |
ERROR(cursor, "statement may jump over dealloc of heavy type"); | |
} | |
} | |
} | |
// Process kinds | |
switch (cursorKind) { | |
default: { | |
/*ERROR(cursor, "unsupported");*/ | |
visitChildren(cursor, ctx); | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_FieldDecl: { | |
// Heavy fields | |
if (cursorIsHeavyType(cursor, ctx)) { | |
if (ctx->ancestorCount >= 1) { | |
CXCursor structDecl = ctx->ancestors[ctx->ancestorCount - 1].cursor; | |
CXType structCanonicalType = clang_getCanonicalType(clang_getCursorType(structDecl)); | |
Type *type = NULL; | |
for (int typeI = 0; typeI < ctx->typeCount; typeI++) { | |
if (clang_equalTypes(ctx->types[typeI].canonicalType, structCanonicalType)) { | |
type = &ctx->types[typeI]; | |
break; | |
} | |
} | |
if (type == NULL) { | |
if (ctx->typeCount >= ARRAY_MAX_COUNT(ctx->types)) { | |
ERROR_FATAL(structDecl, "too many types!"); | |
} | |
type = &ctx->types[ctx->typeCount++]; | |
type->canonicalType = structCanonicalType; | |
} | |
type->heavy = true; | |
} | |
} | |
visitChildren(cursor, ctx); | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_CompoundStmt: | |
case CXCursor_ForStmt: | |
case CXCursor_FunctionDecl: { | |
Ancestor visitResult = visitChildren(cursor, ctx); | |
// Pop child variables | |
for (int variableI = visitResult.variableCountBefore; variableI < ctx->variableCount; variableI++) { | |
Variable *variable = &ctx->variables[variableI]; | |
if (variableNeedsDealloc(variable, ctx)) { | |
ERROR(variable->decl, "missed dealloc"); | |
} | |
*variable = (Variable) {}; | |
} | |
ctx->variableCount = visitResult.variableCountBefore; | |
// Make sure for statement has all parts | |
if (cursorKind == CXCursor_ForStmt && visitResult.currentChildNum != 4) { | |
ERROR(cursor, "for statement must have all four parts"); | |
} | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_ParmDecl: { | |
// Add variable | |
Variable *variable = addVariable(cursor, ctx); | |
if (ctx->ancestorCount >= 1) { | |
CXCursor scope = ctx->ancestors[ctx->ancestorCount - 1].cursor; | |
if (clang_getCursorKind(scope) == CXCursor_FunctionDecl) { | |
variable->scope = scope; | |
} else { | |
ERROR(cursor, "parameter declaration scope type not supported"); | |
} | |
} else { | |
ERROR(cursor, "parameter declaration without scope"); | |
} | |
// Set borrow if pointer | |
if (clang_getCursorType(cursor).kind == CXType_Pointer) { | |
variable->borrow.active = true; | |
variable->borrow.target = cursor; | |
if (ctx->ancestorCount >= 1 && ctx->ancestors[ctx->ancestorCount - 1].currentParamNum == 1) { | |
variable->borrow.firstParameter = true; | |
} | |
} | |
visitChildren(cursor, ctx); | |
ctx->variableSequenceId++; | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_VarDecl: { | |
// Recognize UNCHECKED specifier | |
bool isUnchecked = false; | |
{ | |
CXString spelling = clang_getCursorSpelling(cursor); | |
isUnchecked = !strcmp(clang_getCString(spelling), "UNCHECKED"); | |
clang_disposeString(spelling); | |
} | |
if (isUnchecked) { | |
ctx->uncheckedAncestorCount = ctx->ancestorCount - 1; | |
return CXChildVisit_Continue; | |
} | |
// Check initialization | |
if (clang_Cursor_isNull(clang_Cursor_getVarDeclInitializer(cursor))) { | |
ERROR(cursor, "variable not initialized"); | |
} | |
// Add variable | |
Variable *variable = addVariable(cursor, ctx); | |
if (ctx->ancestorCount >= 2) { | |
CXCursor scope = ctx->ancestors[ctx->ancestorCount - 2].cursor; | |
if (clang_getCursorKind(scope) == CXCursor_CompoundStmt || clang_getCursorKind(scope) == CXCursor_ForStmt) { | |
variable->scope = scope; | |
} else { | |
ERROR(cursor, "variable declaration scope type not supported"); | |
} | |
} else { | |
if (!clang_isConstQualifiedType(clang_getCanonicalType(clang_getCursorType(cursor)))) { | |
ERROR(cursor, "global variable must be `const`"); | |
} | |
} | |
visitChildren(cursor, ctx); | |
ctx->variableSequenceId++; | |
// Pointers should have borrow, non-pointers shouldn't | |
if (clang_getCursorType(cursor).kind == CXType_Pointer && !variable->borrow.active) { | |
ERROR(cursor, "couldn't compute borrow for variable"); | |
} | |
if (clang_getCursorType(cursor).kind != CXType_Pointer && variable->borrow.active) { | |
ERROR(cursor, "computed borrow for non-pointer variable"); | |
} | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_DeclRefExpr: { | |
// Look up referenced variable | |
CXCursor referenced = clang_getCursorReferenced(cursor); | |
CXCursorKind referencedKind = clang_getCursorKind(referenced); | |
if (referencedKind == CXCursor_ParmDecl || referencedKind == CXCursor_VarDecl) { | |
for (int referencedVariableI = ctx->variableCount - 1; referencedVariableI >= 0; referencedVariableI--) { | |
Variable *referencedVariable = &ctx->variables[referencedVariableI]; | |
if (clang_equalCursors(referencedVariable->decl, referenced)) { | |
if (referencedVariable->deallocCalledDeclScope) { | |
ERROR(cursor, "use after dealloc"); | |
} else { | |
// Save use | |
if (referencedVariable->useCount >= ARRAY_MAX_COUNT(referencedVariable->uses)) { | |
ERROR_FATAL(cursor, "too many uses!"); | |
} | |
VariableUse *use = &referencedVariable->uses[referencedVariable->useCount++]; | |
use->referringExpr = cursor; | |
use->variableSequenceId = ctx->variableSequenceId; | |
// Save path | |
for (int ancestorI = ctx->ancestorCount - 1; ancestorI >= 1; ancestorI--) { | |
CXCursor memberRefExpr = ctx->ancestors[ancestorI].cursor; | |
if (clang_getCursorKind(memberRefExpr) == CXCursor_MemberRefExpr) { | |
CXString memberSpelling = clang_getCursorSpelling(memberRefExpr); | |
pathAppend(&use->path, clang_getCString(memberSpelling), cursor); | |
clang_disposeString(memberSpelling); | |
} else { | |
break; | |
} | |
} | |
// Check for next call -- `FooNext(..., ...)` | |
for (int ancestorI = ctx->ancestorCount - 1; ancestorI >= 1; ancestorI--) { | |
CXCursor callExpr = ctx->ancestors[ancestorI].cursor; | |
if (clang_getCursorKind(callExpr) == CXCursor_CallExpr) { | |
bool isNextCall = false; | |
{ | |
CXString spelling = clang_getCursorSpelling(callExpr); | |
isNextCall = spellingHasSuffix(spelling, "Next"); | |
clang_disposeString(spelling); | |
} | |
if (isNextCall) { | |
if (!(clang_getCursorKind(ctx->ancestors[ancestorI - 1].cursor) == CXCursor_ForStmt | |
&& ctx->ancestors[ancestorI - 1].currentChildNum == 2)) { | |
ERROR(callExpr, "next call must be the condition of a `for (; ...;)` statement"); | |
} | |
use->nextCall = ctx->ancestors[ancestorI].currentChildNum == 2; | |
ctx->ancestors[ctx->ancestorCount - 1].nextCall = ctx->ancestors[ancestorI].currentChildNum == 3; | |
break; | |
} | |
} | |
} | |
// If use of variable that is itself a borrow, check for previous conflicting uses | |
if (referencedVariable->borrow.active && !use->nextCall && !referencedVariable->borrowErrored) { | |
VariablePath referencedBorrowPath = referencedVariable->borrow.path; | |
pathAppend(&referencedBorrowPath, use->path.text, cursor); | |
for (int otherVariableI = 0; otherVariableI < ctx->variableCount; otherVariableI++) { | |
Variable *otherVariable = &ctx->variables[otherVariableI]; | |
if (clang_equalCursors(otherVariable->decl, referencedVariable->decl)) { | |
continue; | |
} | |
if (otherVariable->borrow.reborrow | |
&& clang_equalCursors(referencedVariable->decl, otherVariable->borrow.reborrowTarget)) { | |
continue; | |
} | |
bool conflict = false; | |
for (int otherUseI = 0; otherUseI < otherVariable->useCount; otherUseI++) { | |
VariableUse *otherUse = &otherVariable->uses[otherUseI]; | |
if (otherUse->nextCall) { | |
continue; | |
} | |
if (otherUse->variableSequenceId <= referencedVariable->sequenceId) { | |
continue; | |
} | |
if (clang_equalCursors(otherVariable->decl, referencedVariable->borrow.target)) { | |
conflict = pathHasPrefix(&referencedBorrowPath, otherUse->path.text); | |
} else if (otherVariable->borrow.active | |
&& clang_equalCursors(otherVariable->borrow.target, referencedVariable->borrow.target)) { | |
VariablePath otherBorrowPath = otherVariable->borrow.path; | |
pathAppend(&otherBorrowPath, otherUse->path.text, cursor); | |
conflict = pathHasPrefix(&referencedBorrowPath, otherBorrowPath.text); | |
} | |
if (conflict) { | |
referencedVariable->borrowErrored = true; | |
ERROR(otherUse->referringExpr, "this use conflicts with later use through a borrow"); | |
ERROR(cursor, "this is the later use through a borrow"); | |
break; | |
} | |
} | |
if (conflict) { | |
break; | |
} | |
} | |
} | |
// Check for borrow | |
{ | |
Borrow borrow = { .active = false, .target = referenced }; | |
if (referencedVariable->borrow.active) { | |
// Start borrow if referenced variable is already a borrow | |
borrow = referencedVariable->borrow; | |
borrow.reborrow = true; | |
borrow.reborrowTarget = referencedVariable->decl; | |
} | |
for (int ancestorI = ctx->ancestorCount - 1; ancestorI >= 0; ancestorI--) { | |
Ancestor *ancestor = &ctx->ancestors[ancestorI]; | |
// Save borrow when variable initialization is reached | |
if (borrow.active && clang_getCursorKind(ancestor->cursor) == CXCursor_VarDecl) { | |
Variable *initializedVariable = &ctx->variables[ctx->variableCount - 1]; | |
if (clang_equalCursors(initializedVariable->decl, ancestor->cursor)) { | |
initializedVariable->borrow = borrow; | |
} | |
break; | |
} | |
// Validate return of borrow of first parameter | |
if (borrow.active && clang_getCursorKind(ancestor->cursor) == CXCursor_ReturnStmt) { | |
if (borrow.firstParameter) { | |
ancestor->borrow = borrow; | |
} | |
break; | |
} | |
// Start borrow at address-of | |
if (clang_getCursorUnaryOperatorKind(ancestor->cursor) == CXUnaryOperator_AddrOf) { | |
if (borrow.active && !ancestor->nextCall) { | |
ERROR(ancestor->cursor, "taking address of pointer not allowed"); | |
} else { | |
borrow.active = true; | |
ancestor->borrow = borrow; | |
} | |
continue; | |
} | |
// Pause borrow at deref | |
if (clang_getCursorUnaryOperatorKind(ancestor->cursor) == CXUnaryOperator_Deref) { | |
borrow.active = false; | |
continue; | |
} | |
CXCursorKind ancestorKind = clang_getCursorKind(ancestor->cursor); | |
if (ancestorKind == CXCursor_ParenExpr) { | |
continue; | |
} | |
if (ancestorKind == CXCursor_MemberRefExpr && ancestor->currentChildNum == 1) { | |
borrow.active = false; | |
CXString memberSpelling = clang_getCursorSpelling(ancestor->cursor); | |
pathAppend(&borrow.path, clang_getCString(memberSpelling), cursor); | |
clang_disposeString(memberSpelling); | |
continue; | |
} | |
if (borrow.active && ancestorKind == CXCursor_CallExpr && ancestor->currentChildNum == 2 | |
&& clang_getCursorType(ancestor->cursor).kind == CXType_Pointer) { | |
continue; // Function call returning a pointer borrows from first argument | |
} | |
break; | |
} | |
} | |
// Check for dealloc call -- `FooDealloc(&foo)` | |
if (ctx->ancestorCount >= 3) { | |
CXCursor addressOfExpr = ctx->ancestors[ctx->ancestorCount - 1].cursor; // `&...` | |
CXCursor callExpr = ctx->ancestors[ctx->ancestorCount - 2].cursor; // `FooDealloc(...)` | |
CXCursor scope = ctx->ancestors[ctx->ancestorCount - 3].cursor; // `{ ... }` | |
if (clang_getCursorUnaryOperatorKind(addressOfExpr) == CXUnaryOperator_AddrOf | |
&& clang_getCursorKind(callExpr) == CXCursor_CallExpr) { | |
bool isDeallocCall = false; | |
{ | |
CXString spelling = clang_getCursorSpelling(callExpr); | |
isDeallocCall = spellingHasSuffix(spelling, "Dealloc"); | |
clang_disposeString(spelling); | |
} | |
if (isDeallocCall) { | |
ctx->ancestors[ctx->ancestorCount - 2].deallocCall = true; | |
if (clang_getCursorKind(scope) != CXCursor_CompoundStmt) { | |
ERROR(callExpr, "dealloc call must to be in a `{ ... }` block"); | |
} | |
if (clang_equalCursors(referencedVariable->scope, scope)) { | |
referencedVariable->deallocCalledDeclScope = true; | |
} else { | |
referencedVariable->deallocCalledInnerScope = true; | |
if (clang_getCursorKind(referencedVariable->decl) != CXCursor_ParmDecl) { | |
ERROR(callExpr, | |
"dealloc call at inner scope, needs to be at same scope as variable declaration"); | |
} | |
} | |
} | |
} | |
} | |
} | |
break; | |
} | |
} | |
} | |
visitChildren(cursor, ctx); | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_BinaryOperator: { | |
if (clang_getCursorBinaryOperatorKind(cursor) == CXBinaryOperator_Assign) { | |
// Assignment of pointers not supported yet -- would affect borrow at LHS | |
// TODO: Disallow general pointer arithmetic? | |
if (clang_getCursorType(cursor).kind == CXType_Pointer) { | |
ERROR(cursor, "assignment to pointer"); | |
} | |
// Overwrite of heavy value | |
if (cursorIsHeavyType(cursor, ctx)) { | |
ERROR(cursor, "assignment to heavy type"); | |
} | |
} | |
visitChildren(cursor, ctx); | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_UnaryOperator: { | |
Ancestor visitResult = visitChildren(cursor, ctx); | |
if (clang_getCursorUnaryOperatorKind(cursor) == CXUnaryOperator_AddrOf) { | |
if (!visitResult.borrow.active) { | |
ERROR(cursor, "this borrow is not supported"); | |
} | |
} | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_ReturnStmt: { | |
Ancestor visitResult = visitChildren(cursor, ctx); | |
if (visitResult.borrowReturn && !(visitResult.borrow.active && visitResult.borrow.firstParameter)) { | |
ERROR(cursor, "must return borrow of first parameter"); | |
} | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_GotoStmt: | |
case CXCursor_IndirectGotoStmt: { | |
ERROR(cursor, "goto not allowed"); | |
visitChildren(cursor, ctx); | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_ArraySubscriptExpr: { | |
ERROR(cursor, "array subscript not allowed"); | |
visitChildren(cursor, ctx); | |
return CXChildVisit_Continue; | |
} | |
case CXCursor_CallExpr: { | |
Ancestor visitResult = visitChildren(cursor, ctx); | |
bool isDeallocCall = false; | |
{ | |
CXString spelling = clang_getCursorSpelling(cursor); | |
isDeallocCall = spellingHasSuffix(spelling, "Dealloc"); | |
clang_disposeString(spelling); | |
} | |
if (isDeallocCall && !visitResult.deallocCall) { | |
ERROR(cursor, "dealloc call must have the form `TDealloc(&var)` where `var` is a local variable"); | |
} | |
return CXChildVisit_Continue; | |
} | |
} | |
} | |
// | |
// Main | |
// | |
const char *__lsan_default_suppressions(void) { | |
return "leak:_fetchInitializingClassList\nleak:clang_parseTranslationUnit"; | |
} | |
int main(int argc, const char **argv) { | |
if (argc != 2) { | |
fprintf(stderr, "Usage: %s <source-file>\n", argv[0]); | |
return 1; | |
} | |
CXIndex index = clang_createIndex(0, 0); | |
CXTranslationUnit unit = NULL; | |
const char *sourceFile = argv[1]; | |
// First try to load compilation database | |
CXCompilationDatabase_Error error; | |
CXCompilationDatabase compilationDb = clang_CompilationDatabase_fromDirectory(".", &error); | |
if (error == CXCompilationDatabase_NoError && compilationDb != NULL) { | |
// Found compilation database, try to get commands for our file | |
CXCompileCommands commands = clang_CompilationDatabase_getCompileCommands(compilationDb, sourceFile); | |
if (commands != NULL) { | |
// Get the first (and should be only) command for this file | |
int numCommands = clang_CompileCommands_getSize(commands); | |
if (numCommands > 0) { | |
CXCompileCommand command = clang_CompileCommands_getCommand(commands, 0); | |
// Get working directory and all arguments | |
CXString workingDir = clang_CompileCommand_getDirectory(command); | |
int numArgs = clang_CompileCommand_getNumArgs(command); | |
// Skip compiler executable (arg 0) and output file args | |
const char **commandLine = malloc(sizeof(char *) * numArgs); | |
int actualArgs = 0; | |
for (int i = 1; i < numArgs; i++) { | |
CXString arg = clang_CompileCommand_getArg(command, i); | |
const char *argStr = clang_getCString(arg); | |
if (!strcmp(argStr, "-c")) { | |
break; | |
} | |
commandLine[actualArgs++] = strdup(argStr); | |
clang_disposeString(arg); | |
} | |
// Parse with compilation database arguments | |
unit = clang_parseTranslationUnit( | |
index, sourceFile, commandLine, actualArgs, NULL, 0, CXTranslationUnit_None); | |
// Clean up | |
for (int i = 0; i < actualArgs; i++) { | |
free((void *)commandLine[i]); | |
} | |
free(commandLine); | |
clang_disposeString(workingDir); | |
} | |
clang_CompileCommands_dispose(commands); | |
} | |
clang_CompilationDatabase_dispose(compilationDb); | |
} | |
// Fall back to default parse if compilation database parse failed | |
if (unit == NULL) { | |
unit = clang_parseTranslationUnit(index, sourceFile, NULL, 0, NULL, 0, CXTranslationUnit_None); | |
} | |
// Visit | |
if (unit != NULL) { | |
static VisitContext ctx = {}; | |
ctx.mainFileName = sourceFile; | |
clang_visitChildren(clang_getTranslationUnitCursor(unit), visit, &ctx); | |
if (errorCount > 0) { | |
printf("%d errors\n", errorCount); | |
} | |
} | |
clang_disposeTranslationUnit(unit); | |
clang_disposeIndex(index); | |
return errorCount > 0 ? 1 : 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment