Skip to content

Commit

Permalink
JS infinite loop protection
Browse files Browse the repository at this point in the history
  • Loading branch information
TheLogicMaster committed Apr 24, 2021
1 parent 1fa96fd commit ae46acc
Show file tree
Hide file tree
Showing 12 changed files with 63 additions and 56 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
A programming game where the goal is to guide a robot through various levels using programmatic controls.

## Features
- Multi-language programming support (PHP, Python, JavaScript, Lua, and Google Blockly)
- Multi-language programming support (PHP, Python, JavaScript, Lua, Ruby, BASIC, and Google Blockly)
- Custom code editor
- Cloud Saves
- Tutorial Levels
Expand Down Expand Up @@ -31,26 +31,26 @@ A programming game where the goal is to guide a robot through various levels usi
- [Chaquopy](https://chaquo.com/chaquopy) (Android Python)
- [LuaJ](https://github.com/luaj/luaj) (Lua)
- [Quercus](https://www.caucho.com/resin-3.1/doc/quercus.xtp) (PHP)
- [Duktape Android](https://github.com/cashapp/duktape-android) (Android JavaScript)
- [duktape4j](https://github.com/webfolderio/duktape4j) (Desktop JavaScript)
- [Duktape Android](https://github.com/TheLogicMaster/duktape-android) (Android JavaScript)
- [duktape4j](https://github.com/TheLogicMaster/duktape4j) (Desktop JavaScript)
- [Jasic](https://github.com/munificent/jasic) (BASIC interpreter base code)
- [JRuby](https://github.com/jruby/jruby) (Ruby)
- [MaryTTS](https://github.com/marytts/marytts) (Desktop text to speech)
- [Android MaryTTS](https://github.com/AndroidMaryTTS/AndroidMaryTTS) (Android text to speech)
- [Project Lombok](https://projectlombok.org/) (Boilerplate generation)
- [JCEF](https://github.com/chromiumembedded/java-cef) (Desktop embedded browser)
- [JavaPackager](https://github.com/fvarrui/JavaPackager) (Gradle plugin to package desktop builds)
- [Jasic](https://github.com/munificent/jasic) (BASIC interpreter base code)
- [JRuby](https://github.com/jruby/jruby) (Ruby)

## Todo
- Debug/step through mode
- Tab to double space functionality in editor
- Tab to double-space functionality in editor
- Make the UI not terrible
- Mobile HTML custom keyboard (Or not if it's not worth it and just disable code editor on mobile browsers)
- Code testing (JS code transformations, for instance)
- Level editor
- Trim HTML reflection cache of unused components like most of VisUI
- Prevent Firefox '/' quick search functionality
- Robot customization
- Android separate execution process using synchronous AIDL. Would provide a sandbox and the ability to interrupt infinite loops.

## Building
### GitHub Actions
Expand All @@ -60,7 +60,7 @@ the web demo to the gh-pages branch, and the Android application to the Google P
### Building From Source
Building the project requires several external libraries depending on which modules you want to build. Regardless of
which platform you are building, the Android SDK is required to compile to project. To build without it, it would be
neccesary to comment out parts of `settings.gradle` and `build.gradle` at the project root. To enable debug mode
necessary to comment out parts of `settings.gradle` and `build.gradle` at the project root. To enable debug mode
(mostly logging) for the entire project, add `debug=true` to a `local.properties` file at the root for the project.

For the Desktop module, the respective [JCEF library](https://github.com/jcefbuild/jcefbuild/releases) for your platform is required at
Expand Down Expand Up @@ -90,4 +90,4 @@ To build and copy Blockly, run the `updateBlockly` task.
To update to a new JCEF release, download the platform assets from [jcefbuild](https://github.com/jcefbuild/jcefbuild/releases).
The 6 native platform JARs in the release archives need to have their native libraries extracted to the `desktop/natives`
directory with the existing format. The `desktop/libs` directory should have its contents updated from one of the new platform
release archives. The `Libraries/jcef` directory needs to be updated using the archive `bin/lib` directories.
release archives. The `Libraries/jcef` directory needs to be updated using the archive `bin/lib` directories.
6 changes: 4 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ android {
exclude 'META-INF/dependencies.txt'
// Contains outdated libpng library that prevents Play Store approval
exclude 'lib/armeabi-v7a/libjniopencv_highgui.so'
exclude 'lib/armeabi-v7a/libopencv_highgui.so'
}
defaultConfig {
applicationId 'com.thelogicmaster.robot_recharge'
minSdkVersion 21
targetSdkVersion 29
versionCode 9
versionCode 10
versionName "1.0"
multiDexEnabled true

Expand Down Expand Up @@ -80,6 +81,7 @@ configurations { natives }

dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation fileTree(dir: 'lib', include: ['*.aar'])
implementation project(':core')
implementation project(':java')
implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion"
Expand All @@ -93,7 +95,7 @@ dependencies {
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86"
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86_64"
implementation 'org.apache.commons:commons-text:1.6'
implementation 'com.squareup.duktape:duktape-android:1.3.0'
//implementation 'com.squareup.duktape:duktape-android:1.3.0'
implementation 'com.marytts.android:marylib:1.0.1'
implementation "de.golfgl.gdxgamesvcs:gdx-gamesvcs-android-gpgs:$gamesvcsVersion"
implementation "de.golfgl.gdxgamesvcs:gdx-gamesvcs-core-gamejolt:$gamesvcsVersion"
Expand Down
Binary file added android/lib/duktape.aar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,32 @@ public class AndroidJavaScriptEngine implements CodeEngine {

@Override
public ExecutionInstance run(final IRobot robot, final String code, final ExecutionListener listener) {
Duktape duktape = Duktape.create();
Thread thread = new Thread(() -> {
try (Duktape duktape = Duktape.create()) {
try {
duktape.set("Robot", IRobot.class, robot);
duktape.evaluate(code);
listener.onExecutionFinish();
} catch (Exception e) {
if (e instanceof InterruptedException) {
//noinspection ConstantConditions
if (e instanceof InterruptedException ||
(e.getMessage() != null && e.getMessage().contains("RangeError: execution timeout"))) {
listener.onExecutionInterrupted();
return;
}
Gdx.app.error("Duktape", e.toString());
listener.onExecutionError(e.getMessage());
} finally {
duktape.close();
}
});
thread.start();
return new ExecutionInstance(thread);
return new ExecutionInstance(thread) {
@Override
public void stop () {
super.stop();
duktape.interrupt();
}
};
}
}
6 changes: 0 additions & 6 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ dependencies {
api "com.github.crykn.guacamole:core:$guacamoleVersion"
api "com.github.crykn.guacamole:gdx:$guacamoleVersion"
implementation "de.golfgl.gdxgamesvcs:gdx-gamesvcs-core:$gamesvcsVersion"
implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1'
implementation group: 'com.caucho', name: 'quercus', version: '4.0.45'

// 9000 Versions have compatibility issues with Android, see: https://github.com/jruby/jruby/issues/4004
//noinspection GradleDependency
implementation group: 'org.jruby', name: 'jruby-complete', version: '1.7.27'

compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'
Expand Down
2 changes: 1 addition & 1 deletion desktop/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dependencies {
//implementation "com.github.crykn.guacamole:gdx:$guacamoleVersion"
//implementation "com.github.crykn.guacamole:gdx-desktop:$guacamoleVersion"

implementation 'io.webfolder:duktape4j:1.2.1'
//implementation 'io.webfolder:duktape4j:1.2.1'
implementation 'org.python:jython-slim:2.7.2'
implementation group: 'de.dfki.mary', name: 'voice-cmu-slt-hsmm', version: '5.2'
implementation "de.golfgl.gdxgamesvcs:gdx-gamesvcs-core-gamejolt:$gamesvcsVersion"
Expand Down
Binary file added desktop/libs/duktape4j-1.2.1.jar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,31 @@ public class DesktopJavaScriptEngine implements CodeEngine {
@Override
public ExecutionInstance run(final IRobot robot, final String code, final ExecutionListener listener) {
Duktape duktape = Duktape.create();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
duktape.set("Robot", IRobot.class, robot);
duktape.evaluate(code);
listener.onExecutionFinish();
duktape.close();
} catch (Exception e) {
duktape.close();
if (e instanceof InterruptedException) {
listener.onExecutionInterrupted();
return;
}
Gdx.app.error("Duktape", e.toString());
listener.onExecutionError(e.getMessage());
Thread thread = new Thread(() -> {
try {
duktape.set("Robot", IRobot.class, robot);
duktape.evaluate(code);
listener.onExecutionFinish();
} catch (Exception e) {
//noinspection ConstantConditions
if (e instanceof InterruptedException ||
(e.getMessage() != null && e.getMessage().contains("RangeError: execution timeout"))) {
listener.onExecutionInterrupted();
return;
}
Gdx.app.error("Duktape", e.toString());
listener.onExecutionError(e.getMessage());
} finally {
duktape.close();
}
});
thread.start();
return new ExecutionInstance(thread) {
@Override
public void stop () {
thread.interrupt();
duktape.interrupt();
super.stop();

duktape.close();
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.thelogicmaster.robot_recharge;

import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.thelogicmaster.robot_recharge.code.IExecutionInstance;
import lombok.AllArgsConstructor;
Expand All @@ -21,8 +22,9 @@ public void stop () {
try {
Thread.sleep(100);
if (thread.isAlive()) {
thread.stop();
Gdx.app.error("ExecutionInstance", "Had to kill thread, this is bad");
if (Gdx.app.getType() != Application.ApplicationType.Android)
thread.stop();
Gdx.app.error("ExecutionInstance", "Failed to interrupt thread, this is real bad");
}
} catch (InterruptedException ignored) {}
}).start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public ExecutionInstance run(final IRobot robot, final String code, final Execut
return new ExecutionInstance(thread);
}

private class Watchdog extends DebugLib {
private static class Watchdog extends DebugLib {

@Override
@SneakyThrows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ public static IRobot getRobot() {
public ExecutionInstance run(final IRobot robot, final String code, final ExecutionListener listener) {
PhpEngine.robot = robot;
Thread thread = new Thread(() -> {
QuercusEngine engine = new QuercusEngine();
try {
QuercusEngine engine = new QuercusEngine();
engine.init();
engine.execute("<?php\nimport com.thelogicmaster.robot_recharge.code.PhpEngine;\n$Robot=PhpEngine::getRobot();\n" + code + "\n?>");
engine.execute("<?php\n"
+ "import com.thelogicmaster.robot_recharge.PhpEngine;\n"
+ "$Robot=PhpEngine::getRobot();\n"
+ code + "\n"
+ "?>");
listener.onExecutionFinish();
} catch (QuercusException e) {
if (e.getCause() instanceof InterruptedException) {
Expand All @@ -35,6 +39,8 @@ public ExecutionInstance run(final IRobot robot, final String code, final Execut
} catch (IOException e) {
Gdx.app.error("PHP", "IO", e);
listener.onExecutionError(e.getMessage());
} finally {
engine.getQuercus().close();
}
});
thread.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@ public class RubyEngine implements CodeEngine {

@Override
public ExecutionInstance run(final IRobot robot, final String code, final ExecutionListener listener) {
RubyInstanceConfig config = new RubyInstanceConfig();
config.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
Ruby ruby = Ruby.newInstance(config);
Thread thread = new Thread(() -> {
RubyInstanceConfig config = new RubyInstanceConfig();
config.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
Ruby ruby = Ruby.newInstance(config);
ruby.defineGlobalConstant("Robot", JavaUtil.convertJavaToRuby(ruby, robot));
try {
ruby.evalScriptlet(code);
JavaEmbedUtils.terminate(ruby);
listener.onExecutionFinish();
ruby.tearDown();
} catch (Exception e) {
//noinspection ConstantConditions
if (e instanceof InterruptedException) {
Expand All @@ -30,17 +29,11 @@ public ExecutionInstance run(final IRobot robot, final String code, final Execut
}
Gdx.app.error("Ruby", e.toString());
listener.onExecutionError(e.getMessage());
} finally {
ruby.tearDown();
}
});
thread.start();
return new ExecutionInstance(thread) {
@Override
public void stop () {
super.stop();

ruby.tearDown();
}
};
return new ExecutionInstance(thread);
}
}

0 comments on commit ae46acc

Please sign in to comment.