cmListFileCache.cxx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmListFileCache.h"
  4. #include <cassert>
  5. #include <memory>
  6. #include <sstream>
  7. #include <utility>
  8. #include "cmListFileLexer.h"
  9. #include "cmMessageType.h"
  10. #include "cmMessenger.h"
  11. #include "cmState.h"
  12. #include "cmStateDirectory.h"
  13. #include "cmStringAlgorithms.h"
  14. #include "cmSystemTools.h"
  15. cmCommandContext::cmCommandName& cmCommandContext::cmCommandName::operator=(
  16. std::string const& name)
  17. {
  18. this->Original = name;
  19. this->Lower = cmSystemTools::LowerCase(name);
  20. return *this;
  21. }
  22. struct cmListFileParser
  23. {
  24. cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
  25. cmMessenger* messenger);
  26. ~cmListFileParser();
  27. cmListFileParser(const cmListFileParser&) = delete;
  28. cmListFileParser& operator=(const cmListFileParser&) = delete;
  29. void IssueFileOpenError(std::string const& text) const;
  30. void IssueError(std::string const& text) const;
  31. bool ParseFile(const char* filename);
  32. bool ParseString(const char* str, const char* virtual_filename);
  33. bool Parse();
  34. bool ParseFunction(const char* name, long line);
  35. bool AddArgument(cmListFileLexer_Token* token,
  36. cmListFileArgument::Delimiter delim);
  37. cmListFile* ListFile;
  38. cmListFileBacktrace Backtrace;
  39. cmMessenger* Messenger;
  40. const char* FileName;
  41. cmListFileLexer* Lexer;
  42. cmListFileFunction Function;
  43. enum
  44. {
  45. SeparationOkay,
  46. SeparationWarning,
  47. SeparationError
  48. } Separation;
  49. };
  50. cmListFileParser::cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
  51. cmMessenger* messenger)
  52. : ListFile(lf)
  53. , Backtrace(std::move(lfbt))
  54. , Messenger(messenger)
  55. , FileName(nullptr)
  56. , Lexer(cmListFileLexer_New())
  57. {
  58. }
  59. cmListFileParser::~cmListFileParser()
  60. {
  61. cmListFileLexer_Delete(this->Lexer);
  62. }
  63. void cmListFileParser::IssueFileOpenError(const std::string& text) const
  64. {
  65. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text,
  66. this->Backtrace);
  67. }
  68. void cmListFileParser::IssueError(const std::string& text) const
  69. {
  70. cmListFileContext lfc;
  71. lfc.FilePath = this->FileName;
  72. lfc.Line = cmListFileLexer_GetCurrentLine(this->Lexer);
  73. cmListFileBacktrace lfbt = this->Backtrace;
  74. lfbt = lfbt.Push(lfc);
  75. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text, lfbt);
  76. cmSystemTools::SetFatalErrorOccured();
  77. }
  78. bool cmListFileParser::ParseFile(const char* filename)
  79. {
  80. this->FileName = filename;
  81. // Open the file.
  82. cmListFileLexer_BOM bom;
  83. if (!cmListFileLexer_SetFileName(this->Lexer, this->FileName, &bom)) {
  84. this->IssueFileOpenError("cmListFileCache: error can not open file.");
  85. return false;
  86. }
  87. if (bom == cmListFileLexer_BOM_Broken) {
  88. cmListFileLexer_SetFileName(this->Lexer, nullptr, nullptr);
  89. this->IssueFileOpenError("Error while reading Byte-Order-Mark. "
  90. "File not seekable?");
  91. return false;
  92. }
  93. // Verify the Byte-Order-Mark, if any.
  94. if (bom != cmListFileLexer_BOM_None && bom != cmListFileLexer_BOM_UTF8) {
  95. cmListFileLexer_SetFileName(this->Lexer, nullptr, nullptr);
  96. this->IssueFileOpenError(
  97. "File starts with a Byte-Order-Mark that is not UTF-8.");
  98. return false;
  99. }
  100. return Parse();
  101. }
  102. bool cmListFileParser::ParseString(const char* str,
  103. const char* virtual_filename)
  104. {
  105. this->FileName = virtual_filename;
  106. if (!cmListFileLexer_SetString(this->Lexer, str)) {
  107. this->IssueFileOpenError("cmListFileCache: cannot allocate buffer.");
  108. return false;
  109. }
  110. return Parse();
  111. }
  112. bool cmListFileParser::Parse()
  113. {
  114. // Use a simple recursive-descent parser to process the token
  115. // stream.
  116. bool haveNewline = true;
  117. while (cmListFileLexer_Token* token = cmListFileLexer_Scan(this->Lexer)) {
  118. if (token->type == cmListFileLexer_Token_Space) {
  119. } else if (token->type == cmListFileLexer_Token_Newline) {
  120. haveNewline = true;
  121. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  122. haveNewline = false;
  123. } else if (token->type == cmListFileLexer_Token_Identifier) {
  124. if (haveNewline) {
  125. haveNewline = false;
  126. if (this->ParseFunction(token->text, token->line)) {
  127. this->ListFile->Functions.push_back(this->Function);
  128. } else {
  129. return false;
  130. }
  131. } else {
  132. std::ostringstream error;
  133. error << "Parse error. Expected a newline, got "
  134. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  135. << " with text \"" << token->text << "\".";
  136. this->IssueError(error.str());
  137. return false;
  138. }
  139. } else {
  140. std::ostringstream error;
  141. error << "Parse error. Expected a command name, got "
  142. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  143. << " with text \"" << token->text << "\".";
  144. this->IssueError(error.str());
  145. return false;
  146. }
  147. }
  148. return true;
  149. }
  150. bool cmListFile::ParseFile(const char* filename, cmMessenger* messenger,
  151. cmListFileBacktrace const& lfbt)
  152. {
  153. if (!cmSystemTools::FileExists(filename) ||
  154. cmSystemTools::FileIsDirectory(filename)) {
  155. return false;
  156. }
  157. bool parseError = false;
  158. {
  159. cmListFileParser parser(this, lfbt, messenger);
  160. parseError = !parser.ParseFile(filename);
  161. }
  162. return !parseError;
  163. }
  164. bool cmListFile::ParseString(const char* str, const char* virtual_filename,
  165. cmMessenger* messenger,
  166. const cmListFileBacktrace& lfbt)
  167. {
  168. bool parseError = false;
  169. {
  170. cmListFileParser parser(this, lfbt, messenger);
  171. parseError = !parser.ParseString(str, virtual_filename);
  172. }
  173. return !parseError;
  174. }
  175. bool cmListFileParser::ParseFunction(const char* name, long line)
  176. {
  177. // Ininitialize a new function call.
  178. this->Function = cmListFileFunction();
  179. this->Function.Name = name;
  180. this->Function.Line = line;
  181. // Command name has already been parsed. Read the left paren.
  182. cmListFileLexer_Token* token;
  183. while ((token = cmListFileLexer_Scan(this->Lexer)) &&
  184. token->type == cmListFileLexer_Token_Space) {
  185. }
  186. if (!token) {
  187. std::ostringstream error;
  188. /* clang-format off */
  189. error << "Unexpected end of file.\n"
  190. << "Parse error. Function missing opening \"(\".";
  191. /* clang-format on */
  192. this->IssueError(error.str());
  193. return false;
  194. }
  195. if (token->type != cmListFileLexer_Token_ParenLeft) {
  196. std::ostringstream error;
  197. error << "Parse error. Expected \"(\", got "
  198. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  199. << " with text \"" << token->text << "\".";
  200. this->IssueError(error.str());
  201. return false;
  202. }
  203. // Arguments.
  204. unsigned long parenDepth = 0;
  205. this->Separation = SeparationOkay;
  206. while ((token = cmListFileLexer_Scan(this->Lexer))) {
  207. if (token->type == cmListFileLexer_Token_Space ||
  208. token->type == cmListFileLexer_Token_Newline) {
  209. this->Separation = SeparationOkay;
  210. continue;
  211. }
  212. if (token->type == cmListFileLexer_Token_ParenLeft) {
  213. parenDepth++;
  214. this->Separation = SeparationOkay;
  215. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  216. return false;
  217. }
  218. } else if (token->type == cmListFileLexer_Token_ParenRight) {
  219. if (parenDepth == 0) {
  220. return true;
  221. }
  222. parenDepth--;
  223. this->Separation = SeparationOkay;
  224. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  225. return false;
  226. }
  227. this->Separation = SeparationWarning;
  228. } else if (token->type == cmListFileLexer_Token_Identifier ||
  229. token->type == cmListFileLexer_Token_ArgumentUnquoted) {
  230. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  231. return false;
  232. }
  233. this->Separation = SeparationWarning;
  234. } else if (token->type == cmListFileLexer_Token_ArgumentQuoted) {
  235. if (!this->AddArgument(token, cmListFileArgument::Quoted)) {
  236. return false;
  237. }
  238. this->Separation = SeparationWarning;
  239. } else if (token->type == cmListFileLexer_Token_ArgumentBracket) {
  240. if (!this->AddArgument(token, cmListFileArgument::Bracket)) {
  241. return false;
  242. }
  243. this->Separation = SeparationError;
  244. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  245. this->Separation = SeparationError;
  246. } else {
  247. // Error.
  248. std::ostringstream error;
  249. error << "Parse error. Function missing ending \")\". "
  250. << "Instead found "
  251. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  252. << " with text \"" << token->text << "\".";
  253. this->IssueError(error.str());
  254. return false;
  255. }
  256. }
  257. std::ostringstream error;
  258. cmListFileContext lfc;
  259. lfc.FilePath = this->FileName;
  260. lfc.Line = line;
  261. cmListFileBacktrace lfbt = this->Backtrace;
  262. lfbt = lfbt.Push(lfc);
  263. error << "Parse error. Function missing ending \")\". "
  264. << "End of file reached.";
  265. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, error.str(), lfbt);
  266. return false;
  267. }
  268. bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
  269. cmListFileArgument::Delimiter delim)
  270. {
  271. this->Function.Arguments.emplace_back(token->text, delim, token->line);
  272. if (this->Separation == SeparationOkay) {
  273. return true;
  274. }
  275. bool isError = (this->Separation == SeparationError ||
  276. delim == cmListFileArgument::Bracket);
  277. std::ostringstream m;
  278. cmListFileContext lfc;
  279. lfc.FilePath = this->FileName;
  280. lfc.Line = token->line;
  281. cmListFileBacktrace lfbt = this->Backtrace;
  282. lfbt = lfbt.Push(lfc);
  283. m << "Syntax " << (isError ? "Error" : "Warning") << " in cmake code at "
  284. << "column " << token->column << "\n"
  285. << "Argument not separated from preceding token by whitespace.";
  286. /* clang-format on */
  287. if (isError) {
  288. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, m.str(), lfbt);
  289. return false;
  290. }
  291. this->Messenger->IssueMessage(MessageType::AUTHOR_WARNING, m.str(), lfbt);
  292. return true;
  293. }
  294. // We hold either the bottom scope of a directory or a call/file context.
  295. // Discriminate these cases via the parent pointer.
  296. struct cmListFileBacktrace::Entry
  297. {
  298. Entry(cmStateSnapshot bottom)
  299. : Bottom(bottom)
  300. {
  301. }
  302. Entry(std::shared_ptr<Entry const> parent, cmListFileContext lfc)
  303. : Context(std::move(lfc))
  304. , Parent(std::move(parent))
  305. {
  306. }
  307. ~Entry()
  308. {
  309. if (this->Parent) {
  310. this->Context.~cmListFileContext();
  311. } else {
  312. this->Bottom.~cmStateSnapshot();
  313. }
  314. }
  315. bool IsBottom() const { return !this->Parent; }
  316. union
  317. {
  318. cmStateSnapshot Bottom;
  319. cmListFileContext Context;
  320. };
  321. std::shared_ptr<Entry const> Parent;
  322. };
  323. cmListFileBacktrace::cmListFileBacktrace(cmStateSnapshot const& snapshot)
  324. : TopEntry(std::make_shared<Entry const>(snapshot.GetCallStackBottom()))
  325. {
  326. }
  327. /* NOLINTNEXTLINE(performance-unnecessary-value-param) */
  328. cmListFileBacktrace::cmListFileBacktrace(std::shared_ptr<Entry const> parent,
  329. cmListFileContext const& lfc)
  330. : TopEntry(std::make_shared<Entry const>(std::move(parent), lfc))
  331. {
  332. }
  333. cmListFileBacktrace::cmListFileBacktrace(std::shared_ptr<Entry const> top)
  334. : TopEntry(std::move(top))
  335. {
  336. }
  337. cmStateSnapshot cmListFileBacktrace::GetBottom() const
  338. {
  339. cmStateSnapshot bottom;
  340. if (Entry const* cur = this->TopEntry.get()) {
  341. while (Entry const* parent = cur->Parent.get()) {
  342. cur = parent;
  343. }
  344. bottom = cur->Bottom;
  345. }
  346. return bottom;
  347. }
  348. cmListFileBacktrace cmListFileBacktrace::Push(std::string const& file) const
  349. {
  350. // We are entering a file-level scope but have not yet reached
  351. // any specific line or command invocation within it. This context
  352. // is useful to print when it is at the top but otherwise can be
  353. // skipped during call stack printing.
  354. cmListFileContext lfc;
  355. lfc.FilePath = file;
  356. return this->Push(lfc);
  357. }
  358. cmListFileBacktrace cmListFileBacktrace::Push(
  359. cmListFileContext const& lfc) const
  360. {
  361. assert(this->TopEntry);
  362. assert(!this->TopEntry->IsBottom() || this->TopEntry->Bottom.IsValid());
  363. return cmListFileBacktrace(this->TopEntry, lfc);
  364. }
  365. cmListFileBacktrace cmListFileBacktrace::Pop() const
  366. {
  367. assert(this->TopEntry);
  368. assert(!this->TopEntry->IsBottom());
  369. return cmListFileBacktrace(this->TopEntry->Parent);
  370. }
  371. cmListFileContext const& cmListFileBacktrace::Top() const
  372. {
  373. assert(this->TopEntry);
  374. assert(!this->TopEntry->IsBottom());
  375. return this->TopEntry->Context;
  376. }
  377. void cmListFileBacktrace::PrintTitle(std::ostream& out) const
  378. {
  379. // The title exists only if we have a call on top of the bottom.
  380. if (!this->TopEntry || this->TopEntry->IsBottom()) {
  381. return;
  382. }
  383. cmListFileContext lfc = this->TopEntry->Context;
  384. cmStateSnapshot bottom = this->GetBottom();
  385. if (!bottom.GetState()->GetIsInTryCompile()) {
  386. lfc.FilePath = bottom.GetDirectory().ConvertToRelPathIfNotContained(
  387. bottom.GetState()->GetSourceDirectory(), lfc.FilePath);
  388. }
  389. out << (lfc.Line ? " at " : " in ") << lfc;
  390. }
  391. void cmListFileBacktrace::PrintCallStack(std::ostream& out) const
  392. {
  393. // The call stack exists only if we have at least two calls on top
  394. // of the bottom.
  395. if (!this->TopEntry || this->TopEntry->IsBottom() ||
  396. this->TopEntry->Parent->IsBottom()) {
  397. return;
  398. }
  399. bool first = true;
  400. cmStateSnapshot bottom = this->GetBottom();
  401. for (Entry const* cur = this->TopEntry->Parent.get(); !cur->IsBottom();
  402. cur = cur->Parent.get()) {
  403. if (cur->Context.Name.empty() &&
  404. cur->Context.Line != cmListFileContext::DeferPlaceholderLine) {
  405. // Skip this whole-file scope. When we get here we already will
  406. // have printed a more-specific context within the file.
  407. continue;
  408. }
  409. if (first) {
  410. first = false;
  411. out << "Call Stack (most recent call first):\n";
  412. }
  413. cmListFileContext lfc = cur->Context;
  414. if (!bottom.GetState()->GetIsInTryCompile()) {
  415. lfc.FilePath = bottom.GetDirectory().ConvertToRelPathIfNotContained(
  416. bottom.GetState()->GetSourceDirectory(), lfc.FilePath);
  417. }
  418. out << " " << lfc << "\n";
  419. }
  420. }
  421. size_t cmListFileBacktrace::Depth() const
  422. {
  423. size_t depth = 0;
  424. if (Entry const* cur = this->TopEntry.get()) {
  425. for (; !cur->IsBottom(); cur = cur->Parent.get()) {
  426. ++depth;
  427. }
  428. }
  429. return depth;
  430. }
  431. bool cmListFileBacktrace::Empty() const
  432. {
  433. return !this->TopEntry || this->TopEntry->IsBottom();
  434. }
  435. std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
  436. {
  437. os << lfc.FilePath;
  438. if (lfc.Line > 0) {
  439. os << ":" << lfc.Line;
  440. if (!lfc.Name.empty()) {
  441. os << " (" << lfc.Name << ")";
  442. }
  443. } else if (lfc.Line == cmListFileContext::DeferPlaceholderLine) {
  444. os << ":DEFERRED";
  445. }
  446. return os;
  447. }
  448. bool operator<(const cmListFileContext& lhs, const cmListFileContext& rhs)
  449. {
  450. if (lhs.Line != rhs.Line) {
  451. return lhs.Line < rhs.Line;
  452. }
  453. return lhs.FilePath < rhs.FilePath;
  454. }
  455. bool operator==(const cmListFileContext& lhs, const cmListFileContext& rhs)
  456. {
  457. return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
  458. }
  459. bool operator!=(const cmListFileContext& lhs, const cmListFileContext& rhs)
  460. {
  461. return !(lhs == rhs);
  462. }
  463. std::ostream& operator<<(std::ostream& os, BT<std::string> const& s)
  464. {
  465. return os << s.Value;
  466. }
  467. std::vector<BT<std::string>> ExpandListWithBacktrace(
  468. std::string const& list, cmListFileBacktrace const& bt)
  469. {
  470. std::vector<BT<std::string>> result;
  471. std::vector<std::string> tmp = cmExpandedList(list);
  472. result.reserve(tmp.size());
  473. for (std::string& i : tmp) {
  474. result.emplace_back(std::move(i), bt);
  475. }
  476. return result;
  477. }