From a0563ab7ce204cf82feb56259d7a52c0b84077b7 Mon Sep 17 00:00:00 2001 From: Paul Joseph Davis Date: Wed, 11 May 2011 18:50:41 +0000 Subject: Fix circular imports in CommonJS modules. Backport of 1102006 from trunk. git-svn-id: https://svn.apache.org/repos/asf/couchdb/branches/1.1.x@1102017 13f79535-47bb-0310-9956-ffa450edef68 --- THANKS | 2 + share/server/util.js | 35 ++++++++---- share/www/script/test/design_docs.js | 108 ++++++++++++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 13 deletions(-) diff --git a/THANKS b/THANKS index 193e89b5..aae7991c 100644 --- a/THANKS +++ b/THANKS @@ -79,5 +79,7 @@ suggesting improvements or submitting changes. Some of these people are: * Tim Smith * Sam Bisbee * Nathan Vander Wilt + * Caolan McMahon + For a list of authors see the `AUTHORS` file. diff --git a/share/server/util.js b/share/server/util.js index b55480b9..e4386701 100644 --- a/share/server/util.js +++ b/share/server/util.js @@ -31,16 +31,16 @@ var resolveModule = function(names, mod, root) { } return resolveModule(names, { id : mod.id.slice(0, mod.id.lastIndexOf('/')), - parent : mod.parent.parent.parent, - current : mod.parent.parent.current + parent : mod.parent.parent, + current : mod.parent.current }); } else if (n == '.') { if (!mod.parent) { throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(mod.current)]; } return resolveModule(names, { - parent : mod.parent.parent, - current : mod.parent.current, + parent : mod.parent, + current : mod.current, id : mod.id }); } else if (root) { @@ -66,17 +66,28 @@ var Couch = { try { if (sandbox) { if (ddoc) { + if (!ddoc._module_cache) { + ddoc._module_cache = {}; + } var require = function(name, module) { module = module || {}; - var newModule = resolveModule(name.split('/'), module, ddoc); - var s = "function (module, exports, require) { " + newModule.current + " }"; - try { - var func = sandbox ? evalcx(s, sandbox) : eval(s); - func.apply(sandbox, [newModule, newModule.exports, function(name) {return require(name, newModule)}]); - } catch(e) { - throw ["error","compilation_error","Module require('"+name+"') raised error "+e.toSource()]; + var newModule = resolveModule(name.split('/'), module.parent, ddoc); + if (!ddoc._module_cache.hasOwnProperty(newModule.id)) { + // create empty exports object before executing the module, + // stops circular requires from filling the stack + ddoc._module_cache[newModule.id] = {}; + var s = "function (module, exports, require) { " + newModule.current + " }"; + try { + var func = sandbox ? evalcx(s, sandbox) : eval(s); + func.apply(sandbox, [newModule, newModule.exports, function(name) { + return require(name, newModule); + }]); + } catch(e) { + throw ["error","compilation_error","Module require('"+name+"') raised error "+e.toSource()]; + } + ddoc._module_cache[newModule.id] = newModule.exports; } - return newModule.exports; + return ddoc._module_cache[newModule.id]; } sandbox.require = require; } diff --git a/share/www/script/test/design_docs.js b/share/www/script/test/design_docs.js index f2f63841..702f0441 100644 --- a/share/www/script/test/design_docs.js +++ b/share/www/script/test/design_docs.js @@ -49,7 +49,48 @@ couchTests.design_docs = function(debug) { whynot : "exports.test = require('../stringzone'); " + "exports.foo = require('whatever/stringzone');", upper : "exports.testing = require('./whynot').test.string.toUpperCase()+" + - "module.id+require('./whynot').foo.string" + "module.id+require('./whynot').foo.string", + circular_one: "require('./circular_two'); exports.name = 'One';", + circular_two: "require('./circular_one'); exports.name = 'Two';" + }, + // paths relative to parent + idtest1: { + a: { + b: {d: "module.exports = require('../c/e').id;"}, + c: {e: "exports.id = module.id;"} + } + }, + // multiple paths relative to parent + idtest2: { + a: { + b: {d: "module.exports = require('../../a/c/e').id;"}, + c: {e: "exports.id = module.id;"} + } + }, + // paths relative to module + idtest3: { + a: { + b: "module.exports = require('./c/d').id;", + c: { + d: "module.exports = require('./e');", + e: "exports.id = module.id;" + } + } + }, + // paths relative to module and parent + idtest4: { + a: { + b: "module.exports = require('../a/./c/d').id;", + c: { + d: "module.exports = require('./e');", + e: "exports.id = module.id;" + } + } + }, + // paths relative to root + idtest5: { + a: "module.exports = require('whatever/idtest5/b').id;", + b: "exports.id = module.id;" } }, views: { @@ -134,6 +175,25 @@ couchTests.design_docs = function(debug) { (function() { var lib = require('whatever/commonjs/upper'); return JSON.stringify(this); + }).toString(), + circular_require: + (function() { + return require('whatever/commonjs/circular_one').name; + }).toString(), + idtest1: (function() { + return require('whatever/idtest1/a/b/d'); + }).toString(), + idtest2: (function() { + return require('whatever/idtest2/a/b/d'); + }).toString(), + idtest3: (function() { + return require('whatever/idtest3/a/b'); + }).toString(), + idtest4: (function() { + return require('whatever/idtest4/a/b'); + }).toString(), + idtest5: (function() { + return require('whatever/idtest5/a'); }).toString() } }; // designDoc @@ -174,6 +234,52 @@ couchTests.design_docs = function(debug) { T(xhr.status == 200); TEquals("javascript", JSON.parse(xhr.responseText).language); + // test circular commonjs dependencies + xhr = CouchDB.request( + "GET", + "/test_suite_db/_design/test/_show/circular_require" + ); + TEquals(200, xhr.status); + TEquals("One", xhr.responseText); + + // Test that changes to the design doc properly invalidate cached modules: + + // update the designDoc and replace + designDoc.whatever.commonjs.circular_one = "exports.name = 'Updated';" + T(db.save(designDoc).ok); + + // request circular_require show function again and check the response has + // changed + xhr = CouchDB.request( + "GET", + "/test_suite_db/_design/test/_show/circular_require" + ); + TEquals(200, xhr.status); + TEquals("Updated", xhr.responseText); + + + // test module id values are as expected: + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest1"); + TEquals(200, xhr.status); + TEquals("whatever/idtest1/a/c/e", xhr.responseText); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest2"); + TEquals(200, xhr.status); + TEquals("whatever/idtest2/a/c/e", xhr.responseText); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest3"); + TEquals(200, xhr.status); + TEquals("whatever/idtest3/a/c/e", xhr.responseText); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest4"); + TEquals(200, xhr.status); + TEquals("whatever/idtest4/a/c/e", xhr.responseText); + + xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest5"); + TEquals(200, xhr.status); + TEquals("whatever/idtest5/b", xhr.responseText); + + var prev_view_sig = db.designInfo("_design/test").view_index.signature; var prev_view_size = db.designInfo("_design/test").view_index.disk_size; -- cgit v1.2.3