[jsscripting] Fix failure on some platforms & JDKs (#13714)

* [jsscripting] Downgrade GraalVM to fix issue with armv7l & OpenJDK 11.0.16

The community reported several cases where JS Scripting was not working due to some issue with the injection of the global script.
This issue seems to only occur on armv7l (e.g. Raspberry Pi 32bit) and OpenJDK 11.0.16.
Investigation showed that the occurrence of the problem depends on the GraalJS version.

See https://community.openhab.org/t/js-scripting-all-scripts-stop-working-when-upgrading-to-3-4-0-m4/140837.

* [jsscripting] Add logging for injection of JSRuntimeFeatures
* [jsscripting] Lint `@jsscripting-globals.js` with semistandard
* [jsscripting] Remove ICU4J as it moved to `org.graalvm.truffle`

Reference f5661d4655/CHANGELOG.md (version-2200).

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
This commit is contained in:
Florian Hotze 2022-11-14 20:30:44 +01:00 committed by GitHub
parent 40723a80a0
commit 11d3c641e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 177 additions and 178 deletions

View File

@ -22,7 +22,7 @@
!jdk.internal.reflect.*,
!jdk.vm.ci.services
</bnd.importpackage>
<graal.version>22.3.0</graal.version>
<graal.version>22.0.0.2</graal.version> <!-- DO NOT UPGRADE: 22.0.0.2 is the latest version working on armv7l / OpenJDK 11.0.16 -->
<asm.version>6.2.1</asm.version>
<oh.version>${project.version}</oh.version>
<ohjs.version>openhab@2.1.0</ohjs.version>
@ -135,11 +135,7 @@
<artifactId>js</artifactId>
<version>${graal.version}</version>
</dependency>
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>69.1</version>
</dependency>
<!-- com.ibm.icu.icu4j/69.1 is not required on GraalJS >= 22.0.0 as it moved to org.graalvm.truffle -->
<!-- include as version required is older than OH provides -->
<dependency>

View File

@ -210,7 +210,10 @@ public class OpenhabGraalJSScriptEngine
delegate.getBindings(ScriptContext.ENGINE_SCOPE).put(REQUIRE_WRAPPER_NAME, wrapRequireFn);
// Injections into the JS runtime
delegate.put("require", wrapRequireFn.apply((Function<Object[], Object>) delegate.get("require")));
jsRuntimeFeatures.getFeatures().forEach((key, obj) -> delegate.put(key, obj));
jsRuntimeFeatures.getFeatures().forEach((key, obj) -> {
LOGGER.debug("Injecting {} into the JS runtime...", key);
delegate.put(key, obj);
});
initialized = true;

View File

@ -1,189 +1,189 @@
// ThreadsafeTimers is injected into the JS runtime
(function (global) {
'use strict';
'use strict';
// Append the script file name OR rule UID depending on which is available
const defaultIdentifier = "org.openhab.automation.script" + (globalThis["javax.script.filename"] ? ".file." + globalThis["javax.script.filename"].replace(/^.*[\\\/]/, '') : globalThis["ruleUID"] ? ".ui." + globalThis["ruleUID"] : "");
const System = Java.type('java.lang.System');
const formatRegExp = /%[sdj%]/g;
// Pass the defaultIdentifier to ThreadsafeTimers to enable naming of scheduled jobs
ThreadsafeTimers.setIdentifier(defaultIdentifier);
// Append the script file name OR rule UID depending on which is available
const defaultIdentifier = 'org.openhab.automation.script' + (globalThis['javax.script.filename'] ? '.file.' + globalThis['javax.script.filename'].replace(/^.*[\\\/]/, '') : globalThis.ruleUID ? '.ui.' + globalThis.ruleUID : '');
const System = Java.type('java.lang.System');
const formatRegExp = /%[sdj%]/g;
// Pass the defaultIdentifier to ThreadsafeTimers to enable naming of scheduled jobs
ThreadsafeTimers.setIdentifier(defaultIdentifier);
function createLogger(name = defaultIdentifier) {
return Java.type("org.slf4j.LoggerFactory").getLogger(name);
function createLogger (name = defaultIdentifier) {
return Java.type('org.slf4j.LoggerFactory').getLogger(name);
}
// User configurable
let log = createLogger();
function stringify (value) {
try {
if (Java.isJavaObject(value)) {
return value.toString();
} else {
// special cases
if (value === undefined) {
return 'undefined';
}
if (typeof value === 'function') {
return '[Function]';
}
if (value instanceof RegExp) {
return value.toString();
}
// fallback to JSON
return JSON.stringify(value, null, 2);
}
} catch (e) {
return '[Circular: ' + e + ']';
}
}
function format (f) {
if (typeof f !== 'string') {
const objects = [];
for (let index = 0; index < arguments.length; index++) {
objects.push(stringify(arguments[index]));
}
return objects.join(' ');
}
// User configurable
let log = createLogger();
if (arguments.length === 1) return f;
function stringify(value) {
try {
if (Java.isJavaObject(value)) {
return value.toString();
} else {
// special cases
if (value === undefined) {
return "undefined"
}
if (typeof value === 'function') {
return "[Function]"
}
if (value instanceof RegExp) {
return value.toString();
}
// fallback to JSON
return JSON.stringify(value, null, 2);
}
} catch (e) {
return '[Circular: ' + e + ']';
}
let i = 1;
const args = arguments;
const len = args.length;
let str = String(f).replace(formatRegExp, function (x) {
if (x === '%%') return '%';
if (i >= len) return x;
switch (x) {
case '%s': return String(args[i++]);
case '%d': return Number(args[i++]);
case '%j':
try {
return stringify(args[i++]);
} catch (_) {
return '[Circular]';
}
// falls through
default:
return x;
}
});
for (let x = args[i]; i < len; x = args[++i]) {
if (x === null || (typeof x !== 'object' && typeof x !== 'symbol')) {
str += ' ' + x;
} else {
str += ' ' + stringify(x);
}
}
return str;
}
function format(f) {
if (typeof f !== 'string') {
var objects = [];
for (var index = 0; index < arguments.length; index++) {
objects.push(stringify(arguments[index]));
}
return objects.join(' ');
const counters = {};
const timers = {};
// Polyfills for common NodeJS functions
const console = {
assert: function (expression, message) {
if (!expression) {
log.error(message);
}
},
count: function (label) {
let counter;
if (label) {
if (counters.hasOwnProperty(label)) {
counter = counters[label];
} else {
counter = 0;
}
if (arguments.length === 1) return f;
// update
counters[label] = ++counter;
log.debug(format.apply(null, [label + ':', counter]));
}
},
var i = 1;
var args = arguments;
var len = args.length;
var str = String(f).replace(formatRegExp, function (x) {
if (x === '%%') return '%';
if (i >= len) return x;
switch (x) {
case '%s': return String(args[i++]);
case '%d': return Number(args[i++]);
case '%j':
try {
return stringify(args[i++]);
} catch (_) {
return '[Circular]';
}
// falls through
default:
return x;
}
});
for (var x = args[i]; i < len; x = args[++i]) {
if (x === null || (typeof x !== 'object' && typeof x !== 'symbol')) {
str += ' ' + x;
} else {
str += ' ' + stringify(x);
}
debug: function () {
log.debug(format.apply(null, arguments));
},
info: function () {
log.info(format.apply(null, arguments));
},
log: function () {
log.info(format.apply(null, arguments));
},
warn: function () {
log.warn(format.apply(null, arguments));
},
error: function () {
log.error(format.apply(null, arguments));
},
trace: function (e) {
if (Java.isJavaObject(e)) {
log.trace(e.getLocalizedMessage(), e);
} else {
if (e.stack) {
log.trace(e.stack);
} else {
if (e.message) {
log.trace(format.apply(null, [(e.name || 'Error') + ':', e.message]));
} else {
log.trace((e.name || 'Error'));
}
}
return str;
}
},
time: function (label) {
if (label) {
timers[label] = System.currentTimeMillis();
}
},
timeEnd: function (label) {
if (label) {
const now = System.currentTimeMillis();
if (timers.hasOwnProperty(label)) {
log.info(format.apply(null, [label + ':', (now - timers[label]) + 'ms']));
delete timers[label];
} else {
log.info(format.apply(null, [label + ':', '<no timer>']));
}
}
},
// Allow user customizable logging names
// Be aware that a log4j2 required a logger defined for the logger name, otherwise messages won't be logged!
set loggerName (name) {
log = createLogger(name);
this._loggerName = name;
ThreadsafeTimers.setIdentifier(name);
},
get loggerName () {
return this._loggerName || defaultIdentifier;
}
};
const counters = {};
const timers = {};
// Polyfill common NodeJS functions onto the global object
globalThis.console = console;
globalThis.setTimeout = ThreadsafeTimers.setTimeout;
globalThis.clearTimeout = ThreadsafeTimers.clearTimeout;
globalThis.setInterval = ThreadsafeTimers.setInterval;
globalThis.clearInterval = ThreadsafeTimers.clearInterval;
// Polyfills for common NodeJS functions
const console = {
'assert': function (expression, message) {
if (!expression) {
log.error(message);
}
},
count: function (label) {
let counter;
if (label) {
if (counters.hasOwnProperty(label)) {
counter = counters[label];
} else {
counter = 0;
}
// update
counters[label] = ++counter;
log.debug(format.apply(null, [label + ':', counter]));
}
},
debug: function () {
log.debug(format.apply(null, arguments));
},
info: function () {
log.info(format.apply(null, arguments));
},
log: function () {
log.info(format.apply(null, arguments));
},
warn: function () {
log.warn(format.apply(null, arguments));
},
error: function () {
log.error(format.apply(null, arguments));
},
trace: function (e) {
if (Java.isJavaObject(e)) {
log.trace(e.getLocalizedMessage(), e);
} else {
if (e.stack) {
log.trace(e.stack);
} else {
if (e.message) {
log.trace(format.apply(null, [(e.name || 'Error') + ':', e.message]));
} else {
log.trace((e.name || 'Error'));
}
}
}
},
time: function (label) {
if (label) {
timers[label] = System.currentTimeMillis();
}
},
timeEnd: function (label) {
if (label) {
const now = System.currentTimeMillis();
if (timers.hasOwnProperty(label)) {
log.info(format.apply(null, [label + ':', (now - timers[label]) + 'ms']));
delete timers[label];
} else {
log.info(format.apply(null, [label + ':', '<no timer>']));
}
}
},
// Allow user customizable logging names
// Be aware that a log4j2 required a logger defined for the logger name, otherwise messages won't be logged!
set loggerName(name) {
log = createLogger(name);
this._loggerName = name;
ThreadsafeTimers.setIdentifier(name);
},
get loggerName() {
return this._loggerName || defaultIdentifier;
}
};
// Polyfill common NodeJS functions onto the global object
globalThis.console = console;
globalThis.setTimeout = ThreadsafeTimers.setTimeout;
globalThis.clearTimeout = ThreadsafeTimers.clearTimeout;
globalThis.setInterval = ThreadsafeTimers.setInterval;
globalThis.clearInterval = ThreadsafeTimers.clearInterval;
// Support legacy NodeJS libraries
globalThis.global = globalThis;
globalThis.process = { env: { NODE_ENV: '' } };
// Support legacy NodeJS libraries
globalThis.global = globalThis;
globalThis.process = { env: { NODE_ENV: '' } };
})(this);