cmListFileCache.cxx 17 KB

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