cmListFileCache.cxx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #define cmListFileCache_cxx
  4. #include "cmListFileCache.h"
  5. #include <memory>
  6. #include <sstream>
  7. #include <utility>
  8. #ifdef _WIN32
  9. # include <cmsys/Encoding.hxx>
  10. #endif
  11. #include "cmList.h"
  12. #include "cmListFileLexer.h"
  13. #include "cmMessageType.h"
  14. #include "cmMessenger.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 = nullptr;
  36. cmListFileLexer* Lexer;
  37. std::string FunctionName;
  38. long FunctionLine;
  39. long FunctionLineEnd;
  40. std::vector<cmListFileArgument> FunctionArguments;
  41. enum
  42. {
  43. SeparationOkay,
  44. SeparationWarning,
  45. SeparationError
  46. } Separation;
  47. };
  48. cmListFileParser::cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
  49. cmMessenger* messenger)
  50. : ListFile(lf)
  51. , Backtrace(std::move(lfbt))
  52. , Messenger(messenger)
  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::SetFatalErrorOccurred();
  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. this->FunctionLineEnd, 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::SetFatalErrorOccurred();
  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. this->FunctionLineEnd = token->line;
  233. return true;
  234. }
  235. parenDepth--;
  236. this->Separation = SeparationOkay;
  237. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  238. return false;
  239. }
  240. this->Separation = SeparationWarning;
  241. } else if (token->type == cmListFileLexer_Token_Identifier ||
  242. token->type == cmListFileLexer_Token_ArgumentUnquoted) {
  243. if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
  244. return false;
  245. }
  246. this->Separation = SeparationWarning;
  247. } else if (token->type == cmListFileLexer_Token_ArgumentQuoted) {
  248. if (!this->AddArgument(token, cmListFileArgument::Quoted)) {
  249. return false;
  250. }
  251. this->Separation = SeparationWarning;
  252. } else if (token->type == cmListFileLexer_Token_ArgumentBracket) {
  253. if (!this->AddArgument(token, cmListFileArgument::Bracket)) {
  254. return false;
  255. }
  256. this->Separation = SeparationError;
  257. } else if (token->type == cmListFileLexer_Token_CommentBracket) {
  258. this->Separation = SeparationError;
  259. } else {
  260. // Error.
  261. std::ostringstream error;
  262. error << "Parse error. Function missing ending \")\". "
  263. << "Instead found "
  264. << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
  265. << " with text \"" << token->text << "\".";
  266. this->IssueError(error.str());
  267. return false;
  268. }
  269. }
  270. std::ostringstream error;
  271. cmListFileContext lfc;
  272. lfc.FilePath = this->FileName;
  273. lfc.Line = line;
  274. cmListFileBacktrace lfbt = this->Backtrace;
  275. lfbt = lfbt.Push(lfc);
  276. error << "Parse error. Function missing ending \")\". "
  277. << "End of file reached.";
  278. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, error.str(), lfbt);
  279. return false;
  280. }
  281. bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
  282. cmListFileArgument::Delimiter delim)
  283. {
  284. this->FunctionArguments.emplace_back(token->text, delim, token->line);
  285. if (this->Separation == SeparationOkay) {
  286. return true;
  287. }
  288. bool isError = (this->Separation == SeparationError ||
  289. delim == cmListFileArgument::Bracket);
  290. std::ostringstream m;
  291. cmListFileContext lfc;
  292. lfc.FilePath = this->FileName;
  293. lfc.Line = token->line;
  294. cmListFileBacktrace lfbt = this->Backtrace;
  295. lfbt = lfbt.Push(lfc);
  296. m << "Syntax " << (isError ? "Error" : "Warning") << " in cmake code at "
  297. << "column " << token->column << "\n"
  298. << "Argument not separated from preceding token by whitespace.";
  299. /* clang-format on */
  300. if (isError) {
  301. this->Messenger->IssueMessage(MessageType::FATAL_ERROR, m.str(), lfbt);
  302. return false;
  303. }
  304. this->Messenger->IssueMessage(MessageType::AUTHOR_WARNING, m.str(), lfbt);
  305. return true;
  306. }
  307. namespace {
  308. enum class NestingStateEnum
  309. {
  310. If,
  311. Else,
  312. While,
  313. Foreach,
  314. Function,
  315. Macro,
  316. Block
  317. };
  318. struct NestingState
  319. {
  320. NestingStateEnum State;
  321. cmListFileContext Context;
  322. };
  323. bool TopIs(std::vector<NestingState>& stack, NestingStateEnum state)
  324. {
  325. return !stack.empty() && stack.back().State == state;
  326. }
  327. }
  328. cm::optional<cmListFileContext> cmListFileParser::CheckNesting() const
  329. {
  330. std::vector<NestingState> stack;
  331. for (auto const& func : this->ListFile->Functions) {
  332. auto const& name = func.LowerCaseName();
  333. if (name == "if") {
  334. stack.push_back({
  335. NestingStateEnum::If,
  336. cmListFileContext::FromListFileFunction(func, this->FileName),
  337. });
  338. } else if (name == "elseif") {
  339. if (!TopIs(stack, NestingStateEnum::If)) {
  340. return cmListFileContext::FromListFileFunction(func, this->FileName);
  341. }
  342. stack.back() = {
  343. NestingStateEnum::If,
  344. cmListFileContext::FromListFileFunction(func, this->FileName),
  345. };
  346. } else if (name == "else") {
  347. if (!TopIs(stack, NestingStateEnum::If)) {
  348. return cmListFileContext::FromListFileFunction(func, this->FileName);
  349. }
  350. stack.back() = {
  351. NestingStateEnum::Else,
  352. cmListFileContext::FromListFileFunction(func, this->FileName),
  353. };
  354. } else if (name == "endif") {
  355. if (!TopIs(stack, NestingStateEnum::If) &&
  356. !TopIs(stack, NestingStateEnum::Else)) {
  357. return cmListFileContext::FromListFileFunction(func, this->FileName);
  358. }
  359. stack.pop_back();
  360. } else if (name == "while") {
  361. stack.push_back({
  362. NestingStateEnum::While,
  363. cmListFileContext::FromListFileFunction(func, this->FileName),
  364. });
  365. } else if (name == "endwhile") {
  366. if (!TopIs(stack, NestingStateEnum::While)) {
  367. return cmListFileContext::FromListFileFunction(func, this->FileName);
  368. }
  369. stack.pop_back();
  370. } else if (name == "foreach") {
  371. stack.push_back({
  372. NestingStateEnum::Foreach,
  373. cmListFileContext::FromListFileFunction(func, this->FileName),
  374. });
  375. } else if (name == "endforeach") {
  376. if (!TopIs(stack, NestingStateEnum::Foreach)) {
  377. return cmListFileContext::FromListFileFunction(func, this->FileName);
  378. }
  379. stack.pop_back();
  380. } else if (name == "function") {
  381. stack.push_back({
  382. NestingStateEnum::Function,
  383. cmListFileContext::FromListFileFunction(func, this->FileName),
  384. });
  385. } else if (name == "endfunction") {
  386. if (!TopIs(stack, NestingStateEnum::Function)) {
  387. return cmListFileContext::FromListFileFunction(func, this->FileName);
  388. }
  389. stack.pop_back();
  390. } else if (name == "macro") {
  391. stack.push_back({
  392. NestingStateEnum::Macro,
  393. cmListFileContext::FromListFileFunction(func, this->FileName),
  394. });
  395. } else if (name == "endmacro") {
  396. if (!TopIs(stack, NestingStateEnum::Macro)) {
  397. return cmListFileContext::FromListFileFunction(func, this->FileName);
  398. }
  399. stack.pop_back();
  400. } else if (name == "block") {
  401. stack.push_back({
  402. NestingStateEnum::Block,
  403. cmListFileContext::FromListFileFunction(func, this->FileName),
  404. });
  405. } else if (name == "endblock") {
  406. if (!TopIs(stack, NestingStateEnum::Block)) {
  407. return cmListFileContext::FromListFileFunction(func, this->FileName);
  408. }
  409. stack.pop_back();
  410. }
  411. }
  412. if (!stack.empty()) {
  413. return stack.back().Context;
  414. }
  415. return cm::nullopt;
  416. }
  417. #include "cmConstStack.tcc"
  418. template class cmConstStack<cmListFileContext, cmListFileBacktrace>;
  419. std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
  420. {
  421. os << lfc.FilePath;
  422. if (lfc.Line > 0) {
  423. os << ":" << lfc.Line;
  424. if (!lfc.Name.empty()) {
  425. os << " (" << lfc.Name << ")";
  426. }
  427. } else if (lfc.Line == cmListFileContext::DeferPlaceholderLine) {
  428. os << ":DEFERRED";
  429. }
  430. return os;
  431. }
  432. bool operator<(const cmListFileContext& lhs, const cmListFileContext& rhs)
  433. {
  434. if (lhs.Line != rhs.Line) {
  435. return lhs.Line < rhs.Line;
  436. }
  437. return lhs.FilePath < rhs.FilePath;
  438. }
  439. bool operator==(const cmListFileContext& lhs, const cmListFileContext& rhs)
  440. {
  441. return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
  442. }
  443. bool operator!=(const cmListFileContext& lhs, const cmListFileContext& rhs)
  444. {
  445. return !(lhs == rhs);
  446. }
  447. std::ostream& operator<<(std::ostream& os, BT<std::string> const& s)
  448. {
  449. return os << s.Value;
  450. }
  451. std::vector<BT<std::string>> cmExpandListWithBacktrace(
  452. std::string const& list, cmListFileBacktrace const& bt,
  453. cmList::EmptyElements emptyArgs)
  454. {
  455. std::vector<BT<std::string>> result;
  456. cmList tmp{ list, emptyArgs };
  457. result.reserve(tmp.size());
  458. for (std::string& i : tmp) {
  459. result.emplace_back(std::move(i), bt);
  460. }
  461. return result;
  462. }