force order of JFlex switch statements to help with reproducible binaries
diff --git a/compiler-build-tools/build.xml b/compiler-build-tools/build.xml
index b548f01..4ea1a61 100644
--- a/compiler-build-tools/build.xml
+++ b/compiler-build-tools/build.xml
@@ -81,6 +81,22 @@
</javac>
</target>
+ <!-- The OrderSwitches tool is used to order a switch statement from
+ JFlex for reproducible binaries -->
+ <target name="order.switches"
+ description="Compiles the OrderSwitches build tool" >
+ <mkdir dir="${compiler-build-tools}/target/classes"/>
+ <javac debug="${javac.debug}" deprecation="${javac.deprecation}" destdir="${compiler-build-tools}/target/classes" includeAntRuntime="true"
+ source="${javac.src}" target="${javac.src}">
+ <compilerarg value="-Xlint:all,-path,-fallthrough"/>
+ <src path="${compiler-build-tools}/src/main/java"/>
+ <include name="org/apache/royale/compiler/tools/annotate/OrderSwitches.java"/>
+ <classpath>
+ <pathelement location="${compiler-build-tools}/target/classes"/>
+ </classpath>
+ </javac>
+ </target>
+
<!-- The UnknownTreePatternInputOutput tool is used to compile an XML file containing unknown AST patterns to a Java class -->
<target name="unknown.tree.pattern.input.output"
description="Compiles the UnknownTreePatternInputOutput tool">
@@ -108,7 +124,7 @@
</javac>
</target>
- <target name="main" depends="annotate.class, unknown.tree.pattern.input.output" />
+ <target name="main" depends="annotate.class,order.switches, unknown.tree.pattern.input.output" />
<!--
diff --git a/compiler-build-tools/src/main/java/org/apache/royale/compiler/tools/annotate/OrderSwitches.java b/compiler-build-tools/src/main/java/org/apache/royale/compiler/tools/annotate/OrderSwitches.java
new file mode 100644
index 0000000..f99c287
--- /dev/null
+++ b/compiler-build-tools/src/main/java/org/apache/royale/compiler/tools/annotate/OrderSwitches.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.royale.compiler.tools.annotate;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+public class OrderSwitches
+{
+ public static void processFile(File file) throws AnnotateClass.AnnotateClassDeleteException, AnnotateClass.AnnotateClassRenameException {
+ if(!file.exists()) {
+ System.out.println("Missing file: " + file.getPath());
+ return;
+ }
+ try
+ {
+ // Prepare to read the file.
+ FileInputStream fileInputStream = new FileInputStream(file);
+ InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
+ BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
+
+ File tmpOutputFile = new File(file.getParentFile(), file.getName() + ".tmp");
+ FileOutputStream fileOutputStream = new FileOutputStream(tmpOutputFile);
+ PrintStream outputStream = new PrintStream(fileOutputStream);
+ try
+ {
+ // Read it line-by-line.
+ String line;
+ ArrayList<String> lines = new ArrayList<String>();
+ boolean inSwitch = false;
+ while ((line = bufferedReader.readLine()) != null)
+ {
+ if (line.contains("The following code was generated by JFlex "))
+ {
+ int c = line.indexOf(" on ");
+ if (c != -1)
+ line = line.substring(0, c) + " */";
+ }
+ if (line.contains("* on ") && line.contains("from the specification file"))
+ line = " * from the specification file";
+ if (line.contains("<tt>") && line.contains("compiler/src/main/jflex"))
+ {
+ int t = line.indexOf("<tt>");
+ int rc = line.indexOf("compiler/src/main/jflex");
+ line = line.substring(0, t + 4) + line.substring(rc);
+ }
+ // If the line starts with "switch", and the cases are numbers, make sure they are in order.
+ if (line.contains(" switch (zzAction")) {
+ inSwitch = true;
+ System.out.println("Ordering Switch in file: " + file.getPath());
+ }
+ if (inSwitch)
+ {
+ lines.add(line);
+ if (line.startsWith(" }")) {
+ orderSwitch(lines);
+ for (String orderedLine : lines)
+ {
+ outputStream.println(orderedLine);
+ }
+ inSwitch = false;
+ }
+ }
+ else
+ outputStream.println(line);
+ }
+ }
+ finally
+ {
+ try {
+ bufferedReader.close();
+ } catch(Exception e) {
+ // Ignore.
+ }
+ try {
+ outputStream.close();
+ } catch(Exception e) {
+ // Ignore.
+ }
+ try {
+ fileOutputStream.close();
+ } catch(Exception e) {
+ // Ignore.
+ }
+ try {
+ inputStreamReader.close();
+ } catch(Exception e) {
+ // Ignore.
+ }
+ try {
+ fileInputStream.close();
+ } catch(Exception e) {
+ // Ignore.
+ }
+ }
+
+ // Remove the original file.
+ if(!file.delete()) {
+ // wait a bit then retry on Windows
+ if (file.exists())
+ {
+ for (int i = 0; i < 6; i++)
+ {
+ Thread.sleep(500);
+ System.gc();
+ if (file.delete())
+ break;
+ }
+ if (file.exists())
+ throw new AnnotateClass.AnnotateClassDeleteException("Error deleting original file at: " + file.getPath());
+ }
+ }
+
+ // Rename the temp file to the name of the original file.
+ if(!tmpOutputFile.renameTo(file)) {
+ throw new AnnotateClass.AnnotateClassRenameException("Error renaming the temp file from: " + tmpOutputFile.getPath() +
+ " to: " + file.getPath());
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private static void orderSwitch(ArrayList<String> lines) {
+ HashMap<String,List<String>> switchMap = new HashMap<String,List<String>>();
+ ArrayList<String> defaultCase = new ArrayList<String>();
+ ArrayList<String> breaks = new ArrayList<String>();
+ String line0 = lines.get(0); // first line should be switch statement
+ boolean inDefault = false;
+ int n = lines.size();
+ for (int i = 1; i < n; i++)
+ {
+ String line = lines.get(i);
+ int c = line.indexOf(" case ");
+ if (!inDefault && (c != -1))
+ {
+ String cayse = line.substring(c);
+ if (line.contains("break;"))
+ {
+ // some cases just contain a break statement.
+ // the are used to make sure the prior case
+ // ends with a break without having to check
+ // if the break statement would be reachable
+ // due to return statements in the case.
+ breaks.add(line);
+ continue;
+ }
+ else
+ {
+ ArrayList<String> caseLines = new ArrayList<String>();
+ for (int j = i + 1; j < n; j++)
+ {
+ line = lines.get(j);
+ if (line.contains(" case "))
+ {
+ switchMap.put(cayse, caseLines);
+ i = j - 1;
+ break;
+ }
+ else if (line.contains("default:"))
+ {
+ switchMap.put(cayse, caseLines);
+ inDefault = true;
+ i = j;
+ // assumes no break at end of default case
+ defaultCase.add(line);
+ break;
+ }
+ caseLines.add(line);
+ }
+ }
+ }
+ else if (line.contains("default:"))
+ {
+ inDefault = true;
+ // assumes no break at end of default case
+ defaultCase.add(line);
+ }
+ else if (inDefault)
+ defaultCase.add(line);
+ }
+ Set<String> keys = switchMap.keySet();
+ ArrayList<String> keyList = new ArrayList<String>();
+ keyList.addAll(keys);
+ Collections.sort(keyList);
+ lines.clear();
+ lines.add(line0);
+ int breakIndex = 0;
+ for (String key : keyList)
+ {
+ lines.add(" " + key);
+ List<String> caseLines = switchMap.get(key);
+ lines.addAll(caseLines);
+ lines.add(breaks.get(breakIndex++));
+ }
+ boolean inSwitch = false;
+ boolean sawDefault = false;
+ ArrayList<String> switchLines = new ArrayList<String>();
+ for (String defaultLine : defaultCase)
+ {
+ // If the line starts with "switch", and the cases are numbers, make sure they are in order.
+ if (defaultLine.contains("switch (zzLexicalState)")) {
+ inSwitch = true;
+ System.out.println("Ordering Switch in default: ");
+ }
+ if (inSwitch)
+ {
+ switchLines.add(defaultLine);
+ if (defaultLine.contains("default:"))
+ sawDefault = true;
+ if (sawDefault && defaultLine.startsWith(" }")) {
+ orderSwitch(switchLines);
+ for (String orderedLine : switchLines)
+ {
+ lines.add(orderedLine);
+ }
+ inSwitch = false;
+ }
+ }
+ else
+ lines.add(defaultLine);
+
+ }
+
+ }
+
+ public static void main(String[] args)
+ {
+ File f = new File(args[0]);
+ try {
+ processFile(f);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/compiler/build.xml b/compiler/build.xml
index bca3b89..3348d3e 100644
--- a/compiler/build.xml
+++ b/compiler/build.xml
@@ -202,6 +202,25 @@
</macrodef>
<!--
+ Defines an <order.switches file="..."> macro
+ used for ordering the switch statement generated by
+ JFlex in order to get reproducible builds.
+ -->
+ <macrodef name="order.switches">
+ <attribute name="file"/>
+ <sequential>
+ <java classname="org.apache.royale.compiler.tools.annotate.OrderSwitches" fork="false">
+ <classpath>
+ <path refid="classpath"/>
+ <pathelement location="${compiler}/../compiler-build-tools/target/classes"/>
+ <pathelement location="${compiler}/target/classes"/>
+ </classpath>
+ <arg value="@{file}"/>
+ </java>
+ </sequential>
+ </macrodef>
+
+ <!--
SETUP
@@ -237,6 +256,7 @@
<jflex input="${compiler}/src/main/jflex/org/apache/royale/compiler/internal/parsing/as/RawASTokenizer.lex"
skeleton="${compiler}/src/main/jflex/org/apache/royale/compiler/internal/parsing/as/skeleton.royale"
output="${compiler}/target/generated-sources/jflex/org/apache/royale/compiler/internal/parsing/as"/>
+ <order.switches file="${compiler}/target/generated-sources/jflex/org/apache/royale/compiler/internal/parsing/as/RawASTokenizer.java" />
</target>
<target name="set.raw.asdoc.tokenizer.uptodate">
@@ -254,6 +274,7 @@
<echo message="Generating RawASDocTokenizer"/>
<jflex input="${compiler}/src/main/jflex/org/apache/royale/compiler/internal/parsing/as/RawASDocTokenizer.lex"
output="${compiler}/target/generated-sources/jflex/org/apache/royale/compiler/internal/parsing/as"/>
+ <order.switches file="${compiler}/target/generated-sources/jflex/org/apache/royale/compiler/internal/parsing/as/RawASDocTokenizer.java" />
</target>
<target name="set.raw.mxml.tokenizer.uptodate">
@@ -271,6 +292,7 @@
<echo message="Generating RawMXMLTokenizer"/>
<jflex input="${compiler}/src/main/jflex/org/apache/royale/compiler/internal/parsing/mxml/RawMXMLTokenizer.lex"
output="${compiler}/target/generated-sources/jflex/org/apache/royale/compiler/internal/parsing/mxml"/>
+ <order.switches file="${compiler}/target/generated-sources/jflex/org/apache/royale/compiler/internal/parsing/mxml/RawMXMLTokenizer.java" />
</target>
<target name="jflex" depends="raw.as.tokenizer, raw.asdoc.tokenizer, raw.mxml.tokenizer"