[gjs/gnome-3-34] GLib: Override string manipulation functions



commit ac1b11ee241a450e5ad5f12ac176b1776354e206
Author: Philip Chimento <philip chimento gmail com>
Date:   Sun Sep 15 22:15:48 2019 -0700

    GLib: Override string manipulation functions
    
    There are a number of string manipulation functions in GLib that return
    a pointer to the same passed-in string. These can't be annotated
    properly, and will mostly crash. We want to prevent user code from
    calling them, and so we override them.
    
    In the overrides we provide approximate implementations of each function
    so that if they had happened to work in the past, they will continue
    working, but log a stack trace and a suggestion of what to use instead.
    
    Closes: #283

 installed-tests/js/testGLib.js | 121 +++++++++++++++++++++++++++++++++++++
 modules/overrides/GLib.js      | 133 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 254 insertions(+)
---
diff --git a/installed-tests/js/testGLib.js b/installed-tests/js/testGLib.js
index 12735713..d25e3785 100644
--- a/installed-tests/js/testGLib.js
+++ b/installed-tests/js/testGLib.js
@@ -109,3 +109,124 @@ describe('GVariantDict lookup', function () {
         expect(variantDict.lookup('bar', new GLib.VariantType('s'))).toBeNull();
     });
 });
+
+describe('GLib string function overrides', function () {
+    let numExpectedWarnings;
+
+    function expectWarnings(count) {
+        numExpectedWarnings = count;
+        for (let c = 0; c < count; c++) {
+            GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+                '*not introspectable*');
+        }
+    }
+
+    function assertWarnings(testName) {
+        for (let c = 0; c < numExpectedWarnings; c++) {
+            GLib.test_assert_expected_messages_internal('Gjs', 'testGLib.js', 0,
+                `test GLib.${testName}`);
+        }
+        numExpectedWarnings = 0;
+    }
+
+    beforeEach(function () {
+        numExpectedWarnings = 0;
+    });
+
+    it('GLib.stpcpy', function () {
+        expect(() => GLib.stpcpy('dest', 'src')).toThrowError(/not introspectable/);
+    });
+
+    it('GLib.strstr_len', function () {
+        expectWarnings(4);
+        expect(GLib.strstr_len('haystack', -1, 'needle')).toBeNull();
+        expect(GLib.strstr_len('haystacks', -1, 'stack')).toEqual('stacks');
+        expect(GLib.strstr_len('haystacks', 4, 'stack')).toBeNull();
+        expect(GLib.strstr_len('haystack', 4, 'ays')).toEqual('aystack');
+        assertWarnings('strstr_len');
+    });
+
+    it('GLib.strrstr', function () {
+        expectWarnings(2);
+        expect(GLib.strrstr('haystack', 'needle')).toBeNull();
+        expect(GLib.strrstr('hackstacks', 'ack')).toEqual('acks');
+        assertWarnings('strrstr');
+    });
+
+    it('GLib.strrstr_len', function () {
+        expectWarnings(3);
+        expect(GLib.strrstr_len('haystack', -1, 'needle')).toBeNull();
+        expect(GLib.strrstr_len('hackstacks', -1, 'ack')).toEqual('acks');
+        expect(GLib.strrstr_len('hackstacks', 4, 'ack')).toEqual('ackstacks');
+        assertWarnings('strrstr_len');
+    });
+
+    it('GLib.strup', function () {
+        expectWarnings(1);
+        expect(GLib.strup('string')).toEqual('STRING');
+        assertWarnings('strup');
+    });
+
+    it('GLib.strdown', function () {
+        expectWarnings(1);
+        expect(GLib.strdown('STRING')).toEqual('string');
+        assertWarnings('strdown');
+    });
+
+    it('GLib.strreverse', function () {
+        expectWarnings(1);
+        expect(GLib.strreverse('abcdef')).toEqual('fedcba');
+        assertWarnings('strreverse');
+    });
+
+    it('GLib.ascii_dtostr', function () {
+        expectWarnings(2);
+        expect(GLib.ascii_dtostr('', GLib.ASCII_DTOSTR_BUF_SIZE, Math.PI))
+            .toEqual('3.141592653589793');
+        expect(GLib.ascii_dtostr('', 4, Math.PI)).toEqual('3.14');
+        assertWarnings('ascii_dtostr');
+    });
+
+    it('GLib.ascii_formatd', function () {
+        expect(() => GLib.ascii_formatd('', 8, '%e', Math.PI)).toThrowError(/not introspectable/);
+    });
+
+    it('GLib.strchug', function () {
+        expectWarnings(2);
+        expect(GLib.strchug('text')).toEqual('text');
+        expect(GLib.strchug('   text')).toEqual('text');
+        assertWarnings('strchug');
+    });
+
+    it('GLib.strchomp', function () {
+        expectWarnings(2);
+        expect(GLib.strchomp('text')).toEqual('text');
+        expect(GLib.strchomp('text   ')).toEqual('text');
+        assertWarnings('strchomp');
+    });
+
+    it('GLib.strstrip', function () {
+        expectWarnings(4);
+        expect(GLib.strstrip('text')).toEqual('text');
+        expect(GLib.strstrip('   text')).toEqual('text');
+        expect(GLib.strstrip('text   ')).toEqual('text');
+        expect(GLib.strstrip('   text   ')).toEqual('text');
+        assertWarnings('strstrip');
+    });
+
+    it('GLib.strdelimit', function () {
+        expectWarnings(4);
+        expect(GLib.strdelimit('1a2b3c4', 'abc', '_'.charCodeAt())).toEqual('1_2_3_4');
+        expect(GLib.strdelimit('1-2_3<4', null, '|'.charCodeAt())).toEqual('1|2|3|4');
+        expect(GLib.strdelimit('1a2b3c4', 'abc', '_')).toEqual('1_2_3_4');
+        expect(GLib.strdelimit('1-2_3<4', null, '|')).toEqual('1|2|3|4');
+        assertWarnings('strdelimit');
+    });
+
+    it('GLib.strcanon', function () {
+        expectWarnings(2);
+        expect(GLib.strcanon('1a2b3c4', 'abc', '?'.charCodeAt())).toEqual('?a?b?c?');
+        expect(GLib.strcanon('1a2b3c4', 'abc', '?')).toEqual('?a?b?c?');
+        assertWarnings('strcanon');
+    });
+});
diff --git a/modules/overrides/GLib.js b/modules/overrides/GLib.js
index db8eee08..5e8e9cdb 100644
--- a/modules/overrides/GLib.js
+++ b/modules/overrides/GLib.js
@@ -266,6 +266,20 @@ function _unpackVariant(variant, deep, recursive = false) {
     throw new Error('Assertion failure: this code should not be reached');
 }
 
+function _notIntrospectableError(funcName, replacement) {
+    return new Error(`${funcName} is not introspectable. Use ${replacement} instead.`);
+}
+
+function _warnNotIntrospectable(funcName, replacement) {
+    logError(_notIntrospectableError(funcName, replacement));
+}
+
+function _escapeCharacterSetChars(char) {
+    if ('-^]\\'.includes(char))
+        return `\\${char}`;
+    return char;
+}
+
 function _init() {
     // this is imports.gi.GLib
 
@@ -331,4 +345,123 @@ function _init() {
             return null;
         return _unpackVariant(variant, deep);
     };
+
+    // Prevent user code from calling GLib string manipulation functions that
+    // return the same string that was passed in. These can't be annotated
+    // properly, and will mostly crash.
+    // Here we provide approximate implementations of the functions so that if
+    // they had happened to work in the past, they will continue working, but
+    // log a stack trace and a suggestion of what to use instead.
+    // Exceptions are thrown instead for GLib.stpcpy() of which the return value
+    // is useless anyway and GLib.ascii_formatd() which is too complicated to
+    // implement here.
+
+    this.stpcpy = function () {
+        throw _notIntrospectableError('GLib.stpcpy()', 'the + operator');
+    };
+
+    this.strstr_len = function (haystack, len, needle) {
+        _warnNotIntrospectable('GLib.strstr_len()', 'String.indexOf()');
+        let searchString = haystack;
+        if (len !== -1)
+            searchString = searchString.slice(0, len);
+        const index = searchString.indexOf(needle);
+        if (index === -1)
+            return null;
+        return haystack.slice(index);
+    };
+
+    this.strrstr = function (haystack, needle) {
+        _warnNotIntrospectable('GLib.strrstr()', 'String.lastIndexOf()');
+        const index = haystack.lastIndexOf(needle);
+        if (index === -1)
+            return null;
+        return haystack.slice(index);
+    };
+
+    this.strrstr_len = function (haystack, len, needle) {
+        _warnNotIntrospectable('GLib.strrstr_len()', 'String.lastIndexOf()');
+        let searchString = haystack;
+        if (len !== -1)
+            searchString = searchString.slice(0, len);
+        const index = searchString.lastIndexOf(needle);
+        if (index === -1)
+            return null;
+        return haystack.slice(index);
+    };
+
+    this.strup = function (string) {
+        _warnNotIntrospectable('GLib.strup()',
+            'String.toUpperCase() or GLib.ascii_strup()');
+        return string.toUpperCase();
+    };
+
+    this.strdown = function (string) {
+        _warnNotIntrospectable('GLib.strdown()',
+            'String.toLowerCase() or GLib.ascii_strdown()');
+        return string.toLowerCase();
+    };
+
+    this.strreverse = function (string) {
+        _warnNotIntrospectable('GLib.strreverse()',
+            'Array.reverse() and String.join()');
+        return [...string].reverse().join('');
+    };
+
+    this.ascii_dtostr = function (unused, len, number) {
+        _warnNotIntrospectable('GLib.ascii_dtostr()', 'JS string conversion');
+        return `${number}`.slice(0, len);
+    };
+
+    this.ascii_formatd = function () {
+        throw _notIntrospectableError('GLib.ascii_formatd()',
+            'Number.toExponential() and string interpolation');
+    };
+
+    this.strchug = function (string) {
+        // COMPAT: replace with trimStart() in mozjs68
+        _warnNotIntrospectable('GLib.strchug()',
+            'String.trimLeft() until SpiderMonkey 68, then String.trimStart()');
+        return string.trimLeft();
+    };
+
+    this.strchomp = function (string) {
+        // COMPAT: replace with trimEnd() in mozjs68
+        _warnNotIntrospectable('GLib.strchomp()',
+            'String.trimRight() until SpiderMonkey 68, then String.trimEnd()');
+        return string.trimRight();
+    };
+
+    // g_strstrip() is a macro and therefore doesn't even appear in the GIR
+    // file, but we may as well include it here since it's trivial
+    this.strstrip = function (string) {
+        _warnNotIntrospectable('GLib.strstrip()', 'String.trim()');
+        return string.trim();
+    };
+
+    this.strdelimit = function (string, delimiters, newDelimiter) {
+        _warnNotIntrospectable('GLib.strdelimit()', 'String.replace()');
+
+        if (delimiters === null)
+            delimiters = GLib.STR_DELIMITERS;
+        if (typeof newDelimiter === 'number')
+            newDelimiter = String.fromCharCode(newDelimiter);
+
+        const delimiterChars = delimiters.split('');
+        const escapedDelimiterChars = delimiterChars.map(_escapeCharacterSetChars);
+        const delimiterRegex = new RegExp(`[${escapedDelimiterChars.join('')}]`, 'g');
+        return string.replace(delimiterRegex, newDelimiter);
+    };
+
+    this.strcanon = function (string, validChars, substitutor) {
+        _warnNotIntrospectable('GLib.strcanon()', 'String.replace()');
+
+        if (typeof substitutor === 'number')
+            substitutor = String.fromCharCode(substitutor);
+
+        const validArray = validChars.split('');
+        const escapedValidArray = validArray.map(_escapeCharacterSetChars);
+        const invalidRegex = new RegExp(`[^${escapedValidArray.join('')}]`, 'g');
+        return string.replace(invalidRegex, substitutor);
+    };
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]