-
-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Parse module name out of .beam files
- Loading branch information
1 parent
627b8ad
commit 4bb71f9
Showing
7 changed files
with
341 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |