]> git.saurik.com Git - cycript.git/blob - Console.cpp
Remove ?syntax and set failure exit code on throw.
[cycript.git] / Console.cpp
1 /* Cycript - Optimizing JavaScript Compiler/Runtime
2 * Copyright (C) 2009-2015 Jay Freeman (saurik)
3 */
4
5 /* GNU Affero General Public License, Version 3 {{{ */
6 /*
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20 /* }}} */
21
22 #include "cycript.hpp"
23
24 #ifdef CY_EXECUTE
25 #include "JavaScript.hpp"
26 #endif
27
28 #include <cstdio>
29 #include <fstream>
30 #include <sstream>
31
32 #include <setjmp.h>
33
34 #ifdef HAVE_READLINE_H
35 #include <readline.h>
36 #else
37 #include <readline/readline.h>
38 #endif
39
40 #ifdef HAVE_HISTORY_H
41 #include <history.h>
42 #else
43 #include <readline/history.h>
44 #endif
45
46 #include <errno.h>
47 #include <getopt.h>
48 #include <signal.h>
49 #include <unistd.h>
50
51 #include <sys/socket.h>
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <fcntl.h>
55 #include <netdb.h>
56
57 #include <sys/types.h>
58 #include <sys/socket.h>
59 #include <netinet/in.h>
60 #include <sys/un.h>
61 #include <pwd.h>
62
63 #include <dlfcn.h>
64
65 #ifdef __APPLE__
66 #include <mach/mach_time.h>
67 #endif
68
69 #include "Display.hpp"
70 #include "Driver.hpp"
71 #include "Error.hpp"
72 #include "Highlight.hpp"
73 #include "Syntax.hpp"
74
75 static volatile enum {
76 Working,
77 Parsing,
78 Running,
79 Sending,
80 Waiting,
81 } mode_;
82
83 static jmp_buf ctrlc_;
84
85 static void sigint(int) {
86 switch (mode_) {
87 case Working:
88 return;
89 case Parsing:
90 longjmp(ctrlc_, 1);
91 case Running:
92 CYCancel();
93 return;
94 case Sending:
95 return;
96 case Waiting:
97 return;
98 }
99 }
100
101 static bool bison_;
102 static bool timing_;
103 static bool strict_;
104 static bool pretty_;
105
106 void Setup(CYDriver &driver) {
107 if (bison_)
108 driver.debug_ = 1;
109 if (strict_)
110 driver.strict_ = true;
111 }
112
113 void Setup(CYOutput &out, CYDriver &driver, CYOptions &options, bool lower) {
114 out.pretty_ = pretty_;
115 if (lower)
116 driver.Replace(options);
117 }
118
119 static CYUTF8String Run(CYPool &pool, int client, CYUTF8String code) {
120 const char *json;
121 uint32_t size;
122
123 if (client == -1) {
124 mode_ = Running;
125 #ifdef CY_EXECUTE
126 json = CYExecute(CYGetJSContext(), pool, code);
127 #else
128 json = NULL;
129 #endif
130 mode_ = Working;
131 if (json == NULL)
132 size = 0;
133 else
134 size = strlen(json);
135 } else {
136 mode_ = Sending;
137 size = code.size;
138 _assert(CYSendAll(client, &size, sizeof(size)));
139 _assert(CYSendAll(client, code.data, code.size));
140 mode_ = Waiting;
141 _assert(CYRecvAll(client, &size, sizeof(size)));
142 if (size == _not(uint32_t))
143 json = NULL;
144 else {
145 char *temp(new(pool) char[size + 1]);
146 _assert(CYRecvAll(client, temp, size));
147 temp[size] = '\0';
148 json = temp;
149 }
150 mode_ = Working;
151 }
152
153 return CYUTF8String(json, size);
154 }
155
156 static CYUTF8String Run(CYPool &pool, int client, const std::string &code) {
157 return Run(pool, client, CYUTF8String(code.c_str(), code.size()));
158 }
159
160 static std::ostream *out_;
161
162 static void Output(CYUTF8String json, std::ostream *out, bool expand = false) {
163 const char *data(json.data);
164 size_t size(json.size);
165
166 if (data == NULL || out == NULL)
167 return;
168
169 if (!expand ||
170 data[0] != '@' && data[0] != '"' && data[0] != '\'' ||
171 data[0] == '@' && data[1] != '"' && data[1] != '\''
172 )
173 CYLexerHighlight(data, size, *out);
174 else for (size_t i(0); i != size; ++i)
175 if (data[i] != '\\')
176 *out << data[i];
177 else switch(data[++i]) {
178 case '\0': goto done;
179 case '\\': *out << '\\'; break;
180 case '\'': *out << '\''; break;
181 case '"': *out << '"'; break;
182 case 'b': *out << '\b'; break;
183 case 'f': *out << '\f'; break;
184 case 'n': *out << '\n'; break;
185 case 'r': *out << '\r'; break;
186 case 't': *out << '\t'; break;
187 case 'v': *out << '\v'; break;
188 default: *out << '\\'; --i; break;
189 }
190
191 done:
192 *out << std::endl;
193 }
194
195 int (*append_history$)(int, const char *);
196
197 static std::string command_;
198
199 static int client_;
200
201 static CYUTF8String Run(CYPool &pool, const std::string &code) {
202 return Run(pool, client_, code);
203 }
204
205 static char **Complete(const char *word, int start, int end) {
206 rl_attempted_completion_over = ~0;
207 std::string line(rl_line_buffer, start);
208 return CYComplete(word, command_ + line, &Run);
209 }
210
211 // need char *, not const char *
212 static char name_[] = "cycript";
213 static char break_[] = " \t\n\"\\'`@><=;|&{(" ")}" ".:[]";
214
215 class History {
216 private:
217 std::string histfile_;
218 size_t histlines_;
219
220 public:
221 History(std::string histfile) :
222 histfile_(histfile),
223 histlines_(0)
224 {
225 read_history(histfile_.c_str());
226 }
227
228 ~History() {
229 if (append_history$ != NULL) {
230 int fd(_syscall(open(histfile_.c_str(), O_CREAT | O_WRONLY, 0600)));
231 _syscall(close(fd));
232 _assert((*append_history$)(histlines_, histfile_.c_str()) == 0);
233 } else {
234 _assert(write_history(histfile_.c_str()) == 0);
235 }
236 }
237
238 void operator +=(const std::string &command) {
239 add_history(command.c_str());
240 ++histlines_;
241 }
242 };
243
244 static void Console(CYOptions &options) {
245 std::string basedir;
246 if (const char *home = getenv("HOME"))
247 basedir = home;
248 else {
249 passwd *passwd;
250 if (const char *username = getenv("LOGNAME"))
251 passwd = getpwnam(username);
252 else
253 passwd = getpwuid(getuid());
254 basedir = passwd->pw_dir;
255 }
256
257 basedir += "/.cycript";
258 mkdir(basedir.c_str(), 0700);
259
260 rl_initialize();
261 rl_readline_name = name_;
262
263 History history(basedir + "/history");
264
265 bool bypass(false);
266 bool debug(false);
267 bool expand(false);
268 bool lower(true);
269
270 out_ = &std::cout;
271
272 // rl_completer_word_break_characters is broken in libedit
273 rl_basic_word_break_characters = break_;
274
275 rl_completer_word_break_characters = break_;
276 rl_attempted_completion_function = &Complete;
277 rl_bind_key('\t', rl_complete);
278
279 #if RL_READLINE_VERSION >= 0x0600
280 rl_redisplay_function = CYDisplayUpdate;
281 #endif
282
283 struct sigaction action;
284 sigemptyset(&action.sa_mask);
285 action.sa_handler = &sigint;
286 action.sa_flags = 0;
287 sigaction(SIGINT, &action, NULL);
288
289 restart: for (;;) {
290 command_.clear();
291 std::vector<std::string> lines;
292
293 bool extra(false);
294 const char *prompt("cy# ");
295
296 if (setjmp(ctrlc_) != 0) {
297 mode_ = Working;
298 *out_ << std::endl;
299 goto restart;
300 }
301
302 read:
303 mode_ = Parsing;
304 char *line(readline(prompt));
305 mode_ = Working;
306
307 if (line == NULL) {
308 *out_ << std::endl;
309 break;
310 } else if (line[0] == '\0')
311 goto read;
312
313 if (!extra) {
314 extra = true;
315 if (line[0] == '?') {
316 std::string data(line + 1);
317 if (data == "bypass") {
318 bypass = !bypass;
319 *out_ << "bypass == " << (bypass ? "true" : "false") << std::endl;
320 } else if (data == "debug") {
321 debug = !debug;
322 *out_ << "debug == " << (debug ? "true" : "false") << std::endl;
323 } else if (data == "destroy") {
324 CYDestroyContext();
325 } else if (data == "gc") {
326 *out_ << "collecting... " << std::flush;
327 CYGarbageCollect(CYGetJSContext());
328 *out_ << "done." << std::endl;
329 } else if (data == "exit") {
330 return;
331 } else if (data == "expand") {
332 expand = !expand;
333 *out_ << "expand == " << (expand ? "true" : "false") << std::endl;
334 } else if (data == "lower") {
335 lower = !lower;
336 *out_ << "lower == " << (lower ? "true" : "false") << std::endl;
337 }
338 command_ = line;
339 history += command_;
340 goto restart;
341 }
342 }
343
344 command_ += line;
345 command_ += "\n";
346
347 char *begin(line), *end(line + strlen(line));
348 while (char *nl = reinterpret_cast<char *>(memchr(begin, '\n', end - begin))) {
349 *nl = '\0';
350 lines.push_back(begin);
351 begin = nl + 1;
352 }
353
354 lines.push_back(begin);
355
356 free(line);
357
358 std::string code;
359
360 if (bypass)
361 code = command_;
362 else {
363 std::istringstream stream(command_);
364
365 CYPool pool;
366 CYDriver driver(pool, stream);
367 Setup(driver);
368
369 bool failed(driver.Parse());
370
371 if (failed || !driver.errors_.empty()) {
372 for (CYDriver::Errors::const_iterator error(driver.errors_.begin()); error != driver.errors_.end(); ++error) {
373 CYPosition begin(error->location_.begin);
374 if (begin.line != lines.size() + 1 || error->warning_) {
375 CYPosition end(error->location_.end);
376
377 if (begin.line != lines.size()) {
378 std::cerr << " | ";
379 std::cerr << lines[begin.line - 1] << std::endl;
380 }
381
382 std::cerr << "....";
383 for (size_t i(0); i != begin.column; ++i)
384 std::cerr << '.';
385 if (begin.line != end.line || begin.column == end.column)
386 std::cerr << '^';
387 else for (size_t i(0), e(end.column - begin.column); i != e; ++i)
388 std::cerr << '^';
389 std::cerr << std::endl;
390
391 std::cerr << " | ";
392 std::cerr << error->message_ << std::endl;
393
394 history += command_.substr(0, command_.size() - 1);
395 goto restart;
396 }
397 }
398
399 driver.errors_.clear();
400
401 prompt = "cy> ";
402 goto read;
403 }
404
405 if (driver.script_ == NULL)
406 goto restart;
407
408 std::stringbuf str;
409 CYOutput out(str, options);
410 Setup(out, driver, options, lower);
411 out << *driver.script_;
412 code = str.str();
413 }
414
415 history += command_.substr(0, command_.size() - 1);
416
417 if (debug) {
418 std::cout << "cy= ";
419 CYLexerHighlight(code.c_str(), code.size(), std::cout);
420 std::cout << std::endl;
421 }
422
423 CYPool pool;
424 Output(Run(pool, client_, code), &std::cout, expand);
425 }
426 }
427
428 void InjectLibrary(pid_t, int, const char *const []);
429
430 static uint64_t CYGetTime() {
431 #ifdef __APPLE__
432 return mach_absolute_time();
433 #else
434 struct timespec spec;
435 clock_gettime(CLOCK_MONOTONIC, &spec);
436 return spec.tv_sec * UINT64_C(1000000000) + spec.tv_nsec;
437 #endif
438 }
439
440 int Main(int argc, char * const argv[], char const * const envp[]) {
441 bool tty(isatty(STDIN_FILENO));
442 bool compile(false);
443 bool target(false);
444 CYOptions options;
445
446 append_history$ = (int (*)(int, const char *)) (dlsym(RTLD_DEFAULT, "append_history"));
447
448 #ifdef CY_ATTACH
449 pid_t pid(_not(pid_t));
450 #endif
451
452 const char *host(NULL);
453 const char *port(NULL);
454
455 optind = 1;
456
457 for (;;) {
458 int option(getopt_long(argc, argv,
459 "c"
460 "g:"
461 "n:"
462 #ifdef CY_ATTACH
463 "p:"
464 #endif
465 "r:"
466 "s"
467 , (const struct option[]) {
468 {NULL, no_argument, NULL, 'c'},
469 {NULL, required_argument, NULL, 'g'},
470 {NULL, required_argument, NULL, 'n'},
471 #ifdef CY_ATTACH
472 {NULL, required_argument, NULL, 'p'},
473 #endif
474 {NULL, required_argument, NULL, 'r'},
475 {NULL, no_argument, NULL, 's'},
476 {0, 0, 0, 0}}, NULL));
477
478 switch (option) {
479 case -1:
480 goto getopt;
481
482 case ':':
483 case '?':
484 fprintf(stderr,
485 "usage: cycript [-c]"
486 #ifdef CY_ATTACH
487 " [-p <pid|name>]"
488 #endif
489 " [-r <host:port>]"
490 " [<script> [<arg>...]]\n"
491 );
492 return 1;
493
494 target:
495 if (!target)
496 target = true;
497 else {
498 fprintf(stderr, "only one of -[c"
499 #ifdef CY_ATTACH
500 "p"
501 #endif
502 "r] may be used at a time\n");
503 return 1;
504 }
505 break;
506
507 case 'c':
508 compile = true;
509 goto target;
510
511 case 'g':
512 if (false);
513 else if (strcmp(optarg, "rename") == 0)
514 options.verbose_ = true;
515 else if (strcmp(optarg, "bison") == 0)
516 bison_ = true;
517 else if (strcmp(optarg, "timing") == 0)
518 timing_ = true;
519 else {
520 fprintf(stderr, "invalid name for -g\n");
521 return 1;
522 }
523 break;
524
525 case 'n':
526 if (false);
527 else if (strcmp(optarg, "minify") == 0)
528 pretty_ = true;
529 else {
530 fprintf(stderr, "invalid name for -n\n");
531 return 1;
532 }
533 break;
534
535 #ifdef CY_ATTACH
536 case 'p': {
537 size_t size(strlen(optarg));
538 char *end;
539
540 pid = strtoul(optarg, &end, 0);
541 if (optarg + size != end) {
542 // XXX: arg needs to be escaped in some horrendous way of doom
543 // XXX: this is a memory leak now because I just don't care enough
544 char *command;
545 int writ(asprintf(&command, "ps axc|sed -e '/^ *[0-9]/{s/^ *\\([0-9]*\\)\\( *[^ ]*\\)\\{3\\} *-*\\([^ ]*\\)/\\3 \\1/;/^%s /{s/^[^ ]* //;q;};};d'", optarg));
546 _assert(writ != -1);
547
548 if (FILE *pids = popen(command, "r")) {
549 char value[32];
550 size = 0;
551
552 for (;;) {
553 size_t read(fread(value + size, 1, sizeof(value) - size, pids));
554 if (read == 0)
555 break;
556 else {
557 size += read;
558 if (size == sizeof(value))
559 goto fail;
560 }
561 }
562
563 size:
564 if (size == 0)
565 goto fail;
566 if (value[size - 1] == '\n') {
567 --size;
568 goto size;
569 }
570
571 value[size] = '\0';
572 size = strlen(value);
573 pid = strtoul(value, &end, 0);
574 if (value + size != end) fail:
575 pid = _not(pid_t);
576 _syscall(pclose(pids));
577 }
578
579 if (pid == _not(pid_t)) {
580 fprintf(stderr, "unable to find process `%s' using ps\n", optarg);
581 return 1;
582 }
583 }
584 } goto target;
585 #endif
586
587 case 'r': {
588 //size_t size(strlen(optarg));
589
590 char *colon(strrchr(optarg, ':'));
591 if (colon == NULL) {
592 fprintf(stderr, "missing colon in hostspec\n");
593 return 1;
594 }
595
596 /*char *end;
597 port = strtoul(colon + 1, &end, 10);
598 if (end != optarg + size) {
599 fprintf(stderr, "invalid port in hostspec\n");
600 return 1;
601 }*/
602
603 host = optarg;
604 *colon = '\0';
605 port = colon + 1;
606 } goto target;
607
608 case 's':
609 strict_ = true;
610 break;
611
612 default:
613 _assert(false);
614 }
615 }
616
617 getopt:
618 argc -= optind;
619 argv += optind;
620
621 const char *script;
622
623 #ifdef CY_ATTACH
624 if (pid != _not(pid_t) && argc > 1) {
625 fprintf(stderr, "-p cannot set argv\n");
626 return 1;
627 }
628 #endif
629
630 if (argc == 0)
631 script = NULL;
632 else {
633 #ifdef CY_EXECUTE
634 // XXX: const_cast?! wtf gcc :(
635 CYSetArgs(argc - 1, const_cast<const char **>(argv + 1));
636 #endif
637 script = argv[0];
638 if (strcmp(script, "-") == 0)
639 script = NULL;
640 }
641
642 #ifdef CY_ATTACH
643 if (pid == _not(pid_t))
644 client_ = -1;
645 else {
646 struct Socket {
647 int fd_;
648
649 Socket(int fd) :
650 fd_(fd)
651 {
652 }
653
654 ~Socket() {
655 close(fd_);
656 }
657
658 operator int() {
659 return fd_;
660 }
661 } server(_syscall(socket(PF_UNIX, SOCK_STREAM, 0)));
662
663 struct sockaddr_un address;
664 memset(&address, 0, sizeof(address));
665 address.sun_family = AF_UNIX;
666
667 const char *tmp;
668 #if defined(__APPLE__) && (defined(__arm__) || defined(__arm64__))
669 tmp = "/Library/Caches";
670 #else
671 tmp = "/tmp";
672 #endif
673
674 sprintf(address.sun_path, "%s/.s.cy.%u", tmp, getpid());
675 unlink(address.sun_path);
676
677 struct File {
678 const char *path_;
679
680 File(const char *path) :
681 path_(path)
682 {
683 }
684
685 ~File() {
686 unlink(path_);
687 }
688 } file(address.sun_path);
689
690 _syscall(bind(server, reinterpret_cast<sockaddr *>(&address), SUN_LEN(&address)));
691 _syscall(chmod(address.sun_path, 0777));
692
693 _syscall(listen(server, 1));
694 const char *const argv[] = {address.sun_path, NULL};
695 InjectLibrary(pid, 1, argv);
696 client_ = _syscall(accept(server, NULL, NULL));
697 }
698 #else
699 client_ = -1;
700 #endif
701
702 if (client_ == -1 && host != NULL && port != NULL) {
703 struct addrinfo hints;
704 memset(&hints, 0, sizeof(hints));
705 hints.ai_family = AF_UNSPEC;
706 hints.ai_socktype = SOCK_STREAM;
707 hints.ai_protocol = 0;
708 hints.ai_flags = 0;
709
710 struct addrinfo *infos;
711 _syscall(getaddrinfo(host, port, &hints, &infos));
712
713 _assert(infos != NULL); try {
714 for (struct addrinfo *info(infos); info != NULL; info = info->ai_next) {
715 int client(_syscall(socket(info->ai_family, info->ai_socktype, info->ai_protocol))); try {
716 _syscall(connect(client, info->ai_addr, info->ai_addrlen));
717 client_ = client;
718 break;
719 } catch (...) {
720 _syscall(close(client));
721 throw;
722 }
723 }
724 } catch (...) {
725 freeaddrinfo(infos);
726 throw;
727 }
728 }
729
730 if (script == NULL && tty)
731 Console(options);
732 else {
733 std::istream *stream;
734 if (script == NULL) {
735 stream = &std::cin;
736 script = "<stdin>";
737 } else {
738 stream = new std::fstream(script, std::ios::in | std::ios::binary);
739 _assert(!stream->fail());
740 }
741
742 if (timing_) {
743 std::stringbuf buffer;
744 stream->get(buffer, '\0');
745 _assert(!stream->fail());
746
747 double average(0);
748 int samples(-50);
749 uint64_t start(CYGetTime());
750
751 for (;;) {
752 stream = new std::istringstream(buffer.str());
753
754 CYPool pool;
755 CYDriver driver(pool, *stream, script);
756 Setup(driver);
757
758 uint64_t begin(CYGetTime());
759 driver.Parse();
760 uint64_t end(CYGetTime());
761
762 delete stream;
763
764 average += (end - begin - average) / ++samples;
765
766 uint64_t now(CYGetTime());
767 if (samples == 0)
768 average = 0;
769 else if ((now - start) / 1000000000 >= 1)
770 std::cout << std::fixed << average << '\t' << (end - begin) << '\t' << samples << std::endl;
771 else continue;
772
773 start = now;
774 }
775
776 stream = new std::istringstream(buffer.str());
777 std::cin.get();
778 }
779
780 CYPool pool;
781 CYDriver driver(pool, *stream, script);
782 Setup(driver);
783
784 bool failed(driver.Parse());
785
786 if (failed || !driver.errors_.empty()) {
787 for (CYDriver::Errors::const_iterator i(driver.errors_.begin()); i != driver.errors_.end(); ++i)
788 std::cerr << i->location_.begin << ": " << i->message_ << std::endl;
789 } else if (driver.script_ != NULL) {
790 std::stringbuf str;
791 CYOutput out(str, options);
792 Setup(out, driver, options, true);
793 out << *driver.script_;
794 std::string code(str.str());
795 if (compile)
796 std::cout << code;
797 else {
798 CYUTF8String json(Run(pool, client_, code));
799 if (CYStartsWith(json, "throw ")) {
800 CYLexerHighlight(json.data, json.size, std::cerr);
801 std::cerr << std::endl;
802 return 1;
803 }
804 }
805 }
806 }
807
808 return 0;
809 }
810
811 int main(int argc, char * const argv[], char const * const envp[]) {
812 try {
813 return Main(argc, argv, envp);
814 } catch (const CYException &error) {
815 CYPool pool;
816 fprintf(stderr, "%s\n", error.PoolCString(pool));
817 return 1;
818 }
819 }