Skip to content

Instantly share code, notes, and snippets.

@nikki93
Last active November 19, 2024 15:47
Show Gist options
  • Save nikki93/2bb11237bf76fceb0bf687d6d9eef1b3 to your computer and use it in GitHub Desktop.
Save nikki93/2bb11237bf76fceb0bf687d6d9eef1b3 to your computer and use it in GitHub Desktop.
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);
}
/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
#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;
}
}
// Print
/*
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