Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Implement Automatic Proxy Between the Host JS and the Prepack JS Environment #644

Open
sebmarkbage opened this issue May 15, 2017 · 4 comments

Comments

@sebmarkbage
Copy link
Contributor

sebmarkbage commented May 15, 2017

While writing #397 most of what I wrote was sloppy proxying code between the host (Node) JS environment and the Prepack JS environment. 1:1. It would be nice to have an automatic way of doing that.

Proxy

The use case for Proxy in the JS spec is to be able to put up a membrane between two environments. We can write a two-way bridging model between the two environments. Whenever we expose an unseen object reference from the host to JS we wrap it in a "proxy object value" and store that in a weak map for reuse. Similarly, when we call into the JS host we unwrap such objects. If it is a new object value from the JS side, we wrap it in a host Proxy object on the native JS side. Abstract values are not allowed on this level. Prepacked code can introspect values using __isAbstract and provide a fallback in those cases.

This lets us call seamlessly between the two environments.

Now we can expose new functionality such as requiring arbitary Node modules from within a Prepacked file. This allows custom Prepack helpers to be written in user space code rather than having to expose anything about the internal APIs and implementation details.

"Plugin" API

if (typeof __prepackRequire === 'function') {
  let fs = __prepackRequire('fs');
  fs.writeFileSync(...);
}

We have requests for some plugin API for various custom static analysis use cases. One example, is being able to generate stand-alone CSS files from the content of a JS object and replace the JS object with a string to CSS class name.

This might be the only plugin mechanism we really need. Now a JS library can just execute arbitrary code inside their Prepacked helpers.

function generateCSS(style) {
  if (typeof __prepackRequire === 'function') {
    let fs = __prepackRequire('fs');
    let process = __prepackRequire('process');
    let options = parseOptions(process.argv);
    let className = 'AutoCSS' + Math.random();
    let cssText = `.${className} { ${generateStyle(style)} }`;
    fs.writeFileSync(options.cssOutputFile, cssText, 'utf8');
    return className;
  } else {
    throw new Error('All CSS needs to be statically generated.');
  }
}

let MyOtherModule = require('MyOtherModule');

let div = document.createElement('div');
div.className = generateCSS({
  position: 'absolute',
  width: '100%',
  border: `${MyOtherModule.borderWidth}px solid ${MyOtherModule.borderColor}`,
});

->
JS file:

let div = document.createElement('div');
div.className = 'AutoCSS123';

CSS file:

.AutoCSS123 {
  position: absolute;
  width: 100%;
  border: 45px solid #6789AB;
}

We might need some security mechanism to prevent abuse. E.g. only allowing particular modules to be require or just a global option allowArbitraryExecution.

@sebmarkbage
Copy link
Contributor Author

sebmarkbage commented May 15, 2017

Note that this is also related to #584. We want libraries to be able to successfully strip their code out when Prepack is not in use.

@ryaninvents
Copy link

This is a great idea! I'd suggest perhaps that this not be the only plugin mechanism available; I'm having trouble picturing how one would use this to trace value flow through a program. For instance, imagine marking places where user input is accepted, tracing its flow, and ensuring that it is SQL- and HTML-sanitized before being used in a database call. This information could then be logged or passed to some sort of linter.

I'm interested in discussing plugins of this nature, so if I should open a new issue for that conversation please let me know.

@sebmarkbage
Copy link
Contributor Author

@r24y That sounds related to "poisoned" values. #444 However, I'm not sure Prepack is suitable for that general use case since it doesn't track any residual code branches outside the optimized path (currently just initialization).

@ryaninvents
Copy link

@sebmarkbage Point taken with regard to Prepack only tracking a single code branch at a time.

Thanks for the link to 444. "Poisoned" values seem exactly like what I was describing, but I was using that as a specific example of a general pattern: tagging values with arbitrary metadata and using that metadata as the information flows through the system. The discussion over there describes "tagging" values in a way that matches what I had in my head.

After thinking a bit more on what I was trying to say, it seems like the plugin API proposed here would work perfectly. Take for example showing documentation in your IDE on mouseover. Given the following file (pardon the pseudocode):

import uuid from 'uuid';

const id = uuid.v4();

If the cursor were over the id declaration, it could run a Babel transform and create:

import uuid from 'uuid';

const id = uuid.v4();
__emitDocumentation(id);

Which would return an HTML snippet of the uuid.v4 documentation to the IDE.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants