📜 ⬆️ ⬇️

Life and amazing adventures in exotic JavaScript environments

Have you ever had to write in a familiar language under a never-before-seen platform? A strange feeling. Kodabra shares her experience on how to quickly deal with unfamiliar surroundings and start living.



Minecraft programming for kids and adults


Everyone knows Minecraft is a cubic phenomenon that in a matter of time grew out of an indie project by an unknown Swedish programmer into one of the main franchises of Microsoft itself.


We in Kodabre are very fond of Minecraft, because children love it, which means we can use it for educational purposes, using the well-known game world, built-in mechanics and familiar terminology, discovering the amazing world of programming with their help.


The MinecraftEdu project is specifically designed to combine the process of playing and learning, bringing new objects and tools to the standard game, for example, programmable robot turtles, with which it is convenient to explain the introduction to programming and algorithms.


Turtle programming interface


Although “under the hood” of turtles is a real Lua, but the external API is too limited and in fact does not fit anything more complicated than the automation of the turtles. It quickly becomes boring, and after mastering the basics, children want to move on and do something more complex and interesting.


So we came to the idea of ​​trying to use JavaScript from the ScriptCraft mod, which offers full access to creating its own mods. Language has a rather low threshold of entry and is perceived by children without any problems. It is more difficult for adult teachers who have previously worked with Node.js or programmed under a web browser, since ScriptCraft provides a not-so-usual JavaScript environment that you need to deal with before you can write courses and dive headlong into the depths of developing mods .


We have summarized our experience of starting to communicate with ScriptCraft so that it can be applied to any other unfamiliar or unknown platform with JavaScript on board, under which you may need to write code.


/ js - where have I got and what can I do here?


A successfully installed mod adds two new commands to the game - /js [code] for executing JavaScript code from the console and /jsp [command] for executing commands specified in the plugins via the command() function ("jsp" here has nothing to do with JavaServer Pages).


Let's test the mod operation by entering the /js 1 + 1 command offered in the documentation. The result will really be 2 , but this does not prove almost anything. You can not even understand if JavaScript is actually executed? Let's try to make sure by doing something more characteristic of JS, for example, a self-calling function that returns an answer to the main question of the universe through a specific type conversion.


/js (function () { var a = 4, b = 2; return 'The answer is ' + a + b; }())


The answer


Now there is no doubt, this is really JavaScript, but which one? This question will sound strange to most programmers in other languages, but, for example, experienced front-end developers are well aware that JS JS is different and the first thing to do when starting to write code is to figure out exactly which engine and environment you are dealing with.


Usually, the easiest way to find out which engine is used in a particular product is to read the documentation or source code for it. But in the real world it is not always possible - the documentation may be missing or simply omit such a moment, and the source code may be closed or unavailable for quick understanding. In such cases, everything will have to figure out manually.


To our happiness, the mod is written in Java and is available freely on Github, so you can find where and how the engine is connected - https://git.io/vHc3g .


 ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("JavaScript"); 

If you are not familiar with Java, then little will become clear from this code. But we know that the default JavaScript engine connects here, which in Java is either Rhino (in the old JDK) or Nashorn (with Java 8). In principle, we could stop at this and continue to read into Oracle the formal documentation, but of course we will not do this :) Firstly, it’s boring, and secondly, it won’t give us a complete understanding of the environment, since we are inside Minecraft-mod, written by an adult Irish man, and you can expect anything here.


But let's assume for now that we did not have the source code and the technology at the core, we also do not know what to do then?
It is a little theory - JavaScript is, first of all, an implementation of the ECMAScript standard of one or another version and any engine must implement it in whole or in part. In practice, this is not enough and, depending on the tasks of the manufacturer, specific extensions and interfaces are added to the engine. Let's call this "implementation features" - it is for them that one engine can be distinguished from another.


For example, one of the features of Nashorn is that objects have an __noSuchProperty__ method used to describe the behavior when trying to read a missing property of an object. In particular, this method always has a global object and can serve as a good indirect sign of the engine.


/js __noSuchProperty__


 function __noSuchProperty__() { [native code] } 

For other engines, the features will be their own, fortunately the choice of options is not so great according to Wikipedia - the List of ECMAScript engines , and those found in the wild conditions of engines are even less. Usually the choice always consists of 2-3 options, not more.


Another fairly effective way to learn more about the engine is to throw an escape.


/js throw 'dusk'


Throw dusk


From the output it is already clear that we are in Java, and looking at the full Java-ekspepshen, it will become clear and everything else.


 javax.script.ScriptException: javax.script.ScriptException: 1 in <eval> at line number 1 at column number 0 in <eval> at line number 638 at column number 8 at jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(NashornScriptEngine.java:467) at jdk.nashorn.api.scripting.NashornScriptEngine.invokeImpl(NashornScriptEngine.java:389) at jdk.nashorn.api.scripting.NashornScriptEngine.invokeFunction(NashornScriptEngine.java:190) ... 

Well, now we have at once found out in several different ways who exactly executes our JavaScript code and can move on, straight into the dark world of the runtime environment.


To begin with, let's check whether we are in "sctrict mode", as this may be important for the technician with which we will know the world.


/js (function () { return !this; }())


 false 

In strict mode, this inside the anonymous function will not be defined. We are lucky, we are not there, and it significantly unties the hands.


Next, you must find the global object. If you're lucky, this already proves them, which is easy to check in the following way.


/js this === new Function('return this')()


 true 

The Function constructor trick is universal and can be used to obtain a global object, even if its name is unknown and direct access is not possible.


We are lucky again and we will use this for convenience of writing, as well as global in other cases (since global is a global object in Nashorn, which we would not know if we did not find out first the engine).


Finally, it's time to look into the most intimate of any runtime - into a global object. In addition to a great way to take "prints", it will give us a basic knowledge of what is available here.


We use the Object.getOwnPropertyNames method, which returns an array of all object property names, regardless of whether they are enumerable or not. This method is available starting with the ECMAScript 5.1 standard, which is implemented in Nashorn. In case this method was not available, one would have to use the standard for..in and be content with only enumerated properties.


The console object in Nashorn is not natively implemented, so I will use the print() method to output information to the server console, since it is a bit more convenient than using echo() from the mod to output directly to the game console.


We derive native methods.


/js print('Native methods:\n' + Object.getOwnPropertyNames(this).filter(function (name) { return (typeof global[name] === 'function' && global[name].toString().indexOf('native code') >= 0) }).join(', '))


 Native methods: parseInt, parseFloat, isNaN, isFinite, encodeURI, encodeURIComponent, decodeURI, decodeURIComponent, escape, unescape, print, load, loadWithNewGlobal, exit, quit, eval, Object, Function, Array, String, Boolean, Number, Error, ReferenceError, SyntaxError, TypeError, Date, RegExp, JSAdapter, EvalError, RangeError, URIError, ArrayBuffer, DataView, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, JavaImporter, __noSuchProperty__ 

Separately - custom methods.


/js print('User-defined methods:\n' + Object.getOwnPropertyNames(this).filter(function (name) { return (typeof global[name] === 'function' && global[name].toString().indexOf('native code') < 0) }).join(', '))


 User-defined methods: __scboot, __onDisable, __onEnable, __onDisableImpl, addUnloadHandler, refresh, echo, alert, scload, scsave, scloadJSON, isOp, require, setTimeout, clearTimeout, setInterval, clearInterval, persist, command, __onTabComplete, plugin, __onCommand, box, box0, boxa, arc, bed, blocktype, copy, paste, cylinder0, cylinder, door, door_iron, door2, door2_iron, firework, garden, ladder, chkpt, move, turn, right, left, fwd, back, up, down, prism0, prism, rand, sign, signpost, wallsign, sphere, sphere0, hemisphere, hemisphere0, stairs, oak, birch, jungle, spruce, commando, castle, chessboard, cottage_road, cottage, dancefloor, fort, hangtorch, lcdclock, logojs, logojscube, maze, rainbow, wireblock, wire, torchblock, repeaterblock, wirestraight, redstoneroad, spawn, spiral_stairs, temple, Drone, hello 

If you separate native methods from user ones, it’s quite simple, then such a number cannot be done with the properties of a global object. You can separate them based on the descriptor (having received it using Object.getOwnPropertyDescriptor ) and configurable / enumerable flags , but in my opinion it is not very efficient and much easier to accomplish the same task with your eyes.


/js print('Properties:\n' + Object.getOwnPropertyNames(this).filter(function (name) { return typeof global[name] !== 'function' }).join(', '))


 Properties: arguments, NaN, Infinity, undefined, Math, Packages, com, edu, java, javafx, javax, org, __FILE__, __DIR__, __LINE__, JSON, Java, javax.script.filename, global, server, nashorn, config, __plugin, console, events, arrows, classroom, blocks, entities, homes, Game_NumberGuess, signs, self, __engine 

Since, based on the output of the methods, we already understand that most of the plug-in APIs are exported to the global visibility zone, we can easily separate the global properties of arguments, NaN, Infinity, undefined, Math, JSON and Nashorn specific for any JS environment. Packages, com, edu, java, javafx, javax, org, __FILE__, __DIR__, __LINE__, javax.script.filename, classroom , and all that remains is most likely added by the ScriptCraft mod itself.


What else does the output of global variables tell us? The set of native methods and properties is in principle fairly standard for the ES 5.1 environment. There are certainly no familiar APIs from Node.js and even more so from the browser, but there are plenty of wrappers for accessing the inner world of Java. The mod itself, without much agony, exports both external and internal interfaces to a global object with arbitrary names, overrides some native methods such as setTimeout() , setInterval() , clearTimeout() , clearInterval() , adds a console object and a require() , working in Node.js style.


setTimeout


You can use the toString() method of a function to get a string representation of the function body (except native ones), as well as the name and length properties to get the name (useful for functions behind a bind) and the number of arguments taken, respectively (useful for unknown native functions ).


Although the information received is already enough to get started, our micro-research would be incomplete without determining its place in the call stack, this knowledge will simplify future debugging.


In JavaScript, the easiest way is to get the stack trace from an arbitrary place without stopping the execution of the code by creating a new instance of the Error object and accessing its dynamically-generated stack property.


Executing the /js print(new Error().stack) command unambiguously tells us that we are in a certain REPL interface and everything we enter will fall into one or another form of eval in the engine. It is logical, yet we are trying to execute code from the console.


 Error at <program> (<eval>:1) at __onEnable$__onCommand (<eval>:613) 

It is noteworthy that __onEnable$__onCommand here is most likely a generated Java class.


Execution of the same code from an external file, the situation does not fundamentally change, apparently due to the implementation of the module loading mechanism and the features of the engine itself.


 Error at <anonymous> (<eval>:1) at <anonymous> (<eval>:278) at <anonymous> (<eval>:306) at <anonymous> (<eval>:56) at __onEnable (<eval>:783) at <anonymous> (<eval>:91) 

But now we can easily find out the entry point into the bootloader by simply looking for the __onEnable call in the source code.


And now for the work!


The heuristic algorithm described above shows itself well in situations like ours, when it is not very clear what to deal with at all and from which side it is better to approach. All you really need to quickly get to the bottom of things is a field in which you can enter code. The approach is somewhat extreme, but very effective and can work even in the complete absence of documentation and debugging tools, although in this case much more tricks will be needed.


It is also worth saying that in this article we didn’t touch on the Nashron API for accessing Java, because this is not directly related to the topic of the article and there you can get bogged down for a long time. In short, the whole Nashorn is designed in such a way that Java programmers are not limited by anything from JavaScript. Access is almost unlimited - you can parallelize code execution by creating Java threads that are native to Java, you can inherit from Java classes, you can create several global objects, dynamically load code, read files, execute OS commands and much, much more.


Here are just a few links for those interested:



')

Source: https://habr.com/ru/post/329646/


All Articles