void QStringAllocations::VisitFromLatin1OrUtf8(Stmt *stmt) { CallExpr *callExpr = dyn_cast<CallExpr>(stmt); if (!callExpr) return; FunctionDecl *functionDecl = callExpr->getDirectCallee(); if (!StringUtils::functionIsOneOf(functionDecl, {"fromLatin1", "fromUtf8"})) return; CXXMethodDecl *methodDecl = dyn_cast<CXXMethodDecl>(functionDecl); if (!StringUtils::isOfClass(methodDecl, "QString")) return; if (!Utils::callHasDefaultArguments(callExpr) || !hasCharPtrArgument(functionDecl, 2)) // QString::fromLatin1("foo", 1) is ok return; if (!containsStringLiteralNoCallExpr(callExpr)) return; if (!isOptionSet("no-msvc-compat")) { StringLiteral *lt = stringLiteralForCall(callExpr); if (lt && lt->getNumConcatenated() > 1) { return; // Nothing to do here, MSVC doesn't like it } } vector<ConditionalOperator*> ternaries; HierarchyUtils::getChilds(callExpr, ternaries, 2); if (!ternaries.empty()) { auto ternary = ternaries[0]; if (Utils::ternaryOperatorIsOfStringLiteral(ternary)) { emitWarning(stmt->getLocStart(), string("QString::fromLatin1() being passed a literal")); } return; } std::vector<FixItHint> fixits; if (isFixitEnabled(FromLatin1_FromUtf8Allocations)) { const FromFunction fromFunction = functionDecl->getNameAsString() == "fromLatin1" ? FromLatin1 : FromUtf8; fixits = fixItReplaceFromLatin1OrFromUtf8(callExpr, fromFunction); } if (functionDecl->getNameAsString() == "fromLatin1") { emitWarning(stmt->getLocStart(), string("QString::fromLatin1() being passed a literal"), fixits); } else { emitWarning(stmt->getLocStart(), string("QString::fromUtf8() being passed a literal"), fixits); } }
void RuleOfTwoSoft::VisitStmt(Stmt *s) { CXXOperatorCallExpr *op = dyn_cast<CXXOperatorCallExpr>(s); if (op) { FunctionDecl *func = op->getDirectCallee(); if (func && func->getNameAsString() == "operator=") { CXXMethodDecl *method = dyn_cast<CXXMethodDecl>(func); if (method && method->getParent()) { CXXRecordDecl *record = method->getParent(); const bool hasCopyCtor = record->hasNonTrivialCopyConstructor(); const bool hasCopyAssignOp = record->hasNonTrivialCopyAssignment(); if (hasCopyCtor && !hasCopyAssignOp && !isBlacklisted(record)) { string msg = "Using assign operator but class " + record->getQualifiedNameAsString() + " has copy-ctor but no assign operator"; emitWarning(s->getLocStart(), msg); } } } } else if (CXXConstructExpr *ctorExpr = dyn_cast<CXXConstructExpr>(s)) { CXXConstructorDecl *ctorDecl = ctorExpr->getConstructor(); CXXRecordDecl *record = ctorDecl->getParent(); if (ctorDecl->isCopyConstructor() && record) { const bool hasCopyCtor = record->hasNonTrivialCopyConstructor(); const bool hasCopyAssignOp = record->hasNonTrivialCopyAssignment(); if (!hasCopyCtor && hasCopyAssignOp && !isBlacklisted(record)) { string msg = "Using copy-ctor but class " + record->getQualifiedNameAsString() + " has a trivial copy-ctor but non trivial assign operator"; emitWarning(s->getLocStart(), msg); } } } }
void QGetEnv::VisitStmt(clang::Stmt *stmt) { // Lets check only in function calls. Otherwise there are too many false positives, it's common // to implicit cast to bool when checking pointers for validity, like if (ptr) CXXMemberCallExpr *memberCall = dyn_cast<CXXMemberCallExpr>(stmt); if (!memberCall) return; CXXMethodDecl *method = memberCall->getMethodDecl(); if (!method) return; CXXRecordDecl *record = method->getParent(); if (!record || record->getNameAsString() != "QByteArray") { return; } std::vector<CallExpr *> calls = Utils::callListForChain(memberCall); if (calls.size() != 2) return; CallExpr *qgetEnvCall = calls.back(); FunctionDecl *func = qgetEnvCall->getDirectCallee(); if (!func || func->getNameAsString() != "qgetenv") return; string methodname = method->getNameAsString(); string errorMsg; std::string replacement; if (methodname == "isEmpty") { errorMsg = "qgetenv().isEmpty() allocates."; replacement = "qEnvironmentVariableIsEmpty"; } else if (methodname == "isNull") { errorMsg = "qgetenv().isNull() allocates."; replacement = "qEnvironmentVariableIsSet"; } else if (methodname == "toInt") { errorMsg = "qgetenv().toInt() is slow."; replacement = "qEnvironmentVariableIntValue"; } if (!errorMsg.empty()) { std::vector<FixItHint> fixits; if (isFixitEnabled(FixitAll)) { const bool success = FixItUtils::transformTwoCallsIntoOne(m_ci, qgetEnvCall, memberCall, replacement, fixits); if (!success) { queueManualFixitWarning(memberCall->getLocStart(), FixitAll); } } errorMsg += " Use " + replacement + "() instead"; emitWarning(memberCall->getLocStart(), errorMsg.c_str(), fixits); } }
void FunctionArgsByRef::VisitDecl(Decl *decl) { FunctionDecl *functionDecl = dyn_cast<FunctionDecl>(decl); if (functionDecl == nullptr || !functionDecl->hasBody() || shouldIgnoreFunction(functionDecl->getNameAsString()) || !functionDecl->isThisDeclarationADefinition()) return; Stmt *body = functionDecl->getBody(); for (auto it = functionDecl->param_begin(), end = functionDecl->param_end(); it != end; ++it) { const ParmVarDecl *param = *it; QualType paramQt = param->getType(); const Type *paramType = paramQt.getTypePtrOrNull(); if (paramType == nullptr || paramType->isDependentType()) continue; const int size_of_T = m_ci.getASTContext().getTypeSize(paramQt) / 8; const bool isSmall = size_of_T <= 16; // TODO: What about arm ? CXXRecordDecl *recordDecl = paramType->getAsCXXRecordDecl(); const bool isUserNonTrivial = recordDecl && (recordDecl->hasUserDeclaredCopyConstructor() || recordDecl->hasUserDeclaredDestructor()); const bool isReference = paramType->isLValueReferenceType(); const bool isConst = paramQt.isConstQualified(); if (recordDecl && shouldIgnoreClass(recordDecl->getQualifiedNameAsString())) continue; std::string error; if (isConst && !isReference) { if (!isSmall) { error += warningMsgForSmallType(size_of_T, paramQt.getAsString()); } else if (isUserNonTrivial) { error += "Missing reference on non-trivial type " + recordDecl->getQualifiedNameAsString(); } } else if (isConst && isReference && !isUserNonTrivial && isSmall) { //error += "Don't use by-ref on small trivial type"; } else if (!isConst && !isReference && (!isSmall || isUserNonTrivial)) { if (Utils::containsNonConstMemberCall(body, param) || Utils::containsCallByRef(body, param)) continue; if (!isSmall) { error += warningMsgForSmallType(size_of_T, paramQt.getAsString()); } else if (isUserNonTrivial) { error += "Missing reference on non-trivial type " + recordDecl->getQualifiedNameAsString(); } } if (!error.empty()) { emitWarning(param->getLocStart(), error.c_str()); } } }
void NullDerefProtectionTransformer::Transform() { using namespace clang; FunctionDecl* FD = getTransaction()->getWrapperFD(); if (!FD) return; // Copied from Interpreter.cpp; if (!m_MangleCtx) m_MangleCtx.reset(FD->getASTContext().createMangleContext()); std::string mangledName; if (m_MangleCtx->shouldMangleDeclName(FD)) { llvm::raw_string_ostream RawStr(mangledName); switch(FD->getKind()) { case Decl::CXXConstructor: //Ctor_Complete, // Complete object ctor //Ctor_Base, // Base object ctor //Ctor_CompleteAllocating // Complete object allocating ctor (unused) m_MangleCtx->mangleCXXCtor(cast<CXXConstructorDecl>(FD), Ctor_Complete, RawStr); break; case Decl::CXXDestructor: //Dtor_Deleting, // Deleting dtor //Dtor_Complete, // Complete object dtor //Dtor_Base // Base object dtor m_MangleCtx->mangleCXXDtor(cast<CXXDestructorDecl>(FD), Dtor_Deleting, RawStr); break; default : m_MangleCtx->mangleName(FD, RawStr); break; } RawStr.flush(); } else { mangledName = FD->getNameAsString(); } // Find the function in the module. llvm::Function* F = getTransaction()->getModule()->getFunction(mangledName); if (F) runOnFunction(*F); }
bool Utils::isAssignOperator(CXXOperatorCallExpr *op, const std::string &className, const std::string &argumentType, const clang::LangOptions &lo) { if (!op) return false; FunctionDecl *functionDecl = op->getDirectCallee(); if (!functionDecl) return false; CXXMethodDecl *methodDecl = dyn_cast<clang::CXXMethodDecl>(functionDecl); if (!className.empty() && !StringUtils::isOfClass(methodDecl, className)) return false; if (functionDecl->getNameAsString() != "operator=" || functionDecl->param_size() != 1) return false; if (!argumentType.empty() && !StringUtils::hasArgumentOfType(functionDecl, argumentType, lo)) { return false; } return true; }
/// Skip the 'skipMe' function. bool shouldSkipFunctionBody(Decl *D) override { FunctionDecl *F = dyn_cast<FunctionDecl>(D); return F && F->getNameAsString() == "skipMe"; }
/* Does the given statement look like: * • g_return_if_fail(…) * • g_return_val_if_fail(…) * • g_assert(…) * • g_assert_*(…) * • assert(…) * This is complicated by the fact that if the gmessages.h header isn’t * available, they’ll present as CallExpr function calls with those names; if it * is available, they’ll be expanded as macros and turn into DoStmts with misc. * rubbish beneath. * * If the statement changes program state at all, return NULL. Otherwise, return * the condition which holds for the assertion to be bypassed (i.e. for the * assertion to succeed). This function is built recursively, building a boolean * expression for the condition based on avoiding branches which call * abort()-like functions. * * This function is based on a transformation of the AST to an augmented boolean * expression, using rules documented in each switch case. In this * documentation, calc(S) refers to the transformation function. The augmented * boolean expressions can be either NULL, or a normal boolean expression * (TRUE, FALSE, ∧, ∨, ¬). NULL is used iff the statement potentially changes * program state, and poisons any boolean expression: * B ∧ NULL ≡ NULL * B ∨ NULL ≡ NULL * ¬NULL ≡ NULL */ Expr* AssertionExtracter::is_assertion_stmt (Stmt& stmt, const ASTContext& context) { DEBUG ("Checking " << stmt.getStmtClassName () << " for assertions."); /* Slow path: walk through the AST, aborting on statements which * potentially mutate program state, and otherwise trying to find a base * function call such as: * • g_return_if_fail_warning() * • g_assertion_message() * • g_assertion_message_*() */ switch ((int) stmt.getStmtClass ()) { case Stmt::StmtClass::CallExprClass: { /* Handle a direct function call. * Transformations: * [g_return_if_fail|assert|…](C) ↦ C * [g_return_if_fail_warning|__assert_fail|…](C) ↦ FALSE * other_funcs(…) ↦ NULL */ CallExpr& call_expr = cast<CallExpr> (stmt); FunctionDecl* func = call_expr.getDirectCallee (); if (func == NULL) return NULL; std::string func_name = func->getNameAsString (); DEBUG ("CallExpr to function " << func_name); if (_is_assertion_name (func_name)) { /* Assertion path where the compiler hasn't seen the * definition of the assertion macro, so still thinks * it's a function. * * Extract the assertion condition as the first function * parameter. * * TODO: May need to fix up the condition for macros * like g_assert_null(). */ return call_expr.getArg (0); } else if (_is_assertion_fail_func_name (func_name)) { /* Assertion path where the assertion macro has been * expanded and we're on the assertion failure branch. * * In this case, the assertion condition has been * grabbed from an if statement already, so negate it * (to avoid the failure condition) and return. */ return new (context) IntegerLiteral (context, context.MakeIntValue (0, context.getLogicalOperationType ()), context.getLogicalOperationType (), SourceLocation ()); } /* Not an assertion path. */ return NULL; } case Stmt::StmtClass::DoStmtClass: { /* Handle a do { … } while (0) block (commonly used to allow * macros to optionally be suffixed by a semicolon). * Transformations: * do { S } while (0) ↦ calc(S) * do { S } while (C) ↦ NULL * Note the second condition is overly-conservative. No * solutions for the halting problem here. */ DoStmt& do_stmt = cast<DoStmt> (stmt); Stmt* body = do_stmt.getBody (); Stmt* cond = do_stmt.getCond (); Expr* expr = dyn_cast<Expr> (cond); llvm::APSInt bool_expr; if (body != NULL && expr != NULL && expr->isIntegerConstantExpr (bool_expr, context) && !bool_expr.getBoolValue ()) { return is_assertion_stmt (*body, context); } return NULL; } case Stmt::StmtClass::IfStmtClass: { /* Handle an if(…) { … } else { … } block. * Transformations: * if (C) { S1 } else { S2 } ↦ * (C ∧ calc(S1)) ∨ (¬C ∧ calc(S2)) * if (C) { S } ↦ (C ∧ calc(S)) ∨ ¬C * i.e. * if (C) { S } ≡ if (C) { S } else {} * where {} is an empty compound statement, below. */ IfStmt& if_stmt = cast<IfStmt> (stmt); assert (if_stmt.getThen () != NULL); Expr* neg_cond = _negation_expr (if_stmt.getCond (), context); Expr* then_assertion = is_assertion_stmt (*(if_stmt.getThen ()), context); if (then_assertion == NULL) return NULL; then_assertion = _conjunction_expr (if_stmt.getCond (), then_assertion, context); if (if_stmt.getElse () == NULL) return _disjunction_expr (then_assertion, neg_cond, context); Expr* else_assertion = is_assertion_stmt (*(if_stmt.getElse ()), context); if (else_assertion == NULL) return NULL; else_assertion = _conjunction_expr (neg_cond, else_assertion, context); return _disjunction_expr (then_assertion, else_assertion, context); } case Stmt::StmtClass::ConditionalOperatorClass: { /* Handle a ternary operator. * Transformations: * C ? S1 : S2 ↦ * (C ∧ calc(S1)) ∨ (¬C ∧ calc(S2)) */ ConditionalOperator& op_expr = cast<ConditionalOperator> (stmt); assert (op_expr.getTrueExpr () != NULL); assert (op_expr.getFalseExpr () != NULL); Expr* neg_cond = _negation_expr (op_expr.getCond (), context); Expr* then_assertion = is_assertion_stmt (*(op_expr.getTrueExpr ()), context); if (then_assertion == NULL) return NULL; then_assertion = _conjunction_expr (op_expr.getCond (), then_assertion, context); Expr* else_assertion = is_assertion_stmt (*(op_expr.getFalseExpr ()), context); if (else_assertion == NULL) return NULL; else_assertion = _conjunction_expr (neg_cond, else_assertion, context); return _disjunction_expr (then_assertion, else_assertion, context); } case Stmt::StmtClass::SwitchStmtClass: { /* Handle a switch statement. * Transformations: * switch (C) { L1: S1; L2: S2; …; Lz: Sz } ↦ NULL * FIXME: This should get a proper transformation sometime. */ return NULL; } case Stmt::StmtClass::AttributedStmtClass: { /* Handle an attributed statement, e.g. G_LIKELY(…). * Transformations: * att S ↦ calc(S) */ AttributedStmt& attr_stmt = cast<AttributedStmt> (stmt); Stmt* sub_stmt = attr_stmt.getSubStmt (); if (sub_stmt == NULL) return NULL; return is_assertion_stmt (*sub_stmt, context); } case Stmt::StmtClass::CompoundStmtClass: { /* Handle a compound statement, e.g. { stmt1; stmt2; }. * Transformations: * S1; S2; …; Sz ↦ calc(S1) ∧ calc(S2) ∧ … ∧ calc(Sz) * {} ↦ TRUE * * This is implemented by starting with a base TRUE case in the * compound_condition, then taking the conjunction with the next * statement’s assertion condition for each statement in the * compound. * * If the compound is empty, the compound_condition will be * TRUE. Otherwise, it will be (TRUE ∧ …), which will be * simplified later. */ CompoundStmt& compound_stmt = cast<CompoundStmt> (stmt); Expr* compound_condition = new (context) IntegerLiteral (context, context.MakeIntValue (1, context.getLogicalOperationType ()), context.getLogicalOperationType (), SourceLocation ()); for (CompoundStmt::const_body_iterator it = compound_stmt.body_begin (), ie = compound_stmt.body_end (); it != ie; ++it) { Stmt* body_stmt = *it; Expr* body_assertion = is_assertion_stmt (*body_stmt, context); if (body_assertion == NULL) { /* Reached a program state mutation. */ return NULL; } /* Update the compound condition. */ compound_condition = _conjunction_expr (compound_condition, body_assertion, context); DEBUG_EXPR ("Compound condition: ", *compound_condition); } return compound_condition; } case Stmt::StmtClass::GotoStmtClass: /* Handle a goto statement. * Transformations: * goto L ↦ FALSE */ case Stmt::StmtClass::ReturnStmtClass: { /* Handle a return statement. * Transformations: * return ↦ FALSE */ return new (context) IntegerLiteral (context, context.MakeIntValue (0, context.getLogicalOperationType ()), context.getLogicalOperationType (), SourceLocation ()); } case Stmt::StmtClass::NullStmtClass: /* Handle a null statement. * Transformations: * ; ↦ TRUE */ case Stmt::StmtClass::DeclRefExprClass: /* Handle a variable reference expression. These don’t modify * program state. * Transformations: * E ↦ TRUE */ case Stmt::StmtClass::DeclStmtClass: { /* Handle a variable declaration statement. These don’t modify * program state; they only introduce new state, so can’t affect * subsequent assertions. (FIXME: For the moment, we ignore the * possibility of the rvalue modifying program state.) * Transformations: * T S1 ↦ TRUE * T S1 = S2 ↦ TRUE */ return new (context) IntegerLiteral (context, context.MakeIntValue (1, context.getLogicalOperationType ()), context.getLogicalOperationType (), SourceLocation ()); } case Stmt::StmtClass::IntegerLiteralClass: { /* Handle an integer literal. This doesn’t modify program state, * and evaluates directly to a boolean. * Transformations: * 0 ↦ FALSE * I ↦ TRUE */ return dyn_cast<Expr> (&stmt); } case Stmt::StmtClass::ParenExprClass: { /* Handle a parenthesised expression. * Transformations: * ( S ) ↦ calc(S) */ ParenExpr& paren_expr = cast<ParenExpr> (stmt); Stmt* sub_expr = paren_expr.getSubExpr (); if (sub_expr == NULL) return NULL; return is_assertion_stmt (*sub_expr, context); } case Stmt::StmtClass::LabelStmtClass: { /* Handle a label statement. * Transformations: * label: S ↦ calc(S) */ LabelStmt& label_stmt = cast<LabelStmt> (stmt); Stmt* sub_stmt = label_stmt.getSubStmt (); if (sub_stmt == NULL) return NULL; return is_assertion_stmt (*sub_stmt, context); } case Stmt::StmtClass::ImplicitCastExprClass: case Stmt::StmtClass::CStyleCastExprClass: { /* Handle an explicit or implicit cast. * Transformations: * (T) S ↦ calc(S) */ CastExpr& cast_expr = cast<CastExpr> (stmt); Stmt* sub_expr = cast_expr.getSubExpr (); if (sub_expr == NULL) return NULL; return is_assertion_stmt (*sub_expr, context); } case Stmt::StmtClass::GCCAsmStmtClass: case Stmt::StmtClass::MSAsmStmtClass: /* Inline assembly. There is no way we are parsing this, so * conservatively assume it modifies program state. * Transformations: * A ↦ NULL */ case Stmt::StmtClass::BinaryOperatorClass: /* Handle a binary operator statement. Since this is being * processed at the top level, it’s most likely an assignment, * so conservatively assume it modifies program state. * Transformations: * S1 op S2 ↦ NULL */ case Stmt::StmtClass::UnaryOperatorClass: /* Handle a unary operator statement. Since this is being * processed at the top level, it’s not very interesting re. * assertions, even though it probably won’t modify program * state (unless it’s a pre- or post-increment or -decrement * operator). Be conservative and assume it does, though. * Transformations: * op S ↦ NULL */ case Stmt::StmtClass::CompoundAssignOperatorClass: /* Handle a compound assignment operator, e.g. x += 5. This * definitely modifies program state, so ignore it. * Transformations: * S1 op S2 ↦ NULL */ case Stmt::StmtClass::ForStmtClass: /* Handle a for statement. We assume these *always* change * program state. * Transformations: * for (…) { … } ↦ NULL */ case Stmt::StmtClass::WhileStmtClass: { /* Handle a while(…) { … } block. Because we don't want to solve * the halting problem, just assume all while statements cannot * be assertion statements. * Transformations: * while (C) { S } ↦ NULL */ return NULL; } case Stmt::StmtClass::NoStmtClass: default: WARN_EXPR (__func__ << "() can’t handle statements of type " << stmt.getStmtClassName (), stmt); return NULL; } }
bool DeclExtractor::ExtractDecl(Decl* D) { FunctionDecl* FD = dyn_cast<FunctionDecl>(D); if (FD) { if (FD->getNameAsString().find("__cling_Un1Qu3")) return true; llvm::SmallVector<NamedDecl*, 4> TouchedDecls; CompoundStmt* CS = dyn_cast<CompoundStmt>(FD->getBody()); assert(CS && "Function body not a CompoundStmt?"); DeclContext* DC = FD->getTranslationUnitDecl(); Scope* TUScope = m_Sema->TUScope; assert(TUScope == m_Sema->getScopeForContext(DC) && "TU scope from DC?"); llvm::SmallVector<Stmt*, 4> Stmts; for (CompoundStmt::body_iterator I = CS->body_begin(), EI = CS->body_end(); I != EI; ++I) { DeclStmt* DS = dyn_cast<DeclStmt>(*I); if (!DS) { Stmts.push_back(*I); continue; } for (DeclStmt::decl_iterator J = DS->decl_begin(); J != DS->decl_end(); ++J) { NamedDecl* ND = dyn_cast<NamedDecl>(*J); if (ND) { DeclContext* OldDC = ND->getDeclContext(); // Make sure the decl is not found at its old possition OldDC->removeDecl(ND); if (Scope* S = m_Sema->getScopeForContext(OldDC)) { S->RemoveDecl(ND); m_Sema->IdResolver.RemoveDecl(ND); } if (ND->getDeclContext() == ND->getLexicalDeclContext()) ND->setLexicalDeclContext(DC); else assert("Not implemented: Decl with different lexical context"); ND->setDeclContext(DC); if (VarDecl* VD = dyn_cast<VarDecl>(ND)) { VD->setStorageClass(SC_None); VD->setStorageClassAsWritten(SC_None); // if we want to print the result of the initializer of int i = 5 // or the default initializer int i if (I+1 == EI || !isa<NullStmt>(*(I+1))) { QualType VDTy = VD->getType().getNonReferenceType(); Expr* DRE = m_Sema->BuildDeclRefExpr(VD, VDTy,VK_LValue, SourceLocation() ).take(); Stmts.push_back(DRE); } } // force recalc of the linkage (to external) ND->ClearLinkageCache(); TouchedDecls.push_back(ND); } } } bool hasNoErrors = !CheckForClashingNames(TouchedDecls, DC, TUScope); if (hasNoErrors) { for (size_t i = 0; i < TouchedDecls.size(); ++i) { m_Sema->PushOnScopeChains(TouchedDecls[i], m_Sema->getScopeForContext(DC), /*AddCurContext*/!isa<UsingDirectiveDecl>(TouchedDecls[i])); // The transparent DeclContexts (eg. scopeless enum) doesn't have // scopes. While extracting their contents we need to update the // lookup tables and telling them to pick up the new possitions // in the AST. if (DeclContext* InnerDC = dyn_cast<DeclContext>(TouchedDecls[i])) { if (InnerDC->isTransparentContext()) { // We can't PushDeclContext, because we don't have scope. Sema::ContextRAII pushedDC(*m_Sema, InnerDC); for(DeclContext::decl_iterator DI = InnerDC->decls_begin(), DE = InnerDC->decls_end(); DI != DE ; ++DI) { if (NamedDecl* ND = dyn_cast<NamedDecl>(*DI)) InnerDC->makeDeclVisibleInContext(ND); } } } // Append the new top level decl to the current transaction. getTransaction()->appendUnique(DeclGroupRef(TouchedDecls[i])); } } CS->setStmts(*m_Context, Stmts.data(), Stmts.size()); // Put the wrapper after its declarations. (Nice when AST dumping) DC->removeDecl(FD); DC->addDecl(FD); return hasNoErrors; } return true; }