1 /* Cycript - Optimizing JavaScript Compiler/Runtime 
   2  * Copyright (C) 2009-2012  Jay Freeman (saurik) 
   5 /* GNU Lesser General Public License, Version 3 {{{ */ 
   7  * Cycript is free software: you can redistribute it and/or modify it under 
   8  * the terms of the GNU Lesser General Public License as published by the 
   9  * Free Software Foundation, either version 3 of the License, or (at your 
  10  * option) any later version. 
  12  * Cycript is distributed in the hope that it will be useful, but WITHOUT 
  13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
  14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
  15  * License for more details. 
  17  * You should have received a copy of the GNU Lesser General Public License 
  18  * along with Cycript.  If not, see <http://www.gnu.org/licenses/>. 
  22 #include "cycript.hpp" 
  25 #include "JavaScript.hpp" 
  33 #ifdef HAVE_READLINE_H 
  36 #include <readline/readline.h> 
  42 #include <readline/history.h> 
  50 #include <sys/types.h> 
  54 #include "Cycript.tab.hh" 
  56 #include <sys/types.h> 
  57 #include <sys/socket.h> 
  58 #include <netinet/in.h> 
  62 #include <apr_getopt.h> 
  66 #include "Replace.hpp" 
  68 static volatile enum { 
  76 static jmp_buf ctrlc_
; 
  78 static void sigint(int) { 
  99 void Setup(CYDriver 
&driver
, cy::parser 
&parser
) { 
 102         parser
.set_debug_level(1); 
 105         driver
.strict_ 
= true; 
 108 void Setup(CYOutput 
&out
, CYDriver 
&driver
, CYOptions 
&options
) { 
 109     out
.pretty_ 
= pretty_
; 
 110     CYContext 
context(options
); 
 111     driver
.program_
->Replace(context
); 
 114 static CYUTF8String 
Run(CYPool 
&pool
, int client
, CYUTF8String code
) { 
 121         json 
= CYExecute(pool
, code
); 
 133         CYSendAll(client
, &size
, sizeof(size
)); 
 134         CYSendAll(client
, code
.data
, code
.size
); 
 136         CYRecvAll(client
, &size
, sizeof(size
)); 
 137         if (size 
== _not(size_t)) 
 140             char *temp(new(pool
) char[size 
+ 1]); 
 141             CYRecvAll(client
, temp
, size
); 
 148     return CYUTF8String(json
, size
); 
 151 static CYUTF8String 
Run(CYPool 
&pool
, int client
, const std::string 
&code
) { 
 152     return Run(pool
, client
, CYUTF8String(code
.c_str(), code
.size())); 
 157 static void Output(CYUTF8String json
, FILE *fout
, bool expand 
= false) { 
 158     const char *data(json
.data
); 
 159     size_t size(json
.size
); 
 161     if (data 
== NULL 
|| fout 
== NULL
) 
 165         data
[0] != '@' && data
[0] != '"' && data
[0] != '\'' || 
 166         data
[0] == '@' && data
[1] != '"' && data
[1] != '\'' 
 169     else for (size_t i(0); i 
!= size
; ++i
) 
 171             fputc(data
[i
], fout
); 
 172         else switch(data
[++i
]) { 
 173             case '\0': goto done
; 
 174             case '\\': fputc('\\', fout
); break; 
 175             case '\'': fputc('\'', fout
); break; 
 176             case '"': fputc('"', fout
); break; 
 177             case 'b': fputc('\b', fout
); break; 
 178             case 'f': fputc('\f', fout
); break; 
 179             case 'n': fputc('\n', fout
); break; 
 180             case 'r': fputc('\r', fout
); break; 
 181             case 't': fputc('\t', fout
); break; 
 182             case 'v': fputc('\v', fout
); break; 
 183             default: fputc('\\', fout
); --i
; break; 
 191 static void Run(int client
, const char *data
, size_t size
, FILE *fout 
= NULL
, bool expand 
= false) { 
 193     Output(Run(pool
, client
, CYUTF8String(data
, size
)), fout
, expand
); 
 196 static void Run(int client
, std::string 
&code
, FILE *fout 
= NULL
, bool expand 
= false) { 
 197     Run(client
, code
.c_str(), code
.size(), fout
, expand
); 
 200 int (*append_history$
)(int, const char *); 
 202 static std::string command_
; 
 204 static CYExpression 
*ParseExpression(CYUTF8String code
) { 
 205     std::ostringstream str
; 
 206     str 
<< '(' << code 
<< ')'; 
 207     std::string 
string(str
.str()); 
 210     driver
.data_ 
= string
.c_str(); 
 211     driver
.size_ 
= string
.size(); 
 213     cy::parser 
parser(driver
); 
 214     Setup(driver
, parser
); 
 216     if (parser
.parse() != 0 || !driver
.errors_
.empty()) 
 220     CYContext 
context(options
); 
 222     // XXX: this could be replaced with a CYStatement::Primitive() 
 223     if (CYExpress 
*express 
= dynamic_cast<CYExpress 
*>(driver
.program_
->statements_
)) 
 224         return express
->expression_
->Primitive(context
); 
 231 static char **Complete(const char *word
, int start
, int end
) { 
 232     rl_attempted_completion_over 
= TRUE
; 
 236     cy::parser 
parser(driver
); 
 237     Setup(driver
, parser
); 
 239     std::string 
line(rl_line_buffer
, start
); 
 240     std::string 
command(command_ 
+ line
); 
 242     driver
.data_ 
= command
.c_str(); 
 243     driver
.size_ 
= command
.size(); 
 247     if (parser
.parse() != 0 || !driver
.errors_
.empty()) 
 250     if (driver
.mode_ 
== CYDriver::AutoNone
) 
 253     CYExpression 
*expression
; 
 256     CYContext 
context(options
); 
 258     std::ostringstream prefix
; 
 260     switch (driver
.mode_
) { 
 261         case CYDriver::AutoPrimary
: 
 262             expression 
= $ 
CYThis(); 
 265         case CYDriver::AutoDirect
: 
 266             expression 
= driver
.context_
; 
 269         case CYDriver::AutoIndirect
: 
 270             expression 
= $ 
CYIndirect(driver
.context_
); 
 273         case CYDriver::AutoMessage
: { 
 274             CYDriver::Context 
&thing(driver
.contexts_
.back()); 
 275             expression 
= $
M($
C1($
V("object_getClass"), thing
.context_
), $
S("messages")); 
 276             for (CYDriver::Context::Words::const_iterator 
part(thing
.words_
.begin()); part 
!= thing
.words_
.end(); ++part
) 
 277                 prefix 
<< (*part
)->word_ 
<< ':'; 
 284     std::string 
begin(prefix
.str()); 
 286     driver
.program_ 
= $ 
CYProgram($ 
CYExpress($
C3(ParseExpression( 
 287     "   function(object, prefix, word) {\n" 
 289     "       var before = prefix.length;\n" 
 291     "       var entire = prefix.length;\n" 
 292     "       for (name in object)\n" 
 293     "           if (name.substring(0, entire) == prefix)\n" 
 294     "               names.push(name.substr(before));\n" 
 297     ), expression
, $
S(begin
.c_str()), $
S(word
)))); 
 299     driver
.program_
->Replace(context
); 
 301     std::ostringstream str
; 
 302     CYOutput 
out(str
, options
); 
 303     out 
<< *driver
.program_
; 
 305     std::string 
code(str
.str()); 
 306     CYUTF8String 
json(Run(pool
, client_
, code
)); 
 307     // XXX: if this fails we should not try to parse it 
 309     CYExpression 
*result(ParseExpression(json
)); 
 313     CYArray 
*array(dynamic_cast<CYArray 
*>(result
)); 
 316         fprintf(fout_
, "\n"); 
 318         rl_forced_update_display(); 
 322     // XXX: use an std::set? 
 323     typedef std::vector
<std::string
> Completions
; 
 324     Completions completions
; 
 329     CYForEach (element
, array
->elements_
) { 
 330         CYString 
*string(dynamic_cast<CYString 
*>(element
->value_
)); 
 331         _assert(string 
!= NULL
); 
 333         std::string completion
; 
 334         if (string
->size_ 
!= 0) 
 335             completion
.assign(string
->value_
, string
->size_
); 
 336         else if (driver
.mode_ 
== CYDriver::AutoMessage
) 
 341         completions
.push_back(completion
); 
 347             size_t limit(completion
.size()), size(common
.size()); 
 349                 common 
= common
.substr(0, limit
); 
 352             for (limit 
= 0; limit 
!= size
; ++limit
) 
 353                 if (common
[limit
] != completion
[limit
]) 
 356                 common 
= common
.substr(0, limit
); 
 360     size_t count(completions
.size()); 
 364     size_t colon(common
.find(':')); 
 365     if (colon 
!= std::string::npos
) 
 366         common 
= common
.substr(0, colon 
+ 1); 
 367     if (completions
.size() == 1) 
 370     char **results(reinterpret_cast<char **>(malloc(sizeof(char *) * (count 
+ 2)))); 
 372     results
[0] = strdup(common
.c_str()); 
 374     for (Completions::const_iterator 
i(completions
.begin()); i 
!= completions
.end(); ++i
) 
 375         results
[++index
] = strdup(i
->c_str()); 
 376     results
[count 
+ 1] = NULL
; 
 381 // need char *, not const char * 
 382 static char name_
[] = "cycript"; 
 383 static char break_
[] = " \t\n\"\\'`@$><=;|&{(" ")}" ".:[]"; 
 385 static void Console(CYOptions 
&options
) { 
 389     if (const char *username 
= getenv("LOGNAME")) 
 390         passwd 
= getpwnam(username
); 
 392         passwd 
= getpwuid(getuid()); 
 394     const char *basedir(apr_psprintf(pool
, "%s/.cycript", passwd
->pw_dir
)); 
 395     const char *histfile(apr_psprintf(pool
, "%s/history", basedir
)); 
 399     rl_readline_name 
= name_
; 
 401     mkdir(basedir
, 0700); 
 402     read_history(histfile
); 
 410     // rl_completer_word_break_characters is broken in libedit 
 411     rl_basic_word_break_characters 
= break_
; 
 413     rl_completer_word_break_characters 
= break_
; 
 414     rl_attempted_completion_function 
= &Complete
; 
 415     rl_bind_key('\t', rl_complete
); 
 417     struct sigaction action
; 
 418     sigemptyset(&action
.sa_mask
); 
 419     action
.sa_handler 
= &sigint
; 
 421     sigaction(SIGINT
, &action
, NULL
); 
 425         std::vector
<std::string
> lines
; 
 428         const char *prompt("cy# "); 
 430         if (setjmp(ctrlc_
) != 0) { 
 439         char *line(readline(prompt
)); 
 448             if (line
[0] == '?') { 
 449                 std::string 
data(line 
+ 1); 
 450                 if (data 
== "bypass") { 
 452                     fprintf(fout_
, "bypass == %s\n", bypass 
? "true" : "false"); 
 454                 } else if (data 
== "debug") { 
 456                     fprintf(fout_
, "debug == %s\n", debug 
? "true" : "false"); 
 458                 } else if (data 
== "expand") { 
 460                     fprintf(fout_
, "expand == %s\n", expand 
? "true" : "false"); 
 471         char *begin(line
), *end(line 
+ strlen(line
)); 
 472         while (char *nl 
= reinterpret_cast<char *>(memchr(begin
, '\n', end 
- begin
))) { 
 474             lines
.push_back(begin
); 
 478         lines
.push_back(begin
); 
 489             cy::parser 
parser(driver
); 
 490             Setup(driver
, parser
); 
 492             driver
.data_ 
= command_
.c_str(); 
 493             driver
.size_ 
= command_
.size(); 
 495             if (parser
.parse() != 0 || !driver
.errors_
.empty()) { 
 496                 for (CYDriver::Errors::const_iterator 
error(driver
.errors_
.begin()); error 
!= driver
.errors_
.end(); ++error
) { 
 497                     cy::position 
begin(error
->location_
.begin
); 
 498                     if (begin
.line 
!= lines
.size() || begin
.column 
< lines
.back().size() || error
->warning_
) { 
 499                         cy::position 
end(error
->location_
.end
); 
 501                         if (begin
.line 
!= lines
.size()) { 
 503                             std::cerr 
<< lines
[begin
.line 
- 1] << std::endl
; 
 507                         for (size_t i(0); i 
!= begin
.column
; ++i
) 
 509                         if (begin
.line 
!= end
.line 
|| begin
.column 
== end
.column
) 
 511                         else for (size_t i(0), e(end
.column 
- begin
.column
); i 
!= e
; ++i
) 
 513                         std::cerr 
<< std::endl
; 
 516                         std::cerr 
<< error
->message_ 
<< std::endl
; 
 518                         add_history(command_
.c_str()); 
 524                 driver
.errors_
.clear(); 
 531             if (driver
.program_ 
== NULL
) 
 537                 std::ostringstream str
; 
 538                 CYOutput 
out(str
, options
); 
 539                 Setup(out
, driver
, options
); 
 540                 out 
<< *driver
.program_
; 
 545         add_history(command_
.c_str()); 
 549             std::cout 
<< code 
<< std::endl
; 
 551         Run(client_
, code
, fout_
, expand
); 
 554     if (append_history$ 
!= NULL
) { 
 555         _syscall(close(_syscall(open(histfile
, O_CREAT 
| O_WRONLY
, 0600)))); 
 556         (*append_history$
)(histlines
, histfile
); 
 558         write_history(histfile
); 
 565 static void *Map(const char *path
, size_t *psize
) { 
 567     _syscall(fd 
= open(path
, O_RDONLY
)); 
 570     _syscall(fstat(fd
, &stat
)); 
 571     size_t size(stat
.st_size
); 
 576     _syscall(base 
= mmap(NULL
, size
, PROT_READ
, MAP_SHARED
, fd
, 0)); 
 582 void InjectLibrary(pid_t pid
); 
 584 int Main(int argc
, char const * const argv
[], char const * const envp
[]) { 
 585     bool tty(isatty(STDIN_FILENO
)); 
 589     append_history$ 
= (int (*)(int, const char *)) (dlsym(RTLD_DEFAULT
, "append_history")); 
 592     pid_t 
pid(_not(pid_t
)); 
 597     _aprcall(apr_getopt_init(&state
, pool
, argc
, argv
)); 
 603         apr_status_t 
status(apr_getopt(state
, 
 617                     "usage: cycript [-c]" 
 621                     " [<script> [<arg>...]]\n" 
 635                 else if (strcmp(arg
, "rename") == 0) 
 636                     options
.verbose_ 
= true; 
 638                 else if (strcmp(arg
, "bison") == 0) 
 642                     fprintf(stderr
, "invalid name for -g\n"); 
 649                 else if (strcmp(arg
, "minify") == 0) 
 652                     fprintf(stderr
, "invalid name for -n\n"); 
 659                 size_t size(strlen(arg
)); 
 662                 pid 
= strtoul(arg
, &end
, 0); 
 663                 if (arg 
+ size 
!= end
) { 
 664                     // XXX: arg needs to be escaped in some horrendous way of doom 
 665                     const char *command(apr_psprintf(pool
, "ps axc|sed -e '/^ *[0-9]/{s/^ *\\([0-9]*\\)\\( *[^ ]*\\)\\{3\\} *-*\\([^ ]*\\)/\\3 \\1/;/^%s /{s/^[^ ]* //;q;};};d'", arg
)); 
 667                     if (FILE *pids 
= popen(command
, "r")) { 
 672                             size_t read(fread(value 
+ size
, 1, sizeof(value
) - size
, pids
)); 
 677                                 if (size 
== sizeof(value
)) { 
 687                         if (value
[size 
- 1] == '\n') { 
 693                         size 
= strlen(value
); 
 694                         pid 
= strtoul(value
, &end
, 0); 
 695                         if (value 
+ size 
!= end
) fail
: 
 697                         _syscall(pclose(pids
)); 
 700                     if (pid 
== _not(pid_t
)) { 
 701                         fprintf(stderr
, "invalid pid for -p\n"); 
 718     if (pid 
!= _not(pid_t
) && ind 
< argc 
- 1) { 
 719         fprintf(stderr
, "-p cannot set argv\n"); 
 723     if (pid 
!= _not(pid_t
) && compile
) { 
 724         fprintf(stderr
, "-p conflicts with -c\n"); 
 733         // XXX: const_cast?! wtf gcc :( 
 734         CYSetArgs(argc 
- ind 
- 1, const_cast<const char **>(argv 
+ ind 
+ 1)); 
 737         if (strcmp(script
, "-") == 0) 
 742     if (pid 
!= _not(pid_t
) && script 
== NULL 
&& !tty
) { 
 743         fprintf(stderr
, "non-terminal attaching to remote console\n"); 
 749     if (pid 
== _not(pid_t
)) 
 752         int server(_syscall(socket(PF_UNIX
, SOCK_STREAM
, 0))); try { 
 753             struct sockaddr_un address
; 
 754             memset(&address
, 0, sizeof(address
)); 
 755             address
.sun_family 
= AF_UNIX
; 
 757             sprintf(address
.sun_path
, "/tmp/.s.cy.%u", getpid()); 
 759             _syscall(bind(server
, reinterpret_cast<sockaddr 
*>(&address
), SUN_LEN(&address
))); 
 760             _syscall(chmod(address
.sun_path
, 0777)); 
 763                 _syscall(listen(server
, 1)); 
 765                 client_ 
= _syscall(accept(server
, NULL
, NULL
)); 
 768                 unlink(address
.sun_path
); 
 772             _syscall(close(server
)); 
 780     if (script 
== NULL 
&& tty
) 
 784         CYDriver 
driver(script 
?: "<stdin>"); 
 785         cy::parser 
parser(driver
); 
 786         Setup(driver
, parser
); 
 790         if (script 
== NULL
) { 
 794             driver
.file_ 
= stdin
; 
 797             start 
= reinterpret_cast<char *>(Map(script
, &size
)); 
 800             if (size 
>= 2 && start
[0] == '#' && start
[1] == '!') { 
 803                 if (void *line 
= memchr(start
, '\n', end 
- start
)) 
 804                     start 
= reinterpret_cast<char *>(line
); 
 809             driver
.data_ 
= start
; 
 810             driver
.size_ 
= end 
- start
; 
 813         if (parser
.parse() != 0 || !driver
.errors_
.empty()) { 
 814             for (CYDriver::Errors::const_iterator 
i(driver
.errors_
.begin()); i 
!= driver
.errors_
.end(); ++i
) 
 815                 std::cerr 
<< i
->location_
.begin 
<< ": " << i
->message_ 
<< std::endl
; 
 816         } else if (driver
.program_ 
!= NULL
) 
 818                 std::string 
code(start
, end
-start
); 
 819                 Run(client_
, code
, stdout
); 
 821                 std::ostringstream str
; 
 822                 CYOutput 
out(str
, options
); 
 823                 Setup(out
, driver
, options
); 
 824                 out 
<< *driver
.program_
; 
 825                 std::string 
code(str
.str()); 
 829                     Run(client_
, code
, stdout
); 
 836 int main(int argc
, char const * const argv
[], char const * const envp
[]) { 
 837     apr_status_t 
status(apr_app_initialize(&argc
, &argv
, &envp
)); 
 839     if (status 
!= APR_SUCCESS
) { 
 840         fprintf(stderr
, "apr_app_initialize() != APR_SUCCESS\n"); 
 843         return Main(argc
, argv
, envp
); 
 844     } catch (const CYException 
&error
) { 
 846         fprintf(stderr
, "%s\n", error
.PoolCString(pool
));