From f50f0530b12827d5fb9305b2348ab2dc362f194e Mon Sep 17 00:00:00 2001 From: Zbynek Konecny Date: Thu, 5 Mar 2026 22:37:45 +0100 Subject: [PATCH 1/5] Minify boolean and numeric literals in obfuscated mode --- .../gwt/dev/jjs/JavaToJavaScriptCompiler.java | 6 +- .../gwt/dev/js/JsReportGenerationVisitor.java | 4 +- .../gwt/dev/js/JsSourceGenerationVisitor.java | 6 +- ...rceGenerationVisitorWithSizeBreakdown.java | 4 +- .../dev/js/JsToStringGenerationVisitor.java | 111 ++++++++++++++---- .../src/com/google/gwt/dev/js/ast/JsNode.java | 3 +- .../gwt/dev/util/AbstractTextOutput.java | 6 +- .../dev/js/JsReportGenerationVisitorTest.java | 3 +- .../js/JsToStringGenerationVisitorTest.java | 83 +++++++++++++ 9 files changed, 186 insertions(+), 40 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index efd7bf15a33..65483b75fe0 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java @@ -151,6 +151,7 @@ import com.google.gwt.dev.js.JsStackEmulator; import com.google.gwt.dev.js.JsStaticEval; import com.google.gwt.dev.js.JsSymbolResolver; +import com.google.gwt.dev.js.JsToStringGenerationVisitor; import com.google.gwt.dev.js.JsUnusedFunctionRemover; import com.google.gwt.dev.js.JsVerboseNamer; import com.google.gwt.dev.js.SizeBreakdown; @@ -772,7 +773,9 @@ private void generateJavaScriptCode(JavaToJavaScriptMap jjsMap, String[] jsFragm DefaultTextOutput out = new DefaultTextOutput(!options.isIncrementalCompileEnabled() && options.getOutput().shouldMinimize()); JsReportGenerationVisitor v = new JsReportGenerationVisitor(out, jjsMap, - options.isJsonSoycEnabled()); + options.isJsonSoycEnabled(), + new JsToStringGenerationVisitor.PrintOptions(false, + options.getOutput() == JsOutputOption.OBFUSCATED)); v.accept(jsProgram.getFragmentBlock(i)); StatementRanges statementRanges = v.getStatementRanges(); @@ -1479,6 +1482,7 @@ private void optimizeJavaToFixedPoint() throws InterruptedException { nodeCount = jprogram.getNodeCount(); mods = stats.getNumMods(); + logger.log(TreeLogger.Type.TRACE, "Optimization pass " + passCount + " of " + passLimit + "\n" + stats); } float nodeChangeRate = mods / (float) lastNodeCount; diff --git a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java index 47e5763830d..8dfd3b06e7a 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsReportGenerationVisitor.java @@ -56,8 +56,8 @@ public class JsReportGenerationVisitor extends private List parentStack = Lists.newArrayList(); public JsReportGenerationVisitor(TextOutput out, JavaToJavaScriptMap map, - boolean needSourcemapNames) { - super(out, map); + boolean needSourcemapNames, PrintOptions options) { + super(out, map, options); this.out = out; this.needSourcemapNames = needSourcemapNames; } diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java index 725b7a61199..d8cc541207f 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java @@ -36,10 +36,10 @@ public JsSourceGenerationVisitor(TextOutput out) { /** * Generate the output source code using short or long identifiers. * - * @param useLongIdents if true, emit all identifiers in long form + * @param options minification options */ - public JsSourceGenerationVisitor(TextOutput out, boolean useLongIdents) { - super(out, useLongIdents); + public JsSourceGenerationVisitor(TextOutput out, PrintOptions options) { + super(out, options); } @Override diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java index 60c190b7fce..c3211eeb148 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java +++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java @@ -40,8 +40,8 @@ public class JsSourceGenerationVisitorWithSizeBreakdown extends JsSourceGenerati private final Map sizeMap = new HashMap(); public JsSourceGenerationVisitorWithSizeBreakdown(TextOutput out, - JavaToJavaScriptMap javaToJavaScriptMap) { - super(out); + JavaToJavaScriptMap javaToJavaScriptMap, PrintOptions options) { + super(out, options); this.out = out; this.map = javaToJavaScriptMap; } diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java index c8619e7e485..2223683bd42 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java @@ -115,9 +115,10 @@ public class JsToStringGenerationVisitor extends JsVisitor { * How many lines of code to print inside of a JsBlock when printing terse. */ private static final int JSBLOCK_LINES_TO_PRINT = 3; + private static final long MAX_DECIMAL_VALUE = 999_999_999_999L; protected boolean needSemi = true; - private List classRanges = new ArrayList(); + private final List classRanges = new ArrayList<>(); private NamedRange currentClassRange; private NamedRange programClassRange; @@ -127,27 +128,39 @@ public class JsToStringGenerationVisitor extends JsVisitor { * because the statements designated by statementEnds and statementStarts are * those that appear directly within these global blocks. */ - private Set globalBlocks = new HashSet(); + private final Set globalBlocks = new HashSet<>(); private final TextOutput p; - private ArrayList statementEnds = new ArrayList(); - private ArrayList statementStarts = new ArrayList(); + private final ArrayList statementEnds = new ArrayList<>(); + private final ArrayList statementStarts = new ArrayList<>(); private final boolean useLongIdents; + private final boolean minifyLiterals; + + public static class PrintOptions { + public final boolean useLongIdents; + public final boolean minifyLiterals; + + public PrintOptions(boolean useLongIdents, boolean minifyLiterals) { + this.useLongIdents = useLongIdents; + this.minifyLiterals = minifyLiterals; + } + } /** * Generate the output string using short identifiers. */ public JsToStringGenerationVisitor(TextOutput out) { - this(out, false); + this(out, new PrintOptions(false, false)); } /** * Generate the output string using short or long identifiers. * - * @param useLongIdents if true, emit all identifiers in long form + * @param options settings for minification */ - JsToStringGenerationVisitor(TextOutput out, boolean useLongIdents) { + JsToStringGenerationVisitor(TextOutput out, PrintOptions options) { this.p = out; - this.useLongIdents = useLongIdents; + this.useLongIdents = options.useLongIdents; + this.minifyLiterals = options.minifyLiterals; } public List getClassRanges() { @@ -183,8 +196,7 @@ public boolean visit(JsArrayAccess x, JsContext ctx) { public boolean visit(JsArrayLiteral x, JsContext ctx) { _lsquare(); boolean sep = false; - for (Object element : x.getExpressions()) { - JsExpression arg = (JsExpression) element; + for (JsExpression arg : x.getExpressions()) { sep = _sepCommaOptSpace(sep); _parenPushIfCommaExpr(arg); accept(arg); @@ -227,6 +239,10 @@ public boolean visit(JsBlock x, JsContext ctx) { @Override public boolean visit(JsBooleanLiteral x, JsContext ctx) { + if (minifyLiterals) { + p.print(x.getValue() ? "!0" : "!1"); + return false; + } if (x.getValue()) { _true(); } else { @@ -257,8 +273,7 @@ public boolean visit(JsCase x, JsContext ctx) { _newlineOpt(); indent(); - for (Object element : x.getStmts()) { - JsStatement stmt = (JsStatement) element; + for (JsStatement stmt : x.getStmts()) { needSemi = true; accept(stmt); if (needSemi) { @@ -387,8 +402,7 @@ public boolean visit(JsDefault x, JsContext ctx) { _colon(); indent(); - for (Object element : x.getStmts()) { - JsStatement stmt = (JsStatement) element; + for (JsStatement stmt : x.getStmts()) { needSemi = true; accept(stmt); if (needSemi) { @@ -532,8 +546,7 @@ public boolean visit(JsFunction x, JsContext ctx) { _lparen(); boolean sep = false; - for (Object element : x.getParameters()) { - JsParameter param = (JsParameter) element; + for (JsParameter param : x.getParameters()) { sep = _sepCommaOptSpace(sep); accept(param); } @@ -588,8 +601,7 @@ public boolean visit(JsInvocation x, JsContext ctx) { _lparen(); boolean sep = false; - for (Object element : x.getArguments()) { - JsExpression arg = (JsExpression) element; + for (JsExpression arg : x.getArguments()) { sep = _sepCommaOptSpace(sep); _parenPushIfCommaExpr(arg); accept(arg); @@ -626,7 +638,7 @@ public boolean visit(JsNameRef x, JsContext ctx) { _parenPush(x, q, false); accept(q); if (q instanceof JsNumberLiteral) { - /** + /* * Fix for Issue #3796. "42.foo" is not allowed, but "42 .foo" is. */ _space(); @@ -681,20 +693,51 @@ public boolean visit(JsNullLiteral x, JsContext ctx) { @Override public boolean visit(JsNumberLiteral x, JsContext ctx) { + String val = stringifyNumber(x); + p.print(val); + return false; + } + + private String stringifyNumber(JsNumberLiteral x) { double dvalue = x.getValue(); if (dvalue == 0.0 && 1.0 / dvalue == Double.NEGATIVE_INFINITY) { // Negative zero is distinct from 0.0 and (integer) 0 - p.print("-0."); - return false; + return "-0"; } long lvalue = (long) dvalue; if (lvalue == dvalue) { - p.print(Long.toString(lvalue)); + String longVal = Long.toString(lvalue); + if (minifyLiterals && lvalue != 0) { + int trailingZeros = numberOfTrailingDecZeros(longVal); + if (trailingZeros > 2) { + // print 1000 as 1e3, keep 100 as is + longVal = longVal.substring(0, longVal.length() - trailingZeros) + "e" + trailingZeros; + } else if (Math.abs(lvalue) > MAX_DECIMAL_VALUE) { + // from 1e12 we may save 1 or 2 bytes by using the hex code + longVal = (lvalue < 0 ? "-0x" : "0x") + Long.toString(Math.abs(lvalue), 16); + } + } + return longVal; } else { - p.print(Double.toString(dvalue)); + String doubleVal = Double.toString(dvalue); + if (minifyLiterals) { + if (doubleVal.startsWith("0.")) { + doubleVal = doubleVal.substring(1); + } else if (doubleVal.startsWith("-0.")) { + doubleVal = "-" + doubleVal.substring(2); + } + } + return doubleVal; } - return false; + } + + private int numberOfTrailingDecZeros(String longVal) { + int idx = longVal.length() - 1; + while (longVal.charAt(idx) == '0') { + idx--; + } + return longVal.length() - idx - 1; } @Override @@ -790,7 +833,11 @@ public boolean visit(JsReturn x, JsContext ctx) { _return(); JsExpression expr = x.getExpr(); if (expr != null) { - _space(); + if (spaceReturn(expr)) { + _space(); + } else { + _spaceOpt(); + } accept(expr); } return false; @@ -1275,6 +1322,20 @@ private boolean _spaceCalc(JsOperator op, JsExpression arg) { return false; } + private boolean spaceReturn(JsExpression arg) { + if (arg instanceof JsBooleanLiteral) { + return !minifyLiterals; + } + if (arg instanceof JsPrefixOperation) { + return ((JsPrefixOperation) arg).getOperator().isKeyword(); + } + if (arg instanceof JsNumberLiteral) { + String value = stringifyNumber((JsNumberLiteral) arg); + return value.charAt(0) != '-' && value.charAt(0) != '.'; + } + return true; + } + private void _spaceOpt() { p.printOpt(' '); } diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java index 98472c7442c..432abd4f84c 100644 --- a/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java +++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java @@ -59,7 +59,8 @@ public final String toSource() { */ public final String toSource(boolean useLongIdents) { DefaultTextOutput out = new DefaultTextOutput(false); - JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out, useLongIdents); + JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out, + new JsToStringGenerationVisitor.PrintOptions(useLongIdents, false)); v.accept(this); return out.toString(); } diff --git a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java index ffee551409d..18a7d5bce58 100644 --- a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java +++ b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java @@ -73,11 +73,7 @@ public void indentOut() { @Override public void newline() { - if (compact) { - out.print('\n'); - } else { - out.print('\n'); - } + out.print('\n'); position++; line++; column = 0; diff --git a/dev/core/test/com/google/gwt/dev/js/JsReportGenerationVisitorTest.java b/dev/core/test/com/google/gwt/dev/js/JsReportGenerationVisitorTest.java index 6bde11f98e5..9fedda8fb1d 100644 --- a/dev/core/test/com/google/gwt/dev/js/JsReportGenerationVisitorTest.java +++ b/dev/core/test/com/google/gwt/dev/js/JsReportGenerationVisitorTest.java @@ -253,7 +253,8 @@ private void checkMappings(String ...expectedLines) throws IOException, JsParserException { DefaultTextOutput text = new DefaultTextOutput(compact); JsReportGenerationVisitor generator = new JsReportGenerationVisitor(text, - JavaToJavaScriptMap.EMPTY, false) { + JavaToJavaScriptMap.EMPTY, false, + new JsToStringGenerationVisitor.PrintOptions(false, false)) { @Override boolean surroundsInJavaSource(SourceInfo parent, SourceInfo child) { // The Rhino-based JavaScript parser doesn't provide character ranges diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java index 4194954b6d2..e9c186aacd7 100644 --- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java +++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java @@ -18,7 +18,9 @@ import com.google.gwt.dev.cfg.BindingProperty; import com.google.gwt.dev.cfg.ConditionNone; import com.google.gwt.dev.cfg.ConfigurationProperty; +import com.google.gwt.dev.jjs.impl.DeadCodeElimination; import com.google.gwt.dev.jjs.impl.FullCompileTestBase; +import com.google.gwt.dev.jjs.impl.FullOptimizerContext; import com.google.gwt.dev.util.DefaultTextOutput; import com.google.gwt.dev.util.TextOutput; import com.google.gwt.thirdparty.guava.common.collect.Maps; @@ -31,6 +33,8 @@ */ public class JsToStringGenerationVisitorTest extends FullCompileTestBase { + private boolean runDeadCodeElimination = false; + // Compilation Configuration Properties. @Override public void setUp() throws Exception { @@ -39,6 +43,7 @@ public void setUp() throws Exception { stackMode.addDefinedValue(new ConditionNone(), "STRIP"); setProperties(new BindingProperty[] {stackMode}, new String[] {"STRIP"}, new ConfigurationProperty[] {}); + runDeadCodeElimination = false; super.setUp(); } @@ -75,7 +80,85 @@ public void testClassRangeMarking() throws UnableToCompleteException { assertTrue(programClassRange.getEndPosition() < text.getPosition()); } + public void testLiteralPrint() throws UnableToCompleteException { + TextOutput text = buildTextOutput(new JsToStringGenerationVisitor.PrintOptions(false, false)); + + assertContains("_.truth=function(){return true}", text.toString()); + assertContains("_.falsehood=function(){return false}", text.toString()); + assertContains("_.zero=function(){return 0}", text.toString()); + assertContains("_.negZero=function(){return-0}", text.toString()); + assertContains("_.decimal=function(){return 0.1}", text.toString()); + assertContains("_.negDecimal=function(){return-0.1}", text.toString()); + assertContains("_.hundred=function(){return 100}", text.toString()); + assertContains("_.thousand=function(){return 1000}", text.toString()); + assertContains("_.maxDec=function(){return 999999999999}", text.toString()); + assertContains("_.minHex=function(){return 1000000000001}", text.toString()); + assertContains("_.maxAbsNegDec=function(){return-999999999999}", text.toString()); + assertContains("_.minAbsNegHex=function(){return-1000000000001}", text.toString()); + } + + public void testLiteralPrintWithDCE() throws UnableToCompleteException { + runDeadCodeElimination = true; + TextOutput text = buildTextOutput(new JsToStringGenerationVisitor.PrintOptions(false, false)); + assertContains("_.negZero=function(){return-0}", text.toString()); + assertContains("_.negDecimal=function(){return-0.1}", text.toString()); + assertContains("_.maxAbsNegDec=function(){return-999999999999}", text.toString()); + assertContains("_.minAbsNegHex=function(){return-1000000000001}", text.toString()); + } + + public void testLiteralMinification() throws UnableToCompleteException { + runDeadCodeElimination = true; + TextOutput text = buildTextOutput(new JsToStringGenerationVisitor.PrintOptions(false, true)); + + assertContains("_.truth=function(){return!0}", text.toString()); + assertContains("_.falsehood=function(){return!1}", text.toString()); + assertContains("_.zero=function(){return 0}", text.toString()); + assertContains("_.negZero=function(){return-0}", text.toString()); + assertContains("_.decimal=function(){return.1}", text.toString()); + assertContains("_.negDecimal=function(){return-.1}", text.toString()); + assertContains("_.hundred=function(){return 100}", text.toString()); + assertContains("_.thousand=function(){return 1e3}", text.toString()); + assertContains("_.maxDec=function(){return 999999999999}", text.toString()); + assertContains("_.minHex=function(){return 0xe8d4a51001}", text.toString()); + assertContains("_.maxAbsNegDec=function(){return-999999999999}", text.toString()); + assertContains("_.minAbsNegHex=function(){return-0xe8d4a51001}", text.toString()); + } + + private TextOutput buildTextOutput(JsToStringGenerationVisitor.PrintOptions options) + throws UnableToCompleteException { + String code = "package test;\n" + + "public class EntryPoint {\n" + + " public double decimal() { return 0.1;}\n" + + " public double negDecimal() { return -0.1;}\n" + + " public double zero() { return 0.0;}\n" + + " public double negZero() { return -0.0;}\n" + + " public double hundred() { return 100;}\n" + + " public double thousand() { return 1000;}\n" + + " public double maxDec() { return 999999999999.0;}\n" + + " public double minHex() { return 1000000000001.0;}\n" + + " public double maxAbsNegDec() { return -999999999999.0;}\n" + + " public double minAbsNegHex() { return -1000000000001.0;}\n" + + " public boolean truth() { return true;}\n" + + " public boolean falsehood() { return false;}\n" + + " public static void onModuleLoad() {}\n" + + "}\n"; + + // Compiles EntryPoint to JS. + compileSnippetToJS(code); + TextOutput text = new DefaultTextOutput(true); + JsSourceGenerationVisitor visitor = new JsSourceGenerationVisitor(text, options); + visitor.accept(jsProgram); + return text; + } + + private void assertContains(String needle, String haystack) { + assertTrue("Should contain " + needle + " but was " + haystack, haystack.contains(needle)); + } + @Override protected void optimizeJava() { + if (runDeadCodeElimination) { + DeadCodeElimination.exec(jProgram, new FullOptimizerContext(jProgram)); + } } } From edd94455fa5ab4a9b299829c06020cd70260da21 Mon Sep 17 00:00:00 2001 From: Zbynek Konecny Date: Fri, 6 Mar 2026 18:15:01 +0100 Subject: [PATCH 2/5] Avoid String to char[] conversion, remove log message --- .../gwt/dev/jjs/JavaToJavaScriptCompiler.java | 1 - .../dev/js/JsToStringGenerationVisitor.java | 96 +++++++++---------- .../gwt/dev/util/AbstractTextOutput.java | 10 +- 3 files changed, 56 insertions(+), 51 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index 65483b75fe0..d11ceb05479 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java @@ -1482,7 +1482,6 @@ private void optimizeJavaToFixedPoint() throws InterruptedException { nodeCount = jprogram.getNodeCount(); mods = stats.getNumMods(); - logger.log(TreeLogger.Type.TRACE, "Optimization pass " + passCount + " of " + passLimit + "\n" + stats); } float nodeChangeRate = mods / (float) lastNodeCount; diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java index 2223683bd42..63d0bb9d443 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java @@ -87,30 +87,30 @@ @SuppressWarnings("checkstyle:MethodName") public class JsToStringGenerationVisitor extends JsVisitor { - private static final char[] CHARS_BREAK = "break".toCharArray(); - private static final char[] CHARS_CASE = "case".toCharArray(); - private static final char[] CHARS_CATCH = "catch".toCharArray(); - private static final char[] CHARS_CONTINUE = "continue".toCharArray(); - private static final char[] CHARS_DEBUGGER = "debugger".toCharArray(); - private static final char[] CHARS_DEFAULT = "default".toCharArray(); - private static final char[] CHARS_DO = "do".toCharArray(); - private static final char[] CHARS_ELSE = "else".toCharArray(); - private static final char[] CHARS_FALSE = "false".toCharArray(); - private static final char[] CHARS_FINALLY = "finally".toCharArray(); - private static final char[] CHARS_FOR = "for".toCharArray(); - private static final char[] CHARS_FUNCTION = "function".toCharArray(); - private static final char[] CHARS_IF = "if".toCharArray(); - private static final char[] CHARS_IN = "in".toCharArray(); - private static final char[] CHARS_NEW = "new".toCharArray(); - private static final char[] CHARS_NULL = "null".toCharArray(); - private static final char[] CHARS_RETURN = "return".toCharArray(); - private static final char[] CHARS_SWITCH = "switch".toCharArray(); - private static final char[] CHARS_THIS = "this".toCharArray(); - private static final char[] CHARS_THROW = "throw".toCharArray(); - private static final char[] CHARS_TRUE = "true".toCharArray(); - private static final char[] CHARS_TRY = "try".toCharArray(); - private static final char[] CHARS_VAR = "var".toCharArray(); - private static final char[] CHARS_WHILE = "while".toCharArray(); + private static final String BREAK = "break"; + private static final String CASE = "case"; + private static final String CATCH = "catch"; + private static final String CONTINUE = "continue"; + private static final String DEBUGGER = "debugger"; + private static final String DEFAULT = "default"; + private static final String DO = "do"; + private static final String ELSE = "else"; + private static final String FALSE = "false"; + private static final String FINALLY = "finally"; + private static final String FOR = "for"; + private static final String FUNCTION = "function"; + private static final String IF = "if"; + private static final String IN = "in"; + private static final String NEW = "new"; + private static final String NULL = "null"; + private static final String RETURN = "return"; + private static final String SWITCH = "switch"; + private static final String THIS = "this"; + private static final String THROW = "throw"; + private static final String TRUE = "true"; + private static final String TRY = "try"; + private static final String VAR = "var"; + private static final String WHILE = "while"; /** * How many lines of code to print inside of a JsBlock when printing terse. */ @@ -1050,15 +1050,15 @@ private void _blockOpen() { } private void _break() { - p.print(CHARS_BREAK); + p.print(BREAK); } private void _case() { - p.print(CHARS_CASE); + p.print(CASE); } private void _catch() { - p.print(CHARS_CATCH); + p.print(CATCH); } private void _colon() { @@ -1066,19 +1066,19 @@ private void _colon() { } private void _continue() { - p.print(CHARS_CONTINUE); + p.print(CONTINUE); } private void _debugger() { - p.print(CHARS_DEBUGGER); + p.print(DEBUGGER); } private void _default() { - p.print(CHARS_DEFAULT); + p.print(DEFAULT); } private void _do() { - p.print(CHARS_DO); + p.print(DO); } private void _dot() { @@ -1086,31 +1086,31 @@ private void _dot() { } private void _else() { - p.print(CHARS_ELSE); + p.print(ELSE); } private void _false() { - p.print(CHARS_FALSE); + p.print(FALSE); } private void _finally() { - p.print(CHARS_FINALLY); + p.print(FINALLY); } private void _for() { - p.print(CHARS_FOR); + p.print(FOR); } private void _function() { - p.print(CHARS_FUNCTION); + p.print(FUNCTION); } private void _if() { - p.print(CHARS_IF); + p.print(IF); } private void _in() { - p.print(CHARS_IN); + p.print(IN); } private void _lbrace() { @@ -1168,11 +1168,11 @@ private boolean _nestedPush(JsStatement statement, boolean needSpace) { } private void _new() { - p.print(CHARS_NEW); + p.print(NEW); } private void _null() { - p.print(CHARS_NULL); + p.print(NULL); } private boolean _parenCalc(JsExpression parent, JsExpression child, @@ -1249,7 +1249,7 @@ private void _rbrace() { } private void _return() { - p.print(CHARS_RETURN); + p.print(RETURN); } private void _rparen() { @@ -1341,31 +1341,31 @@ private void _spaceOpt() { } private void _switch() { - p.print(CHARS_SWITCH); + p.print(SWITCH); } private void _this() { - p.print(CHARS_THIS); + p.print(THIS); } private void _throw() { - p.print(CHARS_THROW); + p.print(THROW); } private void _true() { - p.print(CHARS_TRUE); + p.print(TRUE); } private void _try() { - p.print(CHARS_TRY); + p.print(TRY); } private void _var() { - p.print(CHARS_VAR); + p.print(VAR); } private void _while() { - p.print(CHARS_WHILE); + p.print(WHILE); } private void indent() { diff --git a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java index 18a7d5bce58..707536ab4e1 100644 --- a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java +++ b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java @@ -110,7 +110,7 @@ public void print(char[] s) { @Override public void print(String s) { maybeIndent(); - printAndCount(s.toCharArray()); + printAndCount(s); justNewlined = false; } @@ -136,7 +136,7 @@ public void printOpt(char[] s) { public void printOpt(String s) { if (!compact) { maybeIndent(); - printAndCount(s.toCharArray()); + printAndCount(s); } } @@ -156,4 +156,10 @@ private void printAndCount(char[] chars) { column += chars.length; out.print(chars); } + + private void printAndCount(String str) { + position += str.length(); + column += str.length(); + out.print(str); + } } From 271e29ca9c69201b2a083488b47fdf7b86311b61 Mon Sep 17 00:00:00 2001 From: Zbynek Konecny Date: Fri, 6 Mar 2026 19:25:25 +0100 Subject: [PATCH 3/5] Avoid double serialization, consistent naming --- .../dev/js/JsToStringGenerationVisitor.java | 36 ++++++++++--------- .../gwt/dev/util/AbstractTextOutput.java | 6 +--- .../js/JsToStringGenerationVisitorTest.java | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java index 63d0bb9d443..0465cfa4c64 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java @@ -693,12 +693,12 @@ public boolean visit(JsNullLiteral x, JsContext ctx) { @Override public boolean visit(JsNumberLiteral x, JsContext ctx) { - String val = stringifyNumber(x); + String val = _stringifyNumber(x); p.print(val); return false; } - private String stringifyNumber(JsNumberLiteral x) { + private String _stringifyNumber(JsNumberLiteral x) { double dvalue = x.getValue(); if (dvalue == 0.0 && 1.0 / dvalue == Double.NEGATIVE_INFINITY) { // Negative zero is distinct from 0.0 and (integer) 0 @@ -833,12 +833,7 @@ public boolean visit(JsReturn x, JsContext ctx) { _return(); JsExpression expr = x.getExpr(); if (expr != null) { - if (spaceReturn(expr)) { - _space(); - } else { - _spaceOpt(); - } - accept(expr); + _printReturnExpression(expr); } return false; } @@ -1322,18 +1317,27 @@ private boolean _spaceCalc(JsOperator op, JsExpression arg) { return false; } - private boolean spaceReturn(JsExpression arg) { + private void _printReturnExpression(JsExpression arg) { + boolean space = false; + String value = null; if (arg instanceof JsBooleanLiteral) { - return !minifyLiterals; + space = !minifyLiterals; + } else if (arg instanceof JsPrefixOperation) { + space = ((JsPrefixOperation) arg).getOperator().isKeyword(); + } else if (arg instanceof JsNumberLiteral) { + value = _stringifyNumber((JsNumberLiteral) arg); + space = value.charAt(0) != '-' && value.charAt(0) != '.'; } - if (arg instanceof JsPrefixOperation) { - return ((JsPrefixOperation) arg).getOperator().isKeyword(); + if (space) { + _space(); + } else { + _spaceOpt(); } - if (arg instanceof JsNumberLiteral) { - String value = stringifyNumber((JsNumberLiteral) arg); - return value.charAt(0) != '-' && value.charAt(0) != '.'; + if (value != null) { + p.print(value); + } else { + accept(arg); } - return true; } private void _spaceOpt() { diff --git a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java index 707536ab4e1..712a27e5466 100644 --- a/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java +++ b/dev/core/src/com/google/gwt/dev/util/AbstractTextOutput.java @@ -83,11 +83,7 @@ public void newline() { @Override public void newlineOpt() { if (!compact) { - out.print('\n'); - position++; - line++; - column = 0; - justNewlined = true; + newline(); } } diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java index e9c186aacd7..4db0da3de13 100644 --- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java +++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorTest.java @@ -43,7 +43,6 @@ public void setUp() throws Exception { stackMode.addDefinedValue(new ConditionNone(), "STRIP"); setProperties(new BindingProperty[] {stackMode}, new String[] {"STRIP"}, new ConfigurationProperty[] {}); - runDeadCodeElimination = false; super.setUp(); } @@ -98,6 +97,7 @@ public void testLiteralPrint() throws UnableToCompleteException { } public void testLiteralPrintWithDCE() throws UnableToCompleteException { + // negative numbers represented as unary operation before DCE and as a literal after DCE runDeadCodeElimination = true; TextOutput text = buildTextOutput(new JsToStringGenerationVisitor.PrintOptions(false, false)); assertContains("_.negZero=function(){return-0}", text.toString()); From 7adbee50a121b854df4e7d0734f44a7dd8afffb0 Mon Sep 17 00:00:00 2001 From: Zbynek Konecny Date: Tue, 10 Mar 2026 20:42:57 +0100 Subject: [PATCH 4/5] Fix failing test --- .../gwt/dev/js/JsToStringGenerationVisitor.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java index 0465cfa4c64..e1f5339bc06 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java @@ -1318,14 +1318,13 @@ private boolean _spaceCalc(JsOperator op, JsExpression arg) { } private void _printReturnExpression(JsExpression arg) { - boolean space = false; - String value = null; + boolean space = true; if (arg instanceof JsBooleanLiteral) { space = !minifyLiterals; } else if (arg instanceof JsPrefixOperation) { space = ((JsPrefixOperation) arg).getOperator().isKeyword(); } else if (arg instanceof JsNumberLiteral) { - value = _stringifyNumber((JsNumberLiteral) arg); + String value = _stringifyNumber((JsNumberLiteral) arg); space = value.charAt(0) != '-' && value.charAt(0) != '.'; } if (space) { @@ -1333,11 +1332,7 @@ private void _printReturnExpression(JsExpression arg) { } else { _spaceOpt(); } - if (value != null) { - p.print(value); - } else { - accept(arg); - } + accept(arg); // this may serialize numbers again, but needed for billing } private void _spaceOpt() { From cca743ffc1d4cf75b812539db03e2823578e762e Mon Sep 17 00:00:00 2001 From: Zbynek Konecny Date: Mon, 27 Apr 2026 20:59:32 +0200 Subject: [PATCH 5/5] Inline short bool values --- .../google/gwt/dev/js/JsToStringGenerationVisitor.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java index e1f5339bc06..9bf04b54d17 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java @@ -239,10 +239,6 @@ public boolean visit(JsBlock x, JsContext ctx) { @Override public boolean visit(JsBooleanLiteral x, JsContext ctx) { - if (minifyLiterals) { - p.print(x.getValue() ? "!0" : "!1"); - return false; - } if (x.getValue()) { _true(); } else { @@ -1085,7 +1081,7 @@ private void _else() { } private void _false() { - p.print(FALSE); + p.print(minifyLiterals ? "!1" : FALSE); } private void _finally() { @@ -1352,7 +1348,7 @@ private void _throw() { } private void _true() { - p.print(TRUE); + p.print(minifyLiterals ? "!0" : TRUE); } private void _try() {