| //===--- TransProtectedScope.cpp - Transformations to ARC mode ------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Adds brackets in case statements that "contain" initialization of retaining |
| // variable, thus emitting the "switch case is in protected scope" error. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Transforms.h" |
| #include "Internals.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/Sema/SemaDiagnostic.h" |
| |
| using namespace clang; |
| using namespace arcmt; |
| using namespace trans; |
| |
| namespace { |
| |
| class LocalRefsCollector : public RecursiveASTVisitor<LocalRefsCollector> { |
| SmallVectorImpl<DeclRefExpr *> &Refs; |
| |
| public: |
| LocalRefsCollector(SmallVectorImpl<DeclRefExpr *> &refs) |
| : Refs(refs) { } |
| |
| bool VisitDeclRefExpr(DeclRefExpr *E) { |
| if (ValueDecl *D = E->getDecl()) |
| if (D->getDeclContext()->getRedeclContext()->isFunctionOrMethod()) |
| Refs.push_back(E); |
| return true; |
| } |
| }; |
| |
| struct CaseInfo { |
| SwitchCase *SC; |
| SourceRange Range; |
| enum { |
| St_Unchecked, |
| St_CannotFix, |
| St_Fixed |
| } State; |
| |
| CaseInfo() : SC(nullptr), State(St_Unchecked) {} |
| CaseInfo(SwitchCase *S, SourceRange Range) |
| : SC(S), Range(Range), State(St_Unchecked) {} |
| }; |
| |
| class CaseCollector : public RecursiveASTVisitor<CaseCollector> { |
| ParentMap &PMap; |
| SmallVectorImpl<CaseInfo> &Cases; |
| |
| public: |
| CaseCollector(ParentMap &PMap, SmallVectorImpl<CaseInfo> &Cases) |
| : PMap(PMap), Cases(Cases) { } |
| |
| bool VisitSwitchStmt(SwitchStmt *S) { |
| SwitchCase *Curr = S->getSwitchCaseList(); |
| if (!Curr) |
| return true; |
| Stmt *Parent = getCaseParent(Curr); |
| Curr = Curr->getNextSwitchCase(); |
| // Make sure all case statements are in the same scope. |
| while (Curr) { |
| if (getCaseParent(Curr) != Parent) |
| return true; |
| Curr = Curr->getNextSwitchCase(); |
| } |
| |
| SourceLocation NextLoc = S->getEndLoc(); |
| Curr = S->getSwitchCaseList(); |
| // We iterate over case statements in reverse source-order. |
| while (Curr) { |
| Cases.push_back( |
| CaseInfo(Curr, SourceRange(Curr->getBeginLoc(), NextLoc))); |
| NextLoc = Curr->getBeginLoc(); |
| Curr = Curr->getNextSwitchCase(); |
| } |
| return true; |
| } |
| |
| Stmt *getCaseParent(SwitchCase *S) { |
| Stmt *Parent = PMap.getParent(S); |
| while (Parent && (isa<SwitchCase>(Parent) || isa<LabelStmt>(Parent))) |
| Parent = PMap.getParent(Parent); |
| return Parent; |
| } |
| }; |
| |
| class ProtectedScopeFixer { |
| MigrationPass &Pass; |
| SourceManager &SM; |
| SmallVector<CaseInfo, 16> Cases; |
| SmallVector<DeclRefExpr *, 16> LocalRefs; |
| |
| public: |
| ProtectedScopeFixer(BodyContext &BodyCtx) |
| : Pass(BodyCtx.getMigrationContext().Pass), |
| SM(Pass.Ctx.getSourceManager()) { |
| |
| CaseCollector(BodyCtx.getParentMap(), Cases) |
| .TraverseStmt(BodyCtx.getTopStmt()); |
| LocalRefsCollector(LocalRefs).TraverseStmt(BodyCtx.getTopStmt()); |
| |
| SourceRange BodyRange = BodyCtx.getTopStmt()->getSourceRange(); |
| const CapturedDiagList &DiagList = Pass.getDiags(); |
| // Copy the diagnostics so we don't have to worry about invaliding iterators |
| // from the diagnostic list. |
| SmallVector<StoredDiagnostic, 16> StoredDiags; |
| StoredDiags.append(DiagList.begin(), DiagList.end()); |
| SmallVectorImpl<StoredDiagnostic>::iterator |
| I = StoredDiags.begin(), E = StoredDiags.end(); |
| while (I != E) { |
| if (I->getID() == diag::err_switch_into_protected_scope && |
| isInRange(I->getLocation(), BodyRange)) { |
| handleProtectedScopeError(I, E); |
| continue; |
| } |
| ++I; |
| } |
| } |
| |
| void handleProtectedScopeError( |
| SmallVectorImpl<StoredDiagnostic>::iterator &DiagI, |
| SmallVectorImpl<StoredDiagnostic>::iterator DiagE){ |
| Transaction Trans(Pass.TA); |
| assert(DiagI->getID() == diag::err_switch_into_protected_scope); |
| SourceLocation ErrLoc = DiagI->getLocation(); |
| bool handledAllNotes = true; |
| ++DiagI; |
| for (; DiagI != DiagE && DiagI->getLevel() == DiagnosticsEngine::Note; |
| ++DiagI) { |
| if (!handleProtectedNote(*DiagI)) |
| handledAllNotes = false; |
| } |
| |
| if (handledAllNotes) |
| Pass.TA.clearDiagnostic(diag::err_switch_into_protected_scope, ErrLoc); |
| } |
| |
| bool handleProtectedNote(const StoredDiagnostic &Diag) { |
| assert(Diag.getLevel() == DiagnosticsEngine::Note); |
| |
| for (unsigned i = 0; i != Cases.size(); i++) { |
| CaseInfo &info = Cases[i]; |
| if (isInRange(Diag.getLocation(), info.Range)) { |
| |
| if (info.State == CaseInfo::St_Unchecked) |
| tryFixing(info); |
| assert(info.State != CaseInfo::St_Unchecked); |
| |
| if (info.State == CaseInfo::St_Fixed) { |
| Pass.TA.clearDiagnostic(Diag.getID(), Diag.getLocation()); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| void tryFixing(CaseInfo &info) { |
| assert(info.State == CaseInfo::St_Unchecked); |
| if (hasVarReferencedOutside(info)) { |
| info.State = CaseInfo::St_CannotFix; |
| return; |
| } |
| |
| Pass.TA.insertAfterToken(info.SC->getColonLoc(), " {"); |
| Pass.TA.insert(info.Range.getEnd(), "}\n"); |
| info.State = CaseInfo::St_Fixed; |
| } |
| |
| bool hasVarReferencedOutside(CaseInfo &info) { |
| for (unsigned i = 0, e = LocalRefs.size(); i != e; ++i) { |
| DeclRefExpr *DRE = LocalRefs[i]; |
| if (isInRange(DRE->getDecl()->getLocation(), info.Range) && |
| !isInRange(DRE->getLocation(), info.Range)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool isInRange(SourceLocation Loc, SourceRange R) { |
| if (Loc.isInvalid()) |
| return false; |
| return !SM.isBeforeInTranslationUnit(Loc, R.getBegin()) && |
| SM.isBeforeInTranslationUnit(Loc, R.getEnd()); |
| } |
| }; |
| |
| } // anonymous namespace |
| |
| void ProtectedScopeTraverser::traverseBody(BodyContext &BodyCtx) { |
| ProtectedScopeFixer Fix(BodyCtx); |
| } |