Skip to content

Instantly share code, notes, and snippets.

@maettig
Forked from 140bytes/LICENSE.txt
Created January 16, 2012 16:56
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maettig/1621774 to your computer and use it in GitHub Desktop.
Save maettig/1621774 to your computer and use it in GitHub Desktop.
dumpGlobalLeaks in 140byt.es

Clean JavaScript code does not leak into the global variable scope. That's why we use strange looking stuff like (function() { var a = 'I am local'; })(); to execute code in a local scope. Inside such scopes, we can declare and use variables without conflict.

Since you often don't get a warning when using undeclared variables, I wrote an 140byt.es snippet to detect leaking variables. The idea is very simple and works in Opera and Firefox. Does not work in Internet Explorer.

Due to a bug in Firefox I had to add a try-catch block. Accessing or even checking the type of window.sessionStorage causes a "not supported" exception. It works fine without this in all other browsers. Another bug is that Firefox returns a getInterface method in the second call that was not there in the first call. I did not found out why this happens or how to prevent it. This is why I check for native functions. And (of course) there are problems in Internet Explorer. It does not store global variables in the window object so the function can't display anything. Replacing window with this does not help since it refers to the same object.

Click here to run the test script.

function(a, b, c, d) //`window` (or something else) and three dummy arguments
{
d = a[b = 'Leak:' //collect all info in string `b`
] = a[b] || { }; //initialize the list of known properties, use `Leak:` as name
for (c in a) //check everything in the `window` object
try //not required in Opera, but Firefox crashs for `sessionStorage`
{
d[c] || //if this property is known from the previous call or
/\[native code]/.exec(a[c]) //if this is a native function
? 1 //then do nothing
: b += '\n' + c + ' = ' + a[c], //else add name and value to the string
d[c] = 1 //add this property to the list of known properties
}
catch (e) { }
return b //return the string
}
function(a,b,c,d){d=a[b='Leak:']=a[b]||{};for(c in a)try{d[c]||/\[native code]/.exec(a[c])?1:b+='\n'+c+' = '+a[c],d[c]=1}catch(e){}return b}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2012 Thiemo Mättig <http://maettig.com>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "dumpGlobalLeaks",
"description": "Debugging helper dumps all variables that leaked into the global scope.",
"keywords": [
"debugging",
"global",
"scope",
"test",
"variables",
]
}
<!DOCTYPE html>
<script type="text/javascript">
// 140 bytes
var dumpGlobalLeaks = function(a, b, c, d) //`window` (or something else) and three dummy arguments
{
d = a[b = 'Leak:' //collect all info in string `b`
] = a[b] || { }; //initialize the list of known properties, use `Leak:` as name
for (c in a) //check everything in the `window` object
try //not required in Opera, but Firefox crashs for `sessionStorage`
{
d[c] || //if this property is known from the previous call or
/\[native code]/.exec(a[c]) //if this is a native function
? 1 //then do nothing
: b += '\n' + c + ' = ' + a[c], //else add name and value to the string
d[c] = 1 //add this property to the list of known properties
}
catch (e) { }
return b //return the string
}
var goodFunction = function(a) //`a` is an unused argument, it's declared but not initialized
{
a = 1; //initialize `a`
var b; //declare `b`
b = 2; //initialize `b`
var c = 3; //declare and initialize `c` in a single statement
}
var badFunction = function()
{
a = 1; //leaks to the global scope since it's undeclared in the function
}
dumpGlobalLeaks(window); //first call returns everything, this should be ignored
goodFunction(); //call the non-leaking function
alert(dumpGlobalLeaks(window)); //should display an empty leak dump
badFunction(); //call the leaking function
alert(dumpGlobalLeaks(window)); //should display a leak dump with `number a = 1`
</script>
@tsaniel
Copy link

tsaniel commented Jan 17, 2012

Save 1 byte.

function(a,b,c){a.__=a.__||{};for(c in a)try{a.__[c]||/\[native code]/.exec(a[c])?1:b=[b]+'\n'+c+' = '+a[c],a.__[c]=1}catch(e){}return b}

@maettig
Copy link
Author

maettig commented Jan 17, 2012

Thanks, but this displays "undefined" when it should display an empty dump.

@tsaniel
Copy link

tsaniel commented Jan 17, 2012

Sorry for my bad :(
By the way, what about using Object.keys to get variables and compare them with previous results?

@maettig
Copy link
Author

maettig commented Jan 17, 2012

I didn't know this, thanks. Using Object.keys I'm able to get rid of the try-catch since Firefox skips the broken sessionStorage. But this doesn't save a byte. The strange getInterface method is not skipped. Below is an other idea I had (storing the number of keys instead of an array of keys), but it fails if something is deleted from the global scope.

function(a,b,c,d){a.__|=b='';for(c in d=Object.keys(a).slice(a.__))/\[native code]/.exec(a[c=d[c]])?1:b+='\n'+c+' = '+a[c],a.__++;return b}

@xpansive
Copy link

Save 2 bytes.

function(a,b,c){b='';a.__=a.__||{};for(c in a)try{a.__[c]=a.__[c]||/\[native code]/.exec(a[c])?1:b+='\n'+c+' = '+a[c]}catch(e){}return b}

@azproduction
Copy link

A few bytes more, and some imprevements:

  1. instead of using a.__ we can use a[a] === a["[object Window]"] to avoid 99.9% of all conflicts or save 2 more bytes, using something like µ or Æ
  2. we can use a.__ as shorter variable like d
function(a,b,c,d){d=a[a]=a[a]||{};for(c in a)try{d[c]||/\[native code]/.exec(a[c])?1:b=[b]+"\n"+c+" = "+a[c],d[c]=1}catch(e){}return b} // 135b
function(a,b,c,d){d=a.µ=a.µ||{};for(c in a)try{d[c]||/\[native code]/.exec(a[c])?1:b=[b]+"\n"+c+" = "+a[c],d[c]=1}catch(e){}return b} // 133b

@tsaniel
Copy link

tsaniel commented Jan 20, 2012

What about using the function itself to store the list of variable?

function f(a,b,c){b='';f.a=f.a||{};for(c in a)try{f.a[c]||/\[native code]/.exec(a[c])?1:b+='\n'+c+' = '+a[c],f.a[c]=1}catch(e){}return b}

By the way, how can I reproduce the getInterface problem?

@maettig
Copy link
Author

maettig commented Jan 20, 2012

So many ideas for such a tiny code snippet. That's really cool, thank you all. Let's see:

  1. To be honest, I was waiting for the suggestion to replace the =1 assignment with the ?: check. @xpansive did it. Good job. The reason why I did not do this is that it makes the array very big, about 100 KB when calling the function first. It will shrink in the second call but I'm not sure if 2 bytes are worth the trouble.
  2. Introducing d as a shortcut saves 2 bytes. Accepted.
  3. Removing b='' and adding [b] was suggested before. But this will display undefined instead of an empty dump. If that's not a problem for you, you can save 2 bytes.
  4. Using a.µ instead of a.__ is a nice idea (it even haves a meaning). But in UTF-8 encoding this character is 2 bytes. Not exactly a rule but I'm more comfortable if my snippets don't contain high ASCII characters.
  5. Using a[a] instead of a.__ is crazy, but works very nicely in all browsers including IE.
  6. Using the function itself? No way. JavaScript is freaking me out, not matter how much I know about it.

If you don't care about the problems mentioned you can golf this down to 131 bytes (when using an 8 bit encoding).

function(a,b,c){a.µ=a.µ||{};for(c in a)try{a.µ[c]=a.µ[c]||/\[native code]/.exec(a[c])?1:b=[b]+'\n'+c+' = '+a[c]}catch(e){}return b}

I think I will stick with one of these 137 bytes versions.

function(a,b,c,d){b='';d=a[a]=a[a]||{};for(c in a)try{d[c]||/\[native code]/.exec(a[c])?1:b+='\n'+c+' = '+a[c],d[c]=1}catch(e){}return b}
function f(a,b,c){b='';f.a=f.a||{};for(c in a)try{f.a[c]||/\[native code]/.exec(a[c])?1:b+='\n'+c+' = '+a[c],f.a[c]=1}catch(e){}return b}

@azproduction, I'm not sure why but your byte counts are wrong. Corrected.

@tsaniel, when I remove the "native" check I get getInterface included in the dump when running my test locally in Firefox.

@tsaniel
Copy link

tsaniel commented Jan 21, 2012

A fun fact that if I enable the Firebug panel, getInterface will not appear.
Also, I discover that getInterface will appear only if a function related to pop-up (alert, prompt ...etc) is called.

@tsaniel
Copy link

tsaniel commented Jan 21, 2012

Another way to avoid conflicts by using an "empty variable name" and save 2 bytes.

function(a,b,c,d){d=a[b='']=a[b]||{};for(c in a)try{d[c]||/\[native code]/.exec(a[c])?1:b+='\n'+c+' = '+a[c],d[c]=1}catch(e){}return b}

@maettig
Copy link
Author

maettig commented Jan 31, 2012

Using a zero-length string as a property name is remarkable but also feels strange. Does the ECMAScript specification allow this? I don't think so. However, according to my tests suite it works in all browsers with a single exception: While Internet Explorer (tested with 7, 8 and 9) does allow myObject[''] on user defined functions and objects, it causes a TypeError exception on native objects like window.

Fun thing is, this does not matter since the function doesn't work in Internet Explorer anyway. What I think about is using the remaining 5 bytes to re-add the "Leak:" message.

@azproduction
Copy link

An empty string is valid property name http://es5.github.com/#x11.1.5

PropertyAssignment :
    PropertyName : AssignmentExpression

...

PropertyName :
    IdentifierName
    StringLiteral // <~~
    NumericLiteral

...

StringLiteral ::
    "DoubleStringCharacters(opt)"
    'SingleStringCharacters(opt)' // opt - optional, so '' is allowed as property name

@tsaniel
Copy link

tsaniel commented Feb 1, 2012

@maettig: Yes as @azproduction said it's valid, and I believe no people will use it.
Another fun thing is that my IE6 doesn't have that problem. (wondering IE10's result)

@maettig
Copy link
Author

maettig commented Feb 2, 2012

I did an update and re-added the "Leak:" message. Now it's exactly 140 bytes. Unintentionally, this also avoids the empty property name. Good to know, anyway. Thanks a lot for the research.

IE6 and IE10 didn't show up in my tests suite. Did IE6 countdown succeded? ;-) Update: It's true, setting window[''] works in IE5.5, IE6 and even in certain versions of IE7 but stopped working in IE8. In IE7, it depends on the operating system. It works in Windows Vista but does not in Windows 7.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment