Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module loading support #990

Merged
merged 18 commits into from Dec 23, 2021
Merged

Conversation

pluethi1
Copy link
Contributor

@pluethi1 pluethi1 commented Oct 25, 2021

I will open this PR now after @lahma encouraged me to do so 😉
Please note that this is still very much a work in progress and I won't be really able to work on it as much as I want in the coming weeks.

What's really left is implementing the tests for the module loading and optimization and eventual refactoring.

fixes #430

@lahma
Copy link
Collaborator

lahma commented Oct 25, 2021

Would you like some initial code review already or at later stage,?

@pluethi1
Copy link
Contributor Author

@lahma some initial feedback is always welcome :)

@sebastienros
Copy link
Owner

sebastienros commented Oct 25, 2021

How close did you follow the spec?

Update
I see comments like //https://tc39.es/ecma262/#sec-finishdynamicimport so this must mean you followed them

Jint/HoistingScope.cs Outdated Show resolved Hide resolved
@sebastienros
Copy link
Owner

Great effort. I wish you had added a simple example to show it works. Unless it doesn't ;)

Jint/Engine.Modules.cs Outdated Show resolved Hide resolved
Jint/Engine.Modules.cs Outdated Show resolved Hide resolved
Jint/Options.Extensions.cs Outdated Show resolved Hide resolved
Jint/Options.cs Outdated Show resolved Hide resolved
Jint/Options.cs Outdated Show resolved Hide resolved
Jint/Options.cs Outdated Show resolved Hide resolved

namespace Jint.Runtime.Modules
{
public sealed record ResolvedBinding(JsModule Module, string BindingName)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally make every type internal (and sealed) until someone comes up with a use case of having public types

@lahma
Copy link
Collaborator

lahma commented Oct 25, 2021

@pluethi1 this is looking really good! some remarks for in-progress code, hopefully the test suite will show possible problems.

@lahma
Copy link
Collaborator

lahma commented Nov 6, 2021

@pluethi1 I hope you don't mind that I pushed some tweaks to test selection:

  • skip json-modules and top-level-await tests - these are out of scope for this PR I believe
  • enable module tests - so we can see what's missing (I see now Esprima problems that should be fixed on Esprima side)
  • skip files with _FIXTURE as part of name, these are not meant to be evaluated as tests

Only 115 failing tests of 599 so it's already pretty close. I'll look into the Esprima problems and please let us know if you want any help.

EDIT: ignored import-assertions so now 106/599 failing

@pluethi1
Copy link
Contributor Author

pluethi1 commented Nov 9, 2021

So i was finally able to work on this again :)

I got down to 11/599 failing tests so i'd say that's a rather big improvement.
The failing ones are caused by issues with Esprima. One issue with the 'export * as ns' declarations i already fixed in esprima with the PR sebastienros/esprima-dotnet#196.
The other tests that fail use string literals as export/import specifier which i was confused about as the spec doesn't state that these specifiers can be string literals (imports: https://tc39.es/ecma262/#prod-ImportSpecifier, exports: https://tc39.es/ecma262/#prod-ExportSpecifier)
Is there any way to skip specific files? Or does that need to be hardcoded?

Also i refactored the code a little, based on the initial review. I decided to remove the ability to load remote modules into the engine, as it's not a real requirement in my eyes and definetly poses a security liability.

I'll also prepare a little example to demonstrate how to load modules into a script context.

@lahma
Copy link
Collaborator

lahma commented Nov 9, 2021

@pluethi1 great work!

I also did some research on the Esprima errors and I'm willing to mark them as skipped (skipped.json) and maybe raise a separate issue about the missing bits so we don't forget. There's the section at the end of the file for Esprima problems.

An example project would be great, maybe utilizing NPM to so it's a real-life scenario, maybe lodash or similar library that is not tied to browser context?

@lahma
Copy link
Collaborator

lahma commented Dec 4, 2021

@pluethi1 can we help out here? If you are busy that's fine too. Just checking the status.

@pluethi1
Copy link
Contributor Author

pluethi1 commented Dec 9, 2021

@lahma I have been rather busy the last couple of weeks, so this PR got pushed back a bit on my ToDo 😅. As for your request to show an example with npm: I think supporting npm is a bit out of scope for this feature as @PeterWone mentioned on issue #430 most npm packages rely on dependencies of built in modules of node.js. Also some npm modules use the CommonJS API which is not implemented at the moment.
My intention with this PR was a more simpler approach to include modules - which you have written yourself - from the local file system.
If npm i supposed to be a "must have" feature, I would apprectiate some help.

@lahma
Copy link
Collaborator

lahma commented Dec 9, 2021

@lahma I have been rather busy the last couple of weeks, so this PR got pushed back a bit on my ToDo 😅.

Hey no rush, just wanted to check whether you had lost all hope with the Jint infrastructure 😉

If npm i supposed to be a "must have" feature, I would apprectiate some help.

It most certainly isn't, it would be nice to have somewhat good abstraction to build features upon later, but also interfaces and structures can be modified later on (we have the beta label).

I think this is quite near already, just missing the green build mostly. Many of the problems were esprima related if I recall right so that's currently something that this PR cannot address unless we got some blockers fixed with the latest release (you need to rebase for that).

Please let us know if we can be of help.

@lahma
Copy link
Collaborator

lahma commented Dec 19, 2021

Checked rest of Esprima related problems and this PR should address them: sebastienros/esprima-dotnet#217

@PeterWone
Copy link

I don't think it is possible to overstate the value of access to npm modules. However...

The biggest problem with this is not the technical challenge of implementing node externals, but the security implications of scripts having these capabilities. To sandbox this implementations of node externals would have to expressly respect the sandbox.

  • File system access would have to be limited to children of a logical root defined for the engine instance. This also means preventing back-pathing.
  • Network access would have to be limited to whitelisted subnets and domains.

There may be other things.

@lahma
Copy link
Collaborator

lahma commented Dec 20, 2021

Jint tries to be safe by default and it's also reflected in this feature, module loading is disabled by default. As a first iteration I think it's enough to warn in activation code documentation that there can be security concerns. A truly sandboxed loader can be an enhancement if someone is such willing to contribute.

@lahma
Copy link
Collaborator

lahma commented Dec 21, 2021

@pluethi1 I hope you don't mind I pushed a small cleanup/documentation commit to tweak remaining things I found / were noticed in PR review. With latest Esprima the build is now green and I guess this could be ready for review after you check that I didn't mess anything up with my commit?

I'm OK if you want to revert my changes too as these were given, not asked for.

return module;
}

if (!ModuleLoader.TryLoadModule(specifier, referencingModuleLocation, out var moduleSourceCode, out var moduleLocation))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts about returning the AST from the module loader so it could cache it. Right now it can only cache the source. Would it be beneficial, like a loader could be shared across engines, and even provide some sort of thundering-herd protection? Note that it could already do that and still return a string. Could even be a custom loader wrapper.

Today we can cache parsed programs, and create an engine from that, but returning a string means parsed modules can't be cached, so each execution will have to parse the same module again.

GetExportEntries(true, defaultDeclaration.Declaration, exportEntries);
break;
case ExportAllDeclaration allDeclaration:
//Note: there is a pending PR for Esprima to support exporting an imported modules content as a namespace i.e. 'export * as ns from "mod"'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still?

Comment on lines +146 to +151
//https://tc39.es/ecma262/#sec-performpromisethen
//...
//13. If resultCapability is undefined, then
// a. Return undefined
//14. Else
// a. Return resultCapability.[[Promise]]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//https://tc39.es/ecma262/#sec-performpromisethen
//...
//13. If resultCapability is undefined, then
// a. Return undefined
//14. Else
// a. Return resultCapability.[[Promise]]
// https://tc39.es/ecma262/#sec-performpromisethen
// ...
// 13. If resultCapability is undefined, then
// a. Return undefined
// 14. Else
// a. Return resultCapability.[[Promise]]

Jint/Native/Promise/PromiseOperations.cs Outdated Show resolved Hide resolved
Jint/Runtime/Modules/IModuleSource.cs Outdated Show resolved Hide resolved
Jint/Runtime/Modules/FileModuleSource.cs Outdated Show resolved Hide resolved
@pluethi1
Copy link
Contributor Author

@lahma I don't mind at all! Thanks a lot for helping out :)
It looks good to me. In my opinion this is ready to be merged after review :)

@lahma lahma marked this pull request as ready for review December 21, 2021 20:52
lahma and others added 2 commits December 21, 2021 23:38
@lahma
Copy link
Collaborator

lahma commented Dec 22, 2021

@sebastienros I think I went more towards your suggestions in latest commit 62351ad , so now only public ModuleLoader class that can be extended and tweaked for custom module loading strategies.

@sebastienros
Copy link
Owner

Gitter is down, it let me know only when I typed the whole message ;) checking now

@lahma
Copy link
Collaborator

lahma commented Dec 22, 2021

Gitter is down, it let me know only when I typed the whole message ;) checking now

I know, I wrote you a whole novel short story about design decisions...


protected virtual string LoadModuleSourceCode(Uri location)
{
if (!location.IsFile)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe should call .LocalPath and then check that it's under the defaultBasePath. For instance to block files containing ../ which would go above it.

Copy link
Owner

@sebastienros sebastienros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you agree with the changes I can do it


namespace Jint.Runtime.Modules;

public class ModuleLoader : IModuleLoader
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DefaultModuleLoader, because it's meant to be replaced.

{
private readonly Uri _defaultReferencingLocation;

public ModuleLoader(string defaultBasePath)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basePath, because there is no fallback, so "default" doesn't make much sense to me.


public class ModuleLoader : IModuleLoader
{
private readonly Uri _defaultReferencingLocation;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Uris since it only deals with files? Calling LocalPath should fold everything and then we can check for the basepath.

@lahma
Copy link
Collaborator

lahma commented Dec 23, 2021

If you agree with the changes I can do it

Please do, changes sound logical.

@sebastienros sebastienros merged commit 0c223f4 into sebastienros:main Dec 23, 2021
@pluethi1 pluethi1 deleted the module-loading branch December 27, 2021 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Module support
4 participants