Permalink
Browse files

Parse module name out of .beam files

1 parent 627b8ad commit 4bb71f98ee5102f7e7eb3a4401347d22febb26e0 @KronicDeth committed Dec 11, 2016
View
@@ -1,6 +1,7 @@
/tmp
# user-specific project metadata
/.idea/dictionaries/
+/.idea/markdown-navigator*
/.idea/workspace.xml
/.idea/shelf/
# ignore idea.xml output
View
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
+ <component name="EntryPointsManager">
+ <entry_points version="2.0" />
+ </component>
<component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY" />
<option name="OPTION_SCOPE" value="protected" />
@@ -18,7 +21,7 @@
<option name="OPEN_IN_BROWSER" value="true" />
<option name="OPTION_INCLUDE_LIBS" value="false" />
</component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="false" project-jdk-name="IntelliJ IDEA Community Edition IC-141.3056.4" project-jdk-type="IDEA JDK">
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="IntelliJ IDEA Community Edition IC-141.3056.4" project-jdk-type="IDEA JDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -18,6 +18,7 @@
</option>
<envs>
<env name="ELIXIR_LANG_ELIXIR_PATH" value="/Users/limhoff/git/elixir-lang/elixir" />
+ <env name="ELIXIR_EBIN_DIRECTORY" value="/usr/local/Cellar/elixir/1.3.4/lib/elixir/ebin/" />
</envs>
<patterns />
<RunnerSettings RunnerId="Debug">
@@ -0,0 +1,105 @@
+package org.elixir_lang.beam;
+
+import com.ericsson.otp.erlang.OtpErlangDecodeException;
+import gnu.trove.THashMap;
+import org.elixir_lang.beam.chunk.Atoms;
+import org.elixir_lang.beam.chunk.Chunk;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.elixir_lang.beam.chunk.Chunk.TypeID.ATOM;
+import static org.elixir_lang.beam.chunk.Chunk.length;
+import static org.elixir_lang.beam.chunk.Chunk.typeID;
+
+/**
+ * See http://beam-wisdoms.clau.se/en/latest/indepth-beam-file.html
+ */
+public class Beam {
+ /*
+ * CONSTANTS
+ */
+
+ private static final String HEADER = "FOR1";
+
+ /*
+ * Fields
+ */
+
+ private Map<String, Chunk> chunkByTypeID;
+
+ /*
+ * Constructors
+ */
+
+ private Beam(@NotNull Collection<Chunk> chunkCollection) {
+ chunkByTypeID = new THashMap<String, Chunk>(chunkCollection.size());
+
+ for (Chunk chunk : chunkCollection) {
+ chunkByTypeID.put(chunk.typeID, chunk);
+ }
+ }
+
+ /*
+ * Static Methods
+ */
+
+ @Nullable
+ public static Beam from(@NotNull DataInputStream dataInputStream) throws IOException, OtpErlangDecodeException {
+ if (!HEADER.equals(typeID(dataInputStream))) {
+ return null;
+ }
+
+ length(dataInputStream);
+
+ if (!"BEAM".equals(typeID(dataInputStream))) {
+ return null;
+ }
+
+ List<Chunk> chunkList = new ArrayList<Chunk>();
+ while (true) {
+ Chunk chunk = Chunk.from(dataInputStream);
+
+ if (chunk != null) {
+ chunkList.add(chunk);
+ } else {
+ break;
+ }
+ }
+
+ return new Beam(chunkList);
+ }
+
+ /*
+ * Instance Methods
+ */
+
+ @Nullable
+ public Atoms atoms() {
+ Atoms atoms = null;
+
+ Chunk chunk = chunk(ATOM);
+
+ if (chunk != null) {
+ atoms = Atoms.from(chunk);
+ }
+
+ return atoms;
+ }
+
+ @Nullable
+ private Chunk chunk(@NotNull String typeID) {
+ return chunkByTypeID.get(typeID);
+ }
+
+ @Nullable
+ private Chunk chunk(@NotNull Chunk.TypeID typeID) {
+ return chunk(typeID.toString());
+ }
+}
@@ -0,0 +1,73 @@
+package org.elixir_lang.beam.chunk;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.elixir_lang.beam.chunk.Chunk.TypeID.ATOM;
+import static org.elixir_lang.beam.chunk.Chunk.unsignedByte;
+import static org.elixir_lang.beam.chunk.Chunk.unsignedInt;
+
+public class Atoms {
+ /*
+ * Fields
+ */
+
+ @NotNull
+ private List<String> atomList;
+
+ /*
+ * Constructors
+ */
+
+ public Atoms(@NotNull List<String> atomList) {
+ this.atomList = Collections.unmodifiableList(atomList);
+ }
+
+ /*
+ * Static Methods
+ */
+
+ @Nullable
+ public static Atoms from(@NotNull Chunk chunk) {
+ Atoms atoms = null;
+
+ if (chunk.typeID.equals(ATOM.toString()) && chunk.data.length >= 4) {
+ int offset = 0;
+ long atomCount = unsignedInt(chunk.data, offset);
+ List<String> atomList = new ArrayList<String>();
+ offset += 4;
+
+ for (long i = 0; i < atomCount; i++) {
+ int atomLength = unsignedByte(chunk.data[offset]);
+ offset += 1;
+
+ String entry = new String(chunk.data, offset, atomLength);
+ offset += atomLength;
+ atomList.add(entry);
+ }
+
+ atoms = new Atoms(atomList);
+ }
+
+ return atoms;
+ }
+
+ /*
+ * Instance Methods
+ */
+
+ @Nullable
+ public String moduleName() {
+ String moduleName = null;
+
+ if (atomList.size() > 0) {
+ moduleName = atomList.get(0);
+ }
+
+ return moduleName;
+ }
+}
@@ -0,0 +1,105 @@
+package org.elixir_lang.beam.chunk;
+
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * Chunk of a `.beam` file. Same chunk format as base IFF
+ */
+public class Chunk {
+ private static final int ALIGNMENT = 4;
+ @NotNull
+ public final String typeID;
+ @NotNull
+ public final byte[] data;
+ private Chunk(@NotNull String typeID, @NotNull byte[] data) {
+ this.typeID = typeID;
+ this.data = data;
+ }
+
+ @Nullable
+ public static Chunk from(@NotNull DataInputStream dataInputStream) throws IOException {
+ String typeID = typeID(dataInputStream);
+ Chunk chunk = null;
+
+ if (typeID != null) {
+ long length = length(dataInputStream);
+
+ byte[] data = new byte[(int) length];
+ dataInputStream.readFully(data);
+
+ int padding = (int) ((ALIGNMENT - (length % ALIGNMENT)) % ALIGNMENT);
+ dataInputStream.skipBytes(padding);
+
+ chunk = new Chunk(typeID, data);
+ }
+
+ return chunk;
+ }
+
+ public static long length(@NotNull DataInputStream dataInputStream) throws IOException {
+ return readUnsignedInt(dataInputStream);
+ }
+
+ @Nullable
+ public static String typeID(@NotNull DataInputStream dataInputStream) throws IOException {
+ byte[] bytes = new byte[4];
+ int bytesRead = dataInputStream.read(bytes, 0, bytes.length);
+ String typeID = null;
+
+ if (bytesRead == bytes.length) {
+ typeID = new String(bytes);
+ }
+
+ return typeID;
+ }
+
+ private static long readUnsignedInt(@NotNull DataInputStream dataInputStream) throws IOException {
+ byte[] bytes = new byte[32 / 8];
+
+ dataInputStream.readFully(bytes);
+
+ return unsignedInt(bytes);
+ }
+
+ static int unsignedByte(byte signedByte) {
+ return signedByte & 0xFF;
+ }
+
+ static long unsignedInt(@NotNull byte[] bytes) {
+ return unsignedInt(bytes, 0);
+ }
+
+ @Contract(pure = true)
+ static long unsignedInt(@NotNull byte[] bytes, int offset) {
+ assert bytes.length >= offset + 4;
+
+ long unsignedInt = 0;
+
+ for (int i = 0; i < 4; i++) {
+ int unsignedByte = unsignedByte(bytes[offset + i]);
+ unsignedInt += unsignedByte << 8 * (4 - 1 - i);
+ }
+
+ return unsignedInt;
+ }
+
+ public enum TypeID {
+ ATOM("Atom");
+
+ private final String typeID;
+
+ TypeID(@NotNull String typeID) {
+ this.typeID = typeID;
+ }
+
+ @Override
+ public String toString() {
+ return typeID;
+ }
+ }
+}
@@ -0,0 +1,52 @@
+package org.elixir_lang.beam;
+
+import com.ericsson.otp.erlang.OtpErlangDecodeException;
+import org.elixir_lang.beam.chunk.Atoms;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.*;
+
+import static org.junit.Assert.*;
+
+public class BeamTest {
+ @NotNull
+ private String ebinDirectory;
+
+ @Test
+ public void elixirModule() throws IOException, OtpErlangDecodeException {
+ assertModuleName("Elixir.Kernel", "Elixir.Kernel");
+ }
+
+ @Test
+ public void erlangModule() throws IOException, OtpErlangDecodeException {
+ assertModuleName("elixir_interpolation", "elixir_interpolation");
+ }
+
+ private void assertModuleName(@NotNull String baseName, @NotNull String moduleName) throws IOException, OtpErlangDecodeException {
+ DataInputStream dataInputStream = new DataInputStream(
+ new BufferedInputStream(
+ new FileInputStream(ebinDirectory + baseName + ".beam")
+ )
+ );
+ Beam beam = Beam.from(dataInputStream);
+
+ assertNotNull(beam);
+
+ Atoms atoms = beam.atoms();
+
+ assertNotNull(atoms);
+
+ assertEquals(atoms.moduleName(), moduleName);
+ }
+
+ @Before
+ public void setEbinDirectory() {
+ String ebinDirectory = System.getenv("ELIXIR_EBIN_DIRECTORY");
+
+ assertNotNull("ELIXIR_EBIN_DIRECTORY is not set", ebinDirectory);
+
+ this.ebinDirectory = ebinDirectory;
+ }
+}

0 comments on commit 4bb71f9

Please sign in to comment.