📜 ⬆️ ⬇️

Javascript as the embodiment of evil

JavaScript developers often complain that their programming language is unjustly scolded for having too many overly complicated, tangled features. Many are struggling with this attitude toward JS, talking about why criticizing this language for what it is is wrong. The author of the material, the translation of which we are publishing today, decided not to defend JS, turning instead to the dark side of the language. However, here he does not want to talk, for example, about those pitfalls that JavaScript sets for inexperienced programmers. He is interested in the question of what will happen if we try to confirm the bad reputation of the language with a code that could be written by someone who does not care about others at all.



In the examples for this material a variety of language mechanisms will be used. A lot of what you see here, by the way, works in other languages, therefore, with due diligence, you can find their dark sides. But JavaScript, of course, has a real gift for all kinds of bullying, and with it in this area is very difficult for other languages. If you write code with which other people will need to work, JS gives you an inexhaustible amount of opportunities to annoy, confuse, torment and deceive these people. As a matter of fact, here we will consider only a small part of such techniques.

Getter modifiers


JavaScript supports getters — functions that allow you to work with what they return as with a regular property. Under normal use, it looks like this:
')
let greeter = {  name: 'Bob',  get hello() { return `Hello ${this.name}`} } console.log(greeter.hello) // Hello Bob greeter.name = 'World'; console.log(greeter.hello) // Hello World 

If we use getters, plotting evil, then, for example, you can create self-destructive objects:

 let obj = {  foo: 1,  bar: 2,  baz: 3,  get evil() {     let keys = Object.keys(this);     if(keys) {        delete this[keys[0]]     }     return 'Nothing to see here';  } } 

Here, each time obj.evil , one of the other properties of the object will be deleted. At the same time, the code working with obj.evil will not know that something very strange is happening right under his nose. However, this is only the beginning of a conversation about the harmful side effects that can be achieved using JavaScript mechanisms.

Unexpected proxies


Getters are great, but they have been around for years, many developers know about them. Now, thanks to the proxy, we have at our disposal a much more powerful tool for entertainment with objects. Proxy is an ES6 feature that allows you to create wrappers around objects. With their help, you can control what happens when the user tries to read or write the properties of proxied objects. This allows, for example, to create an object that, in one third of attempts to access a certain key of such an object, will return a value according to a randomly selected key.

 let obj = {a: 1, b: 2, c: 3}; let handler = {   get: function(obj, prop) {     if (Math.random() > 0.33) {       return obj[prop];     } else {       let keys = Object.keys(obj);       let key = keys[Math.floor(Math.random()*keys.length)]       return obj[key];     }   } }; let evilObj = new Proxy(obj, handler); //          console.log(evilObj.a); // 1 console.log(evilObj.b); // 1 console.log(evilObj.c); // 3 console.log(evilObj.a); // 2 console.log(evilObj.b); // 2 console.log(evilObj.c); // 3 

Unfortunately, our developer’s tools, which identify evilObj as an object of the Proxy type, partially unfold our meanness. However, the above-described construction, before its lowly essence is revealed, is capable of delivering many pleasant moments to those who will work with it.

Contagious functions


So far, we have been talking about how objects can modify themselves. But we, in addition, can create innocent-looking functions that infect objects passed to them, changing their behavior. For example, suppose we have a simple get() function that allows you to perform a secure property search in the object passed to it, given that such an object may not exist:

 let get = (obj, property, default) => {  if(!obj) {     return default;  }  return obj[property]; } 

Such a function is easy to rewrite so that it infects the objects passed to it, slightly modifying them. For example, you can make it so that the property to which it helped gain access would no longer be displayed when trying to iterate over the keys of an object:

 let get = (obj, property, defaultValue) => {  if(!obj || !property in obj) {     return defaultValue;  }  let value = obj[property];  delete obj[property];  Object.defineProperty(obj, property, {     value,     enumerable: false  })  return obj[property]; } let x = {a: 1, b:2 }; console.log(Object.keys(x)); // ['a', 'b'] console.log(get(x, 'a')); console.log(Object.keys(x)); // ['b'] 

This is a sample of very subtle interference with the behavior of an object. The enumeration of the keys of an object is not the most conspicuous operation, since it is not very rare, but it is used not too often. Since errors that can be caused by such a modification of objects cannot be tied to their code, they can exist in a project for quite some time.

Prototype mess


Above, we discussed the various possibilities of JS, including quite fresh ones. However, sometimes there is nothing better than the old, time-tested technology. One of the features of JS, due to which it criticizes most, is the possibility of modifying embedded prototypes. This feature was used in the early years of JS to extend embedded objects, for example, arrays. Here's how to extend the standard capabilities of arrays, say, by adding the contains method to the prototype of the Array object:

 Array.prototype.contains = function(item) { return this.indexOf(item) !== -1; } 

As it turns out, if you do something similar in a real-life library, this can disrupt the work with the basic mechanisms of the language in the entire application that uses this library. Therefore, the inclusion of additional useful methods in the prototypes of standard objects can be considered a very good move for patient developers who seek to do other things nasty. However, if we are talking about impatient sociopaths, they can offer something quick-acting, but no less interesting. A prototype modification has one very useful feature, which is that the modification affects all code that runs in some environment, even the one that is loaded from modules or is in closures. As a result, if you issue the following code in the form of a third-party script (for example, it may be an ad network script or an analytical service), then the entire site using this script will be prone to minor errors.

 Array.prototype.map = function(fn) {  let arr = this;  let arr2 = arr.reduce((acc, val, idx) => {     if (Math.random() > 0.95) {        idx = idx + 1     }     let index = acc.length - 1 === idx ? (idx - 1 ) : idx     acc[index] = fn(val, index, arr);     return acc;  },[]);  return arr2; } 

Here we have redefined the standard Array.prototype.map method so that it, in general, works fine, but in 5% of cases it swaps two elements of the array. Here is what you can get after several calls to this method:

 let arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]; let square = x => x * x; console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,100,81,121,144,169,196,225 console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,81,100,121,144,169,196,225] console.log(arr.map(square)); // [1,4,9,16,25,36,49,64,81,100,121,144,169,196,225] 

Here we launched it three times. What came out of his first use is slightly different from the next two results of his call. This is a minor change, it will not always cause some kind of failure. And the most pleasant thing here is that it is impossible to understand the cause of rarely occurring errors caused by this method without reading its source code, which is the cause of these errors. Our function is not noticeable when working with developer tools; it does not produce errors when working in strict mode. In general, with the help of something like this it is quite possible to drive someone crazy.

Difficult names


The naming of entities, as is known, is one of the two most difficult tasks of computer science. Therefore, bad names come up with not only those who consciously seek to harm others. Of course, it can be hard to believe experienced linuksoids. They had years at their disposal to link the most terrible IT naming violator (Microsoft) with the deepest forms of evil. But unsuccessful names do not directly harm programs. We will not talk about small things like names that are misleading and about comments that have lost relevance. For example, about such:

 //   let arrayOfNumbers = { userid: 1, name: 'Darth Vader'}; 

In order to understand this and understand that there is something wrong with the comment, and with the name of a variable, the person who reads the code in which such a thing occurs will have to slow down and think a little. But this is nonsense. Let's talk about really interesting things. Did you know that most Unicode characters can be used to name variables in JavaScript? If you, in the matter of assigning variable names, are set to positive, then you will like the idea of ​​using names in the form of icons ( Habr cut out emoji, although in the original here after let was emoji kakahi ):

 let = { postid: 123, postName: 'Evil JavaScript'} 

Although, after all, we are talking about real nasty things, so let's turn to the symbols that are similar to those usually used for naming variables, but they are not. For example, let's do this:

 let obj = {}; console.log(obj); // Error! 

The letter in the name obj may look almost normal, but this is not a lowercase Latin letter b. This is the so-called full-width lowercase Latin letter b. The characters are different, so anyone who tries to enter the name of such a variable manually will most likely be very confused.

Results


Despite the story about various nasty things that can be created with the help of JavaScript, this material is intended to warn programmers from using techniques similar to those described, and convey to them the fact that this can cause real harm. The author of the material says that it is always useful to know what problems may appear in poorly written code. He believes that something similar can be found in real projects, but he hopes that there it exists in a less destructive form. However, the fact that a programmer who wrote such a code did not seek to harm others does not make it easier to work with such code and debug it. At the same time, the knowledge of what targeted attempts to harm can bring, can broaden the programmer’s horizons and help him find the source of similar errors. No one can be completely sure that there is no mistake in the code with which it works. Perhaps someone, aware of his penchant for excessive suspicion, will try to reassure himself that anxiety about such mistakes is only a figment of his imagination. However, this will not prevent such errors, possibly, intentionally introduced into a certain code, once it will manifest itself.

Dear readers! Have you encountered in practice something similar to what was discussed in this article?

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


All Articles