1 /**
2  * @copyright 2013 Couchbase, Inc.
3  *
4  * @author Filipe Manana  <filipe@couchbase.com>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7  * use this file except in compliance with the License. You may obtain a copy of
8  * the License at
9  *
10  *  http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15  * License for the specific language governing permissions and limitations under
16  * the License.
17  **/
18 
19 #include "mapreduce.h"
20 #include "mapreduce_internal.h"
21 #include <iostream>
22 #include <cstring>
23 #include <stdlib.h>
24 #include <v8.h>
25 
26 
27 using namespace v8;
28 
29 typedef struct {
30     Persistent<Object>    jsonObject;
31     Persistent<Function>  jsonParseFun;
32     Persistent<Function>  stringifyFun;
33     mapreduce_ctx_t       *ctx;
34 } isolate_data_t;
35 
36 
37 static const char *SUM_FUNCTION_STRING =
38     "(function(values) {"
39     "    var sum = 0;"
40     "    for (var i = 0; i < values.length; ++i) {"
41     "        sum += values[i];"
42     "    }"
43     "    return sum;"
44     "})";
45 
46 static const char *DATE_FUNCTION_STRING =
47     // I wish it was on the prototype, but that will require bigger
48     // C changes as adding to the date prototype should be done on
49     // process launch. The code you see here may be faster, but it
50     // is less JavaScripty.
51     // "Date.prototype.toArray = (function() {"
52     "(function(date) {"
53     "    date = date.getUTCDate ? date : new Date(date);"
54     "    return isFinite(date.valueOf()) ?"
55     "      [date.getUTCFullYear(),"
56     "      (date.getUTCMonth() + 1),"
57     "       date.getUTCDate(),"
58     "       date.getUTCHours(),"
59     "       date.getUTCMinutes(),"
60     "       date.getUTCSeconds()] : null;"
61     "})";
62 
63 static const char *BASE64_FUNCTION_STRING =
64     "(function(b64) {"
65     "    var i, j, l, tmp, scratch, arr = [];"
66     "    var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';"
67     "    if (typeof b64 !== 'string') {"
68     "        throw 'Input is not a string';"
69     "    }"
70     "    if (b64.length % 4 > 0) {"
71     "        throw 'Invalid base64 source.';"
72     "    }"
73     "    scratch = b64.indexOf('=');"
74     "    scratch = scratch > 0 ? b64.length - scratch : 0;"
75     "    l = scratch > 0 ? b64.length - 4 : b64.length;"
76     "    for (i = 0, j = 0; i < l; i += 4, j += 3) {"
77     "        tmp = (lookup.indexOf(b64[i]) << 18) | (lookup.indexOf(b64[i + 1]) << 12);"
78     "        tmp |= (lookup.indexOf(b64[i + 2]) << 6) | lookup.indexOf(b64[i + 3]);"
79     "        arr.push((tmp & 0xFF0000) >> 16);"
80     "        arr.push((tmp & 0xFF00) >> 8);"
81     "        arr.push(tmp & 0xFF);"
82     "    }"
83     "    if (scratch === 2) {"
84     "        tmp = (lookup.indexOf(b64[i]) << 2) | (lookup.indexOf(b64[i + 1]) >> 4);"
85     "        arr.push(tmp & 0xFF);"
86     "    } else if (scratch === 1) {"
87     "        tmp = (lookup.indexOf(b64[i]) << 10) | (lookup.indexOf(b64[i + 1]) << 4);"
88     "        tmp |= (lookup.indexOf(b64[i + 2]) >> 2);"
89     "        arr.push((tmp >> 8) & 0xFF);"
90     "        arr.push(tmp & 0xFF);"
91     "    }"
92     "    return arr;"
93     "})";
94 
95 
96 
97 #ifdef V8_POST_3_19_API
98 static Local<Context> createJsContext();
99 static void emit(const v8::FunctionCallbackInfo<Value> &args);
100 #else
101 static Persistent<Context> createJsContext();
102 static Handle<Value> emit(const Arguments &args);
103 #endif
104 
105 static void doInitContext(mapreduce_ctx_t *ctx);
106 static Handle<Function> compileFunction(const std::string &function);
107 static std::string exceptionString(const TryCatch &tryCatch);
108 static void loadFunctions(mapreduce_ctx_t *ctx,
109                           const std::list<std::string> &function_sources);
110 static inline isolate_data_t *getIsolateData();
111 static inline mapreduce_json_t jsonStringify(const Handle<Value> &obj);
112 static inline Handle<Value> jsonParse(const mapreduce_json_t &thing);
113 static inline void taskStarted(mapreduce_ctx_t *ctx);
114 static inline void taskFinished(mapreduce_ctx_t *ctx);
115 static void freeKvListEntries(kv_list_int_t &kvs);
116 static void freeJsonListEntries(json_results_list_t &list);
117 static inline Handle<Array> jsonListToJsArray(const mapreduce_json_list_t &list);
118 
119 
initContext(mapreduce_ctx_t *ctx, const std::list<std::string> &function_sources)120 void initContext(mapreduce_ctx_t *ctx,
121                  const std::list<std::string> &function_sources)
122 {
123     doInitContext(ctx);
124 
125     try {
126         Locker locker(ctx->isolate);
127         Isolate::Scope isolateScope(ctx->isolate);
128 #ifdef V8_POST_3_19_API
129         HandleScope handleScope(ctx->isolate);
130         Context::Scope contextScope(ctx->isolate, ctx->jsContext);
131 #else
132         HandleScope handleScope;
133         Context::Scope contextScope(ctx->jsContext);
134 #endif
135 
136         loadFunctions(ctx, function_sources);
137     } catch (...) {
138         destroyContext(ctx);
139         throw;
140     }
141 }
142 
143 
destroyContext(mapreduce_ctx_t *ctx)144 void destroyContext(mapreduce_ctx_t *ctx)
145 {
146     {
147         Locker locker(ctx->isolate);
148         Isolate::Scope isolateScope(ctx->isolate);
149 #ifdef V8_POST_3_19_API
150         HandleScope handleScope(ctx->isolate);
151         Context::Scope contextScope(ctx->isolate, ctx->jsContext);
152 #else
153         HandleScope handleScope;
154         Context::Scope contextScope(ctx->jsContext);
155 #endif
156 
157         for (unsigned int i = 0; i < ctx->functions->size(); ++i) {
158 #ifdef V8_POST_3_19_API
159             (*ctx->functions)[i]->Dispose();
160             delete (*ctx->functions)[i];
161 #else
162             (*ctx->functions)[i].Dispose();
163 #endif
164 
165         }
166         delete ctx->functions;
167 
168         isolate_data_t *isoData = getIsolateData();
169         isoData->jsonObject.Dispose();
170         isoData->jsonObject.Clear();
171         isoData->jsonParseFun.Dispose();
172         isoData->jsonParseFun.Clear();
173         isoData->stringifyFun.Dispose();
174         isoData->stringifyFun.Clear();
175         delete isoData;
176 
177         ctx->jsContext.Dispose();
178         ctx->jsContext.Clear();
179     }
180 
181     ctx->isolate->Dispose();
182 }
183 
184 
doInitContext(mapreduce_ctx_t *ctx)185 static void doInitContext(mapreduce_ctx_t *ctx)
186 {
187     ctx->isolate = Isolate::New();
188     Locker locker(ctx->isolate);
189     Isolate::Scope isolateScope(ctx->isolate);
190 
191 #ifdef V8_POST_3_19_API
192     HandleScope handleScope(ctx->isolate);
193     ctx->jsContext.Reset(ctx->isolate, createJsContext());
194     Local<Context> context = Local<Context>::New(ctx->isolate, ctx->jsContext);
195     Context::Scope contextScope(context);
196     Handle<Object> jsonObject = Local<Object>::Cast(context->Global()->Get(String::New("JSON")));
197 #else
198     HandleScope handleScope;
199     ctx->jsContext = createJsContext();
200     Context::Scope contextScope(ctx->jsContext);
201     Handle<Object> jsonObject = Local<Object>::Cast(ctx->jsContext->Global()->Get(String::New("JSON")));
202 #endif
203 
204     Handle<Function> parseFun = Local<Function>::Cast(jsonObject->Get(String::New("parse")));
205     Handle<Function> stringifyFun = Local<Function>::Cast(jsonObject->Get(String::New("stringify")));
206 
207     isolate_data_t *isoData = new isolate_data_t();
208 #ifdef V8_POST_3_19_API
209     isoData->jsonObject.Reset(ctx->isolate, jsonObject);
210     isoData->jsonParseFun.Reset(ctx->isolate, parseFun);
211     isoData->stringifyFun.Reset(ctx->isolate, stringifyFun);
212 #else
213     isoData->jsonObject = Persistent<Object>::New(jsonObject);
214     isoData->jsonParseFun = Persistent<Function>::New(parseFun);
215     isoData->stringifyFun = Persistent<Function>::New(stringifyFun);
216 #endif
217     isoData->ctx = ctx;
218 
219     ctx->isolate->SetData(isoData);
220     ctx->taskStartTime = -1;
221 }
222 
223 
224 #ifdef V8_POST_3_19_API
createJsContext()225 static Local<Context> createJsContext()
226 {
227     HandleScope handleScope(Isolate::GetCurrent());
228 #else
229 static Persistent<Context> createJsContext()
230 {
231     HandleScope handleScope;
232 #endif
233 
234     Handle<ObjectTemplate> global = ObjectTemplate::New();
235     global->Set(String::New("emit"), FunctionTemplate::New(emit));
236 
237 #ifdef V8_POST_3_19_API
238     Handle<Context> context = Context::New(Isolate::GetCurrent(), NULL, global);
239 #else
240     Persistent<Context> context = Context::New(NULL, global);
241 #endif
242     Context::Scope contextScope(context);
243 
244     Handle<Function> sumFun = compileFunction(SUM_FUNCTION_STRING);
245     context->Global()->Set(String::New("sum"), sumFun);
246 
247     Handle<Function> decodeBase64Fun = compileFunction(BASE64_FUNCTION_STRING);
248     context->Global()->Set(String::New("decodeBase64"), decodeBase64Fun);
249 
250     Handle<Function> dateToArrayFun = compileFunction(DATE_FUNCTION_STRING);
251     context->Global()->Set(String::New("dateToArray"), dateToArrayFun);
252 
253 #ifdef V8_POST_3_19_API
254     return handleScope.Close(context);
255 #else
256     return context;
257 #endif
258 }
259 
260 
261 void mapDoc(mapreduce_ctx_t *ctx,
262             const mapreduce_json_t &doc,
263             const mapreduce_json_t &meta,
264             mapreduce_map_result_list_t *results)
265 {
266     Locker locker(ctx->isolate);
267     Isolate::Scope isolateScope(ctx->isolate);
268 #ifdef V8_POST_3_19_API
269     HandleScope handleScope(ctx->isolate);
270     Context::Scope contextScope(ctx->isolate, ctx->jsContext);
271 #else
272     HandleScope handleScope;
273     Context::Scope contextScope(ctx->jsContext);
274 #endif
275     Handle<Value> docObject = jsonParse(doc);
276     Handle<Value> metaObject = jsonParse(meta);
277 
278     if (!metaObject->IsObject()) {
279         throw MapReduceError(MAPREDUCE_INVALID_ARG, "metadata is not a JSON object");
280     }
281 
282     Handle<Value> funArgs[] = { docObject, metaObject };
283 
284     taskStarted(ctx);
285     kv_list_int_t kvs;
286     ctx->kvs = &kvs;
287 
288     for (unsigned int i = 0; i < ctx->functions->size(); ++i) {
289         mapreduce_map_result_t mapResult;
290 #ifdef V8_POST_3_19_API
291         Local<Function> fun = Local<Function>::New(ctx->isolate, *(*ctx->functions)[i]);
292 #else
293         Handle<Function> fun = (*ctx->functions)[i];
294 #endif
295         TryCatch trycatch;
296         Handle<Value> result = fun->Call(fun, 2, funArgs);
297 
298         if (!result.IsEmpty()) {
299             mapResult.error = MAPREDUCE_SUCCESS;
300             mapResult.result.kvs.length = kvs.size();
301             size_t sz = sizeof(mapreduce_kv_t) * mapResult.result.kvs.length;
302             mapResult.result.kvs.kvs = (mapreduce_kv_t *) malloc(sz);
303             if (mapResult.result.kvs.kvs == NULL) {
304                 freeKvListEntries(kvs);
305                 throw std::bad_alloc();
306             }
307             kv_list_int_t::iterator it = kvs.begin();
308             for (int j = 0; it != kvs.end(); ++it, ++j) {
309                 mapResult.result.kvs.kvs[j] = *it;
310             }
311         } else {
312             freeKvListEntries(kvs);
313 
314             if (!trycatch.CanContinue()) {
315                 throw MapReduceError(MAPREDUCE_TIMEOUT, "timeout");
316             }
317 
318             mapResult.error = MAPREDUCE_RUNTIME_ERROR;
319             std::string exceptString = exceptionString(trycatch);
320             size_t len = exceptString.length();
321 
322             mapResult.result.error_msg = (char *) malloc(len + 1);
323             if (mapResult.result.error_msg == NULL) {
324                 throw std::bad_alloc();
325             }
326             memcpy(mapResult.result.error_msg, exceptString.data(), len);
327             mapResult.result.error_msg[len] = '\0';
328         }
329 
330         results->list[i] = mapResult;
331         results->length += 1;
332         kvs.clear();
333     }
334 
335     taskFinished(ctx);
336 }
337 
338 
339 json_results_list_t runReduce(mapreduce_ctx_t *ctx,
340                               const mapreduce_json_list_t &keys,
341                               const mapreduce_json_list_t &values)
342 {
343     Locker locker(ctx->isolate);
344     Isolate::Scope isolateScope(ctx->isolate);
345 #ifdef V8_POST_3_19_API
346     HandleScope handleScope(ctx->isolate);
347     Context::Scope contextScope(ctx->isolate, ctx->jsContext);
348 #else
349     HandleScope handleScope;
350     Context::Scope contextScope(ctx->jsContext);
351 #endif
352     Handle<Array> keysArray = jsonListToJsArray(keys);
353     Handle<Array> valuesArray = jsonListToJsArray(values);
354     json_results_list_t results;
355 
356     Handle<Value> args[] = { keysArray, valuesArray, Boolean::New(false) };
357 
358     taskStarted(ctx);
359 
360     for (unsigned int i = 0; i < ctx->functions->size(); ++i) {
361 #ifdef V8_POST_3_19_API
362         Local<Function> fun = Local<Function>::New(ctx->isolate, *(*ctx->functions)[i]);
363 #else
364         Handle<Function> fun = (*ctx->functions)[i];
365 #endif
366         TryCatch trycatch;
367         Handle<Value> result = fun->Call(fun, 3, args);
368 
369         if (result.IsEmpty()) {
370             freeJsonListEntries(results);
371 
372             if (!trycatch.CanContinue()) {
373                 throw MapReduceError(MAPREDUCE_TIMEOUT, "timeout");
374             }
375 
376             throw MapReduceError(MAPREDUCE_RUNTIME_ERROR, exceptionString(trycatch));
377         }
378 
379         try {
380             mapreduce_json_t jsonResult = jsonStringify(result);
381             results.push_back(jsonResult);
382         } catch(...) {
383             freeJsonListEntries(results);
384             throw;
385         }
386     }
387 
388     taskFinished(ctx);
389 
390     return results;
391 }
392 
393 
394 mapreduce_json_t runReduce(mapreduce_ctx_t *ctx,
395                            int reduceFunNum,
396                            const mapreduce_json_list_t &keys,
397                            const mapreduce_json_list_t &values)
398 {
399     Locker locker(ctx->isolate);
400     Isolate::Scope isolateScope(ctx->isolate);
401 #ifdef V8_POST_3_19_API
402     HandleScope handleScope(ctx->isolate);
403     Context::Scope contextScope(ctx->isolate, ctx->jsContext);
404 #else
405     HandleScope handleScope;
406     Context::Scope contextScope(ctx->jsContext);
407 #endif
408 
409     reduceFunNum -= 1;
410     if (reduceFunNum < 0 ||
411         static_cast<unsigned int>(reduceFunNum) >= ctx->functions->size()) {
412         throw MapReduceError(MAPREDUCE_INVALID_ARG, "invalid reduce function number");
413     }
414 
415 #ifdef V8_POST_3_19_API
416     Local<Function> fun = Local<Function>::New(ctx->isolate, *(*ctx->functions)[reduceFunNum]);
417 #else
418     Handle<Function> fun = (*ctx->functions)[reduceFunNum];
419 #endif
420     Handle<Array> keysArray = jsonListToJsArray(keys);
421     Handle<Array> valuesArray = jsonListToJsArray(values);
422     Handle<Value> args[] = { keysArray, valuesArray, Boolean::New(false) };
423 
424     taskStarted(ctx);
425 
426     TryCatch trycatch;
427     Handle<Value> result = fun->Call(fun, 3, args);
428 
429     taskFinished(ctx);
430 
431     if (result.IsEmpty()) {
432         if (!trycatch.CanContinue()) {
433             throw MapReduceError(MAPREDUCE_TIMEOUT, "timeout");
434         }
435 
436         throw MapReduceError(MAPREDUCE_RUNTIME_ERROR, exceptionString(trycatch));
437     }
438 
439     return jsonStringify(result);
440 }
441 
442 
443 mapreduce_json_t runRereduce(mapreduce_ctx_t *ctx,
444                              int reduceFunNum,
445                              const mapreduce_json_list_t &reductions)
446 {
447     Locker locker(ctx->isolate);
448     Isolate::Scope isolateScope(ctx->isolate);
449 #ifdef V8_POST_3_19_API
450     HandleScope handleScope(ctx->isolate);
451     Context::Scope contextScope(ctx->isolate, ctx->jsContext);
452 #else
453     HandleScope handleScope;
454     Context::Scope contextScope(ctx->jsContext);
455 #endif
456 
457     reduceFunNum -= 1;
458     if (reduceFunNum < 0 ||
459         static_cast<unsigned int>(reduceFunNum) >= ctx->functions->size()) {
460         throw MapReduceError(MAPREDUCE_INVALID_ARG, "invalid reduce function number");
461     }
462 
463 #ifdef V8_POST_3_19_API
464     Local<Function> fun = Local<Function>::New(ctx->isolate, *(*ctx->functions)[reduceFunNum]);
465 #else
466     Handle<Function> fun = (*ctx->functions)[reduceFunNum];
467 #endif
468     Handle<Array> valuesArray = jsonListToJsArray(reductions);
469     Handle<Value> args[] = { Null(), valuesArray, Boolean::New(true) };
470 
471     taskStarted(ctx);
472 
473     TryCatch trycatch;
474     Handle<Value> result = fun->Call(fun, 3, args);
475 
476     taskFinished(ctx);
477 
478     if (result.IsEmpty()) {
479         if (!trycatch.CanContinue()) {
480             throw MapReduceError(MAPREDUCE_TIMEOUT, "timeout");
481         }
482 
483         throw MapReduceError(MAPREDUCE_RUNTIME_ERROR, exceptionString(trycatch));
484     }
485 
486     return jsonStringify(result);
487 }
488 
489 
490 void terminateTask(mapreduce_ctx_t *ctx)
491 {
492     V8::TerminateExecution(ctx->isolate);
493     taskFinished(ctx);
494 }
495 
496 
497 static void freeKvListEntries(kv_list_int_t &kvs)
498 {
499     kv_list_int_t::iterator it = kvs.begin();
500 
501     for ( ; it != kvs.end(); ++it) {
502         mapreduce_kv_t kv = *it;
503         free(kv.key.json);
504         free(kv.value.json);
505     }
506     kvs.clear();
507 }
508 
509 
510 static void freeJsonListEntries(json_results_list_t &list)
511 {
512     json_results_list_t::iterator it = list.begin();
513 
514     for ( ; it != list.end(); ++it) {
515         free((*it).json);
516     }
517     list.clear();
518 }
519 
520 
521 static Handle<Function> compileFunction(const std::string &funSource)
522 {
523 #ifdef V8_POST_3_19_API
524     HandleScope handleScope(Isolate::GetCurrent());
525 #else
526     HandleScope handleScope;
527 #endif
528     TryCatch trycatch;
529     Handle<String> source = String::New(funSource.data(), funSource.length());
530     Handle<Script> script = Script::Compile(source);
531 
532     if (script.IsEmpty()) {
533         throw MapReduceError(MAPREDUCE_SYNTAX_ERROR, exceptionString(trycatch));
534     }
535 
536     Handle<Value> result = script->Run();
537 
538     if (result.IsEmpty()) {
539         throw MapReduceError(MAPREDUCE_SYNTAX_ERROR, exceptionString(trycatch));
540     }
541 
542     if (!result->IsFunction()) {
543         throw MapReduceError(MAPREDUCE_SYNTAX_ERROR,
544                              std::string("Invalid function: ") + funSource.c_str());
545     }
546 
547     return handleScope.Close(Handle<Function>::Cast(result));
548 }
549 
550 
551 static std::string exceptionString(const TryCatch &tryCatch)
552 {
553 #ifdef V8_POST_3_19_API
554     HandleScope handleScope(Isolate::GetCurrent());
555 #else
556     HandleScope handleScope;
557 #endif
558     String::Utf8Value exception(tryCatch.Exception());
559     const char *exceptionString = (*exception);
560 
561     if (exceptionString) {
562         return std::string(exceptionString);
563     }
564 
565     return std::string("runtime error");
566 }
567 
568 
569 static void loadFunctions(mapreduce_ctx_t *ctx,
570                           const std::list<std::string> &function_sources)
571 {
572 #ifdef V8_POST_3_19_API
573     HandleScope handleScope(ctx->isolate);
574 #else
575     HandleScope handleScope;
576 #endif
577 
578     ctx->functions = new function_vector_t();
579 
580     std::list<std::string>::const_iterator it = function_sources.begin();
581 
582     for ( ; it != function_sources.end(); ++it) {
583         Handle<Function> fun = compileFunction(*it);
584 #ifdef V8_POST_3_19_API
585         Persistent<Function> *perFn = new Persistent<Function>();
586         perFn->Reset(ctx->isolate, fun);
587         ctx->functions->push_back(perFn);
588 #else
589         ctx->functions->push_back(Persistent<Function>::New(fun));
590 #endif
591     }
592 }
593 
594 
595 #ifdef V8_POST_3_19_API
596 static void emit(const v8::FunctionCallbackInfo<Value> &args)
597 #else
598 static Handle<Value> emit(const Arguments &args)
599 #endif
600 {
601     isolate_data_t *isoData = getIsolateData();
602 
603     if (isoData->ctx->kvs == NULL) {
604 #ifdef V8_POST_3_19_API
605         return;
606 #else
607         return Undefined();
608 #endif
609     }
610 
611     try {
612         mapreduce_kv_t result;
613 
614         result.key   = jsonStringify(args[0]);
615         result.value = jsonStringify(args[1]);
616         isoData->ctx->kvs->push_back(result);
617 
618 #ifdef V8_POST_3_19_API
619         return;
620 #else
621         return Undefined();
622 #endif
623     } catch(Handle<Value> &ex) {
624 #ifdef V8_POST_3_19_API
625         ThrowException(ex);
626 #else
627         return ThrowException(ex);
628 #endif
629     }
630 }
631 
632 
633 static inline isolate_data_t *getIsolateData()
634 {
635     Isolate *isolate = Isolate::GetCurrent();
636     return reinterpret_cast<isolate_data_t*>(isolate->GetData());
637 }
638 
639 
640 static inline mapreduce_json_t jsonStringify(const Handle<Value> &obj)
641 {
642     isolate_data_t *isoData = getIsolateData();
643     Handle<Value> args[] = { obj };
644     TryCatch trycatch;
645 #ifdef V8_POST_3_19_API
646     Local<Function> stringifyFun = Local<Function>::New(Isolate::GetCurrent(), isoData->stringifyFun);
647     Local<Object> jsonObject = Local<Object>::New(Isolate::GetCurrent(), isoData->jsonObject);
648     Handle<Value> result = stringifyFun->Call(jsonObject, 1, args);
649 #else
650     Handle<Value> result = isoData->stringifyFun->Call(isoData->jsonObject, 1, args);
651 #endif
652 
653     if (result.IsEmpty()) {
654         throw trycatch.Exception();
655     }
656 
657     mapreduce_json_t jsonResult;
658 
659     if (!result->IsUndefined()) {
660         Handle<String> str = Handle<String>::Cast(result);
661         jsonResult.length = str->Utf8Length();
662         jsonResult.json = (char *) malloc(jsonResult.length);
663         if (jsonResult.json == NULL) {
664             throw std::bad_alloc();
665         }
666         str->WriteUtf8(jsonResult.json, jsonResult.length,
667                        NULL, String::NO_NULL_TERMINATION);
668     } else {
669         jsonResult.length = sizeof("null") - 1;
670         jsonResult.json = (char *) malloc(jsonResult.length);
671         if (jsonResult.json == NULL) {
672             throw std::bad_alloc();
673         }
674         memcpy(jsonResult.json, "null", jsonResult.length);
675     }
676 
677     // Caller responsible for freeing jsonResult.json
678     return jsonResult;
679 }
680 
681 
682 static inline Handle<Value> jsonParse(const mapreduce_json_t &thing)
683 {
684     isolate_data_t *isoData = getIsolateData();
685     Handle<Value> args[] = { String::New(thing.json, thing.length) };
686     TryCatch trycatch;
687 #ifdef V8_POST_3_19_API
688     Local<Function> jsonParseFun = Local<Function>::New(Isolate::GetCurrent(), isoData->jsonParseFun);
689     Local<Object> jsonObject = Local<Object>::New(Isolate::GetCurrent(), isoData->jsonObject);
690     Handle<Value> result = jsonParseFun->Call(jsonObject, 1, args);
691 #else
692     Handle<Value> result = isoData->jsonParseFun->Call(isoData->jsonObject, 1, args);
693 #endif
694 
695     if (result.IsEmpty()) {
696         throw MapReduceError(MAPREDUCE_RUNTIME_ERROR, exceptionString(trycatch));
697     }
698 
699     return result;
700 }
701 
702 
703 static inline void taskStarted(mapreduce_ctx_t *ctx)
704 {
705     ctx->taskStartTime = time(NULL);
706     ctx->kvs = NULL;
707 }
708 
709 
710 static inline void taskFinished(mapreduce_ctx_t *ctx)
711 {
712     ctx->taskStartTime = -1;
713 }
714 
715 
716 static inline Handle<Array> jsonListToJsArray(const mapreduce_json_list_t &list)
717 {
718     Handle<Array> array = Array::New(list.length);
719 
720     for (int i = 0 ; i < list.length; ++i) {
721         Handle<Value> v = jsonParse(list.values[i]);
722         array->Set(Number::New(i), v);
723     }
724 
725     return array;
726 }
727