Skip to content

Modules caching not working properly #140

@pinepain

Description

@pinepain

In general:

  1. require() function doesn't use cached loaded modules.
  2. If we fix that, loaded modules may be changed after they loaded and required (incl. it cached value), so caching become useless.

TL;DR;
Original problem described and explained in SO question (with perfect answer) - Using char* as a key in std::map.

Current definition is std::map<char *, v8js_persistent_obj_t> modules_loaded; at v8js_class.h#L46,

so the following check if (c->modules_loaded.count(normalised_module_id) > 0) { at v8js_methods.cc#L244 will always fail while normalised_module_id points to dynamically allocated memory.

All above, surprisingly, gives us a valid clean object each time we call require() js method.

When we pass comparator

struct cmp_str
{
  bool operator()(char const *a, char const *b) const
  {
    return strcmp(a, b) < 0;
  }
};

to make loaded modules looks like std::map<char *, v8js_persistent_obj_t, cmp_str> modules_loaded;,

then require() function return previously stored object, BUT following test will fail:

--TEST--
Test V8 require() js function 
--SKIPIF--
<?php
if(!function_exists('json_encode')) {
    die('SKIP');
}
require_once(dirname(__FILE__) . '/skipif.inc');
?>
--FILE--
<?php

$code['test.js'] = 'var out = {"foo" : "unchanged"}; exports.test = out; exports';

$JS = '
var test = require("test.js").test;

print(test.foo, "\n");
test.foo = "changed";
print(test.foo, "\n");

var test2 = require("test.js").test;

print(test2.foo, "\n");
';

$v8 = new V8Js();
$v8->setModuleLoader(function ($module) use (&$code) {
    if (isset($code[$module])) {
        return $code[$module];
    }

    return 'print(' . json_encode($module . PHP_EOL) . ');';
});


$v8->executeString($JS, 'module.js');
?>
--EXPECT--
unchanged
changed
unchanged

It will return

unchanged
changed
changed

which may be interpreted (and it will) by users who face with it as pure magic.

Moreover, freeing map's key here

    c->modules_loaded[normalised_module_id].Reset(isolate, newobj);
    info.GetReturnValue().Set(newobj);
    efree(normalised_module_id);

after using it is wrong while comparator will fail trying to read key string stored under char *.

same here,

    if (c->modules_loaded.count(normalised_module_id) > 0) {
        efree(normalised_module_id);
        efree(normalised_path);

        v8::Persistent<v8::Object> newobj;
        newobj.Reset(isolate, c->modules_loaded[normalised_module_id]);

freeing value and later using it to get value from map.

The question in general is about what to do with require() js method which clearly works, but does slightly different things from what it originally declares. Maybe using v8::Objec::Clone() will fits caching needs better?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions