Move __.SYMDEF handling to ar.go.

One less bit of special-casing in read_symbols.go. We filter out the
sysv-style symbol table, so we should filter out the macOS one too.

Add tests for util/ar to cover this and the Linux case.

Change-Id: Id16d8b0526c1b6e0149df1df4006848d7b3a4b2f
Reviewed-on: https://boringssl-review.googlesource.com/c/33347
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/util/ar/ar.go b/util/ar/ar.go
index f5dee62..ce5c65c 100644
--- a/util/ar/ar.go
+++ b/util/ar/ar.go
@@ -141,6 +141,10 @@
 			name = name[:null]
 		}
 
+		if name == "__.SYMDEF" || name == "__.SYMDEF SORTED" {
+			continue
+		}
+
 		ret[name] = contents
 	}
 
diff --git a/util/ar/ar_test.go b/util/ar/ar_test.go
new file mode 100644
index 0000000..f9e23ff
--- /dev/null
+++ b/util/ar/ar_test.go
@@ -0,0 +1,95 @@
+// Copyright (c) 2018, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+package ar
+
+import (
+	"bytes"
+	"flag"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+var testDataDir = flag.String("testdata", "testdata", "The path to the test data directory.")
+
+type arTest struct {
+	name string
+	in   string
+	out  []string
+	// allowPadding is true if the contents may have trailing newlines at end.
+	// On macOS, ar calls ranlib which pads all inputs up to eight bytes with
+	// newlines. Unlike ar's native padding up to two bytes, this padding is
+	// included in the size field, so it is not removed when decoding.
+	allowPadding bool
+}
+
+func (test *arTest) Path(file string) string {
+	return filepath.Join(*testDataDir, test.name, file)
+}
+
+func removeTrailingNewlines(in []byte) []byte {
+	for len(in) > 0 && in[len(in)-1] == '\n' {
+		in = in[:len(in)-1]
+	}
+	return in
+}
+
+var arTests = []arTest{
+	{"linux", "libsample.a", []string{"foo.c.o", "bar.cc.o"}, false},
+	{"mac", "libsample.a", []string{"foo.c.o", "bar.cc.o"}, true},
+}
+
+func TestAR(t *testing.T) {
+	for _, test := range arTests {
+		t.Run(test.name, func(t *testing.T) {
+			in, err := os.Open(test.Path(test.in))
+			if err != nil {
+				t.Fatalf("opening input failed: %s", err)
+			}
+			defer in.Close()
+
+			ret, err := ParseAR(in)
+			if err != nil {
+				t.Fatalf("reading input failed: %s", err)
+			}
+
+			expectedFiles := make(map[string]struct{})
+			for _, file := range test.out {
+				expectedFiles[file] = struct{}{}
+				expected, err := ioutil.ReadFile(test.Path(file))
+				if err != nil {
+					t.Fatalf("error reading %s: %s", file, err)
+				}
+				got, ok := ret[file]
+				if test.allowPadding {
+					got = removeTrailingNewlines(got)
+					expected = removeTrailingNewlines(got)
+				}
+				if !ok {
+					t.Errorf("file %s missing from output", file)
+				} else if !bytes.Equal(got, expected) {
+					t.Errorf("contents for file %s did not match", file)
+				}
+			}
+
+			for file, _ := range ret {
+				if _, ok := expectedFiles[file]; !ok {
+					t.Errorf("output contained unexpected file %q", file)
+				}
+			}
+		})
+	}
+}
diff --git a/util/ar/testdata/linux/bar.cc.o b/util/ar/testdata/linux/bar.cc.o
new file mode 100644
index 0000000..92e83a9
--- /dev/null
+++ b/util/ar/testdata/linux/bar.cc.o
Binary files differ
diff --git a/util/ar/testdata/linux/foo.c.o b/util/ar/testdata/linux/foo.c.o
new file mode 100644
index 0000000..6423c1d
--- /dev/null
+++ b/util/ar/testdata/linux/foo.c.o
Binary files differ
diff --git a/util/ar/testdata/linux/libsample.a b/util/ar/testdata/linux/libsample.a
new file mode 100644
index 0000000..cae6ae7
--- /dev/null
+++ b/util/ar/testdata/linux/libsample.a
Binary files differ
diff --git a/util/ar/testdata/mac/bar.cc.o b/util/ar/testdata/mac/bar.cc.o
new file mode 100644
index 0000000..9c60798
--- /dev/null
+++ b/util/ar/testdata/mac/bar.cc.o
Binary files differ
diff --git a/util/ar/testdata/mac/foo.c.o b/util/ar/testdata/mac/foo.c.o
new file mode 100644
index 0000000..0f96a0a
--- /dev/null
+++ b/util/ar/testdata/mac/foo.c.o
Binary files differ
diff --git a/util/ar/testdata/mac/libsample.a b/util/ar/testdata/mac/libsample.a
new file mode 100644
index 0000000..b7d8eb5
--- /dev/null
+++ b/util/ar/testdata/mac/libsample.a
Binary files differ
diff --git a/util/ar/testdata/sample/CMakeLists.txt b/util/ar/testdata/sample/CMakeLists.txt
new file mode 100644
index 0000000..9ea2fe8
--- /dev/null
+++ b/util/ar/testdata/sample/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.0)
+project(Sample)
+add_library(sample STATIC foo.c bar.cc)
diff --git a/util/ar/testdata/sample/bar.cc b/util/ar/testdata/sample/bar.cc
new file mode 100644
index 0000000..a0ac7e1
--- /dev/null
+++ b/util/ar/testdata/sample/bar.cc
@@ -0,0 +1,15 @@
+extern "C" {
+void foo();
+void bar() {}
+}
+
+namespace bar_namespace {
+
+void SomeExternalFunction();
+
+void SomeFunction() {
+  foo();
+  SomeExternalFunction();
+}
+
+}  // namespace bar_namespace
diff --git a/util/ar/testdata/sample/foo.c b/util/ar/testdata/sample/foo.c
new file mode 100644
index 0000000..fed596c
--- /dev/null
+++ b/util/ar/testdata/sample/foo.c
@@ -0,0 +1,7 @@
+extern void external_symbol(void);
+extern void bar(void);
+
+void foo(void) {
+  external_symbol();
+  bar();
+}
diff --git a/util/read_symbols.go b/util/read_symbols.go
index 495e9e6..fc3e069 100644
--- a/util/read_symbols.go
+++ b/util/read_symbols.go
@@ -93,9 +93,6 @@
 		}
 
 		for name, contents := range objectFiles {
-			if !strings.HasSuffix(name, ".o") {
-				continue
-			}
 			syms, err := listSymbols(contents)
 			if err != nil {
 				printAndExit("Error listing symbols from %q in %q: %s", name, archive, err)