From 2a9989dd18c01081c486fe9089e3bb64079c773b Mon Sep 17 00:00:00 2001 From: "Ashley (Scirra)" Date: Thu, 27 Oct 2016 11:23:04 -0400 Subject: [PATCH] Add --mangle-props-debug and fix --mangle-props=unquoted collision Patch by @AshleyScirra Based on: PR #1316 Renamed the CLI debug option to --mangle-props-debug Fixes: #1321 name collision in --mangle-props=unquoted --- README.md | 16 +++++++ bin/uglifyjs | 4 +- lib/propmangle.js | 34 ++++++++++++-- test/compress/issue-1321.js | 54 ++++++++++++++++++++++ test/compress/properties.js | 92 +++++++++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 test/compress/issue-1321.js diff --git a/README.md b/README.md index 789219dc..6e9e5f2f 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,21 @@ of mangled property names. Using the name cache is not necessary if you compress all your files in a single call to UglifyJS. +#### Debugging property name mangling + +You can also pass `--mangle-props-debug` in order to mangle property names +without completely obscuring them. For example the property `o.foo` +would mangle to `o._$foo$_` with this option. This allows property mangling +of a large codebase while still being able to debug the code and identify +where mangling is breaking things. + +You can also pass a custom suffix using `--mangle-props-debug=XYZ`. This would then +mangle `o.foo` to `o._$foo$XYZ_`. You can change this each time you compile a +script to identify how a property got mangled. One technique is to pass a +random number on every compile to simulate mangling changing with different +inputs (e.g. as you update the input script with new properties), and to help +identify mistakes like writing mangled keys to storage. + ## Compressor options You need to pass `--compress` (`-c`) to enable the compressor. Optionally @@ -749,6 +764,7 @@ Other options: - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mangle-regex` CLI arguments option) - `ignore_quoted` – Only mangle unquoted property names (maps to the `--mangle-props 2` CLI arguments option) + - `debug` – Mangle names with the original name still present (maps to the `--mangle-props-debug` CLI arguments option). Defaults to `false`. Pass an empty string to enable, or a non-empty string to set the suffix. We could add more options to `UglifyJS.minify` — if you need additional functionality please suggest! diff --git a/bin/uglifyjs b/bin/uglifyjs index ce2e9411..d5025827 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -98,6 +98,7 @@ You need to pass an argument to this option to specify the name that your module .string("beautify") .string("m") .string("mangle") + .string("mangle-props-debug") .string("c") .string("compress") .string("d") @@ -419,7 +420,8 @@ async.eachLimit(files, 1, function (file, cb) { cache : cache, only_cache : !ARGS.mangle_props, regex : regex, - ignore_quoted : ARGS.mangle_props == 2 + ignore_quoted : ARGS.mangle_props == 2, + debug : typeof ARGS.mangle_props_debug === "undefined" ? false : ARGS.mangle_props_debug }); writeNameCache("props", cache); })(); diff --git a/lib/propmangle.js b/lib/propmangle.js index 3923baa6..f2777475 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -66,7 +66,8 @@ function mangle_properties(ast, options) { cache : null, only_cache : false, regex : null, - ignore_quoted : false + ignore_quoted : false, + debug : false }); var reserved = options.reserved; @@ -84,6 +85,15 @@ function mangle_properties(ast, options) { var regex = options.regex; var ignore_quoted = options.ignore_quoted; + // note debug is either false (disabled), or a string of the debug suffix to use (enabled). + // note debug may be enabled as an empty string, which is falsey. Also treat passing 'true' + // the same as passing an empty string. + var debug = (options.debug !== false); + var debug_name_suffix; + if (debug) { + debug_name_suffix = (options.debug === true ? "" : options.debug); + } + var names_to_mangle = []; var unmangleable = []; var ignored = {}; @@ -177,9 +187,25 @@ function mangle_properties(ast, options) { var mangled = cache.props.get(name); if (!mangled) { - do { - mangled = base54(++cache.cname); - } while (!can_mangle(mangled)); + if (debug) { + // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_. + var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_"; + + if (can_mangle(debug_mangled) && !(ignore_quoted && debug_mangled in ignored)) { + mangled = debug_mangled; + } + } + + // either debug mode is off, or it is on and we could not use the mangled name + if (!mangled) { + // note can_mangle() does not check if the name collides with the 'ignored' set + // (filled with quoted properties when ignore_quoted set). Make sure we add this + // check so we don't collide with a quoted name. + do { + mangled = base54(++cache.cname); + } while (!can_mangle(mangled) || (ignore_quoted && mangled in ignored)); + } + cache.props.set(name, mangled); } return mangled; diff --git a/test/compress/issue-1321.js b/test/compress/issue-1321.js new file mode 100644 index 00000000..2b6f17bf --- /dev/null +++ b/test/compress/issue-1321.js @@ -0,0 +1,54 @@ +issue_1321_no_debug: { + mangle_props = { + ignore_quoted: true + } + input: { + var x = {}; + x.foo = 1; + x["a"] = 2 * x.foo; + console.log(x.foo, x["a"]); + } + expect: { + var x = {}; + x.b = 1; + x["a"] = 2 * x.b; + console.log(x.b, x["a"]); + } +} + +issue_1321_debug: { + mangle_props = { + ignore_quoted: true, + debug: "" + } + input: { + var x = {}; + x.foo = 1; + x["_$foo$_"] = 2 * x.foo; + console.log(x.foo, x["_$foo$_"]); + } + expect: { + var x = {}; + x.a = 1; + x["_$foo$_"] = 2 * x.a; + console.log(x.a, x["_$foo$_"]); + } +} + +issue_1321_with_quoted: { + mangle_props = { + ignore_quoted: false + } + input: { + var x = {}; + x.foo = 1; + x["a"] = 2 * x.foo; + console.log(x.foo, x["a"]); + } + expect: { + var x = {}; + x.a = 1; + x["b"] = 2 * x.a; + console.log(x.a, x["b"]); + } +} diff --git a/test/compress/properties.js b/test/compress/properties.js index f1680808..22f2c339 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -142,6 +142,98 @@ mangle_unquoted_properties: { } } +mangle_debug: { + mangle_props = { + debug: "" + }; + input: { + a.foo = "bar"; + x = { baz: "ban" }; + } + expect: { + a._$foo$_ = "bar"; + x = { _$baz$_: "ban" }; + } +} + +mangle_debug_true: { + mangle_props = { + debug: true + }; + input: { + a.foo = "bar"; + x = { baz: "ban" }; + } + expect: { + a._$foo$_ = "bar"; + x = { _$baz$_: "ban" }; + } +} + +mangle_debug_suffix: { + mangle_props = { + debug: "XYZ" + }; + input: { + a.foo = "bar"; + x = { baz: "ban" }; + } + expect: { + a._$foo$XYZ_ = "bar"; + x = { _$baz$XYZ_: "ban" }; + } +} + +mangle_debug_suffix_ignore_quoted: { + options = { + properties: false + } + mangle_props = { + ignore_quoted: true, + debug: "XYZ", + reserved: [] + } + beautify = { + beautify: false, + quote_style: 3, + keep_quoted_props: true, + } + input: { + a.top = 1; + function f1() { + a["foo"] = "bar"; + a.color = "red"; + a.stuff = 2; + x = {"bar": 10, size: 7}; + a.size = 9; + } + function f2() { + a.foo = "bar"; + a['color'] = "red"; + x = {bar: 10, size: 7}; + a.size = 9; + a.stuff = 3; + } + } + expect: { + a._$top$XYZ_ = 1; + function f1() { + a["foo"] = "bar"; + a.color = "red"; + a._$stuff$XYZ_ = 2; + x = {"bar": 10, _$size$XYZ_: 7}; + a._$size$XYZ_ = 9; + } + function f2() { + a.foo = "bar"; + a['color'] = "red"; + x = {bar: 10, _$size$XYZ_: 7}; + a._$size$XYZ_ = 9; + a._$stuff$XYZ_ = 3; + } + } +} + first_256_chars_as_properties: { beautify = { ascii_only: true, -- 2.34.1