Fixes memory leak when an exception goes through runCvc4(). (Fixes #4590) (#4750)
[cvc5.git] / src / main / driver_unified.cpp
1 /********************* */
2 /*! \file driver_unified.cpp
3 ** \verbatim
4 ** Top contributors (to current version):
5 ** Morgan Deters, Liana Hadarean, Tim King
6 ** This file is part of the CVC4 project.
7 ** Copyright (c) 2009-2020 by the authors listed in the file AUTHORS
8 ** in the top-level source directory) and their institutional affiliations.
9 ** All rights reserved. See the file COPYING in the top-level source
10 ** directory for licensing information.\endverbatim
11 **
12 ** \brief Driver for CVC4 executable (cvc4)
13 **/
14
15 #include <stdio.h>
16 #include <unistd.h>
17
18 #include <cstdlib>
19 #include <cstring>
20 #include <fstream>
21 #include <iostream>
22 #include <memory>
23 #include <new>
24
25 #include "cvc4autoconfig.h"
26
27 #include "api/cvc4cpp.h"
28 #include "base/configuration.h"
29 #include "base/output.h"
30 #include "expr/expr_iomanip.h"
31 #include "expr/expr_manager.h"
32 #include "main/command_executor.h"
33 #include "main/interactive_shell.h"
34 #include "main/main.h"
35 #include "main/signal_handlers.h"
36 #include "main/time_limit.h"
37 #include "options/options.h"
38 #include "options/set_language.h"
39 #include "parser/parser.h"
40 #include "parser/parser_builder.h"
41 #include "parser/parser_exception.h"
42 #include "smt/command.h"
43 #include "util/result.h"
44 #include "util/statistics_registry.h"
45
46 using namespace std;
47 using namespace CVC4;
48 using namespace CVC4::parser;
49 using namespace CVC4::main;
50
51 namespace CVC4 {
52 namespace main {
53 /** Global options variable */
54 thread_local Options* pOptions;
55
56 /** Full argv[0] */
57 const char *progPath;
58
59 /** Just the basename component of argv[0] */
60 const std::string *progName;
61
62 /** A pointer to the CommandExecutor (the signal handlers need it) */
63 CVC4::main::CommandExecutor* pExecutor = nullptr;
64
65 /** A pointer to the totalTime driver stat (the signal handlers need it) */
66 CVC4::TimerStat* pTotalTime = nullptr;
67
68 }/* CVC4::main namespace */
69 }/* CVC4 namespace */
70
71
72 void printUsage(Options& opts, bool full) {
73 stringstream ss;
74 ss << "usage: " << opts.getBinaryName() << " [options] [input-file]"
75 << endl << endl
76 << "Without an input file, or with `-', CVC4 reads from standard input."
77 << endl << endl
78 << "CVC4 options:" << endl;
79 if(full) {
80 Options::printUsage( ss.str(), *(opts.getOut()) );
81 } else {
82 Options::printShortUsage( ss.str(), *(opts.getOut()) );
83 }
84 }
85
86 int runCvc4(int argc, char* argv[], Options& opts) {
87
88 // Timer statistic
89 pTotalTime = new TimerStat("totalTime");
90 pTotalTime->start();
91
92 // For the signal handlers' benefit
93 pOptions = &opts;
94
95 // Initialize the signal handlers
96 signal_handlers::install();
97
98 progPath = argv[0];
99
100 // Parse the options
101 vector<string> filenames = Options::parseOptions(&opts, argc, argv);
102
103 install_time_limit(opts);
104
105 string progNameStr = opts.getBinaryName();
106 progName = &progNameStr;
107
108 if( opts.getHelp() ) {
109 printUsage(opts, true);
110 exit(1);
111 } else if( opts.getLanguageHelp() ) {
112 Options::printLanguageHelp(*(opts.getOut()));
113 exit(1);
114 } else if( opts.getVersion() ) {
115 *(opts.getOut()) << Configuration::about().c_str() << flush;
116 exit(0);
117 }
118
119 segvSpin = opts.getSegvSpin();
120
121 // If in competition mode, set output stream option to flush immediately
122 #ifdef CVC4_COMPETITION_MODE
123 *(opts.getOut()) << unitbuf;
124 #endif /* CVC4_COMPETITION_MODE */
125
126 // We only accept one input file
127 if(filenames.size() > 1) {
128 throw Exception("Too many input files specified.");
129 }
130
131 // If no file supplied we will read from standard input
132 const bool inputFromStdin = filenames.empty() || filenames[0] == "-";
133
134 // if we're reading from stdin on a TTY, default to interactive mode
135 if(!opts.wasSetByUserInteractive()) {
136 opts.setInteractive(inputFromStdin && isatty(fileno(stdin)));
137 }
138
139 // Auto-detect input language by filename extension
140 std::string filenameStr("<stdin>");
141 if (!inputFromStdin) {
142 // Use swap to avoid copying the string
143 // TODO: use std::move() when switching to c++11
144 filenameStr.swap(filenames[0]);
145 }
146 const char* filename = filenameStr.c_str();
147
148 if(opts.getInputLanguage() == language::input::LANG_AUTO) {
149 if( inputFromStdin ) {
150 // We can't do any fancy detection on stdin
151 opts.setInputLanguage(language::input::LANG_CVC4);
152 } else {
153 unsigned len = filenameStr.size();
154 if(len >= 5 && !strcmp(".smt2", filename + len - 5)) {
155 opts.setInputLanguage(language::input::LANG_SMTLIB_V2_6);
156 } else if((len >= 2 && !strcmp(".p", filename + len - 2))
157 || (len >= 5 && !strcmp(".tptp", filename + len - 5))) {
158 opts.setInputLanguage(language::input::LANG_TPTP);
159 } else if(( len >= 4 && !strcmp(".cvc", filename + len - 4) )
160 || ( len >= 5 && !strcmp(".cvc4", filename + len - 5) )) {
161 opts.setInputLanguage(language::input::LANG_CVC4);
162 } else if((len >= 3 && !strcmp(".sy", filename + len - 3))
163 || (len >= 3 && !strcmp(".sl", filename + len - 3))) {
164 // version 2 sygus is the default
165 opts.setInputLanguage(language::input::LANG_SYGUS_V2);
166 }
167 }
168 }
169
170 if(opts.getOutputLanguage() == language::output::LANG_AUTO) {
171 opts.setOutputLanguage(language::toOutputLanguage(opts.getInputLanguage()));
172 }
173
174 // Determine which messages to show based on smtcomp_mode and verbosity
175 if(Configuration::isMuzzledBuild()) {
176 DebugChannel.setStream(&CVC4::null_os);
177 TraceChannel.setStream(&CVC4::null_os);
178 NoticeChannel.setStream(&CVC4::null_os);
179 ChatChannel.setStream(&CVC4::null_os);
180 MessageChannel.setStream(&CVC4::null_os);
181 WarningChannel.setStream(&CVC4::null_os);
182 }
183
184 // important even for muzzled builds (to get result output right)
185 (*(opts.getOut())) << language::SetLanguage(opts.getOutputLanguage());
186
187 // Create the command executor to execute the parsed commands
188 pExecutor = new CommandExecutor(opts);
189
190 int returnValue = 0;
191 {
192 // Timer statistic
193 RegisterStatistic statTotalTime(&pExecutor->getStatisticsRegistry(),
194 pTotalTime);
195
196 // Filename statistics
197 ReferenceStat<std::string> s_statFilename("filename", filenameStr);
198 RegisterStatistic statFilenameReg(&pExecutor->getStatisticsRegistry(),
199 &s_statFilename);
200 // notify SmtEngine that we are starting to parse
201 pExecutor->getSmtEngine()->notifyStartParsing(filenameStr);
202
203 // Parse and execute commands until we are done
204 std::unique_ptr<Command> cmd;
205 bool status = true;
206 if(opts.getInteractive() && inputFromStdin) {
207 if(opts.getTearDownIncremental() > 0) {
208 throw OptionException(
209 "--tear-down-incremental doesn't work in interactive mode");
210 }
211 if(!opts.wasSetByUserIncrementalSolving()) {
212 cmd.reset(new SetOptionCommand("incremental", SExpr(true)));
213 cmd->setMuted(true);
214 pExecutor->doCommand(cmd);
215 }
216 InteractiveShell shell(pExecutor->getSolver());
217 if(opts.getInteractivePrompt()) {
218 Message() << Configuration::getPackageName()
219 << " " << Configuration::getVersionString();
220 if(Configuration::isGitBuild()) {
221 Message() << " [" << Configuration::getGitId() << "]";
222 }
223 Message() << (Configuration::isDebugBuild() ? " DEBUG" : "")
224 << " assertions:"
225 << (Configuration::isAssertionBuild() ? "on" : "off")
226 << endl << endl;
227 Message() << Configuration::copyright() << endl;
228 }
229
230 while(true) {
231 try {
232 cmd.reset(shell.readCommand());
233 } catch(UnsafeInterruptException& e) {
234 (*opts.getOut()) << CommandInterrupted();
235 break;
236 }
237 if (cmd == nullptr)
238 break;
239 status = pExecutor->doCommand(cmd) && status;
240 if (cmd->interrupted()) {
241 break;
242 }
243 }
244 } else if( opts.getTearDownIncremental() > 0) {
245 if(!opts.getIncrementalSolving() && opts.getTearDownIncremental() > 1) {
246 // For tear-down-incremental values greater than 1, need incremental
247 // on too.
248 cmd.reset(new SetOptionCommand("incremental", SExpr(true)));
249 cmd->setMuted(true);
250 pExecutor->doCommand(cmd);
251 // if(opts.wasSetByUserIncrementalSolving()) {
252 // throw OptionException(
253 // "--tear-down-incremental incompatible with --incremental");
254 // }
255
256 // cmd.reset(new SetOptionCommand("incremental", SExpr(false)));
257 // cmd->setMuted(true);
258 // pExecutor->doCommand(cmd);
259 }
260
261 ParserBuilder parserBuilder(pExecutor->getSolver(), filename, opts);
262
263 if( inputFromStdin ) {
264 #if defined(CVC4_COMPETITION_MODE) && !defined(CVC4_SMTCOMP_APPLICATION_TRACK)
265 parserBuilder.withStreamInput(cin);
266 #else /* CVC4_COMPETITION_MODE && !CVC4_SMTCOMP_APPLICATION_TRACK */
267 parserBuilder.withLineBufferedStreamInput(cin);
268 #endif /* CVC4_COMPETITION_MODE && !CVC4_SMTCOMP_APPLICATION_TRACK */
269 }
270
271 vector< vector<Command*> > allCommands;
272 allCommands.push_back(vector<Command*>());
273 std::unique_ptr<Parser> parser(parserBuilder.build());
274 int needReset = 0;
275 // true if one of the commands was interrupted
276 bool interrupted = false;
277 while (status)
278 {
279 if (interrupted) {
280 (*opts.getOut()) << CommandInterrupted();
281 break;
282 }
283
284 try {
285 cmd.reset(parser->nextCommand());
286 if (cmd == nullptr) break;
287 } catch (UnsafeInterruptException& e) {
288 interrupted = true;
289 continue;
290 }
291
292 if(dynamic_cast<PushCommand*>(cmd.get()) != nullptr) {
293 if(needReset >= opts.getTearDownIncremental()) {
294 pExecutor->reset();
295 for(size_t i = 0; i < allCommands.size() && !interrupted; ++i) {
296 if (interrupted) break;
297 for(size_t j = 0; j < allCommands[i].size() && !interrupted; ++j)
298 {
299 Command* ccmd = allCommands[i][j]->clone();
300 ccmd->setMuted(true);
301 pExecutor->doCommand(ccmd);
302 if (ccmd->interrupted())
303 {
304 interrupted = true;
305 }
306 delete ccmd;
307 }
308 }
309 needReset = 0;
310 }
311 allCommands.push_back(vector<Command*>());
312 Command* copy = cmd->clone();
313 allCommands.back().push_back(copy);
314 status = pExecutor->doCommand(cmd);
315 if(cmd->interrupted()) {
316 interrupted = true;
317 continue;
318 }
319 } else if(dynamic_cast<PopCommand*>(cmd.get()) != nullptr) {
320 allCommands.pop_back(); // fixme leaks cmds here
321 if (needReset >= opts.getTearDownIncremental()) {
322 pExecutor->reset();
323 for(size_t i = 0; i < allCommands.size() && !interrupted; ++i) {
324 for(size_t j = 0; j < allCommands[i].size() && !interrupted; ++j)
325 {
326 std::unique_ptr<Command> ccmd(allCommands[i][j]->clone());
327 ccmd->setMuted(true);
328 pExecutor->doCommand(ccmd);
329 if (ccmd->interrupted())
330 {
331 interrupted = true;
332 }
333 }
334 }
335 if (interrupted) continue;
336 (*opts.getOut()) << CommandSuccess();
337 needReset = 0;
338 } else {
339 status = pExecutor->doCommand(cmd);
340 if(cmd->interrupted()) {
341 interrupted = true;
342 continue;
343 }
344 }
345 } else if(dynamic_cast<CheckSatCommand*>(cmd.get()) != nullptr ||
346 dynamic_cast<QueryCommand*>(cmd.get()) != nullptr) {
347 if(needReset >= opts.getTearDownIncremental()) {
348 pExecutor->reset();
349 for(size_t i = 0; i < allCommands.size() && !interrupted; ++i) {
350 for(size_t j = 0; j < allCommands[i].size() && !interrupted; ++j)
351 {
352 Command* ccmd = allCommands[i][j]->clone();
353 ccmd->setMuted(true);
354 pExecutor->doCommand(ccmd);
355 if (ccmd->interrupted())
356 {
357 interrupted = true;
358 }
359 delete ccmd;
360 }
361 }
362 needReset = 0;
363 } else {
364 ++needReset;
365 }
366 if (interrupted) {
367 continue;
368 }
369
370 status = pExecutor->doCommand(cmd);
371 if(cmd->interrupted()) {
372 interrupted = true;
373 continue;
374 }
375 } else if(dynamic_cast<ResetCommand*>(cmd.get()) != nullptr) {
376 pExecutor->doCommand(cmd);
377 allCommands.clear();
378 allCommands.push_back(vector<Command*>());
379 } else {
380 // We shouldn't copy certain commands, because they can cause
381 // an error on replay since there's no associated sat/unsat check
382 // preceding them.
383 if(dynamic_cast<GetUnsatCoreCommand*>(cmd.get()) == nullptr &&
384 dynamic_cast<GetProofCommand*>(cmd.get()) == nullptr &&
385 dynamic_cast<GetValueCommand*>(cmd.get()) == nullptr &&
386 dynamic_cast<GetModelCommand*>(cmd.get()) == nullptr &&
387 dynamic_cast<GetAssignmentCommand*>(cmd.get()) == nullptr &&
388 dynamic_cast<GetInstantiationsCommand*>(cmd.get()) == nullptr &&
389 dynamic_cast<GetAssertionsCommand*>(cmd.get()) == nullptr &&
390 dynamic_cast<GetInfoCommand*>(cmd.get()) == nullptr &&
391 dynamic_cast<GetOptionCommand*>(cmd.get()) == nullptr &&
392 dynamic_cast<EchoCommand*>(cmd.get()) == nullptr) {
393 Command* copy = cmd->clone();
394 allCommands.back().push_back(copy);
395 }
396 status = pExecutor->doCommand(cmd);
397 if(cmd->interrupted()) {
398 interrupted = true;
399 continue;
400 }
401
402 if(dynamic_cast<QuitCommand*>(cmd.get()) != nullptr) {
403 break;
404 }
405 }
406 }
407 } else {
408 if(!opts.wasSetByUserIncrementalSolving()) {
409 cmd.reset(new SetOptionCommand("incremental", SExpr(false)));
410 cmd->setMuted(true);
411 pExecutor->doCommand(cmd);
412 }
413
414 ParserBuilder parserBuilder(pExecutor->getSolver(), filename, opts);
415
416 if( inputFromStdin ) {
417 #if defined(CVC4_COMPETITION_MODE) && !defined(CVC4_SMTCOMP_APPLICATION_TRACK)
418 parserBuilder.withStreamInput(cin);
419 #else /* CVC4_COMPETITION_MODE && !CVC4_SMTCOMP_APPLICATION_TRACK */
420 parserBuilder.withLineBufferedStreamInput(cin);
421 #endif /* CVC4_COMPETITION_MODE && !CVC4_SMTCOMP_APPLICATION_TRACK */
422 }
423
424 std::unique_ptr<Parser> parser(parserBuilder.build());
425 bool interrupted = false;
426 while (status)
427 {
428 if (interrupted) {
429 (*opts.getOut()) << CommandInterrupted();
430 pExecutor->reset();
431 break;
432 }
433 try {
434 cmd.reset(parser->nextCommand());
435 if (cmd == nullptr) break;
436 } catch (UnsafeInterruptException& e) {
437 interrupted = true;
438 continue;
439 }
440
441 status = pExecutor->doCommand(cmd);
442 if (cmd->interrupted() && status == 0) {
443 interrupted = true;
444 break;
445 }
446
447 if(dynamic_cast<QuitCommand*>(cmd.get()) != nullptr) {
448 break;
449 }
450 }
451 }
452
453 Result result;
454 if(status) {
455 result = pExecutor->getResult();
456 returnValue = 0;
457 } else {
458 // there was some kind of error
459 returnValue = 1;
460 }
461
462 #ifdef CVC4_COMPETITION_MODE
463 opts.flushOut();
464 // exit, don't return (don't want destructors to run)
465 // _exit() from unistd.h doesn't run global destructors
466 // or other on_exit/atexit stuff.
467 _exit(returnValue);
468 #endif /* CVC4_COMPETITION_MODE */
469
470 ReferenceStat< Result > s_statSatResult("sat/unsat", result);
471 RegisterStatistic statSatResultReg(&pExecutor->getStatisticsRegistry(),
472 &s_statSatResult);
473
474 pTotalTime->stop();
475
476 // Tim: I think that following comment is out of date?
477 // Set the global executor pointer to nullptr first. If we get a
478 // signal while dumping statistics, we don't want to try again.
479 pExecutor->flushOutputStreams();
480
481 #ifdef CVC4_DEBUG
482 if(opts.getEarlyExit() && opts.wasSetByUserEarlyExit()) {
483 _exit(returnValue);
484 }
485 #else /* CVC4_DEBUG */
486 if(opts.getEarlyExit()) {
487 _exit(returnValue);
488 }
489 #endif /* CVC4_DEBUG */
490 }
491
492 // On exceptional exit, these are leaked, but that's okay... they
493 // need to be around in that case for main() to print statistics.
494 delete pTotalTime;
495 delete pExecutor;
496
497 pTotalTime = nullptr;
498 pExecutor = nullptr;
499
500 signal_handlers::cleanup();
501
502 return returnValue;
503 }