delocate: be able to preprocess inputs.

In the CMake build we did this with
https://boringssl-review.googlesource.com/c/boringssl/+/44847. But in
other environments delocate may need to run cpp itself.

Change-Id: I429e849f6d7c566aa14e63be6c8e93f9dd6847ed
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/55306
Commit-Queue: Bob Beck <bbe@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/util/fipstools/delocate/delocate.go b/util/fipstools/delocate/delocate.go
index 9c4df04..7f4b8f5 100644
--- a/util/fipstools/delocate/delocate.go
+++ b/util/fipstools/delocate/delocate.go
@@ -17,10 +17,13 @@
 package main
 
 import (
+	"bytes"
 	"errors"
 	"flag"
 	"fmt"
 	"os"
+	"os/exec"
+	"path/filepath"
 	"sort"
 	"strconv"
 	"strings"
@@ -1955,7 +1958,25 @@
 	return nil
 }
 
-func parseInputs(inputs []inputFile) error {
+// preprocess runs source through the C preprocessor.
+func preprocess(cppCommand []string, path string) ([]byte, error) {
+	var args []string
+	args = append(args, cppCommand...)
+	args = append(args, path)
+
+	cpp := exec.Command(args[0], args[1:]...)
+	cpp.Stderr = os.Stderr
+	var result bytes.Buffer
+	cpp.Stdout = &result
+
+	if err := cpp.Run(); err != nil {
+		return nil, err
+	}
+
+	return result.Bytes(), nil
+}
+
+func parseInputs(inputs []inputFile, cppCommand []string) error {
 	for i, input := range inputs {
 		var contents string
 
@@ -1979,7 +2000,14 @@
 				contents = string(c)
 			}
 		} else {
-			inBytes, err := os.ReadFile(input.path)
+			var inBytes []byte
+			var err error
+
+			if len(cppCommand) > 0 {
+				inBytes, err = preprocess(cppCommand, input.path)
+			} else {
+				inBytes, err = os.ReadFile(input.path)
+			}
 			if err != nil {
 				return err
 			}
@@ -2001,12 +2029,36 @@
 	return nil
 }
 
+// includePathFromHeaderFilePath returns an include directory path based on the
+// path of a specific header file. It walks up the path and assumes that the
+// include files are rooted in a directory called "openssl".
+func includePathFromHeaderFilePath(path string) (string, error) {
+	dir := path
+	for {
+		var file string
+		dir, file = filepath.Split(dir)
+
+		if file == "openssl" {
+			return dir, nil
+		}
+
+		if len(dir) == 0 {
+			break
+		}
+		dir = dir[:len(dir)-1]
+	}
+
+	return "", fmt.Errorf("failed to find 'openssl' path element in header file path %q", path)
+}
+
 func main() {
 	// The .a file, if given, is expected to be an archive of textual
 	// assembly sources. That's odd, but CMake really wants to create
 	// archive files so it's the only way that we can make it work.
 	arInput := flag.String("a", "", "Path to a .a file containing assembly sources")
 	outFile := flag.String("o", "", "Path to output assembly")
+	ccPath := flag.String("cc", "", "Path to the C compiler for preprocessing inputs")
+	ccFlags := flag.String("cc-flags", "", "Flags for the C compiler when preprocessing")
 
 	flag.Parse()
 
@@ -2024,18 +2076,52 @@
 		})
 	}
 
+	includePaths := make(map[string]struct{})
+
 	for i, path := range flag.Args() {
 		if len(path) == 0 {
 			continue
 		}
 
+		// Header files are not processed but their path is remembered
+		// and passed as -I arguments when invoking the preprocessor.
+		if strings.HasSuffix(path, ".h") {
+			dir, err := includePathFromHeaderFilePath(path)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "%s\n", err)
+				os.Exit(1)
+			}
+			includePaths[dir] = struct{}{}
+			continue
+		}
+
 		inputs = append(inputs, inputFile{
 			path:  path,
 			index: i + 1,
 		})
 	}
 
-	if err := parseInputs(inputs); err != nil {
+	var cppCommand []string
+	if len(*ccPath) > 0 {
+		cppCommand = append(cppCommand, *ccPath)
+		cppCommand = append(cppCommand, strings.Fields(*ccFlags)...)
+		// Some of ccFlags might be superfluous when running the
+		// preprocessor, but we don't want the compiler complaining that
+		// "argument unused during compilation".
+		cppCommand = append(cppCommand, "-Wno-unused-command-line-argument")
+		// We are preprocessing for assembly output and need to simulate that
+		// environment for arm_arch.h.
+		cppCommand = append(cppCommand, "-D__ASSEMBLER__=1")
+
+		for includePath := range includePaths {
+			cppCommand = append(cppCommand, "-I"+includePath)
+		}
+
+		// -E requests only preprocessing.
+		cppCommand = append(cppCommand, "-E")
+	}
+
+	if err := parseInputs(inputs, cppCommand); err != nil {
 		fmt.Fprintf(os.Stderr, "%s\n", err)
 		os.Exit(1)
 	}
diff --git a/util/fipstools/delocate/delocate_test.go b/util/fipstools/delocate/delocate_test.go
index 5176c3c..082b3aa 100644
--- a/util/fipstools/delocate/delocate_test.go
+++ b/util/fipstools/delocate/delocate_test.go
@@ -65,7 +65,7 @@
 				})
 			}
 
-			if err := parseInputs(inputs); err != nil {
+			if err := parseInputs(inputs, nil); err != nil {
 				t.Fatalf("parseInputs failed: %s", err)
 			}