1 //===--- FrontendAction.cpp -----------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "flang/Frontend/FrontendAction.h"
10 #include "flang/Frontend/CompilerInstance.h"
11 #include "flang/Frontend/FrontendActions.h"
12 #include "flang/Frontend/FrontendOptions.h"
13 #include "flang/Frontend/FrontendPluginRegistry.h"
14 #include "flang/FrontendTool/Utils.h"
15 #include "clang/Basic/DiagnosticFrontend.h"
16 #include "llvm/Support/Errc.h"
17 #include "llvm/Support/VirtualFileSystem.h"
18 
19 using namespace Fortran::frontend;
20 
21 LLVM_INSTANTIATE_REGISTRY(FrontendPluginRegistry)
22 
23 void FrontendAction::set_currentInput(const FrontendInputFile &currentInput) {
24   this->currentInput_ = currentInput;
25 }
26 
27 // Call this method if BeginSourceFile fails.
28 // Deallocate compiler instance, input and output descriptors
29 static void BeginSourceFileCleanUp(FrontendAction &fa, CompilerInstance &ci) {
30   ci.ClearOutputFiles(/*EraseFiles=*/true);
31   fa.set_currentInput(FrontendInputFile());
32   fa.set_instance(nullptr);
33 }
34 
35 bool FrontendAction::BeginSourceFile(
36     CompilerInstance &ci, const FrontendInputFile &realInput) {
37 
38   FrontendInputFile input(realInput);
39 
40   // Return immediately if the input file does not exist or is not a file. Note
41   // that we cannot check this for input from stdin.
42   if (input.file() != "-") {
43     if (!llvm::sys::fs::is_regular_file(input.file())) {
44       // Create an diagnostic ID to report
45       unsigned diagID;
46       if (llvm::vfs::getRealFileSystem()->exists(input.file())) {
47         ci.diagnostics().Report(clang::diag::err_fe_error_reading)
48             << input.file();
49         diagID = ci.diagnostics().getCustomDiagID(
50             clang::DiagnosticsEngine::Error, "%0 is not a regular file");
51       } else {
52         diagID = ci.diagnostics().getCustomDiagID(
53             clang::DiagnosticsEngine::Error, "%0 does not exist");
54       }
55 
56       // Report the diagnostic and return
57       ci.diagnostics().Report(diagID) << input.file();
58       BeginSourceFileCleanUp(*this, ci);
59       return false;
60     }
61   }
62 
63   assert(!instance_ && "Already processing a source file!");
64   assert(!realInput.IsEmpty() && "Unexpected empty filename!");
65   set_currentInput(realInput);
66   set_instance(&ci);
67 
68   if (!ci.HasAllSources()) {
69     BeginSourceFileCleanUp(*this, ci);
70     return false;
71   }
72 
73   auto &invoc = ci.invocation();
74 
75   // Include command-line and predefined preprocessor macros. Use either:
76   //  * `-cpp/-nocpp`, or
77   //  * the file extension (if the user didn't express any preference)
78   // to decide whether to include them or not.
79   if ((invoc.preprocessorOpts().macrosFlag == PPMacrosFlag::Include) ||
80       (invoc.preprocessorOpts().macrosFlag == PPMacrosFlag::Unknown &&
81           currentInput().MustBePreprocessed())) {
82     invoc.setDefaultPredefinitions();
83     invoc.collectMacroDefinitions();
84   }
85 
86   // Decide between fixed and free form (if the user didn't express any
87   // preference, use the file extension to decide)
88   if (invoc.frontendOpts().fortranForm == FortranForm::Unknown) {
89     invoc.fortranOpts().isFixedForm = currentInput().IsFixedForm();
90   }
91 
92   if (!BeginSourceFileAction()) {
93     BeginSourceFileCleanUp(*this, ci);
94     return false;
95   }
96 
97   return true;
98 }
99 
100 bool FrontendAction::ShouldEraseOutputFiles() {
101   return instance().diagnostics().hasErrorOccurred();
102 }
103 
104 llvm::Error FrontendAction::Execute() {
105   ExecuteAction();
106 
107   return llvm::Error::success();
108 }
109 
110 void FrontendAction::EndSourceFile() {
111   CompilerInstance &ci = instance();
112 
113   // Cleanup the output streams, and erase the output files if instructed by the
114   // FrontendAction.
115   ci.ClearOutputFiles(/*EraseFiles=*/ShouldEraseOutputFiles());
116 
117   set_instance(nullptr);
118   set_currentInput(FrontendInputFile());
119 }
120 
121 bool FrontendAction::RunPrescan() {
122   CompilerInstance &ci = this->instance();
123   std::string currentInputPath{GetCurrentFileOrBufferName()};
124   Fortran::parser::Options parserOptions = ci.invocation().fortranOpts();
125 
126   if (ci.invocation().frontendOpts().fortranForm == FortranForm::Unknown) {
127     // Switch between fixed and free form format based on the input file
128     // extension.
129     //
130     // Ideally we should have all Fortran options set before entering this
131     // method (i.e. before processing any specific input files). However, we
132     // can't decide between fixed and free form based on the file extension
133     // earlier than this.
134     parserOptions.isFixedForm = currentInput().IsFixedForm();
135   }
136 
137   // Prescan. In case of failure, report and return.
138   ci.parsing().Prescan(currentInputPath, parserOptions);
139 
140   return !reportFatalScanningErrors();
141 }
142 
143 bool FrontendAction::RunParse() {
144   CompilerInstance &ci = this->instance();
145 
146   // Parse. In case of failure, report and return.
147   ci.parsing().Parse(llvm::outs());
148 
149   if (reportFatalParsingErrors()) {
150     return false;
151   }
152 
153   // Report the diagnostics from parsing
154   ci.parsing().messages().Emit(llvm::errs(), ci.allCookedSources());
155 
156   return true;
157 }
158 
159 bool FrontendAction::RunSemanticChecks() {
160   CompilerInstance &ci = this->instance();
161   std::optional<parser::Program> &parseTree{ci.parsing().parseTree()};
162   assert(parseTree && "Cannot run semantic checks without a parse tree!");
163 
164   // Prepare semantics
165   ci.setSemantics(std::make_unique<Fortran::semantics::Semantics>(
166       ci.invocation().semanticsContext(), *parseTree,
167       ci.invocation().debugModuleDir()));
168   auto &semantics = ci.semantics();
169 
170   // Run semantic checks
171   semantics.Perform();
172 
173   if (reportFatalSemanticErrors()) {
174     return false;
175   }
176 
177   // Report the diagnostics from the semantic checks
178   semantics.EmitMessages(ci.semaOutputStream());
179 
180   return true;
181 }
182 
183 template <unsigned N>
184 bool FrontendAction::reportFatalErrors(const char (&message)[N]) {
185   if (!instance_->parsing().messages().empty() &&
186       (instance_->invocation().warnAsErr() ||
187           instance_->parsing().messages().AnyFatalError())) {
188     const unsigned diagID = instance_->diagnostics().getCustomDiagID(
189         clang::DiagnosticsEngine::Error, message);
190     instance_->diagnostics().Report(diagID) << GetCurrentFileOrBufferName();
191     instance_->parsing().messages().Emit(
192         llvm::errs(), instance_->allCookedSources());
193     return true;
194   }
195   return false;
196 }
197 
198 bool FrontendAction::reportFatalSemanticErrors() {
199   auto &diags = instance_->diagnostics();
200   auto &sema = instance_->semantics();
201 
202   if (instance_->semantics().AnyFatalError()) {
203     unsigned DiagID = diags.getCustomDiagID(
204         clang::DiagnosticsEngine::Error, "Semantic errors in %0");
205     diags.Report(DiagID) << GetCurrentFileOrBufferName();
206     sema.EmitMessages(instance_->semaOutputStream());
207 
208     return true;
209   }
210 
211   return false;
212 }
213