From d53d0b8db9de6dcdfafb713f67e26f229425b59b Mon Sep 17 00:00:00 2001 From: vharseko Date: Thu, 25 Apr 2024 20:44:54 +0300 Subject: [PATCH] [OpenIdentityPlatform/OpenIDM#10 OpenIdentityPlatform/OpenIDM#14] FIX rhino binding global context (#110) --- .github/workflows/build.yml | 2 +- commons/rest/api-descriptor/pom.xml | 2 +- pom.xml | 1 + .../exception/ScriptCompilationException.java | 15 ++- script/javascript/pom.xml | 8 +- .../script/javascript/RhinoScript.java | 108 +++++++--------- .../script/javascript/RhinoScriptEngine.java | 122 ++++++++++++++---- 7 files changed, 160 insertions(+), 98 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 607426145..724584f0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ 'ubuntu-latest', 'macos-latest', 'windows-latest' ] + os: [ 'ubuntu-latest', 'macos-12', 'windows-latest' ] java: [ '8', '11', '17', '21' ] fail-fast: false steps: diff --git a/commons/rest/api-descriptor/pom.xml b/commons/rest/api-descriptor/pom.xml index e92903518..b3d26e8cb 100644 --- a/commons/rest/api-descriptor/pom.xml +++ b/commons/rest/api-descriptor/pom.xml @@ -28,7 +28,7 @@ bundle - 1.7.12 + diff --git a/pom.xml b/pom.xml index b5afef99f..642260087 100644 --- a/pom.xml +++ b/pom.xml @@ -190,6 +190,7 @@ 2.14.3 1.7.36 1.6.11 + 1.7.14 diff --git a/script/common/src/main/java/org/forgerock/script/exception/ScriptCompilationException.java b/script/common/src/main/java/org/forgerock/script/exception/ScriptCompilationException.java index bf6c17c1d..c131d772d 100644 --- a/script/common/src/main/java/org/forgerock/script/exception/ScriptCompilationException.java +++ b/script/common/src/main/java/org/forgerock/script/exception/ScriptCompilationException.java @@ -1,8 +1,4 @@ /* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2015 ForgeRock AS. All rights reserved. - * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in @@ -20,6 +16,8 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" + * + * Copyright 2015-2016 ForgeRock AS. */ package org.forgerock.script.exception; @@ -35,6 +33,13 @@ public class ScriptCompilationException extends ScriptException { /** Serializable class a version number. */ static final long serialVersionUID = 1L; + /** + * Constructs a new exception with the specified detail message. + */ + public ScriptCompilationException(String message) { + super(message); + } + /** * Constructs a new exception with the specified detail message and cause. */ @@ -50,4 +55,4 @@ public ScriptCompilationException(String message, Exception cause, String fileNa super(message, fileName, lineNumber, columnNumber); initCause(cause); } -} +} \ No newline at end of file diff --git a/script/javascript/pom.xml b/script/javascript/pom.xml index 0185b9817..2743abe2d 100644 --- a/script/javascript/pom.xml +++ b/script/javascript/pom.xml @@ -67,7 +67,7 @@ org.apache.servicemix.bundles org.apache.servicemix.bundles.rhino - 1.7R4_1 + ${rhino.version}_2 org.eclipse.wst.jsdt.debug @@ -130,12 +130,12 @@ test - + org.slf4j slf4j-simple diff --git a/script/javascript/src/main/java/org/forgerock/script/javascript/RhinoScript.java b/script/javascript/src/main/java/org/forgerock/script/javascript/RhinoScript.java index c14ad0a74..3411a1f68 100644 --- a/script/javascript/src/main/java/org/forgerock/script/javascript/RhinoScript.java +++ b/script/javascript/src/main/java/org/forgerock/script/javascript/RhinoScript.java @@ -1,8 +1,4 @@ /* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2012-2014 ForgeRock AS. All rights reserved. - * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in @@ -20,6 +16,8 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" + * + * Copyright 2012-2016 ForgeRock AS. */ package org.forgerock.script.javascript; @@ -97,6 +95,9 @@ public class RhinoScript implements CompiledScript { /** The CommonJS module builder for Require instances. */ private final RequireBuilder requireBuilder; + /** Indicates if this script instance should use the shared scope. */ + private final boolean sharedScope; + public static final Global GLOBAL = new Global(); static { @@ -112,62 +113,43 @@ public void quit(Context cx, int exitCode) { } } - /** Indicates if this script instance should use the shared scope. */ - private final boolean sharedScope; - /** - * Compiles the JavaScript source code into an executable script. If - * {@code useSharedScope} is {@code true}, then a sealed shared scope - * containing standard JavaScript objects (Object, String, Number, Date, - * etc.) will be used for script execution; otherwise a new unsealed scope - * will be allocated for each execution. * - * - * @param compiledScript - * the source code of the JavaScript script. - * @param sharedScope - * if {@code true}, uses the shared scope, otherwise allocates - * new scope. - * @throws ScriptException - * if there was an exception encountered while compiling the - * script. + * @param name the name of the script + * @param compiledScript the to-be-executed compiled script + * @param engine the parent script engine + * @param requireBuilder The CommonJS module builder for Require instances. + * @param sharedScope Indicates if this script instance should use the shared scope. If {@code useSharedScope} is + * {@code true}, then a sealed shared scope containing standard JavaScript objects (Object, String, Number, Date, + * etc.) will be used for script execution; otherwise a new unsealed scope will be allocated for each execution. */ public RhinoScript(String name, Script compiledScript, final RhinoScriptEngine engine, RequireBuilder requireBuilder, - boolean sharedScope) throws ScriptException { + boolean sharedScope) { this.scriptName = name; this.sharedScope = sharedScope; this.engine = engine; this.requireBuilder = requireBuilder; Context cx = Context.enter(); + cx.setLanguageVersion(Context.VERSION_ES6); try { scriptScope = getScriptScope(cx); script = compiledScript; - // script = cx.compileString(source, name, 1, null); - } catch (RhinoException re) { - throw new ScriptException(re.getMessage()); } finally { Context.exit(); } } /** - * TEMPORARY + * + * @param name the name of the script + * @param engine the parent script engine + * @param requireBuilder The CommonJS module builder for Require instances. + * @param sharedScope Indicates if this script instance should use the shared scope. If {@code useSharedScope} is + * {@code true}, then a sealed shared scope containing standard JavaScript objects (Object, String, Number, Date, + * etc.) will be used for script execution; otherwise a new unsealed scope will be allocated for each execution. */ - public RhinoScript(String name, final RhinoScriptEngine engine, RequireBuilder requireBuilder, boolean sharedScope) - throws ScriptException { - this.scriptName = name; - this.sharedScope = sharedScope; - this.engine = engine; - this.requireBuilder = requireBuilder; - Context cx = Context.enter(); - try { - scriptScope = getScriptScope(cx); - script = null;// cx.compileReader(reader, name, 1, null); - } catch (RhinoException re) { - throw new ScriptException(re); - } finally { - Context.exit(); - } + public RhinoScript(String name, final RhinoScriptEngine engine, RequireBuilder requireBuilder, boolean sharedScope) { + this(name, null, engine, requireBuilder, sharedScope); } /** @@ -184,7 +166,6 @@ private ScriptableObject getStandardObjects(final Context context) { if (!sharedScope) { // somewhat expensive ScriptableObject scope = context.initStandardObjects(); - installRequire(context, scope); return scope; } // lazy initialization race condition is harmless @@ -203,7 +184,6 @@ private ScriptableObject getStandardObjects(final Context context) { } } addLoggerProperty(scope); - installRequire(context, scope); // seal the whole scope (not just standard objects) scope.sealObject(); topSharedScope = scope; @@ -211,12 +191,6 @@ private ScriptableObject getStandardObjects(final Context context) { return topSharedScope; } - // install require function per unofficial CommonJS author documentation - // https://groups.google.com/d/msg/mozilla-rhino/HCMh_lAKiI4/P1MA3sFsNKQJ - private void installRequire(final Context context, final ScriptableObject scope) { - requireBuilder.createRequire(context, scope).install(scope); - } - /** * Get the scope scriptable re-used for this script Holds common * functionality such as the logger @@ -260,6 +234,7 @@ public Object eval(final org.forgerock.services.context.Context ctx, Bindings re throws ScriptException { Context context = Context.enter(); + context.setLanguageVersion(Context.VERSION_ES6); try { Scriptable outer = context.newObject(getStandardObjects(context)); @@ -303,15 +278,19 @@ public Object eval(final org.forgerock.services.context.Context ctx, Bindings re } outer.setPrototype(scriptScope); // script level context and - // standard objects included with - // every box + // standard objects included with + // every box outer.setParentScope(null); Scriptable inner = context.newObject(outer); // inner transient - // scope for new - // properties + // scope for new + // properties inner.setPrototype(outer); inner.setParentScope(null); + // install require function per unofficial CommonJS author documentation + // https://groups.google.com/d/msg/mozilla-rhino/HCMh_lAKiI4/P1MA3sFsNKQJ + requireBuilder.createRequire(context, inner).install(inner); + final Script scriptInstance = null != script ? script : engine.createScript(scriptName); Object result = Converter.convert(scriptInstance.exec(context, inner)); return result; // Context.jsToJava(result, Object.class); @@ -319,27 +298,24 @@ public Object eval(final org.forgerock.services.context.Context ctx, Bindings re throw e; } catch (WrappedException e) { if (e.getWrappedException() instanceof ResourceException) { - throw new ScriptThrownException(e.getMessage(), e.sourceName(), e.lineNumber(), e.columnNumber(), - ((ResourceException) e.getWrappedException()).toJsonValue().getObject()); + throw getScriptExecutionGenerator().newScriptThrownException(e, + ((ResourceException)e.getWrappedException()).toJsonValue().getObject()); } else { - ScriptException exception = - new ScriptThrownException(e.getMessage(), e.sourceName(), e.lineNumber(), e.columnNumber(), - e.getWrappedException()); + ScriptException exception = getScriptExecutionGenerator().newScriptThrownException(e, + e.getWrappedException()); exception.initCause(e.getWrappedException()); throw exception; } } catch (JavaScriptException e) { logger.debug("Failed to evaluate {} script.", scriptName, e); - ScriptThrownException exception = - new ScriptThrownException(e.getMessage(), e.sourceName(), e.lineNumber(), e.columnNumber(), - Converter.convert(e.getValue())); + ScriptThrownException exception = getScriptExecutionGenerator().newScriptThrownException(e, + Converter.convert(e.getValue())); exception.initCause(e); throw exception; } catch (RhinoException e) { logger.debug("Failed to evaluate {} script.", scriptName, e); // some other runtime exception encountered - final ScriptException exception = - new ScriptException(e.getMessage(), e.sourceName(), e.lineNumber(), e.columnNumber()); + final ScriptException exception = getScriptExecutionGenerator().newScriptException(e); exception.initCause(e); throw exception; } catch (Exception e) { @@ -351,6 +327,10 @@ public Object eval(final org.forgerock.services.context.Context ctx, Bindings re } } + private RhinoScriptEngine.ScriptExceptionGenerator getScriptExecutionGenerator() { + return engine.getScriptExceptionGenerator(); + } + private static class InnerClassLoader extends SecureClassLoader { public InnerClassLoader(ClassLoader parent) { @@ -372,4 +352,4 @@ public Class loadClass(String name) throws ClassNotFoundException { } } -} +} \ No newline at end of file diff --git a/script/javascript/src/main/java/org/forgerock/script/javascript/RhinoScriptEngine.java b/script/javascript/src/main/java/org/forgerock/script/javascript/RhinoScriptEngine.java index fea928d88..fdef9e780 100644 --- a/script/javascript/src/main/java/org/forgerock/script/javascript/RhinoScriptEngine.java +++ b/script/javascript/src/main/java/org/forgerock/script/javascript/RhinoScriptEngine.java @@ -1,8 +1,4 @@ /* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2012-2014 ForgeRock AS. All rights reserved. - * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in @@ -20,15 +16,19 @@ * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" + * + * Copyright 2012-2016 ForgeRock AS. */ package org.forgerock.script.javascript; +import org.forgerock.json.JsonValue; import org.forgerock.json.resource.ResourceException; import org.forgerock.script.engine.AbstractScriptEngine; import org.forgerock.script.engine.CompilationHandler; import org.forgerock.script.engine.ScriptEngineFactory; import org.forgerock.script.exception.ScriptCompilationException; +import org.forgerock.script.exception.ScriptThrownException; import org.forgerock.script.scope.OperationParameter; import org.forgerock.script.source.ScriptSource; import org.forgerock.script.source.SourceContainer; @@ -57,6 +57,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; /** * A NAME does ... @@ -64,6 +65,12 @@ * @author Laszlo Hordos */ public class RhinoScriptEngine extends AbstractScriptEngine { + public static final String CONFIG_DEBUG_PROPERTY = "javascript.debug"; + public static final String CONFIG_RECOMPILE_MINIMUM_INTERVAL_PROPERTY = + "javascript.recompile.minimumInterval"; + public static final String CONFIG_EXCEPTION_DEBUG_INFO = "javascript.exception.debug.info"; + + static final Pattern RHINO_EXCEPTION_FILE_INFO_PATTERN = Pattern.compile("[ ][(].+[)]$"); /** * Setup logging for the {@link RhinoScriptEngine}. @@ -75,25 +82,24 @@ public class RhinoScriptEngine extends AbstractScriptEngine { private final ConcurrentMap scriptCache = new ConcurrentHashMap(); - private long minimumRecompilationInterval = -1; + private final long minimumRecompilationInterval; private RequireBuilder requireBuilder; private ClassLoader classLoader; + private final ScriptExceptionGenerator scriptExceptionGenerator; + RhinoScriptEngine(final Map configuration, final ScriptEngineFactory factory, - final Collection sourceContainers, ClassLoader registryLevelClassLoader) { + final Collection sourceContainers, ClassLoader registryLevelClassLoader) { this.factory = factory; - - Object debugProperty = configuration.get(CONFIG_DEBUG_PROPERTY); - if (debugProperty instanceof String) { - initDebugListener((String) debugProperty); - } - Object recompile = configuration.get(CONFIG_RECOMPILE_MINIMUM_INTERVAL_PROPERTY); - if (recompile instanceof String) { - minimumRecompilationInterval = Long.valueOf((String) recompile); - } - + final JsonValue jsonValueConfig = new JsonValue(configuration); + initDebugListener(jsonValueConfig.get(CONFIG_DEBUG_PROPERTY).defaultTo(null).asString()); + minimumRecompilationInterval = Long.valueOf( + jsonValueConfig.get(CONFIG_RECOMPILE_MINIMUM_INTERVAL_PROPERTY).defaultTo("-1").asString()); + scriptExceptionGenerator = jsonValueConfig.get(CONFIG_EXCEPTION_DEBUG_INFO).defaultTo(true).asBoolean() + ? DEBUG_SCRIPT_EXCEPTION_GENERATOR + : NON_DEBUG_SCRIPT_EXCEPTION_GENERATOR; // Use an Iterable over the SourceContainer collection--that way if it // changes (adds, removes, changes)--the new collection is reflected in // the UrlModuleSourceProvider. @@ -145,7 +151,7 @@ private static final class ScriptCacheEntry { private final long lastCheck; private ScriptCacheEntry(final Script compiledScript, final ScriptSource scriptSource, - long lastModified, long lastCheck) { + long lastModified, long lastCheck) { this.compiledScript = compiledScript; this.scriptSource = scriptSource; this.lastModified = lastModified; @@ -231,12 +237,13 @@ public void compileScript(CompilationHandler handler) throws ScriptException { private Script compileScript(String name, Reader scriptReader) throws ScriptCompilationException { Context cx = Context.enter(); + cx.setLanguageVersion(Context.VERSION_ES6); try { return cx.compileReader(scriptReader, name, 1, null); } catch (IOException ioe) { throw new ScriptCompilationException(ioe.getMessage(), ioe); } catch (RhinoException re) { - throw new ScriptCompilationException(re.getMessage(), re, re.sourceName(), re.lineNumber(), re.columnNumber()); + throw getScriptExceptionGenerator().newScriptCompilationException(re); } finally { Context.exit(); if (scriptReader != null) { @@ -261,10 +268,6 @@ public OperationParameter getOperationParameter(final org.forgerock.services.con private volatile Boolean debugInitialised = null; - public static final String CONFIG_DEBUG_PROPERTY = "javascript.debug"; - public static final String CONFIG_RECOMPILE_MINIMUM_INTERVAL_PROPERTY = - "javascript.recompile.minimumInterval"; - private synchronized void initDebugListener(String configString) { if (null == debugInitialised) { // Get here only once when the first factory initialised. @@ -364,4 +367,77 @@ public Bindings compileBindings(org.forgerock.services.context.Context context, // return new RhinoScript(name, source, sharedScope); // } -} + /** + * Interface defining factory for exceptions resulting from script compilation or execution. + */ + interface ScriptExceptionGenerator { + /** + * @param e the RhinoException representing the thrown exception + * @param scriptThrownExceptionValue the actual exception value thrown by the script, converted to a java object + * @return a ScriptThrownException representing this exception, and encapsulating the correct level of debug information + */ + ScriptThrownException newScriptThrownException(RhinoException e, Object scriptThrownExceptionValue); + + /** + * + * @param e the RhinoException generated by the script compilation failure + * @return a ScriptCompilationException representing this failure, and encapsulating the correct level of debug information + */ + ScriptCompilationException newScriptCompilationException(RhinoException e); + + /** + * + * @param e the RhinoException thrown by the RhinoEngine during script execution + * @return a ScriptException representing this exception, and encapsulating the correct level of debug information. + */ + ScriptException newScriptException(RhinoException e); + } + + ScriptExceptionGenerator getScriptExceptionGenerator() { + return scriptExceptionGenerator; + } + + private static final ScriptExceptionGenerator DEBUG_SCRIPT_EXCEPTION_GENERATOR = new ScriptExceptionGenerator() { + @Override + public ScriptThrownException newScriptThrownException(RhinoException e, Object scriptThrownExceptionValue) { + return new ScriptThrownException(e.getMessage(), e.sourceName(), e.lineNumber(), e.columnNumber(), + scriptThrownExceptionValue); + } + + @Override + public ScriptCompilationException newScriptCompilationException(RhinoException e) { + return new ScriptCompilationException(e.getMessage(), e, e.sourceName(), e.lineNumber(), e.columnNumber()); + } + + @Override + public ScriptException newScriptException(RhinoException e) { + return new ScriptException(e.getMessage(), e.sourceName(), e.lineNumber(), e.columnNumber()); + } + }; + + private static final ScriptExceptionGenerator NON_DEBUG_SCRIPT_EXCEPTION_GENERATOR = new ScriptExceptionGenerator() { + @Override + public ScriptThrownException newScriptThrownException(RhinoException e, Object scriptThrownExceptionValue) { + return new ScriptThrownException(stripDebugInformationFromRhinoExceptionMessage(e), scriptThrownExceptionValue); + } + + @Override + public ScriptCompilationException newScriptCompilationException(RhinoException e) { + return new ScriptCompilationException(stripDebugInformationFromRhinoExceptionMessage(e)); + } + + @Override + public ScriptException newScriptException(RhinoException e) { + return new ScriptException(stripDebugInformationFromRhinoExceptionMessage(e)); + } + + /* + The Rhino runtime can include script debug information in the exception message. The format of this included information + can be found in RhinoException#getMessage. The pattern below will match this information, if present, and return only + the preceeding message state. + */ + private String stripDebugInformationFromRhinoExceptionMessage(RhinoException e) { + return RHINO_EXCEPTION_FILE_INFO_PATTERN.split(e.getMessage())[0]; + } + }; +} \ No newline at end of file