/*
* Copyright 2016-present Facebook, 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.facebook.buck.cxx.elf;
import com.facebook.buck.model.Pair;
import com.facebook.buck.util.RichStream;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class ElfStringTable {
private ElfStringTable() {}
private static ImmutableList<Integer> writeStringTable(
Iterable<Entry> entries, OutputStream output) throws IOException {
// Build a list from the original input strings storing them with the index of their original
// input index.
List<Pair<Integer, Entry>> originalPositionsAndEntries = new ArrayList<>();
int index = 0;
for (Entry entry : entries) {
originalPositionsAndEntries.add(new Pair<>(index++, entry));
}
// Sort the entries by the "ends with" comparator, so that strings appear immediately before
// strings that end with.
originalPositionsAndEntries.sort(
(o1, o2) -> Entry.COMMON_SUFFIXES.compare(o1.getSecond(), o2.getSecond()));
// The size of the output string table.
int size = 0;
// Initial zero byte is written for all tables.
output.write(0);
size += 1;
// Array maintaining the new indices in the new string table of the input strings, in the order
// that the input strings were given.
Integer[] indices = new Integer[originalPositionsAndEntries.size()];
// Walk over the input entries and write them out to the new string table.
Entry previousEntry = null;
for (Pair<Integer, Entry> ent : originalPositionsAndEntries) {
Entry entry = ent.getSecond();
int newIndex;
if (entry.len == 0) {
newIndex = 0;
} else {
// If this is the first entry, or the previous string doesn't end with the current one, we
// have to write out a new string entry to the output stream.
if (previousEntry == null || !previousEntry.endsWith(entry)) {
output.write(entry.data, entry.offset, entry.len);
output.write(0);
size += entry.len + 1;
previousEntry = entry;
}
newIndex = size - 1 - entry.len;
}
// Add the new string index.
indices[ent.getFirst()] = newIndex;
}
return ImmutableList.copyOf(indices);
}
private static int getLengthForStringTableAndOffset(byte[] data, int offset) {
int index = offset;
while (data[index] != 0) {
index++;
}
return index - offset;
}
/**
* Writes a string table from null terminated byte strings described by the byte array and
* indices.
*/
public static ImmutableList<Integer> writeStringTableFromStringTable(
byte[] data, Iterable<Integer> indices, OutputStream output) throws IOException {
return writeStringTable(
RichStream.from(indices)
.map(index -> new Entry(data, index, getLengthForStringTableAndOffset(data, index)))
.toImmutableList(),
output);
}
/**
* Writes a string table from the given strings.
*
* @return a list of offsets into the written string table for the input strings (where each
* offset positionally corresponds to a string in the input strings iterable).
*/
public static ImmutableList<Integer> writeStringTableFromStrings(
Iterable<String> strings, OutputStream output) throws IOException {
return writeStringTable(
RichStream.from(strings).map(s -> new Entry(s.getBytes(Charsets.UTF_8))).toImmutableList(),
output);
}
private static class Entry {
/**
* A comparator that sorts entries so that an entry immediately precedes any entry it ends with.
* This makes it simple to merge suffix strings when writing out the string table.
*/
private static final Comparator<Entry> COMMON_SUFFIXES =
(o1, o2) -> {
int idx1 = o1.offset + o1.len - 1;
int idx2 = o2.offset + o2.len - 1;
while (true) {
if (idx1 < o1.offset && idx2 < o2.offset) {
return 0;
} else if (idx1 < o1.offset) {
return 1;
} else if (idx2 < o2.offset) {
return -1;
} else {
int cmp = Byte.compare(o1.data[idx1], o2.data[idx2]);
if (cmp != 0) {
return cmp;
}
}
idx1--;
idx2--;
}
};
public final byte[] data;
public final int offset;
public final int len;
private Entry(byte[] data, int offset, int len) {
this.data = data;
this.offset = offset;
this.len = len;
}
private Entry(byte[] data) {
this(data, 0, data.length);
}
public boolean endsWith(Entry other) {
if (other.len > len) {
return false;
}
for (int idx = offset + (len - other.len), oidx = other.offset;
idx < offset + len;
idx++, oidx++) {
if (data[idx] != other.data[oidx]) {
return false;
}
}
return true;
}
@Override
public String toString() {
return new String(data, offset, len, Charsets.UTF_8);
}
}
}