From e749ffad9b4fe3110d97f366ebe39e7c9a4edd9d Mon Sep 17 00:00:00 2001 From: Jason Carey Date: Thu, 9 Jul 2015 14:05:20 -0400 Subject: SERVER-18531 Integrate SpiderMonkey Provides SpiderMonkey 38.0.1esr as a JS engine for mongo and mongod. --- SConstruct | 14 +- buildscripts/packager-enterprise.py | 2 +- buildscripts/packager.py | 2 +- buildscripts/utils.py | 4 +- debian/mongodb-enterprise-server.docs | 1 + debian/mongodb-enterprise-unstable-server.docs | 1 + debian/mongodb-org-server.docs | 1 + debian/mongodb-org-unstable-server.docs | 1 + distsrc/MPL-2 | 373 +++++++++++ distsrc/THIRD-PARTY-NOTICES | 201 ++++++ jstests/core/dbadmin.js | 2 +- jstests/core/hashtest1.js | 5 +- jstests/core/where5.js | 5 +- jstests/replsets/capped_id.js | 11 +- rpm/mongodb-enterprise-unstable.spec | 1 + rpm/mongodb-enterprise.spec | 1 + rpm/mongodb-org-unstable.spec | 1 + rpm/mongodb-org.spec | 1 + src/mongo/SConscript | 6 +- src/mongo/base/error_codes.err | 1 + src/mongo/dbtests/jstests.cpp | 66 +- src/mongo/installer/msi/wxs/LicensingFragment.wxs | 5 + src/mongo/scripting/SConscript | 78 ++- src/mongo/scripting/deadline_monitor.h | 167 +++++ src/mongo/scripting/deadline_monitor_test.cpp | 177 +++++ src/mongo/scripting/engine_v8-3.25.h | 2 +- src/mongo/scripting/engine_v8.h | 2 +- src/mongo/scripting/mozjs/PosixNSPR.cpp | 259 ++++++++ src/mongo/scripting/mozjs/base.cpp | 68 ++ src/mongo/scripting/mozjs/base.h | 92 +++ src/mongo/scripting/mozjs/bindata.cpp | 216 ++++++ src/mongo/scripting/mozjs/bindata.h | 63 ++ src/mongo/scripting/mozjs/bson.cpp | 235 +++++++ src/mongo/scripting/mozjs/bson.h | 77 +++ src/mongo/scripting/mozjs/countdownlatch.cpp | 198 ++++++ src/mongo/scripting/mozjs/countdownlatch.h | 61 ++ src/mongo/scripting/mozjs/cursor.cpp | 122 ++++ src/mongo/scripting/mozjs/cursor.h | 76 +++ src/mongo/scripting/mozjs/db.cpp | 139 ++++ src/mongo/scripting/mozjs/db.h | 56 ++ src/mongo/scripting/mozjs/dbcollection.cpp | 85 +++ src/mongo/scripting/mozjs/dbcollection.h | 56 ++ src/mongo/scripting/mozjs/dbpointer.cpp | 66 ++ src/mongo/scripting/mozjs/dbpointer.h | 52 ++ src/mongo/scripting/mozjs/dbquery.cpp | 143 ++++ src/mongo/scripting/mozjs/dbquery.h | 53 ++ src/mongo/scripting/mozjs/dbref.cpp | 68 ++ src/mongo/scripting/mozjs/dbref.h | 53 ++ src/mongo/scripting/mozjs/engine.cpp | 133 ++++ src/mongo/scripting/mozjs/engine.h | 93 +++ src/mongo/scripting/mozjs/exception.cpp | 89 +++ src/mongo/scripting/mozjs/exception.h | 67 ++ src/mongo/scripting/mozjs/global.cpp | 103 +++ src/mongo/scripting/mozjs/global.h | 56 ++ src/mongo/scripting/mozjs/idwrapper.cpp | 74 +++ src/mongo/scripting/mozjs/idwrapper.h | 71 ++ src/mongo/scripting/mozjs/implscope.cpp | 728 +++++++++++++++++++++ src/mongo/scripting/mozjs/implscope.h | 323 +++++++++ src/mongo/scripting/mozjs/jscustomallocator.cpp | 234 +++++++ src/mongo/scripting/mozjs/jsstringwrapper.cpp | 84 +++ src/mongo/scripting/mozjs/jsstringwrapper.h | 61 ++ src/mongo/scripting/mozjs/jsthread.cpp | 274 ++++++++ src/mongo/scripting/mozjs/jsthread.h | 74 +++ src/mongo/scripting/mozjs/maxkey.cpp | 94 +++ src/mongo/scripting/mozjs/maxkey.h | 59 ++ src/mongo/scripting/mozjs/minkey.cpp | 94 +++ src/mongo/scripting/mozjs/minkey.h | 59 ++ src/mongo/scripting/mozjs/mongo.cpp | 565 ++++++++++++++++ src/mongo/scripting/mozjs/mongo.h | 88 +++ src/mongo/scripting/mozjs/nativefunction.cpp | 124 ++++ src/mongo/scripting/mozjs/nativefunction.h | 73 +++ src/mongo/scripting/mozjs/numberint.cpp | 112 ++++ src/mongo/scripting/mozjs/numberint.h | 61 ++ src/mongo/scripting/mozjs/numberlong.cpp | 164 +++++ src/mongo/scripting/mozjs/numberlong.h | 68 ++ src/mongo/scripting/mozjs/object.cpp | 87 +++ src/mongo/scripting/mozjs/object.h | 56 ++ src/mongo/scripting/mozjs/objectwrapper.cpp | 385 +++++++++++ src/mongo/scripting/mozjs/objectwrapper.h | 181 +++++ src/mongo/scripting/mozjs/oid.cpp | 87 +++ src/mongo/scripting/mozjs/oid.h | 54 ++ src/mongo/scripting/mozjs/proxyscope.cpp | 318 +++++++++ src/mongo/scripting/mozjs/proxyscope.h | 195 ++++++ src/mongo/scripting/mozjs/regexp.cpp | 39 ++ src/mongo/scripting/mozjs/regexp.h | 49 ++ src/mongo/scripting/mozjs/timestamp.cpp | 77 +++ src/mongo/scripting/mozjs/timestamp.h | 53 ++ src/mongo/scripting/mozjs/valuereader.cpp | 272 ++++++++ src/mongo/scripting/mozjs/valuereader.h | 61 ++ src/mongo/scripting/mozjs/valuewriter.cpp | 252 +++++++ src/mongo/scripting/mozjs/valuewriter.h | 84 +++ src/mongo/scripting/mozjs/wraptype.h | 474 ++++++++++++++ src/mongo/scripting/v8_deadline_monitor.h | 167 ----- src/mongo/scripting/v8_deadline_monitor_test.cpp | 177 ----- src/mongo/shell/bulk_api.js | 2 - src/mongo/shell/types.js | 4 +- src/mongo/util/concurrency/threadlocal.h | 2 + src/third_party/SConscript | 28 +- src/third_party/mozjs-38/SConscript | 98 +++ .../mozjs-38/mongo_sources/jscustomallocator.h | 54 ++ .../mozjs-38/mongo_sources/solaris_hacks.h | 44 ++ .../mozjs-38/mongo_sources/vm/PosixNSPR.cpp | 5 + .../mozjs-38/mongo_sources/vm/PosixNSPR.h | 137 ++++ src/third_party/shim_mozjs.cpp | 3 + 104 files changed, 10445 insertions(+), 394 deletions(-) create mode 100644 distsrc/MPL-2 create mode 100644 src/mongo/scripting/deadline_monitor.h create mode 100644 src/mongo/scripting/deadline_monitor_test.cpp create mode 100644 src/mongo/scripting/mozjs/PosixNSPR.cpp create mode 100644 src/mongo/scripting/mozjs/base.cpp create mode 100644 src/mongo/scripting/mozjs/base.h create mode 100644 src/mongo/scripting/mozjs/bindata.cpp create mode 100644 src/mongo/scripting/mozjs/bindata.h create mode 100644 src/mongo/scripting/mozjs/bson.cpp create mode 100644 src/mongo/scripting/mozjs/bson.h create mode 100644 src/mongo/scripting/mozjs/countdownlatch.cpp create mode 100644 src/mongo/scripting/mozjs/countdownlatch.h create mode 100644 src/mongo/scripting/mozjs/cursor.cpp create mode 100644 src/mongo/scripting/mozjs/cursor.h create mode 100644 src/mongo/scripting/mozjs/db.cpp create mode 100644 src/mongo/scripting/mozjs/db.h create mode 100644 src/mongo/scripting/mozjs/dbcollection.cpp create mode 100644 src/mongo/scripting/mozjs/dbcollection.h create mode 100644 src/mongo/scripting/mozjs/dbpointer.cpp create mode 100644 src/mongo/scripting/mozjs/dbpointer.h create mode 100644 src/mongo/scripting/mozjs/dbquery.cpp create mode 100644 src/mongo/scripting/mozjs/dbquery.h create mode 100644 src/mongo/scripting/mozjs/dbref.cpp create mode 100644 src/mongo/scripting/mozjs/dbref.h create mode 100644 src/mongo/scripting/mozjs/engine.cpp create mode 100644 src/mongo/scripting/mozjs/engine.h create mode 100644 src/mongo/scripting/mozjs/exception.cpp create mode 100644 src/mongo/scripting/mozjs/exception.h create mode 100644 src/mongo/scripting/mozjs/global.cpp create mode 100644 src/mongo/scripting/mozjs/global.h create mode 100644 src/mongo/scripting/mozjs/idwrapper.cpp create mode 100644 src/mongo/scripting/mozjs/idwrapper.h create mode 100644 src/mongo/scripting/mozjs/implscope.cpp create mode 100644 src/mongo/scripting/mozjs/implscope.h create mode 100644 src/mongo/scripting/mozjs/jscustomallocator.cpp create mode 100644 src/mongo/scripting/mozjs/jsstringwrapper.cpp create mode 100644 src/mongo/scripting/mozjs/jsstringwrapper.h create mode 100644 src/mongo/scripting/mozjs/jsthread.cpp create mode 100644 src/mongo/scripting/mozjs/jsthread.h create mode 100644 src/mongo/scripting/mozjs/maxkey.cpp create mode 100644 src/mongo/scripting/mozjs/maxkey.h create mode 100644 src/mongo/scripting/mozjs/minkey.cpp create mode 100644 src/mongo/scripting/mozjs/minkey.h create mode 100644 src/mongo/scripting/mozjs/mongo.cpp create mode 100644 src/mongo/scripting/mozjs/mongo.h create mode 100644 src/mongo/scripting/mozjs/nativefunction.cpp create mode 100644 src/mongo/scripting/mozjs/nativefunction.h create mode 100644 src/mongo/scripting/mozjs/numberint.cpp create mode 100644 src/mongo/scripting/mozjs/numberint.h create mode 100644 src/mongo/scripting/mozjs/numberlong.cpp create mode 100644 src/mongo/scripting/mozjs/numberlong.h create mode 100644 src/mongo/scripting/mozjs/object.cpp create mode 100644 src/mongo/scripting/mozjs/object.h create mode 100644 src/mongo/scripting/mozjs/objectwrapper.cpp create mode 100644 src/mongo/scripting/mozjs/objectwrapper.h create mode 100644 src/mongo/scripting/mozjs/oid.cpp create mode 100644 src/mongo/scripting/mozjs/oid.h create mode 100644 src/mongo/scripting/mozjs/proxyscope.cpp create mode 100644 src/mongo/scripting/mozjs/proxyscope.h create mode 100644 src/mongo/scripting/mozjs/regexp.cpp create mode 100644 src/mongo/scripting/mozjs/regexp.h create mode 100644 src/mongo/scripting/mozjs/timestamp.cpp create mode 100644 src/mongo/scripting/mozjs/timestamp.h create mode 100644 src/mongo/scripting/mozjs/valuereader.cpp create mode 100644 src/mongo/scripting/mozjs/valuereader.h create mode 100644 src/mongo/scripting/mozjs/valuewriter.cpp create mode 100644 src/mongo/scripting/mozjs/valuewriter.h create mode 100644 src/mongo/scripting/mozjs/wraptype.h delete mode 100644 src/mongo/scripting/v8_deadline_monitor.h delete mode 100644 src/mongo/scripting/v8_deadline_monitor_test.cpp create mode 100644 src/third_party/mozjs-38/SConscript create mode 100644 src/third_party/mozjs-38/mongo_sources/jscustomallocator.h create mode 100644 src/third_party/mozjs-38/mongo_sources/solaris_hacks.h create mode 100644 src/third_party/mozjs-38/mongo_sources/vm/PosixNSPR.cpp create mode 100644 src/third_party/mozjs-38/mongo_sources/vm/PosixNSPR.h create mode 100644 src/third_party/shim_mozjs.cpp diff --git a/SConstruct b/SConstruct index 33312bc9439..c25ad2ce68b 100644 --- a/SConstruct +++ b/SConstruct @@ -214,7 +214,7 @@ add_option('wiredtiger', ) # library choices -js_engine_choices = ['v8-3.12', 'v8-3.25', 'none'] +js_engine_choices = ['v8-3.12', 'v8-3.25', 'mozjs', 'none'] add_option('js-engine', choices=js_engine_choices, default=js_engine_choices[0], @@ -756,12 +756,14 @@ jsEngine = get_option( "js-engine") serverJs = get_option( "server-js" ) == "on" -usev8 = (jsEngine != 'none') +usev8 = (jsEngine.startswith('v8')) + +usemozjs = (jsEngine.startswith('mozjs')) v8version = jsEngine[3:] if jsEngine.startswith('v8-') else 'none' v8suffix = '' if v8version == '3.12' else '-' + v8version -if not serverJs and not usev8: +if not serverJs and not usev8 and not usemozjs: print("Warning: --server-js=off is not needed with --js-engine=none") # We defer building the env until we have determined whether we want certain values. Some values @@ -1197,12 +1199,9 @@ elif env.TargetOSIs('windows'): 'DbgHelp.lib', 'shell32.lib', 'Iphlpapi.lib', + 'winmm.lib', 'version.lib']) - # v8 calls timeGetTime() - if usev8: - env.Append(LIBS=['winmm.lib']) - # When building on visual studio, this sets the name of the debug symbols file if env.ToolchainIs('msvc'): env['PDB'] = '${TARGET.base}.pdb' @@ -2303,6 +2302,7 @@ Export("get_option") Export("has_option use_system_version_of_library") Export("serverJs") Export("usev8") +Export("usemozjs") Export("v8version v8suffix") Export("boostSuffix") Export('module_sconscripts') diff --git a/buildscripts/packager-enterprise.py b/buildscripts/packager-enterprise.py index bbc9c28995e..26e10b553e5 100755 --- a/buildscripts/packager-enterprise.py +++ b/buildscripts/packager-enterprise.py @@ -335,7 +335,7 @@ def unpack_binaries_into(build_os, arch, spec, where): try: sysassert(["tar", "xvzf", rootdir+"/"+tarfile(build_os, arch, spec)]) release_dir = glob('mongodb-linux-*')[0] - for releasefile in "bin", "snmp", "LICENSE.txt", "README", "THIRD-PARTY-NOTICES": + for releasefile in "bin", "snmp", "LICENSE.txt", "README", "THIRD-PARTY-NOTICES", "MPL-2": os.rename("%s/%s" % (release_dir, releasefile), releasefile) os.rmdir(release_dir) except Exception: diff --git a/buildscripts/packager.py b/buildscripts/packager.py index 54b2c53ee22..6896eabb542 100755 --- a/buildscripts/packager.py +++ b/buildscripts/packager.py @@ -385,7 +385,7 @@ def unpack_binaries_into(build_os, arch, spec, where): try: sysassert(["tar", "xvzf", rootdir+"/"+tarfile(build_os, arch, spec)]) release_dir = glob('mongodb-linux-*')[0] - for releasefile in "bin", "GNU-AGPL-3.0", "README", "THIRD-PARTY-NOTICES": + for releasefile in "bin", "GNU-AGPL-3.0", "README", "THIRD-PARTY-NOTICES", "MPL-2": print "moving file: %s/%s" % (release_dir, releasefile) os.rename("%s/%s" % (release_dir, releasefile), releasefile) os.rmdir(release_dir) diff --git a/buildscripts/utils.py b/buildscripts/utils.py index 62a2b1d13eb..a05a4f6f9f9 100644 --- a/buildscripts/utils.py +++ b/buildscripts/utils.py @@ -24,10 +24,10 @@ def getAllSourceFiles( arr=None , prefix="." ): for x in os.listdir( prefix ): if x.startswith( "." ) or x.startswith( "pcre-" ) or x.startswith( "32bit" ) or x.startswith( "mongodb-" ) or x.startswith("debian") or x.startswith( "mongo-cxx-driver" ): continue - # XXX: Avoid conflict between v8 and v8-3.25 source files in + # XXX: Avoid conflict between v8, v8-3.25 and mozjs source files in # src/mongo/scripting # Remove after v8-3.25 migration. - if x.find("v8-3.25") != -1: + if x.find("v8-3.25") != -1 or x.find("mozjs") != -1: continue full = prefix + "/" + x if os.path.isdir( full ) and not os.path.islink( full ): diff --git a/debian/mongodb-enterprise-server.docs b/debian/mongodb-enterprise-server.docs index 862a424eb58..9b2620af0ce 100644 --- a/debian/mongodb-enterprise-server.docs +++ b/debian/mongodb-enterprise-server.docs @@ -6,3 +6,4 @@ snmp/MONGODBINC-MIB.txt LICENSE.txt README THIRD-PARTY-NOTICES +MPL-2 diff --git a/debian/mongodb-enterprise-unstable-server.docs b/debian/mongodb-enterprise-unstable-server.docs index 862a424eb58..9b2620af0ce 100644 --- a/debian/mongodb-enterprise-unstable-server.docs +++ b/debian/mongodb-enterprise-unstable-server.docs @@ -6,3 +6,4 @@ snmp/MONGODBINC-MIB.txt LICENSE.txt README THIRD-PARTY-NOTICES +MPL-2 diff --git a/debian/mongodb-org-server.docs b/debian/mongodb-org-server.docs index c01d53eb317..b1cea8cce6f 100644 --- a/debian/mongodb-org-server.docs +++ b/debian/mongodb-org-server.docs @@ -1,3 +1,4 @@ GNU-AGPL-3.0 README THIRD-PARTY-NOTICES +MPL-2 diff --git a/debian/mongodb-org-unstable-server.docs b/debian/mongodb-org-unstable-server.docs index c01d53eb317..b1cea8cce6f 100644 --- a/debian/mongodb-org-unstable-server.docs +++ b/debian/mongodb-org-unstable-server.docs @@ -1,3 +1,4 @@ GNU-AGPL-3.0 README THIRD-PARTY-NOTICES +MPL-2 diff --git a/distsrc/MPL-2 b/distsrc/MPL-2 new file mode 100644 index 00000000000..14e2f777f6c --- /dev/null +++ b/distsrc/MPL-2 @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/distsrc/THIRD-PARTY-NOTICES b/distsrc/THIRD-PARTY-NOTICES index 54d3f4d4886..164d1838974 100644 --- a/distsrc/THIRD-PARTY-NOTICES +++ b/distsrc/THIRD-PARTY-NOTICES @@ -8,6 +8,9 @@ please bring it to our attention through any of the ways detailed here : The attached notices are provided for information only. +For any licenses that require disclosure of source, sources are available at +https://github.com/mongodb/mongo. + 1) License Notice for Boost --------------------------- @@ -498,4 +501,202 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +13) License Notice for SpiderMonkey +----------------------------------- + +|------------------------------------------------|------------------|---------------| +| SpiderMonkey Distribution Files | Copyright Holder | License | +|------------------------------------------------|------------------|---------------| +| js/src/jit/shared/AssemblerBuffer-x86-shared.h | Apple, Inc | BSD-2-Clause | +| js/src/jit/shared/BaseAssembler-x86-shared.h | | | +|------------------------------------------------|------------------|---------------| +| js/src/builtin/ | Google, Inc | BSD-3-Clause | +| js/src/irregexp/ | | | +| js/src/jit/arm/ | | | +| js/src/jit/mips/ | | | +| mfbt/double-conversion/ | | | +|------------------------------------------------|------------------|---------------| +| intl/icu/source/common/unicode/ | IBM, Inc | ICU | +|------------------------------------------------|------------------|---------------| +| js/src/asmjs/ | Mozilla, Inc | Apache2 | +|------------------------------------------------|------------------|---------------| +| js/public/ | Mozilla, Inc | MPL2 | +| js/src/ | | | +| mfbt | | | +|------------------------------------------------|------------------|---------------| +| js/src/vm/Unicode.cpp | None | Public Domain | +|------------------------------------------------|------------------|---------------| +| mfbt/lz4.c | Yann Collet | BSD-2-Clause | +| mfbt/lz4.h | | | +|------------------------------------------------|------------------|---------------| + +Other optional 3rd party software included in the SpiderMonkey distribution is removed by MongoDB. + + +Apple, Inc: BSD-2-Clause +------------------------ + +Copyright (C) 2008 Apple Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Google, Inc: BSD-3-Clause +------------------------- + +Copyright 2012 the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +ICU License - ICU 1.8.1 and later +--------------------------------- + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2012 International Business Machines Corporation and +others + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, provided that the above copyright notice(s) and this +permission notice appear in all copies of the Software and that both the +above copyright notice(s) and this permission notice appear in supporting +documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in this Software without prior written authorization of the +copyright holder. + +All trademarks and registered trademarks mentioned herein are the property +of their respective owners. + + +Mozilla, Inc: Apache 2 +---------------------- + +Copyright 2014 Mozilla Foundation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +Mozilla, Inc: MPL 2 +------------------- + +Copyright 2014 Mozilla Foundation + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +Public Domain +------------- + +Any copyright is dedicated to the Public Domain. +http://creativecommons.org/licenses/publicdomain/ + + +LZ4: BSD-2-Clause +----------------- + +Copyright (C) 2011-2014, Yann Collet. +BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +You can contact the author at : +- LZ4 source repository : http://code.google.com/p/lz4/ +- LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c + + End diff --git a/jstests/core/dbadmin.js b/jstests/core/dbadmin.js index dbf195e056c..93e7dea308f 100644 --- a/jstests/core/dbadmin.js +++ b/jstests/core/dbadmin.js @@ -99,5 +99,5 @@ for (var i = 0; i < (versionArray.length - 1); i++) if (versionArray[i] >= 0) { assert.eq(serverStatus.version, latestStartUpLog.buildinfo.version, "Mongo version doesn't match that from ServerStatus"); assert.eq(version, versionArrayCleaned.join('.'), "version doesn't match that from the versionArray"); var jsEngine = latestStartUpLog.buildinfo.javascriptEngine; -assert((jsEngine.startsWith("v8") || jsEngine == "none")); +assert((jsEngine.startsWith("v8") || jsEngine == "none" || jsEngine.startsWith("mozjs"))); assert.eq(isMaster.maxBsonObjectSize, latestStartUpLog.buildinfo.maxBsonObjectSize, "maxBsonObjectSize doesn't match one from ismaster"); diff --git a/jstests/core/hashtest1.js b/jstests/core/hashtest1.js index 981a0c36877..d0307c2e59e 100644 --- a/jstests/core/hashtest1.js +++ b/jstests/core/hashtest1.js @@ -34,7 +34,8 @@ var nullHash = hash( null ); assert(! friendlyEqual( falseHash , nullHash ) , "false and null should hash to different things"); var dateHash = hash( new Date() ); -sleep(1); +// Sleep so we get a new date. Sleeping for 1 sometimes returns the same date, so 2 +sleep(2); var isodateHash = hash( ISODate() ); assert(! friendlyEqual( dateHash, isodateHash) , "different dates should hash to different things"); @@ -75,4 +76,4 @@ assert.eq( nanHash , zeroHash , "NaN and Zero should hash to the same thing"); //should also test that CodeWScope hashes correctly -//but waiting for SERVER-3391 (CodeWScope support in shell) \ No newline at end of file +//but waiting for SERVER-3391 (CodeWScope support in shell) diff --git a/jstests/core/where5.js b/jstests/core/where5.js index d7140cd21f6..1c0ea8d0076 100644 --- a/jstests/core/where5.js +++ b/jstests/core/where5.js @@ -1,4 +1,4 @@ -// Tests toString() on _v8_function in object constructor. +// Tests toString() in object constructor. // Verifies that native functions do not expose the _native_function and _native_data properties. var t = db.where5; @@ -14,9 +14,6 @@ function printIdConstructor(doc) { doc = this; } - // This used to crash. - doc._id.constructor._v8_function.toString(); - // Verify that function and data fields are hidden. assert(!('_native_function' in sleep)); assert(!('_native_data' in sleep)); diff --git a/jstests/replsets/capped_id.js b/jstests/replsets/capped_id.js index 520b7c4cca5..cd866fb0234 100644 --- a/jstests/replsets/capped_id.js +++ b/jstests/replsets/capped_id.js @@ -39,6 +39,12 @@ var masterdb = master.getDB( dbname ); var slave1db = slave1.getDB( dbname ); var slave2db = slave2.getDB( dbname ); +function countIdIndexes(theDB, coll) { + return theDB[coll].getIndexes().filter(function(idx) { + return friendlyEqual(idx.key, {_id: 1}); + }).length; +} + var numtests = 4; for( testnum=0; testnum < numtests; testnum++ ){ @@ -70,11 +76,6 @@ for( testnum=0; testnum < numtests; testnum++ ){ } replTest.awaitReplication(); - function countIdIndexes(theDB, coll) { - return theDB[coll].getIndexes().filter(function(idx) { - return friendlyEqual(idx.key, {_id: 1}); - }).length; - } // make sure _id index exists on primary assert.eq( 1 , countIdIndexes(masterdb, coll), diff --git a/rpm/mongodb-enterprise-unstable.spec b/rpm/mongodb-enterprise-unstable.spec index 0bb9794c0cb..f15346f2758 100644 --- a/rpm/mongodb-enterprise-unstable.spec +++ b/rpm/mongodb-enterprise-unstable.spec @@ -220,6 +220,7 @@ fi %doc LICENSE.txt %doc README %doc THIRD-PARTY-NOTICES +%doc MPL-2 %files shell %defattr(-,root,root,-) diff --git a/rpm/mongodb-enterprise.spec b/rpm/mongodb-enterprise.spec index 784af693c92..2e8e265bab0 100644 --- a/rpm/mongodb-enterprise.spec +++ b/rpm/mongodb-enterprise.spec @@ -229,6 +229,7 @@ fi %doc LICENSE.txt %doc README %doc THIRD-PARTY-NOTICES +%doc MPL-2 diff --git a/rpm/mongodb-org-unstable.spec b/rpm/mongodb-org-unstable.spec index 22b66e73e28..692f6ac8557 100644 --- a/rpm/mongodb-org-unstable.spec +++ b/rpm/mongodb-org-unstable.spec @@ -214,6 +214,7 @@ fi %doc GNU-AGPL-3.0 %doc README %doc THIRD-PARTY-NOTICES +%doc MPL-2 %files shell %defattr(-,root,root,-) diff --git a/rpm/mongodb-org.spec b/rpm/mongodb-org.spec index 382e3f1c6db..8117b9341a2 100644 --- a/rpm/mongodb-org.spec +++ b/rpm/mongodb-org.spec @@ -224,6 +224,7 @@ fi %doc GNU-AGPL-3.0 %doc README %doc THIRD-PARTY-NOTICES +%doc MPL-2 diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 72388e5a6a0..c66798e4e0c 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -15,6 +15,7 @@ Import("env") Import("has_option") Import("get_option") Import("usev8") +Import("usemozjs") Import("use_system_version_of_library") # Boost we need everywhere. 's2' is spammed in all over the place by @@ -269,7 +270,7 @@ if env.TargetOSIs('osx') or env["_HAVEPCAP"]: # --- shell --- -if not has_option('noshell') and usev8: +if not has_option('noshell') and (usev8 or usemozjs): shell_core_env = env.Clone() if has_option("safeshell"): shell_core_env.Append(CPPDEFINES=["MONGO_SAFE_SHELL"]) @@ -415,7 +416,8 @@ env.Alias( "core", [ '#/%s' % b for b in [ add_exe( "mongo" ), add_exe( "mongod" # Stage the top-level mongodb banners distsrc = env.Dir('#distsrc') env.Append(MODULE_BANNERS = [distsrc.File('README'), - distsrc.File('THIRD-PARTY-NOTICES')]) + distsrc.File('THIRD-PARTY-NOTICES'), + distsrc.File('MPL-2')]) # If no module has introduced a file named LICENSE.txt, then inject the AGPL. if sum(itertools.imap(lambda x: x.name == "LICENSE.txt", env['MODULE_BANNERS'])) == 0: diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err index 68311bc4296..f518cfd07d9 100644 --- a/src/mongo/base/error_codes.err +++ b/src/mongo/base/error_codes.err @@ -138,6 +138,7 @@ error_code("StaleTerm", 135) error_code("CappedPositionLost", 136) error_code("IncompatibleShardingConfigVersion", 137) error_code("RemoteOplogStale", 138) +error_code("JSInterpreterFailure", 139) # Non-sequential error codes (for compatibility only) error_code("NotMaster", 10107) #this comes from assert_util.h diff --git a/src/mongo/dbtests/jstests.cpp b/src/mongo/dbtests/jstests.cpp index 68c6e330258..e5216d5842d 100644 --- a/src/mongo/dbtests/jstests.cpp +++ b/src/mongo/dbtests/jstests.cpp @@ -208,7 +208,14 @@ public: // An error is logged for an invalid statement when reportError == true. ASSERT(!scope->exec("notAFunction()", "foo", false, true, false)); - ASSERT(_logger.logged()); + + // Don't check if we're using SpiderMonkey. Our threading model breaks + // this test + // TODO: figure out a way to check for SpiderMonkey + auto ivs = globalScriptEngine->getInterpreterVersionString(); + if (ivs.compare(0, ivs.length(), "MozJS") != 0) { + ASSERT(_logger.logged()); + } } private: @@ -231,7 +238,14 @@ public: } catch (const DBException&) { // ignore the exception; just test that we logged something } - ASSERT(_logger.logged()); + + // Don't check if we're using SpiderMonkey. Our threading model breaks + // this test + // TODO: figure out a way to check for SpiderMonkey + auto ivs = globalScriptEngine->getInterpreterVersionString(); + if (ivs.compare(0, ivs.length(), "MozJS") != 0) { + ASSERT(_logger.logged()); + } } private: @@ -389,25 +403,43 @@ public: << "zz" << BSONObj()); s->setObject("blah", o, true); - s->invoke("blah.y = 'e'", 0, 0); - BSONObj out = s->getObject("blah"); - ASSERT(strlen(out["y"].valuestr()) > 1); + BSONObj out; + + /** + * TODO remove the v8 tests after we switch over + * + * Note that we've changed behavior so that uncaught js exceptions that + * bubble up actually convert into user exceptions, instead of just + * logging to stdout and silently failing otherwise. + */ + auto ivs = globalScriptEngine->getInterpreterVersionString(); + if (ivs.compare(0, ivs.length(), "MozJS") == 0) { + ASSERT_THROWS(s->invoke("blah.y = 'e'", 0, 0), mongo::UserException); + ASSERT_THROWS(s->invoke("blah.a = 19;", 0, 0), mongo::UserException); + ASSERT_THROWS(s->invoke("blah.zz.a = 19;", 0, 0), mongo::UserException); + ASSERT_THROWS(s->setObject("blah.zz", BSON("a" << 19)), mongo::UserException); + ASSERT_THROWS(s->invoke("delete blah['x']", 0, 0), mongo::UserException); + } else { + s->invoke("blah.y = 'e'", 0, 0); + out = s->getObject("blah"); + ASSERT(strlen(out["y"].valuestr()) > 1); - s->invoke("blah.a = 19;", 0, 0); - out = s->getObject("blah"); - ASSERT(out["a"].eoo()); + s->invoke("blah.a = 19;", 0, 0); + out = s->getObject("blah"); + ASSERT(out["a"].eoo()); - s->invoke("blah.zz.a = 19;", 0, 0); - out = s->getObject("blah"); - ASSERT(out["zz"].embeddedObject()["a"].eoo()); + s->invoke("blah.zz.a = 19;", 0, 0); + out = s->getObject("blah"); + ASSERT(out["zz"].embeddedObject()["a"].eoo()); - s->setObject("blah.zz", BSON("a" << 19)); - out = s->getObject("blah"); - ASSERT(out["zz"].embeddedObject()["a"].eoo()); + s->setObject("blah.zz", BSON("a" << 19)); + out = s->getObject("blah"); + ASSERT(out["zz"].embeddedObject()["a"].eoo()); - s->invoke("delete blah['x']", 0, 0); - out = s->getObject("blah"); - ASSERT(!out["x"].eoo()); + s->invoke("delete blah['x']", 0, 0); + out = s->getObject("blah"); + ASSERT(!out["x"].eoo()); + } // read-only object itself can be overwritten s->invoke("blah = {}", 0, 0); diff --git a/src/mongo/installer/msi/wxs/LicensingFragment.wxs b/src/mongo/installer/msi/wxs/LicensingFragment.wxs index 41c7b4bb010..7e2ff5ba8cf 100644 --- a/src/mongo/installer/msi/wxs/LicensingFragment.wxs +++ b/src/mongo/installer/msi/wxs/LicensingFragment.wxs @@ -25,6 +25,10 @@ + + + + diff --git a/src/mongo/scripting/SConscript b/src/mongo/scripting/SConscript index f05572b98c8..f3c3d9a05fd 100644 --- a/src/mongo/scripting/SConscript +++ b/src/mongo/scripting/SConscript @@ -4,6 +4,7 @@ Import([ 'env', 'serverJs', 'usev8', + 'usemozjs', 'v8suffix', ]) @@ -68,6 +69,79 @@ if usev8: '$BUILD_DIR/mongo/db/service_context', ], ) +elif usemozjs: + scriptingEnv = env.Clone() + scriptingEnv.InjectThirdPartyIncludePaths(libraries=['mozjs']) + + # TODO: get rid of all of this /FI and -include stuff and migrate to a shim + # header we include in all of our files. + if env.TargetOSIs('windows'): + scriptingEnv.Append(CCFLAGS=[ + '/FI', 'js-config.h', + '/FI', 'js/RequiredDefines.h', + ]) + else: + scriptingEnv.Append( + CCFLAGS=[ + '-include', 'js-config.h', + '-include', 'js/RequiredDefines.h', + '-Wno-invalid-offsetof', + ], + CXXFLAGS=[ + '-Wno-non-virtual-dtor', + ], + ) + + scriptingEnv.Prepend(CPPDEFINES=[ + 'JS_USE_CUSTOM_ALLOCATOR', + 'STATIC_JS_API=1', + ]) + + scriptingEnv.Library( + target='scripting', + source=[ + 'mozjs/base.cpp', + 'mozjs/bindata.cpp', + 'mozjs/bson.cpp', + 'mozjs/countdownlatch.cpp', + 'mozjs/cursor.cpp', + 'mozjs/dbcollection.cpp', + 'mozjs/db.cpp', + 'mozjs/dbpointer.cpp', + 'mozjs/dbquery.cpp', + 'mozjs/dbref.cpp', + 'mozjs/engine.cpp', + 'mozjs/exception.cpp', + 'mozjs/global.cpp', + 'mozjs/idwrapper.cpp', + 'mozjs/implscope.cpp', + 'mozjs/jscustomallocator.cpp', + 'mozjs/jsstringwrapper.cpp', + 'mozjs/jsthread.cpp', + 'mozjs/maxkey.cpp', + 'mozjs/minkey.cpp', + 'mozjs/mongo.cpp', + 'mozjs/nativefunction.cpp', + 'mozjs/numberint.cpp', + 'mozjs/numberlong.cpp', + 'mozjs/object.cpp', + 'mozjs/objectwrapper.cpp', + 'mozjs/oid.cpp', + 'mozjs/PosixNSPR.cpp', + 'mozjs/proxyscope.cpp', + 'mozjs/regexp.cpp', + 'mozjs/timestamp.cpp', + 'mozjs/valuereader.cpp', + 'mozjs/valuewriter.cpp', + ], + LIBDEPS=[ + 'bson_template_evaluator', + 'scripting_common', + '$BUILD_DIR/third_party/shim_mozjs', + '$BUILD_DIR/mongo/shell/mongojs', + '$BUILD_DIR/mongo/db/service_context', + ], + ) else: env.Library( target='scripting', @@ -88,9 +162,9 @@ env.Library( ) env.CppUnitTest( - target='v8_deadline_monitor_test', + target='deadline_monitor_test', source=[ - 'v8_deadline_monitor_test.cpp', + 'deadline_monitor_test.cpp', ], LIBDEPS=[ ], diff --git a/src/mongo/scripting/deadline_monitor.h b/src/mongo/scripting/deadline_monitor.h new file mode 100644 index 00000000000..89f7e9d1b84 --- /dev/null +++ b/src/mongo/scripting/deadline_monitor.h @@ -0,0 +1,167 @@ +/** + * Copyright (C) 2013 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ +#pragma once + +#include + +#include "mongo/base/disallow_copying.h" +#include "mongo/platform/unordered_map.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/stdx/thread.h" +#include "mongo/util/concurrency/mutex.h" +#include "mongo/util/time_support.h" + +namespace mongo { + +/** + * DeadlineMonitor + * + * Monitors tasks which are required to complete before a deadline. When + * a deadline is started on a _Task*, either the deadline must be stopped, + * or _Task::kill() will be called when the deadline arrives. + * + * Each instance of a DeadlineMonitor spawns a thread which waits for one of the + * following conditions: + * - a task is added to the monitor + * - a task is removed from the monitor + * - the nearest deadline has arrived + * + * Ownership: + * The _Task* must not be freed until the deadline has elapsed or stopDeadline() + * has been called. + * + * NOTE: Each instance of this class spawns a new thread. It is intended to be a stop-gap + * solution for simple deadline monitoring until a more robust solution can be + * implemented. + * + * NOTE: timing is based on wallclock time, which may not be precise. + */ +template +class DeadlineMonitor { + MONGO_DISALLOW_COPYING(DeadlineMonitor); + +public: + DeadlineMonitor() { + // NOTE(schwerin): Because _monitorThread takes a pointer to "this", all of the fields + // of this instance must be initialized before the thread is created. As a result, we + // should not create the thread in the initializer list. Creating it there leaves us + // vulnerable to errors introduced by rearranging the order of fields in the class. + _monitorThread = stdx::thread(&mongo::DeadlineMonitor<_Task>::deadlineMonitorThread, this); + } + + ~DeadlineMonitor() { + { + // ensure the monitor thread has been stopped before destruction + stdx::lock_guard lk(_deadlineMutex); + _inShutdown = true; + _newDeadlineAvailable.notify_one(); + } + _monitorThread.join(); + } + + /** + * Start monitoring a task for deadline lapse. User must call stopDeadline() before + * deleting the task. Note that stopDeadline() cannot be called from within the + * kill() method. + * @param task the task to kill() + * @param timeoutMs number of milliseconds before the deadline expires + */ + void startDeadline(_Task* const task, uint64_t timeoutMs) { + const auto deadline = Date_t::now() + Milliseconds(timeoutMs); + stdx::lock_guard lk(_deadlineMutex); + + _tasks[task] = deadline; + + if (deadline < _nearestDeadlineWallclock) { + _nearestDeadlineWallclock = deadline; + _newDeadlineAvailable.notify_one(); + } + } + + /** + * Stop monitoring a task. Can be called multiple times, before or after a + * deadline has expired (as long as the task remains allocated). + * @return true if the task was found and erased + */ + bool stopDeadline(_Task* const task) { + stdx::lock_guard lk(_deadlineMutex); + return _tasks.erase(task); + } + +private: + /** + * Main deadline monitor loop. Waits on a condition variable until a task + * is started, stopped, or the nearest deadline arrives. If a deadline arrives, + * _Task::kill() is invoked. + */ + void deadlineMonitorThread() { + stdx::unique_lock lk(_deadlineMutex); + while (!_inShutdown) { + // get the next interval to wait + const Date_t now = Date_t::now(); + + // wait for a task to be added or a deadline to expire + if (_nearestDeadlineWallclock > now) { + if (_nearestDeadlineWallclock == Date_t::max()) { + _newDeadlineAvailable.wait(lk); + } else { + _newDeadlineAvailable.wait_until(lk, + _nearestDeadlineWallclock.toSystemTimePoint()); + } + continue; + } + + // set the next interval to wait for deadline completion + _nearestDeadlineWallclock = Date_t::max(); + typename TaskDeadlineMap::iterator i = _tasks.begin(); + while (i != _tasks.end()) { + if (i->second < now) { + // deadline expired + i->first->kill(); + _tasks.erase(i++); + } else { + if (i->second < _nearestDeadlineWallclock) { + // nearest deadline seen so far + _nearestDeadlineWallclock = i->second; + } + ++i; + } + } + } + } + + typedef unordered_map<_Task*, Date_t> TaskDeadlineMap; + TaskDeadlineMap _tasks; // map of running tasks with deadlines + stdx::mutex _deadlineMutex; // protects all non-const members, except _monitorThread + stdx::condition_variable _newDeadlineAvailable; // Signaled for timeout, start and stop + stdx::thread _monitorThread; // the deadline monitor thread + Date_t _nearestDeadlineWallclock = Date_t::max(); // absolute time of the nearest deadline + bool _inShutdown = false; +}; + +} // namespace mongo diff --git a/src/mongo/scripting/deadline_monitor_test.cpp b/src/mongo/scripting/deadline_monitor_test.cpp new file mode 100644 index 00000000000..98f4008b234 --- /dev/null +++ b/src/mongo/scripting/deadline_monitor_test.cpp @@ -0,0 +1,177 @@ +/** + * Copyright (C) 2013 10gen Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +// DeadlineMonitor unit tests + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/deadline_monitor.h" + + +#include "mongo/unittest/unittest.h" + +namespace mongo { + +using std::shared_ptr; +using std::vector; + +class TaskGroup { +public: + TaskGroup() : _c(), _killCount(0), _targetKillCount(0) {} + void noteKill() { + stdx::lock_guard lk(_m); + ++_killCount; + if (_killCount >= _targetKillCount) + _c.notify_one(); + } + void waitForKillCount(uint64_t target) { + stdx::unique_lock lk(_m); + _targetKillCount = target; + while (_killCount < _targetKillCount) + _c.wait(lk); + } + +private: + stdx::mutex _m; + stdx::condition_variable _c; + uint64_t _killCount; + uint64_t _targetKillCount; +}; + +class Task { +public: + Task() : _group(NULL), _killed(0) {} + explicit Task(TaskGroup* group) : _group(group), _killed(0) {} + void kill() { + _killed = curTimeMillis64(); + if (_group) + _group->noteKill(); + } + TaskGroup* _group; + uint64_t _killed; +}; + +// single task expires before stopping the deadline +TEST(DeadlineMonitor, ExpireThenRemove) { + DeadlineMonitor dm; + TaskGroup group; + Task task(&group); + dm.startDeadline(&task, 10); + group.waitForKillCount(1); + ASSERT(task._killed); + ASSERT(!dm.stopDeadline(&task)); +} + +// single task deadline stopped before the task expires +TEST(DeadlineMonitor, RemoveBeforeExpire) { + DeadlineMonitor dm; + Task task; + dm.startDeadline(&task, 3600 * 1000); + ASSERT(dm.stopDeadline(&task)); + ASSERT(!task._killed); +} + +// multiple tasks complete before deadline expires (with 10ms window) +TEST(DeadlineMonitor, MultipleTasksCompleteBeforeExpire) { + DeadlineMonitor dm; + vector> tasks; + + // start 100 tasks with varying deadlines (1-100 hours) + for (int i = 1; i <= 100; i++) { + shared_ptr task(new Task()); + dm.startDeadline(task.get(), i * 3600 * 1000); + tasks.push_back(task); + } + + // verify each deadline is stopped arrival + for (vector>::iterator i = tasks.begin(); i != tasks.end(); ++i) { + ASSERT(dm.stopDeadline(i->get())); + ASSERT(!(*i)->_killed); + } +} + +// multiple tasks expire before stopping the deadline +TEST(DeadlineMonitor, MultipleTasksExpire) { + DeadlineMonitor dm; + TaskGroup group; + vector> tasks; + + // start 100 tasks with varying deadlines + for (int i = 1; i <= 100; i++) { + shared_ptr task(new Task(&group)); + dm.startDeadline(task.get(), i); + tasks.push_back(task); + } + + group.waitForKillCount(100); + + // verify each deadline has expired + for (vector>::iterator i = tasks.begin(); i != tasks.end(); ++i) { + ASSERT(!dm.stopDeadline(i->get())); + ASSERT((*i)->_killed); + } +} + +// mixed expiration and completion +TEST(DeadlineMonitor, MultipleTasksExpireOrComplete) { + DeadlineMonitor dm; + TaskGroup group; + vector> expiredTasks; // tasks that should expire + vector> stoppedTasks; // tasks that should not expire + + // start 100 tasks with varying deadlines + for (int i = 1; i <= 100; i++) { + shared_ptr task(new Task(&group)); + if (i % 2 == 0) { + // stop every other task + dm.startDeadline(task.get(), i * 3600 * 1000); + dm.stopDeadline(task.get()); + stoppedTasks.push_back(task); + continue; + } + dm.startDeadline(task.get(), i); + expiredTasks.push_back(task); + } + + group.waitForKillCount(50); + + // check tasks which exceed the deadline + for (vector>::iterator i = expiredTasks.begin(); i != expiredTasks.end(); + ++i) { + ASSERT(!dm.stopDeadline(i->get())); + ASSERT((*i)->_killed); + } + + // check tasks with a deadline that was stopped + for (vector>::iterator i = stoppedTasks.begin(); i != stoppedTasks.end(); + ++i) { + ASSERT(!(*i)->_killed); + } +} + +} // namespace mongo diff --git a/src/mongo/scripting/engine_v8-3.25.h b/src/mongo/scripting/engine_v8-3.25.h index f3e594c33ee..0865ab59ac3 100644 --- a/src/mongo/scripting/engine_v8-3.25.h +++ b/src/mongo/scripting/engine_v8-3.25.h @@ -40,7 +40,7 @@ #include "mongo/client/dbclientcursor.h" #include "mongo/platform/unordered_map.h" #include "mongo/scripting/engine.h" -#include "mongo/scripting/v8_deadline_monitor.h" +#include "mongo/scripting/deadline_monitor.h" #include "mongo/scripting/v8-3.25_profiler.h" /** diff --git a/src/mongo/scripting/engine_v8.h b/src/mongo/scripting/engine_v8.h index 8825580c987..d70439c45ff 100644 --- a/src/mongo/scripting/engine_v8.h +++ b/src/mongo/scripting/engine_v8.h @@ -38,7 +38,7 @@ #include "mongo/client/dbclientcursor.h" #include "mongo/platform/unordered_map.h" #include "mongo/scripting/engine.h" -#include "mongo/scripting/v8_deadline_monitor.h" +#include "mongo/scripting/deadline_monitor.h" #include "mongo/scripting/v8_profiler.h" /** diff --git a/src/mongo/scripting/mozjs/PosixNSPR.cpp b/src/mongo/scripting/mozjs/PosixNSPR.cpp new file mode 100644 index 00000000000..2e6bbe93fe0 --- /dev/null +++ b/src/mongo/scripting/mozjs/PosixNSPR.cpp @@ -0,0 +1,259 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file was copied out of the firefox 38.0.1esr source tree from + * js/src/vm/PosixNSPR.cpp and modified to use the MongoDB threading + * primitives. + * + * The point of this file is to shim the posix emulation of nspr that Mozilla + * ships with firefox. We force configuration such that the SpiderMonkey build + * looks for these symbols and we provide them from within our object code + * rather than attempting to build it in there's so we can take advantage of + * the cross platform abstractions that we rely upon. + */ + +#include "mongo/platform/basic.h" + +#include +#include +#include + +#include "mongo/stdx/chrono.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/stdx/mutex.h" +#include "mongo/stdx/thread.h" +#include "mongo/util/concurrency/thread_name.h" +#include "mongo/util/concurrency/threadlocal.h" + +class nspr::Thread { + mongo::stdx::thread thread_; + void (*start)(void* arg); + void* arg; + bool joinable; + +public: + Thread(void (*start)(void* arg), void* arg, bool joinable) + : start(start), arg(arg), joinable(joinable) {} + + static void* ThreadRoutine(void* arg); + + mongo::stdx::thread& thread() { + return thread_; + } +}; + +namespace { +MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL nspr::Thread* kCurrentThread; +} // namespace + +void* nspr::Thread::ThreadRoutine(void* arg) { + Thread* self = static_cast(arg); + kCurrentThread = self; + self->start(self->arg); + if (!self->joinable) + js_delete(self); + return nullptr; +} + +PRThread* PR_CreateThread(PRThreadType type, + void (*start)(void* arg), + void* arg, + PRThreadPriority priority, + PRThreadScope scope, + PRThreadState state, + uint32_t stackSize) { + MOZ_ASSERT(type == PR_USER_THREAD); + MOZ_ASSERT(priority == PR_PRIORITY_NORMAL); + + try { + std::unique_ptr t( + js_new(start, arg, state != PR_UNJOINABLE_THREAD), + js_delete); + + t->thread() = mongo::stdx::thread(&nspr::Thread::ThreadRoutine, t.get()); + + if (state == PR_UNJOINABLE_THREAD) { + t->thread().detach(); + } + + return t.release(); + } catch (...) { + return nullptr; + } +} + +PRStatus PR_JoinThread(PRThread* thread) { + try { + thread->thread().join(); + + js_delete(thread); + + return PR_SUCCESS; + } catch (...) { + return PR_FAILURE; + } +} + +PRThread* PR_GetCurrentThread() { + return kCurrentThread; +} + +PRStatus PR_SetCurrentThreadName(const char* name) { + mongo::setThreadName(name); + + return PR_SUCCESS; +} + +static const size_t MaxTLSKeyCount = 32; +static size_t gTLSKeyCount; +namespace { +MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL std::array gTLSArray; + +} // namespace + +PRStatus PR_NewThreadPrivateIndex(unsigned* newIndex, PRThreadPrivateDTOR destructor) { + /* + * We only call PR_NewThreadPrivateIndex from the main thread, so there's no + * need to lock the table of TLS keys. + */ + MOZ_ASSERT(gTLSKeyCount + 1 < MaxTLSKeyCount); + + *newIndex = gTLSKeyCount; + gTLSKeyCount++; + + return PR_SUCCESS; +} + +PRStatus PR_SetThreadPrivate(unsigned index, void* priv) { + if (index >= gTLSKeyCount) + return PR_FAILURE; + + gTLSArray[index] = priv; + + return PR_SUCCESS; +} + +void* PR_GetThreadPrivate(unsigned index) { + if (index >= gTLSKeyCount) + return nullptr; + + return gTLSArray[index]; +} + +PRStatus PR_CallOnce(PRCallOnceType* once, PRCallOnceFN func) { + MOZ_CRASH("PR_CallOnce unimplemented"); +} + +PRStatus PR_CallOnceWithArg(PRCallOnceType* once, PRCallOnceWithArgFN func, void* arg) { + MOZ_CRASH("PR_CallOnceWithArg unimplemented"); +} + +class nspr::Lock { + mongo::stdx::mutex mutex_; + +public: + Lock() {} + mongo::stdx::mutex& mutex() { + return mutex_; + } +}; + +PRLock* PR_NewLock() { + return js_new(); +} + +void PR_DestroyLock(PRLock* lock) { + js_delete(lock); +} + +void PR_Lock(PRLock* lock) { + lock->mutex().lock(); +} + +PRStatus PR_Unlock(PRLock* lock) { + lock->mutex().unlock(); + + return PR_SUCCESS; +} + +class nspr::CondVar { + mongo::stdx::condition_variable cond_; + nspr::Lock* lock_; + +public: + CondVar(nspr::Lock* lock) : lock_(lock) {} + mongo::stdx::condition_variable& cond() { + return cond_; + } + nspr::Lock* lock() { + return lock_; + } +}; + +PRCondVar* PR_NewCondVar(PRLock* lock) { + return js_new(lock); +} + +void PR_DestroyCondVar(PRCondVar* cvar) { + js_delete(cvar); +} + +PRStatus PR_NotifyCondVar(PRCondVar* cvar) { + cvar->cond().notify_one(); + + return PR_SUCCESS; +} + +PRStatus PR_NotifyAllCondVar(PRCondVar* cvar) { + cvar->cond().notify_all(); + + return PR_SUCCESS; +} + +uint32_t PR_MillisecondsToInterval(uint32_t milli) { + return milli; +} + +uint32_t PR_MicrosecondsToInterval(uint32_t micro) { + return (micro + 999) / 1000; +} + +static const uint64_t TicksPerSecond = 1000; +static const uint64_t NanoSecondsInSeconds = 1000000000; +static const uint64_t MicroSecondsInSeconds = 1000000; + +uint32_t PR_TicksPerSecond() { + return TicksPerSecond; +} + +PRStatus PR_WaitCondVar(PRCondVar* cvar, uint32_t timeout) { + if (timeout == PR_INTERVAL_NO_TIMEOUT) { + try { + mongo::stdx::unique_lock lk(cvar->lock()->mutex(), + mongo::stdx::adopt_lock_t()); + + cvar->cond().wait(lk); + lk.release(); + + return PR_SUCCESS; + } catch (...) { + return PR_FAILURE; + } + } else { + try { + mongo::stdx::unique_lock lk(cvar->lock()->mutex(), + mongo::stdx::adopt_lock_t()); + + cvar->cond().wait_for(lk, mongo::stdx::chrono::microseconds(timeout)); + lk.release(); + + return PR_SUCCESS; + } catch (...) { + return PR_FAILURE; + } + } +} diff --git a/src/mongo/scripting/mozjs/base.cpp b/src/mongo/scripting/mozjs/base.cpp new file mode 100644 index 00000000000..b85229ae354 --- /dev/null +++ b/src/mongo/scripting/mozjs/base.cpp @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/base.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec* BaseInfo::freeFunctions = nullptr; +const JSFunctionSpec* BaseInfo::methods = nullptr; + +const char* const BaseInfo::inheritFrom = nullptr; + +void BaseInfo::addProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue v) {} +void BaseInfo::call(JSContext* cx, JS::CallArgs args) {} +void BaseInfo::construct(JSContext* cx, JS::CallArgs args) {} +void BaseInfo::convert(JSContext* cx, + JS::HandleObject obj, + JSType type, + JS::MutableHandleValue vp) {} +void BaseInfo::delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded) {} +void BaseInfo::enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties) {} +void BaseInfo::finalize(JSFreeOp* fop, JSObject* obj) {} +void BaseInfo::getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp) {} +void BaseInfo::hasInstance(JSContext* cx, + JS::HandleObject obj, + JS::MutableHandleValue vp, + bool* bp) {} +void BaseInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {} +void BaseInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {} +void BaseInfo::setProperty( + JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict, JS::MutableHandleValue vp) {} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/base.h b/src/mongo/scripting/mozjs/base.h new file mode 100644 index 00000000000..fe90ea65b97 --- /dev/null +++ b/src/mongo/scripting/mozjs/base.h @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include + +namespace mongo { +namespace mozjs { + +/** + * InstallType represents how we want this type overlayed in the JS world. + * + * Global is for regular types that get installed into the global scope + * Private gives us the type, but doesn't make the prototype publicly available + * OverNative is used to attach functionality to prototypes that are already there. + */ +enum class InstallType : char { + Global = 0, + Private, + OverNative, +}; + +/** + * The Base object for all info types + * + * It's difficult to access the array types correctly in a non constexpr world, + * so we just stash some nullptrs that are universally available. + */ +struct BaseInfo { + static const char* const inheritFrom; + static const InstallType installType = InstallType::Global; + static const JSFunctionSpec* freeFunctions; + static const JSFunctionSpec* methods; + static const unsigned classFlags = 0; + static void addProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue v); + static void call(JSContext* cx, JS::CallArgs args); + static void construct(JSContext* cx, JS::CallArgs args); + static void convert(JSContext* cx, + JS::HandleObject obj, + JSType type, + JS::MutableHandleValue vp); + static void delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded); + static void enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties); + static void finalize(JSFreeOp* fop, JSObject* obj); + static void getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp); + static void hasInstance(JSContext* cx, + JS::HandleObject obj, + JS::MutableHandleValue vp, + bool* bp); + static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto); + static void resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp); + static void setProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + bool strict, + JS::MutableHandleValue vp); +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/bindata.cpp b/src/mongo/scripting/mozjs/bindata.cpp new file mode 100644 index 00000000000..10a3045f103 --- /dev/null +++ b/src/mongo/scripting/mozjs/bindata.cpp @@ -0,0 +1,216 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/bindata.h" + +#include + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/util/base64.h" +#include "mongo/util/hex.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec BinDataInfo::methods[4] = { + MONGO_ATTACH_JS_FUNCTION(base64), + MONGO_ATTACH_JS_FUNCTION(hex), + MONGO_ATTACH_JS_FUNCTION(toString), + JS_FS_END, +}; + +const JSFunctionSpec BinDataInfo::freeFunctions[4] = { + MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(HexData, JSFUN_CONSTRUCTOR), + MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(MD5, JSFUN_CONSTRUCTOR), + MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(UUID, JSFUN_CONSTRUCTOR), + JS_FS_END, +}; + +const char* const BinDataInfo::className = "BinData"; + +namespace { + +void hexToBinData(JSContext* cx, int type, StringData hexstr, JS::MutableHandleValue out) { + auto scope = getScope(cx); + + // SERVER-9686: This function does not correctly check to make sure hexstr is actually made + // up of valid hex digits, and fails in the hex utility functions + + int len = hexstr.size() / 2; + std::unique_ptr data(new char[len]); + const char* src = hexstr.rawData(); + for (int i = 0; i < len; i++) { + data[i] = fromHex(src + i * 2); + } + + std::string encoded = base64::encode(data.get(), len); + JS::AutoValueArray<2> args(cx); + + args[0].setInt32(type); + ValueReader(cx, args[1]).fromStringData(encoded); + return scope->getBinDataProto().newInstance(args, out); +} + +std::string* getEncoded(JS::HandleValue thisv) { + return static_cast(JS_GetPrivate(thisv.toObjectOrNull())); +} + +std::string* getEncoded(JSObject* thisv) { + return static_cast(JS_GetPrivate(thisv)); +} + +} // namespace + +void BinDataInfo::finalize(JSFreeOp* fop, JSObject* obj) { + auto str = getEncoded(obj); + + if (str) { + delete str; + } +} + +void BinDataInfo::Functions::UUID(JSContext* cx, JS::CallArgs args) { + if (args.length() != 1) + uasserted(ErrorCodes::BadValue, "UUID needs 1 argument"); + + auto str = ValueWriter(cx, args.get(0)).toString(); + + if (str.length() != 32) + uasserted(ErrorCodes::BadValue, "UUID string must have 32 characters"); + + hexToBinData(cx, bdtUUID, str, args.rval()); +} + +void BinDataInfo::Functions::MD5(JSContext* cx, JS::CallArgs args) { + if (args.length() != 1) + uasserted(ErrorCodes::BadValue, "MD5 needs 1 argument"); + + auto str = ValueWriter(cx, args.get(0)).toString(); + + if (str.length() != 32) + uasserted(ErrorCodes::BadValue, "MD5 string must have 32 characters"); + + hexToBinData(cx, MD5Type, str, args.rval()); +} + +void BinDataInfo::Functions::HexData(JSContext* cx, JS::CallArgs args) { + if (args.length() != 2) + uasserted(ErrorCodes::BadValue, "HexData needs 2 arguments"); + + JS::RootedValue type(cx, args.get(0)); + + if (!type.isNumber() || type.toInt32() < 0 || type.toInt32() > 255) + uasserted(ErrorCodes::BadValue, + "HexData subtype must be a Number between 0 and 255 inclusive"); + + auto str = ValueWriter(cx, args.get(1)).toString(); + + hexToBinData(cx, type.toInt32(), str, args.rval()); +} + +void BinDataInfo::Functions::toString(JSContext* cx, JS::CallArgs args) { + ObjectWrapper o(cx, args.thisv()); + + auto str = getEncoded(args.thisv()); + + str::stream ss; + + ss << "BinData(" << o.getNumber("type") << ",\"" << *str << "\")"; + + ValueReader(cx, args.rval()).fromStringData(ss.operator std::string()); +} + +void BinDataInfo::Functions::base64(JSContext* cx, JS::CallArgs args) { + auto str = getEncoded(args.thisv()); + + ValueReader(cx, args.rval()).fromStringData(*str); +} + +void BinDataInfo::Functions::hex(JSContext* cx, JS::CallArgs args) { + auto str = getEncoded(args.thisv()); + + std::string data = base64::decode(*str); + std::stringstream ss; + ss.setf(std::ios_base::hex, std::ios_base::basefield); + ss.fill('0'); + ss.setf(std::ios_base::right, std::ios_base::adjustfield); + for (auto it = data.begin(); it != data.end(); ++it) { + unsigned v = (unsigned char)*it; + ss << std::setw(2) << v; + } + + ValueReader(cx, args.rval()).fromStringData(ss.str()); +} + +void BinDataInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (args.length() != 2) { + uasserted(ErrorCodes::BadValue, "BinData takes 2 arguments -- BinData(subtype,data)"); + } + + auto type = args.get(0); + + if (!type.isNumber() || type.toInt32() < 0 || type.toInt32() > 255) { + uasserted(ErrorCodes::BadValue, + "BinData subtype must be a Number between 0 and 255 inclusive"); + } + + auto utf = args.get(1); + + if (!utf.isString()) { + uasserted(ErrorCodes::BadValue, "BinData data must be a String"); + } + + auto str = ValueWriter(cx, utf).toString(); + + auto tmpBase64 = base64::decode(str); + + JS::RootedObject thisv(cx); + scope->getBinDataProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + JS::RootedValue len(cx); + len.setInt32(tmpBase64.length()); + + o.defineProperty("len", len, JSPROP_READONLY); + o.defineProperty("type", type, JSPROP_READONLY); + + JS_SetPrivate(thisv, new std::string(std::move(str))); + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/bindata.h b/src/mongo/scripting/mozjs/bindata.h new file mode 100644 index 00000000000..85b504230ff --- /dev/null +++ b/src/mongo/scripting/mozjs/bindata.h @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * Wrapper for the BinData bson type + * + * It offers some simple methods and a handful of specialized constructors + */ +struct BinDataInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void finalize(JSFreeOp* fop, JSObject* obj); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(base64); + MONGO_DEFINE_JS_FUNCTION(hex); + MONGO_DEFINE_JS_FUNCTION(toString); + + MONGO_DEFINE_JS_FUNCTION(HexData); + MONGO_DEFINE_JS_FUNCTION(MD5); + MONGO_DEFINE_JS_FUNCTION(UUID); + }; + + static const JSFunctionSpec methods[4]; + static const JSFunctionSpec freeFunctions[4]; + + static const char* const className; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/bson.cpp b/src/mongo/scripting/mozjs/bson.cpp new file mode 100644 index 00000000000..308e29d90f3 --- /dev/null +++ b/src/mongo/scripting/mozjs/bson.cpp @@ -0,0 +1,235 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/bson.h" + +#include + +#include "mongo/scripting/mozjs/idwrapper.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" + +namespace mongo { +namespace mozjs { + +const char* const BSONInfo::className = "BSON"; + +const JSFunctionSpec BSONInfo::freeFunctions[2] = { + MONGO_ATTACH_JS_FUNCTION(bsonWoCompare), JS_FS_END, +}; + +namespace { + +/** + * Holder for bson objects which tracks state for the js wrapper + * + * Basically, we have read only and read/write variants, and a need to manage + * the appearance of mutable state on the read/write versions. + */ +struct BSONHolder { + BSONHolder(const BSONObj& obj, bool ro) + : _obj(obj.getOwned()), _resolved(false), _readOnly(ro), _altered(false) {} + + BSONObj _obj; + bool _resolved; + bool _readOnly; + bool _altered; + std::set _removed; +}; + +BSONHolder* getHolder(JSObject* obj) { + return static_cast(JS_GetPrivate(obj)); +} + +} // namespace + +void BSONInfo::make(JSContext* cx, JS::MutableHandleObject obj, BSONObj bson, bool ro) { + auto scope = getScope(cx); + + scope->getBsonProto().newInstance(obj); + JS_SetPrivate(obj, new BSONHolder(bson, ro)); +} + +void BSONInfo::finalize(JSFreeOp* fop, JSObject* obj) { + auto holder = getHolder(obj); + + if (!holder) + return; + + delete holder; +} + +void BSONInfo::enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties) { + auto holder = getHolder(obj); + + if (!holder) + return; + + BSONObjIterator i(holder->_obj); + + ObjectWrapper o(cx, obj); + JS::RootedValue val(cx); + JS::RootedId id(cx); + + while (i.more()) { + BSONElement e = i.next(); + + // TODO: when we get heterogenous set lookup, switch to StringData + // rather than involving the temporary string + if (holder->_removed.count(e.fieldName())) + continue; + + ValueReader(cx, &val).fromStringData(e.fieldNameStringData()); + + if (!JS_ValueToId(cx, val, &id)) + uasserted(ErrorCodes::JSInterpreterFailure, "Failed to invoke JS_ValueToId"); + + properties.append(id); + } +} + +void BSONInfo::setProperty( + JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict, JS::MutableHandleValue vp) { + auto holder = getHolder(obj); + + if (holder) { + if (holder->_readOnly) { + uasserted(ErrorCodes::BadValue, "Read only object"); + } + + auto iter = holder->_removed.find(IdWrapper(cx, id).toString()); + + if (iter != holder->_removed.end()) { + holder->_removed.erase(iter); + } + + holder->_altered = true; + } + + ObjectWrapper(cx, obj).defineProperty(id, vp, JSPROP_ENUMERATE); +} + +void BSONInfo::delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded) { + auto holder = getHolder(obj); + + if (holder) { + if (holder->_readOnly) { + uasserted(ErrorCodes::BadValue, "Read only object"); + } + + holder->_altered = true; + + holder->_removed.insert(IdWrapper(cx, id).toString()); + } + + *succeeded = true; +} + +void BSONInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) { + auto holder = getHolder(obj); + + *resolvedp = false; + + if (!holder) { + return; + } + + IdWrapper idw(cx, id); + + if (!holder->_readOnly && holder->_removed.count(idw.toString())) { + return; + } + + ObjectWrapper o(cx, obj); + + std::string sname = IdWrapper(cx, id).toString(); + + if (holder->_obj.hasField(sname)) { + auto elem = holder->_obj[sname]; + + JS::RootedValue vp(cx); + + ValueReader(cx, &vp).fromBSONElement(elem, holder->_readOnly); + + o.defineProperty(id, vp, JSPROP_ENUMERATE); + + if (!holder->_readOnly && (elem.type() == mongo::Object || elem.type() == mongo::Array)) { + // if accessing a subobject, we have no way to know if + // modifications are being made on writable objects + + holder->_altered = true; + } + + *resolvedp = true; + } +} + +void BSONInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + scope->getBsonProto().newObject(args.rval()); +} + +std::tuple BSONInfo::originalBSON(JSContext* cx, JS::HandleObject obj) { + std::tuple out(nullptr, false); + + if (auto holder = getHolder(obj)) + out = std::make_tuple(&holder->_obj, holder->_altered); + + return out; +} + +void BSONInfo::Functions::bsonWoCompare(JSContext* cx, JS::CallArgs args) { + if (args.length() != 2) + uasserted(ErrorCodes::BadValue, "bsonWoCompare needs 2 argument"); + + if (!args.get(0).isObject()) + uasserted(ErrorCodes::BadValue, "first argument to bsonWoCompare must be an object"); + + if (!args.get(1).isObject()) + uasserted(ErrorCodes::BadValue, "second argument to bsonWoCompare must be an object"); + + BSONObj firstObject = ValueWriter(cx, args.get(0)).toBSON(); + BSONObj secondObject = ValueWriter(cx, args.get(1)).toBSON(); + + args.rval().setInt32(firstObject.woCompare(secondObject)); +} + +void BSONInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) { + JS::RootedValue value(cx); + value.setBoolean(true); + + ObjectWrapper(cx, proto).defineProperty("_bson", value, 0); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/bson.h b/src/mongo/scripting/mozjs/bson.h new file mode 100644 index 00000000000..bcf57b5eda4 --- /dev/null +++ b/src/mongo/scripting/mozjs/bson.h @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include + +#include "mongo/db/jsobj.h" +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * Provides a wrapper for BSONObj's in JS. The main idea here is that BSONObj's + * can be read only, or read write when we shim them in, and in all cases we + * lazily load their member elements into JS. So a bunch of these lifecycle + * methods are set up to wrap field access (enumerate, resolve and + * del/setProperty). + * + * Note that installType is private. So you can only get BSON types in JS via + * ::make() from C++. + */ +struct BSONInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded); + static void enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties); + static void finalize(JSFreeOp* fop, JSObject* obj); + static void resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp); + static void setProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + bool strict, + JS::MutableHandleValue vp); + + static const char* const className; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; + static const InstallType installType = InstallType::Private; + static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(bsonWoCompare); + }; + + static const JSFunctionSpec freeFunctions[2]; + + static std::tuple originalBSON(JSContext* cx, JS::HandleObject obj); + static void make(JSContext* cx, JS::MutableHandleObject obj, BSONObj bson, bool ro); +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/countdownlatch.cpp b/src/mongo/scripting/mozjs/countdownlatch.cpp new file mode 100644 index 00000000000..bf7ae4ff551 --- /dev/null +++ b/src/mongo/scripting/mozjs/countdownlatch.cpp @@ -0,0 +1,198 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/countdownlatch.h" + +#include + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/stdx/mutex.h" + +namespace mongo { +namespace mozjs { + +const char* const CountDownLatchInfo::className = "CountDownLatch"; + +const JSFunctionSpec CountDownLatchInfo::methods[5] = { + MONGO_ATTACH_JS_FUNCTION(_new), + MONGO_ATTACH_JS_FUNCTION(_await), + MONGO_ATTACH_JS_FUNCTION(_countDown), + MONGO_ATTACH_JS_FUNCTION(_getCount), + JS_FS_END, +}; + +/** + * The global CountDownLatch holder. + * + * Provides an interface for communicating between JSThread's + */ +class CountDownLatchHolder { +public: + CountDownLatchHolder() : _counter(0) {} + + int32_t make(int32_t count) { + uassert(ErrorCodes::JSInterpreterFailure, "argument must be >= 0", count >= 0); + stdx::lock_guard lock(_mutex); + + int32_t desc = ++_counter; + _latches.insert(std::make_pair(desc, std::make_shared(count))); + + return desc; + } + + void await(int32_t desc) { + std::shared_ptr latch = get(desc); + stdx::unique_lock lock(latch->mutex); + + while (latch->count != 0) { + latch->cv.wait(lock); + } + } + + void countDown(int32_t desc) { + std::shared_ptr latch = get(desc); + stdx::unique_lock lock(latch->mutex); + + if (latch->count > 0) + latch->count--; + + if (latch->count == 0) + latch->cv.notify_all(); + } + + int32_t getCount(int32_t desc) { + std::shared_ptr latch = get(desc); + stdx::unique_lock lock(latch->mutex); + + return latch->count; + } + +private: + /** + * Latches for communication between threads + */ + struct Latch { + Latch(int32_t count) : count(count) {} + + stdx::mutex mutex; + stdx::condition_variable cv; + int32_t count; + }; + + std::shared_ptr get(int32_t desc) { + stdx::lock_guard lock(_mutex); + + auto iter = _latches.find(desc); + uassert(ErrorCodes::JSInterpreterFailure, + "not a valid CountDownLatch descriptor", + iter != _latches.end()); + + return iter->second; + } + + using Map = std::unordered_map>; + + stdx::mutex _mutex; + Map _latches; + int32_t _counter; +}; + +namespace { +CountDownLatchHolder globalCountDownLatchHolder; +} // namespace + +void CountDownLatchInfo::Functions::_new(JSContext* cx, JS::CallArgs args) { + uassert(ErrorCodes::JSInterpreterFailure, "need exactly one argument", args.length() == 1); + uassert( + ErrorCodes::JSInterpreterFailure, "argument must be an integer", args.get(0).isNumber()); + + args.rval().setInt32(globalCountDownLatchHolder.make(args.get(0).toNumber())); +} + +void CountDownLatchInfo::Functions::_await(JSContext* cx, JS::CallArgs args) { + uassert(ErrorCodes::JSInterpreterFailure, "need exactly one argument", args.length() == 1); + uassert( + ErrorCodes::JSInterpreterFailure, "argument must be an integer", args.get(0).isNumber()); + + globalCountDownLatchHolder.await(args.get(0).toNumber()); + + args.rval().setUndefined(); +} + +void CountDownLatchInfo::Functions::_countDown(JSContext* cx, JS::CallArgs args) { + uassert(ErrorCodes::JSInterpreterFailure, "need exactly one argument", args.length() == 1); + uassert( + ErrorCodes::JSInterpreterFailure, "argument must be an integer", args.get(0).isNumber()); + + globalCountDownLatchHolder.countDown(args.get(0).toNumber()); + + args.rval().setUndefined(); +} + +void CountDownLatchInfo::Functions::_getCount(JSContext* cx, JS::CallArgs args) { + uassert(ErrorCodes::JSInterpreterFailure, "need exactly one argument", args.length() == 1); + uassert( + ErrorCodes::JSInterpreterFailure, "argument must be an integer", args.get(0).isNumber()); + + args.rval().setInt32(globalCountDownLatchHolder.getCount(args.get(0).toNumber())); +} + +/** + * We have to do this odd dance here because we need the methods from + * CountDownLatch to be installed in a plain object as enumerable properties. + * This is due to the way CountDownLatch is invoked, specifically after being + * transmitted across our js fork(). So we can't inherit and can't rely on the + * type. Practically, we also end up wrapping up all of these functions in pure + * js variants that call down, which makes them bson <-> js safe. + */ +void CountDownLatchInfo::postInstall(JSContext* cx, + JS::HandleObject global, + JS::HandleObject proto) { + auto objPtr = JS_NewPlainObject(cx); + uassert(ErrorCodes::JSInterpreterFailure, "Failed to JS_NewPlainObject", objPtr); + + JS::RootedObject obj(cx, objPtr); + ObjectWrapper objWrapper(cx, obj); + ObjectWrapper protoWrapper(cx, proto); + + JS::RootedValue val(cx); + for (auto iter = methods; iter->name; ++iter) { + protoWrapper.getValue(iter->name, &val); + objWrapper.setValue(iter->name, val); + } + + val.setObjectOrNull(obj); + ObjectWrapper(cx, global).setValue("CountDownLatch", val); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/countdownlatch.h b/src/mongo/scripting/mozjs/countdownlatch.h new file mode 100644 index 00000000000..07001ebf473 --- /dev/null +++ b/src/mongo/scripting/mozjs/countdownlatch.h @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "CountDownLatch" javascript object. + * + * Installs a global "CountDownLatch" object with associated methods. + * + * Note that there is only one instance of this class and it is used to + * communicate between different C++ threads. + */ +struct CountDownLatchInfo : public BaseInfo { + struct Functions { + MONGO_DEFINE_JS_FUNCTION(_new); + MONGO_DEFINE_JS_FUNCTION(_await); + MONGO_DEFINE_JS_FUNCTION(_countDown); + MONGO_DEFINE_JS_FUNCTION(_getCount); + }; + + static const JSFunctionSpec methods[5]; + + static const char* const className; + static const InstallType installType = InstallType::Private; + + static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto); +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/cursor.cpp b/src/mongo/scripting/mozjs/cursor.cpp new file mode 100644 index 00000000000..8f6bb7b540c --- /dev/null +++ b/src/mongo/scripting/mozjs/cursor.cpp @@ -0,0 +1,122 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/cursor.h" + +#include "mongo/scripting/mozjs/bson.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec CursorInfo::methods[5] = { + MONGO_ATTACH_JS_FUNCTION(hasNext), + MONGO_ATTACH_JS_FUNCTION(next), + MONGO_ATTACH_JS_FUNCTION(objsLeftInBatch), + MONGO_ATTACH_JS_FUNCTION(readOnly), + JS_FS_END, +}; + +const char* const CursorInfo::className = "Cursor"; + +namespace { + +DBClientCursor* getCursor(JSObject* thisv) { + return static_cast(JS_GetPrivate(thisv))->cursor.get(); +} + +DBClientCursor* getCursor(JS::CallArgs& args) { + return getCursor(args.thisv().toObjectOrNull()); +} + +} // namespace + +void CursorInfo::finalize(JSFreeOp* fop, JSObject* obj) { + auto cursor = static_cast(JS_GetPrivate(obj)); + + if (cursor) { + delete cursor; + } +} + +void CursorInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + scope->getCursorProto().newObject(args.rval()); +} + +void CursorInfo::Functions::next(JSContext* cx, JS::CallArgs args) { + auto cursor = getCursor(args); + + if (!cursor) { + args.rval().setUndefined(); + return; + } + + ObjectWrapper o(cx, args.thisv()); + + BSONObj bson = cursor->next(); + bool ro = o.hasField("_ro") ? o.getBoolean("_ro") : false; + + ValueReader(cx, args.rval()).fromBSON(bson, ro); +} + +void CursorInfo::Functions::hasNext(JSContext* cx, JS::CallArgs args) { + auto cursor = getCursor(args); + + if (!cursor) { + args.rval().setBoolean(false); + return; + } + + args.rval().setBoolean(cursor->more()); +} + +void CursorInfo::Functions::objsLeftInBatch(JSContext* cx, JS::CallArgs args) { + auto cursor = getCursor(args); + + if (!cursor) { + args.rval().setInt32(0); + return; + } + + args.rval().setInt32(cursor->objsLeftInBatch()); +} + +void CursorInfo::Functions::readOnly(JSContext* cx, JS::CallArgs args) { + ObjectWrapper(cx, args.thisv()).setBoolean("_ro", true); + + args.rval().set(args.thisv()); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/cursor.h b/src/mongo/scripting/mozjs/cursor.h new file mode 100644 index 00000000000..39c8ba56295 --- /dev/null +++ b/src/mongo/scripting/mozjs/cursor.h @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/client/dbclientcursor.h" +#include "mongo/client/dbclientinterface.h" +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * Wraps a DBClientCursor in javascript + * + * Note that the install is private, so this class should only be constructible + * from C++. Current callers are all via the Mongo object. + */ +struct CursorInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void finalize(JSFreeOp* fop, JSObject* obj); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(hasNext); + MONGO_DEFINE_JS_FUNCTION(next); + MONGO_DEFINE_JS_FUNCTION(objsLeftInBatch); + MONGO_DEFINE_JS_FUNCTION(readOnly); + }; + + static const JSFunctionSpec methods[5]; + + static const char* const className; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; + static const InstallType installType = InstallType::Private; + + /** + * We need this because the DBClientBase can go out of scope before all of + * its children (as in global shutdown). So we have to manage object + * lifetimes in C++ land. + */ + struct CursorHolder { + CursorHolder(std::unique_ptr cursor, std::shared_ptr client) + : client(std::move(client)), cursor(std::move(cursor)) {} + + std::shared_ptr client; + std::unique_ptr cursor; + }; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/db.cpp b/src/mongo/scripting/mozjs/db.cpp new file mode 100644 index 00000000000..da0197cc293 --- /dev/null +++ b/src/mongo/scripting/mozjs/db.cpp @@ -0,0 +1,139 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/db.h" + +#include "mongo/db/namespace_string.h" +#include "mongo/db/operation_context.h" +#include "mongo/scripting/mozjs/idwrapper.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/s/d_state.h" + +namespace mongo { +namespace mozjs { + +const char* const DBInfo::className = "DB"; + +void DBInfo::getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp) { + JS::RootedObject parent(cx); + if (!JS_GetPrototype(cx, obj, &parent)) + uasserted(ErrorCodes::JSInterpreterFailure, "Couldn't get prototype"); + + auto scope = getScope(cx); + + ObjectWrapper parentWrapper(cx, parent); + + std::string sname = IdWrapper(cx, id).toString(); + + // 2nd look into real values, may be cached collection object + if (!vp.isUndefined()) { + if (vp.isObject()) { + ObjectWrapper o(cx, vp); + + if (o.hasField("_fullName")) { + auto opContext = scope->getOpContext(); + + // need to check every time that the collection did not get sharded + if (opContext && + haveLocalShardingInfo(opContext->getClient(), o.getString("_fullName"))) + uasserted(ErrorCodes::BadValue, "can't use sharded collection from db.eval"); + } + } + + return; + } else if (parentWrapper.hasField(id)) { + parentWrapper.getValue(id, vp); + return; + } else if (sname.length() == 0 || sname[0] == '_') { + // if starts with '_' we dont return collection, one must use getCollection() + return; + } + + // no hit, create new collection + JS::RootedValue getCollection(cx); + parentWrapper.getValue("getCollection", &getCollection); + + if (!(getCollection.isObject() && JS_ObjectIsFunction(cx, getCollection.toObjectOrNull()))) { + uasserted(ErrorCodes::BadValue, "getCollection is not a function"); + } + + JS::AutoValueArray<1> args(cx); + + ValueReader(cx, args[0]).fromStringData(sname); + + JS::RootedValue coll(cx); + ObjectWrapper(cx, obj).callMethod(getCollection, args, &coll); + + uassert(16861, + "getCollection returned something other than a collection", + scope->getDbCollectionProto().instanceOf(coll)); + + // cache collection for reuse, don't enumerate + ObjectWrapper(cx, obj).defineProperty(sname.c_str(), coll, 0); + + vp.set(coll); +} + +void DBInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (args.length() != 2) + uasserted(ErrorCodes::BadValue, "db constructor requires 2 arguments"); + + for (unsigned i = 0; i < args.length(); ++i) { + uassert(ErrorCodes::BadValue, + "db initializer called with undefined argument", + !args.get(i).isUndefined()); + } + + JS::RootedObject thisv(cx); + scope->getDbProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + o.setValue("_mongo", args.get(0)); + o.setValue("_name", args.get(1)); + + std::string dbName = ValueWriter(cx, args.get(1)).toString(); + + if (!NamespaceString::validDBName(dbName)) + uasserted(ErrorCodes::BadValue, + str::stream() << "[" << dbName << "] is not a valid database name"); + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/db.h b/src/mongo/scripting/mozjs/db.h new file mode 100644 index 00000000000..c752953236f --- /dev/null +++ b/src/mongo/scripting/mozjs/db.h @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "DB" Javascript object. + * + * This maps to the 'db' global variable you can call db.COLLECTION_NAME.X() on + * in the shell. + * + * Its major magic is in its getProperty() callback, which threads through to + * a getCollection method installed in js + */ +struct DBInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp); + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/dbcollection.cpp b/src/mongo/scripting/mozjs/dbcollection.cpp new file mode 100644 index 00000000000..fd8bc086676 --- /dev/null +++ b/src/mongo/scripting/mozjs/dbcollection.cpp @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/dbcollection.h" + +#include "mongo/db/namespace_string.h" +#include "mongo/db/operation_context.h" +#include "mongo/scripting/mozjs/bson.h" +#include "mongo/scripting/mozjs/db.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/s/d_state.h" + +namespace mongo { +namespace mozjs { + +const char* const DBCollectionInfo::className = "DBCollection"; + +void DBCollectionInfo::getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp) { + DBInfo::getProperty(cx, obj, id, vp); +} + +void DBCollectionInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (args.length() != 4) + uasserted(ErrorCodes::BadValue, "collection constructor requires 4 arguments"); + + for (unsigned i = 0; i < args.length(); ++i) { + uassert(ErrorCodes::BadValue, + "collection constructor called with undefined argument", + !args.get(i).isUndefined()); + } + + JS::RootedObject thisv(cx); + scope->getDbCollectionProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + o.setValue("_mongo", args.get(0)); + o.setValue("_db", args.get(1)); + o.setValue("_shortName", args.get(2)); + o.setValue("_fullName", args.get(3)); + + std::string fullName = ValueWriter(cx, args.get(3)).toString(); + + auto context = scope->getOpContext(); + if (context && haveLocalShardingInfo(context->getClient(), fullName)) + uasserted(ErrorCodes::BadValue, "can't use sharded collection from db.eval"); + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/dbcollection.h b/src/mongo/scripting/mozjs/dbcollection.h new file mode 100644 index 00000000000..34be422dfd4 --- /dev/null +++ b/src/mongo/scripting/mozjs/dbcollection.h @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "DBCollection" Javascript object. + * + * This maps to the object you get after calling db.COLLECTION_NAME on the + * global 'db' object in the shell. + * + * Its major magic is in its getProperty() callback, which threads through to + * a getCollection method installed in js + */ +struct DBCollectionInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp); + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/dbpointer.cpp b/src/mongo/scripting/mozjs/dbpointer.cpp new file mode 100644 index 00000000000..fbfb8282649 --- /dev/null +++ b/src/mongo/scripting/mozjs/dbpointer.cpp @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/dbpointer.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace mozjs { + +const char* const DBPointerInfo::className = "DBPointer"; + +void DBPointerInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (args.length() != 2) + uasserted(ErrorCodes::BadValue, "DBPointer needs 2 arguments"); + + if (!args.get(0).isString()) + uasserted(ErrorCodes::BadValue, "DBPointer 1st parameter must be a string"); + + if (!scope->getOidProto().instanceOf(args.get(1))) + uasserted(ErrorCodes::BadValue, "DBPointer 2nd parameter must be an ObjectId"); + + JS::RootedObject thisv(cx); + scope->getDbPointerProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + o.setValue("ns", args.get(0)); + o.setValue("id", args.get(1)); + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/dbpointer.h b/src/mongo/scripting/mozjs/dbpointer.h new file mode 100644 index 00000000000..d521eda4681 --- /dev/null +++ b/src/mongo/scripting/mozjs/dbpointer.h @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "DBPointer" Javascript Object + * + * These look like: + * { + * id : OID(), + * ns : String(), + * } + */ +struct DBPointerInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/dbquery.cpp b/src/mongo/scripting/mozjs/dbquery.cpp new file mode 100644 index 00000000000..e9bddf4b30e --- /dev/null +++ b/src/mongo/scripting/mozjs/dbquery.cpp @@ -0,0 +1,143 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/dbquery.h" + +#include "mongo/scripting/mozjs/idwrapper.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" + +namespace mongo { +namespace mozjs { + +const char* const DBQueryInfo::className = "DBQuery"; + +void DBQueryInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (args.length() < 4) + uasserted(ErrorCodes::BadValue, "dbQuery constructor requires at least 4 arguments"); + + JS::RootedObject thisv(cx); + scope->getDbQueryProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + o.setValue("_mongo", args.get(0)); + o.setValue("_db", args.get(1)); + o.setValue("_collection", args.get(2)); + o.setValue("_ns", args.get(3)); + + JS::RootedObject emptyObj(cx); + JS::RootedValue emptyObjVal(cx); + emptyObjVal.setObjectOrNull(emptyObj); + + JS::RootedValue nullVal(cx); + nullVal.setNull(); + + if (args.length() > 4 && args.get(4).isObject()) { + o.setValue("_query", args.get(4)); + } else { + o.setValue("_query", emptyObjVal); + } + + if (args.length() > 5 && args.get(5).isObject()) { + o.setValue("_fields", args.get(5)); + } else { + o.setValue("_fields", nullVal); + } + + if (args.length() > 6 && args.get(6).isNumber()) { + o.setValue("_limit", args.get(6)); + } else { + o.setNumber("_limit", 0); + } + + if (args.length() > 7 && args.get(7).isNumber()) { + o.setValue("_skip", args.get(7)); + } else { + o.setNumber("_skip", 0); + } + + if (args.length() > 8 && args.get(8).isNumber()) { + o.setValue("_batchSize", args.get(8)); + } else { + o.setNumber("_batchSize", 0); + } + + if (args.length() > 9 && args.get(9).isNumber()) { + o.setValue("_options", args.get(9)); + } else { + o.setNumber("_options", 0); + } + + o.setValue("_cursor", nullVal); + o.setNumber("_numReturned", 0); + o.setBoolean("_special", false); + + args.rval().setObjectOrNull(thisv); +} + +void DBQueryInfo::getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp) { + if (!vp.isUndefined()) { + return; + } + + IdWrapper wid(cx, id); + + // We only use this for index access + if (!wid.isInt()) { + return; + } + + JS::RootedObject parent(cx); + if (!JS_GetPrototype(cx, obj, &parent)) + uasserted(ErrorCodes::InternalError, "Couldn't get prototype"); + + ObjectWrapper parentWrapper(cx, parent); + + JS::RootedValue arrayAccess(cx); + parentWrapper.getValue("arrayAccess", &arrayAccess); + + if (arrayAccess.isObject() && JS_ObjectIsFunction(cx, arrayAccess.toObjectOrNull())) { + JS::AutoValueArray<1> args(cx); + + args[0].setInt32(wid.toInt32()); + + ObjectWrapper(cx, obj).callMethod(arrayAccess, args, vp); + } else { + uasserted(ErrorCodes::BadValue, "arrayAccess is not a function"); + } +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/dbquery.h b/src/mongo/scripting/mozjs/dbquery.h new file mode 100644 index 00000000000..dc844c3084c --- /dev/null +++ b/src/mongo/scripting/mozjs/dbquery.h @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "DBQuery" Javascript object. + * + * This represents the result of a find() and uses its getProperty() callback + * to shim operator[]. I.e. db.test.find()[4] + */ +struct DBQueryInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp); + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/dbref.cpp b/src/mongo/scripting/mozjs/dbref.cpp new file mode 100644 index 00000000000..16ad0058084 --- /dev/null +++ b/src/mongo/scripting/mozjs/dbref.cpp @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/dbref.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" + +namespace mongo { +namespace mozjs { + +const char* const DBRefInfo::className = "DBRef"; + +void DBRefInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (!(args.length() == 2 || args.length() == 3)) + uasserted(ErrorCodes::BadValue, "DBRef needs 2 or 3 arguments"); + + if (!args.get(0).isString()) + uasserted(ErrorCodes::BadValue, "DBRef 1st parameter must be a string"); + + JS::RootedObject thisv(cx); + scope->getDbRefProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + o.setValue("$ref", args.get(0)); + o.setValue("$id", args.get(1)); + + if (args.length() == 3) { + if (!args.get(2).isString()) + uasserted(ErrorCodes::BadValue, "DBRef 3rd parameter must be a string"); + + o.setValue("$db", args.get(2)); + } + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/dbref.h b/src/mongo/scripting/mozjs/dbref.h new file mode 100644 index 00000000000..e556a61f765 --- /dev/null +++ b/src/mongo/scripting/mozjs/dbref.h @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "DBRef" Javascript object. + * + * These look like: + * { + * $ref : String(), + * $id : Any, + * $db : String(), + * } + */ +struct DBRefInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/engine.cpp b/src/mongo/scripting/mozjs/engine.cpp new file mode 100644 index 00000000000..15875da16e1 --- /dev/null +++ b/src/mongo/scripting/mozjs/engine.cpp @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/engine.h" + +#include "mongo/db/operation_context.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/proxyscope.h" +#include "mongo/util/log.h" + +namespace mongo { + +void ScriptEngine::setup() { + if (!globalScriptEngine) { + globalScriptEngine = new mozjs::MozJSScriptEngine(); + + if (hasGlobalServiceContext()) { + getGlobalServiceContext()->registerKillOpListener(globalScriptEngine); + } + } +} + +std::string ScriptEngine::getInterpreterVersionString() { + return "MozJS-38"; +} + +namespace mozjs { + +MozJSScriptEngine::MozJSScriptEngine() { + uassert(ErrorCodes::JSInterpreterFailure, "Failed to JS_Init()", JS_Init()); +} + +MozJSScriptEngine::~MozJSScriptEngine() { + JS_ShutDown(); +} + +mongo::Scope* MozJSScriptEngine::createScope() { + return new MozJSProxyScope(this); +} + +void MozJSScriptEngine::interrupt(unsigned opId) { + stdx::lock_guard intLock(_globalInterruptLock); + OpIdToScopeMap::iterator iScope = _opToScopeMap.find(opId); + if (iScope == _opToScopeMap.end()) { + // got interrupt request for a scope that no longer exists + LOG(1) << "received interrupt request for unknown op: " << opId << printKnownOps_inlock(); + return; + } + + LOG(1) << "interrupting op: " << opId << printKnownOps_inlock(); + iScope->second->kill(); +} + +std::string MozJSScriptEngine::printKnownOps_inlock() { + str::stream out; + + if (shouldLog(logger::LogSeverity::Debug(2))) { + out << " known ops: \n"; + + for (auto&& iSc : _opToScopeMap) { + out << " " << iSc.first << "\n"; + } + } + + return out; +} + +void MozJSScriptEngine::interruptAll() { + stdx::lock_guard interruptLock(_globalInterruptLock); + + for (auto&& iScope : _opToScopeMap) { + iScope.second->kill(); + } +} + +void MozJSScriptEngine::registerOperation(OperationContext* txn, MozJSImplScope* scope) { + stdx::lock_guard giLock(_globalInterruptLock); + + auto opId = txn->getOpID(); + + _opToScopeMap[opId] = scope; + + LOG(2) << "SMScope " << static_cast(scope) << " registered for op " << opId; + Status status = txn->checkForInterruptNoAssert(); + if (!status.isOK()) { + scope->kill(); + } +} + +void MozJSScriptEngine::unregisterOperation(unsigned int opId) { + stdx::lock_guard giLock(_globalInterruptLock); + + LOG(2) << "ImplScope " << static_cast(this) << " unregistered for op " << opId; + + if (opId != 0) { + // scope is currently associated with an operation id + auto it = _opToScopeMap.find(opId); + if (it != _opToScopeMap.end()) + _opToScopeMap.erase(it); + } +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/engine.h b/src/mongo/scripting/mozjs/engine.h new file mode 100644 index 00000000000..a337ef0bcdc --- /dev/null +++ b/src/mongo/scripting/mozjs/engine.h @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include +#include + +#include "mongo/scripting/deadline_monitor.h" +#include "mongo/scripting/engine.h" +#include "mongo/stdx/mutex.h" +#include "mongo/util/concurrency/mutex.h" + +namespace mongo { +namespace mozjs { + +class MozJSImplScope; + +/** + * Implements the global ScriptEngine interface for MozJS. The associated TU + * pulls this in for the polymorphic globalScriptEngine. + */ +class MozJSScriptEngine final : public mongo::ScriptEngine { +public: + MozJSScriptEngine(); + ~MozJSScriptEngine() override; + + mongo::Scope* createScope() override; + + void runTest() override {} + + bool utf8Ok() const override { + return true; + } + + void interrupt(unsigned opId) override; + + void interruptAll() override; + + void registerOperation(OperationContext* ctx, MozJSImplScope* scope); + void unregisterOperation(unsigned int opId); + + using ScopeCallback = void (*)(Scope&); + ScopeCallback getScopeInitCallback() { + return _scopeInitCallback; + }; + + DeadlineMonitor& getDeadlineMonitor() { + return _deadlineMonitor; + } + +private: + std::string printKnownOps_inlock(); + + /** + * This mutex protects _opToScopeMap + */ + stdx::mutex _globalInterruptLock; + + using OpIdToScopeMap = std::unordered_map; + OpIdToScopeMap _opToScopeMap; // map of mongo op ids to scopes (protected by + // _globalInterruptLock). + + DeadlineMonitor _deadlineMonitor; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/exception.cpp b/src/mongo/scripting/mozjs/exception.cpp new file mode 100644 index 00000000000..7f7349acee1 --- /dev/null +++ b/src/mongo/scripting/mozjs/exception.cpp @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/exception.h" + +#include + +#include "mongo/scripting/mozjs/jsstringwrapper.h" +#include "mongo/util/assert_util.h" + +namespace mongo { +namespace mozjs { + +namespace { + +JSErrorFormatString kFormatString = {"{0}", 1, JSEXN_ERR}; +const JSErrorFormatString* errorCallback(void* data, const unsigned code) { + return &kFormatString; +} + +} // namespace + +void mongoToJSException(JSContext* cx) { + auto status = exceptionToStatus(); + + JS_ReportErrorNumber(cx, errorCallback, nullptr, status.code(), status.reason().c_str()); +} + +void setJSException(JSContext* cx, ErrorCodes::Error code, StringData sd) { + JS_ReportErrorNumber(cx, errorCallback, nullptr, code, sd.rawData()); +} + +Status currentJSExceptionToStatus(JSContext* cx, ErrorCodes::Error altCode, StringData altReason) { + JS::RootedValue vp(cx); + if (!JS_GetPendingException(cx, &vp)) + return Status(altCode, altReason.rawData()); + + JS::RootedObject obj(cx, vp.toObjectOrNull()); + JSErrorReport* report = JS_ErrorFromException(cx, obj); + if (!report) + return Status(altCode, altReason.rawData()); + + JSStringWrapper jsstr(cx, js::ErrorReportToString(cx, report)); + if (!jsstr) + return Status(altCode, altReason.rawData()); + + /** + * errorNumber is only set by library consumers of MozJS, and then only via + * JS_ReportErrorNumber, so all of the codes we see here are ours. + */ + return Status(report->errorNumber ? static_cast(report->errorNumber) + : altCode, + jsstr.toStringData().rawData()); +} + +void throwCurrentJSException(JSContext* cx, ErrorCodes::Error altCode, StringData altReason) { + auto status = currentJSExceptionToStatus(cx, altCode, altReason); + uasserted(status.code(), status.reason()); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/exception.h b/src/mongo/scripting/mozjs/exception.h new file mode 100644 index 00000000000..9ef48664db6 --- /dev/null +++ b/src/mongo/scripting/mozjs/exception.h @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include + +#include "mongo/base/error_codes.h" +#include "mongo/base/string_data.h" +#include "mongo/util/assert_util.h" + +namespace mongo { +namespace mozjs { + +/** + * Turns a current C++ exception into a JS exception + */ +void mongoToJSException(JSContext* cx); + +/** + * Sets an exception for javascript + */ +void setJSException(JSContext* cx, ErrorCodes::Error code, StringData sd); + +/** + * Converts the current pending js expection into a status + * + * The altCode and altReason are used if no JS exception is pending + */ +Status currentJSExceptionToStatus(JSContext* cx, ErrorCodes::Error altCode, StringData altReason); + +/** + * Turns the current JS exception into a C++ exception + * + * The altCode and altReason are used if no JS exception is pending + */ +MONGO_COMPILER_NORETURN void throwCurrentJSException(JSContext* cx, + ErrorCodes::Error altCode, + StringData altReason); + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/global.cpp b/src/mongo/scripting/mozjs/global.cpp new file mode 100644 index 00000000000..d8c6da94e35 --- /dev/null +++ b/src/mongo/scripting/mozjs/global.cpp @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/global.h" + +#include + +#include "mongo/base/init.h" +#include "mongo/logger/logstream_builder.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/jsstringwrapper.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec GlobalInfo::freeFunctions[4] = { + MONGO_ATTACH_JS_FUNCTION(gc), + MONGO_ATTACH_JS_FUNCTION(print), + MONGO_ATTACH_JS_FUNCTION(version), + JS_FS_END, +}; + +const char* const GlobalInfo::className = "Global"; + +namespace { + +logger::MessageLogDomain* jsPrintLogDomain; + +} // namespace + +void GlobalInfo::Functions::print(JSContext* cx, JS::CallArgs args) { + logger::LogstreamBuilder builder(jsPrintLogDomain, getThreadName(), logger::LogSeverity::Log()); + std::ostream& ss = builder.stream(); + + bool first = true; + for (size_t i = 0; i < args.length(); i++) { + if (first) + first = false; + else + ss << " "; + + if (args.get(i).isNullOrUndefined()) { + // failed to get object to convert + ss << "[unknown type]"; + continue; + } + + JSStringWrapper jsstr(cx, JS::ToString(cx, args.get(i))); + ss << jsstr.toStringData(); + } + ss << std::endl; + + args.rval().setUndefined(); +} + +void GlobalInfo::Functions::version(JSContext* cx, JS::CallArgs args) { + ValueReader(cx, args.rval()).fromStringData(JS_VersionToString(JS_GetVersion(cx))); +} + +void GlobalInfo::Functions::gc(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + scope->gc(); + + args.rval().setUndefined(); +} + +MONGO_INITIALIZER(JavascriptPrintDomain)(InitializerContext*) { + jsPrintLogDomain = logger::globalLogManager()->getNamedDomain("javascriptOutput"); + return Status::OK(); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/global.h b/src/mongo/scripting/mozjs/global.h new file mode 100644 index 00000000000..1da83350137 --- /dev/null +++ b/src/mongo/scripting/mozjs/global.h @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The global object for all of our JS. + * + * This function is super special and it's properties are the globally visible + * symbol for JS execution. + */ +struct GlobalInfo : public BaseInfo { + struct Functions { + MONGO_DEFINE_JS_FUNCTION(gc); + MONGO_DEFINE_JS_FUNCTION(print); + MONGO_DEFINE_JS_FUNCTION(version); + }; + + static const JSFunctionSpec freeFunctions[4]; + + static const char* const className; + static const unsigned classFlags = JSCLASS_GLOBAL_FLAGS; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/idwrapper.cpp b/src/mongo/scripting/mozjs/idwrapper.cpp new file mode 100644 index 00000000000..2c701037a22 --- /dev/null +++ b/src/mongo/scripting/mozjs/idwrapper.cpp @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/idwrapper.h" + +#include "mongo/base/error_codes.h" +#include "mongo/scripting/mozjs/exception.h" +#include "mongo/scripting/mozjs/jsstringwrapper.h" +#include "mongo/util/assert_util.h" + +namespace mongo { +namespace mozjs { + +IdWrapper::IdWrapper(JSContext* cx, JS::HandleId value) : _context(cx), _value(cx, value) {} + +std::string IdWrapper::toString() const { + if (JSID_IS_STRING(_value)) { + return JSStringWrapper(_context, JSID_TO_STRING(_value)).toString(); + } else if (JSID_IS_INT(_value)) { + return std::to_string(JSID_TO_INT(_value)); + } else { + throwCurrentJSException(_context, + ErrorCodes::TypeMismatch, + "Cannot toString() non-string and non-integer jsid"); + } +} + +uint32_t IdWrapper::toInt32() const { + uassert(ErrorCodes::TypeMismatch, "Cannot toInt32() non-integer jsid", JSID_IS_INT(_value)); + + return JSID_TO_INT(_value); +} + +bool IdWrapper::equals(StringData sd) const { + return sd.compare(toString()) == 0; +} + +bool IdWrapper::isInt() const { + return JSID_IS_INT(_value); +} + +bool IdWrapper::isString() const { + return JSID_IS_STRING(_value); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/idwrapper.h b/src/mongo/scripting/mozjs/idwrapper.h new file mode 100644 index 00000000000..4865e5e0aec --- /dev/null +++ b/src/mongo/scripting/mozjs/idwrapper.h @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include +#include + +#include "mongo/base/string_data.h" + +namespace mongo { +namespace mozjs { + +/** + * Wraps jsid's to make them slightly easier to use + * + * As these own a JS::RootedId they're not movable or copyable + * + * IdWrapper should only be used on the stack, never in a heap allocation + */ +class IdWrapper { +public: + IdWrapper(JSContext* cx, JS::HandleId id); + + /** + * Converts to a string. This coerces for integers + */ + std::string toString() const; + + /** + * Converts to an int. This throws if the id is not an integer + */ + uint32_t toInt32() const; + + bool isString() const; + bool isInt() const; + + bool equals(StringData sd) const; + +private: + JSContext* _context; + JS::RootedId _value; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp new file mode 100644 index 00000000000..0d5d3675603 --- /dev/null +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -0,0 +1,728 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/implscope.h" + +#include +#include + +#include "mongo/base/error_codes.h" +#include "mongo/db/operation_context.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/stdx/mutex.h" +#include "mongo/util/concurrency/threadlocal.h" +#include "mongo/util/log.h" + +using namespace mongoutils; + +namespace mongo { + +// Generated symbols for JS files +namespace JSFiles { +extern const JSFile types; +extern const JSFile assert; +} // namespace + +namespace mozjs { + +const char* const MozJSImplScope::kExecResult = "__lastres__"; +const char* const MozJSImplScope::kInvokeResult = "__returnValue"; + +namespace { + +/** + * The maximum amount of memory to be given out per thread to mozilla. We + * manage this by trapping all calls to malloc, free, etc. and keeping track of + * counts in some thread locals + */ +const size_t kMallocMemoryLimit = 1024ul * 1024 * 1024 * 1.1; + +/** + * The number of bytes to allocate after which garbage collection is run + */ +const int kMaxBytesBeforeGC = 8 * 1024 * 1024; + +/** + * The size, in bytes, of each "stack chunk". 8192 is the recommended amount + * from mozilla + */ +const int kStackChunkSize = 8192; + +/** + * Runtime's can race on first creation (on some function statics), so we just + * serialize the initial Runtime creation. + */ +stdx::mutex gRuntimeCreationMutex; +bool gFirstRuntimeCreated = false; + +} // namespace + +MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL MozJSImplScope* kCurrentScope; + +struct MozJSImplScope::MozJSEntry { + MozJSEntry(MozJSImplScope* scope) : ar(scope->_context), ac(scope->_context, scope->_global) {} + + JSAutoRequest ar; + JSAutoCompartment ac; +}; + +void MozJSImplScope::_reportError(JSContext* cx, const char* message, JSErrorReport* report) { + auto scope = getScope(cx); + + if (!JSREPORT_IS_WARNING(report->flags)) { + scope->_status = + Status(report->errorNumber ? static_cast(report->errorNumber) + : ErrorCodes::JSInterpreterFailure, + str::stream() << message << ":\n" + << JS::FormatStackDump(cx, nullptr, true, true, false) << "\n"); + } +} + +std::string MozJSImplScope::getError() { + return ""; +} + +void MozJSImplScope::registerOperation(OperationContext* txn) { + invariant(_opId == 0); + _opId = txn->getOpID(); + + _engine->registerOperation(txn, this); +} + +void MozJSImplScope::unregisterOperation() { + if (_opId != 0) { + _engine->unregisterOperation(_opId); + + _opId = 0; + } +} + +void MozJSImplScope::kill() { + _pendingKill.store(true); + JS_RequestInterruptCallback(_runtime); +} + +bool MozJSImplScope::isKillPending() const { + return _pendingKill.load(); +} + +OperationContext* MozJSImplScope::getOpContext() const { + return _opCtx; +} + +bool MozJSImplScope::_interruptCallback(JSContext* cx) { + auto scope = getScope(cx); + + if (scope->_pendingGC.load()) { + JS_GC(scope->_runtime); + } + + bool kill = scope->isKillPending(); + + if (kill) { + scope->_engine->getDeadlineMonitor().stopDeadline(scope); + scope->unregisterOperation(); + } + + return !kill; +} + +void MozJSImplScope::_gcCallback(JSRuntime* rt, JSGCStatus status, void* data) { + if (!shouldLog(logger::LogSeverity::Debug(1))) { + // don't collect stats unless verbose + return; + } + + log() << "MozJS GC " << (status == JSGC_BEGIN ? "prologue" : "epilogue") << " heap stats - " + << " total: " << mongo::sm::get_total_bytes() << " limit: " << mongo::sm::get_max_bytes() + << std::endl; +} + +MozJSImplScope::MozRuntime::MozRuntime() { + mongo::sm::reset(kMallocMemoryLimit); + + { + stdx::unique_lock lk(gRuntimeCreationMutex); + + if (gFirstRuntimeCreated) { + // If we've already made a runtime, just proceed + lk.unlock(); + } else { + // If this is the first one, hold the lock until after the first + // one's done + gFirstRuntimeCreated = true; + } + + _runtime = JS_NewRuntime(kMaxBytesBeforeGC); + } + + uassert(ErrorCodes::JSInterpreterFailure, "Failed to initialize JSRuntime", _runtime); + + _context = JS_NewContext(_runtime, kStackChunkSize); + uassert(ErrorCodes::JSInterpreterFailure, "Failed to initialize JSContext", _context); +} + +MozJSImplScope::MozRuntime::~MozRuntime() { + JS_DestroyContext(_context); + JS_DestroyRuntime(_runtime); +} + +MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine) + : _engine(engine), + _mr(), + _runtime(_mr._runtime), + _context(_mr._context), + _globalProto(_context), + _global(_globalProto.getProto()), + _funcs(), + _pendingKill(false), + _opId(0), + _opCtx(nullptr), + _pendingGC(false), + _connectState(ConnectState::Not), + _status(Status::OK()), + _binDataProto(_context), + _bsonProto(_context), + _countDownLatchProto(_context), + _cursorProto(_context), + _dbCollectionProto(_context), + _dbPointerProto(_context), + _dbQueryProto(_context), + _dbProto(_context), + _dbRefProto(_context), + _jsThreadProto(_context), + _maxKeyProto(_context), + _minKeyProto(_context), + _mongoExternalProto(_context), + _mongoLocalProto(_context), + _nativeFunctionProto(_context), + _numberIntProto(_context), + _numberLongProto(_context), + _objectProto(_context), + _oidProto(_context), + _regExpProto(_context), + _timestampProto(_context) { + kCurrentScope = this; + + // The default is quite low and doesn't seem to directly correlate with + // malloc'd bytes. Set it to MAX_INT here and catching things in the + // jscustomallocator.cpp + JS_SetGCParameter(_runtime, JSGC_MAX_BYTES, 0xffffffff); + + JS_SetInterruptCallback(_runtime, _interruptCallback); + JS_SetGCCallback(_runtime, _gcCallback, this); + JS_SetContextPrivate(_context, this); + JSAutoRequest ar(_context); + + JS_SetErrorReporter(_runtime, _reportError); + + JSAutoCompartment ac(_context, _global); + + _checkErrorState(JS_InitStandardClasses(_context, _global)); + + installBSONTypes(); + execSetup(JSFiles::assert); + execSetup(JSFiles::types); + + // install process-specific utilities in the global scope (dependancy: types.js, assert.js) + if (_engine->getScopeInitCallback()) + _engine->getScopeInitCallback()(*this); + + // install global utility functions + installGlobalUtils(*this); +} + +MozJSImplScope::~MozJSImplScope() { + for (auto&& x : _funcs) { + x.reset(); + } + + unregisterOperation(); +} + +bool MozJSImplScope::hasOutOfMemoryException() { + return false; +} + +void MozJSImplScope::init(const BSONObj* data) { + if (!data) + return; + + BSONObjIterator i(*data); + while (i.more()) { + BSONElement e = i.next(); + setElement(e.fieldName(), e); + } +} + +void MozJSImplScope::setNumber(const char* field, double val) { + MozJSEntry entry(this); + + ObjectWrapper(_context, _global).setNumber(field, val); +} + +void MozJSImplScope::setString(const char* field, StringData val) { + MozJSEntry entry(this); + + ObjectWrapper(_context, _global).setString(field, val); +} + +void MozJSImplScope::setBoolean(const char* field, bool val) { + MozJSEntry entry(this); + + ObjectWrapper(_context, _global).setBoolean(field, val); +} + +void MozJSImplScope::setElement(const char* field, const BSONElement& e) { + MozJSEntry entry(this); + + ObjectWrapper(_context, _global).setBSONElement(field, e, false); +} + +void MozJSImplScope::setObject(const char* field, const BSONObj& obj, bool readOnly) { + MozJSEntry entry(this); + + ObjectWrapper(_context, _global).setBSON(field, obj, readOnly); +} + +int MozJSImplScope::type(const char* field) { + MozJSEntry entry(this); + + return ObjectWrapper(_context, _global).type(field); +} + +double MozJSImplScope::getNumber(const char* field) { + MozJSEntry entry(this); + + return ObjectWrapper(_context, _global).getNumber(field); +} + +int MozJSImplScope::getNumberInt(const char* field) { + MozJSEntry entry(this); + + return ObjectWrapper(_context, _global).getNumberInt(field); +} + +long long MozJSImplScope::getNumberLongLong(const char* field) { + MozJSEntry entry(this); + + return ObjectWrapper(_context, _global).getNumberLongLong(field); +} + +std::string MozJSImplScope::getString(const char* field) { + MozJSEntry entry(this); + + return ObjectWrapper(_context, _global).getString(field); +} + +bool MozJSImplScope::getBoolean(const char* field) { + MozJSEntry entry(this); + + return ObjectWrapper(_context, _global).getBoolean(field); +} + +BSONObj MozJSImplScope::getObject(const char* field) { + MozJSEntry entry(this); + + return ObjectWrapper(_context, _global).getObject(field); +} + +void MozJSImplScope::newFunction(StringData raw, JS::MutableHandleValue out) { + MozJSEntry entry(this); + + std::string code = str::stream() << "____MongoToSM_newFunction_temp = " << raw; + + JS::CompileOptions co(_context); + setCompileOptions(&co); + _checkErrorState(JS::Evaluate(_context, _global, co, code.c_str(), code.length(), out)); +} + +BSONObj MozJSImplScope::callThreadArgs(const BSONObj& args) { + MozJSEntry entry(this); + + JS::RootedValue function(_context); + ValueReader(_context, &function).fromBSONElement(args.firstElement(), true); + + int argc = args.nFields() - 1; + + JS::AutoValueVector argv(_context); + BSONObjIterator it(args); + it.next(); + JS::RootedValue value(_context); + + for (int i = 0; i < argc; ++i) { + ValueReader(_context, &value).fromBSONElement(*it, true); + argv.append(value); + it.next(); + } + + JS::RootedValue out(_context); + JS::RootedObject thisv(_context); + + bool success = JS::Call(_context, thisv, function, argv, &out); + + if (!success) { + auto status = currentJSExceptionToStatus( + _context, ErrorCodes::JSInterpreterFailure, "Unknown callThread failure"); + + log() << "js thread raised js exception: " << status; + + uasserted(status.code(), status.reason()); + } + + BSONObjBuilder b; + ValueWriter(_context, out).writeThis(&b, "ret"); + + return b.obj(); +} + +bool hasFunctionIdentifier(StringData code) { + if (code.size() < 9 || code.find("function") != 0) + return false; + + return code[8] == ' ' || code[8] == '('; +} + +// TODO: This function identification code is broken. Fix it up to be more robust +// +// See: SERVER-16703 for more info +void MozJSImplScope::_MozJSCreateFunction(const char* raw, + ScriptingFunction functionNumber, + JS::MutableHandleValue fun) { + std::string code = jsSkipWhiteSpace(raw); + if (!hasFunctionIdentifier(code)) { + if (code.find('\n') == std::string::npos && !hasJSReturn(code) && + (code.find(';') == std::string::npos || code.find(';') == code.size() - 1)) { + code = "return " + code; + } + code = "function(){ " + code + "}"; + } + + code = str::stream() << "_funcs" << functionNumber << " = " << code; + + JS::CompileOptions co(_context); + setCompileOptions(&co); + + _checkErrorState(JS::Evaluate(_context, _global, co, code.c_str(), code.length(), fun)); + uassert(10232, + "not a function", + fun.isObject() && JS_ObjectIsFunction(_context, fun.toObjectOrNull())); +} + +ScriptingFunction MozJSImplScope::_createFunction(const char* raw, + ScriptingFunction functionNumber) { + MozJSEntry entry(this); + + JS::RootedValue fun(_context); + _MozJSCreateFunction(raw, functionNumber, &fun); + _funcs.emplace_back(_context, fun.get()); + + return functionNumber; +} + +void MozJSImplScope::setFunction(const char* field, const char* code) { + MozJSEntry entry(this); + + JS::RootedValue fun(_context); + + _MozJSCreateFunction(code, getFunctionCache().size() + 1, &fun); + + ObjectWrapper(_context, _global).setValue(field, fun); +} + +void MozJSImplScope::rename(const char* from, const char* to) { + MozJSEntry entry(this); + + ObjectWrapper(_context, _global).rename(from, to); +} + +int MozJSImplScope::invoke(ScriptingFunction func, + const BSONObj* argsObject, + const BSONObj* recv, + int timeoutMs, + bool ignoreReturn, + bool readOnlyArgs, + bool readOnlyRecv) { + MozJSEntry entry(this); + + auto funcValue = _funcs[func - 1]; + JS::RootedValue result(_context); + + const int nargs = argsObject ? argsObject->nFields() : 0; + + JS::AutoValueVector args(_context); + + if (nargs) { + BSONObjIterator it(*argsObject); + for (int i = 0; i < nargs; i++) { + BSONElement next = it.next(); + + JS::RootedValue value(_context); + ValueReader(_context, &value).fromBSONElement(next, readOnlyArgs); + + args.append(value); + } + } + + JS::RootedValue smrecv(_context); + if (recv) + ValueReader(_context, &smrecv).fromBSON(*recv, readOnlyRecv); + else + smrecv.setObjectOrNull(_global); + + if (timeoutMs) + _engine->getDeadlineMonitor().startDeadline(this, timeoutMs); + + JS::RootedValue out(_context); + JS::RootedObject obj(_context, smrecv.toObjectOrNull()); + + bool success = JS::Call(_context, obj, funcValue, args, &out); + + if (timeoutMs) + _engine->getDeadlineMonitor().stopDeadline(this); + + _checkErrorState(success); + + if (!ignoreReturn) { + // must validate the handle because TerminateExecution may have + // been thrown after the above checks + if (out.isObject() && _nativeFunctionProto.instanceOf(out)) { + warning() << "storing native function as return value"; + _lastRetIsNativeCode = true; + } else { + _lastRetIsNativeCode = false; + } + + ObjectWrapper(_context, _global).setValue(kInvokeResult, out); + } + + return 0; +} + +bool MozJSImplScope::exec(StringData code, + const std::string& name, + bool printResult, + bool reportError, + bool assertOnError, + int timeoutMs) { + MozJSEntry entry(this); + + JS::CompileOptions co(_context); + setCompileOptions(&co); + JS::RootedScript script(_context); + + bool success = JS::Compile(_context, _global, co, code.rawData(), code.size(), &script); + + if (_checkErrorState(success, reportError, assertOnError)) + return false; + + if (timeoutMs) + _engine->getDeadlineMonitor().startDeadline(this, timeoutMs); + + JS::RootedValue out(_context); + + success = JS_ExecuteScript(_context, _global, script, &out); + + if (timeoutMs) + _engine->getDeadlineMonitor().stopDeadline(this); + + if (_checkErrorState(success, reportError, assertOnError)) + return false; + + ObjectWrapper(_context, _global).setValue(kExecResult, out); + + if (printResult && !out.isUndefined()) { + // TODO: We seem to use this productively in v8, but it seems + // unecessary under sm. That probably means somethings off + // + // appears to only be used by shell + // std::cout << ValueWriter(_context, out).toString() << std::endl; + } + + return true; +} + +void MozJSImplScope::injectNative(const char* field, NativeFunction func, void* data) { + MozJSEntry entry(this); + + JS::RootedObject obj(_context); + + NativeFunctionInfo::make(_context, &obj, func, data); + + JS::RootedValue value(_context); + value.setObjectOrNull(obj); + ObjectWrapper(_context, _global).setValue(field, value); +} + +void MozJSImplScope::gc() { + _pendingGC.store(true); + JS_RequestInterruptCallback(_runtime); +} + +void MozJSImplScope::localConnectForDbEval(OperationContext* txn, const char* dbName) { + MozJSEntry entry(this); + + invariant(_opCtx == NULL); + _opCtx = txn; + + if (_connectState == ConnectState::External) + uasserted(12510, "externalSetup already called, can't call localConnect"); + if (_connectState == ConnectState::Local) { + if (_localDBName == dbName) + return; + uasserted(12511, + str::stream() << "localConnect previously called with name " << _localDBName); + } + + // NOTE: order is important here. the following methods must be called after + // the above conditional statements. + + // install db access functions in the global object + installDBAccess(); + + // install the Mongo function object and instantiate the 'db' global + _mongoLocalProto.install(_global); + execCoreFiles(); + + const char* const makeMongo = "_mongo = new Mongo()"; + exec(makeMongo, "local connect 2", false, true, true, 0); + + std::string makeDB = str::stream() << "db = _mongo.getDB(\"" << dbName << "\");"; + exec(makeDB, "local connect 3", false, true, true, 0); + + _connectState = ConnectState::Local; + _localDBName = dbName; + + loadStored(txn); +} + +void MozJSImplScope::externalSetup() { + MozJSEntry entry(this); + + if (_connectState == ConnectState::External) + return; + if (_connectState == ConnectState::Local) + uasserted(12512, "localConnect already called, can't call externalSetup"); + + mongo::sm::reset(0); + + // install db access functions in the global object + installDBAccess(); + + // install thread-related functions (e.g. _threadInject) + installFork(); + + // install the Mongo function object + _mongoExternalProto.install(_global); + execCoreFiles(); + _connectState = ConnectState::External; +} + +void MozJSImplScope::reset() { + unregisterOperation(); + _pendingKill.store(false); + _pendingGC.store(false); +} + +void MozJSImplScope::installBSONTypes() { + _binDataProto.install(_global); + _bsonProto.install(_global); + _dbPointerProto.install(_global); + _dbRefProto.install(_global); + _maxKeyProto.install(_global); + _minKeyProto.install(_global); + _nativeFunctionProto.install(_global); + _numberIntProto.install(_global); + _numberLongProto.install(_global); + _objectProto.install(_global); + _oidProto.install(_global); + _regExpProto.install(_global); + _timestampProto.install(_global); + + // This builtin map is a javascript 6 thing. We want our version. so + // take theirs out + ObjectWrapper(_context, _global).deleteProperty("Map"); +} + +void MozJSImplScope::installDBAccess() { + _cursorProto.install(_global); + _dbProto.install(_global); + _dbQueryProto.install(_global); + _dbCollectionProto.install(_global); +} + +void MozJSImplScope::installFork() { + _countDownLatchProto.install(_global); + _jsThreadProto.install(_global); +} + +bool MozJSImplScope::_checkErrorState(bool success, bool reportError, bool assertOnError) { + if (success) + return false; + + if (_status.isOK()) { + _status = Status(ErrorCodes::UnknownError, "Unknown Failure from JSInterpreter"); + } + + _error = _status.reason(); + + if (reportError) + error() << _error << std::endl; + + // Clear the status state + auto status = std::move(_status); + + if (assertOnError) { + // Throw if necessary + uassertStatusOK(status); + } + + return true; +} + + +void MozJSImplScope::setCompileOptions(JS::CompileOptions* co) { + co->setUTF8(true); +} + +MozJSImplScope* MozJSImplScope::getThreadScope() { + return kCurrentScope; +} + +void MozJSImplScope::setOOM() { + _status = Status(ErrorCodes::JSInterpreterFailure, "Out of memory"); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/implscope.h b/src/mongo/scripting/mozjs/implscope.h new file mode 100644 index 00000000000..4bd29e76676 --- /dev/null +++ b/src/mongo/scripting/mozjs/implscope.h @@ -0,0 +1,323 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include + +#include "mongo/client/dbclientcursor.h" +#include "mongo/scripting/mozjs/bindata.h" +#include "mongo/scripting/mozjs/bson.h" +#include "mongo/scripting/mozjs/countdownlatch.h" +#include "mongo/scripting/mozjs/cursor.h" +#include "mongo/scripting/mozjs/db.h" +#include "mongo/scripting/mozjs/dbcollection.h" +#include "mongo/scripting/mozjs/dbpointer.h" +#include "mongo/scripting/mozjs/dbquery.h" +#include "mongo/scripting/mozjs/dbref.h" +#include "mongo/scripting/mozjs/engine.h" +#include "mongo/scripting/mozjs/global.h" +#include "mongo/scripting/mozjs/jsthread.h" +#include "mongo/scripting/mozjs/maxkey.h" +#include "mongo/scripting/mozjs/minkey.h" +#include "mongo/scripting/mozjs/mongo.h" +#include "mongo/scripting/mozjs/nativefunction.h" +#include "mongo/scripting/mozjs/numberint.h" +#include "mongo/scripting/mozjs/numberlong.h" +#include "mongo/scripting/mozjs/object.h" +#include "mongo/scripting/mozjs/oid.h" +#include "mongo/scripting/mozjs/regexp.h" +#include "mongo/scripting/mozjs/timestamp.h" + +namespace mongo { +namespace mozjs { + +/** + * Implementation Scope for MozJS + * + * The Implementation scope holds the actual mozjs runtime and context objects, + * along with a number of global prototypes for mongoDB specific types. Each + * ImplScope requires it's own thread and cannot be accessed from any thread + * other than the one it was created on (this is a detail inherited from the + * JSRuntime). If you need a scope that can be accessed by different threads + * over the course of it's lifetime, see MozJSProxyScope + * + * For more information about overriden fields, see mongo::Scope + */ +class MozJSImplScope final : public Scope { + MONGO_DISALLOW_COPYING(MozJSImplScope); + +public: + explicit MozJSImplScope(MozJSScriptEngine* engine); + ~MozJSImplScope(); + + void init(const BSONObj* data) override; + + void reset() override; + + void kill(); + + bool isKillPending() const override; + + OperationContext* getOpContext() const; + + void registerOperation(OperationContext* txn) override; + + void unregisterOperation() override; + + void localConnectForDbEval(OperationContext* txn, const char* dbName) override; + + void externalSetup() override; + + std::string getError() override; + + bool hasOutOfMemoryException() override; + + void gc() override; + + double getNumber(const char* field) override; + int getNumberInt(const char* field) override; + long long getNumberLongLong(const char* field) override; + std::string getString(const char* field) override; + bool getBoolean(const char* field) override; + BSONObj getObject(const char* field) override; + + void setNumber(const char* field, double val) override; + void setString(const char* field, StringData val) override; + void setBoolean(const char* field, bool val) override; + void setElement(const char* field, const BSONElement& e) override; + void setObject(const char* field, const BSONObj& obj, bool readOnly) override; + void setFunction(const char* field, const char* code) override; + + int type(const char* field) override; + + void rename(const char* from, const char* to) override; + + int invoke(ScriptingFunction func, + const BSONObj* args, + const BSONObj* recv, + int timeoutMs = 0, + bool ignoreReturn = false, + bool readOnlyArgs = false, + bool readOnlyRecv = false) override; + + bool exec(StringData code, + const std::string& name, + bool printResult, + bool reportError, + bool assertOnError, + int timeoutMs) override; + + void injectNative(const char* field, NativeFunction func, void* data = 0) override; + + ScriptingFunction _createFunction(const char* code, + ScriptingFunction functionNumber = 0) override; + + void newFunction(StringData code, JS::MutableHandleValue out); + + BSONObj callThreadArgs(const BSONObj& obj); + + WrapType& getBinDataProto() { + return _binDataProto; + } + + WrapType& getBsonProto() { + return _bsonProto; + } + + WrapType& getCountDownLatchProto() { + return _countDownLatchProto; + } + + WrapType& getCursorProto() { + return _cursorProto; + } + + WrapType& getDbCollectionProto() { + return _dbCollectionProto; + } + + WrapType& getDbPointerProto() { + return _dbPointerProto; + } + + WrapType& getDbQueryProto() { + return _dbQueryProto; + } + + WrapType& getDbProto() { + return _dbProto; + } + + WrapType& getDbRefProto() { + return _dbRefProto; + } + + WrapType& getJSThreadProto() { + return _jsThreadProto; + } + + WrapType& getMaxKeyProto() { + return _maxKeyProto; + } + + WrapType& getMinKeyProto() { + return _minKeyProto; + } + + WrapType& getMongoExternalProto() { + return _mongoExternalProto; + } + + WrapType& getMongoLocalProto() { + return _mongoLocalProto; + } + + WrapType& getNativeFunctionProto() { + return _nativeFunctionProto; + } + + WrapType& getNumberIntProto() { + return _numberIntProto; + } + + WrapType& getNumberLongProto() { + return _numberLongProto; + } + + WrapType& getObjectProto() { + return _objectProto; + } + + WrapType& getOidProto() { + return _oidProto; + } + + WrapType& getRegExpProto() { + return _regExpProto; + } + + WrapType& getTimestampProto() { + return _timestampProto; + } + + static const char* const kExecResult; + static const char* const kInvokeResult; + + static MozJSImplScope* getThreadScope(); + void setOOM(); + +private: + void _MozJSCreateFunction(const char* raw, + ScriptingFunction functionNumber, + JS::MutableHandleValue fun); + + /** + * This structure exists exclusively to construct the runtime and context + * ahead of the various global prototypes in the ImplScope construction. + * Basically, we have to call some c apis on the way up and down and this + * takes care of that + */ + struct MozRuntime { + public: + MozRuntime(); + ~MozRuntime(); + + JSRuntime* _runtime; + JSContext* _context; + }; + + /** + * The connection state of the scope. + * + * This is for dbeval and the shell + */ + enum class ConnectState : char { + Not, + Local, + External, + }; + + struct MozJSEntry; + friend struct MozJSEntry; + + static void _reportError(JSContext* cx, const char* message, JSErrorReport* report); + static bool _interruptCallback(JSContext* cx); + static void _gcCallback(JSRuntime* rt, JSGCStatus status, void* data); + bool _checkErrorState(bool success, bool reportError = true, bool assertOnError = true); + + void installDBAccess(); + void installBSONTypes(); + void installFork(); + + void setCompileOptions(JS::CompileOptions* co); + + MozJSScriptEngine* _engine; + MozRuntime _mr; + JSRuntime* _runtime; + JSContext* _context; + WrapType _globalProto; + JS::HandleObject _global; + std::vector _funcs; + std::atomic _pendingKill; + std::string _error; + unsigned int _opId; // op id for this scope + OperationContext* _opCtx; // Op context for DbEval + std::atomic _pendingGC; + ConnectState _connectState; + Status _status; + + WrapType _binDataProto; + WrapType _bsonProto; + WrapType _countDownLatchProto; + WrapType _cursorProto; + WrapType _dbCollectionProto; + WrapType _dbPointerProto; + WrapType _dbQueryProto; + WrapType _dbProto; + WrapType _dbRefProto; + WrapType _jsThreadProto; + WrapType _maxKeyProto; + WrapType _minKeyProto; + WrapType _mongoExternalProto; + WrapType _mongoLocalProto; + WrapType _nativeFunctionProto; + WrapType _numberIntProto; + WrapType _numberLongProto; + WrapType _objectProto; + WrapType _oidProto; + WrapType _regExpProto; + WrapType _timestampProto; +}; + +inline MozJSImplScope* getScope(JSContext* cx) { + return static_cast(JS_GetContextPrivate(cx)); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/jscustomallocator.cpp b/src/mongo/scripting/mozjs/jscustomallocator.cpp new file mode 100644 index 00000000000..adba220129d --- /dev/null +++ b/src/mongo/scripting/mozjs/jscustomallocator.cpp @@ -0,0 +1,234 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include +#include +#include + +#include "mongo/config.h" +#include "mongo/util/concurrency/threadlocal.h" +#include "mongo/scripting/mozjs/implscope.h" + +#ifdef __linux__ +#include +#elif defined(__APPLE__) +#include +#elif defined(_WIN32) +#include +#else +#define MONGO_NO_MALLOC_USABLE_SIZE +#endif + +/** + * This shim interface (which controls dynamic allocation within SpiderMonkey), + * consciously uses std::malloc and friends over mongoMalloc. It does this + * because SpiderMonkey has some plausible options in the event of OOM, + * specifically it can begin aggressive garbage collection. It would also be + * reasonable to go the other route and fail, but for the moment I erred on the + * side of maintaining the contract that SpiderMonkey expects. + * + * The overall strategy here is to keep track of allocations in a thread local, + * offering us the chance to enforce soft limits on memory use rather than + * waiting for the OS to OOM us. + */ + +namespace mongo { +namespace sm { + +namespace { +/** + * These two variables track the total number of bytes handed out, and the + * maximum number of bytes we will consider handing out. They are set by + * MozJSImplScope on start up. + */ +MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL size_t total_bytes; +MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL size_t max_bytes; + +/** + * When we don't have malloc_usable_size, we manage by adjusting our pointer by + * kMaxAlign bytes and storing the size of the allocation kMaxAlign bytes + * behind the pointer we hand back. That let's us get to the value at runtime. + * We know kMaxAlign is enough (generally 8 or 16 bytes), because that's + * literally the contract between malloc and std::max_align_t. + * + * This is commented out right now because std::max_align_t didn't seem to be + * available on our solaris builder. TODO: revisit in the future to see if that + * still holds. + */ +// const size_t kMaxAlign = std::alignment_of::value; +const size_t kMaxAlign = 16; +} // namespace + +size_t get_total_bytes() { + return total_bytes; +} + +void reset(size_t bytes) { + total_bytes = 0; + max_bytes = bytes; +} + +size_t get_max_bytes() { + return max_bytes; +} + +/** + * Wraps std::Xalloc functions + * + * The idea here is to abstract soft limits on allocations, as well as possibly + * necessary pointer adjustment (if we don't have a malloc_usable_size + * replacement). + * + */ +template +void* wrap_alloc(T&& func, void* ptr, size_t bytes) { + size_t mb = get_max_bytes(); + size_t tb = get_total_bytes(); + + if (mb && (tb + bytes > mb)) { + auto scope = mongo::mozjs::MozJSImplScope::getThreadScope(); + invariant(scope); + + scope->setOOM(); + + return nullptr; + } + +#ifdef MONGO_NO_MALLOC_USABLE_SIZE + void* p = func(ptr ? static_cast(ptr) - kMaxAlign : nullptr, bytes + kMaxAlign); +#else + void* p = func(ptr, bytes); +#endif + + if (!p) { + return nullptr; + } + +#ifdef MONGO_NO_MALLOC_USABLE_SIZE + *reinterpret_cast(p) = bytes; + p = static_cast(p) + kMaxAlign; +#endif + + total_bytes = tb + bytes; + + return p; +} + +size_t get_current(void* ptr) { +#ifdef MONGO_NO_MALLOC_USABLE_SIZE + if (!ptr) + return 0; + + return *reinterpret_cast(static_cast(ptr) - kMaxAlign); +#elif defined(__linux__) + return malloc_usable_size(ptr); +#elif defined(__APPLE__) + return malloc_size(ptr); +#elif defined(_WIN32) + return _msize(ptr); +#else +#error "Should be unreachable" +#endif +} + +} // namespace sm +} // namespace mongo + +void* js_malloc(size_t bytes) { + return mongo::sm::wrap_alloc( + [](void* ptr, size_t b) { return std::malloc(b); }, nullptr, bytes); +} + +void* js_calloc(size_t bytes) { + return mongo::sm::wrap_alloc( + [](void* ptr, size_t b) { return std::calloc(b, 1); }, nullptr, bytes); +} + +void* js_calloc(size_t nmemb, size_t size) { + return mongo::sm::wrap_alloc( + [](void* ptr, size_t b) { return std::calloc(b, 1); }, nullptr, nmemb * size); +} + +void js_free(void* p) { + if (!p) + return; + + size_t current = mongo::sm::get_current(p); + size_t tb = mongo::sm::get_total_bytes(); + + if (tb >= current) { + mongo::sm::total_bytes = tb - current; + } + + mongo::sm::wrap_alloc([](void* ptr, size_t b) { + std::free(ptr); + return nullptr; + }, p, 0); +} + +void* js_realloc(void* p, size_t bytes) { + if (!p) { + return js_malloc(bytes); + } + + if (!bytes) { + js_free(p); + return nullptr; + } + + size_t current = mongo::sm::get_current(p); + + if (current >= bytes) { + return p; + } + + size_t tb = mongo::sm::total_bytes; + + if (tb >= current) { + mongo::sm::total_bytes = tb - current; + } + + return mongo::sm::wrap_alloc( + [](void* ptr, size_t b) { return std::realloc(ptr, b); }, p, bytes); +} + +char* js_strdup(const char* s) { + size_t bytes = std::strlen(s) + 1; + + char* new_s = static_cast(js_malloc(bytes)); + + if (!new_s) { + return nullptr; + } + + std::memcpy(new_s, s, bytes); + + return new_s; +} diff --git a/src/mongo/scripting/mozjs/jsstringwrapper.cpp b/src/mongo/scripting/mozjs/jsstringwrapper.cpp new file mode 100644 index 00000000000..8261b44a2fe --- /dev/null +++ b/src/mongo/scripting/mozjs/jsstringwrapper.cpp @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/jsstringwrapper.h" + +#include +#include +#include + +#include "mongo/base/error_codes.h" +#include "mongo/scripting/mozjs/exception.h" +#include "mongo/util/assert_util.h" + +namespace mongo { +namespace mozjs { + +JSStringWrapper::JSStringWrapper(JSContext* cx, JSString* str) : _context(cx) { + if (!str) + throwCurrentJSException(cx, ErrorCodes::InternalError, "Cannot encode null JSString"); + + // We have to do this flatstring thing because no public api tells us + // how long the utf8 strings we get out are. + // + // Well, at least js/CharacterEncoding's GetDeflatedUTF8StringLength + // and JS_flattenString are all in the public headers... + JSFlatString* flat = JS_FlattenString(cx, str); + if (!flat) + throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to flatten JSString"); + + _length = JS::GetDeflatedUTF8StringLength(flat); + + JS::RootedString rstr(cx, str); + + JSAutoByteString abs; + abs.encodeUtf8(cx, rstr); + + if (!abs) + throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to encode JSString"); + + _str.reset(new char[_length]); + std::memcpy(_str.get(), abs.ptr(), _length); +} + +StringData JSStringWrapper::toStringData() const { + return StringData(_str.get(), _length); +} + +std::string JSStringWrapper::toString() const { + return toStringData().toString(); +} + +JSStringWrapper::operator bool() const { + return _str.get(); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/jsstringwrapper.h b/src/mongo/scripting/mozjs/jsstringwrapper.h new file mode 100644 index 00000000000..9a0ee21e900 --- /dev/null +++ b/src/mongo/scripting/mozjs/jsstringwrapper.h @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include +#include +#include + +#include "mongo/base/string_data.h" + +namespace mongo { +namespace mozjs { + +/** + * Wraps JSStrings to simplify coercing them to and from C++ style StringData + * and std::strings. + */ +class JSStringWrapper { +public: + JSStringWrapper() = default; + JSStringWrapper(JSContext* cx, JSString* str); + + StringData toStringData() const; + std::string toString() const; + + explicit operator bool() const; + +private: + JSContext* _context = nullptr; + std::unique_ptr _str; + size_t _length = 0; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/jsthread.cpp b/src/mongo/scripting/mozjs/jsthread.cpp new file mode 100644 index 00000000000..24b4c5d88af --- /dev/null +++ b/src/mongo/scripting/mozjs/jsthread.cpp @@ -0,0 +1,274 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/jsthread.h" + +#include + +#include "mongo/db/jsobj.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/stdx/memory.h" +#include "mongo/stdx/mutex.h" +#include "mongo/stdx/thread.h" +#include "mongo/util/log.h" +#include "mongo/util/stacktrace.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec JSThreadInfo::threadMethods[6] = { + MONGO_ATTACH_JS_FUNCTION(init), + MONGO_ATTACH_JS_FUNCTION(start), + MONGO_ATTACH_JS_FUNCTION(join), + MONGO_ATTACH_JS_FUNCTION(hasFailed), + MONGO_ATTACH_JS_FUNCTION(returnData), + JS_FS_END, +}; + +const JSFunctionSpec JSThreadInfo::freeFunctions[3] = { + MONGO_ATTACH_JS_FUNCTION(_threadInject), + MONGO_ATTACH_JS_FUNCTION(_scopedThreadInject), + JS_FS_END, +}; + +const char* const JSThreadInfo::className = "JSThread"; + +/** + * Holder for JSThreads as exposed by fork() in the shell. + * + * The idea here is that we create a jsthread by taking a js function and its + * parameters and encoding them into a single bson object. Then we spawn a + * thread, have that thread do the work and join() it before checking it's + * result (serialized through bson). We can check errors at any time by + * checking a mutex guarded hasError(). + */ +class JSThreadConfig { +public: + JSThreadConfig(JSContext* cx, JS::CallArgs args) + : _started(false), _done(false), _sharedData(new SharedData()) { + uassert(ErrorCodes::JSInterpreterFailure, "need at least one argument", args.length() > 0); + uassert(ErrorCodes::JSInterpreterFailure, + "first argument must be a function", + args.get(0).isObject() && JS_ObjectIsFunction(cx, args.get(0).toObjectOrNull())); + + BSONObjBuilder b; + for (unsigned i = 0; i < args.length(); ++i) { + // 10 decimal digits for a 32 bit unsigned, then 1 for the null + char buf[11]; + std::sprintf(buf, "%i", i); + + ValueWriter(cx, args.get(i)).writeThis(&b, buf); + } + + _sharedData->_args = b.obj(); + } + + void start() { + uassert(ErrorCodes::JSInterpreterFailure, "Thread already started", !_started); + + _thread = stdx::thread(JSThread(*this)); + _started = true; + } + + void join() { + uassert(ErrorCodes::JSInterpreterFailure, "Thread not running", _started && !_done); + + _thread.join(); + _done = true; + } + + /** + * Returns true if the JSThread terminated as a result of an error + * during its execution, and false otherwise. This operation does + * not block, nor does it require join() to have been called. + */ + bool hasFailed() const { + uassert(ErrorCodes::JSInterpreterFailure, "Thread not started", _started); + + return _sharedData->getErrored(); + } + + BSONObj returnData() { + if (!_done) + join(); + + return _sharedData->_returnData; + } + +private: + /** + * SharedData between the calling thread and the callee + * + * JSThreadConfig doesn't always outlive its JSThread (for example, if the parent thread + * garbage collects the JSThreadConfig before the JSThread has finished running), so any + * data shared between them has to go in a shared_ptr. + */ + class SharedData { + public: + SharedData() : _errored(false) {} + + void setErrored(bool value) { + stdx::lock_guard lck(_erroredMutex); + _errored = value; + } + + bool getErrored() { + stdx::lock_guard lck(_erroredMutex); + return _errored; + } + + /** + * These two members aren't protected in any way, so you have to be + * mindful about how they're used. I.e. _args needs to be set before + * start() and _returnData can't be touched until after join(). + */ + BSONObj _args; + BSONObj _returnData; + + private: + stdx::mutex _erroredMutex; + bool _errored; + }; + + /** + * The callable object used by stdx::thread + */ + class JSThread { + public: + JSThread(JSThreadConfig& config) : _sharedData(config._sharedData) {} + + void operator()() { + try { + MozJSImplScope scope(static_cast(globalScriptEngine)); + + _sharedData->_returnData = scope.callThreadArgs(_sharedData->_args); + } catch (...) { + auto status = exceptionToStatus(); + + log() << "js thread threw c++ exception: " << status; + _sharedData->setErrored(true); + _sharedData->_returnData = BSON("ret" << BSONUndefined); + } + } + + private: + std::shared_ptr _sharedData; + }; + + bool _started; + bool _done; + stdx::thread _thread; + std::shared_ptr _sharedData; +}; + +namespace { + +JSThreadConfig* getConfig(JSContext* cx, JS::CallArgs args) { + JS::RootedValue value(cx); + ObjectWrapper(cx, args.thisv()).getValue("_JSThreadConfig", &value); + + if (!value.isObject()) + uasserted(ErrorCodes::InternalError, "_JSThreadConfig not an object"); + + return static_cast(JS_GetPrivate(value.toObjectOrNull())); +} + +} // namespace + +void JSThreadInfo::finalize(JSFreeOp* fop, JSObject* obj) { + auto config = static_cast(JS_GetPrivate(obj)); + + if (!config) + return; + + delete config; +} + +void JSThreadInfo::Functions::init(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + JS::RootedObject obj(cx); + scope->getJSThreadProto().newObject(&obj); + JSThreadConfig* config = new JSThreadConfig(cx, args); + JS_SetPrivate(obj, config); + + ObjectWrapper(cx, args.thisv()).setObject("_JSThreadConfig", obj); + + args.rval().setUndefined(); +} + +void JSThreadInfo::Functions::start(JSContext* cx, JS::CallArgs args) { + getConfig(cx, args)->start(); + + args.rval().setUndefined(); +} + +void JSThreadInfo::Functions::join(JSContext* cx, JS::CallArgs args) { + getConfig(cx, args)->join(); + + args.rval().setUndefined(); +} + +void JSThreadInfo::Functions::hasFailed(JSContext* cx, JS::CallArgs args) { + args.rval().setBoolean(getConfig(cx, args)->hasFailed()); +} + +void JSThreadInfo::Functions::returnData(JSContext* cx, JS::CallArgs args) { + ValueReader(cx, args.rval()) + .fromBSONElement(getConfig(cx, args)->returnData().firstElement(), true); +} + +void JSThreadInfo::Functions::_threadInject(JSContext* cx, JS::CallArgs args) { + uassert(ErrorCodes::JSInterpreterFailure, + "threadInject takes exactly 1 argument", + args.length() == 1); + uassert(ErrorCodes::JSInterpreterFailure, + "threadInject needs to be passed a prototype", + args.get(0).isObject()); + + JS::RootedObject o(cx, args.get(0).toObjectOrNull()); + + if (!JS_DefineFunctions(cx, o, JSThreadInfo::threadMethods)) + throwCurrentJSException(cx, ErrorCodes::JSInterpreterFailure, "Failed to define functions"); + + args.rval().setUndefined(); +} + +void JSThreadInfo::Functions::_scopedThreadInject(JSContext* cx, JS::CallArgs args) { + _threadInject(cx, args); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/jsthread.h b/src/mongo/scripting/mozjs/jsthread.h new file mode 100644 index 00000000000..ff468d987ab --- /dev/null +++ b/src/mongo/scripting/mozjs/jsthread.h @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * Helper for the JSThread javascript object + * + * The workflow is strange because we have a thing in javascript called a + * JSThread, but we don't actually get to construct it. Instead, we have to + * inject methods into that thing (via _threadInject) and hang our C++ thread + * separately (via init() on that type). + * + * To manage lifetime, we just add a field into the injected object that's our + * JSThread and add our holder in as our JSThread's private member. + */ +struct JSThreadInfo : public BaseInfo { + static void finalize(JSFreeOp* fop, JSObject* obj); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(init); + MONGO_DEFINE_JS_FUNCTION(start); + MONGO_DEFINE_JS_FUNCTION(join); + MONGO_DEFINE_JS_FUNCTION(hasFailed); + MONGO_DEFINE_JS_FUNCTION(returnData); + + MONGO_DEFINE_JS_FUNCTION(_threadInject); + MONGO_DEFINE_JS_FUNCTION(_scopedThreadInject); + }; + + /** + * Note that this isn't meant to supply methods for JSThread, it's just + * there to work with _threadInject. So the name isn't a mistake + */ + static const JSFunctionSpec threadMethods[6]; + static const JSFunctionSpec freeFunctions[3]; + + static const char* const className; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; + static const InstallType installType = InstallType::Private; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/maxkey.cpp b/src/mongo/scripting/mozjs/maxkey.cpp new file mode 100644 index 00000000000..022ce347773 --- /dev/null +++ b/src/mongo/scripting/mozjs/maxkey.cpp @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/maxkey.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec MaxKeyInfo::methods[2] = { + MONGO_ATTACH_JS_FUNCTION(tojson), JS_FS_END, +}; + +const char* const MaxKeyInfo::className = "MaxKey"; + +namespace { +const char* const kSingleton = "singleton"; +} // namespace + +void MaxKeyInfo::construct(JSContext* cx, JS::CallArgs args) { + call(cx, args); +} + +/** + * The idea here is that MinKey and MaxKey are singleton callable objects that + * return the singleton when called. This enables all instances to compare + * == and === to MinKey even if created by "new MinKey()" in JS. + */ +void MaxKeyInfo::call(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + ObjectWrapper o(cx, scope->getMaxKeyProto().getProto()); + + JS::RootedValue val(cx); + + if (!o.hasField(kSingleton)) { + JS::RootedObject thisv(cx); + scope->getMaxKeyProto().newObject(&thisv); + + val.setObjectOrNull(thisv); + o.setValue(kSingleton, val); + } else { + o.getValue(kSingleton, &val); + } + + args.rval().setObjectOrNull(val.toObjectOrNull()); +} + +void MaxKeyInfo::Functions::tojson(JSContext* cx, JS::CallArgs args) { + ValueReader(cx, args.rval()).fromStringData("{ \"$maxKey\" : 1 }"); +} + +void MaxKeyInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) { + ObjectWrapper protoWrapper(cx, proto); + + JS::RootedValue value(cx); + getScope(cx)->getMaxKeyProto().newObject(&value); + + ObjectWrapper(cx, global).setValue("MaxKey", value); + protoWrapper.setValue(kSingleton, value); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/maxkey.h b/src/mongo/scripting/mozjs/maxkey.h new file mode 100644 index 00000000000..ac5d937f157 --- /dev/null +++ b/src/mongo/scripting/mozjs/maxkey.h @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "MaxKey" Javascript object. + * + * These are slightly special, in that there is only one MaxKey object and + * whenever you call the constructor to make a new one you just get the + * "singleton" MaxKey from the prototype. See the postInstall for details. + */ +struct MaxKeyInfo : public BaseInfo { + static void call(JSContext* cx, JS::CallArgs args); + static void construct(JSContext* cx, JS::CallArgs args); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(tojson); + }; + + static const JSFunctionSpec methods[2]; + + static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto); + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/minkey.cpp b/src/mongo/scripting/mozjs/minkey.cpp new file mode 100644 index 00000000000..fe06fa6bb49 --- /dev/null +++ b/src/mongo/scripting/mozjs/minkey.cpp @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/minkey.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec MinKeyInfo::methods[2] = { + MONGO_ATTACH_JS_FUNCTION(tojson), JS_FS_END, +}; + +const char* const MinKeyInfo::className = "MinKey"; + +namespace { +const char* const kSingleton = "singleton"; +} // namespace + +void MinKeyInfo::construct(JSContext* cx, JS::CallArgs args) { + call(cx, args); +} + +/** + * The idea here is that MinKey and MaxKey are singleton callable objects that + * return the singleton when called. This enables all instances to compare + * == and === to MinKey even if created by "new MinKey()" in JS. + */ +void MinKeyInfo::call(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + ObjectWrapper o(cx, scope->getMinKeyProto().getProto()); + + JS::RootedValue val(cx); + + if (!o.hasField(kSingleton)) { + JS::RootedObject thisv(cx); + scope->getMinKeyProto().newObject(&thisv); + + val.setObjectOrNull(thisv); + o.setValue(kSingleton, val); + } else { + o.getValue(kSingleton, &val); + } + + args.rval().setObjectOrNull(val.toObjectOrNull()); +} + +void MinKeyInfo::Functions::tojson(JSContext* cx, JS::CallArgs args) { + ValueReader(cx, args.rval()).fromStringData("{ \"$minKey\" : 1 }"); +} + +void MinKeyInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) { + ObjectWrapper protoWrapper(cx, proto); + + JS::RootedValue value(cx); + getScope(cx)->getMinKeyProto().newObject(&value); + + ObjectWrapper(cx, global).setValue("MinKey", value); + protoWrapper.setValue(kSingleton, value); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/minkey.h b/src/mongo/scripting/mozjs/minkey.h new file mode 100644 index 00000000000..caea91d54ac --- /dev/null +++ b/src/mongo/scripting/mozjs/minkey.h @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "MinKey" Javascript object. + * + * These are slightly special, in that there is only one MinKey object and + * whenever you call the constructor to make a new one you just get the + * "singleton" MinKey from the prototype. See the postInstall for details. + */ +struct MinKeyInfo : public BaseInfo { + static void call(JSContext* cx, JS::CallArgs args); + static void construct(JSContext* cx, JS::CallArgs args); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(tojson); + }; + + static const JSFunctionSpec methods[2]; + + static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto); + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp new file mode 100644 index 00000000000..f1f69d0d39c --- /dev/null +++ b/src/mongo/scripting/mozjs/mongo.cpp @@ -0,0 +1,565 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/mongo.h" + +#include "mongo/client/dbclientinterface.h" +#include "mongo/client/native_sasl_client_session.h" +#include "mongo/client/sasl_client_authenticate.h" +#include "mongo/client/sasl_client_session.h" +#include "mongo/db/namespace_string.h" +#include "mongo/scripting/mozjs/cursor.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/stdx/memory.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec MongoBase::methods[13] = { + MONGO_ATTACH_JS_FUNCTION(auth), + MONGO_ATTACH_JS_FUNCTION(copyDatabaseWithSCRAM), + MONGO_ATTACH_JS_FUNCTION(cursorFromId), + MONGO_ATTACH_JS_FUNCTION(find), + MONGO_ATTACH_JS_FUNCTION(getClientRPCProtocols), + MONGO_ATTACH_JS_FUNCTION(getServerRPCProtocols), + MONGO_ATTACH_JS_FUNCTION(insert), + MONGO_ATTACH_JS_FUNCTION(logout), + MONGO_ATTACH_JS_FUNCTION(remove), + MONGO_ATTACH_JS_FUNCTION(runCommand), + MONGO_ATTACH_JS_FUNCTION(setClientRPCProtocols), + MONGO_ATTACH_JS_FUNCTION(update), + JS_FS_END, +}; + +const char* const MongoBase::className = "Mongo"; + +const JSFunctionSpec MongoExternalInfo::freeFunctions[2] = { + MONGO_ATTACH_JS_FUNCTION(load), JS_FS_END, +}; + +namespace { +DBClientBase* getConnection(JS::CallArgs& args) { + return static_cast*>(JS_GetPrivate(args.thisv().toObjectOrNull())) + ->get(); +} + +void setCursor(JS::HandleObject target, + std::unique_ptr cursor, + JS::CallArgs& args) { + auto client = + static_cast*>(JS_GetPrivate(args.thisv().toObjectOrNull())); + + // Copy the client shared pointer to up the refcount + JS_SetPrivate(target, new CursorInfo::CursorHolder(std::move(cursor), *client)); +} +} // namespace + +void MongoBase::finalize(JSFreeOp* fop, JSObject* obj) { + auto conn = static_cast*>(JS_GetPrivate(obj)); + + if (conn) { + delete conn; + } +} + +void MongoBase::Functions::runCommand(JSContext* cx, JS::CallArgs args) { + if (args.length() != 3) + uasserted(ErrorCodes::BadValue, "runCommand needs 3 args"); + + if (!args.get(0).isString()) + uasserted(ErrorCodes::BadValue, "the database parameter to runCommand must be a string"); + + if (!args.get(1).isObject()) + uasserted(ErrorCodes::BadValue, "the cmdObj parameter to runCommand must be an object"); + + if (!args.get(2).isNumber()) + uasserted(ErrorCodes::BadValue, "the options parameter to runCommand must be a number"); + + auto conn = getConnection(args); + + std::string database = ValueWriter(cx, args.get(0)).toString(); + + BSONObj cmdObj = ValueWriter(cx, args.get(1)).toBSON(); + + int queryOptions = ValueWriter(cx, args.get(2)).toInt32(); + BSONObj cmdRes; + conn->runCommand(database, cmdObj, cmdRes, queryOptions); + + // the returned object is not read only as some of our tests depend on modifying it. + ValueReader(cx, args.rval()).fromBSON(cmdRes, false /* read only */); +} + +void MongoBase::Functions::find(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (args.length() != 7) + uasserted(ErrorCodes::BadValue, "find needs 7 args"); + + if (!args.get(1).isObject()) + uasserted(ErrorCodes::BadValue, "needs to be an object"); + + auto conn = getConnection(args); + + std::string ns = ValueWriter(cx, args.get(0)).toString(); + + BSONObj fields; + BSONObj q = ValueWriter(cx, args.get(1)).toBSON(); + + bool haveFields = false; + + if (args.get(2).isObject()) { + size_t i = 0; + + JS::RootedObject obj(cx, args.get(2).toObjectOrNull()); + + ObjectWrapper(cx, obj).enumerate([&i](jsid) { ++i; }); + + if (i > 0) + haveFields = true; + } + + if (haveFields) + fields = ValueWriter(cx, args.get(2)).toBSON(); + + int nToReturn = ValueWriter(cx, args.get(3)).toInt32(); + int nToSkip = ValueWriter(cx, args.get(4)).toInt32(); + int batchSize = ValueWriter(cx, args.get(5)).toInt32(); + int options = ValueWriter(cx, args.get(6)).toInt32(); + + std::unique_ptr cursor( + conn->query(ns, q, nToReturn, nToSkip, haveFields ? &fields : NULL, options, batchSize)); + if (!cursor.get()) { + uasserted(ErrorCodes::InternalError, "error doing query: failed"); + } + + JS::RootedObject c(cx); + scope->getCursorProto().newInstance(&c); + + setCursor(c, std::move(cursor), args); + + args.rval().setObjectOrNull(c); +} + +void MongoBase::Functions::insert(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (args.length() != 3) + uasserted(ErrorCodes::BadValue, "insert needs 3 args"); + + if (!args.get(1).isObject()) + uasserted(ErrorCodes::BadValue, "attempted to insert a non-object"); + + ObjectWrapper o(cx, args.thisv()); + + if (o.hasField("readOnly") && o.getBoolean("readOnly")) + uasserted(ErrorCodes::BadValue, "js db in read only mode"); + + auto conn = getConnection(args); + + std::string ns = ValueWriter(cx, args.get(0)).toString(); + + int flags = ValueWriter(cx, args.get(2)).toInt32(); + + auto addId = [cx, scope](JS::HandleValue value) { + if (!value.isObject()) + uasserted(ErrorCodes::BadValue, "attempted to insert a non-object type"); + + JS::RootedObject elementObj(cx, value.toObjectOrNull()); + + ObjectWrapper ele(cx, elementObj); + + if (!ele.hasField("_id")) { + JS::RootedValue value(cx); + scope->getOidProto().newInstance(&value); + ele.setValue("_id", value); + } + + return ValueWriter(cx, value).toBSON(); + }; + + if (args.get(1).isObject() && JS_IsArrayObject(cx, args.get(1))) { + JS::RootedObject obj(cx, args.get(1).toObjectOrNull()); + ObjectWrapper array(cx, obj); + + std::vector bos; + + bool foundElement = false; + + array.enumerate([&](JS::HandleId id) { + foundElement = true; + + JS::RootedValue value(cx); + array.getValue(id, &value); + + bos.push_back(addId(value)); + }); + + if (!foundElement) + uasserted(ErrorCodes::BadValue, "attempted to insert an empty array"); + + conn->insert(ns, bos, flags); + } else { + conn->insert(ns, addId(args.get(1))); + } + + args.rval().setUndefined(); +} + +void MongoBase::Functions::remove(JSContext* cx, JS::CallArgs args) { + if (!(args.length() == 2 || args.length() == 3)) + uasserted(ErrorCodes::BadValue, "remove needs 2 or 3 args"); + + if (!(args.get(1).isObject())) + uasserted(ErrorCodes::BadValue, "attempted to remove a non-object"); + + ObjectWrapper o(cx, args.thisv()); + + if (o.hasField("readOnly") && o.getBoolean("readOnly")) + uasserted(ErrorCodes::BadValue, "js db in read only mode"); + + auto conn = getConnection(args); + std::string ns = ValueWriter(cx, args.get(0)).toString(); + + BSONObj bson = ValueWriter(cx, args.get(1)).toBSON(); + + bool justOne = false; + if (args.length() > 2) { + justOne = args.get(2).toBoolean(); + } + + conn->remove(ns, bson, justOne); + args.rval().setUndefined(); +} + +void MongoBase::Functions::update(JSContext* cx, JS::CallArgs args) { + if (args.length() < 3) + uasserted(ErrorCodes::BadValue, "update needs at least 3 args"); + + if (!args.get(1).isObject()) + uasserted(ErrorCodes::BadValue, "1st param to update has to be an object"); + + if (!args.get(2).isObject()) + uasserted(ErrorCodes::BadValue, "2nd param to update has to be an object"); + + ObjectWrapper o(cx, args.thisv()); + + if (o.hasField("readOnly") && o.getBoolean("readOnly")) + uasserted(ErrorCodes::BadValue, "js db in read only mode"); + + auto conn = getConnection(args); + std::string ns = ValueWriter(cx, args.get(0)).toString(); + + BSONObj q1 = ValueWriter(cx, args.get(1)).toBSON(); + BSONObj o1 = ValueWriter(cx, args.get(2)).toBSON(); + + bool upsert = args.length() > 3 && args.get(3).isBoolean() && args.get(3).toBoolean(); + bool multi = args.length() > 4 && args.get(4).isBoolean() && args.get(4).toBoolean(); + + conn->update(ns, q1, o1, upsert, multi); + args.rval().setUndefined(); +} + +void MongoBase::Functions::auth(JSContext* cx, JS::CallArgs args) { + auto conn = getConnection(args); + if (!conn) + uasserted(ErrorCodes::BadValue, "no connection"); + + BSONObj params; + switch (args.length()) { + case 1: + params = ValueWriter(cx, args.get(0)).toBSON(); + break; + case 3: + params = BSON(saslCommandMechanismFieldName + << "MONGODB-CR" << saslCommandUserDBFieldName + << ValueWriter(cx, args[0]).toString() << saslCommandUserFieldName + << ValueWriter(cx, args[1]).toString() << saslCommandPasswordFieldName + << ValueWriter(cx, args[2]).toString()); + break; + default: + uasserted(ErrorCodes::BadValue, "mongoAuth takes 1 object or 3 string arguments"); + } + + conn->auth(params); + + args.rval().setBoolean(true); +} + +void MongoBase::Functions::logout(JSContext* cx, JS::CallArgs args) { + if (args.length() != 1) + uasserted(ErrorCodes::BadValue, "logout needs 1 arg"); + + BSONObj ret; + + std::string db = ValueWriter(cx, args.get(0)).toString(); + + auto conn = getConnection(args); + if (conn) { + conn->logout(db, ret); + } + + ValueReader(cx, args.rval()).fromBSON(ret, false); +} + +void MongoBase::Functions::cursorFromId(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (!(args.length() == 2 || args.length() == 3)) + uasserted(ErrorCodes::BadValue, "cursorFromId needs 2 or 3 args"); + + if (!scope->getNumberLongProto().instanceOf(args.get(1))) + uasserted(ErrorCodes::BadValue, "2nd arg must be a NumberLong"); + + if (!(args.get(2).isNumber() || args.get(2).isUndefined())) + uasserted(ErrorCodes::BadValue, "3rd arg must be a js Number"); + + auto conn = getConnection(args); + + std::string ns = ValueWriter(cx, args.get(0)).toString(); + + long long cursorId = NumberLongInfo::ToNumberLong(cx, args.get(1)); + + auto cursor = stdx::make_unique(conn, ns, cursorId, 0, 0); + + if (args.get(2).isNumber()) + cursor->setBatchSize(ValueWriter(cx, args.get(2)).toInt32()); + + JS::RootedObject c(cx); + scope->getCursorProto().newInstance(&c); + + setCursor(c, std::move(cursor), args); + + args.rval().setObjectOrNull(c); +} + +void MongoBase::Functions::copyDatabaseWithSCRAM(JSContext* cx, JS::CallArgs args) { + auto conn = getConnection(args); + + if (!conn) + uasserted(ErrorCodes::BadValue, "no connection"); + + if (args.length() != 5) + uasserted(ErrorCodes::BadValue, "copyDatabase needs 5 arg"); + + // copyDatabase(fromdb, todb, fromhost, username, password); + std::string fromDb = ValueWriter(cx, args.get(0)).toString(); + std::string toDb = ValueWriter(cx, args.get(1)).toString(); + std::string fromHost = ValueWriter(cx, args.get(2)).toString(); + std::string user = ValueWriter(cx, args.get(3)).toString(); + std::string password = ValueWriter(cx, args.get(4)).toString(); + + std::string hashedPwd = DBClientWithCommands::createPasswordDigest(user, password); + + std::unique_ptr session(new NativeSaslClientSession()); + + session->setParameter(SaslClientSession::parameterMechanism, "SCRAM-SHA-1"); + session->setParameter(SaslClientSession::parameterUser, user); + session->setParameter(SaslClientSession::parameterPassword, hashedPwd); + session->initialize(); + + BSONObj saslFirstCommandPrefix = + BSON("copydbsaslstart" << 1 << "fromhost" << fromHost << "fromdb" << fromDb + << saslCommandMechanismFieldName << "SCRAM-SHA-1"); + + BSONObj saslFollowupCommandPrefix = + BSON("copydb" << 1 << "fromhost" << fromHost << "fromdb" << fromDb << "todb" << toDb); + + BSONObj saslCommandPrefix = saslFirstCommandPrefix; + BSONObj inputObj = BSON(saslCommandPayloadFieldName << ""); + bool isServerDone = false; + + while (!session->isDone()) { + std::string payload; + BSONType type; + + Status status = saslExtractPayload(inputObj, &payload, &type); + uassertStatusOK(status); + + std::string responsePayload; + status = session->step(payload, &responsePayload); + uassertStatusOK(status); + + BSONObjBuilder commandBuilder; + + commandBuilder.appendElements(saslCommandPrefix); + commandBuilder.appendBinData(saslCommandPayloadFieldName, + static_cast(responsePayload.size()), + BinDataGeneral, + responsePayload.c_str()); + BSONElement conversationId = inputObj[saslCommandConversationIdFieldName]; + if (!conversationId.eoo()) + commandBuilder.append(conversationId); + + BSONObj command = commandBuilder.obj(); + + bool ok = conn->runCommand("admin", command, inputObj); + + ErrorCodes::Error code = + ErrorCodes::fromInt(inputObj[saslCommandCodeFieldName].numberInt()); + + if (!ok || code != ErrorCodes::OK) { + if (code == ErrorCodes::OK) + code = ErrorCodes::UnknownError; + + ValueReader(cx, args.rval()).fromBSON(inputObj, true); + return; + } + + isServerDone = inputObj[saslCommandDoneFieldName].trueValue(); + saslCommandPrefix = saslFollowupCommandPrefix; + } + + if (!isServerDone) { + uasserted(ErrorCodes::InternalError, "copydb client finished before server."); + } + + ValueReader(cx, args.rval()).fromBSON(inputObj, true); +} + +void MongoBase::Functions::getClientRPCProtocols(JSContext* cx, JS::CallArgs args) { + auto conn = getConnection(args); + + if (args.length() != 0) + uasserted(ErrorCodes::BadValue, "getClientRPCProtocols takes no args"); + + auto clientRPCProtocols = rpc::toString(conn->getClientRPCProtocols()); + uassertStatusOK(clientRPCProtocols); + + auto protoStr = clientRPCProtocols.getValue().toString(); + + ValueReader(cx, args.rval()).fromStringData(protoStr); +} + +void MongoBase::Functions::setClientRPCProtocols(JSContext* cx, JS::CallArgs args) { + auto conn = getConnection(args); + + if (args.length() != 1) + uasserted(ErrorCodes::BadValue, "setClientRPCProtocols needs 1 arg"); + if (!args.get(0).isString()) + uasserted(ErrorCodes::BadValue, "first argument to setClientRPCProtocols must be a string"); + + std::string rpcProtosStr = ValueWriter(cx, args.get(0)).toString(); + + auto clientRPCProtocols = rpc::parseProtocolSet(rpcProtosStr); + uassertStatusOK(clientRPCProtocols); + + conn->setClientRPCProtocols(clientRPCProtocols.getValue()); + + args.rval().setUndefined(); +} + +void MongoBase::Functions::getServerRPCProtocols(JSContext* cx, JS::CallArgs args) { + auto conn = getConnection(args); + + if (args.length() != 0) + uasserted(ErrorCodes::BadValue, "getServerRPCProtocols takes no args"); + + auto serverRPCProtocols = rpc::toString(conn->getServerRPCProtocols()); + uassertStatusOK(serverRPCProtocols); + + auto protoStr = serverRPCProtocols.getValue().toString(); + + ValueReader(cx, args.rval()).fromStringData(protoStr); +} + +void MongoLocalInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + if (args.length() != 0) + uasserted(ErrorCodes::BadValue, "local Mongo constructor takes no args"); + + std::unique_ptr conn; + + conn.reset(createDirectClient(scope->getOpContext())); + + JS::RootedObject thisv(cx); + scope->getMongoLocalProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + JS_SetPrivate(thisv, new std::shared_ptr(conn.release())); + + o.setBoolean("slaveOk", false); + o.setString("host", "EMBEDDED"); + + args.rval().setObjectOrNull(thisv); +} + +void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + std::string host("127.0.0.1"); + + if (args.length() > 0 && args.get(0).isString()) { + host = ValueWriter(cx, args.get(0)).toString(); + } + + auto statusWithHost = ConnectionString::parse(host); + uassertStatusOK(statusWithHost); + + const ConnectionString cs(statusWithHost.getValue()); + + std::string errmsg; + std::unique_ptr conn(cs.connect(errmsg)); + + if (!conn.get()) { + uasserted(ErrorCodes::InternalError, errmsg); + } + + JS::RootedObject thisv(cx); + scope->getMongoExternalProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + JS_SetPrivate(thisv, new std::shared_ptr(conn.release())); + + o.setBoolean("slaveOk", false); + o.setString("host", host); + + args.rval().setObjectOrNull(thisv); +} + +void MongoExternalInfo::Functions::load(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + for (unsigned i = 0; i < args.length(); ++i) { + std::string filename = ValueWriter(cx, args.get(i)).toString(); + + if (!scope->execFile(filename, false, true)) { + uasserted(ErrorCodes::BadValue, std::string("error loading js file: ") + filename); + } + } + + args.rval().setBoolean(true); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/mongo.h b/src/mongo/scripting/mozjs/mongo.h new file mode 100644 index 00000000000..35815da5455 --- /dev/null +++ b/src/mongo/scripting/mozjs/mongo.h @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * Shared code for the "Mongo" javascript object. + * + * The idea here is that there is a lot of shared functionality between the + * "Mongo" we see in the shell and the "Mongo" in dbeval. So we provide one + * info type with common code and differentiate with varying constructors. + */ +struct MongoBase : public BaseInfo { + static void finalize(JSFreeOp* fop, JSObject* obj); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(auth); + MONGO_DEFINE_JS_FUNCTION(copyDatabaseWithSCRAM); + MONGO_DEFINE_JS_FUNCTION(cursorFromId); + MONGO_DEFINE_JS_FUNCTION(find); + MONGO_DEFINE_JS_FUNCTION(getClientRPCProtocols); + MONGO_DEFINE_JS_FUNCTION(getServerRPCProtocols); + MONGO_DEFINE_JS_FUNCTION(insert); + MONGO_DEFINE_JS_FUNCTION(logout); + MONGO_DEFINE_JS_FUNCTION(remove); + MONGO_DEFINE_JS_FUNCTION(runCommand); + MONGO_DEFINE_JS_FUNCTION(setClientRPCProtocols); + MONGO_DEFINE_JS_FUNCTION(update); + }; + + static const JSFunctionSpec methods[13]; + + static const char* const className; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; +}; + +/** + * The dbeval variant of "Mongo" + */ +struct MongoLocalInfo : public MongoBase { + static void construct(JSContext* cx, JS::CallArgs args); +}; + +/** + * The shell variant of "Mongo" + */ +struct MongoExternalInfo : public MongoBase { + static void construct(JSContext* cx, JS::CallArgs args); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(load); + }; + + static const JSFunctionSpec freeFunctions[2]; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/nativefunction.cpp b/src/mongo/scripting/mozjs/nativefunction.cpp new file mode 100644 index 00000000000..010b6a13587 --- /dev/null +++ b/src/mongo/scripting/mozjs/nativefunction.cpp @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/nativefunction.h" + +#include + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace mozjs { + +const char* const NativeFunctionInfo::inheritFrom = "Function"; +const char* const NativeFunctionInfo::className = "NativeFunction"; + +const JSFunctionSpec NativeFunctionInfo::methods[2] = { + MONGO_ATTACH_JS_FUNCTION(toString), JS_FS_END, +}; + +namespace { + +/** + * Holder for the caller of ::make()'s callback function and context pointer + */ +class NativeHolder { +public: + NativeHolder(NativeFunction func, void* ctx) : _func(func), _ctx(ctx) {} + + NativeFunction _func; + void* _ctx; +}; + +NativeHolder* getHolder(JS::CallArgs args) { + return static_cast(JS_GetPrivate(&args.callee())); +} + +} // namespace + +void NativeFunctionInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + scope->getNativeFunctionProto().newObject(args.rval()); +} + +void NativeFunctionInfo::call(JSContext* cx, JS::CallArgs args) { + auto holder = getHolder(args); + + BSONObjBuilder bob; + + for (unsigned i = 0; i < args.length(); i++) { + // 11 is enough here. unsigned's are only 32 bits, and 1 << 32 is only + // 10 decimal digits. +1 for the null and we're only at 11. + char buf[11]; + std::sprintf(buf, "%i", i); + + ValueWriter(cx, args.get(i)).writeThis(&bob, buf); + } + + BSONObj out = holder->_func(bob.obj(), holder->_ctx); + + ValueReader(cx, args.rval()).fromBSONElement(out.firstElement(), false); +} + +void NativeFunctionInfo::finalize(JSFreeOp* fop, JSObject* obj) { + auto holder = static_cast(JS_GetPrivate(obj)); + + if (holder) + delete holder; +} + +void NativeFunctionInfo::Functions::toString(JSContext* cx, JS::CallArgs args) { + ObjectWrapper o(cx, args.thisv()); + + str::stream ss; + ss << "[native code]"; + + ValueReader(cx, args.rval()).fromStringData(ss.operator std::string()); +} + + +void NativeFunctionInfo::make(JSContext* cx, + JS::MutableHandleObject obj, + NativeFunction function, + void* data) { + auto scope = getScope(cx); + + scope->getNativeFunctionProto().newInstance(obj); + + JS_SetPrivate(obj, new NativeHolder(function, data)); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/nativefunction.h b/src/mongo/scripting/mozjs/nativefunction.h new file mode 100644 index 00000000000..0467b2c4743 --- /dev/null +++ b/src/mongo/scripting/mozjs/nativefunction.h @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/engine.h" +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * Wrapper for JS Interpreter agnostic functions. Think mapReduce, or any use + * case that can tolerate automatic json <-> bson translation. + * + * The business end of the shim methods comes via ::call(). These types are + * invokable as js functions, with a little bit of automatic translation for + * arguments. + * + * This inherits from the global Function type. + * + * Also note that installType is private. So you can only get NativeFunctions + * in JS via ::make() from C++. + */ +struct NativeFunctionInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void call(JSContext* cx, JS::CallArgs args); + static void finalize(JSFreeOp* fop, JSObject* obj); + + static const char* const inheritFrom; + static const char* const className; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; + static const InstallType installType = InstallType::Private; + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(toString); + }; + + static const JSFunctionSpec methods[2]; + + static void make(JSContext* cx, + JS::MutableHandleObject obj, + NativeFunction function, + void* data); +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/numberint.cpp b/src/mongo/scripting/mozjs/numberint.cpp new file mode 100644 index 00000000000..1704154ac74 --- /dev/null +++ b/src/mongo/scripting/mozjs/numberint.cpp @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/numberint.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec NumberIntInfo::methods[4] = { + MONGO_ATTACH_JS_FUNCTION(toNumber), + MONGO_ATTACH_JS_FUNCTION(toString), + MONGO_ATTACH_JS_FUNCTION(valueOf), + JS_FS_END, +}; + +const char* const NumberIntInfo::className = "NumberInt"; + +void NumberIntInfo::finalize(JSFreeOp* fop, JSObject* obj) { + auto x = static_cast(JS_GetPrivate(obj)); + + if (x) + delete x; +} + +int NumberIntInfo::ToNumberInt(JSContext* cx, JS::HandleValue thisv) { + auto x = static_cast(JS_GetPrivate(thisv.toObjectOrNull())); + + return x ? *x : 0; +} + +int NumberIntInfo::ToNumberInt(JSContext* cx, JS::HandleObject thisv) { + auto x = static_cast(JS_GetPrivate(thisv)); + + return x ? *x : 0; +} + +void NumberIntInfo::Functions::valueOf(JSContext* cx, JS::CallArgs args) { + int out = NumberIntInfo::ToNumberInt(cx, args.thisv()); + + args.rval().setInt32(out); +} + +void NumberIntInfo::Functions::toNumber(JSContext* cx, JS::CallArgs args) { + valueOf(cx, args); +} + +void NumberIntInfo::Functions::toString(JSContext* cx, JS::CallArgs args) { + int val = NumberIntInfo::ToNumberInt(cx, args.thisv()); + + str::stream ss; + ss << "NumberInt(" << val << ")"; + + ValueReader(cx, args.rval()).fromStringData(ss.operator std::string()); +} + +void NumberIntInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + JS::RootedObject thisv(cx); + + scope->getNumberIntProto().newObject(&thisv); + + int32_t x = 0; + + if (args.length() == 0) { + // Do nothing + } else if (args.length() == 1) { + x = ValueWriter(cx, args.get(0)).toInt32(); + } else { + uasserted(ErrorCodes::BadValue, "NumberInt takes 0 or 1 arguments"); + } + + JS_SetPrivate(thisv, new int(x)); + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/numberint.h b/src/mongo/scripting/mozjs/numberint.h new file mode 100644 index 00000000000..378c3a0d57e --- /dev/null +++ b/src/mongo/scripting/mozjs/numberint.h @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "NumberInt" Javascript object. + * + * Wraps an actual c++ 'int' as its private member + */ +struct NumberIntInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + static void finalize(JSFreeOp* fop, JSObject* obj); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(toNumber); + MONGO_DEFINE_JS_FUNCTION(toString); + MONGO_DEFINE_JS_FUNCTION(valueOf); + }; + + static const JSFunctionSpec methods[4]; + + static const char* const className; + static const unsigned classFlags = JSCLASS_HAS_PRIVATE; + + static int ToNumberInt(JSContext* cx, JS::HandleObject object); + static int ToNumberInt(JSContext* cx, JS::HandleValue value); +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/numberlong.cpp b/src/mongo/scripting/mozjs/numberlong.cpp new file mode 100644 index 00000000000..f8955689c9e --- /dev/null +++ b/src/mongo/scripting/mozjs/numberlong.cpp @@ -0,0 +1,164 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/numberlong.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/util/mongoutils/str.h" +#include "mongo/util/text.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec NumberLongInfo::methods[4] = { + MONGO_ATTACH_JS_FUNCTION(toNumber), + MONGO_ATTACH_JS_FUNCTION(toString), + MONGO_ATTACH_JS_FUNCTION(valueOf), + JS_FS_END, +}; + +const char* const NumberLongInfo::className = "NumberLong"; + +namespace { +const char* const kTop = "top"; +const char* const kBottom = "bottom"; +const char* const kFloatApprox = "floatApprox"; +} // namespace + +long long NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleValue thisv) { + JS::RootedObject obj(cx, thisv.toObjectOrNull()); + return ToNumberLong(cx, obj); +} + +long long NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleObject thisv) { + ObjectWrapper o(cx, thisv); + + if (!o.hasField(kTop)) { + if (!o.hasField(kFloatApprox)) + uasserted(ErrorCodes::InternalError, "No top and no floatApprox fields"); + + return o.getNumber(kFloatApprox); + } + + if (!o.hasField(kBottom)) + uasserted(ErrorCodes::InternalError, "top but no bottom field"); + + return ((unsigned long long)((long long)o.getNumber(kTop) << 32) + + (unsigned)(o.getNumber(kBottom))); +} + +void NumberLongInfo::Functions::valueOf(JSContext* cx, JS::CallArgs args) { + long long out = NumberLongInfo::ToNumberLong(cx, args.thisv()); + + args.rval().setDouble(out); +} + +void NumberLongInfo::Functions::toNumber(JSContext* cx, JS::CallArgs args) { + valueOf(cx, args); +} + +void NumberLongInfo::Functions::toString(JSContext* cx, JS::CallArgs args) { + str::stream ss; + + long long val = NumberLongInfo::ToNumberLong(cx, args.thisv()); + + const long long limit = 2LL << 30; + + if (val <= -limit || limit <= val) + ss << "NumberLong(\"" << val << "\")"; + else + ss << "NumberLong(" << val << ")"; + + ValueReader(cx, args.rval()).fromStringData(ss.operator std::string()); +} + +void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) { + uassert(ErrorCodes::BadValue, + "NumberLong needs 0, 1 or 3 arguments", + args.length() == 0 || args.length() == 1 || args.length() == 3); + + auto scope = getScope(cx); + + JS::RootedObject thisv(cx); + + scope->getNumberLongProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + JS::RootedValue floatApprox(cx); + JS::RootedValue top(cx); + JS::RootedValue bottom(cx); + + if (args.length() == 0) { + o.setNumber(kFloatApprox, 0); + } else if (args.length() == 1) { + if (args.get(0).isNumber()) { + o.setValue(kFloatApprox, args.get(0)); + } else { + std::string str = ValueWriter(cx, args.get(0)).toString(); + + unsigned long long val = parseLL(str.c_str()); + + // values above 2^53 are not accurately represented in JS + if ((long long)val == (long long)(double)(long long)(val) && + val < 9007199254740992ULL) { + o.setNumber(kFloatApprox, val); + } else { + o.setNumber(kFloatApprox, val); + o.setNumber(kTop, val >> 32); + o.setNumber(kBottom, val & 0x00000000ffffffff); + } + } + } else { + if (!args.get(0).isNumber()) + uasserted(ErrorCodes::BadValue, "floatApprox must be a number"); + + if (!args.get(1).isNumber() || + args.get(1).toNumber() != + static_cast(static_cast(args.get(1).toNumber()))) + uasserted(ErrorCodes::BadValue, "top must be a 32 bit unsigned number"); + + if (!args.get(2).isNumber() || + args.get(2).toNumber() != + static_cast(static_cast(args.get(2).toNumber()))) + uasserted(ErrorCodes::BadValue, "bottom must be a 32 bit unsigned number"); + + o.setValue(kFloatApprox, args.get(0)); + o.setValue(kTop, args.get(1)); + o.setValue(kBottom, args.get(2)); + } + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/numberlong.h b/src/mongo/scripting/mozjs/numberlong.h new file mode 100644 index 00000000000..bedfa558b2b --- /dev/null +++ b/src/mongo/scripting/mozjs/numberlong.h @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "NumberLong" Javascript object. + * + * Represents a 64 integer with a JS representation like: + * + * { + * top : Double, + * bottom : Double, + * floatApprox : Double, + * } + * + * Where top is the high 32 bits, bottom the low 32 bits and floatApprox a + * floating point approximation. + */ +struct NumberLongInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(toNumber); + MONGO_DEFINE_JS_FUNCTION(toString); + MONGO_DEFINE_JS_FUNCTION(valueOf); + }; + + static const JSFunctionSpec methods[4]; + + static const char* const className; + + static long long ToNumberLong(JSContext* cx, JS::HandleObject object); + static long long ToNumberLong(JSContext* cx, JS::HandleValue value); +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/object.cpp b/src/mongo/scripting/mozjs/object.cpp new file mode 100644 index 00000000000..a90bec36a72 --- /dev/null +++ b/src/mongo/scripting/mozjs/object.cpp @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/object.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec ObjectInfo::methods[3] = { + MONGO_ATTACH_JS_FUNCTION(bsonsize), MONGO_ATTACH_JS_FUNCTION(invalidForStorage), JS_FS_END, +}; + +const char* const ObjectInfo::className = "Object"; + +void ObjectInfo::Functions::bsonsize(JSContext* cx, JS::CallArgs args) { + if (args.length() != 1) + uasserted(ErrorCodes::BadValue, "bsonsize needs 1 argument"); + + if (args.get(0).isNull()) { + args.rval().setInt32(0); + return; + } + + if (!args.get(0).isObject()) + uasserted(ErrorCodes::BadValue, "argument to bsonsize has to be an object"); + + args.rval().setInt32(ValueWriter(cx, args.get(0)).toBSON().objsize()); +} + +void ObjectInfo::Functions::invalidForStorage(JSContext* cx, JS::CallArgs args) { + if (args.length() != 1) + uasserted(ErrorCodes::BadValue, "invalidForStorage needs 1 argument"); + + if (args.get(0).isNull()) { + args.rval().setNull(); + return; + } + + if (!args.get(0).isObject()) + uasserted(ErrorCodes::BadValue, "argument to invalidForStorage has to be an object"); + + Status validForStorage = ValueWriter(cx, args.get(0)).toBSON().storageValid(true); + if (validForStorage.isOK()) { + args.rval().setNull(); + return; + } + + std::string errmsg = str::stream() << validForStorage.codeString() << ": " + << validForStorage.reason(); + + ValueReader(cx, args.rval()).fromStringData(errmsg); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/object.h b/src/mongo/scripting/mozjs/object.h new file mode 100644 index 00000000000..70f475c3f0e --- /dev/null +++ b/src/mongo/scripting/mozjs/object.h @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * Adds some methods onto the JS type "Object" + * + * Note that this installs "overNative", so we don't actually do anything other + * than layer a couple of our own functions on top of the existing prototype. + */ +struct ObjectInfo : public BaseInfo { + struct Functions { + MONGO_DEFINE_JS_FUNCTION(bsonsize); + MONGO_DEFINE_JS_FUNCTION(invalidForStorage); + }; + + static const JSFunctionSpec methods[3]; + + static const char* const className; + + static const InstallType installType = InstallType::OverNative; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/objectwrapper.cpp b/src/mongo/scripting/mozjs/objectwrapper.cpp new file mode 100644 index 00000000000..0450eeee42b --- /dev/null +++ b/src/mongo/scripting/mozjs/objectwrapper.cpp @@ -0,0 +1,385 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/objectwrapper.h" + +#include "mongo/base/error_codes.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/scripting/mozjs/idwrapper.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" + +namespace mongo { +namespace mozjs { + +void ObjectWrapper::Key::get(JSContext* cx, JS::HandleObject o, JS::MutableHandleValue value) { + switch (_type) { + case Type::Field: + if (JS_GetProperty(cx, o, _field, value)) + return; + break; + case Type::Index: + if (JS_GetElement(cx, o, _idx, value)) + return; + break; + case Type::Id: { + JS::RootedId id(cx, _id); + + if (JS_GetPropertyById(cx, o, id, value)) + return; + break; + } + } + + throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to get value on a JSObject"); +} + +void ObjectWrapper::Key::set(JSContext* cx, JS::HandleObject o, JS::HandleValue value) { + switch (_type) { + case Type::Field: + if (JS_SetProperty(cx, o, _field, value)) + return; + break; + case Type::Index: + if (JS_SetElement(cx, o, _idx, value)) + return; + break; + case Type::Id: { + JS::RootedId id(cx, _id); + + if (JS_SetPropertyById(cx, o, id, value)) + return; + break; + } + } + + throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to set value on a JSObject"); +} + +void ObjectWrapper::Key::define(JSContext* cx, + JS::HandleObject o, + JS::HandleValue value, + unsigned attrs) { + switch (_type) { + case Type::Field: + if (JS_DefineProperty(cx, o, _field, value, attrs)) + return; + break; + case Type::Index: + if (JS_DefineElement(cx, o, _idx, value, attrs)) + return; + break; + case Type::Id: { + JS::RootedId id(cx, _id); + + if (JS_DefinePropertyById(cx, o, id, value, attrs)) + return; + break; + } + } + + throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to define value on a JSObject"); +} + +bool ObjectWrapper::Key::has(JSContext* cx, JS::HandleObject o) { + bool has; + + switch (_type) { + case Type::Field: + if (JS_HasProperty(cx, o, _field, &has)) + return has; + break; + case Type::Index: + if (JS_HasElement(cx, o, _idx, &has)) + return has; + break; + case Type::Id: { + JS::RootedId id(cx, _id); + + if (JS_HasPropertyById(cx, o, id, &has)) + return has; + break; + } + } + + throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to has value on a JSObject"); +} + +void ObjectWrapper::Key::del(JSContext* cx, JS::HandleObject o) { + switch (_type) { + case Type::Field: + if (JS_DeleteProperty(cx, o, _field)) + return; + break; + case Type::Index: + if (JS_DeleteElement(cx, o, _idx)) + return; + break; + case Type::Id: { + JS::RootedId id(cx, _id); + + // For some reason JS_DeletePropertyById doesn't link + if (JS_DeleteProperty(cx, o, IdWrapper(cx, id).toString().c_str())) + return; + break; + } + } + + throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to delete value on a JSObject"); +} + +std::string ObjectWrapper::Key::toString(JSContext* cx) { + switch (_type) { + case Type::Field: + return _field; + case Type::Index: + return std::to_string(_idx); + case Type::Id: { + JS::RootedId id(cx, _id); + return IdWrapper(cx, id).toString(); + } + } + + throwCurrentJSException( + cx, ErrorCodes::InternalError, "Failed to toString a ObjectWrapper::Key"); +} + +ObjectWrapper::ObjectWrapper(JSContext* cx, JS::HandleObject obj, int depth) + : _context(cx), _object(cx, obj), _depth(depth) {} + +ObjectWrapper::ObjectWrapper(JSContext* cx, JS::HandleValue value, int depth) + : _context(cx), _object(cx, value.toObjectOrNull()), _depth(depth) {} + +double ObjectWrapper::getNumber(Key key) { + JS::RootedValue x(_context); + getValue(key, &x); + + return ValueWriter(_context, x).toNumber(); +} + +int ObjectWrapper::getNumberInt(Key key) { + JS::RootedValue x(_context); + getValue(key, &x); + + return ValueWriter(_context, x).toInt32(); +} + +long long ObjectWrapper::getNumberLongLong(Key key) { + JS::RootedValue x(_context); + getValue(key, &x); + + return ValueWriter(_context, x).toInt64(); +} + +std::string ObjectWrapper::getString(Key key) { + JS::RootedValue x(_context); + getValue(key, &x); + + return ValueWriter(_context, x).toString(); +} + +bool ObjectWrapper::getBoolean(Key key) { + JS::RootedValue x(_context); + getValue(key, &x); + + return ValueWriter(_context, x).toBoolean(); +} + +BSONObj ObjectWrapper::getObject(Key key) { + JS::RootedValue x(_context); + getValue(key, &x); + + return ValueWriter(_context, x, _depth).toBSON(); +} + +void ObjectWrapper::getValue(Key key, JS::MutableHandleValue value) { + key.get(_context, _object, value); +} + +void ObjectWrapper::setNumber(Key key, double val) { + JS::RootedValue jsValue(_context); + jsValue.setDouble(val); + + setValue(key, jsValue); +} + +void ObjectWrapper::setString(Key key, StringData val) { + JS::RootedValue jsValue(_context); + ValueReader(_context, &jsValue).fromStringData(val); + + setValue(key, jsValue); +} + +void ObjectWrapper::setBoolean(Key key, bool val) { + JS::RootedValue jsValue(_context); + jsValue.setBoolean(val); + + setValue(key, jsValue); +} + +void ObjectWrapper::setBSONElement(Key key, const BSONElement& elem, bool readOnly) { + JS::RootedValue value(_context); + ValueReader(_context, &value, _depth).fromBSONElement(elem, readOnly); + + setValue(key, value); +} + +void ObjectWrapper::setBSON(Key key, const BSONObj& obj, bool readOnly) { + JS::RootedValue value(_context); + ValueReader(_context, &value, _depth).fromBSON(obj, readOnly); + + setValue(key, value); +} + +void ObjectWrapper::setValue(Key key, JS::HandleValue val) { + key.set(_context, _object, val); +} + +void ObjectWrapper::setObject(Key key, JS::HandleObject object) { + JS::RootedValue value(_context); + value.setObjectOrNull(object); + + setValue(key, value); +} + +void ObjectWrapper::defineProperty(Key key, JS::HandleValue val, unsigned attrs) { + key.define(_context, _object, val, attrs); +} + +void ObjectWrapper::deleteProperty(Key key) { + key.del(_context, _object); +} + +int ObjectWrapper::type(Key key) { + JS::RootedValue x(_context); + getValue(key, &x); + + return ValueWriter(_context, x).type(); +} + +void ObjectWrapper::rename(Key from, const char* to) { + JS::RootedValue value(_context); + + JS::RootedValue undefValue(_context); + undefValue.setUndefined(); + + getValue(from, &value); + + setValue(to, value); + setValue(from, undefValue); +} + +bool ObjectWrapper::hasField(Key key) { + return key.has(_context, _object); +} + +void ObjectWrapper::callMethod(const char* field, + const JS::HandleValueArray& args, + JS::MutableHandleValue out) { + if (JS::Call(_context, _object, field, args, out)) + return; + + throwCurrentJSException(_context, ErrorCodes::InternalError, "Failed to call method"); +} + +void ObjectWrapper::callMethod(const char* field, JS::MutableHandleValue out) { + JS::AutoValueVector args(_context); + + callMethod(field, args, out); +} + +void ObjectWrapper::callMethod(JS::HandleValue fun, + const JS::HandleValueArray& args, + JS::MutableHandleValue out) { + if (JS::Call(_context, _object, fun, args, out)) + return; + + throwCurrentJSException(_context, ErrorCodes::InternalError, "Failed to call method"); +} + +void ObjectWrapper::callMethod(JS::HandleValue fun, JS::MutableHandleValue out) { + JS::AutoValueVector args(_context); + + callMethod(fun, args, out); +} + +void ObjectWrapper::writeThis(BSONObjBuilder* b) { + auto scope = getScope(_context); + + BSONObj* originalBSON = nullptr; + if (scope->getBsonProto().instanceOf(_object)) { + bool altered; + + std::tie(originalBSON, altered) = BSONInfo::originalBSON(_context, _object); + + if (!altered) { + b->appendElements(*originalBSON); + return; + } + } + + // We special case the _id field in top-level objects and move it to the front. + // This matches other drivers behavior and makes finding the _id field quicker in BSON. + if (_depth == 0 && hasField("_id")) { + _writeField(b, "_id", originalBSON); + } + + enumerate([&](JS::HandleId id) { + JS::RootedValue x(_context); + + IdWrapper idw(_context, id); + + if (_depth == 0 && idw.isString() && idw.equals("_id")) + return; + + _writeField(b, id, originalBSON); + }); + + const int sizeWithEOO = b->len() + 1 /*EOO*/ - 4 /*BSONObj::Holder ref count*/; + uassert(17260, + str::stream() << "Converting from JavaScript to BSON failed: " + << "Object size " << sizeWithEOO << " exceeds limit of " + << BSONObjMaxInternalSize << " bytes.", + sizeWithEOO <= BSONObjMaxInternalSize); +} + +void ObjectWrapper::_writeField(BSONObjBuilder* b, Key key, BSONObj* originalParent) { + JS::RootedValue value(_context); + key.get(_context, _object, &value); + + ValueWriter x(_context, value, _depth); + x.setOriginalBSON(originalParent); + + x.writeThis(b, key.toString(_context)); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/objectwrapper.h b/src/mongo/scripting/mozjs/objectwrapper.h new file mode 100644 index 00000000000..efac05a1e71 --- /dev/null +++ b/src/mongo/scripting/mozjs/objectwrapper.h @@ -0,0 +1,181 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include +#include + +#include "mongo/scripting/mozjs/exception.h" + +namespace mongo { + +class BSONObj; +class BSONObjBuilder; +class BSONElement; + +namespace mozjs { + +class MozJSImplScope; + +/** + * Wraps JSObject's with helpers for accessing their properties + * + * This wraps a RootedObject, so should only be allocated on the stack and is + * not movable or copyable + */ +class ObjectWrapper { +public: + /** + * Helper subclass that provides some easy boilerplate for accessing + * properties by string, index or id. + */ + class Key { + friend class ObjectWrapper; + + enum class Type : char { + Field, + Index, + Id, + }; + + public: + Key(const char* field) : _field(field), _type(Type::Field) {} + Key(uint32_t idx) : _idx(idx), _type(Type::Index) {} + Key(JS::HandleId id) : _id(id), _type(Type::Id) {} + + private: + void get(JSContext* cx, JS::HandleObject o, JS::MutableHandleValue value); + void set(JSContext* cx, JS::HandleObject o, JS::HandleValue value); + bool has(JSContext* cx, JS::HandleObject o); + void define(JSContext* cx, JS::HandleObject o, JS::HandleValue value, unsigned attrs); + void del(JSContext* cx, JS::HandleObject o); + std::string toString(JSContext* cx); + + union { + const char* _field; + uint32_t _idx; + jsid _id; + }; + Type _type; + }; + + /** + * The depth parameter here allows us to detect overly nested or circular + * objects and bail without blowing the stack. + */ + ObjectWrapper(JSContext* cx, JS::HandleObject obj, int depth = 0); + ObjectWrapper(JSContext* cx, JS::HandleValue value, int depth = 0); + + double getNumber(Key key); + int getNumberInt(Key key); + long long getNumberLongLong(Key key); + std::string getString(Key key); + bool getBoolean(Key key); + BSONObj getObject(Key key); + void getValue(Key key, JS::MutableHandleValue value); + + void setNumber(Key key, double val); + void setString(Key key, StringData val); + void setBoolean(Key key, bool val); + void setBSONElement(Key key, const BSONElement& elem, bool readOnly); + void setBSON(Key key, const BSONObj& obj, bool readOnly); + void setValue(Key key, JS::HandleValue value); + void setObject(Key key, JS::HandleObject value); + + /** + * See JS_DefineProperty for what sort of attributes might be useful + */ + void defineProperty(Key key, JS::HandleValue value, unsigned attrs); + + void deleteProperty(Key key); + + /** + * Returns the bson type of the property + */ + int type(Key key); + + void rename(Key key, const char* to); + + bool hasField(Key key); + + void callMethod(const char* name, const JS::HandleValueArray& args, JS::MutableHandleValue out); + void callMethod(const char* name, JS::MutableHandleValue out); + void callMethod(JS::HandleValue fun, + const JS::HandleValueArray& args, + JS::MutableHandleValue out); + void callMethod(JS::HandleValue fun, JS::MutableHandleValue out); + + /** + * Safely enumerates fields in the object, invoking a callback for each id + */ + template + void enumerate(T&& callback) { + JS::AutoIdArray ids(_context, JS_Enumerate(_context, _object)); + + if (!ids) + throwCurrentJSException( + _context, ErrorCodes::JSInterpreterFailure, "Failure to enumerate object"); + + JS::RootedId rid(_context); + for (size_t i = 0; i < ids.length(); ++i) { + rid.set(ids[i]); + callback(rid); + } + } + + /** + * concatenates all of the fields in the object into the associated builder + */ + void writeThis(BSONObjBuilder* b); + + JS::HandleObject thisv() { + return _object; + } + +private: + /** + * writes the field "key" into the associated builder + * + * optional originalBSON is used to track updates to types (NumberInt + * overwritten by a float, but coercible to the original type, etc.) + */ + void _writeField(BSONObjBuilder* b, Key key, BSONObj* originalBSON); + + JSContext* _context; + JS::RootedObject _object; + + /** + * The depth of an object wrapper has to do with how many parents it has. + * Used to avoid circular object graphs and associate stack smashing. + */ + int _depth; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/oid.cpp b/src/mongo/scripting/mozjs/oid.cpp new file mode 100644 index 00000000000..cf391bf03e8 --- /dev/null +++ b/src/mongo/scripting/mozjs/oid.cpp @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/oid.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/stdx/memory.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace mozjs { + +const JSFunctionSpec OIDInfo::methods[2] = { + MONGO_ATTACH_JS_FUNCTION(toString), JS_FS_END, +}; + +const char* const OIDInfo::className = "ObjectId"; + +void OIDInfo::Functions::toString(JSContext* cx, JS::CallArgs args) { + ObjectWrapper o(cx, args.thisv()); + + if (!o.hasField("str")) + uasserted(ErrorCodes::BadValue, "Must have \"str\" field"); + + JS::RootedValue value(cx); + o.getValue("str", &value); + + std::string str = str::stream() << "ObjectId(\"" << ValueWriter(cx, value).toString() << "\")"; + + ValueReader(cx, args.rval()).fromStringData(str); +} + +void OIDInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + auto oid = stdx::make_unique(); + + if (args.length() == 0) { + oid->init(); + } else { + auto str = ValueWriter(cx, args.get(0)).toString(); + + Scope::validateObjectIdString(str); + oid->init(str); + } + + JS::RootedObject thisv(cx); + scope->getOidProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + o.setString("str", oid->toString()); + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/oid.h b/src/mongo/scripting/mozjs/oid.h new file mode 100644 index 00000000000..109282be882 --- /dev/null +++ b/src/mongo/scripting/mozjs/oid.h @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "ObjectId" Javascript object. + * + * Holds a private bson OID + */ +struct OIDInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + + struct Functions { + MONGO_DEFINE_JS_FUNCTION(toString); + }; + + static const JSFunctionSpec methods[2]; + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/proxyscope.cpp b/src/mongo/scripting/mozjs/proxyscope.cpp new file mode 100644 index 00000000000..665d249f4c5 --- /dev/null +++ b/src/mongo/scripting/mozjs/proxyscope.cpp @@ -0,0 +1,318 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/proxyscope.h" + +#include "mongo/db/client.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/service_context.h" +#include "mongo/scripting/mozjs/implscope.h" + +namespace mongo { +namespace mozjs { + +MozJSProxyScope::MozJSProxyScope(MozJSScriptEngine* engine) + : _engine(engine), + _implScope(nullptr), + _mutex(), + _state(State::Idle), + _status(Status::OK()), + _condvar(), + _thread(&MozJSProxyScope::implThread, this) { + // Test the child on startup to make sure it's awake and that the + // implementation scope sucessfully constructed. + try { + runOnImplThread([] {}); + } catch (...) { + shutdownThread(); + throw; + } +} + +MozJSProxyScope::~MozJSProxyScope() { + DESTRUCTOR_GUARD(kill(); shutdownThread();); +} + +void MozJSProxyScope::init(const BSONObj* data) { + runOnImplThread([&] { _implScope->init(data); }); +} + +void MozJSProxyScope::reset() { + runOnImplThread([&] { _implScope->reset(); }); +} + +bool MozJSProxyScope::isKillPending() const { + return _implScope->isKillPending(); +} + +void MozJSProxyScope::registerOperation(OperationContext* txn) { + runOnImplThread([&] { _implScope->registerOperation(txn); }); +} + +void MozJSProxyScope::unregisterOperation() { + runOnImplThread([&] { _implScope->unregisterOperation(); }); +} + +void MozJSProxyScope::localConnectForDbEval(OperationContext* txn, const char* dbName) { + runOnImplThread([&] { _implScope->localConnectForDbEval(txn, dbName); }); +} + +void MozJSProxyScope::externalSetup() { + runOnImplThread([&] { _implScope->externalSetup(); }); +} + +std::string MozJSProxyScope::getError() { + std::string out; + runOnImplThread([&] { out = _implScope->getError(); }); + return out; +} + +/** + * This is an artifact of how out of memory errors were communicated in V8. We + * just throw out of memory errors from spidermonkey when we get them, rather + * than setting a flag and having to pick them up here. + */ +bool MozJSProxyScope::hasOutOfMemoryException() { + return false; +} + +void MozJSProxyScope::gc() { + _implScope->gc(); +} + +double MozJSProxyScope::getNumber(const char* field) { + double out; + runOnImplThread([&] { out = _implScope->getNumber(field); }); + return out; +} + +int MozJSProxyScope::getNumberInt(const char* field) { + int out; + runOnImplThread([&] { out = _implScope->getNumberInt(field); }); + return out; +} + +long long MozJSProxyScope::getNumberLongLong(const char* field) { + long long out; + runOnImplThread([&] { out = _implScope->getNumberLongLong(field); }); + return out; +} + +std::string MozJSProxyScope::getString(const char* field) { + std::string out; + runOnImplThread([&] { out = _implScope->getString(field); }); + return out; +} + +bool MozJSProxyScope::getBoolean(const char* field) { + bool out; + runOnImplThread([&] { out = _implScope->getBoolean(field); }); + return out; +} + +BSONObj MozJSProxyScope::getObject(const char* field) { + BSONObj out; + runOnImplThread([&] { out = _implScope->getObject(field); }); + return out; +} + +void MozJSProxyScope::setNumber(const char* field, double val) { + runOnImplThread([&] { _implScope->setNumber(field, val); }); +} + +void MozJSProxyScope::setString(const char* field, StringData val) { + runOnImplThread([&] { _implScope->setString(field, val); }); +} + +void MozJSProxyScope::setBoolean(const char* field, bool val) { + runOnImplThread([&] { _implScope->setBoolean(field, val); }); +} + +void MozJSProxyScope::setElement(const char* field, const BSONElement& e) { + runOnImplThread([&] { _implScope->setElement(field, e); }); +} + +void MozJSProxyScope::setObject(const char* field, const BSONObj& obj, bool readOnly) { + runOnImplThread([&] { _implScope->setObject(field, obj, readOnly); }); +} + +void MozJSProxyScope::setFunction(const char* field, const char* code) { + runOnImplThread([&] { _implScope->setFunction(field, code); }); +} + +int MozJSProxyScope::type(const char* field) { + int out; + runOnImplThread([&] { out = _implScope->type(field); }); + return out; +} + +void MozJSProxyScope::rename(const char* from, const char* to) { + runOnImplThread([&] { _implScope->rename(from, to); }); +} + +int MozJSProxyScope::invoke(ScriptingFunction func, + const BSONObj* argsObject, + const BSONObj* recv, + int timeoutMs, + bool ignoreReturn, + bool readOnlyArgs, + bool readOnlyRecv) { + int out; + runOnImplThread([&] { + out = _implScope->invoke( + func, argsObject, recv, timeoutMs, ignoreReturn, readOnlyArgs, readOnlyRecv); + }); + + return out; +} + +bool MozJSProxyScope::exec(StringData code, + const std::string& name, + bool printResult, + bool reportError, + bool assertOnError, + int timeoutMs) { + bool out; + runOnImplThread([&] { + out = _implScope->exec(code, name, printResult, reportError, assertOnError, timeoutMs); + }); + return out; +} + +void MozJSProxyScope::injectNative(const char* field, NativeFunction func, void* data) { + runOnImplThread([&] { _implScope->injectNative(field, func, data); }); +} + +ScriptingFunction MozJSProxyScope::_createFunction(const char* raw, + ScriptingFunction functionNumber) { + ScriptingFunction out; + runOnImplThread([&] { out = _implScope->_createFunction(raw, functionNumber); }); + return out; +} + +OperationContext* MozJSProxyScope::getOpContext() const { + return _implScope->getOpContext(); +} + +void MozJSProxyScope::kill() { + _implScope->kill(); +} + +/** + * Invokes a function on the implementation thread + * + * It does this by serializing the invocation through a stdx::function and + * capturing any exceptions through _status. + * + * We transition: + * + * Idle -> ProxyRequest -> ImplResponse -> Idle + */ +void MozJSProxyScope::runOnImplThread(std::function f) { + stdx::unique_lock lk(_mutex); + _function = std::move(f); + + invariant(_state == State::Idle); + _state = State::ProxyRequest; + + _condvar.notify_one(); + + _condvar.wait(lk, [this] { return _state == State::ImplResponse; }); + + _state = State::Idle; + + // Clear the _status state and throw it if necessary + auto status = std::move(_status); + uassertStatusOK(status); +} + +void MozJSProxyScope::shutdownThread() { + { + stdx::lock_guard lk(_mutex); + + invariant(_state == State::Idle); + + _state = State::Shutdown; + } + + _condvar.notify_one(); + + _thread.join(); +} + +/** + * The main loop for the implementation thread + * + * This owns the actual implementation scope (which needs to be created on this + * child thread) and has essentially two transition paths: + * + * Standard: ProxyRequest -> ImplResponse + * Invoke _function. Serialize exceptions to _status. + * + * Shutdown: Shutdown -> _ + * break out of the loop and return. + */ +void MozJSProxyScope::implThread() { + if (hasGlobalServiceContext()) + Client::initThread("js"); + + std::unique_ptr scope; + + // This will leave _status set for the first noop runOnImplThread(), which + // captures the startup exception that way + try { + scope.reset(new MozJSImplScope(_engine)); + _implScope = scope.get(); + } catch (...) { + _status = exceptionToStatus(); + } + + while (true) { + stdx::unique_lock lk(_mutex); + _condvar.wait( + lk, [this] { return _state == State::ProxyRequest || _state == State::Shutdown; }); + + if (_state == State::Shutdown) + break; + + try { + _function(); + } catch (...) { + _status = exceptionToStatus(); + } + + _state = State::ImplResponse; + + _condvar.notify_one(); + } +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/proxyscope.h b/src/mongo/scripting/mozjs/proxyscope.h new file mode 100644 index 00000000000..8b874360a68 --- /dev/null +++ b/src/mongo/scripting/mozjs/proxyscope.h @@ -0,0 +1,195 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/client/dbclientcursor.h" +#include "mongo/scripting/mozjs/engine.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/stdx/functional.h" +#include "mongo/stdx/mutex.h" +#include "mongo/stdx/thread.h" + +namespace mongo { +namespace mozjs { + +class MozJSImplScope; + +/** + * Proxies all methods on the implementation scope over a side channel that + * allows the SpiderMonkey runtime to operate entirely on one thread. This + * implements the public scope interface and is the right way to talk to an + * implementation scope from multiple threads. + * + * In terms of implementation, it does most of it's heavy lifting through a + * stdx::function. The proxy scope owns an implementation scope transitively + * through the thread it owns. They communicate by setting a variable, then + * signaling each other. That communication has to work, there's no fallback + * for timing out. + * + * There are probably performance gains to be had from making + * the argument capture and method dispatch explicit, but I'll wait until we've + * measured it before bothering. + * + * See mongo::Scope for details on all of the overridden functions + * + */ +class MozJSProxyScope final : public Scope { + MONGO_DISALLOW_COPYING(MozJSProxyScope); + + /** + * The FSM is fairly tight: + * + * +----------+ shutdownThread() +--------------------+ + * | Shutdown | <------------------ | Idle | <+ + * +----------+ +--------------------+ | + * | | + * | runOnImplThread() | + * v | + * +--------------------+ | + * | ProxyRequest | | impl -> proxy + * +--------------------+ | + * | | + * | proxy -> impl | + * v | + * +--------------------+ | + * | ImplResponse | -+ + * +--------------------+ + * + * The regular flow: + * - We start at Idle and on the proxy thread. + * - runOnImplThread sets ProxyRequest and notifies the impl thread + * - The impl thread wakes up, invokes _function(), sets ImplResponse and notifies the proxy + * thread + * - The proxy thread wakes up and sets Idle + * + * Shutdown: + * - On destruction, The proxy thread sets Shutdown and notifies the impl thread + * - The impl thread wakes up, breaks out of it's loop and returns + * - The proxy thread joins the impl thread + * + */ + enum class State : char { + Idle, + ProxyRequest, + ImplResponse, + Shutdown, + }; + +public: + MozJSProxyScope(MozJSScriptEngine* engine); + ~MozJSProxyScope(); + + void init(const BSONObj* data) override; + + void reset() override; + + bool isKillPending() const override; + + void registerOperation(OperationContext* txn) override; + + void unregisterOperation() override; + + void localConnectForDbEval(OperationContext* txn, const char* dbName) override; + + void externalSetup() override; + + std::string getError() override; + + bool hasOutOfMemoryException() override; + + void gc() override; + + double getNumber(const char* field) override; + int getNumberInt(const char* field) override; + long long getNumberLongLong(const char* field) override; + std::string getString(const char* field) override; + bool getBoolean(const char* field) override; + BSONObj getObject(const char* field) override; + + void setNumber(const char* field, double val) override; + void setString(const char* field, StringData val) override; + void setBoolean(const char* field, bool val) override; + void setElement(const char* field, const BSONElement& e) override; + void setObject(const char* field, const BSONObj& obj, bool readOnly) override; + void setFunction(const char* field, const char* code) override; + + int type(const char* field) override; + + void rename(const char* from, const char* to) override; + + int invoke(ScriptingFunction func, + const BSONObj* args, + const BSONObj* recv, + int timeoutMs = 0, + bool ignoreReturn = false, + bool readOnlyArgs = false, + bool readOnlyRecv = false) override; + + bool exec(StringData code, + const std::string& name, + bool printResult, + bool reportError, + bool assertOnError, + int timeoutMs) override; + + void injectNative(const char* field, NativeFunction func, void* data = 0) override; + + ScriptingFunction _createFunction(const char* code, + ScriptingFunction functionNumber = 0) override; + + OperationContext* getOpContext() const; + + /** + * Thread safe. Kills the running operation + */ + void kill(); + +private: + void runOnImplThread(std::function f); + void shutdownThread(); + void implThread(); + + MozJSScriptEngine* const _engine; + MozJSImplScope* _implScope; + + /** + * This mutex protects _function, _state and _status as channels for + * function invocation and exception handling + */ + stdx::mutex _mutex; + stdx::function _function; + State _state; + Status _status; + + stdx::condition_variable _condvar; + stdx::thread _thread; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/regexp.cpp b/src/mongo/scripting/mozjs/regexp.cpp new file mode 100644 index 00000000000..b935a9fb354 --- /dev/null +++ b/src/mongo/scripting/mozjs/regexp.cpp @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/regexp.h" + +namespace mongo { +namespace mozjs { + +const char* const RegExpInfo::className = "RegExp"; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/regexp.h b/src/mongo/scripting/mozjs/regexp.h new file mode 100644 index 00000000000..b8553518127 --- /dev/null +++ b/src/mongo/scripting/mozjs/regexp.h @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "RegExp" Javascript object. + * + * Note that this installs over native. We only use this to grab the regexp + * prototype early in case users overwrite it. + */ +struct RegExpInfo : public BaseInfo { + static const char* const className; + + static const InstallType installType = InstallType::OverNative; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/timestamp.cpp b/src/mongo/scripting/mozjs/timestamp.cpp new file mode 100644 index 00000000000..f81cc4050ce --- /dev/null +++ b/src/mongo/scripting/mozjs/timestamp.cpp @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/timestamp.h" + +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/scripting/mozjs/valuewriter.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace mozjs { + +const char* const TimestampInfo::className = "Timestamp"; + +void TimestampInfo::construct(JSContext* cx, JS::CallArgs args) { + auto scope = getScope(cx); + + JS::RootedObject thisv(cx); + scope->getTimestampProto().newObject(&thisv); + ObjectWrapper o(cx, thisv); + + if (args.length() == 0) { + o.setNumber("t", 0); + o.setNumber("i", 0); + } else if (args.length() == 2) { + if (!args.get(0).isNumber()) + uasserted(ErrorCodes::BadValue, "Timestamp time must be a number"); + if (!args.get(1).isNumber()) + uasserted(ErrorCodes::BadValue, "Timestamp increment must be a number"); + + int64_t t = ValueWriter(cx, args.get(0)).toInt64(); + int64_t largestVal = int64_t(Timestamp::max().getSecs()); + if (t > largestVal) + uasserted(ErrorCodes::BadValue, + str::stream() << "The first argument must be in seconds; " << t + << " is too large (max " << largestVal << ")"); + + o.setValue("t", args.get(0)); + o.setValue("i", args.get(1)); + } else { + uasserted(ErrorCodes::BadValue, "Timestamp needs 0 or 2 arguments"); + } + + args.rval().setObjectOrNull(thisv); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/timestamp.h b/src/mongo/scripting/mozjs/timestamp.h new file mode 100644 index 00000000000..1dc4a998420 --- /dev/null +++ b/src/mongo/scripting/mozjs/timestamp.h @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/scripting/mozjs/wraptype.h" + +namespace mongo { +namespace mozjs { + +/** + * The "Timestamp" Javascript object. + * + * Represents a bson timestamp that looks like: + * + * { + * t : Double, + * i : Double, + * } + */ +struct TimestampInfo : public BaseInfo { + static void construct(JSContext* cx, JS::CallArgs args); + + static const char* const className; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/valuereader.cpp b/src/mongo/scripting/mozjs/valuereader.cpp new file mode 100644 index 00000000000..ad45fafc06e --- /dev/null +++ b/src/mongo/scripting/mozjs/valuereader.cpp @@ -0,0 +1,272 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/valuereader.h" + +#include +#include + +#include "mongo/base/error_codes.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/util/base64.h" +#include "mongo/util/log.h" + +namespace mongo { +namespace mozjs { + +ValueReader::ValueReader(JSContext* cx, JS::MutableHandleValue value, int depth) + : _context(cx), _value(value), _depth(depth) {} + +void ValueReader::fromBSONElement(const BSONElement& elem, bool readOnly) { + auto scope = getScope(_context); + + switch (elem.type()) { + case mongo::Code: + scope->newFunction(elem.valueStringData(), _value); + return; + case mongo::CodeWScope: + if (!elem.codeWScopeObject().isEmpty()) + warning() << "CodeWScope doesn't transfer to db.eval"; + scope->newFunction(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1), + _value); + return; + case mongo::Symbol: + case mongo::String: + fromStringData(elem.valueStringData()); + return; + case mongo::jstOID: { + JS::AutoValueArray<1> args(_context); + + ValueReader(_context, args[0]).fromStringData(elem.OID().toString()); + + scope->getOidProto().newInstance(args, _value); + return; + } + case mongo::NumberDouble: + _value.setDouble(elem.Number()); + return; + case mongo::NumberInt: + _value.setInt32(elem.Int()); + return; + case mongo::Array: { + auto arrayPtr = JS_NewArrayObject(_context, 0); + uassert(ErrorCodes::JSInterpreterFailure, "Failed to JS_NewArrayObject", arrayPtr); + JS::RootedObject array(_context, arrayPtr); + + unsigned i = 0; + BSONForEach(subElem, elem.embeddedObject()) { + // We use an unsigned 32 bit integer, so 10 base 10 digits and + // 1 null byte + char str[11]; + sprintf(str, "%i", i++); + JS::RootedValue member(_context); + + ValueReader(_context, &member, _depth + 1).fromBSONElement(subElem, readOnly); + ObjectWrapper(_context, array, _depth + 1).setValue(str, member); + } + _value.setObjectOrNull(array); + return; + } + case mongo::Object: + fromBSON(elem.embeddedObject(), readOnly); + return; + case mongo::Date: + _value.setObjectOrNull( + JS_NewDateObjectMsec(_context, elem.Date().toMillisSinceEpoch())); + return; + case mongo::Bool: + _value.setBoolean(elem.Bool()); + return; + case mongo::EOO: + case mongo::jstNULL: + case mongo::Undefined: + _value.setNull(); + return; + case mongo::RegEx: { + // TODO parse into a custom type that can support any patterns and flags SERVER-9803 + + JS::AutoValueArray<2> args(_context); + + ValueReader(_context, args[0]).fromStringData(elem.regex()); + ValueReader(_context, args[1]).fromStringData(elem.regexFlags()); + + JS::RootedObject obj(_context); + scope->getRegExpProto().newInstance(args, &obj); + + _value.setObjectOrNull(obj); + + return; + } + case mongo::BinData: { + int len; + const char* data = elem.binData(len); + std::stringstream ss; + base64::encode(ss, data, len); + + JS::AutoValueArray<2> args(_context); + + args[0].setInt32(elem.binDataType()); + + ValueReader(_context, args[1]).fromStringData(ss.str()); + + scope->getBinDataProto().newInstance(args, _value); + return; + } + case mongo::bsonTimestamp: { + JS::AutoValueArray<2> args(_context); + + args[0].setDouble(elem.timestampTime().toMillisSinceEpoch() / 1000); + args[1].setNumber(elem.timestampInc()); + + scope->getTimestampProto().newInstance(args, _value); + + return; + } + case mongo::NumberLong: { + unsigned long long nativeUnsignedLong = elem.numberLong(); + // values above 2^53 are not accurately represented in JS + if (static_cast(nativeUnsignedLong) == + static_cast( + static_cast(static_cast(nativeUnsignedLong))) && + nativeUnsignedLong < 9007199254740992ULL) { + JS::AutoValueArray<1> args(_context); + args[0].setNumber(static_cast(static_cast(nativeUnsignedLong))); + + scope->getNumberLongProto().newInstance(args, _value); + } else { + JS::AutoValueArray<3> args(_context); + args[0].setNumber(static_cast(static_cast(nativeUnsignedLong))); + args[1].setDouble(nativeUnsignedLong >> 32); + args[2].setDouble( + static_cast(nativeUnsignedLong & 0x00000000ffffffff)); + scope->getNumberLongProto().newInstance(args, _value); + } + + return; + } + case mongo::MinKey: + scope->getMinKeyProto().newInstance(_value); + return; + case mongo::MaxKey: + scope->getMaxKeyProto().newInstance(_value); + return; + case mongo::DBRef: { + JS::AutoValueArray<1> oidArgs(_context); + ValueReader(_context, oidArgs[0]).fromStringData(elem.dbrefOID().toString()); + + JS::AutoValueArray<2> dbPointerArgs(_context); + ValueReader(_context, dbPointerArgs[0]).fromStringData(elem.dbrefNS()); + scope->getOidProto().newInstance(oidArgs, dbPointerArgs[1]); + + scope->getDbPointerProto().newInstance(dbPointerArgs, _value); + return; + } + default: + massert(16661, + str::stream() << "can't handle type: " << elem.type() << " " << elem.toString(), + false); + break; + } + + _value.setUndefined(); +} + +void ValueReader::fromBSON(const BSONObj& obj, bool readOnly) { + if (obj.firstElementType() == String && str::equals(obj.firstElementFieldName(), "$ref")) { + BSONObjIterator it(obj); + const BSONElement ref = it.next(); + const BSONElement id = it.next(); + + if (id.ok() && str::equals(id.fieldName(), "$id")) { + JS::AutoValueArray<2> args(_context); + + ValueReader(_context, args[0]).fromBSONElement(ref, readOnly); + + // id can be a subobject + ValueReader(_context, args[1], _depth + 1).fromBSONElement(id, readOnly); + + JS::RootedObject obj(_context); + + auto scope = getScope(_context); + + scope->getDbRefProto().newInstance(args, &obj); + ObjectWrapper o(_context, obj); + + while (it.more()) { + BSONElement elem = it.next(); + o.setBSONElement(elem.fieldName(), elem, readOnly); + } + + _value.setObjectOrNull(obj); + return; + } + } + + JS::RootedObject child(_context); + BSONInfo::make(_context, &child, obj, readOnly); + + _value.setObjectOrNull(child); +} + +/** + * SpiderMonkey doesn't have a direct entry point to create a jsstring from + * utf8, so we have to flow through some slightly less public interfaces. + * + * Basically, we have to use their routines to convert to utf16, then assign + * those bytes with JS_NewUCStringCopyN + */ +void ValueReader::fromStringData(StringData sd) { + size_t utf16Len; + + // TODO: we have tests that involve dropping garbage in. Do we want to + // throw, or to take the lossy conversion? + auto utf16 = JS::LossyUTF8CharsToNewTwoByteCharsZ( + _context, JS::UTF8Chars(sd.rawData(), sd.size()), &utf16Len); + + mozilla::UniquePtr utf16Deleter(utf16.get()); + + uassert(ErrorCodes::JSInterpreterFailure, + str::stream() << "Failed to encode \"" << sd << "\" as utf16", + utf16); + + auto jsStr = JS_NewUCStringCopyN(_context, utf16.get(), utf16Len); + + uassert(ErrorCodes::JSInterpreterFailure, + str::stream() << "Unable to copy \"" << sd << "\" into MozJS", + jsStr); + + _value.setString(jsStr); +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/valuereader.h b/src/mongo/scripting/mozjs/valuereader.h new file mode 100644 index 00000000000..be916ccbc25 --- /dev/null +++ b/src/mongo/scripting/mozjs/valuereader.h @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include +#include + +#include "mongo/bson/bsonobj.h" + +namespace mongo { +namespace mozjs { + +/** + * Reads into a JS Value from some Mongo C++ primitive + */ +class ValueReader { +public: + /** + * Depth is used when readers are invoked from ObjectWrappers to avoid + * reading out overly nested objects + */ + ValueReader(JSContext* cx, JS::MutableHandleValue value, int depth = 0); + + void fromBSONElement(const BSONElement& elem, bool readOnly); + void fromBSON(const BSONObj& obj, bool readOnly); + void fromStringData(StringData sd); + +private: + JSContext* _context; + JS::MutableHandleValue _value; + int _depth; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/valuewriter.cpp b/src/mongo/scripting/mozjs/valuewriter.cpp new file mode 100644 index 00000000000..31928f7ab44 --- /dev/null +++ b/src/mongo/scripting/mozjs/valuewriter.cpp @@ -0,0 +1,252 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/scripting/mozjs/valuewriter.h" + +#include + +#include "mongo/base/error_codes.h" +#include "mongo/scripting/mozjs/exception.h" +#include "mongo/scripting/mozjs/implscope.h" +#include "mongo/scripting/mozjs/jsstringwrapper.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/scripting/mozjs/valuereader.h" +#include "mongo/util/base64.h" + +namespace mongo { +namespace mozjs { + +ValueWriter::ValueWriter(JSContext* cx, JS::HandleValue value, int depth) + : _context(cx), _value(value), _depth(depth), _originalParent(nullptr) {} + +void ValueWriter::setOriginalBSON(BSONObj* obj) { + _originalParent = obj; +} + +int ValueWriter::type() { + if (_value.isNull()) + return jstNULL; + if (_value.isUndefined()) + return Undefined; + if (_value.isString()) + return String; + if (JS_IsArrayObject(_context, _value)) + return Array; + if (_value.isBoolean()) + return Bool; + + // We could do something more sophisticated here by checking to see if we + // round trip through int32_t, int64_t and double and picking a type that + // way, for now just always come back as double for numbers though (it's + // what we did for v8) + if (_value.isNumber()) + return NumberDouble; + + if (_value.isObject()) { + JS::RootedObject obj(_context, _value.toObjectOrNull()); + if (JS_ObjectIsDate(_context, obj)) + return Date; + if (JS_ObjectIsFunction(_context, obj)) + return Code; + + return Object; + } + + uasserted(ErrorCodes::BadValue, "unable to get type"); +} + +BSONObj ValueWriter::toBSON() { + if (!_value.isObject()) + return BSONObj(); + + JS::RootedObject obj(_context, _value.toObjectOrNull()); + + if (getScope(_context)->getBsonProto().instanceOf(obj)) { + BSONObj* originalBSON; + bool altered; + + std::tie(originalBSON, altered) = BSONInfo::originalBSON(_context, obj); + + if (!altered) + return *originalBSON; + } + + BSONObjBuilder bob; + ObjectWrapper(_context, obj, _depth).writeThis(&bob); + + return bob.obj(); +} + +std::string ValueWriter::toString() { + return JSStringWrapper(_context, JS::ToString(_context, _value)).toString(); +} + +double ValueWriter::toNumber() { + double out; + if (JS::ToNumber(_context, _value, &out)) + return out; + + throwCurrentJSException(_context, ErrorCodes::BadValue, "Failure to convert value to number"); +} + +bool ValueWriter::toBoolean() { + return JS::ToBoolean(_value); +} + +int32_t ValueWriter::toInt32() { + int32_t out; + if (JS::ToInt32(_context, _value, &out)) + return out; + + throwCurrentJSException(_context, ErrorCodes::BadValue, "Failure to convert value to number"); +} + +int64_t ValueWriter::toInt64() { + int64_t out; + if (JS::ToInt64(_context, _value, &out)) + return out; + + throwCurrentJSException(_context, ErrorCodes::BadValue, "Failure to convert value to number"); +} + +void ValueWriter::writeThis(BSONObjBuilder* b, StringData sd) { + uassert(17279, + str::stream() << "Exceeded depth limit of " << 150 + << " when converting js object to BSON. Do you have a cycle?", + _depth < 149); + + // Null char should be at the end, not in the string + uassert(16985, + str::stream() << "JavaScript property (name) contains a null char " + << "which is not allowed in BSON. " + << (_originalParent ? _originalParent->jsonString() : ""), + (std::string::npos == sd.find('\0'))); + + if (_value.isString()) { + b->append(sd, toString()); + } else if (_value.isNumber()) { + double val = toNumber(); + + // if previous type was integer, keep it + int intval = static_cast(val); + + if (val == intval && _originalParent) { + // This makes copying an object of numbers O(n**2) :( + BSONElement elmt = _originalParent->getField(sd); + if (elmt.type() == mongo::NumberInt) { + b->append(sd, intval); + return; + } + } + + b->append(sd, val); + } else if (_value.isObject()) { + JS::RootedObject childObj(_context, _value.toObjectOrNull()); + _writeObject(b, sd, childObj); + } else if (_value.isBoolean()) { + b->appendBool(sd, _value.toBoolean()); + } else if (_value.isUndefined()) { + b->appendUndefined(sd); + } else if (_value.isNull()) { + b->appendNull(sd); + } else { + uasserted(16662, + str::stream() << "unable to convert JavaScript property to mongo element " << sd); + } +} + +void ValueWriter::_writeObject(BSONObjBuilder* b, StringData sd, JS::HandleObject obj) { + auto scope = getScope(_context); + + ObjectWrapper o(_context, obj, _depth); + + if (JS_ObjectIsFunction(_context, _value.toObjectOrNull())) { + uassert(16716, + "cannot convert native function to BSON", + !scope->getNativeFunctionProto().instanceOf(obj)); + b->appendCode(sd, ValueWriter(_context, _value).toString()); + } else if (JS_ObjectIsRegExp(_context, obj)) { + JS::RootedValue v(_context); + v.setObjectOrNull(obj); + + std::string regex = ValueWriter(_context, v).toString(); + regex = regex.substr(1); + std::string r = regex.substr(0, regex.rfind('/')); + std::string o = regex.substr(regex.rfind('/') + 1); + + b->appendRegex(sd, r, o); + } else if (JS_ObjectIsDate(_context, obj)) { + JS::RootedValue dateval(_context); + o.callMethod("getTime", &dateval); + + auto d = Date_t::fromMillisSinceEpoch(ValueWriter(_context, dateval).toNumber()); + b->appendDate(sd, d); + } else if (scope->getOidProto().instanceOf(obj)) { + b->append(sd, OID(o.getString("str"))); + } else if (scope->getNumberLongProto().instanceOf(obj)) { + long long out = NumberLongInfo::ToNumberLong(_context, obj); + b->append(sd, out); + } else if (scope->getNumberIntProto().instanceOf(obj)) { + b->append(sd, NumberIntInfo::ToNumberInt(_context, obj)); + } else if (scope->getDbPointerProto().instanceOf(obj)) { + JS::RootedValue id(_context); + o.getValue("id", &id); + + b->appendDBRef(sd, o.getString("ns"), OID(ObjectWrapper(_context, id).getString("str"))); + } else if (scope->getBinDataProto().instanceOf(obj)) { + auto str = static_cast(JS_GetPrivate(obj)); + + auto binData = base64::decode(*str); + + b->appendBinData(sd, + binData.size(), + static_cast(static_cast(o.getNumber("type"))), + binData.c_str()); + } else if (scope->getTimestampProto().instanceOf(obj)) { + Timestamp ot(o.getNumber("t"), o.getNumber("i")); + b->append(sd, ot); + } else if (scope->getMinKeyProto().instanceOf(obj)) { + b->appendMinKey(sd); + } else if (scope->getMaxKeyProto().instanceOf(obj)) { + b->appendMaxKey(sd); + } else { + // nested object or array + + BSONObjBuilder subbob(JS_IsArrayObject(_context, obj) ? b->subarrayStart(sd) + : b->subobjStart(sd)); + + ObjectWrapper child(_context, obj, _depth + 1); + + child.writeThis(b); + } +} + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/valuewriter.h b/src/mongo/scripting/mozjs/valuewriter.h new file mode 100644 index 00000000000..47358398f81 --- /dev/null +++ b/src/mongo/scripting/mozjs/valuewriter.h @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include +#include + +#include "mongo/bson/bsonobj.h" + +namespace mongo { +namespace mozjs { + +/** + * Writes C++ values out of JS Values + * + * depth is used to trap circular objects in js and prevent stack smashing + * + * originalBSON is a hack to keep integer types in their original type when + * they're read out, manipulated in js and saved back. + */ +class ValueWriter { +public: + ValueWriter(JSContext* cx, JS::HandleValue value, int depth = 0); + + BSONObj toBSON(); + + /** + * These coercions flow through JS::To_X. I.e. they can call toString() or + * toNumber() + */ + std::string toString(); + int type(); + double toNumber(); + int32_t toInt32(); + int64_t toInt64(); + bool toBoolean(); + + /** + * Writes the value into a bsonobjbuilder under the name in sd. + */ + void writeThis(BSONObjBuilder* b, StringData sd); + + void setOriginalBSON(BSONObj* obj); + +private: + /** + * Writes the object into a bsonobjbuilder under the name in sd. + */ + void _writeObject(BSONObjBuilder* b, StringData sd, JS::HandleObject obj); + + JSContext* _context; + JS::HandleValue _value; + int _depth; + BSONObj* _originalParent; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/mozjs/wraptype.h b/src/mongo/scripting/mozjs/wraptype.h new file mode 100644 index 00000000000..c5ca6095e76 --- /dev/null +++ b/src/mongo/scripting/mozjs/wraptype.h @@ -0,0 +1,474 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include +#include +#include + +#include "mongo/scripting/mozjs/base.h" +#include "mongo/scripting/mozjs/exception.h" +#include "mongo/scripting/mozjs/objectwrapper.h" +#include "mongo/util/assert_util.h" + +// The purpose of this class is to take in specially crafted types and generate +// a wrapper which installs the type, along with any useful life cycle methods +// and free functions that might be associated with it. The template magic in +// here, along with some useful macros, hides a lot of the implementation +// complexity of exposing C++ code into javascript. Most prominently, we have +// to wrap every function that can be called from javascript to prevent any C++ +// exceptions from leaking out. We do this, with template and macro based +// codegen, and turn mongo exceptions into instances of Status, then convert +// those into javascript exceptions before returning. That allows all consumers +// of this library to throw exceptions freely, with the understanding that +// they'll be visible in javascript. Javascript exceptions are trapped at the +// top level and converted back to mongo exceptions by an error handler on +// ImplScope. + +// MONGO_*_JS_FUNCTION_* macros are public and allow wrapped types to install +// their own functions on types and into the global scope +#define MONGO_DEFINE_JS_FUNCTION(name) \ + static void name(JSContext* cx, JS::CallArgs args); \ + static bool WRAPPER_##name(JSContext* cx, unsigned argc, JS::Value* vp) { \ + try { \ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ + name(cx, args); \ + return true; \ + } catch (...) { \ + mongoToJSException(cx); \ + return false; \ + } \ + } + +#define MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(name, flags) \ + JS_FS(#name, Functions::WRAPPER_##name, 0, flags) + +#define MONGO_ATTACH_JS_FUNCTION(name) MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(name, 0) + +namespace mongo { +namespace mozjs { + +namespace smUtils { + +// Now all the spidermonkey type methods +template +static bool addProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue v) { + try { + T::addProperty(cx, obj, id, v); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool call(JSContext* cx, unsigned argc, JS::Value* vp) { + try { + T::call(cx, JS::CallArgsFromVp(argc, vp)); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool construct(JSContext* cx, unsigned argc, JS::Value* vp) { + try { + T::construct(cx, JS::CallArgsFromVp(argc, vp)); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool convert(JSContext* cx, JS::HandleObject obj, JSType type, JS::MutableHandleValue vp) { + try { + T::convert(cx, obj, type, vp); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded) { + try { + T::delProperty(cx, obj, id, succeeded); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties) { + try { + T::enumerate(cx, obj, properties); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool getProperty(JSContext* cx, + JS::HandleObject obj, + JS::HandleId id, + JS::MutableHandleValue vp) { + try { + T::getProperty(cx, obj, id, vp); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool hasInstance(JSContext* cx, JS::HandleObject obj, JS::MutableHandleValue vp, bool* bp) { + try { + T::hasInstance(cx, obj, vp, bp); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool setProperty( + JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict, JS::MutableHandleValue vp) { + try { + T::setProperty(cx, obj, id, strict, vp); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +template +static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) { + try { + T::resolve(cx, obj, id, resolvedp); + return true; + } catch (...) { + mongoToJSException(cx); + return false; + } +}; + +} // namespace smUtils + +template +class WrapType : public T { +public: + WrapType(JSContext* context) + : _context(context), + _proto(), + _jsclass({T::className, + T::classFlags, + T::addProperty != BaseInfo::addProperty ? smUtils::addProperty : nullptr, + T::delProperty != BaseInfo::delProperty ? smUtils::delProperty : nullptr, + T::getProperty != BaseInfo::getProperty ? smUtils::getProperty : nullptr, + T::setProperty != BaseInfo::setProperty ? smUtils::setProperty : nullptr, + // We don't use the regular enumerate because we want the fancy new one + nullptr, + T::resolve != BaseInfo::resolve ? smUtils::resolve : nullptr, + T::convert != BaseInfo::convert ? smUtils::convert : nullptr, + T::finalize != BaseInfo::finalize ? T::finalize : nullptr, + T::call != BaseInfo::call ? smUtils::call : nullptr, + T::hasInstance != BaseInfo::hasInstance ? smUtils::hasInstance : nullptr, + T::construct != BaseInfo::construct ? smUtils::construct : nullptr, + nullptr}) { + _installEnumerate(T::enumerate != BaseInfo::enumerate ? smUtils::enumerate : nullptr); + + // The global object is different. We need it for basic setup + // before the other types are installed. Might as well just do it + // in the constructor. + if (T::classFlags & JSCLASS_GLOBAL_FLAGS) { + JS::RootedObject proto(_context); + + _proto.init(_context, + _assertPtr(JS_NewGlobalObject( + _context, &_jsclass, nullptr, JS::DontFireOnNewGlobalHook))); + + JSAutoCompartment ac(_context, _proto); + _installFunctions(_proto, T::freeFunctions); + } + } + + ~WrapType() { + // Persistent globals don't RAII, you have to reset() them manually + _proto.reset(); + } + + void install(JS::HandleObject global) { + switch (static_cast(T::installType)) { + case InstallType::Global: + _installGlobal(global); + break; + case InstallType::Private: + _installPrivate(global); + break; + case InstallType::OverNative: + _installOverNative(global); + break; + } + } + + /** + * newObject methods don't invoke the constructor. So they're good for + * types without a constructor or inside the constructor + */ + void newObject(JS::MutableHandleObject out) { + // The regular form of JS_NewObject, where we pass proto as the + // third param, actually does a global object lookup for some + // reason. This way allows object creation with non-public + // prototypes and if someone deletes the symbol up the chain. + out.set(_assertPtr(JS_NewObject(_context, &_jsclass, JS::NullPtr()))); + + if (!JS_SetPrototype(_context, out, _proto)) + throwCurrentJSException( + _context, ErrorCodes::JSInterpreterFailure, "Failed to set prototype"); + } + + void newObject(JS::MutableHandleValue out) { + JS::RootedObject obj(_context); + newObject(&obj); + + out.setObjectOrNull(obj); + } + + /** + * newInstance calls the constructor, a la new Type() in js + */ + void newInstance(JS::MutableHandleObject out) { + JS::AutoValueVector args(_context); + + newInstance(args, out); + } + + void newInstance(const JS::HandleValueArray& args, JS::MutableHandleObject out) { + out.set(_assertPtr(JS_New(_context, _proto, args))); + } + + void newInstance(JS::MutableHandleValue out) { + JS::AutoValueVector args(_context); + + newInstance(args, out); + } + + void newInstance(const JS::HandleValueArray& args, JS::MutableHandleValue out) { + out.setObjectOrNull(_assertPtr(JS_New(_context, _proto, args))); + } + + // instanceOf doesn't go up the prototype tree. It's a lower level more specific match + bool instanceOf(JS::HandleObject obj) { + return JS_InstanceOf(_context, obj, &_jsclass, nullptr); + } + + bool instanceOf(JS::HandleValue value) { + if (!value.isObject()) + return false; + + JS::RootedObject obj(_context, value.toObjectOrNull()); + + return instanceOf(obj); + } + + const JSClass* getJSClass() const { + return &_jsclass; + } + + JS::HandleObject getProto() const { + return _proto; + } + +private: + /** + * Use this if you want your types installed visibly in the global scope + */ + void _installGlobal(JS::HandleObject global) { + JS::RootedObject parent(_context); + _inheritFrom(T::inheritFrom, global, &parent); + + _proto.init(_context, + _assertPtr(JS_InitClass( + _context, + global, + parent, + &_jsclass, + T::construct != BaseInfo::construct ? smUtils::construct : nullptr, + 0, + nullptr, + T::methods, + nullptr, + nullptr))); + + _installFunctions(global, T::freeFunctions); + _postInstall(global, T::postInstall); + } + + // Use this if you want your types installed, but not visible in the + // global scope + void _installPrivate(JS::HandleObject global) { + JS::RootedObject parent(_context); + _inheritFrom(T::inheritFrom, global, &parent); + + // See newObject() for why we have to do this dance with the explicit + // SetPrototype + _proto.init(_context, _assertPtr(JS_NewObject(_context, &_jsclass, JS::NullPtr()))); + if (parent.get() && !JS_SetPrototype(_context, _proto, parent)) + throwCurrentJSException( + _context, ErrorCodes::JSInterpreterFailure, "Failed to set prototype"); + + _installFunctions(_proto, T::methods); + _installFunctions(global, T::freeFunctions); + + _installConstructor(T::construct != BaseInfo::construct ? smUtils::construct : nullptr); + + _postInstall(global, T::postInstall); + } + + // Use this to attach things to types that we don't provide like + // Object, or Array + void _installOverNative(JS::HandleObject global) { + JS::RootedValue value(_context); + if (!JS_GetProperty(_context, global, T::className, &value)) + throwCurrentJSException( + _context, ErrorCodes::JSInterpreterFailure, "Couldn't get className property"); + + if (!value.isObject()) + uasserted(ErrorCodes::BadValue, "className isn't object"); + + _proto.init(_context, value.toObjectOrNull()); + + _installFunctions(_proto, T::methods); + _installFunctions(global, T::freeFunctions); + _postInstall(global, T::postInstall); + } + + void _installFunctions(JS::HandleObject global, const JSFunctionSpec* fs) { + if (!fs) + return; + if (JS_DefineFunctions(_context, global, fs)) + return; + + throwCurrentJSException( + _context, ErrorCodes::JSInterpreterFailure, "Failed to define functions"); + } + + // We have to do this awkward dance to set the new style enumeration. + // You used to be able to set this with JSCLASS_NEW_ENUMERATE in class + // flags, in the future you'll probably only set ObjectOps, but for now + // we have this. There are a host of static_asserts in js/Class.h that + // ensure that these two structures are equal. + // + // This is a landmine to watch out for during upgrades + using enumerateT = bool (*)(JSContext*, JS::HandleObject, JS::AutoIdVector&); + void _installEnumerate(enumerateT enumerate) { + if (!enumerate) + return; + + auto implClass = reinterpret_cast(&_jsclass); + + implClass->ops.enumerate = enumerate; + } + + // This is for inheriting from something other than Object + void _inheritFrom(const char* name, JS::HandleObject global, JS::MutableHandleObject out) { + if (!name) + return; + + JS::RootedValue val(_context); + + if (!JS_GetProperty(_context, global, name, &val)) { + throwCurrentJSException( + _context, ErrorCodes::JSInterpreterFailure, "Failed to get parent"); + } + + if (!val.isObject()) { + uasserted(ErrorCodes::JSInterpreterFailure, "Parent is not an object"); + } + + out.set(val.toObjectOrNull()); + } + + using postInstallT = void (*)(JSContext*, JS::HandleObject, JS::HandleObject); + void _postInstall(JS::HandleObject global, postInstallT postInstall) { + if (!postInstall) + return; + + postInstall(_context, global, _proto); + } + + void _installConstructor(JSNative ctor) { + if (!ctor) + return; + + auto ptr = JS_NewFunction(_context, ctor, 0, JSFUN_CONSTRUCTOR, JS::NullPtr(), nullptr); + if (!ptr) { + throwCurrentJSException( + _context, ErrorCodes::JSInterpreterFailure, "Failed to install constructor"); + } + + JS::RootedObject ctorObj(_context, JS_GetFunctionObject(ptr)); + + if (!JS_LinkConstructorAndPrototype(_context, ctorObj, _proto)) + throwCurrentJSException(_context, + ErrorCodes::JSInterpreterFailure, + "Failed to link constructor and prototype"); + } + + JSObject* _assertPtr(JSObject* ptr) { + if (!ptr) + throwCurrentJSException( + _context, ErrorCodes::JSInterpreterFailure, "Failed to JS_NewX"); + + return ptr; + } + + JSContext* _context; + JS::PersistentRootedObject _proto; + JSClass _jsclass; +}; + +} // namespace mozjs +} // namespace mongo diff --git a/src/mongo/scripting/v8_deadline_monitor.h b/src/mongo/scripting/v8_deadline_monitor.h deleted file mode 100644 index 89f7e9d1b84..00000000000 --- a/src/mongo/scripting/v8_deadline_monitor.h +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright (C) 2013 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ -#pragma once - -#include - -#include "mongo/base/disallow_copying.h" -#include "mongo/platform/unordered_map.h" -#include "mongo/stdx/condition_variable.h" -#include "mongo/stdx/thread.h" -#include "mongo/util/concurrency/mutex.h" -#include "mongo/util/time_support.h" - -namespace mongo { - -/** - * DeadlineMonitor - * - * Monitors tasks which are required to complete before a deadline. When - * a deadline is started on a _Task*, either the deadline must be stopped, - * or _Task::kill() will be called when the deadline arrives. - * - * Each instance of a DeadlineMonitor spawns a thread which waits for one of the - * following conditions: - * - a task is added to the monitor - * - a task is removed from the monitor - * - the nearest deadline has arrived - * - * Ownership: - * The _Task* must not be freed until the deadline has elapsed or stopDeadline() - * has been called. - * - * NOTE: Each instance of this class spawns a new thread. It is intended to be a stop-gap - * solution for simple deadline monitoring until a more robust solution can be - * implemented. - * - * NOTE: timing is based on wallclock time, which may not be precise. - */ -template -class DeadlineMonitor { - MONGO_DISALLOW_COPYING(DeadlineMonitor); - -public: - DeadlineMonitor() { - // NOTE(schwerin): Because _monitorThread takes a pointer to "this", all of the fields - // of this instance must be initialized before the thread is created. As a result, we - // should not create the thread in the initializer list. Creating it there leaves us - // vulnerable to errors introduced by rearranging the order of fields in the class. - _monitorThread = stdx::thread(&mongo::DeadlineMonitor<_Task>::deadlineMonitorThread, this); - } - - ~DeadlineMonitor() { - { - // ensure the monitor thread has been stopped before destruction - stdx::lock_guard lk(_deadlineMutex); - _inShutdown = true; - _newDeadlineAvailable.notify_one(); - } - _monitorThread.join(); - } - - /** - * Start monitoring a task for deadline lapse. User must call stopDeadline() before - * deleting the task. Note that stopDeadline() cannot be called from within the - * kill() method. - * @param task the task to kill() - * @param timeoutMs number of milliseconds before the deadline expires - */ - void startDeadline(_Task* const task, uint64_t timeoutMs) { - const auto deadline = Date_t::now() + Milliseconds(timeoutMs); - stdx::lock_guard lk(_deadlineMutex); - - _tasks[task] = deadline; - - if (deadline < _nearestDeadlineWallclock) { - _nearestDeadlineWallclock = deadline; - _newDeadlineAvailable.notify_one(); - } - } - - /** - * Stop monitoring a task. Can be called multiple times, before or after a - * deadline has expired (as long as the task remains allocated). - * @return true if the task was found and erased - */ - bool stopDeadline(_Task* const task) { - stdx::lock_guard lk(_deadlineMutex); - return _tasks.erase(task); - } - -private: - /** - * Main deadline monitor loop. Waits on a condition variable until a task - * is started, stopped, or the nearest deadline arrives. If a deadline arrives, - * _Task::kill() is invoked. - */ - void deadlineMonitorThread() { - stdx::unique_lock lk(_deadlineMutex); - while (!_inShutdown) { - // get the next interval to wait - const Date_t now = Date_t::now(); - - // wait for a task to be added or a deadline to expire - if (_nearestDeadlineWallclock > now) { - if (_nearestDeadlineWallclock == Date_t::max()) { - _newDeadlineAvailable.wait(lk); - } else { - _newDeadlineAvailable.wait_until(lk, - _nearestDeadlineWallclock.toSystemTimePoint()); - } - continue; - } - - // set the next interval to wait for deadline completion - _nearestDeadlineWallclock = Date_t::max(); - typename TaskDeadlineMap::iterator i = _tasks.begin(); - while (i != _tasks.end()) { - if (i->second < now) { - // deadline expired - i->first->kill(); - _tasks.erase(i++); - } else { - if (i->second < _nearestDeadlineWallclock) { - // nearest deadline seen so far - _nearestDeadlineWallclock = i->second; - } - ++i; - } - } - } - } - - typedef unordered_map<_Task*, Date_t> TaskDeadlineMap; - TaskDeadlineMap _tasks; // map of running tasks with deadlines - stdx::mutex _deadlineMutex; // protects all non-const members, except _monitorThread - stdx::condition_variable _newDeadlineAvailable; // Signaled for timeout, start and stop - stdx::thread _monitorThread; // the deadline monitor thread - Date_t _nearestDeadlineWallclock = Date_t::max(); // absolute time of the nearest deadline - bool _inShutdown = false; -}; - -} // namespace mongo diff --git a/src/mongo/scripting/v8_deadline_monitor_test.cpp b/src/mongo/scripting/v8_deadline_monitor_test.cpp deleted file mode 100644 index 702160cd1ed..00000000000 --- a/src/mongo/scripting/v8_deadline_monitor_test.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Copyright (C) 2013 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -// DeadlineMonitor unit tests - -#include "mongo/platform/basic.h" - -#include "mongo/scripting/v8_deadline_monitor.h" - - -#include "mongo/unittest/unittest.h" - -namespace mongo { - -using std::shared_ptr; -using std::vector; - -class TaskGroup { -public: - TaskGroup() : _c(), _killCount(0), _targetKillCount(0) {} - void noteKill() { - stdx::lock_guard lk(_m); - ++_killCount; - if (_killCount >= _targetKillCount) - _c.notify_one(); - } - void waitForKillCount(uint64_t target) { - stdx::unique_lock lk(_m); - _targetKillCount = target; - while (_killCount < _targetKillCount) - _c.wait(lk); - } - -private: - stdx::mutex _m; - stdx::condition_variable _c; - uint64_t _killCount; - uint64_t _targetKillCount; -}; - -class Task { -public: - Task() : _group(NULL), _killed(0) {} - explicit Task(TaskGroup* group) : _group(group), _killed(0) {} - void kill() { - _killed = curTimeMillis64(); - if (_group) - _group->noteKill(); - } - TaskGroup* _group; - uint64_t _killed; -}; - -// single task expires before stopping the deadline -TEST(DeadlineMonitor, ExpireThenRemove) { - DeadlineMonitor dm; - TaskGroup group; - Task task(&group); - dm.startDeadline(&task, 10); - group.waitForKillCount(1); - ASSERT(task._killed); - ASSERT(!dm.stopDeadline(&task)); -} - -// single task deadline stopped before the task expires -TEST(DeadlineMonitor, RemoveBeforeExpire) { - DeadlineMonitor dm; - Task task; - dm.startDeadline(&task, 3600 * 1000); - ASSERT(dm.stopDeadline(&task)); - ASSERT(!task._killed); -} - -// multiple tasks complete before deadline expires (with 10ms window) -TEST(DeadlineMonitor, MultipleTasksCompleteBeforeExpire) { - DeadlineMonitor dm; - vector> tasks; - - // start 100 tasks with varying deadlines (1-100 hours) - for (int i = 1; i <= 100; i++) { - shared_ptr task(new Task()); - dm.startDeadline(task.get(), i * 3600 * 1000); - tasks.push_back(task); - } - - // verify each deadline is stopped arrival - for (vector>::iterator i = tasks.begin(); i != tasks.end(); ++i) { - ASSERT(dm.stopDeadline(i->get())); - ASSERT(!(*i)->_killed); - } -} - -// multiple tasks expire before stopping the deadline -TEST(DeadlineMonitor, MultipleTasksExpire) { - DeadlineMonitor dm; - TaskGroup group; - vector> tasks; - - // start 100 tasks with varying deadlines - for (int i = 1; i <= 100; i++) { - shared_ptr task(new Task(&group)); - dm.startDeadline(task.get(), i); - tasks.push_back(task); - } - - group.waitForKillCount(100); - - // verify each deadline has expired - for (vector>::iterator i = tasks.begin(); i != tasks.end(); ++i) { - ASSERT(!dm.stopDeadline(i->get())); - ASSERT((*i)->_killed); - } -} - -// mixed expiration and completion -TEST(DeadlineMonitor, MultipleTasksExpireOrComplete) { - DeadlineMonitor dm; - TaskGroup group; - vector> expiredTasks; // tasks that should expire - vector> stoppedTasks; // tasks that should not expire - - // start 100 tasks with varying deadlines - for (int i = 1; i <= 100; i++) { - shared_ptr task(new Task(&group)); - if (i % 2 == 0) { - // stop every other task - dm.startDeadline(task.get(), i * 3600 * 1000); - dm.stopDeadline(task.get()); - stoppedTasks.push_back(task); - continue; - } - dm.startDeadline(task.get(), i); - expiredTasks.push_back(task); - } - - group.waitForKillCount(50); - - // check tasks which exceed the deadline - for (vector>::iterator i = expiredTasks.begin(); i != expiredTasks.end(); - ++i) { - ASSERT(!dm.stopDeadline(i->get())); - ASSERT((*i)->_killed); - } - - // check tasks with a deadline that was stopped - for (vector>::iterator i = stoppedTasks.begin(); i != stoppedTasks.end(); - ++i) { - ASSERT(!(*i)->_killed); - } -} - -} // namespace mongo diff --git a/src/mongo/shell/bulk_api.js b/src/mongo/shell/bulk_api.js index df2c868fa5c..89cb762f1e8 100644 --- a/src/mongo/shell/bulk_api.js +++ b/src/mongo/shell/bulk_api.js @@ -357,7 +357,6 @@ var _bulk_api_module = (function() { if(!(this instanceof BulkWriteError)) return new BulkWriteError(bulkResult, singleBatchType, writeConcern, message); - Error.captureStackTrace(this, this.constructor); this.name = 'BulkWriteError'; this.message = message || 'unknown bulk write error'; @@ -403,7 +402,6 @@ var _bulk_api_module = (function() { defineReadOnlyProperty(this, "code", commandError.code); defineReadOnlyProperty(this, "errmsg", commandError.errmsg); - Error.captureStackTrace(this, this.constructor); this.name = 'WriteCommandError'; this.message = this.errmsg; diff --git a/src/mongo/shell/types.js b/src/mongo/shell/types.js index af529d63a1a..8f860cac59d 100644 --- a/src/mongo/shell/types.js +++ b/src/mongo/shell/types.js @@ -219,7 +219,9 @@ Object.extend = function(dst, src, deep){ for (var k in src){ var v = src[k]; if (deep && typeof(v) == "object"){ - if ("floatApprox" in v) { // convert NumberLong properly + if (v.constructor === ObjectId) { // convert ObjectId properly + eval("v = " + tojson(v)); + } else if ("floatApprox" in v) { // convert NumberLong properly eval("v = " + tojson(v)); } else { v = Object.extend(typeof (v.length) == "number" ? [] : {}, v, true); diff --git a/src/mongo/util/concurrency/threadlocal.h b/src/mongo/util/concurrency/threadlocal.h index 8973f267208..d6190bffb0f 100644 --- a/src/mongo/util/concurrency/threadlocal.h +++ b/src/mongo/util/concurrency/threadlocal.h @@ -32,6 +32,8 @@ #include "mongo/config.h" +#include "mongo/base/disallow_copying.h" + #if defined(MONGO_CONFIG_HAVE_THREAD_LOCAL) #define MONGO_TRIVIALLY_CONSTRUCTIBLE_THREAD_LOCAL thread_local #elif defined(MONGO_CONFIG_HAVE___THREAD) diff --git a/src/third_party/SConscript b/src/third_party/SConscript index 594ea2725e3..0b7cf49d33c 100644 --- a/src/third_party/SConscript +++ b/src/third_party/SConscript @@ -1,11 +1,12 @@ # -*- mode: python -*- -Import("env use_system_version_of_library usev8 v8suffix boostSuffix") +Import("env use_system_version_of_library usemozjs usev8 v8suffix boostSuffix") Import("wiredtiger") snappySuffix = '-1.1.2' zlibSuffix = '-1.2.8' pcreSuffix = "-8.37" +mozjsSuffix = '-38' thirdPartyIncludePathList = [ ('s2', '#/src/third_party/s2'), @@ -36,6 +37,17 @@ if not use_system_version_of_library('v8'): thirdPartyIncludePathList.append( ('v8', '#/src/third_party/v8' + v8suffix + '/include')) +# TODO: figure out if we want to offer system versions of mozjs. Mozilla +# hasn't offered a source tarball since 24, but in theory they could. +# +#if not use_system_version_of_library('mozjs'): +if True: + thirdPartyIncludePathList.append( + ('mozjs', ['#/src/third_party/mozjs' + mozjsSuffix + '/include', + '#/src/third_party/mozjs' + mozjsSuffix + '/mongo_sources', + '#/src/third_party/mozjs' + mozjsSuffix + '/platform/' + env["TARGET_ARCH"] + "/" + env["TARGET_OS"] + "/include", + ])) + if not use_system_version_of_library('stemmer'): thirdPartyIncludePathList.append( ('stemmer', '#/src/third_party/libstemmer_c/include')) @@ -199,6 +211,20 @@ if usev8: 'shim_v8.cpp', ]) +if usemozjs: + mozjsEnv = env.Clone() + mozjsEnv.SConscript('mozjs' + mozjsSuffix + '/SConscript', exports={'env' : mozjsEnv }) + mozjsEnv = mozjsEnv.Clone( + LIBDEPS=[ + 'mozjs' + mozjsSuffix + '/mozjs', + 'shim_zlib', + ]) + + mozjsEnv.Library( + target="shim_mozjs", + source=[ + 'shim_mozjs.cpp', + ]) gperftoolsEnv = env if (GetOption("allocator") == "tcmalloc"): diff --git a/src/third_party/mozjs-38/SConscript b/src/third_party/mozjs-38/SConscript new file mode 100644 index 00000000000..3b197a8bbcf --- /dev/null +++ b/src/third_party/mozjs-38/SConscript @@ -0,0 +1,98 @@ +# -*- mode: python -*- + +Import("env") + +env = env.Clone() +env.InjectThirdPartyIncludePaths(libraries=['zlib']) + +def removeIfPresent(lst, item): + try: + lst.remove(item) + except ValueError: + pass + +for to_remove in ['-Werror', '-Wall', '-W']: + removeIfPresent(env['CCFLAGS'], to_remove) + +# See what -D's show up in make. The AB_CD one might change, but we're little +# endian only for now so I think it's sane +env.Prepend(CPPDEFINES=[ + 'AB_CD', + 'IMPL_MFBT', + 'JS_USE_CUSTOM_ALLOCATOR', + 'NO_NSPR_10_SUPPORT', + 'STATIC_JS_API=1', + 'U_NO_DEFAULT_INCLUDE_UTF_HEADERS=1', + ]) + +# js-confdefs.h has to get in front on windows or wherever +if env.TargetOSIs('windows'): + env.Prepend(CCFLAGS=[ + '/FI', 'js-confdefs.h' + ]) +else: + if env.TargetOSIs('solaris'): + env.Prepend(CCFLAGS=[ + '-include', 'solaris_hacks.h' + ]) + + env.Append( + CCFLAGS=[ + '-include', 'js-confdefs.h', + '-Wno-invalid-offsetof', + ], + CXXFLAGS=[ + '-Wno-non-virtual-dtor', + ], + ) + +# js/src, js/public and mfbt are the only required sources right now, that +# could change in the future +# +# Also: +# We pre-generate configs for platforms and just check them in. Running +# mozilla's config requires a relatively huge portion of their tree. +env.Prepend(CPPPATH=[ + '#src', + '$BUILD_DIR', + 'extract/js/src', + 'extract/mfbt', + 'extract/intl/icu/source/common', + 'include', + 'mongo_sources', + 'platform/' + env["TARGET_ARCH"] + "/" + env["TARGET_OS"] + "/build", + 'platform/' + env["TARGET_ARCH"] + "/" + env["TARGET_OS"] + "/include", +]) + +sources = [ + "extract/js/src/builtin/RegExp.cpp", + "extract/js/src/frontend/Parser.cpp", + "extract/js/src/jsarray.cpp", + "extract/js/src/jsatom.cpp", + "extract/js/src/jsmath.cpp", + "extract/js/src/jsutil.cpp", + "extract/js/src/mfbt/Unified_cpp_mfbt0.cpp", + "extract/js/src/perf/pm_stub.cpp", + "extract/js/src/vm/TraceLogging.cpp", + "extract/js/src/vm/TraceLoggingGraph.cpp", + "extract/js/src/vm/TraceLoggingTypes.cpp", + "extract/mfbt/Compression.cpp", +] + +if env.TargetOSIs('windows'): + sources.extend([ + "extract/js/src/jit/ExecutableAllocatorWin.cpp", + ]) + env.Prepend(CPPDEFINES=[ + ("_CRT_RAND_S", "1") + ]) +else: + sources.extend([ + "extract/js/src/jit/ExecutableAllocatorPosix.cpp", + ]) + +sources.extend(Glob('platform/' + env["TARGET_ARCH"] + "/" + env["TARGET_OS"] + "/build/*.cpp")), + +# All of those unified sources come in from configure. The files don't +# actually build individually anymore. +env.Library( "mozjs", sources ) diff --git a/src/third_party/mozjs-38/mongo_sources/jscustomallocator.h b/src/third_party/mozjs-38/mongo_sources/jscustomallocator.h new file mode 100644 index 00000000000..bfaec9ee4ff --- /dev/null +++ b/src/third_party/mozjs-38/mongo_sources/jscustomallocator.h @@ -0,0 +1,54 @@ +/* Copyright 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include +#include + +#include + +#define JS_OOM_POSSIBLY_FAIL() \ + do { \ + } while (0) + +#define JS_OOM_POSSIBLY_FAIL_BOOL() \ + do { \ + } while (0) + +namespace mongo { +namespace sm { +JS_PUBLIC_API(size_t) get_total_bytes(); +JS_PUBLIC_API(void) reset(size_t max_bytes); +JS_PUBLIC_API(size_t) get_max_bytes(); +} // namespace sm +} // namespace mongo + +JS_PUBLIC_API(void*) js_malloc(size_t bytes); +JS_PUBLIC_API(void*) js_calloc(size_t bytes); +JS_PUBLIC_API(void*) js_calloc(size_t nmemb, size_t size); +JS_PUBLIC_API(void) js_free(void* p); +JS_PUBLIC_API(void*) js_realloc(void* p, size_t bytes); +JS_PUBLIC_API(char*) js_strdup(const char* s); diff --git a/src/third_party/mozjs-38/mongo_sources/solaris_hacks.h b/src/third_party/mozjs-38/mongo_sources/solaris_hacks.h new file mode 100644 index 00000000000..3e792f5427f --- /dev/null +++ b/src/third_party/mozjs-38/mongo_sources/solaris_hacks.h @@ -0,0 +1,44 @@ +/* Copyright 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include + +/* Solaris doesn't expose madvise to c++ compilers, so just define in + * posix_madvise + */ +#define madvise posix_madvise + +/* This doesn't seem to be provided on solaris. This no opt function is + * similiar to a patch that was introduced into firefox after 38 + */ +namespace js { +namespace gc { +static void* MapAlignedPagesLastDitch(unsigned long, unsigned long alignment) { return nullptr; } +} +} diff --git a/src/third_party/mozjs-38/mongo_sources/vm/PosixNSPR.cpp b/src/third_party/mozjs-38/mongo_sources/vm/PosixNSPR.cpp new file mode 100644 index 00000000000..2abfcfe2afe --- /dev/null +++ b/src/third_party/mozjs-38/mongo_sources/vm/PosixNSPR.cpp @@ -0,0 +1,5 @@ +/** + * This file is purposefully empty. We use it to satisfy the unity headers that + * mozilla's configure generates. It's fine that it's empty because we supply + * all the symbols we need in src/mongo/scripting/mozjs/PosixNSPR.cpp + */ diff --git a/src/third_party/mozjs-38/mongo_sources/vm/PosixNSPR.h b/src/third_party/mozjs-38/mongo_sources/vm/PosixNSPR.h new file mode 100644 index 00000000000..ddd9aa2f6f9 --- /dev/null +++ b/src/third_party/mozjs-38/mongo_sources/vm/PosixNSPR.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_PosixNSPR_h +#define vm_PosixNSPR_h + +#include + +namespace nspr { +class Thread; +class Lock; +class CondVar; +}; + +typedef nspr::Thread PRThread; +typedef nspr::Lock PRLock; +typedef nspr::CondVar PRCondVar; + +enum PRThreadType { + PR_USER_THREAD, + PR_SYSTEM_THREAD +}; + +enum PRThreadPriority +{ + PR_PRIORITY_FIRST = 0, + PR_PRIORITY_LOW = 0, + PR_PRIORITY_NORMAL = 1, + PR_PRIORITY_HIGH = 2, + PR_PRIORITY_URGENT = 3, + PR_PRIORITY_LAST = 3 +}; + +enum PRThreadScope { + PR_LOCAL_THREAD, + PR_GLOBAL_THREAD, + PR_GLOBAL_BOUND_THREAD +}; + +enum PRThreadState { + PR_JOINABLE_THREAD, + PR_UNJOINABLE_THREAD +}; + +PRThread* +PR_CreateThread(PRThreadType type, + void (*start)(void* arg), + void* arg, + PRThreadPriority priority, + PRThreadScope scope, + PRThreadState state, + uint32_t stackSize); + +typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus; + +PRStatus +PR_JoinThread(PRThread* thread); + +PRThread* +PR_GetCurrentThread(); + +PRStatus +PR_SetCurrentThreadName(const char* name); + +typedef void (*PRThreadPrivateDTOR)(void* priv); + +PRStatus +PR_NewThreadPrivateIndex(unsigned* newIndex, PRThreadPrivateDTOR destructor); + +PRStatus +PR_SetThreadPrivate(unsigned index, void* priv); + +void* +PR_GetThreadPrivate(unsigned index); + +struct PRCallOnceType { + int initialized; + int32_t inProgress; + PRStatus status; +}; + +typedef PRStatus (*PRCallOnceFN)(); + +PRStatus +PR_CallOnce(PRCallOnceType* once, PRCallOnceFN func); + +typedef PRStatus (*PRCallOnceWithArgFN)(void*); + +PRStatus +PR_CallOnceWithArg(PRCallOnceType* once, PRCallOnceWithArgFN func, void* arg); + +PRLock* +PR_NewLock(); + +void +PR_DestroyLock(PRLock* lock); + +void +PR_Lock(PRLock* lock); + +PRStatus +PR_Unlock(PRLock* lock); + +PRCondVar* +PR_NewCondVar(PRLock* lock); + +void +PR_DestroyCondVar(PRCondVar* cvar); + +PRStatus +PR_NotifyCondVar(PRCondVar* cvar); + +PRStatus +PR_NotifyAllCondVar(PRCondVar* cvar); + +#define PR_INTERVAL_MIN 1000UL +#define PR_INTERVAL_MAX 100000UL + +#define PR_INTERVAL_NO_WAIT 0UL +#define PR_INTERVAL_NO_TIMEOUT 0xffffffffUL + +uint32_t +PR_MillisecondsToInterval(uint32_t milli); + +uint32_t +PR_MicrosecondsToInterval(uint32_t micro); + +uint32_t +PR_TicksPerSecond(); + +PRStatus +PR_WaitCondVar(PRCondVar* cvar, uint32_t timeout); + +#endif /* vm_PosixNSPR_h */ diff --git a/src/third_party/shim_mozjs.cpp b/src/third_party/shim_mozjs.cpp new file mode 100644 index 00000000000..c1b9dbe8ec2 --- /dev/null +++ b/src/third_party/shim_mozjs.cpp @@ -0,0 +1,3 @@ +// This file intentionally blank. shim_mozjs.cpp is part of the +// third_party/mozjs library, which is just a placeholder for forwarding +// library dependencies. -- cgit v1.2.1