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

Function toString does not behave the same with hermes turned on #114

Closed
Collin3 opened this issue Sep 20, 2019 · 43 comments
Closed

Function toString does not behave the same with hermes turned on #114

Collin3 opened this issue Sep 20, 2019 · 43 comments
Assignees
Labels
enhancement New feature or request

Comments

@Collin3
Copy link

Collin3 commented Sep 20, 2019

I use react-native-qrcode and noticed that after upgrading the React Native v0.60 and turning on hermes, the QR code was broken. We debugged the issue and found that the problem comes when the library tries to stringify this renderCanvas function and then insert it into some HTML to show in the webview.

When calling .toString() on the function we found that we were actually getting

function renderCanvas(a0) { [ native code ] }

instead of getting a string of the actual function.

I realize this is probably not a very common use case, but figured I'd report it in case anyone else is seeing anything similar.

@avp
Copy link
Contributor

avp commented Sep 20, 2019

Hermes compiles all source to bytecode before executing it on a phone. As such, we discard source information including the actual text of the code, parameter names, etc. during compilation. This allows us to keep the size of our bytecode file small and ensure that the code is quick to execute.

Due to this, we do not support actually getting the source code using Function.prototype.toString - it's not available in the bytecode file, so we can't print the source.

@Collin3
Copy link
Author

Collin3 commented Sep 20, 2019

Ahh, interesting. Thanks for the explanation! I’ll close this issue then.

@Collin3 Collin3 closed this as completed Sep 20, 2019
@cawfree
Copy link

cawfree commented Sep 26, 2019

I don't suppose there'd be any way to inform Hermes that we'd like to hold onto the conventional result of a call toString()?

@avp
Copy link
Contributor

avp commented Sep 27, 2019

@cawfree Correct: Hermes does not currently support a way to include the original source in the bytecode bundle.

@tmikov
Copy link
Contributor

tmikov commented Sep 27, 2019

@cawfree this is an interesting idea. Perhaps we could consider a kind of annotation telling our compiler to preserve the source of a particular function. I can see how obtaining the source in a different way may become very cumbersome.

I think it is doable. The question is what would such an annotation look like. One possibility would be to include "use source" in the beginning of the function (similar to "use strict").

Do you have any ideas or a preference?

@tmikov tmikov reopened this Sep 27, 2019
@tmikov tmikov self-assigned this Sep 27, 2019
@ljharb
Copy link

ljharb commented Sep 27, 2019

You may want to mirror https://github.com/tc39/proposal-function-implementation-hiding and go with something like “show implementation”.

@SuhairZain
Copy link

@tmikov Were you able to get somewhere in implementing this? One of our production apps broke after upgrading to hermes because we call toString() on functions and it doesn't return the expected results.

@matthargett
Copy link

At various points in the DFA, it seems like it would be possible to know that toString() is being called on a function object/prototype, and then only include the source for those precise functions. If that is possible, figuring how to store them in the binary format so the function string usages are friendly to CPU prefetch and don’t disrupt current L1 cache hit rates would be nice to have (tm).

@tmikov
Copy link
Contributor

tmikov commented Feb 25, 2020

Unfortunately we don't have a use case for this internally, so the priority for implementing it is not very high. I would love to see a PR from community, if anyone is interested I can give detailed pointers about what needs to be done.

@lb90
Copy link

lb90 commented Mar 5, 2020

I would love to see a PR from community, if anyone is interested I can give detailed pointers about what needs to be done.

Hi, I can work on that. Could you write down all the details?

@tmikov
Copy link
Contributor

tmikov commented Apr 1, 2020

@lb90 sorry for the delay! Are you still interested?

@lb90
Copy link

lb90 commented Apr 1, 2020

Hi @tmikov! Yes

@tmikov
Copy link
Contributor

tmikov commented Apr 3, 2020

@lb90 great, I will start filling up the details. We will do it incrementally, it should be a fun project!

@chj-damon
Copy link

Is there someone making some progress about this? I think this case is very important when working with webview. we will injectJavaScript to webview to execute it. If Hermes cannot support this, which means webview is kind of broken.

I don't understand why Hermes team thinks it's not a big problem.

@tmikov
Copy link
Contributor

tmikov commented Jan 26, 2021

@chj-damon what is preventing you from including the source of the function as a string?

@chj-damon
Copy link

chj-damon commented Jan 27, 2021

for example, I'm using echarts in webview. it provides an option like this:

{
            title: {
              text: 'ECharts demo'
            },
            tooltip: {
              show: true,
              formatter: (params) => params.name + ': ' + params.value,
            },
            legend: {
              data: ['销量']
            },
            xAxis: {
              data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
            },
            yAxis: {},
            series: [
              {
                name: '销量',
                type: 'bar',
                data: [5, 20, 36, 10, 10, 20]
              }
            ]
          }

you see there's a formatter function I declared in the option, when Hermes was enabled, this formatter function will be transformed to formatter: function formatter(params) {[bytecode]} before I inject it to webview. which means formatter will not work.

@ljharb
Copy link

ljharb commented Jan 27, 2021

if formatter is being changed into a string, then having the source code won't help you - it needs to be transmitted as a function.

@tmikov
Copy link
Contributor

tmikov commented Jan 28, 2021

@ljharb I think the idea may be to eval() the entire string in the WebView, which will then recreate the function. Technically this is not JSON.

@tmikov
Copy link
Contributor

tmikov commented Jan 28, 2021

@chj-damon I see the appeal of being able to reuse the same JS function in the WebView, but there is a fundamental problem with that approach - there is no guarantee that the WebView and Hermes support the same language features. You are applying one set of Babel transforms to JS for Hermes, but the WebView has a different JS engine and it may need a different set of transforms. So, as soon as you start using it for anything more complex, differences will appear.

@ljharb
Copy link

ljharb commented Jan 28, 2021

I think the idea may be to eval() the entire string

Given that there's no way in the language to guarantee a function is portable (can be toStringed and re-evalled elsewhere, even in the same global environment), that seems like an inherently brittle design that needs rearchitecting.

@chj-damon
Copy link

maybe this problem will be solved if Hermes support some signature, like 'noHermes' which defined in a function, like react-native-reanimated@2

formatted: function(params) {
   'noHermes';
   return params.name;
}

Hermes will ignore this function if it sees 'noHermes' signature.

react-native-reanimated@2 using 'worklet' to define a function that will be running in UI thread.

@Huxpro Huxpro self-assigned this Feb 18, 2021
facebook-github-bot pushed a commit that referenced this issue Jul 10, 2021
Summary:
This diff introduced the notion of "source visibility" and
1. add it to ESTree FunctionLikeNode
2. compute the visibility in semantic validator according to
   the override rule.

This is the fundation to implemetn the Stage2 proposal
"Function Implementation Hiding":
https://github.com/tc39/proposal-function-implementation-hiding,
as well as the `'show source'` extension that Hermes is proposing
to accomendate the needs of preserving source for `toString` at
#114

Note that previously by computing the visibility in semantic
validation phase, we can scale this to support external AST
and lazy compilation properly.

Reviewed By: avp

Differential Revision: D26269664

fbshipit-source-id: d4eba4a78c0e41c0cd7d2320fe2a469ba7928d95
@denissb
Copy link

denissb commented Feb 14, 2023

TLDR: Hermes 0.8.1 (available starting from React Native 0.65-rc.3) introduced a special directive "show source" to make toString returning original source code.

This is awesome 👍 🙏 . However, when running with React Native 0.67.2, Android and Hermes and injecting code into a react-native-webview, I notice that triggering the app to reload seems to reintroduce the problem despite annotating the function with 'show source':

function myFunctionWithShowSource() {
  'show source'
  // ...
}

Causes this in the webview console:

Uncaught ReferenceError: bytecode is not defined
    at myFunctionWithShowSource (<anonymous>:41:40)
    at <anonymous>:41:52
    at <anonymous>:57:3

I'm unable to create a minimal reproducible example at the moment; it's possible that the issue could be somewhere in my own setup or implementation. Just leaving this here in case someone else runs into issues with show source.

For those of you wondering, I just tried reproducing this problem on React Native 0.71.3 and it is no longer valid.

My basic reproduction scenario:

const testFunction = () => {
  'show source';
  Alert.alert('Hello world!!');
}

function App(): JSX.Element {
  const isDarkMode = useColorScheme() === 'dark';

  const showAlert = useCallback(() => {
    console.log(testFunction.toString());
    testFunction();
  }, []);
  ....

This implementation logs the following to the console regardless of weather I am in debug or release modes using Hermes engine:

function testFunction() {
    'show source';

    _reactNative.Alert.alert('Hello world!!');
  }

It even works for hot reloading when I change the implementation inside testFunction 🥳

@tmikov tmikov removed their assignment Feb 14, 2023
@arlovip
Copy link

arlovip commented Feb 24, 2023

For me, "show source" only works on a archive package in release mode with Archiving like ./gradlew clean && ./gradlew bundleRelease. It won't work longer after a new js bundle is downloaded using react-native-code-push. So the issue also exists. Is there any better solution for that?

@tmikov
Copy link
Contributor

tmikov commented Feb 24, 2023

@lchenfox out of curiosity: does the new bundle downloaded using react-native-code-push contain JS source?

@arlovip
Copy link

arlovip commented Feb 27, 2023

@lchenfox out of curiosity: does the new bundle downloaded using react-native-code-push contain JS source?

Yes. I fixed a bug using react-native-code-push, then the show source won't take effect in my app. Strangly, it works without downloading a new patch package using react-native-code-push.

@tmikov
Copy link
Contributor

tmikov commented Feb 27, 2023

@lchenfox Hermes is not intended to run from source in production, we strongly recommend against it.

@chj-damon
Copy link

@lchenfox Hermes is not intended to run from source in production, we strongly recommend against it.

does this mean react-native-code-push means nothing when Hermes enabled?

@arlovip
Copy link

arlovip commented Feb 27, 2023

@lchenfox Hermes is not intended to run from source in production, we strongly recommend against it.

Yeah. Theoretically speaking, however, using react-native-code-push to fix other bugs online shouldn't affect the source that has already been added "show source" before in production. In fact, it indeed makes the formatter function using show source unavailable. If so, show source will be always unavailable after I release each patch package via react-native-code-push?

@arlovip
Copy link

arlovip commented Feb 27, 2023

@lchenfox Hermes is not intended to run from source in production, we strongly recommend against it.

does this mean react-native-code-push means nothing when Hermes enabled?

react-native-code-push also takes effect, because the online bug was fixed. However, the formatter function using "show source" won't take effect after releasing this patch package. I tried many times, it can be reproduced. the formatter function like:

_tooltipFormatter = (params) => {
        'show source';
        let htmlStr = '<div>' + params[0].name + '<br/>';
        for (let i = 0, l = params.length; i < l; i++) {
            const color = params[i].color; 
            htmlStr += '<span style="margin-right:5px;display:inline-block;width:10px;height:10px;border-radius:5px;background-color:' + color + ';"></span>';
            let symbolIndex = params[i].seriesName.indexOf('@');
            const hasSymbol = symbolIndex >= 0;
            const seriesName = hasSymbol ? params[i].seriesName.substring(0, symbolIndex) : params[i].seriesName;
            symbolIndex += 1;

            const unit = hasSymbol ? params[i].seriesName.substr(symbolIndex) : '';
            let value = '--';
            if (unit === '$' || unit === '¥') {
                value = unit + (params[i].data !== undefined ? params[i].data : '--');
            } else {
                value = (params[i].data !== undefined ? params[i].data : '--') + unit;
            }
            htmlStr += seriesName + '\: ' + value + '<br/>';
        }
        htmlStr += '</div>';
        return htmlStr;
};

tooltip: {
          trigger: 'axis',
          confine: true, 
          formatter: this._tooltipFormatter,  // Uses `show source`.
          backgroundColor: 'white', 
          borderColor: '#badafa',  
          borderWidth: 1,           
          padding: 2,               
          textStyle: {
               color: '#0273f2', 
               fontSize: 12,      
          }, 
}

@arlovip
Copy link

arlovip commented Feb 27, 2023

In case I may make myself unclear. Here is a demo reproduced by npx react-native init chartsdemo --version 0.71.1: App.js🔗

Code snippets

const option = {
      ......
      tooltip: {
        show: true,
        formatter: function(params) {
          "show source";  // Add "show source" in production.
          if (Array.isArray(params)) {
            return params[0].name + ": " + params[0].data;
          }
          return params.name + ": " + params.data;
        }
     ......
}

return (
  <View style={{ flex: 1, backgroundColor: "white" }}>
    <RNEChartsPro height={350} option={option} />
    <Text>
      Test echarts1
    </Text>
  </View>
);

Steps

  • Archiving a release apk, then install the apk on a real device.
  • The tooltip shows as expected. That is to say, "show source" works in the formatter function.
  • The text shows Test echarts1 normally on the device.
  • Changing the Test echarts1 above to Test echarts2.
  • Releasing a new patch package using react-native-code-push
  • The text shows Test echarts2 normally on the device.
  • The tooltip shows nothing. "show source" DO NOT WORK in the formatter function.

I don't know how to fix this. Please help. Thanks a lot.

@tmikov @chj-damon

@Stophface
Copy link

Stophface commented Mar 11, 2023

I was banging my head against that why something works in debug, but not in production. I tried implementing 'show source', without luck. Still having the same problem. Any ideas why this is not working in this context

const someObject = {
        component: selectedColor => {
          'show source';
          return <Wuii color={selectedColor} />;
        },
        id: WUI,
  },

@tmikov
Copy link
Contributor

tmikov commented Mar 11, 2023

@Stophface the first step to identifying the problem is to look at the source that is actually passed to Hermes after being transformed by various tools in the build pipeline.

@Stophface
Copy link

@tmikov How can I see the source code? Plus: I thought adding 'show source' and then calling .toString() would keep the string representation

@tmikov
Copy link
Contributor

tmikov commented Mar 11, 2023

@Stophface In order to identify whether this is a problem in Hermes, we need to be able to examine the input given to Hermes. Unfortunately we can't help you debug parts of the build pipeline that happen before Hermes. It is quite possible that the "show source" annotation is stripped before it gets to the Hermes compiler, for example by a minifier.

@KonstantinKostianytsia
Copy link

As a workaround, I added a script that stringifies the needed git module. So the processed module looks like

export const injectionScript = `
/// Functions from the module are here
`;

And I can use injectionScript in my WebView. I would have been happy to use 'show source' if it had worked.

@linonetwo
Copy link

Expo performs very unstable with this feature, it works 1 out of 10 times...

I finally find for the first time it bundles, it does not work. And you can add a console.log to the file that uses 'show source';, let it hot reload, then it works. (on development)

@wcandillon
Copy link

tree.js uses fn.toString() in their shader library.
For three.js to run on React Native we need to patch that functionality.
I just thought I would mention it as a use-case.

in RN Skia and RN WebGPU we use this feature for e2e testing as well (but here we use v8 not hermes).

@tmikov
Copy link
Contributor

tmikov commented Aug 20, 2024

@wcandillon what functionality is missing?

This task should have been closed a long time ago, since Hermes supports fn.toString(). Closing it now.

@tmikov tmikov closed this as completed Aug 20, 2024
@wcandillon
Copy link

wcandillon commented Aug 20, 2024 via email

@tmikov
Copy link
Contributor

tmikov commented Aug 20, 2024

@wcandillon yes, as was explained in this comment on this same issue 3 years ago, adding a "show source" directive to the body of the function causes Hermes to preserve the source.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests