Work around Safari 5 failures where right-shift of a non-integer is not
coerced to an integer.
See https://bugs.webkit.org/show_bug.cgi?id=40367 and
http://trac.webkit.org/changeset/60990 for details.
Patch by: jat
Review by: spoon, jgw
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8300 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 90cf5df..3904c43 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -95,6 +95,7 @@
import com.google.gwt.dev.jjs.impl.gflow.DataflowOptimizer;
import com.google.gwt.dev.js.EvalFunctionsAtTopScope;
import com.google.gwt.dev.js.JsBreakUpLargeVarStatements;
+import com.google.gwt.dev.js.JsCoerceIntShift;
import com.google.gwt.dev.js.JsDuplicateFunctionRemover;
import com.google.gwt.dev.js.JsIEBlockSizeVisitor;
import com.google.gwt.dev.js.JsInliner;
@@ -302,6 +303,13 @@
*/
JsStackEmulator.exec(jsProgram, propertyOracles);
+ /*
+ * Work around Safari 5 bug by rewriting a >> b as ~~a >> b.
+ *
+ * No shifts may be generated after this point.
+ */
+ JsCoerceIntShift.exec(jsProgram, logger, propertyOracles);
+
// (10) Split up the program into fragments
SyntheticArtifact dependencies = null;
if (options.isRunAsyncEnabled()) {
diff --git a/dev/core/src/com/google/gwt/dev/js/JsCoerceIntShift.java b/dev/core/src/com/google/gwt/dev/js/JsCoerceIntShift.java
new file mode 100644
index 0000000..9ede191
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsCoerceIntShift.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.SelectionProperty;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.js.ast.JsBinaryOperation;
+import com.google.gwt.dev.js.ast.JsBinaryOperator;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsPrefixOperation;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsUnaryOperator;
+
+/**
+ * Coerces lhs of right shift operations to int. Necessary for Safari 5 bug
+ * https://bugs.webkit.org/show_bug.cgi?id=40367 fixed in
+ * http://trac.webkit.org/changeset/60990 -- this should be removed once that
+ * fix has been pushed.
+ */
+public class JsCoerceIntShift {
+ // TODO(jat): remove this once Safari 5 has the update
+
+ /**
+ * Rewrite a >> b as (~~a) >> b.
+ */
+ private static class MyVisitor extends JsModVisitor {
+
+ @Override
+ public void endVisit(JsBinaryOperation x, JsContext<JsExpression> ctx) {
+ JsBinaryOperator op = x.getOperator();
+ if (op != JsBinaryOperator.SHR && op != JsBinaryOperator.SHRU) {
+ return;
+ }
+
+ SourceInfo sourceInfo = x.getSourceInfo();
+ JsExpression lhs = x.getArg1();
+ JsExpression rhs = x.getArg2();
+ JsExpression newNode = new JsBinaryOperation(sourceInfo, op,
+ new JsPrefixOperation(sourceInfo, JsUnaryOperator.BIT_NOT,
+ new JsPrefixOperation(sourceInfo, JsUnaryOperator.BIT_NOT, lhs)),
+ rhs);
+ ctx.replaceMe(newNode);
+ }
+ }
+
+ /**
+ * If this permutation may be executed on WebKit, rewrite a >> b as ~~a >> b.
+ *
+ * @param program
+ * @param logger
+ * @param propertyOracles
+ * @return true if any changes were made
+ */
+ public static boolean exec(JsProgram program, TreeLogger logger,
+ PropertyOracle[] propertyOracles) {
+ boolean seenWebKit = false;
+ for (PropertyOracle oracle : propertyOracles) {
+ try {
+ SelectionProperty prop = oracle.getSelectionProperty(logger,
+ "user.agent");
+ // TODO(jat): more checks if we split up the safari permutation
+ if ("safari".equals(prop.getCurrentValue())) {
+ seenWebKit = true;
+ break;
+ }
+ } catch (BadPropertyValueException e) {
+ // if we couldn't get the property, assume this might be used on WebKit.
+ seenWebKit = true;
+ break;
+ }
+ }
+ if (!seenWebKit) {
+ return false;
+ }
+ MyVisitor v = new MyVisitor();
+ v.accept(program);
+ return v.didChange();
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/js/JsCoerceIntShiftTest.java b/dev/core/test/com/google/gwt/dev/js/JsCoerceIntShiftTest.java
new file mode 100644
index 0000000..1ae331d
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/js/JsCoerceIntShiftTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.ConfigurationProperty;
+import com.google.gwt.core.ext.DefaultSelectionProperty;
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.SelectionProperty;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.dev.shell.FailErrorLogger;
+import com.google.gwt.dev.util.DefaultTextOutput;
+import com.google.gwt.dev.util.TextOutput;
+
+import junit.framework.TestCase;
+
+import java.io.StringReader;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Test for {@link JsCoerceIntShift}.
+ */
+public class JsCoerceIntShiftTest extends TestCase {
+
+ /**
+ * Oracle that mocks the user.agent property.
+ */
+ private static class MockOracle implements PropertyOracle {
+
+ private static SelectionProperty createSelectionProperty(String value) {
+ if (value == null) {
+ return null;
+ } else {
+ SortedSet<String> valueSet = new TreeSet<String>();
+ valueSet.add(value);
+ return new DefaultSelectionProperty(value, value, value, valueSet);
+ }
+ }
+
+ private final SelectionProperty userAgent;
+
+ /**
+ * @param userAgentName value of user.agent property, if null then the
+ * user.agent property is treated as if it doesn't exist.
+ */
+ public MockOracle(String userAgentName) {
+ userAgent = createSelectionProperty(userAgentName);
+ }
+
+ public ConfigurationProperty getConfigurationProperty(String propertyName)
+ throws BadPropertyValueException {
+ throw new BadPropertyValueException("no config properties");
+ }
+
+ @Deprecated
+ public String getPropertyValue(TreeLogger logger, String propertyName)
+ throws BadPropertyValueException {
+ throw new BadPropertyValueException("no deprecated api");
+ }
+
+ @Deprecated
+ public String[] getPropertyValueSet(TreeLogger logger, String propertyName)
+ throws BadPropertyValueException {
+ throw new BadPropertyValueException("no deprecated api");
+ }
+
+ public SelectionProperty getSelectionProperty(TreeLogger logger,
+ String propertyName) throws BadPropertyValueException {
+ if (userAgent != null && "user.agent".equals(propertyName)) {
+ return userAgent;
+ }
+ throw new BadPropertyValueException("no property " + propertyName);
+ }
+ }
+
+ private PropertyOracle safariOracle = new MockOracle("safari");
+ private PropertyOracle firefoxOracle = new MockOracle("gecko1_8");
+ private PropertyOracle ieOracle = new MockOracle("ie6");
+ private PropertyOracle noAgentOracle = new MockOracle(null);
+
+ private TreeLogger logger = new FailErrorLogger();
+
+ public void testNonSafari() throws Exception {
+ assertNotRewritten(firefoxOracle);
+ assertNotRewritten(ieOracle, firefoxOracle);
+ }
+
+ public void testNoUserAgent() throws Exception {
+ assertRewritten(noAgentOracle);
+ assertRewritten(firefoxOracle, noAgentOracle);
+ }
+
+ public void testSafari() throws Exception {
+ assertRewritten(safariOracle);
+ assertRewritten(firefoxOracle, safariOracle);
+ }
+
+ /**
+ * Assert that the provided PropertyOracles do not cause a rewrite of
+ * right-shift operations.
+ *
+ * @param oracles
+ * @throws Exception
+ */
+ private void assertNotRewritten(PropertyOracle... oracles) throws Exception {
+ assertEquals("a<<b;", process("a<<b;", oracles));
+ assertEquals("a>>b;", process("a>>b;", oracles));
+ assertEquals("a>>>b;", process("a>>>b;", oracles));
+ assertEquals("1+1>>2;", process("1+1>>2;", oracles));
+ }
+
+ /**
+ * Assert that the provided PropertyOracles do cause a rewrite of right-shift
+ * operations.
+ *
+ * @param oracles
+ * @throws Exception
+ */
+ private void assertRewritten(PropertyOracle... oracles) throws Exception {
+ assertEquals("a<<b;", process("a<<b;", oracles));
+ assertEquals("~~a>>b;", process("a>>b;", oracles));
+ assertEquals("~~a>>>b;", process("a>>>b;", oracles));
+ assertEquals("~~(1+1)>>2;", process("1+1>>2;", oracles));
+ }
+
+ /**
+ * Process a JS program with the {@link JsCoerceIntShift} pass.
+ *
+ * @param js the source program
+ * @param oracles
+ * @return processed JS
+ */
+ private String process(String js, PropertyOracle[] oracles)
+ throws Exception {
+ JsProgram program = new JsProgram();
+ List<JsStatement> expected = JsParser.parse(SourceOrigin.UNKNOWN,
+ program.getScope(), new StringReader(js));
+
+ program.getGlobalBlock().getStatements().addAll(expected);
+
+ JsCoerceIntShift.exec(program, logger, oracles);
+
+ TextOutput text = new DefaultTextOutput(true);
+ JsVisitor generator = new JsSourceGenerationVisitor(text);
+
+ generator.accept(program);
+ return text.toString();
+ }
+}