Skip to content

Commit ca9a08c

Browse files
Fix incorrect rewritting of function with typdefed type (fix #430) (#436)
Fixes a error in rewriting for function declaration such as typedef void foo(int*); foo bar; void bar(int *a){}; Previously `FunctionDeclBuilder` only replaced the parameter declaration of `bar` because nothing in the return type needed to be rewritten. This caused the rewriting of the first declaration to be missing the return type. With this change, `FunctionDeclBuilder` detects that `bar` is declared using a typedef type, and generates replacement text for the entire declaration instead of only the parameter list. This change does not implement proper constraint generation for these function, so the typedef foo is not rewritten. Reconstruct a parameter declaration if we can't get its original source due to a macro. Co-authored-by: Matt McCutchen (Correct Computation) <[email protected]>
1 parent e101e89 commit ca9a08c

File tree

8 files changed

+177
-26
lines changed

8 files changed

+177
-26
lines changed

clang/include/clang/3C/DeclRewriter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ class FunctionDeclBuilder : public RecursiveASTVisitor<FunctionDeclBuilder> {
122122
void buildItypeDecl(PVConstraint *Defn, DeclaratorDecl *Decl,
123123
std::string &Type, std::string &IType, bool &RewriteParm,
124124
bool &RewriteRet);
125+
126+
bool hasDeclWithTypedef(const FunctionDecl *FD);
125127
};
126128

127129
class FieldFinder : public RecursiveASTVisitor<FieldFinder> {

clang/include/clang/3C/Utils.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ bool isVarArgType(const std::string &TypeName);
142142
bool isStructOrUnionType(clang::VarDecl *VD);
143143

144144
// Helper method to print a Type in a way that can be represented in the source.
145-
std::string tyToStr(const clang::Type *T);
145+
// If Name is given, it is included as the variable name (which otherwise isn't
146+
// trivial to do with function pointers, etc.).
147+
std::string tyToStr(const clang::Type *T, const std::string &Name = "");
148+
149+
// Same as tyToStr with a QualType.
150+
std::string qtyToStr(clang::QualType QT, const std::string &Name = "");
146151

147152
// Get the end source location of the end of the provided function.
148153
clang::SourceLocation getFunctionDeclRParen(clang::FunctionDecl *FD,

clang/lib/3C/DeclRewriter.cpp

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -539,13 +539,13 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) {
539539
if (!Defnc->hasBody())
540540
return true;
541541

542-
// DidAnyParams tracks if we have made any changes to the parameters for this
543-
// declarations. If no changes are made, then there is no need to rewrite the
544-
// parameter declarations. This will also be set to true if an itype is added
545-
// to the return, since return itypes are inserted afters params.
542+
// RewriteParams and RewriteReturn track if we will need to rewrite the
543+
// parameter and return type declarations on this function. They are first
544+
// set to true if any changes are made to the types of the parameter and
545+
// return. If a type has changed, then it must be rewritten. There are then
546+
// some special circumstances which require rewriting the parameter or return
547+
// even when the type as not changed.
546548
bool RewriteParams = false;
547-
// Does the same job as RewriteParams, but with respect to the return value.
548-
// If the return does not change, there is no need to rewrite it.
549549
bool RewriteReturn = false;
550550

551551
// Get rewritten parameter variable declarations
@@ -574,12 +574,27 @@ bool FunctionDeclBuilder::VisitFunctionDecl(FunctionDecl *FD) {
574574
ReturnVar, ItypeStr, RewriteParams, RewriteReturn);
575575

576576
// If the return is a function pointer, we need to rewrite the whole
577-
// declaration even if no actual changes were made to the parameters. It could
578-
// probably be done better, but getting the correct source locations is
579-
// painful.
577+
// declaration even if no actual changes were made to the parameters because
578+
// the parameter for the function pointer type appear later in the source than
579+
// the parameters for the function declaration. It could probably be done
580+
// better, but getting the correct source locations is painful.
580581
if (FD->getReturnType()->isFunctionPointerType() && RewriteReturn)
581582
RewriteParams = true;
582583

584+
// If the function is declared using a typedef for the function type, then we
585+
// need to rewrite parameters and the return if either would have been
586+
// rewritten. What this does is expand the typedef to the full function type
587+
// to avoid the problem of rewriting inside the typedef.
588+
// FIXME: If issue #437 is fixed in way that preserves typedefs on function
589+
// declarations, then this conditional should be removed to enable
590+
// separate rewriting of return type and parameters on the
591+
// corresponding definition.
592+
// https://github.com/correctcomputation/checkedc-clang/issues/437
593+
if ((RewriteReturn || RewriteParams) && hasDeclWithTypedef(FD)) {
594+
RewriteParams = true;
595+
RewriteReturn = true;
596+
}
597+
583598
// Combine parameter and return variables rewritings into a single rewriting
584599
// for the entire function declaration.
585600
std::string NewSig = "";
@@ -647,6 +662,10 @@ void FunctionDeclBuilder::buildItypeDecl(PVConstraint *Defn,
647662
return;
648663
}
649664

665+
// Note: For a parameter, Type + IType will give the full declaration (including
666+
// the name) but the breakdown between Type and IType is not guaranteed. For a
667+
// return, Type will be what goes before the name and IType will be what goes
668+
// after the parentheses.
650669
void
651670
FunctionDeclBuilder::buildDeclVar(PVConstraint *IntCV, PVConstraint *ExtCV,
652671
DeclaratorDecl *Decl, std::string &Type,
@@ -668,16 +687,34 @@ FunctionDeclBuilder::buildDeclVar(PVConstraint *IntCV, PVConstraint *ExtCV,
668687
buildItypeDecl(ExtCV, Decl, Type, IType, RewriteParm, RewriteRet);
669688
return;
670689
}
671-
// Variables that do not need to be rewritten fall through to here. Type
672-
// strings are taken unchanged from the original source.
673-
if (isa<ParmVarDecl>(Decl)) {
674-
Type = getSourceText(Decl->getSourceRange(), *Context);
675-
IType = "";
690+
// Variables that do not need to be rewritten fall through to here.
691+
// For parameter variables, we try to extract the declaration from the source
692+
// code. This preserves macros and other formatting. This isn't possible for
693+
// return variables because the itype on returns is located after the
694+
// parameter list. Sometimes we cannot get the original source for a parameter
695+
// declaration, for example if a function prototype is declared using a
696+
// typedef or the parameter declaration is inside a macro. For these cases, we
697+
// just fall back to reconstructing the declaration from the PVConstraint.
698+
ParmVarDecl *PVD = dyn_cast<ParmVarDecl>(Decl);
699+
if (PVD) {
700+
SourceRange Range = PVD->getSourceRange();
701+
if (Range.isValid()) {
702+
Type = getSourceText(Range, *Context);
703+
if (!Type.empty()) {
704+
// Great, we got the original source including any itype and bounds.
705+
IType = "";
706+
return;
707+
}
708+
}
709+
// Otherwise, reconstruct the name and type, and reuse the code below for
710+
// the itype and bounds.
711+
// TODO: Do we care about `register` or anything else this doesn't handle?
712+
Type = qtyToStr(PVD->getOriginalType(), PVD->getNameAsString());
676713
} else {
677714
Type = ExtCV->getOriginalTy() + " ";
678-
IType = getExistingIType(ExtCV);
679-
IType += ABRewriter.getBoundsString(ExtCV, Decl, !IType.empty());
680715
}
716+
IType = getExistingIType(ExtCV);
717+
IType += ABRewriter.getBoundsString(ExtCV, Decl, !IType.empty());
681718
}
682719

683720
std::string FunctionDeclBuilder::getExistingIType(ConstraintVariable *DeclC) {
@@ -692,6 +729,37 @@ bool FunctionDeclBuilder::isFunctionVisited(std::string FuncName) {
692729
return VisitedSet.find(FuncName) != VisitedSet.end();
693730
}
694731

732+
// Given a function declaration figure out if this declaration or any other
733+
// declaration of the same function is declared using a typedefed function type.
734+
bool FunctionDeclBuilder::hasDeclWithTypedef(const FunctionDecl *FD) {
735+
for (FunctionDecl *FDIter : FD->redecls()) {
736+
// If the declaration type is TypedefType, then this is definitely declared
737+
// using a typedef. This only happens when the typedefed declaration is the
738+
// first declaration of a function.
739+
if (isa_and_nonnull<TypedefType>(FDIter->getType().getTypePtrOrNull()))
740+
return true;
741+
// Next look for a TypeDefTypeLoc. This is present on the typedefed
742+
// declaration even when it is not the first declaration.
743+
TypeSourceInfo *TSI = FDIter->getTypeSourceInfo();
744+
if (TSI) {
745+
if (!TSI->getTypeLoc().getAs<TypedefTypeLoc>().isNull())
746+
return true;
747+
} else {
748+
// This still could possibly be a typedef type if TSI was NULL.
749+
// TypeSourceInfo is null for implicit function declarations, so if a
750+
// implicit declaration uses a typedef, it will be missed. That's fine
751+
// since an implicit declaration can't be rewritten anyways.
752+
// There might be other ways it can be null that I'm not aware of.
753+
if (Verbose) {
754+
llvm::errs() << "Unable to conclusively determine if a function "
755+
<< "declaration uses a typedef.\n";
756+
FDIter->dump();
757+
}
758+
}
759+
}
760+
return false;
761+
}
762+
695763
bool FieldFinder::VisitFieldDecl(FieldDecl *FD) {
696764
GVG.addGlobalDecl(FD);
697765
return true;

clang/lib/3C/Utils.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,14 @@ bool isStructOrUnionType(clang::VarDecl *VD) {
224224
VD->getType().getTypePtr()->isUnionType();
225225
}
226226

227-
std::string tyToStr(const clang::Type *T) {
228-
if (auto TDT = dyn_cast<TypedefType>(T)) {
229-
auto D = TDT->getDecl();
230-
std::string s = D->getNameAsString();
231-
return s;
232-
} else {
233-
QualType QT(T, 0);
234-
return QT.getAsString();
235-
}
227+
std::string qtyToStr(clang::QualType QT, const std::string &Name) {
228+
std::string S = Name;
229+
QT.getAsStringInternal(S, LangOptions());
230+
return S;
231+
}
232+
233+
std::string tyToStr(const clang::Type *T, const std::string &Name) {
234+
return qtyToStr(QualType(T, 0), Name);
236235
}
237236

238237
Expr *removeAuxillaryCasts(Expr *E) {

clang/test/3C/function_typedef.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: rm -rf %t*
2+
// RUN: 3c -base-dir=%S -alltypes -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_ALL","CHECK" %s
3+
// RUN: 3c -base-dir=%S -addcr %s -- | FileCheck -match-full-lines -check-prefixes="CHECK_NOALL","CHECK" %s
4+
// RUN: 3c -base-dir=%S -addcr %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null -
5+
// RUN: 3c -base-dir=%S -output-dir=%t.checked -alltypes %s --
6+
// RUN: 3c -base-dir=%t.checked -alltypes %t.checked/function_typedef.c -- | diff %t.checked/function_typedef.c -
7+
8+
// Tests for the single file case in issue #430
9+
// Functions declared using a typedef should be rewritten in a way that doesn't
10+
// crash 3C or generate uncompilable code. The expected output for these tests
11+
// is expected to change when issue #437 is resolved.
12+
13+
typedef void foo(int*);
14+
foo foo_impl;
15+
void foo_imp(int *a) {};
16+
//CHECK: foo foo_impl;
17+
//CHECK: void foo_imp(_Ptr<int> a) _Checked {};
18+
19+
typedef int *bar();
20+
bar bar_impl;
21+
int *bar_impl() {
22+
return 0;
23+
};
24+
//CHECK: _Ptr<int> bar_impl(void);
25+
//CHECK: _Ptr<int> bar_impl(void) _Checked {
26+
//CHECK: return 0;
27+
//CHECK: };
28+
29+
typedef int *baz(int *);
30+
baz baz_impl;
31+
int *baz_impl(int *a) {
32+
return 0;
33+
};
34+
//CHECK: _Ptr<int> baz_impl(_Ptr<int> a);
35+
//CHECK: _Ptr<int> baz_impl(_Ptr<int> a) _Checked {
36+
//CHECK: return 0;
37+
//CHECK: };
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: rm -rf %t*
2+
// RUN: 3c -base-dir=%S -addcr -output-dir=%t.checked %S/function_typedef_multi.c %S/function_typedef_multi.h --
3+
// RUN: test ! -f %t.checked/function_typedef_multi.h -a ! -f %t.checked/function_typedef_multi.c
4+
5+
// Test for the two file case in issue #430
6+
// This test caused an assertion to fail prior to PR #436
7+
// This test uses function_typedef_multi.h. The header is deliberately not
8+
// included in this file. Including it prevented the assertion fail even
9+
// without the changes in PR #436.
10+
11+
int foo(int a, int b[1]) {
12+
return 0;
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Used with function_typedef_multi.c
2+
typedef int a(int, int[1]);
3+
a foo;

clang/test/3C/params_in_macro.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Test that if FunctionDeclBuilder::buildDeclVar cannot get the original source
2+
// for a parameter declaration due to a macro, it reconstructs the declaration
3+
// (including the name) instead of leaving a blank.
4+
5+
// RUN: 3c -base-dir=%S %s -- | FileCheck -match-full-lines %s
6+
// RUN: 3c -base-dir=%S %s -- | %clang -c -fcheckedc-extension -x c -o /dev/null -
7+
8+
// 3C is not idempotent on this file because the first pass constrains the
9+
// pointers in the macros to wild but then inlines the macros, so the second
10+
// pass will make those pointers checked. So don't try to test idempotence.
11+
12+
typedef double mydouble;
13+
14+
// TODO: FunctionDeclBuilder::buildDeclVar should be able to handle an itype
15+
// here, but currently the PointerConstraintVariable constructor asserts when it
16+
// fails to retrieve the original source of the itype declaration.
17+
#define parms1 volatile mydouble d, void (*f)(void)
18+
#define parms2 int *const y : count(7), _Ptr<char> z
19+
20+
void test(parms1, int *x, parms2) {}
21+
// CHECK: void test(volatile mydouble d, void (*f)(void), _Ptr<int> x, int *const y : count(7), _Ptr<char> z) {}
22+
23+
// Before the bug fix, we got:
24+
// void test(, , _Ptr<int> x, , ) {}

0 commit comments

Comments
 (0)