NativeScript / ios-jsc

NativeScript for iOS using JavaScriptCore
http://docs.nativescript.org/runtimes/ios
Apache License 2.0
299 stars 59 forks source link

8 Enum values in objc!UIKit.d.ts are way too large #1150

Closed NickIliev closed 5 years ago

NickIliev commented 5 years ago

@bguijt commented on Mon Jun 03 2019

Please check this diff: https://github.com/NativeScript/NativeScript/commit/f54f71bc60df8ba0772fe82f2fc911c2c3494e9d#diff-a4508a7cbf4cc46900ecccb75a31c03d

and find all value changes from 4294967295 (which is 0xFFFFFFFF) to 18446744073709551615 (which is 0xFFFFFFFFFFFFFFFF) which is an overflow to the JS numerical system and therefore incorrect.

This is probably OK for regular JS projects, however we use a commercial code obfuscator which is unable to handle these literals.

Please change these literals back to what they were ;-)

NathanaelA commented 5 years ago

@bguijt - I'm not sure they can change those back. The NativeScript iOS engine now supports accessing 64 bit numbers from the iOS side properly (which is a REALLY REALLY REALLY good thing :grinning:). I suspect all the ANY values where changed to the highest value now accepted.

However, I don't understand what a Obfuscator would be accessing the .d.ts files; those files SHOULD NOT be included with the app; they are purely used by TypeScript to output JS code... So this is probably a bug in your rules for the Obfuscator...

bguijt commented 5 years ago

Hi @NathanaelA,

I agree the ability to use full 64-bit integers is a big plus!

Our obfuscator is not applied to the *.d.ts files at all - it is applied to the output from webpack, in this case vendor.js, which contains this large numeric literal.

However, despite our obfuscator unable to parse this, there is still a problem: These 64-bit literals are NOT processed correctly through the Typescript compiler. For instance, the source for this enum value assigns the value 18446744073709551615 to the Any/All enum values. After compiling TS to JS, this literal is rounded to 18446744073709552000 (0x10000000000000180) which flips almost all bits to 0.

The offending compiled source is this (tns-core-modules/ui/html-view/html-view.ios.js):

function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
var html_view_common_1 = require("./html-view-common");
__export(require("./html-view-common"));
var HtmlView = (function (_super) {
    __extends(HtmlView, _super);
    function HtmlView() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    HtmlView.prototype.createNativeView = function () {
        var view = UITextView.new();
        view.scrollEnabled = false;
        view.editable = false;
        view.selectable = true;
        view.userInteractionEnabled = true;
        view.dataDetectorTypes = 18446744073709552000;
        return view;
    };
    Object.defineProperty(HtmlView.prototype, "ios", {
        get: function () {
            return this.nativeViewProtected;
        },
        enumerable: true,
        configurable: true
    });
    HtmlView.prototype.onMeasure = function (widthMeasureSpec, heightMeasureSpec) {
        var nativeView = this.nativeViewProtected;
        if (nativeView) {
            var width = html_view_common_1.layout.getMeasureSpecSize(widthMeasureSpec);
            var widthMode = html_view_common_1.layout.getMeasureSpecMode(widthMeasureSpec);
            var height = html_view_common_1.layout.getMeasureSpecSize(heightMeasureSpec);
            var heightMode = html_view_common_1.layout.getMeasureSpecMode(heightMeasureSpec);
            var desiredSize = html_view_common_1.layout.measureNativeView(nativeView, width, widthMode, height, heightMode);
            var labelWidth = widthMode === html_view_common_1.layout.AT_MOST ? Math.min(desiredSize.width, width) : desiredSize.width;
            var measureWidth = Math.max(labelWidth, this.effectiveMinWidth);
            var measureHeight = Math.max(desiredSize.height, this.effectiveMinHeight);
            var widthAndState = html_view_common_1.View.resolveSizeAndState(measureWidth, width, widthMode, 0);
            var heightAndState = html_view_common_1.View.resolveSizeAndState(measureHeight, height, heightMode, 0);
            this.setMeasuredDimension(widthAndState, heightAndState);
        }
    };
    HtmlView.prototype[html_view_common_1.htmlProperty.getDefault] = function () {
        return "";
    };
    HtmlView.prototype[html_view_common_1.htmlProperty.setNative] = function (value) {
        var _a;
        var htmlString = NSString.stringWithString(value + "");
        var nsData = htmlString.dataUsingEncoding(NSUnicodeStringEncoding);
        this.nativeViewProtected.attributedText = NSAttributedString.alloc().initWithDataOptionsDocumentAttributesError(nsData, (_a = {}, _a[NSDocumentTypeDocumentAttribute] = NSHTMLTextDocumentType, _a), null);
    };
    return HtmlView;
}(html_view_common_1.HtmlViewBase));
exports.HtmlView = HtmlView;
//# sourceMappingURL=html-view.ios.js.map

NOTE: This is not all. Further down the build, this file is bundled into vendor.js and is assigned yet another literal, which is 0x10000000000000000 (dec: 18446744073709551616, or 2^64) in this snippet:

t.prototype.createNativeView=function(){var e=UITextView.new();return e.scrollEnabled=!1,e.editable=!1,e.selectable=!0,e.userInteractionEnabled=!0,e.dataDetectorTypes=0x10000000000000000,e}

What could be the right solution to this problem? We use Typescript 3.1.1, nativescript 5.3.2, tns-core-modules 5.3.1 and tns-ios 5.2.0. Build is running in Node v10.15.3 on macOS 10.14.5.

mbektchiev commented 5 years ago

@bguijt You're right, these constants overflow the JSValues and indeed are not represented correctly. Since JSC still doesn't support BigInts, I think that the best way to fix it is to convert all constants in the .d.ts files to their signed equivalents. This way 0xFFFFFFFFFFFFFFFF will become -1 and will be correctly marshaled to native without losing precision.

bguijt commented 5 years ago

@mbektchiev Would Number.MAX_SAFE_INTEGER not be a safer integer?

I tried your suggested literal by adding the following snippet to my Webpack config.module.rules (which replaces all large numeric literals with 0xFFFFFFFFFFFFFFFF):

                {
                    test: /\.js$|\.ts$/,
                    loader: StringReplacePlugin.replace({
                        replacements: [
                            {
                                pattern: /0x[0-9a-fA-F]{13,100}|[0-9]{16,100}/mg,
                                replacement: (match) => {
                                    if (match != "340282346638528859811704183484516925440") {
                                        console.warn(`\x1b[1m\x1b[33mWARNING: Javascript source error: Large numeric literal ${match} found - replacing with 0xFFFFFFFFFFFFFFFF\x1b[39m\x1b[21m`);
                                        return "0xFFFFFFFFFFFFFFFF";
                                    } else {
                                        // 340282346638528859811704183484516925440.000000 is a max float value from animation.ios.js
                                        return match;
                                    }
                                }
                            }
                        ]
                    })
                }

This gives me the following console warning:

WARNING: Javascript source error: Large numeric literal 18446744073709552000 found - replacing with 0xFFFFFFFFFFFFFFFF

However, my bundledvendor.js (still) contains the literal 0x10000000000000000.

I tried with Number.MAX_SAFE_INTEGER, and this works well!

mbektchiev commented 5 years ago

0xFFFFFFFFFFFFFFFF is actually the same number written as hexadecimal. To workaround the issue you can simply replace it with -1