//===--- DataflowDiagnostics.cpp - Emits diagnostics based on SIL analysis ===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Utils/ConstExpr.h" #include "swift/AST/ASTContext.h" #include "swift/AST/DiagnosticEngine.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/DiagnosticsSIL.h" #include "swift/AST/Expr.h" #include "swift/AST/Stmt.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/SILLocation.h" #include "swift/SIL/SILModule.h" #include "swift/SIL/SILVisitor.h" #include "swift/SIL/SILConstants.h" using namespace swift; template static void diagnose(ASTContext &Context, SourceLoc loc, Diag diag, U &&...args) { Context.Diags.diagnose(loc, diag, std::forward(args)...); } static void diagnoseMissingReturn(const UnreachableInst *UI, ASTContext &Context) { const SILBasicBlock *BB = UI->getParent(); const SILFunction *F = BB->getParent(); SILLocation FLoc = F->getLocation(); Type ResTy; BraceStmt *BS; if (auto *FD = FLoc.getAsASTNode()) { ResTy = FD->getResultInterfaceType(); BS = FD->getBody(/*canSynthesize=*/false); } else if (auto *CD = FLoc.getAsASTNode()) { ResTy = CD->getResultInterfaceType(); BS = FD->getBody(); } else if (auto *CE = FLoc.getAsASTNode()) { ResTy = CE->getResultType(); BS = CE->getBody(); } else { llvm_unreachable("unhandled case in MissingReturn"); } SILLocation L = UI->getLoc(); assert(L && ResTy); auto numElements = BS->getNumElements(); if (numElements > 0) { auto element = BS->getElement(numElements - 1); if (auto expr = element.dyn_cast()) { if (expr->getType()->isEqual(ResTy)) { Context.Diags.diagnose( expr->getStartLoc(), diag::missing_return_last_expr, ResTy, FLoc.isASTNode() ? 1 : 0) .fixItInsert(expr->getStartLoc(), "return "); return; } } } auto diagID = F->isNoReturnFunction() ? diag::missing_never_call : diag::missing_return; diagnose(Context, L.getEndSourceLoc(), diagID, ResTy, FLoc.isASTNode() ? 1 : 0); } static void diagnoseUnreachable(const SILInstruction *I, ASTContext &Context) { if (auto *UI = dyn_cast(I)) { SILLocation L = UI->getLoc(); // Invalid location means that the instruction has been generated by SIL // passes, such as DCE. FIXME: we might want to just introduce a separate // instruction kind, instead of keeping this invariant. // // We also do not want to emit diagnostics for code that was // transparently inlined. We should have already emitted these // diagnostics when we process the callee function prior to // inlining it. if (!L || L.is()) return; // The most common case of getting an unreachable instruction is a // missing return statement. In this case, we know that the instruction // location will be the enclosing function. if (L.isASTNode() || L.isASTNode()) { diagnoseMissingReturn(UI, Context); return; } if (auto *Guard = L.getAsASTNode()) { diagnose(Context, Guard->getBody()->getEndLoc(), diag::guard_body_must_not_fallthrough); return; } } } /// Issue diagnostics whenever we see Builtin.static_report(1, ...). static void diagnoseStaticReports(const SILInstruction *I, SILModule &M) { // Find out if we are dealing with Builtin.staticReport(). if (auto *BI = dyn_cast(I)) { const BuiltinInfo &B = BI->getBuiltinInfo(); if (B.ID == BuiltinValueKind::StaticReport) { // Report diagnostic if the first argument has been folded to '1'. OperandValueArrayRef Args = BI->getArguments(); auto *V = dyn_cast(Args[0]); if (!V || V->getValue() != 1) return; diagnose(M.getASTContext(), I->getLoc().getSourceLoc(), diag::static_report_error); } } } /// Emit a diagnostic for `poundAssert` builtins whose condition is /// false or whose condition cannot be evaluated. static void diagnosePoundAssert(const SILInstruction *I, SILModule &M, ConstExprEvaluator &constantEvaluator) { auto *builtinInst = dyn_cast(I); if (!builtinInst || builtinInst->getBuiltinKind() != BuiltinValueKind::PoundAssert) return; SmallVector values; constantEvaluator.computeConstantValues({builtinInst->getArguments()[0]}, values); SymbolicValue value = values[0]; if (!value.isConstant()) { diagnose(M.getASTContext(), I->getLoc().getSourceLoc(), diag::pound_assert_condition_not_constant); // If we have more specific information about what went wrong, emit // notes. if (value.getKind() == SymbolicValue::Unknown) value.emitUnknownDiagnosticNotes(builtinInst->getLoc()); return; } assert(value.getKind() == SymbolicValue::Integer && "sema prevents non-integer #assert condition"); APInt intValue = value.getIntegerValue(); assert(intValue.getBitWidth() == 1 && "sema prevents non-int1 #assert condition"); if (intValue.isNullValue()) { auto *message = cast(builtinInst->getArguments()[1]); StringRef messageValue = message->getValue(); if (messageValue.empty()) messageValue = "assertion failed"; diagnose(M.getASTContext(), I->getLoc().getSourceLoc(), diag::pound_assert_failure, messageValue); return; } } namespace { class EmitDFDiagnostics : public SILFunctionTransform { ~EmitDFDiagnostics() override {} /// The entry point to the transformation. void run() override { // Don't rerun diagnostics on deserialized functions. if (getFunction()->wasDeserializedCanonical()) return; SILModule &M = getFunction()->getModule(); for (auto &BB : *getFunction()) for (auto &I : BB) { diagnoseUnreachable(&I, M.getASTContext()); diagnoseStaticReports(&I, M); } if (M.getASTContext().LangOpts.EnableExperimentalStaticAssert) { SymbolicValueBumpAllocator allocator; ConstExprEvaluator constantEvaluator(allocator); for (auto &BB : *getFunction()) for (auto &I : BB) diagnosePoundAssert(&I, M, constantEvaluator); } } }; } // end anonymous namespace SILTransform *swift::createEmitDFDiagnostics() { return new EmitDFDiagnostics(); }