CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-mozilla--rhino

JavaScript engine implementation that enables JavaScript execution within Java applications with full ECMAScript support and Java-JavaScript interoperability.

Pending
Overview
Eval results
Files

security-debugging.mddocs/

Security and Debugging

Comprehensive security framework and debugging capabilities for production JavaScript execution environments, including access control, sandboxing, error handling, and runtime introspection.

Capabilities

Security Framework

ClassShutter

Controls which Java classes are accessible from JavaScript code, providing fine-grained access control.

/**
 * Interface for controlling Java class access from JavaScript
 * Prevents access to sensitive or dangerous Java classes
 */
public interface ClassShutter {
    /**
     * Determines if a Java class should be accessible from JavaScript
     * @param fullClassName Fully qualified Java class name
     * @return true if class should be accessible, false otherwise
     */
    boolean visibleToScripts(String fullClassName);
}

Usage Examples:

// Restrictive ClassShutter implementation
ClassShutter restrictiveShutter = new ClassShutter() {
    @Override
    public boolean visibleToScripts(String fullClassName) {
        // Allow only safe classes
        return fullClassName.startsWith("java.lang.String") ||
               fullClassName.startsWith("java.lang.Math") ||
               fullClassName.startsWith("java.util.Date");
    }
};

// Set ClassShutter on Context
cx.setClassShutter(restrictiveShutter);

// Now JavaScript can only access allowed classes
try {
    cx.evaluateString(scope, "java.lang.System.exit(0);", "test", 1, null);
    // This will fail - System class not accessible
} catch (EvaluatorException e) {
    System.out.println("Access denied: " + e.getMessage());
}

// Whitelist-based ClassShutter
Set<String> allowedClasses = Set.of(
    "java.lang.String",
    "java.lang.Math", 
    "java.util.ArrayList",
    "java.util.HashMap"
);

ClassShutter whitelistShutter = allowedClasses::contains;
cx.setClassShutter(whitelistShutter);

SecurityController

Provides comprehensive security control over script execution with customizable policies.

/**
 * Controls script execution security and access permissions
 * Provides integration with Java security manager
 */
public abstract class SecurityController {
    /**
     * Gets the global SecurityController instance
     * @return Global SecurityController or null if none set
     */
    public static SecurityController global();
    
    /**
     * Checks if global SecurityController is installed
     * @return true if global controller exists
     */
    public static boolean hasGlobal();
    
    /**
     * Sets the global SecurityController instance
     * @param controller SecurityController to install globally
     */
    public static void initGlobal(SecurityController controller);
    
    /**
     * Creates call context for secure execution
     * @param cx Current Context
     * @param callable Callable object
     * @param scope Execution scope
     * @param thisObj 'this' object
     * @param args Call arguments
     * @return Execution result
     */
    public abstract Object callWithDomain(Object securityDomain, Context cx, Callable callable, 
                                        Scriptable scope, Scriptable thisObj, Object[] args);
    
    /**
     * Gets current security domain
     * @param cx Current Context
     * @return Current security domain object
     */
    public abstract Object getDynamicSecurityDomain(Object securityDomain);
    
    /**
     * Gets class loader for generated classes
     * @param cx Current Context
     * @param securityDomain Security domain
     * @return ClassLoader for generated classes
     */
    public abstract ClassLoader createClassLoader(ClassLoader parent, Object securityDomain);
}

/**
 * SecurityController implementation using Java security policy
 * Integrates with Java AccessController and Policy system
 */
public class PolicySecurityController extends SecurityController {
    /**
     * Creates PolicySecurityController instance
     */
    public PolicySecurityController();
}

Usage Examples:

// Install security controller
PolicySecurityController securityController = new PolicySecurityController();
SecurityController.initGlobal(securityController);

// Execute scripts with security domain
Object securityDomain = "trusted-domain";
Context cx = Context.enter();
try {
    cx.setSecurityController(securityController);
    // Scripts compiled with this domain will have restricted permissions
    Script script = cx.compileString("java.lang.System.getProperty('user.home')", 
                                   "test", 1, securityDomain);
    Object result = script.exec(cx, scope);
} finally {
    Context.exit();
}

Safe Standard Objects

Initialize JavaScript environment without Java class access for sandboxed execution.

/**
 * Initialize safe JavaScript environment without Java access
 * Prevents JavaScript code from accessing Java packages and classes
 */
public ScriptableObject initSafeStandardObjects();

/**
 * Initialize safe environment with custom scope object
 * @param scope Scope object to initialize
 * @return Initialized safe scope
 */
public Scriptable initSafeStandardObjects(ScriptableObject scope);

Usage Examples:

// Create safe JavaScript environment
Context cx = Context.enter();
try {
    // Safe scope - no Java access
    Scriptable safeScope = cx.initSafeStandardObjects();
    
    // This will work - standard JavaScript
    Object result1 = cx.evaluateString(safeScope, 
        "Math.max(1, 2, 3)", "safe", 1, null);
    
    // This will fail - no Java access
    try {
        cx.evaluateString(safeScope, 
            "java.lang.System.getProperty('user.name')", "unsafe", 1, null);
    } catch (EvaluatorException e) {
        System.out.println("Java access blocked: " + e.getMessage());
    }
} finally {
    Context.exit();
}

// Custom safe scope with additional restrictions
ScriptableObject customScope = cx.initSafeStandardObjects();
// Remove potentially dangerous global functions
customScope.delete("eval");  // Disable eval
customScope.delete("Function");  // Disable Function constructor

Error Handling and Reporting

ErrorReporter Interface

Handles compilation and runtime errors with customizable error processing.

/**
 * Interface for reporting JavaScript errors and warnings
 * Allows custom error handling and logging
 */
public interface ErrorReporter {
    /**
     * Reports a warning during compilation or execution
     * @param message Warning message
     * @param sourceName Source file name
     * @param line Line number where warning occurred
     * @param lineSource Source code line
     * @param lineOffset Column offset in line
     */
    void warning(String message, String sourceName, int line, String lineSource, int lineOffset);
    
    /**
     * Reports an error during compilation or execution
     * @param message Error message
     * @param sourceName Source file name
     * @param line Line number where error occurred
     * @param lineSource Source code line
     * @param lineOffset Column offset in line
     */
    void error(String message, String sourceName, int line, String lineSource, int lineOffset);
    
    /**
     * Reports a runtime error and returns exception to throw
     * @param message Error message
     * @param sourceName Source file name
     * @param line Line number where error occurred
     * @param lineSource Source code line
     * @param lineOffset Column offset in line
     * @return EvaluatorException to throw
     */
    EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset);
}

/**
 * Default ErrorReporter implementation
 * Prints errors to System.err and creates standard exceptions
 */
public class DefaultErrorReporter implements ErrorReporter {
    /**
     * Gets singleton instance of DefaultErrorReporter
     * @return Default ErrorReporter instance
     */
    public static ErrorReporter instance();
}

Usage Examples:

// Custom error reporter with logging
ErrorReporter loggingReporter = new ErrorReporter() {
    private final Logger logger = Logger.getLogger("JavaScript");
    
    @Override
    public void warning(String message, String sourceName, int line, 
                       String lineSource, int lineOffset) {
        logger.warning(String.format("JS Warning at %s:%d - %s", 
                                   sourceName, line, message));
    }
    
    @Override
    public void error(String message, String sourceName, int line,
                     String lineSource, int lineOffset) {
        logger.severe(String.format("JS Error at %s:%d - %s", 
                                  sourceName, line, message));
    }
    
    @Override
    public EvaluatorException runtimeError(String message, String sourceName, int line,
                                         String lineSource, int lineOffset) {
        logger.severe(String.format("JS Runtime Error at %s:%d - %s", 
                                  sourceName, line, message));
        return new EvaluatorException(message, sourceName, line, lineSource, lineOffset);
    }
};

// Set custom error reporter
cx.setErrorReporter(loggingReporter);

// Errors will now be logged
try {
    cx.evaluateString(scope, "invalidSyntax(((", "test.js", 1, null);
} catch (EvaluatorException e) {
    // Error was logged by custom reporter
}

Exception Hierarchy

Comprehensive exception hierarchy for different types of JavaScript errors.

/**
 * Base class for all Rhino-specific exceptions
 * Provides source location information for debugging
 */
public abstract class RhinoException extends RuntimeException {
    /**
     * Gets source file name where error occurred
     * @return Source file name or null
     */
    public String sourceName();
    
    /**
     * Gets line number where error occurred  
     * @return Line number or -1 if unknown
     */
    public int lineNumber();
    
    /**
     * Gets source code line where error occurred
     * @return Source line or null
     */
    public String lineSource();
    
    /**
     * Gets column number where error occurred
     * @return Column number or -1 if unknown
     */
    public int columnNumber();
    
    /**
     * Gets JavaScript stack trace
     * @return Stack trace string
     */
    public String getScriptStackTrace();
}

/**
 * Exception for compilation and evaluation errors
 * Thrown when JavaScript syntax is invalid or evaluation fails
 */
public class EvaluatorException extends RhinoException {
    /**
     * Creates EvaluatorException with location info
     * @param detail Error detail message
     * @param sourceName Source file name
     * @param lineNumber Line number
     * @param lineSource Source line
     * @param columnNumber Column number
     */
    public EvaluatorException(String detail, String sourceName, int lineNumber, 
                            String lineSource, int columnNumber);
}

/**
 * Exception for ECMA-standard JavaScript runtime errors
 * Represents Error objects thrown by JavaScript code
 */
public class EcmaError extends RhinoException {
    /**
     * Creates EcmaError from JavaScript Error object
     * @param nativeError JavaScript Error object
     * @param sourceName Source file name
     * @param lineNumber Line number
     * @param lineSource Source line
     * @param columnNumber Column number
     */
    public EcmaError(Scriptable nativeError, String sourceName, int lineNumber,
                    String lineSource, int columnNumber);
    
    /**
     * Gets the JavaScript Error object
     * @return Native Error object
     */
    public Scriptable getErrorObject();
    
    /**
     * Gets error name (TypeError, ReferenceError, etc.)
     * @return Error type name
     */
    public String getName();
}

/**
 * Exception for values thrown from JavaScript throw statements
 * Wraps any JavaScript value that was thrown
 */
public class JavaScriptException extends RhinoException {
    /**
     * Creates JavaScriptException wrapping thrown value
     * @param value JavaScript value that was thrown
     * @param sourceName Source file name
     * @param lineNumber Line number
     */
    public JavaScriptException(Object value, String sourceName, int lineNumber);
    
    /**
     * Gets the thrown JavaScript value
     * @return Original thrown value
     */
    public Object getValue();
}

/**
 * Exception wrapping Java exceptions thrown from JavaScript
 * Occurs when Java methods called from JavaScript throw exceptions
 */
public class WrappedException extends EvaluatorException {
    /**
     * Creates WrappedException wrapping Java exception
     * @param exception Original Java exception
     */
    public WrappedException(Throwable exception);
    
    /**
     * Gets the wrapped Java exception
     * @return Original Java exception
     */
    public Throwable getWrappedException();
}

Usage Examples:

// Comprehensive error handling
try {
    Object result = cx.evaluateString(scope, jsCode, "test.js", 1, null);
} catch (EvaluatorException ee) {
    // Compilation or evaluation error
    System.err.println("Evaluation error at " + ee.sourceName() + ":" + ee.lineNumber());
    System.err.println("Message: " + ee.getMessage());
    if (ee.lineSource() != null) {
        System.err.println("Source: " + ee.lineSource());
    }
} catch (EcmaError ee) {
    // JavaScript Error object thrown
    System.err.println("JavaScript " + ee.getName() + ": " + ee.getMessage());
    System.err.println("At " + ee.sourceName() + ":" + ee.lineNumber());
} catch (JavaScriptException jse) {
    // JavaScript throw statement
    Object thrownValue = jse.getValue();
    System.err.println("JavaScript threw: " + Context.toString(thrownValue));
    System.err.println("At " + jse.sourceName() + ":" + jse.lineNumber());
} catch (WrappedException we) {
    // Java exception from JavaScript-called Java method
    Throwable original = we.getWrappedException();
    System.err.println("Java exception in JavaScript: " + original.getClass().getName());
    System.err.println("Message: " + original.getMessage());
    original.printStackTrace();
}

Debugging Support

Debugger Interface

Core debugging interface for runtime inspection and control.

/**
 * Main debugger interface for JavaScript execution debugging
 * Provides hooks for compilation and execution events
 */
public interface Debugger {
    /**
     * Called when a script or function is compiled
     * @param cx Current Context
     * @param fnOrScript Compiled script or function
     * @param source JavaScript source code
     */
    void handleCompilationDone(Context cx, DebuggableScript fnOrScript, String source);
    
    /**
     * Gets debug frame for script execution
     * @param cx Current Context
     * @param fnOrScript Script or function being executed
     * @return DebugFrame for tracking execution
     */
    DebugFrame getFrame(Context cx, DebuggableScript fnOrScript);
}

/**
 * Debug frame for tracking script execution
 * Provides hooks for execution events and variable access
 */
public interface DebugFrame {
    /**
     * Called when execution enters function/script
     * @param cx Current Context
     * @param activation Activation object (local variables)
     * @param thisObj 'this' object for execution
     * @param args Function arguments
     */
    void onEnter(Context cx, Scriptable activation, Scriptable thisObj, Object[] args);
    
    /**
     * Called when execution reaches new source line
     * @param cx Current Context
     * @param lineNumber Current line number
     */
    void onLineChange(Context cx, int lineNumber);
    
    /**
     * Called when exception is thrown
     * @param cx Current Context
     * @param ex Exception that was thrown
     */
    void onExceptionThrown(Context cx, Throwable ex);
    
    /**
     * Called when execution exits function/script
     * @param cx Current Context
     * @param byThrow true if exiting due to exception
     * @param resultOrException Return value or exception
     */
    void onExit(Context cx, boolean byThrow, Object resultOrException);
}

/**
 * Provides debug information about compiled scripts
 * Used by debugger to understand script structure
 */
public interface DebuggableScript {
    /**
     * Checks if this is top-level script (not function)
     * @return true if top-level script
     */
    boolean isTopLevel();
    
    /**
     * Checks if this is a function
     * @return true if function
     */
    boolean isFunction();
    
    /**
     * Gets function name (null for anonymous)
     * @return Function name or null
     */
    String getFunctionName();
    
    /**
     * Gets source file name
     * @return Source file name
     */
    String getSourceName();
    
    /**
     * Gets array of line numbers with executable code
     * @return Array of line numbers
     */
    int[] getLineNumbers();
    
    /**
     * Gets number of nested functions
     * @return Function count
     */
    int getFunctionCount();
    
    /**
     * Gets nested function by index
     * @param index Function index
     * @return Nested DebuggableScript
     */
    DebuggableScript getFunction(int index);
    
    /**
     * Gets parameter names for function
     * @return Array of parameter names
     */
    String[] getParamNames();
}

Usage Examples:

// Simple debugger implementation
Debugger simpleDebugger = new Debugger() {
    @Override
    public void handleCompilationDone(Context cx, DebuggableScript fnOrScript, String source) {
        System.out.println("Compiled: " + fnOrScript.getSourceName() + 
                          (fnOrScript.isFunction() ? " (function)" : " (script)"));
    }
    
    @Override
    public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) {
        return new DebugFrame() {
            @Override
            public void onEnter(Context cx, Scriptable activation, 
                              Scriptable thisObj, Object[] args) {
                System.out.println("Entering: " + fnOrScript.getFunctionName());
            }
            
            @Override
            public void onLineChange(Context cx, int lineNumber) {
                System.out.println("Line: " + lineNumber);
            }
            
            @Override
            public void onExceptionThrown(Context cx, Throwable ex) {
                System.out.println("Exception: " + ex.getMessage());
            }
            
            @Override
            public void onExit(Context cx, boolean byThrow, Object resultOrException) {
                System.out.println("Exit" + (byThrow ? " (exception)" : " (normal)"));
            }
        };
    }
};

// Enable debugging
cx.setDebugger(simpleDebugger, null);
cx.setGeneratingDebug(true);

// Execute with debugging
cx.evaluateString(scope, """
    function test(x) {
        var y = x * 2;
        return y + 1;
    }
    test(5);
    """, "debug-test.js", 1, null);

Stack Introspection

Runtime stack inspection for debugging and error reporting.

/**
 * Represents an element in the JavaScript call stack
 * Provides file, function, and line information
 */
public class ScriptStackElement {
    /**
     * Source file name
     */
    public final String fileName;
    
    /**
     * Function name (null for top-level)
     */
    public final String functionName;
    
    /**
     * Line number in source
     */
    public final int lineNumber;
    
    /**
     * Creates stack element
     * @param fileName Source file name
     * @param functionName Function name
     * @param lineNumber Line number
     */
    public ScriptStackElement(String fileName, String functionName, int lineNumber);
    
    /**
     * String representation for debugging
     * @return Formatted stack element string
     */
    public String toString();
}

Usage Examples:

// Get current JavaScript stack trace
try {
    cx.evaluateString(scope, """
        function a() { b(); }
        function b() { c(); }
        function c() { throw new Error('test'); }
        a();
        """, "stack-test.js", 1, null);
} catch (JavaScriptException jse) {
    // Get stack trace from exception
    String stackTrace = jse.getScriptStackTrace();
    System.out.println("JavaScript stack trace:");
    System.out.println(stackTrace);
    
    // Parse stack elements if needed
    String[] lines = stackTrace.split("\n");
    for (String line : lines) {
        System.out.println("Stack frame: " + line);
    }
}

Advanced Security Features

Context Feature Controls

Fine-grained control over JavaScript language features for security.

// Feature constants for security control
public static final int FEATURE_STRICT_MODE = 11;
public static final int FEATURE_ENHANCED_JAVA_ACCESS = 13;
public static final int FEATURE_E4X = 6;  // E4X XML literals
public static final int FEATURE_DYNAMIC_SCOPE = 7;
public static final int FEATURE_STRICT_VARS = 8;
public static final int FEATURE_STRICT_EVAL = 9;
public static final int FEATURE_WARNING_AS_ERROR = 12;
public static final int FEATURE_THREAD_SAFE_OBJECTS = 17;

/**
 * Checks if feature is enabled in current Context
 * @param featureIndex Feature constant
 * @return true if feature is enabled
 */
public boolean hasFeature(int featureIndex);

Usage Examples:

// Disable potentially dangerous features
Context cx = Context.enter();
try {
    // Enable strict mode
    cx.setLanguageVersion(Context.VERSION_ES6);
    
    // Disable enhanced Java access
    if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) {
        // Custom ContextFactory can override feature settings
    }
    
    // Enable strict variable declarations
    // (requires custom ContextFactory to override hasFeature)
    
} finally {
    Context.exit();
}

// Custom ContextFactory for security  
ContextFactory secureFactory = new ContextFactory() {
    @Override
    protected boolean hasFeature(Context cx, int featureIndex) {
        switch (featureIndex) {
            case Context.FEATURE_ENHANCED_JAVA_ACCESS:
                return false;  // Disable enhanced Java access
            case Context.FEATURE_STRICT_MODE:
                return true;   // Force strict mode
            case Context.FEATURE_E4X:
                return false;  // Disable E4X XML literals
            default:
                return super.hasFeature(cx, featureIndex);
        }
    }
};

// Use secure factory
Object result = secureFactory.call(cx -> {
    Scriptable scope = cx.initSafeStandardObjects();
    return cx.evaluateString(scope, jsCode, "secure.js", 1, null);
});

Timeout and Resource Control

Protection against infinite loops and resource exhaustion.

// Custom ContextFactory with timeout
ContextFactory timeoutFactory = new ContextFactory() {
    @Override
    protected void observeInstructionCount(Context cx, int instructionCount) {
        // Check if execution should be interrupted
        long currentTime = System.currentTimeMillis();
        long startTime = (Long) cx.getThreadLocal("startTime");
        if (currentTime - startTime > 5000) {  // 5 second timeout
            throw new Error("Script execution timeout");
        }
    }
    
    @Override
    protected Context makeContext() {
        Context cx = super.makeContext();
        cx.setInstructionObserverThreshold(10000);  // Check every 10k instructions
        cx.putThreadLocal("startTime", System.currentTimeMillis());
        return cx;
    }
};

// Execute with timeout protection
try {
    timeoutFactory.call(cx -> {
        Scriptable scope = cx.initStandardObjects();
        return cx.evaluateString(scope, "while(true) {}", "infinite.js", 1, null);
    });
} catch (Error e) {
    System.out.println("Execution interrupted: " + e.getMessage());
}

Install with Tessl CLI

npx tessl i tessl/maven-org-mozilla--rhino

docs

index.md

java-integration.md

javascript-objects.md

script-execution.md

security-debugging.md

tile.json