cmListFileCache.cxx 13 KB

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