This patch makes clang add the returns_twice attribute to functions that
are known to have it.
The list of functions is taken from llvm's callsFunctionThatReturnsTwice
plus the __sigsetjmp as reported Khaled.
The hope is that we can simplify callsFunctionThatReturnsTwice to check
only for the attribute.
Is the patch OK?
Would a patch dropping the list from callsFunctionThatReturnsTwice be OK
for 3.0 or 3.1?
Thanks,
Rafael
diff --git a/include/clang/AST/Type.h b/include/clang/AST/Type.h
index 73bf981..20dd90e 100644
--- a/include/clang/AST/Type.h
+++ b/include/clang/AST/Type.h
@@ -1150,7 +1150,7 @@ protected:
/// Extra information which affects how the function is called, like
/// regparm and the calling convention.
- unsigned ExtInfo : 8;
+ unsigned ExtInfo : 9;
/// Whether the function is variadic. Only used by FunctionProtoType.
unsigned Variadic : 1;
@@ -2503,33 +2503,32 @@ class FunctionType : public Type {
/// a FunctionType, although FunctionType does currently use the
/// same bit-pattern.
///
- // If you add a field (say Foo), other than the obvious places (both,
- // constructors, compile failures), what you need to update is
- // * Operator==
+ // If you add a field (say Foo), other than the obvious places (
+ // constructor, compile failures), what you need to update is
// * getFoo
// * withFoo
- // * functionType. Add Foo, getFoo.
- // * ASTContext::getFooType
+ // * functionType. Add getFoo.
// * ASTContext::mergeFunctionTypes
- // * FunctionNoProtoType::Profile
- // * FunctionProtoType::Profile
- // * TypePrinter::PrintFunctionProto
+ // * CGFunctionInfo::Profile
+ // * TypePrinter::printFunctionProto
// * AST read and write
// * Codegen
class ExtInfo {
- // Feel free to rearrange or add bits, but if you go over 8,
+ // Feel free to rearrange or add bits, but if you go over 9,
// you'll need to adjust both the Bits field below and
// Type::FunctionTypeBitfields.
- // | CC |noreturn|produces|regparm|
- // |0 .. 2| 3 | 4 | 5 .. 7|
+ // | CC |noreturn|produces|returns_twice|regparm|
+ // |0 .. 2| 3 | 4 | 5 | 6.. 8 |
//
// regparm is either 0 (no regparm attribute) or the regparm value+1.
enum { CallConvMask = 0x7 };
enum { NoReturnMask = 0x8 };
enum { ProducesResultMask = 0x10 };
- enum { RegParmMask = ~(CallConvMask | NoReturnMask | ProducesResultMask),
- RegParmOffset = 5 }; // Assumed to be the last field
+ enum { ReturnsTwiceMask = 0x20 };
+ enum { RegParmMask = ~(CallConvMask | NoReturnMask | ProducesResultMask |
+ ReturnsTwiceMask),
+ RegParmOffset = 6 }; // Assumed to be the last field
uint16_t Bits;
@@ -2541,10 +2540,11 @@ class FunctionType : public Type {
// Constructor with no defaults. Use this when you know that you
// have all the elements (when reading an AST file for example).
ExtInfo(bool noReturn, bool hasRegParm, unsigned regParm, CallingConv cc,
- bool producesResult) {
+ bool producesResult, bool returnsTwice) {
assert((!hasRegParm || regParm < 7) && "Invalid regparm value");
Bits = ((unsigned) cc) |
(noReturn ? NoReturnMask : 0) |
+ (returnsTwice ? ReturnsTwiceMask : 0) |
(producesResult ? ProducesResultMask : 0) |
(hasRegParm ? ((regParm + 1) << RegParmOffset) : 0);
}
@@ -2553,6 +2553,7 @@ class FunctionType : public Type {
// function know to use defaults.
ExtInfo() : Bits(0) {}
+ bool getReturnsTwice() const { return Bits & ReturnsTwiceMask; }
bool getNoReturn() const { return Bits & NoReturnMask; }
bool getProducesResult() const { return Bits & ProducesResultMask; }
bool getHasRegParm() const { return (Bits >> RegParmOffset) != 0; }
@@ -2574,6 +2575,13 @@ class FunctionType : public Type {
// Note that we don't have setters. That is by design, use
// the following with methods instead of mutating these objects.
+ ExtInfo withReturnsTwice(bool returnsTwice) const {
+ if (returnsTwice)
+ return ExtInfo(Bits | ReturnsTwiceMask);
+ else
+ return ExtInfo(Bits & ~ReturnsTwiceMask);
+ }
+
ExtInfo withNoReturn(bool noReturn) const {
if (noReturn)
return ExtInfo(Bits | NoReturnMask);
@@ -2632,6 +2640,7 @@ public:
bool getHasRegParm() const { return getExtInfo().getHasRegParm(); }
unsigned getRegParmType() const { return getExtInfo().getRegParm(); }
bool getNoReturnAttr() const { return getExtInfo().getNoReturn(); }
+ bool getReturnsTwiceAttr() const { return getExtInfo().getReturnsTwice(); }
CallingConv getCallConv() const { return getExtInfo().getCC(); }
ExtInfo getExtInfo() const { return ExtInfo(FunctionTypeBits.ExtInfo); }
@@ -3209,6 +3218,7 @@ public:
// No operand.
attr_noreturn,
+ attr_returns_twice,
attr_cdecl,
attr_fastcall,
attr_stdcall,
diff --git a/include/clang/Basic/Builtins.def b/include/clang/Basic/Builtins.def
index 60bcde3..d3a5eaf 100644
--- a/include/clang/Basic/Builtins.def
+++ b/include/clang/Basic/Builtins.def
@@ -77,6 +77,7 @@
// in that it accepts its arguments as a va_list rather than
// through an ellipsis
// e -> const, but only when -fmath-errno=0
+// j -> returns_twice (like setjmp)
// FIXME: gcc has nonnull
#if defined(BUILTIN) && !defined(LIBBUILTIN)
@@ -427,7 +428,7 @@ BUILTIN(__builtin_return_address, "v*IUi", "n")
BUILTIN(__builtin_extract_return_addr, "v*v*", "n")
BUILTIN(__builtin_frame_address, "v*IUi", "n")
BUILTIN(__builtin_flt_rounds, "i", "nc")
-BUILTIN(__builtin_setjmp, "iv**", "")
+BUILTIN(__builtin_setjmp, "iv**", "j")
BUILTIN(__builtin_longjmp, "vv**i", "r")
BUILTIN(__builtin_unwind_init, "v", "")
BUILTIN(__builtin_eh_return_data_regno, "iIi", "nc")
@@ -672,6 +673,17 @@ LIBBUILTIN(bzero, "vv*z", "f", "strings.h",
ALL_LANGUAGES)
// POSIX unistd.h
LIBBUILTIN(_exit, "vi", "fr", "unistd.h", ALL_LANGUAGES)
// POSIX setjmp.h
+
+LIBBUILTIN(_setjmp, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+LIBBUILTIN(__sigsetjmp, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+LIBBUILTIN(setjmp, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+LIBBUILTIN(sigsetjmp, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+LIBBUILTIN(setjmp_syscall, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+LIBBUILTIN(savectx, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+LIBBUILTIN(qsetjmp, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+LIBBUILTIN(vfork, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+LIBBUILTIN(getcontext, "iJ", "fj", "setjmp.h", ALL_LANGUAGES)
+
LIBBUILTIN(_longjmp, "vJi", "fr", "setjmp.h", ALL_LANGUAGES)
LIBBUILTIN(siglongjmp, "vSJi", "fr", "setjmp.h", ALL_LANGUAGES)
// non-standard but very common
diff --git a/include/clang/Basic/Builtins.h b/include/clang/Basic/Builtins.h
index fbf4ef4..5afa020 100644
--- a/include/clang/Basic/Builtins.h
+++ b/include/clang/Basic/Builtins.h
@@ -103,6 +103,11 @@ public:
return strchr(GetRecord(ID).Attributes, 'r') != 0;
}
+ /// isReturnsTwice - Return true if we know this builtin can return twice.
+ bool isReturnsTwice(unsigned ID) const {
+ return strchr(GetRecord(ID).Attributes, 'j') != 0;
+ }
+
/// isLibFunction - Return true if this is a builtin for a libc/libm
function,
/// with a "__builtin_" prefix (e.g. __builtin_abs).
bool isLibFunction(unsigned ID) const {
diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h
index 9bb4022..670245f 100644
--- a/include/clang/Sema/Sema.h
+++ b/include/clang/Sema/Sema.h
@@ -1843,6 +1843,7 @@ public:
bool CheckRegparmAttr(const AttributeList &attr, unsigned &value);
bool CheckCallingConvAttr(const AttributeList &attr, CallingConv &CC);
bool CheckNoReturnAttr(const AttributeList &attr);
+ bool CheckReturnsTwiceAttr(const AttributeList &attr);
void WarnUndefinedMethod(SourceLocation ImpLoc, ObjCMethodDecl *method,
bool &IncompleteImpl, unsigned DiagID);
diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp
index b625655..83e945c 100644
--- a/lib/AST/ASTContext.cpp
+++ b/lib/AST/ASTContext.cpp
@@ -5586,8 +5586,11 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs,
QualType rhs,
allRTypes = false;
// FIXME: some uses, e.g. conditional exprs, really want this to be 'both'.
bool NoReturn = lbaseInfo.getNoReturn() || rbaseInfo.getNoReturn();
+ bool ReturnsTwice = lbaseInfo.getReturnsTwice() ||
+ rbaseInfo.getReturnsTwice();
- FunctionType::ExtInfo einfo = lbaseInfo.withNoReturn(NoReturn);
+ FunctionType::ExtInfo einfo = lbaseInfo.withNoReturn(NoReturn)
+ .withReturnsTwice(ReturnsTwice);
if (lproto && rproto) { // two C99 style function prototypes
assert(!lproto->hasExceptionSpec() && !rproto->hasExceptionSpec() &&
@@ -6349,7 +6352,11 @@ QualType ASTContext::GetBuiltinType(unsigned Id,
"'.' should only occur at end of builtin type list!");
FunctionType::ExtInfo EI;
- if (BuiltinInfo.isNoReturn(Id)) EI = EI.withNoReturn(true);
+ if (BuiltinInfo.isNoReturn(Id))
+ EI = EI.withNoReturn(true);
+
+ if (BuiltinInfo.isReturnsTwice(Id))
+ EI = EI.withReturnsTwice(true);
bool Variadic = (TypeStr[0] == '.');
diff --git a/lib/AST/DeclPrinter.cpp b/lib/AST/DeclPrinter.cpp
index 73f5cdb..4f3e8ad 100644
--- a/lib/AST/DeclPrinter.cpp
+++ b/lib/AST/DeclPrinter.cpp
@@ -468,6 +468,10 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) {
if (D->hasAttr<NoReturnAttr>())
Proto += " __attribute((noreturn))";
+
+ if (D->hasAttr<ReturnsTwiceAttr>())
+ Proto += " __attribute((returns_twice))";
+
if (CXXConstructorDecl *CDecl = dyn_cast<CXXConstructorDecl>(D)) {
bool HasInitializerList = false;
for (CXXConstructorDecl::init_const_iterator B = CDecl->init_begin(),
diff --git a/lib/AST/TypePrinter.cpp b/lib/AST/TypePrinter.cpp
index fb7b918..c25c7a2 100644
--- a/lib/AST/TypePrinter.cpp
+++ b/lib/AST/TypePrinter.cpp
@@ -453,6 +453,8 @@ void TypePrinter::printFunctionProto(const
FunctionProtoType *T,
}
if (Info.getNoReturn())
S += " __attribute__((noreturn))";
+ if (Info.getReturnsTwice())
+ S += " __attribute__((returns_twice))";
if (Info.getRegParm())
S += " __attribute__((regparm (" +
llvm::utostr_32(Info.getRegParm()) + ")))";
@@ -510,6 +512,8 @@ void TypePrinter::printFunctionNoProto(const
FunctionNoProtoType *T,
S += "()";
if (T->getNoReturnAttr())
S += " __attribute__((noreturn))";
+ if (T->getReturnsTwiceAttr())
+ S += " __attribute__((returs_twice))";
print(T->getResultType(), S);
}
@@ -940,6 +944,7 @@ void TypePrinter::printAttributed(const AttributedType *T,
break;
case AttributedType::attr_noreturn: S += "noreturn"; break;
+ case AttributedType::attr_returns_twice: S += "returns_twice"; break;
case AttributedType::attr_cdecl: S += "cdecl"; break;
case AttributedType::attr_fastcall: S += "fastcall"; break;
case AttributedType::attr_stdcall: S += "stdcall"; break;
diff --git a/lib/CodeGen/CGCall.cpp b/lib/CodeGen/CGCall.cpp
index f52abef..5f280c7 100644
--- a/lib/CodeGen/CGCall.cpp
+++ b/lib/CodeGen/CGCall.cpp
@@ -260,7 +260,8 @@ const CGFunctionInfo
&CodeGenTypes::getFunctionInfo(CanQualType ResTy,
return *FI;
// Construct the function info.
- FI = new CGFunctionInfo(CC, Info.getNoReturn(), Info.getProducesResult(),
+ FI = new CGFunctionInfo(CC, Info.getNoReturn(), Info.getReturnsTwice(),
+ Info.getProducesResult(),
Info.getHasRegParm(), Info.getRegParm(), ResTy,
ArgTys.data(), ArgTys.size());
FunctionInfos.InsertNode(FI, InsertPos);
@@ -290,14 +291,16 @@ const CGFunctionInfo
&CodeGenTypes::getFunctionInfo(CanQualType ResTy,
}
CGFunctionInfo::CGFunctionInfo(unsigned _CallingConvention,
- bool _NoReturn, bool returnsRetained,
+ bool _NoReturn, bool _ReturnsTwice,
+ bool returnsRetained,
bool _HasRegParm, unsigned _RegParm,
CanQualType ResTy,
const CanQualType *ArgTys,
unsigned NumArgTys)
: CallingConvention(_CallingConvention),
EffectiveCallingConvention(_CallingConvention),
- NoReturn(_NoReturn), ReturnsRetained(returnsRetained),
+ NoReturn(_NoReturn), ReturnsTwice(_ReturnsTwice),
+ ReturnsRetained(returnsRetained),
HasRegParm(_HasRegParm), RegParm(_RegParm)
{
NumArgs = NumArgTys;
@@ -727,6 +730,9 @@ void CodeGenModule::ConstructAttributeList(const
CGFunctionInfo &FI,
if (FI.isNoReturn())
FuncAttrs |= llvm::Attribute::NoReturn;
+ if (FI.isReturnsTwice())
+ FuncAttrs |= llvm::Attribute::ReturnsTwice;
+
// FIXME: handle sseregparm someday...
if (TargetDecl) {
if (TargetDecl->hasAttr<NoThrowAttr>())
diff --git a/lib/CodeGen/CGCall.h b/lib/CodeGen/CGCall.h
index 24ed366..a00dc30 100644
--- a/lib/CodeGen/CGCall.h
+++ b/lib/CodeGen/CGCall.h
@@ -123,6 +123,9 @@ namespace CodeGen {
/// Whether this function is noreturn.
bool NoReturn;
+ /// Whether this function is returns-twice.
+ bool ReturnsTwice;
+
/// Whether this function is returns-retained.
bool ReturnsRetained;
@@ -137,7 +140,7 @@ namespace CodeGen {
typedef const ArgInfo *const_arg_iterator;
typedef ArgInfo *arg_iterator;
- CGFunctionInfo(unsigned CallingConvention, bool NoReturn,
+ CGFunctionInfo(unsigned CallingConvention, bool NoReturn, bool
ReturnsTwice,
bool ReturnsRetained, bool HasRegParm, unsigned RegParm,
CanQualType ResTy,
const CanQualType *ArgTys, unsigned NumArgTys);
@@ -151,6 +154,7 @@ namespace CodeGen {
unsigned arg_size() const { return NumArgs; }
bool isNoReturn() const { return NoReturn; }
+ bool isReturnsTwice() const { return ReturnsTwice; }
/// In ARR, whether this function retains its return value. This
/// is not always reliable for call sites.
@@ -180,6 +184,7 @@ namespace CodeGen {
void Profile(llvm::FoldingSetNodeID &ID) {
ID.AddInteger(getCallingConvention());
ID.AddBoolean(NoReturn);
+ ID.AddBoolean(ReturnsTwice);
ID.AddBoolean(ReturnsRetained);
ID.AddBoolean(HasRegParm);
ID.AddInteger(RegParm);
@@ -195,6 +200,7 @@ namespace CodeGen {
Iterator end) {
ID.AddInteger(Info.getCC());
ID.AddBoolean(Info.getNoReturn());
+ ID.AddBoolean(Info.getReturnsTwice());
ID.AddBoolean(Info.getProducesResult());
ID.AddBoolean(Info.getHasRegParm());
ID.AddInteger(Info.getRegParm());
diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp
index d19023d..31e9917 100644
--- a/lib/Sema/SemaDecl.cpp
+++ b/lib/Sema/SemaDecl.cpp
@@ -1737,6 +1737,11 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, Decl
*OldD) {
RequiresAdjustment = true;
}
+ if (OldTypeInfo.getReturnsTwice() != NewTypeInfo.getReturnsTwice()) {
+ NewTypeInfo = NewTypeInfo.withReturnsTwice(true);
+ RequiresAdjustment = true;
+ }
+
// Merge regparm attribute.
if (OldTypeInfo.getHasRegParm() != NewTypeInfo.getHasRegParm() ||
OldTypeInfo.getRegParm() != NewTypeInfo.getRegParm()) {
diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp
index f76bb58..6474245 100644
--- a/lib/Sema/SemaDeclAttr.cpp
+++ b/lib/Sema/SemaDeclAttr.cpp
@@ -1316,6 +1316,16 @@ bool Sema::CheckNoReturnAttr(const AttributeList &attr) {
return false;
}
+bool Sema::CheckReturnsTwiceAttr(const AttributeList &attr) {
+ if (attr.hasParameterOrArguments()) {
+ Diag(attr.getLoc(), diag::err_attribute_wrong_number_arguments) << 0;
+ attr.setInvalid();
+ return true;
+ }
+
+ return false;
+}
+
static void handleAnalyzerNoReturnAttr(Sema &S, Decl *D,
const AttributeList &Attr) {
diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp
index 154b2a8..c7fcc83 100644
--- a/lib/Sema/SemaType.cpp
+++ b/lib/Sema/SemaType.cpp
@@ -95,6 +95,7 @@ static void diagnoseBadTypeAttribute(Sema &S, const
AttributeList &attr,
// Function type attributes.
#define FUNCTION_TYPE_ATTRS_CASELIST \
+ case AttributeList::AT_returns_twice: \
case AttributeList::AT_noreturn: \
case AttributeList::AT_cdecl: \
case AttributeList::AT_fastcall: \
@@ -2673,6 +2674,8 @@ static AttributeList::Kind
getAttrListKind(AttributedType::Kind kind) {
return AttributeList::AT_objc_ownership;
case AttributedType::attr_noreturn:
return AttributeList::AT_noreturn;
+ case AttributedType::attr_returns_twice:
+ return AttributeList::AT_returns_twice;
case AttributedType::attr_cdecl:
return AttributeList::AT_cdecl;
case AttributedType::attr_fastcall:
@@ -3479,6 +3482,21 @@ static bool handleFunctionTypeAttr(TypeProcessingState
&state,
return true;
}
+ if (attr.getKind() == AttributeList::AT_returns_twice) {
+ if (S.CheckReturnsTwiceAttr(attr))
+ return true;
+
+ // Delay if this is not a function type.
+ if (!unwrapped.isFunctionType())
+ return false;
+
+ // Otherwise we can process right away.
+ FunctionType::ExtInfo EI =
+ unwrapped.get()->getExtInfo().withReturnsTwice(true);
+ type = unwrapped.wrap(S, S.Context.adjustFunctionType(unwrapped.get(),
EI));
+ return true;
+ }
+
// ns_returns_retained is not always a type attribute, but if we got
// here, we're treating it as one right now.
if (attr.getKind() == AttributeList::AT_ns_returns_retained) {
diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp
index 361c3b7..9d96d8f 100644
--- a/lib/Serialization/ASTReader.cpp
+++ b/lib/Serialization/ASTReader.cpp
@@ -3250,13 +3250,13 @@ QualType ASTReader::readTypeRecord(unsigned Index) {
}
case TYPE_FUNCTION_NO_PROTO: {
- if (Record.size() != 6) {
+ if (Record.size() != 7) {
Error("incorrect encoding of no-proto function type");
return QualType();
}
QualType ResultType = readType(*Loc.F, Record, Idx);
FunctionType::ExtInfo Info(Record[1], Record[2], Record[3],
- (CallingConv)Record[4], Record[5]);
+ (CallingConv)Record[4], Record[5], Record[6]);
return Context.getFunctionNoProtoType(ResultType, Info);
}
@@ -3268,9 +3268,10 @@ QualType ASTReader::readTypeRecord(unsigned Index) {
/*hasregparm*/ Record[2],
/*regparm*/ Record[3],
static_cast<CallingConv>(Record[4]),
- /*produces*/ Record[5]);
+ /*produces*/ Record[5],
+ /*returnsTwice*/ Record[6]);
- unsigned Idx = 6;
+ unsigned Idx = 7;
unsigned NumParams = Record[Idx++];
SmallVector<QualType, 16> ParamTypes;
for (unsigned I = 0; I != NumParams; ++I)
diff --git a/lib/Serialization/ASTWriter.cpp b/lib/Serialization/ASTWriter.cpp
index b31262d..30eb4c4 100644
--- a/lib/Serialization/ASTWriter.cpp
+++ b/lib/Serialization/ASTWriter.cpp
@@ -172,6 +172,7 @@ void ASTTypeWriter::VisitFunctionType(const FunctionType
*T) {
// FIXME: need to stabilize encoding of calling convention...
Record.push_back(C.getCC());
Record.push_back(C.getProducesResult());
+ Record.push_back(C.getReturnsTwice());
}
void ASTTypeWriter::VisitFunctionNoProtoType(const FunctionNoProtoType *T) {
diff --git a/test/Analysis/security-syntax-checks.m
b/test/Analysis/security-syntax-checks.m
index a04401b..6fb5b3c 100644
--- a/test/Analysis/security-syntax-checks.m
+++ b/test/Analysis/security-syntax-checks.m
@@ -170,7 +170,7 @@ void test_strcat() {
//===----------------------------------------------------------------------===
typedef int __int32_t;
typedef __int32_t pid_t;
-pid_t vfork(void);
+pid_t vfork(void); //expected-warning{{declaration of built-in function
'vfork' requires inclusion of the header <setjmp.h>}}
void test_vfork() {
vfork(); //expected-warning{{Call to function 'vfork' is insecure as it can
lead to denial of service situations in the parent process.}}
diff --git a/test/CodeGen/function-attributes.c
b/test/CodeGen/function-attributes.c
index fd98458..6cbf40b 100644
--- a/test/CodeGen/function-attributes.c
+++ b/test/CodeGen/function-attributes.c
@@ -100,3 +100,14 @@ __attribute__ ((returns_twice)) void f17(void);
__attribute__ ((returns_twice)) void f18(void) {
f17();
}
+
+// CHECK: define void @f19()
+// CHECK: {
+// CHECK: call i32 @setjmp(i32* null)
+// CHECK: returns_twice
+// CHECK: ret void
+typedef int jmp_buf[((9 * 2) + 3 + 16)];
+int setjmp(jmp_buf);
+void f19(void) {
+ setjmp(0);
+}
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits