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