1 /* Cycript - Optimizing JavaScript Compiler/Runtime 
   2  * Copyright (C) 2009-2015  Jay Freeman (saurik) 
   5 /* GNU Affero General Public License, Version 3 {{{ */ 
   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. 
  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. 
  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/>. 
  22 #include "cycript.hpp" 
  25 #include "JavaScript.hpp" 
  34 #ifdef HAVE_READLINE_H 
  37 #include <readline/readline.h> 
  43 #include <readline/history.h> 
  51 #include <sys/socket.h> 
  52 #include <sys/types.h> 
  57 #include <sys/types.h> 
  58 #include <sys/socket.h> 
  59 #include <netinet/in.h> 
  65 #include "Display.hpp" 
  66 #include "Replace.hpp" 
  68 #include "Cycript.tab.hh" 
  71 static volatile enum { 
  79 static jmp_buf ctrlc_
; 
  81 static void sigint(int) { 
 103 void Setup(CYDriver 
&driver
, cy::parser 
&parser
) { 
 106         parser
.set_debug_level(1); 
 109         driver
.strict_ 
= true; 
 112 void Setup(CYOutput 
&out
, CYDriver 
&driver
, CYOptions 
&options
, bool lower
) { 
 113     out
.pretty_ 
= pretty_
; 
 114     CYContext 
context(options
); 
 116         driver
.program_
->Replace(context
); 
 119 static CYUTF8String 
Run(CYPool 
&pool
, int client
, CYUTF8String code
) { 
 126         json 
= CYExecute(CYGetJSContext(), pool
, code
); 
 138         _assert(CYSendAll(client
, &size
, sizeof(size
))); 
 139         _assert(CYSendAll(client
, code
.data
, code
.size
)); 
 141         _assert(CYRecvAll(client
, &size
, sizeof(size
))); 
 142         if (size 
== _not(uint32_t)) 
 145             char *temp(new(pool
) char[size 
+ 1]); 
 146             _assert(CYRecvAll(client
, temp
, size
)); 
 153     return CYUTF8String(json
, size
); 
 156 static CYUTF8String 
Run(CYPool 
&pool
, int client
, const std::string 
&code
) { 
 157     return Run(pool
, client
, CYUTF8String(code
.c_str(), code
.size())); 
 160 static std::ostream 
*out_
; 
 162 static void Write(bool syntax
, const char *data
, size_t size
, std::ostream 
&out
) { 
 164         CYLexerHighlight(data
, size
, out
); 
 166         out
.write(data
, size
); 
 169 static void Output(bool syntax
, CYUTF8String json
, std::ostream 
*out
, bool expand 
= false) { 
 170     const char *data(json
.data
); 
 171     size_t size(json
.size
); 
 173     if (data 
== NULL 
|| out 
== NULL
) 
 177         data
[0] != '@' && data
[0] != '"' && data
[0] != '\'' || 
 178         data
[0] == '@' && data
[1] != '"' && data
[1] != '\'' 
 180         Write(syntax
, data
, size
, *out
); 
 181     else for (size_t i(0); i 
!= size
; ++i
) 
 184         else switch(data
[++i
]) { 
 185             case '\0': goto done
; 
 186             case '\\': *out 
<< '\\'; break; 
 187             case '\'': *out 
<< '\''; break; 
 188             case '"': *out 
<< '"'; break; 
 189             case 'b': *out 
<< '\b'; break; 
 190             case 'f': *out 
<< '\f'; break; 
 191             case 'n': *out 
<< '\n'; break; 
 192             case 'r': *out 
<< '\r'; break; 
 193             case 't': *out 
<< '\t'; break; 
 194             case 'v': *out 
<< '\v'; break; 
 195             default: *out 
<< '\\'; --i
; break; 
 202 static void Run(int client
, bool syntax
, const char *data
, size_t size
, std::ostream 
*out 
= NULL
, bool expand 
= false) { 
 204     Output(syntax
, Run(pool
, client
, CYUTF8String(data
, size
)), out
, expand
); 
 207 static void Run(int client
, bool syntax
, std::string 
&code
, std::ostream 
*out 
= NULL
, bool expand 
= false) { 
 208     Run(client
, syntax
, code
.c_str(), code
.size(), out
, expand
); 
 211 int (*append_history$
)(int, const char *); 
 213 static std::string command_
; 
 215 static CYExpression 
*ParseExpression(CYUTF8String code
) { 
 216     std::stringstream stream
; 
 217     stream 
<< '(' << code 
<< ')'; 
 218     CYDriver 
driver(stream
); 
 220     cy::parser 
parser(driver
); 
 221     Setup(driver
, parser
); 
 223     if (parser
.parse() != 0 || !driver
.errors_
.empty()) 
 227     CYContext 
context(options
); 
 229     CYStatement 
*statement(driver
.program_
->code_
); 
 230     _assert(statement 
!= NULL
); 
 231     _assert(statement
->next_ 
== NULL
); 
 233     CYExpress 
*express(dynamic_cast<CYExpress 
*>(driver
.program_
->code_
)); 
 234     _assert(express 
!= NULL
); 
 236     CYParenthetical 
*parenthetical(dynamic_cast<CYParenthetical 
*>(express
->expression_
)); 
 237     _assert(parenthetical 
!= NULL
); 
 239     return parenthetical
->expression_
; 
 244 static char **Complete(const char *word
, int start
, int end
) { 
 245     rl_attempted_completion_over 
= ~0; 
 249     std::string 
line(rl_line_buffer
, start
); 
 250     std::istringstream 
stream(command_ 
+ line
); 
 251     CYDriver 
driver(stream
); 
 255     cy::parser 
parser(driver
); 
 256     Setup(driver
, parser
); 
 258     if (parser
.parse() != 0 || !driver
.errors_
.empty()) 
 261     if (driver
.mode_ 
== CYDriver::AutoNone
) 
 264     CYExpression 
*expression
; 
 267     CYContext 
context(options
); 
 269     std::ostringstream prefix
; 
 271     switch (driver
.mode_
) { 
 272         case CYDriver::AutoPrimary
: 
 273             expression 
= $ 
CYThis(); 
 276         case CYDriver::AutoDirect
: 
 277             expression 
= driver
.context_
; 
 280         case CYDriver::AutoIndirect
: 
 281             expression 
= $ 
CYIndirect(driver
.context_
); 
 284         case CYDriver::AutoMessage
: { 
 285             CYDriver::Context 
&thing(driver
.contexts_
.back()); 
 286             expression 
= $
M($
C1($
V("object_getClass"), thing
.context_
), $
S("messages")); 
 287             for (CYDriver::Context::Words::const_iterator 
part(thing
.words_
.begin()); part 
!= thing
.words_
.end(); ++part
) 
 288                 prefix 
<< (*part
)->word_ 
<< ':'; 
 295     std::string 
begin(prefix
.str()); 
 297     driver
.program_ 
= $ 
CYProgram($ 
CYExpress($
C3(ParseExpression( 
 298     "   function(object, prefix, word) {\n" 
 300     "       var before = prefix.length;\n" 
 302     "       var entire = prefix.length;\n" 
 303     "       for (var name in object)\n" 
 304     "           if (name.substring(0, entire) == prefix)\n" 
 305     "               names.push(name.substr(before));\n" 
 308     ), expression
, $
S(begin
.c_str()), $
S(word
)))); 
 310     driver
.program_
->Replace(context
); 
 312     std::ostringstream str
; 
 313     CYOutput 
out(str
, options
); 
 314     out 
<< *driver
.program_
; 
 316     std::string 
code(str
.str()); 
 317     CYUTF8String 
json(Run(pool
, client_
, code
)); 
 318     // XXX: if this fails we should not try to parse it 
 320     CYExpression 
*result(ParseExpression(json
)); 
 324     CYArray 
*array(dynamic_cast<CYArray 
*>(result
->Primitive(context
))); 
 327         Output(false, json
, out_
); 
 328         rl_forced_update_display(); 
 332     // XXX: use an std::set? 
 333     typedef std::vector
<std::string
> Completions
; 
 334     Completions completions
; 
 339     CYForEach (element
, array
->elements_
) { 
 340         CYString 
*string(dynamic_cast<CYString 
*>(element
->value_
)); 
 341         _assert(string 
!= NULL
); 
 343         std::string completion
; 
 344         if (string
->size_ 
!= 0) 
 345             completion
.assign(string
->value_
, string
->size_
); 
 346         else if (driver
.mode_ 
== CYDriver::AutoMessage
) 
 351         completions
.push_back(completion
); 
 357             size_t limit(completion
.size()), size(common
.size()); 
 359                 common 
= common
.substr(0, limit
); 
 362             for (limit 
= 0; limit 
!= size
; ++limit
) 
 363                 if (common
[limit
] != completion
[limit
]) 
 366                 common 
= common
.substr(0, limit
); 
 370     size_t count(completions
.size()); 
 374     size_t colon(common
.find(':')); 
 375     if (colon 
!= std::string::npos
) 
 376         common 
= common
.substr(0, colon 
+ 1); 
 377     if (completions
.size() == 1) 
 380     char **results(reinterpret_cast<char **>(malloc(sizeof(char *) * (count 
+ 2)))); 
 382     results
[0] = strdup(common
.c_str()); 
 384     for (Completions::const_iterator 
i(completions
.begin()); i 
!= completions
.end(); ++i
) 
 385         results
[++index
] = strdup(i
->c_str()); 
 386     results
[count 
+ 1] = NULL
; 
 391 // need char *, not const char * 
 392 static char name_
[] = "cycript"; 
 393 static char break_
[] = " \t\n\"\\'`@><=;|&{(" ")}" ".:[]"; 
 397     std::string histfile_
; 
 401     History(std::string histfile
) : 
 405         read_history(histfile_
.c_str()); 
 409         if (append_history$ 
!= NULL
) { 
 410             int fd(_syscall(open(histfile_
.c_str(), O_CREAT 
| O_WRONLY
, 0600))); 
 412             _assert((*append_history$
)(histlines_
, histfile_
.c_str()) == 0); 
 414             _assert(write_history(histfile_
.c_str()) == 0); 
 418     void operator +=(const std::string 
&command
) { 
 419         add_history(command_
.c_str()); 
 424 static void Console(CYOptions 
&options
) { 
 426     if (const char *home 
= getenv("HOME")) 
 430         if (const char *username 
= getenv("LOGNAME")) 
 431             passwd 
= getpwnam(username
); 
 433             passwd 
= getpwuid(getuid()); 
 434         basedir 
= passwd
->pw_dir
; 
 437     basedir 
+= "/.cycript"; 
 438     mkdir(basedir
.c_str(), 0700); 
 441     rl_readline_name 
= name_
; 
 443     History 
history(basedir 
+ "/history"); 
 453     // rl_completer_word_break_characters is broken in libedit 
 454     rl_basic_word_break_characters 
= break_
; 
 456     rl_completer_word_break_characters 
= break_
; 
 457     rl_attempted_completion_function 
= &Complete
; 
 458     rl_bind_key('\t', rl_complete
); 
 460     struct sigaction action
; 
 461     sigemptyset(&action
.sa_mask
); 
 462     action
.sa_handler 
= &sigint
; 
 464     sigaction(SIGINT
, &action
, NULL
); 
 468         std::vector
<std::string
> lines
; 
 471         const char *prompt("cy# "); 
 473         if (setjmp(ctrlc_
) != 0) { 
 481 #if RL_READLINE_VERSION >= 0x0600 
 483             rl_redisplay_function 
= CYDisplayUpdate
; 
 485             rl_redisplay_function 
= rl_redisplay
; 
 489         char *line(readline(prompt
)); 
 495         } else if (line
[0] == '\0') 
 500             if (line
[0] == '?') { 
 501                 std::string 
data(line 
+ 1); 
 502                 if (data 
== "bypass") { 
 504                     *out_ 
<< "bypass == " << (bypass 
? "true" : "false") << std::endl
; 
 505                 } else if (data 
== "debug") { 
 507                     *out_ 
<< "debug == " << (debug 
? "true" : "false") << std::endl
; 
 508                 } else if (data 
== "destroy") { 
 510                 } else if (data 
== "gc") { 
 511                     *out_ 
<< "collecting... " << std::flush
; 
 512                     CYGarbageCollect(CYGetJSContext()); 
 513                     *out_ 
<< "done." << std::endl
; 
 514                 } else if (data 
== "exit") { 
 516                 } else if (data 
== "expand") { 
 518                     *out_ 
<< "expand == " << (expand 
? "true" : "false") << std::endl
; 
 519                 } else if (data 
== "lower") { 
 521                     *out_ 
<< "lower == " << (lower 
? "true" : "false") << std::endl
; 
 522                 } else if (data 
== "syntax") { 
 524                     *out_ 
<< "syntax == " << (syntax 
? "true" : "false") << std::endl
; 
 534         char *begin(line
), *end(line 
+ strlen(line
)); 
 535         while (char *nl 
= reinterpret_cast<char *>(memchr(begin
, '\n', end 
- begin
))) { 
 537             lines
.push_back(begin
); 
 541         lines
.push_back(begin
); 
 552             std::istringstream 
stream(command_
); 
 553             CYDriver 
driver(stream
); 
 555             cy::parser 
parser(driver
); 
 556             Setup(driver
, parser
); 
 558             if (parser
.parse() != 0 || !driver
.errors_
.empty()) { 
 559                 for (CYDriver::Errors::const_iterator 
error(driver
.errors_
.begin()); error 
!= driver
.errors_
.end(); ++error
) { 
 560                     CYPosition 
begin(error
->location_
.begin
); 
 561                     if (begin
.line 
!= lines
.size() || begin
.column 
< lines
.back().size() || error
->warning_
) { 
 562                         CYPosition 
end(error
->location_
.end
); 
 564                         if (begin
.line 
!= lines
.size()) { 
 566                             std::cerr 
<< lines
[begin
.line 
- 1] << std::endl
; 
 570                         for (size_t i(0); i 
!= begin
.column
; ++i
) 
 572                         if (begin
.line 
!= end
.line 
|| begin
.column 
== end
.column
) 
 574                         else for (size_t i(0), e(end
.column 
- begin
.column
); i 
!= e
; ++i
) 
 576                         std::cerr 
<< std::endl
; 
 579                         std::cerr 
<< error
->message_ 
<< std::endl
; 
 586                 driver
.errors_
.clear(); 
 593             if (driver
.program_ 
== NULL
) 
 596             std::ostringstream str
; 
 597             CYOutput 
out(str
, options
); 
 598             Setup(out
, driver
, options
, lower
); 
 599             out 
<< *driver
.program_
; 
 607             Write(syntax
, code
.c_str(), code
.size(), std::cout
); 
 608             std::cout 
<< std::endl
; 
 611         Run(client_
, syntax
, code
, out_
, expand
); 
 615 void InjectLibrary(pid_t
, int, const char *[]); 
 617 int Main(int argc
, char * const argv
[], char const * const envp
[]) { 
 618     bool tty(isatty(STDIN_FILENO
)); 
 623     append_history$ 
= (int (*)(int, const char *)) (dlsym(RTLD_DEFAULT
, "append_history")); 
 626     pid_t 
pid(_not(pid_t
)); 
 629     const char *host(NULL
); 
 630     const char *port(NULL
); 
 635         int option(getopt_long(argc
, argv
, 
 644         , (const struct option
[]) { 
 645             {NULL
, no_argument
, NULL
, 'c'}, 
 646             {NULL
, required_argument
, NULL
, 'g'}, 
 647             {NULL
, required_argument
, NULL
, 'n'}, 
 649             {NULL
, required_argument
, NULL
, 'p'}, 
 651             {NULL
, required_argument
, NULL
, 'r'}, 
 652             {NULL
, no_argument
, NULL
, 's'}, 
 653         {0, 0, 0, 0}}, NULL
)); 
 662                     "usage: cycript [-c]" 
 667                     " [<script> [<arg>...]]\n" 
 675                     fprintf(stderr
, "only one of -[c" 
 679                     "r] may be used at a time\n"); 
 690                 else if (strcmp(optarg
, "rename") == 0) 
 691                     options
.verbose_ 
= true; 
 693                 else if (strcmp(optarg
, "bison") == 0) 
 697                     fprintf(stderr
, "invalid name for -g\n"); 
 704                 else if (strcmp(optarg
, "minify") == 0) 
 707                     fprintf(stderr
, "invalid name for -n\n"); 
 714                 size_t size(strlen(optarg
)); 
 717                 pid 
= strtoul(optarg
, &end
, 0); 
 718                 if (optarg 
+ size 
!= end
) { 
 719                     // XXX: arg needs to be escaped in some horrendous way of doom 
 720                     // XXX: this is a memory leak now because I just don't care enough 
 722                     asprintf(&command
, "ps axc|sed -e '/^ *[0-9]/{s/^ *\\([0-9]*\\)\\( *[^ ]*\\)\\{3\\} *-*\\([^ ]*\\)/\\3 \\1/;/^%s /{s/^[^ ]* //;q;};};d'", optarg
); 
 724                     if (FILE *pids 
= popen(command
, "r")) { 
 729                             size_t read(fread(value 
+ size
, 1, sizeof(value
) - size
, pids
)); 
 734                                 if (size 
== sizeof(value
)) 
 742                         if (value
[size 
- 1] == '\n') { 
 748                         size 
= strlen(value
); 
 749                         pid 
= strtoul(value
, &end
, 0); 
 750                         if (value 
+ size 
!= end
) fail
: 
 752                         _syscall(pclose(pids
)); 
 755                     if (pid 
== _not(pid_t
)) { 
 756                         fprintf(stderr
, "unable to find process `%s' using ps\n", optarg
); 
 764                 //size_t size(strlen(optarg)); 
 766                 char *colon(strrchr(optarg
, ':')); 
 768                     fprintf(stderr
, "missing colon in hostspec\n"); 
 773                 port = strtoul(colon + 1, &end, 10); 
 774                 if (end != optarg + size) { 
 775                     fprintf(stderr, "invalid port in hostspec\n"); 
 800     if (pid 
!= _not(pid_t
) && argc 
> 1) { 
 801         fprintf(stderr
, "-p cannot set argv\n"); 
 810         // XXX: const_cast?! wtf gcc :( 
 811         CYSetArgs(argc 
- 1, const_cast<const char **>(argv 
+ 1)); 
 814         if (strcmp(script
, "-") == 0) 
 819     if (pid 
== _not(pid_t
)) 
 837         } server(_syscall(socket(PF_UNIX
, SOCK_STREAM
, 0))); 
 839         struct sockaddr_un address
; 
 840         memset(&address
, 0, sizeof(address
)); 
 841         address
.sun_family 
= AF_UNIX
; 
 844 #if defined(__APPLE__) && (defined(__arm__) || defined(__arm64__)) 
 845         tmp 
= "/Library/Caches"; 
 850         sprintf(address
.sun_path
, "%s/.s.cy.%u", tmp
, getpid()); 
 851         unlink(address
.sun_path
); 
 856             File(const char *path
) : 
 864         } file(address
.sun_path
); 
 866         _syscall(bind(server
, reinterpret_cast<sockaddr 
*>(&address
), SUN_LEN(&address
))); 
 867         _syscall(chmod(address
.sun_path
, 0777)); 
 869         _syscall(listen(server
, 1)); 
 870         InjectLibrary(pid
, 1, (const char *[]) {address
.sun_path
, NULL
}); 
 871         client_ 
= _syscall(accept(server
, NULL
, NULL
)); 
 877     if (client_ 
== -1 && host 
!= NULL 
&& port 
!= NULL
) { 
 878         struct addrinfo hints
; 
 879         memset(&hints
, 0, sizeof(hints
)); 
 880         hints
.ai_family 
= AF_UNSPEC
; 
 881         hints
.ai_socktype 
= SOCK_STREAM
; 
 882         hints
.ai_protocol 
= 0; 
 885         struct addrinfo 
*infos
; 
 886         _syscall(getaddrinfo(host
, port
, &hints
, &infos
)); 
 888         _assert(infos 
!= NULL
); try { 
 889             for (struct addrinfo 
*info(infos
); info 
!= NULL
; info 
= info
->ai_next
) { 
 890                 int client(_syscall(socket(info
->ai_family
, info
->ai_socktype
, info
->ai_protocol
))); try { 
 891                     _syscall(connect(client
, info
->ai_addr
, info
->ai_addrlen
)); 
 895                     _syscall(close(client
)); 
 905     if (script 
== NULL 
&& tty
) 
 910         std::istream 
*stream
; 
 911         if (script 
== NULL
) { 
 915             stream 
= new std::fstream(script
, std::ios::in 
| std::ios::binary
); 
 916             _assert(!stream
->fail()); 
 919         CYDriver 
driver(*stream
, script
); 
 920         cy::parser 
parser(driver
); 
 921         Setup(driver
, parser
); 
 923         if (parser
.parse() != 0 || !driver
.errors_
.empty()) { 
 924             for (CYDriver::Errors::const_iterator 
i(driver
.errors_
.begin()); i 
!= driver
.errors_
.end(); ++i
) 
 925                 std::cerr 
<< i
->location_
.begin 
<< ": " << i
->message_ 
<< std::endl
; 
 926         } else if (driver
.program_ 
!= NULL
) { 
 927             std::ostringstream str
; 
 928             CYOutput 
out(str
, options
); 
 929             Setup(out
, driver
, options
, true); 
 930             out 
<< *driver
.program_
; 
 931             std::string 
code(str
.str()); 
 935                 Run(client_
, false, code
, &std::cout
); 
 942 int main(int argc
, char * const argv
[], char const * const envp
[]) { 
 944         return Main(argc
, argv
, envp
); 
 945     } catch (const CYException 
&error
) { 
 947         fprintf(stderr
, "%s\n", error
.PoolCString(pool
));