]> git.saurik.com Git - cycript.git/blob - Console.cpp
Make CYExecute take a CYUTF8String and fix the size shell game in Console's Run.
[cycript.git] / Console.cpp
1 /* Cycript - Inlining/Optimizing JavaScript Compiler
2 * Copyright (C) 2009 Jay Freeman (saurik)
3 */
4
5 /* Modified BSD License {{{ */
6 /*
7 * Redistribution and use in source and binary
8 * forms, with or without modification, are permitted
9 * provided that the following conditions are met:
10 *
11 * 1. Redistributions of source code must retain the
12 * above copyright notice, this list of conditions
13 * and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the
15 * above copyright notice, this list of conditions
16 * and the following disclaimer in the documentation
17 * and/or other materials provided with the
18 * distribution.
19 * 3. The name of the author may not be used to endorse
20 * or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS''
24 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
25 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
34 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38 /* }}} */
39
40 #include "cycript.hpp"
41
42 #ifdef CY_EXECUTE
43 #include "JavaScript.hpp"
44 #endif
45
46 #include <cstdio>
47 #include <sstream>
48
49 #include <setjmp.h>
50
51 #ifdef HAVE_READLINE_H
52 #include <readline.h>
53 #else
54 #include <readline/readline.h>
55 #endif
56
57 #ifdef HAVE_HISTORY_H
58 #include <history.h>
59 #else
60 #include <readline/history.h>
61 #endif
62
63 #include <sys/mman.h>
64
65 #include <errno.h>
66 #include <unistd.h>
67
68 #include <sys/types.h>
69 #include <sys/stat.h>
70 #include <fcntl.h>
71
72 #include "Cycript.tab.hh"
73
74 #include <sys/types.h>
75 #include <sys/socket.h>
76 #include <netinet/in.h>
77 #include <sys/un.h>
78 #include <pwd.h>
79
80 #include <apr_getopt.h>
81
82 #include <dlfcn.h>
83
84 #include "Replace.hpp"
85
86 static volatile enum {
87 Working,
88 Parsing,
89 Running,
90 Sending,
91 Waiting,
92 } mode_;
93
94 static jmp_buf ctrlc_;
95
96 static void sigint(int) {
97 switch (mode_) {
98 case Working:
99 return;
100 case Parsing:
101 longjmp(ctrlc_, 1);
102 case Running:
103 throw "*** Ctrl-C";
104 case Sending:
105 return;
106 case Waiting:
107 return;
108 }
109 }
110
111 #if YYDEBUG
112 static bool bison_;
113 #endif
114 static bool strict_;
115 static bool pretty_;
116
117 void Setup(CYDriver &driver, cy::parser &parser) {
118 #if YYDEBUG
119 if (bison_)
120 parser.set_debug_level(1);
121 #endif
122 if (strict_)
123 driver.strict_ = true;
124 }
125
126 void Setup(CYOutput &out, CYDriver &driver, CYOptions &options) {
127 out.pretty_ = pretty_;
128 CYContext context(driver.pool_, options);
129 driver.program_->Replace(context);
130 }
131
132 static CYUTF8String Run(CYPool &pool, int client, CYUTF8String code) {
133 const char *json;
134 size_t size;
135
136 if (client == -1) {
137 mode_ = Running;
138 #ifdef CY_EXECUTE
139 json = CYExecute(pool, code);
140 #else
141 json = NULL;
142 #endif
143 mode_ = Working;
144 if (json != NULL)
145 size = strlen(json);
146 } else {
147 mode_ = Sending;
148 size = code.size;
149 CYSendAll(client, &size, sizeof(size));
150 CYSendAll(client, code.data, code.size);
151 mode_ = Waiting;
152 CYRecvAll(client, &size, sizeof(size));
153 if (size == _not(size_t))
154 json = NULL;
155 else {
156 char *temp(new(pool) char[size + 1]);
157 CYRecvAll(client, temp, size);
158 temp[size] = '\0';
159 json = temp;
160 }
161 mode_ = Working;
162 }
163
164 return CYUTF8String(json, size);
165 }
166
167 static CYUTF8String Run(CYPool &pool, int client, const std::string &code) {
168 return Run(pool, client, CYUTF8String(code.c_str(), code.size()));
169 }
170
171 void Run(int client, const char *data, size_t size, FILE *fout = NULL, bool expand = false) {
172 CYPool pool;
173 CYUTF8String json(Run(pool, client, CYUTF8String(data, size)));
174
175 data = json.data;
176 size = json.size;
177
178 if (data != NULL && fout != NULL) {
179 if (!expand || data[0] != '"' && data[0] != '\'')
180 fputs(data, fout);
181 else for (size_t i(0); i != size; ++i)
182 if (data[i] != '\\')
183 fputc(data[i], fout);
184 else switch(data[++i]) {
185 case '\0': goto done;
186 case '\\': fputc('\\', fout); break;
187 case '\'': fputc('\'', fout); break;
188 case '"': fputc('"', fout); break;
189 case 'b': fputc('\b', fout); break;
190 case 'f': fputc('\f', fout); break;
191 case 'n': fputc('\n', fout); break;
192 case 'r': fputc('\r', fout); break;
193 case 't': fputc('\t', fout); break;
194 case 'v': fputc('\v', fout); break;
195 default: fputc('\\', fout); --i; break;
196 }
197
198 done:
199 fputs("\n", fout);
200 fflush(fout);
201 }
202 }
203
204 static void Run(int client, std::string &code, FILE *fout = NULL, bool expand = false) {
205 Run(client, code.c_str(), code.size(), fout, expand);
206 }
207
208 int (*append_history$)(int, const char *);
209
210 static std::string command_;
211
212 static CYExpression *ParseExpression(CYPool &pool, CYUTF8String code) {
213 std::ostringstream str;
214 str << '(' << code << ')';
215 std::string string(str.str());
216
217 CYDriver driver(pool);
218 driver.data_ = string.c_str();
219 driver.size_ = string.size();
220
221 cy::parser parser(driver);
222 Setup(driver, parser);
223
224 if (parser.parse() != 0 || !driver.errors_.empty())
225 _assert(false);
226
227 CYExpress *express(dynamic_cast<CYExpress *>(driver.program_->statements_));
228 _assert(express != NULL);
229 return express->expression_;
230 }
231
232 static int client_;
233
234 static char **Complete(const char *word, int start, int end) {
235 rl_attempted_completion_over = TRUE;
236
237 CYPool pool;
238
239 CYDriver driver(pool);
240 cy::parser parser(driver);
241 Setup(driver, parser);
242
243 std::string line(rl_line_buffer, start);
244 std::string command(command_ + line);
245
246 driver.data_ = command.c_str();
247 driver.size_ = command.size();
248
249 driver.auto_ = true;
250
251 if (parser.parse() != 0 || !driver.errors_.empty())
252 return NULL;
253
254 if (driver.mode_ == CYDriver::AutoNone)
255 return NULL;
256
257 CYExpression *expression;
258
259 CYOptions options;
260 CYContext context(driver.pool_, options);
261
262 std::ostringstream prefix;
263
264 switch (driver.mode_) {
265 case CYDriver::AutoPrimary:
266 expression = $ CYThis();
267 break;
268
269 case CYDriver::AutoDirect:
270 expression = driver.context_;
271 break;
272
273 case CYDriver::AutoIndirect:
274 expression = $ CYIndirect(driver.context_);
275 break;
276
277 case CYDriver::AutoMessage: {
278 CYDriver::Context &thing(driver.contexts_.back());
279 expression = $M($M($ CYIndirect(thing.context_), $S("isa")), $S("messages"));
280 for (CYDriver::Context::Words::const_iterator part(thing.words_.begin()); part != thing.words_.end(); ++part)
281 prefix << (*part)->word_ << ':';
282 } break;
283
284 default:
285 _assert(false);
286 }
287
288 std::string begin(prefix.str() + word);
289
290 driver.program_ = $ CYProgram($ CYExpress($C2(ParseExpression(pool,
291 " function(object, prefix) {\n"
292 " var names = [];\n"
293 " var pattern = '^' + prefix;\n"
294 " for (name in object)\n"
295 " if (name.match(pattern) != null)\n"
296 " names.push(name);\n"
297 " return names;\n"
298 " }\n"
299 ), expression, $S(begin.c_str()))));
300
301 driver.program_->Replace(context);
302
303 std::ostringstream str;
304 CYOutput out(str, options);
305 out << *driver.program_;
306
307 std::string code(str.str());
308 CYUTF8String json(Run(pool, client_, code));
309
310 CYExpression *result(ParseExpression(pool, json));
311 CYArray *array(dynamic_cast<CYArray *>(result));
312 _assert(array != NULL);
313
314 // XXX: use an std::set?
315 typedef std::vector<std::string> Completions;
316 Completions completions;
317
318 std::string common;
319 bool rest(false);
320
321 for (CYElement *element(array->elements_); element != NULL; element = element->next_) {
322 CYString *string(dynamic_cast<CYString *>(element->value_));
323 _assert(string != NULL);
324
325 std::string completion(string->value_, string->size_);
326 completions.push_back(completion);
327
328 if (!rest) {
329 common = completion;
330 rest = true;
331 } else {
332 size_t limit(completion.size()), size(common.size());
333 if (size > limit)
334 common = common.substr(0, limit);
335 else
336 limit = size;
337 for (limit = 0; limit != size; ++limit)
338 if (common[limit] != completion[limit])
339 break;
340 if (limit != size)
341 common = common.substr(0, limit);
342 }
343 }
344
345 size_t count(completions.size());
346 if (count == 0)
347 return NULL;
348
349 if (!common.empty()) {
350 size_t size(prefix.str().size());
351 _assert(common.size() >= size);
352 common = common.substr(size);
353 }
354
355 size_t colon(common.find(':'));
356 if (colon != std::string::npos)
357 common = common.substr(0, colon + 1);
358
359 char **results(reinterpret_cast<char **>(malloc(sizeof(char *) * (count + 2))));
360
361 results[0] = strdup(common.c_str());
362 size_t index(0);
363 for (Completions::const_iterator i(completions.begin()); i != completions.end(); ++i)
364 results[++index] = strdup(i->c_str());
365 results[count + 1] = NULL;
366
367 return results;
368 }
369
370 // need char *, not const char *
371 static char name_[] = "cycript";
372 static char break_[] = " \t\n\"\\'`@$><=;|&{(" ".:";
373
374 static void Console(apr_pool_t *pool, CYOptions &options) {
375 passwd *passwd;
376 if (const char *username = getenv("LOGNAME"))
377 passwd = getpwnam(username);
378 else
379 passwd = getpwuid(getuid());
380
381 const char *basedir(apr_psprintf(pool, "%s/.cycript", passwd->pw_dir));
382 const char *histfile(apr_psprintf(pool, "%s/history", basedir));
383 size_t histlines(0);
384
385 rl_initialize();
386 rl_readline_name = name_;
387
388 mkdir(basedir, 0700);
389 read_history(histfile);
390
391 bool bypass(false);
392 bool debug(false);
393 bool expand(false);
394
395 FILE *fout(stdout);
396
397 // rl_completer_word_break_characters is broken in libedit
398 rl_basic_word_break_characters = break_;
399
400 rl_completer_word_break_characters = break_;
401 rl_attempted_completion_function = &Complete;
402 rl_bind_key('\t', rl_complete);
403
404 struct sigaction action;
405 sigemptyset(&action.sa_mask);
406 action.sa_handler = &sigint;
407 action.sa_flags = 0;
408 sigaction(SIGINT, &action, NULL);
409
410 restart: for (;;) {
411 command_.clear();
412 std::vector<std::string> lines;
413
414 bool extra(false);
415 const char *prompt("cy# ");
416
417 if (setjmp(ctrlc_) != 0) {
418 mode_ = Working;
419 fputs("\n", fout);
420 fflush(fout);
421 goto restart;
422 }
423
424 read:
425 mode_ = Parsing;
426 char *line(readline(prompt));
427 mode_ = Working;
428 if (line == NULL)
429 break;
430 if (line[0] == '\0')
431 goto read;
432
433 if (!extra) {
434 extra = true;
435 if (line[0] == '?') {
436 std::string data(line + 1);
437 if (data == "bypass") {
438 bypass = !bypass;
439 fprintf(fout, "bypass == %s\n", bypass ? "true" : "false");
440 fflush(fout);
441 } else if (data == "debug") {
442 debug = !debug;
443 fprintf(fout, "debug == %s\n", debug ? "true" : "false");
444 fflush(fout);
445 } else if (data == "expand") {
446 expand = !expand;
447 fprintf(fout, "expand == %s\n", expand ? "true" : "false");
448 fflush(fout);
449 }
450 add_history(line);
451 ++histlines;
452 goto restart;
453 }
454 }
455
456 command_ += line;
457
458 char *begin(line), *end(line + strlen(line));
459 while (char *nl = reinterpret_cast<char *>(memchr(begin, '\n', end - begin))) {
460 *nl = '\0';
461 lines.push_back(begin);
462 begin = nl + 1;
463 }
464
465 lines.push_back(begin);
466
467 free(line);
468
469 std::string code;
470
471 if (bypass)
472 code = command_;
473 else {
474 CYDriver driver;
475 cy::parser parser(driver);
476 Setup(driver, parser);
477
478 driver.data_ = command_.c_str();
479 driver.size_ = command_.size();
480
481 if (parser.parse() != 0 || !driver.errors_.empty()) {
482 for (CYDriver::Errors::const_iterator error(driver.errors_.begin()); error != driver.errors_.end(); ++error) {
483 cy::position begin(error->location_.begin);
484 if (begin.line != lines.size() || begin.column - 1 != lines.back().size() || error->warning_) {
485 cy::position end(error->location_.end);
486
487 if (begin.line != lines.size()) {
488 std::cerr << " | ";
489 std::cerr << lines[begin.line - 1] << std::endl;
490 }
491
492 std::cerr << "....";
493 for (size_t i(0); i != begin.column - 1; ++i)
494 std::cerr << '.';
495 if (begin.line != end.line || begin.column == end.column)
496 std::cerr << '^';
497 else for (size_t i(0), e(end.column - begin.column); i != e; ++i)
498 std::cerr << '^';
499 std::cerr << std::endl;
500
501 std::cerr << " | ";
502 std::cerr << error->message_ << std::endl;
503
504 add_history(command_.c_str());
505 ++histlines;
506 goto restart;
507 }
508 }
509
510 driver.errors_.clear();
511
512 command_ += '\n';
513 prompt = "cy> ";
514 goto read;
515 }
516
517 if (driver.program_ == NULL)
518 goto restart;
519
520 if (client_ != -1)
521 code = command_;
522 else {
523 std::ostringstream str;
524 CYOutput out(str, options);
525 Setup(out, driver, options);
526 out << *driver.program_;
527 code = str.str();
528 }
529 }
530
531 add_history(command_.c_str());
532 ++histlines;
533
534 if (debug)
535 std::cout << code << std::endl;
536
537 Run(client_, code, fout, expand);
538 }
539
540 if (append_history$ != NULL) {
541 _syscall(close(_syscall(open(histfile, O_CREAT | O_WRONLY, 0600))));
542 (*append_history$)(histlines, histfile);
543 } else {
544 write_history(histfile);
545 }
546
547 fputs("\n", fout);
548 fflush(fout);
549 }
550
551 static void *Map(const char *path, size_t *psize) {
552 int fd;
553 _syscall(fd = open(path, O_RDONLY));
554
555 struct stat stat;
556 _syscall(fstat(fd, &stat));
557 size_t size(stat.st_size);
558
559 *psize = size;
560
561 void *base;
562 _syscall(base = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
563
564 _syscall(close(fd));
565 return base;
566 }
567
568 void InjectLibrary(pid_t pid);
569
570 int Main(int argc, char const * const argv[], char const * const envp[]) {
571 bool tty(isatty(STDIN_FILENO));
572 bool compile(false);
573 CYOptions options;
574
575 append_history$ = reinterpret_cast<int (*)(int, const char *)>(dlsym(RTLD_DEFAULT, "append_history"));
576
577 #ifdef CY_ATTACH
578 pid_t pid(_not(pid_t));
579 #endif
580
581 CYPool pool;
582 apr_getopt_t *state;
583 _aprcall(apr_getopt_init(&state, pool, argc, argv));
584
585 for (;;) {
586 char opt;
587 const char *arg;
588
589 apr_status_t status(apr_getopt(state,
590 "cg:n:"
591 #ifdef CY_ATTACH
592 "p:"
593 #endif
594 "s"
595 , &opt, &arg));
596
597 switch (status) {
598 case APR_EOF:
599 goto getopt;
600 case APR_BADCH:
601 case APR_BADARG:
602 fprintf(stderr,
603 "usage: cycript [-c]"
604 #ifdef CY_ATTACH
605 " [-p <pid|name>]"
606 #endif
607 " [<script> [<arg>...]]\n"
608 );
609 return 1;
610 default:
611 _aprcall(status);
612 }
613
614 switch (opt) {
615 case 'c':
616 compile = true;
617 break;
618
619 case 'g':
620 if (false);
621 else if (strcmp(arg, "rename") == 0)
622 options.verbose_ = true;
623 #if YYDEBUG
624 else if (strcmp(arg, "bison") == 0)
625 bison_ = true;
626 #endif
627 else {
628 fprintf(stderr, "invalid name for -g\n");
629 return 1;
630 }
631 break;
632
633 case 'n':
634 if (false);
635 else if (strcmp(arg, "minify") == 0)
636 pretty_ = true;
637 else {
638 fprintf(stderr, "invalid name for -n\n");
639 return 1;
640 }
641 break;
642
643 #ifdef CY_ATTACH
644 case 'p': {
645 size_t size(strlen(arg));
646 char *end;
647
648 pid = strtoul(arg, &end, 0);
649 if (arg + size != end) {
650 // XXX: arg needs to be escaped in some horrendous way of doom
651 const char *command(apr_psprintf(pool, "ps axc|sed -e '/^ *[0-9]/{s/^ *\\([0-9]*\\)\\( *[^ ]*\\)\\{3\\} *-*\\([^ ]*\\)/\\3 \\1/;/^%s /{s/^[^ ]* //;q;};};d'", arg));
652
653 if (FILE *pids = popen(command, "r")) {
654 char value[32];
655 size = 0;
656
657 for (;;) {
658 size_t read(fread(value + size, 1, sizeof(value) - size, pids));
659 if (read == 0)
660 break;
661 else {
662 size += read;
663 if (size == sizeof(value)) {
664 pid = _not(pid_t);
665 goto fail;
666 }
667 }
668 }
669
670 size:
671 if (size == 0)
672 goto fail;
673 if (value[size - 1] == '\n') {
674 --size;
675 goto size;
676 }
677
678 value[size] = '\0';
679 size = strlen(value);
680 pid = strtoul(value, &end, 0);
681 if (value + size != end) fail:
682 pid = _not(pid_t);
683 _syscall(pclose(pids));
684 }
685
686 if (pid == _not(pid_t)) {
687 fprintf(stderr, "invalid pid for -p\n");
688 return 1;
689 }
690 }
691 } break;
692 #endif
693
694 case 's':
695 strict_ = true;
696 break;
697 }
698 } getopt:;
699
700 const char *script;
701 int ind(state->ind);
702
703 #ifdef CY_ATTACH
704 if (pid != _not(pid_t) && ind < argc - 1) {
705 fprintf(stderr, "-p cannot set argv\n");
706 return 1;
707 }
708
709 if (pid != _not(pid_t) && compile) {
710 fprintf(stderr, "-p conflicts with -c\n");
711 return 1;
712 }
713 #endif
714
715 if (ind == argc)
716 script = NULL;
717 else {
718 #ifdef CY_EXECUTE
719 // XXX: const_cast?! wtf gcc :(
720 CYSetArgs(argc - ind - 1, const_cast<const char **>(argv + ind + 1));
721 #endif
722 script = argv[ind];
723 if (strcmp(script, "-") == 0)
724 script = NULL;
725 }
726
727 #ifdef CY_ATTACH
728 if (pid != _not(pid_t) && script == NULL && !tty) {
729 fprintf(stderr, "non-terminal attaching to remote console\n");
730 return 1;
731 }
732 #endif
733
734 #ifdef CY_ATTACH
735 if (pid == _not(pid_t))
736 client_ = -1;
737 else {
738 int server(_syscall(socket(PF_UNIX, SOCK_STREAM, 0))); try {
739 struct sockaddr_un address;
740 memset(&address, 0, sizeof(address));
741 address.sun_family = AF_UNIX;
742
743 sprintf(address.sun_path, "/tmp/.s.cy.%u", getpid());
744
745 _syscall(bind(server, reinterpret_cast<sockaddr *>(&address), SUN_LEN(&address)));
746 _syscall(chmod(address.sun_path, 0777));
747
748 try {
749 _syscall(listen(server, 1));
750 InjectLibrary(pid);
751 client_ = _syscall(accept(server, NULL, NULL));
752 } catch (...) {
753 // XXX: exception?
754 unlink(address.sun_path);
755 throw;
756 }
757 } catch (...) {
758 _syscall(close(server));
759 throw;
760 }
761 }
762 #else
763 client_ = -1;
764 #endif
765
766 if (script == NULL && tty)
767 Console(pool, options);
768 else {
769 CYDriver driver(pool, script ?: "<stdin>");
770 cy::parser parser(driver);
771 Setup(driver, parser);
772
773 char *start, *end;
774
775 if (script == NULL) {
776 start = NULL;
777 end = NULL;
778
779 driver.file_ = stdin;
780 } else {
781 size_t size;
782 start = reinterpret_cast<char *>(Map(script, &size));
783 end = start + size;
784
785 if (size >= 2 && start[0] == '#' && start[1] == '!') {
786 start += 2;
787
788 if (void *line = memchr(start, '\n', end - start))
789 start = reinterpret_cast<char *>(line);
790 else
791 start = end;
792 }
793
794 driver.data_ = start;
795 driver.size_ = end - start;
796 }
797
798 if (parser.parse() != 0 || !driver.errors_.empty()) {
799 for (CYDriver::Errors::const_iterator i(driver.errors_.begin()); i != driver.errors_.end(); ++i)
800 std::cerr << i->location_.begin << ": " << i->message_ << std::endl;
801 } else if (driver.program_ != NULL)
802 if (client_ != -1) {
803 std::string code(start, end-start);
804 Run(client_, code, stdout);
805 } else {
806 std::ostringstream str;
807 CYOutput out(str, options);
808 Setup(out, driver, options);
809 out << *driver.program_;
810 std::string code(str.str());
811 if (compile)
812 std::cout << code;
813 else
814 Run(client_, code, stdout);
815 }
816 }
817
818 return 0;
819 }
820
821 int main(int argc, char const * const argv[], char const * const envp[]) {
822 apr_status_t status(apr_app_initialize(&argc, &argv, &envp));
823
824 if (status != APR_SUCCESS) {
825 fprintf(stderr, "apr_app_initialize() != APR_SUCCESS\n");
826 return 1;
827 } else try {
828 return Main(argc, argv, envp);
829 } catch (const CYException &error) {
830 CYPool pool;
831 fprintf(stderr, "%s\n", error.PoolCString(pool));
832 return 1;
833 }
834 }