diff --git a/core/src/main/java/de/mirkosertic/bytecoder/core/BytecodeLinkedClass.java b/core/src/main/java/de/mirkosertic/bytecoder/core/BytecodeLinkedClass.java index 4696e9f84..ea5e75663 100644 --- a/core/src/main/java/de/mirkosertic/bytecoder/core/BytecodeLinkedClass.java +++ b/core/src/main/java/de/mirkosertic/bytecoder/core/BytecodeLinkedClass.java @@ -51,7 +51,7 @@ public class BytecodeLinkedClass extends Node { private Boolean callback; private Boolean event; private Set implementedIdentifiersCache; - private BytecodeLinkedClass superClass; + private final BytecodeLinkedClass superClass; public BytecodeLinkedClass(final BytecodeLinkedClass aSuperclass, final int aUniqueId, final BytecodeLinkerContext aLinkerContext, final BytecodeObjectTypeRef aClassName, final BytecodeClass aBytecodeClass) { uniqueId = aUniqueId; @@ -278,13 +278,13 @@ public boolean resolveVirtualMethod(final String aMethodName, final BytecodeMeth return true; } - boolean foundByInterface = false; + boolean somethingFound = false; // Try to find default methods and also mark usage // of interface methods for (final BytecodeLinkedClass theImplementedInterface : getImplementingTypes(false, false)) { if (theImplementedInterface.resolveVirtualMethod(aMethodName, aSignature)) { - foundByInterface = true; + somethingFound = true; } } @@ -301,14 +301,14 @@ public boolean resolveVirtualMethod(final String aMethodName, final BytecodeMeth return true; } - if (!foundByInterface) { - final BytecodeLinkedClass theSuperClass = getSuperClass(); - if (theSuperClass != null) { - return theSuperClass.resolveVirtualMethod(aMethodName, aSignature); + final BytecodeLinkedClass theSuperClass = getSuperClass(); + if (theSuperClass != null) { + if (theSuperClass.resolveVirtualMethod(aMethodName, aSignature)) { + return true; } } - return foundByInterface; + return somethingFound; } public boolean resolveConstructorInvocation(final BytecodeMethodSignature aSignature) { @@ -465,7 +465,9 @@ public void resolveInheritedOverriddenMethods() { final BytecodeResolvedMethods theResolvedMethods = theClass.resolvedMethods(); final List theInstanceMethods = theResolvedMethods.stream().filter(t -> !t.getValue().getAccessFlags().isPrivate() && !t.getValue().getAccessFlags().isStatic()).map(BytecodeResolvedMethods.MethodEntry::getValue).collect(Collectors.toList()); for (final BytecodeMethod theMethod : theInstanceMethods) { - BytecodeLinkedClass.this.resolveVirtualMethod(theMethod.getName().stringValue(), theMethod.getSignature()); + if (!BytecodeLinkedClass.this.resolveVirtualMethod(theMethod.getName().stringValue(), theMethod.getSignature())) { + throw new IllegalStateException("Cannot find method " + theMethod.getName() + " with signature " + theMethod.getSignature() + " in class " + theClass.getClassName().name()); + } } } } diff --git a/core/src/main/java/de/mirkosertic/bytecoder/intrinsics/Intrinsics.java b/core/src/main/java/de/mirkosertic/bytecoder/intrinsics/Intrinsics.java index f52b29a98..0f77976b9 100644 --- a/core/src/main/java/de/mirkosertic/bytecoder/intrinsics/Intrinsics.java +++ b/core/src/main/java/de/mirkosertic/bytecoder/intrinsics/Intrinsics.java @@ -15,12 +15,21 @@ */ package de.mirkosertic.bytecoder.intrinsics; -import de.mirkosertic.bytecoder.core.*; -import de.mirkosertic.bytecoder.ssa.*; - import java.util.ArrayList; import java.util.List; +import de.mirkosertic.bytecoder.core.BytecodeInstructionGETSTATIC; +import de.mirkosertic.bytecoder.core.BytecodeInstructionINVOKESPECIAL; +import de.mirkosertic.bytecoder.core.BytecodeInstructionINVOKESTATIC; +import de.mirkosertic.bytecoder.core.BytecodeInstructionINVOKEVIRTUAL; +import de.mirkosertic.bytecoder.core.BytecodeInstructionPUTSTATIC; +import de.mirkosertic.bytecoder.core.BytecodeObjectTypeRef; +import de.mirkosertic.bytecoder.ssa.ParsingHelper; +import de.mirkosertic.bytecoder.ssa.Program; +import de.mirkosertic.bytecoder.ssa.RegionNode; +import de.mirkosertic.bytecoder.ssa.Value; +import de.mirkosertic.bytecoder.ssa.Variable; + public class Intrinsics { private final List intrinsics; diff --git a/core/src/main/java/de/mirkosertic/bytecoder/intrinsics/JavaLangClassIntrinsic.java b/core/src/main/java/de/mirkosertic/bytecoder/intrinsics/JavaLangClassIntrinsic.java index 7e5b21045..285cf2027 100644 --- a/core/src/main/java/de/mirkosertic/bytecoder/intrinsics/JavaLangClassIntrinsic.java +++ b/core/src/main/java/de/mirkosertic/bytecoder/intrinsics/JavaLangClassIntrinsic.java @@ -15,11 +15,27 @@ */ package de.mirkosertic.bytecoder.intrinsics; -import de.mirkosertic.bytecoder.core.*; -import de.mirkosertic.bytecoder.ssa.*; - import java.util.List; +import de.mirkosertic.bytecoder.core.BytecodeInstructionGETSTATIC; +import de.mirkosertic.bytecoder.core.BytecodeInstructionINVOKESPECIAL; +import de.mirkosertic.bytecoder.core.BytecodeInstructionINVOKESTATIC; +import de.mirkosertic.bytecoder.core.BytecodeInstructionINVOKEVIRTUAL; +import de.mirkosertic.bytecoder.core.BytecodeInstructionPUTSTATIC; +import de.mirkosertic.bytecoder.core.BytecodeLinkedClass; +import de.mirkosertic.bytecoder.core.BytecodeMethodSignature; +import de.mirkosertic.bytecoder.core.BytecodeObjectTypeRef; +import de.mirkosertic.bytecoder.ssa.IntegerValue; +import de.mirkosertic.bytecoder.ssa.NewInstanceFromDefaultConstructorExpression; +import de.mirkosertic.bytecoder.ssa.ParsingHelper; +import de.mirkosertic.bytecoder.ssa.Program; +import de.mirkosertic.bytecoder.ssa.RegionNode; +import de.mirkosertic.bytecoder.ssa.StringValue; +import de.mirkosertic.bytecoder.ssa.TypeOfExpression; +import de.mirkosertic.bytecoder.ssa.TypeRef; +import de.mirkosertic.bytecoder.ssa.Value; +import de.mirkosertic.bytecoder.ssa.Variable; + public class JavaLangClassIntrinsic extends Intrinsic { @Override @@ -52,6 +68,55 @@ public boolean intrinsify(final Program aProgram, final BytecodeInstructionINVOK return false; } + @Override + public boolean intrinsify(final Program aProgram, final BytecodeInstructionINVOKESTATIC aInstruction, + final String aMethodName, final List aArguments, final BytecodeObjectTypeRef aTargetClass, + final RegionNode aTargetBlock, final ParsingHelper aHelper) { + final BytecodeMethodSignature theSignature = aInstruction.getMethodReference().getNameAndTypeIndex().getNameAndType().getDescriptorIndex().methodSignature(); + final BytecodeObjectTypeRef theCalledClass = BytecodeObjectTypeRef.fromUtf8Constant(aInstruction.getMethodReference().getClassIndex().getClassConstant().getConstant()); + + if (theCalledClass.name().equals(Class.class.getName())) { + if ("forName".equals(aMethodName)) { + for (int i=0;i theIncomingFlows = theArgumentValue.incomingDataFlows(); + for (final Value theValue : theIncomingFlows) { + if (theValue instanceof StringValue) { + // We found something as a variable + final String theClassName = ((StringValue) theValue).getStringValue(); + if (aProgram.getLinkerContext() != null) { + aProgram.getLinkerContext().getLogger().warn("Class {} is used by reflection!", theClassName); + } + + break checkblock; + } + } + } + + if (aProgram.getLinkerContext() != null) { + aProgram.getLinkerContext().getLogger().warn("Class.forName usage detected with unknown class name"); + } + } + } + } + } + } + + return super.intrinsify(aProgram, aInstruction, aMethodName, aArguments, aTargetClass, aTargetBlock, aHelper); + } + @Override public boolean intrinsify(final Program aProgram, final BytecodeInstructionINVOKEVIRTUAL aInstruction, final String aMethodName, final List aArguments, final Value aTarget, final RegionNode aTargetBlock, final ParsingHelper aHelper) { final BytecodeMethodSignature theSignature = aInstruction.getMethodReference().getNameAndTypeIndex().getNameAndType().getDescriptorIndex().methodSignature(); diff --git a/core/src/main/java/de/mirkosertic/bytecoder/ssa/NaiveProgramGenerator.java b/core/src/main/java/de/mirkosertic/bytecoder/ssa/NaiveProgramGenerator.java index 32e1abf6b..7d79cb42d 100644 --- a/core/src/main/java/de/mirkosertic/bytecoder/ssa/NaiveProgramGenerator.java +++ b/core/src/main/java/de/mirkosertic/bytecoder/ssa/NaiveProgramGenerator.java @@ -175,9 +175,9 @@ public Program generateFrom(final BytecodeClass aOwningClass, final BytecodeMeth BytecodeLocalVariableTableAttributeInfo theDebugInfos = null; if (theCode != null) { theDebugInfos = theCode.attributeByType(BytecodeLocalVariableTableAttributeInfo.class); - theProgram = new Program(debugInformationFor(aOwningClass, theCode)); + theProgram = new Program(debugInformationFor(aOwningClass, theCode), linkerContext); } else { - theProgram = new Program(DebugInformation.empty()); + theProgram = new Program(DebugInformation.empty(), linkerContext); } int theCurrentIndex = 0; @@ -1143,7 +1143,7 @@ theTarget, new NewObjectAndConstructExpression( final BytecodeMethodHandleConstant theMethodRef = theBootstrapMethod.getMethodRef(); final BytecodeMethodRefConstant theBootstrapMethodToInvoke = (BytecodeMethodRefConstant) theMethodRef.getReferenceIndex().getConstant(); - final Program theProgram = new Program(DebugInformation.empty()); + final Program theProgram = new Program(DebugInformation.empty(), linkerContext); final RegionNode theInitNode = theProgram.getControlFlowGraph().createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); // Don't forget to calculate reachability and dominators here diff --git a/core/src/main/java/de/mirkosertic/bytecoder/ssa/Program.java b/core/src/main/java/de/mirkosertic/bytecoder/ssa/Program.java index b3df49be3..0c58ebac1 100644 --- a/core/src/main/java/de/mirkosertic/bytecoder/ssa/Program.java +++ b/core/src/main/java/de/mirkosertic/bytecoder/ssa/Program.java @@ -15,6 +15,7 @@ */ package de.mirkosertic.bytecoder.ssa; +import de.mirkosertic.bytecoder.core.BytecodeLinkerContext; import de.mirkosertic.bytecoder.core.BytecodeProgram; import java.util.ArrayList; @@ -28,13 +29,19 @@ public class Program { private final List arguments; private BytecodeProgram.FlowInformation flowInformation; private long analysisTime; + private final BytecodeLinkerContext linkerContext; - public Program(final DebugInformation aDebugInformation) { + public Program(final DebugInformation aDebugInformation, final BytecodeLinkerContext aLinkerContext) { controlFlowGraph = new ControlFlowGraph(this); variables = new ArrayList<>(); arguments = new ArrayList<>(); debugInformation = aDebugInformation; analysisTime = 0; + linkerContext = aLinkerContext; + } + + public BytecodeLinkerContext getLinkerContext() { + return linkerContext; } public DebugInformation getDebugInformation() { diff --git a/core/src/test/java/de/mirkosertic/bytecoder/classlib/java/util/HashMapTest.java b/core/src/test/java/de/mirkosertic/bytecoder/classlib/java/util/HashMapTest.java index 7f89e0576..9224dea70 100644 --- a/core/src/test/java/de/mirkosertic/bytecoder/classlib/java/util/HashMapTest.java +++ b/core/src/test/java/de/mirkosertic/bytecoder/classlib/java/util/HashMapTest.java @@ -20,6 +20,7 @@ import org.junit.runner.RunWith; import java.util.HashMap; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -31,16 +32,27 @@ public class HashMapTest { @Test public void containsGetPutUpdate() throws Exception { - HashMap theMap = new HashMap<>(); + final HashMap theMap = new HashMap<>(); assertFalse(theMap.containsKey(new Integer(10))); assertNull(theMap.get(new Integer(1024))); - Integer theOldValue = theMap.put(new Integer(255), new Integer(3000)); + final Integer theOldValue = theMap.put(new Integer(255), new Integer(3000)); assertNull(theOldValue); assertTrue(theMap.containsKey(new Integer(255))); assertEquals(new Integer(3000), theMap.get(new Integer(255))); - Integer theOldValue2 = theMap.put(new Integer(255), new Integer(4000)); + final Integer theOldValue2 = theMap.put(new Integer(255), new Integer(4000)); assertEquals(new Integer(3000), theOldValue2); assertEquals(new Integer(4000), theMap.get(new Integer(255))); } + + @Test + public void testKeySetIterator() { + final Map map = new HashMap<>(); + map.put(1, "1"); + map.put(2, "2"); + map.put(3, "3"); + for (final Map.Entry entry : map.entrySet()) { + System.out.println(entry.getKey() + " -> " + entry.getValue()); + } + } } \ No newline at end of file diff --git a/core/src/test/java/de/mirkosertic/bytecoder/graph/GraphDFSOrderTest.java b/core/src/test/java/de/mirkosertic/bytecoder/graph/GraphDFSOrderTest.java index 1e53df8ac..d4cba08c3 100644 --- a/core/src/test/java/de/mirkosertic/bytecoder/graph/GraphDFSOrderTest.java +++ b/core/src/test/java/de/mirkosertic/bytecoder/graph/GraphDFSOrderTest.java @@ -34,7 +34,7 @@ public class GraphDFSOrderTest { @Test public void testSimpleNode() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); graph.calculateReachabilityAndMarkBackEdges(); @@ -52,7 +52,7 @@ public void testSimpleNode() { @Test public void testSimpleFlow() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node2 = graph.createAt(new BytecodeOpcodeAddress(20), RegionNode.BlockType.NORMAL); @@ -76,7 +76,7 @@ public void testSimpleFlow() { @Test public void testJoiningFlow() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node2 = graph.createAt(new BytecodeOpcodeAddress(20), RegionNode.BlockType.NORMAL); @@ -104,7 +104,7 @@ public void testJoiningFlow() { @Test public void testJoiningFlowWithLoop() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node2 = graph.createAt(new BytecodeOpcodeAddress(20), RegionNode.BlockType.NORMAL); diff --git a/core/src/test/java/de/mirkosertic/bytecoder/ssa/ControlFlowGraphSCCTest.java b/core/src/test/java/de/mirkosertic/bytecoder/ssa/ControlFlowGraphSCCTest.java index 6406a5557..d00655f42 100644 --- a/core/src/test/java/de/mirkosertic/bytecoder/ssa/ControlFlowGraphSCCTest.java +++ b/core/src/test/java/de/mirkosertic/bytecoder/ssa/ControlFlowGraphSCCTest.java @@ -29,7 +29,7 @@ public class ControlFlowGraphSCCTest { @Test public void testSimpleNode() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); graph.calculateReachabilityAndMarkBackEdges(); @@ -45,7 +45,7 @@ public void testSimpleNode() { @Test public void testSimpleFlow() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node2 = graph.createAt(new BytecodeOpcodeAddress(20), RegionNode.BlockType.NORMAL); @@ -67,7 +67,7 @@ public void testSimpleFlow() { @Test public void testJoiningFlow() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node2 = graph.createAt(new BytecodeOpcodeAddress(20), RegionNode.BlockType.NORMAL); @@ -95,7 +95,7 @@ public void testJoiningFlow() { @Test public void testLoop1() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node2 = graph.createAt(new BytecodeOpcodeAddress(20), RegionNode.BlockType.NORMAL); @@ -116,7 +116,7 @@ public void testLoop1() { @Test public void testLoop2() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node2 = graph.createAt(new BytecodeOpcodeAddress(20), RegionNode.BlockType.NORMAL); @@ -138,7 +138,7 @@ public void testLoop2() { @Test public void testLoop3Joining() { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph graph = new ControlFlowGraph(p); final RegionNode startNode = graph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node1 = graph.createAt(new BytecodeOpcodeAddress(10), RegionNode.BlockType.NORMAL); diff --git a/core/src/test/java/de/mirkosertic/bytecoder/ssa/DominanceTest.java b/core/src/test/java/de/mirkosertic/bytecoder/ssa/DominanceTest.java index 2f8769441..04b932cc6 100644 --- a/core/src/test/java/de/mirkosertic/bytecoder/ssa/DominanceTest.java +++ b/core/src/test/java/de/mirkosertic/bytecoder/ssa/DominanceTest.java @@ -28,7 +28,7 @@ public class DominanceTest { @Test public void testDirectFlow() { - final Program theProgram = new Program(DebugInformation.empty()); + final Program theProgram = new Program(DebugInformation.empty(), null); final ControlFlowGraph theGraph = new ControlFlowGraph(theProgram); final RegionNode theNode1 = theGraph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); @@ -66,7 +66,7 @@ public void testDirectFlow() { @Test public void testEndlessLoop() { - final Program theProgram = new Program(DebugInformation.empty()); + final Program theProgram = new Program(DebugInformation.empty(), null); final ControlFlowGraph theGraph = new ControlFlowGraph(theProgram); final RegionNode theNode1 = theGraph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); @@ -85,7 +85,7 @@ public void testEndlessLoop() { @Test public void testIFElseWithJoining() { - final Program theProgram = new Program(DebugInformation.empty()); + final Program theProgram = new Program(DebugInformation.empty(), null); final ControlFlowGraph theGraph = new ControlFlowGraph(theProgram); final RegionNode theNode1 = theGraph.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); diff --git a/core/src/test/java/de/mirkosertic/bytecoder/stackifier/StackifierTest.java b/core/src/test/java/de/mirkosertic/bytecoder/stackifier/StackifierTest.java index f347fd31f..e545218ea 100644 --- a/core/src/test/java/de/mirkosertic/bytecoder/stackifier/StackifierTest.java +++ b/core/src/test/java/de/mirkosertic/bytecoder/stackifier/StackifierTest.java @@ -37,7 +37,7 @@ public class StackifierTest { @Test public void testOnlyOneNode() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); g.calculateReachabilityAndMarkBackEdges(); @@ -51,7 +51,7 @@ public void testOnlyOneNode() throws HeadToHeadControlFlowException { @Test public void testSimpleSequence() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); startNode.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(10))); @@ -73,7 +73,7 @@ public void testSimpleSequence() throws HeadToHeadControlFlowException { @Test public void testSimpleSequenceWithoutGotos() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node1 = g.createAt(new BytecodeOpcodeAddress(10), RegionNode.BlockType.NORMAL); @@ -93,7 +93,7 @@ public void testSimpleSequenceWithoutGotos() throws HeadToHeadControlFlowExcepti @Test public void testIfThenElse() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); startNode.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(10))); @@ -133,7 +133,7 @@ public void testIfThenElse() throws HeadToHeadControlFlowException { @Test public void testSimpleLoop() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); startNode.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(10))); @@ -157,7 +157,7 @@ public void testSimpleLoop() throws HeadToHeadControlFlowException { @Test public void testSimpleLoopWithSuccessor1() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); startNode.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(20))); @@ -189,7 +189,7 @@ public void testSimpleLoopWithSuccessor1() throws HeadToHeadControlFlowException @Test public void testSimpleLoopWithSuccessor2() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); startNode.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(0))); @@ -221,7 +221,7 @@ public void testSimpleLoopWithSuccessor2() throws HeadToHeadControlFlowException @Test public void testSimpleLoopDoubleExit() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); startNode.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(20))); @@ -255,7 +255,7 @@ public void testSimpleLoopDoubleExit() throws HeadToHeadControlFlowException { @Test public void testSimpleLoopDoubleContinue() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); startNode.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(0))); @@ -289,7 +289,7 @@ public void testSimpleLoopDoubleContinue() throws HeadToHeadControlFlowException @Test public void testMoreComplexExample() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); @@ -416,7 +416,7 @@ public void testMoreComplexExample() throws HeadToHeadControlFlowException { @Test public void testComplexExampleWithLoopAsLastElement() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); @@ -490,7 +490,7 @@ public void testComplexExampleWithLoopAsLastElement() throws HeadToHeadControlFl @Test public void testAnotherComplexExample() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode node0 = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); @@ -533,7 +533,7 @@ public void testAnotherComplexExample() throws HeadToHeadControlFlowException { @Test public void testCondition() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode startNode = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); startNode.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(10))); @@ -575,7 +575,7 @@ public void testCondition() throws HeadToHeadControlFlowException { @Test public void testCompleteInitLoopBoundaries() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode node0 = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node1 = g.createAt(new BytecodeOpcodeAddress(13), RegionNode.BlockType.NORMAL); @@ -646,7 +646,7 @@ public void testCompleteInitLoopBoundaries() throws HeadToHeadControlFlowExcepti @Test public void testIfShoulNotExitLoop() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode node0 = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); node0.getExpressions().add(new GotoExpression(p, new BytecodeOpcodeAddress(0), new BytecodeOpcodeAddress(79))); @@ -733,7 +733,7 @@ public void testIfShoulNotExitLoop() throws HeadToHeadControlFlowException { @Test public void testInlinedGoto() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode node0 = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node90 = g.createAt(new BytecodeOpcodeAddress(90), RegionNode.BlockType.NORMAL); @@ -779,7 +779,7 @@ public void testInlinedGoto() throws HeadToHeadControlFlowException { @Test public void testOverlapping() throws HeadToHeadControlFlowException { - final Program p = new Program(DebugInformation.empty()); + final Program p = new Program(DebugInformation.empty(), null); final ControlFlowGraph g = new ControlFlowGraph(p); final RegionNode node0 = g.createAt(BytecodeOpcodeAddress.START_AT_ZERO, RegionNode.BlockType.NORMAL); final RegionNode node1 = g.createAt(new BytecodeOpcodeAddress(87), RegionNode.BlockType.NORMAL); diff --git a/manual/content/chapter-1/page-1-b.md b/manual/content/chapter-1/page-1-b.md index bdac4cae3..57fb1a2d2 100644 --- a/manual/content/chapter-1/page-1-b.md +++ b/manual/content/chapter-1/page-1-b.md @@ -5,12 +5,42 @@ draft: false weight: 2 --- -## Usage with Maven Plugin +## Maven Plugin usage Bytecoder comes with a handy Maven plugin. This plugins supports the JavaScript and WebAssembly backends and can compile JVM bytecode as part of the Maven project lifecycle without any third party or command-line tools. +### Configuration options + +The following configuration options are available: + +* `buildDirectory': The build target directory. Defaults to `${project.build.outputDirectory}` + +* `mainClass` The Classname with the main class to be compiled. Required. + +* `backend`: The Backend to be used. Can be `js` or `wasm`. Defaults to `js`. + +* `debugOutput`: Shall debug output be generated? Defaults to `false`. + +* `enableExceptionHandling`: Shall Exception-Handling be activated? Defaults to `false`. + +* `optimizationLevel`: Which kind of optimization should be applied? Can be `NONE`, `ALL` or `EXPERIMENTAL`. Defaults to `ALL`. + +* `filenamePrefix`: Prefix of the generated files. Defaults to `bytecoder`. + +* `wasmInitialPages`: Minimum number of pages for WASM memory. Defaults to `512`. + +* `wasmMaximumPages`: Maximum number of pages for WASM memory. Defaults to `1024`. + +* `minifyCompileResult`: Shall the compile result be minified? Defaults to `true`. + +* `preferStackifier`: Shall the Stackifier be used and the Relooper as fallback? Defaults to `false`. + +* `registerAllocator`: Which register allocator should be used? Can be `linear` or `passthru`? Defaults to `linear`. + +* `additionalClassesToLink`: List of full qualified class names to be linked beside the statically referenced ones to make them available by reflection API. Optional + ### Compiling to JavaScript ``` diff --git a/manual/content/chapter-1/page-1-d.md b/manual/content/chapter-1/page-1-d.md index a248ed3d7..433380cd2 100644 --- a/manual/content/chapter-1/page-1-d.md +++ b/manual/content/chapter-1/page-1-d.md @@ -8,9 +8,9 @@ weight: 4 ## Overview Bytecoder is an **AOT (Ahead-of-time) compiler**. As this, it has to determine the set -of classes at compile time. It does this by running statical dependency analysis which +of classes at compile time. It does this by running a statical dependency analysis which starts at a class implementing a `public static void main(String[] args)` method and -building a dependency tree from there resulting in the final set of classes and methods +builds a dependency tree from there resulting in the final set of classes and methods that must be included to make the program valid. However, things start to get tricky once we use the Java Reflection API. @@ -19,10 +19,14 @@ The most famous part of the Reflection API is the `Class.forName` method and its Here, the class to be resolved is defined as runtime. This is a problem for an AOT compiler. Bytecoder tries so solve this problem using a set of heuristics and configuration. -Classes that very likely to be resolved by reflection are automatically included by the compiler. +Classes that are very likely to be resolved by reflection are automatically included by the compiler. Some promiment examples are implementations of `java.nio.charset.Charset` or `java.lang.CharacterData`. There is also a compiler option available by the CLI or Maven Plugin to add additional classes. +{{% notice note %}} +Only zero-arg constructors are supported yet. +{{% /notice %}} + ## Support for the Java Reflection API The following APIs are supported by Bytecoder: @@ -33,6 +37,6 @@ Object instance = runtimeClass.newInstance(); // Method 1 to instantiate a class cl.getConstructor(new Class[0]).newInstance(); // Method 2 to instantiate a class ``` -{{% notice note %}} -Only zero-arg constructors are supported yet. +{{% notice warning %}} +The ServiceLocator API is currently not supported! {{% /notice %}} diff --git a/manual/content/chapter-1/page-1-g.md b/manual/content/chapter-1/page-1-g.md index 9edfccc52..7f14e19e6 100644 --- a/manual/content/chapter-1/page-1-g.md +++ b/manual/content/chapter-1/page-1-g.md @@ -12,13 +12,13 @@ Sometimes the methods or properties you want aren't there, but it's very simple ``` public abstract class CustomCanvas extends de.mirkosertic.bytecoder.api.web.HTMLCanvasElement { - // The following two methods are setters and getters for the canvas.width property. + // The following two methods are setters and getters for the canvas.width property. - @de.mirkosertic.bytecoder.api.OpaqueProperty - public abstract void width(float value); - - @de.mirkosertic.bytecoder.api.OpaqueProperty - public abstract float width(); + @de.mirkosertic.bytecoder.api.OpaqueProperty + public abstract void width(float value); + + @de.mirkosertic.bytecoder.api.OpaqueProperty + public abstract float width(); } ``` @@ -40,21 +40,21 @@ Explanation of JavaScript data types in this page: ``` public abstract class Navigator implements de.mirkosertic.bytecoder.api.OpaqueReferenceType { - public static native Navigator navigator(); - - @de.mirkosertic.bytecoder.api.OpaqueProperty - public abstract String userAgent(); - - @de.mirkosertic.bytecoder.api.OpaqueProperty - public abstract boolean cookieEnabled(); - - // If you want to have a different name in the Java code, you can - // name the method for example beaconSend and annotate it with - // @OpaqueMethod("sendBeacon") - // This specific method has more types of possible arguments, - // if you need to support those you just need to add more methods - // but with the other `data` types - public abstract void sendBeacon(String url, String data); + public static native Navigator navigator(); + + @de.mirkosertic.bytecoder.api.OpaqueProperty + public abstract String userAgent(); + + @de.mirkosertic.bytecoder.api.OpaqueProperty + public abstract boolean cookieEnabled(); + + // If you want to have a different name in the Java code, you can + // name the method for example beaconSend and annotate it with + // @OpaqueMethod("sendBeacon") + // This specific method has more types of possible arguments, + // if you need to support those you just need to add more methods + // but with the other `data` types + public abstract void sendBeacon(String url, String data); } ``` @@ -69,7 +69,7 @@ bytecoder.imports.navigator = bytecoder.imports.navigator || {}; // This method has no arguments so it's simply called navigator bytecoder.imports.navigator.navigator = function (thisref) { - return bytecoder.toBytecoderReference(navigator); + return bytecoder.toBytecoderReference(navigator); }; ``` @@ -82,14 +82,14 @@ System.out.println(Navigator.navigator().userAgent()); ``` public abstract class ArrayBuffer implements OpaqueReferenceType { - // The @Import annotation is completely optional and - // it removes the need for having to include the - // parameter types in the method name on the JavaScript side. - @de.mirkosertic.bytecoder.api.Import(module = "arraybuffer", name = "create") - public static native create(int size); - - @de.mirkosertic.bytecoder.api.OpaqueProperty - public int byteLength(); + // The @Import annotation is completely optional and + // it removes the need for having to include the + // parameter types in the method name on the JavaScript side. + @de.mirkosertic.bytecoder.api.Import(module = "arraybuffer", name = "create") + public static native create(int size); + + @de.mirkosertic.bytecoder.api.OpaqueProperty + public int byteLength(); } ``` @@ -108,7 +108,7 @@ bytecoder.imports.arraybuffer = bytecoder.imports.arraybuffer || {}; // Warning: This method uses the @Import annotation which is why // it is called `create` and not `createINT`. bytecoder.imports.arraybuffer.create = function (thisref, size) { - return bytecoder.toBytecoderReference(new ArrayBuffer(size)); + return bytecoder.toBytecoderReference(new ArrayBuffer(size)); }; ``` @@ -120,9 +120,9 @@ System.out.println(ArrayBuffer.create(6).byteLength()); // 6 ``` public abstract class DataView implements de.mirkosertic.bytecoder.api.OpaqueReferenceType { - public static native create(ArrayBuffer arrayBuffer); - - // ... opaque methods and properties ... + public static native create(ArrayBuffer arrayBuffer); + + // ... opaque methods and properties ... } ``` @@ -131,18 +131,28 @@ bytecoder.imports.dataview = bytecoder.imports.dataview || {}; // create - method name, ArrayBuffer - parameter type bytecoder.imports.dataview.createArrayBuffer = function (thisref, arraybufferref) { - return bytecoder.toBytecoderReference(new DataView(bytecoder.toJSReference(arraybufferref))); + return bytecoder.toBytecoderReference(new DataView(bytecoder.toJSReference(arraybufferref))); }; ``` -## Imports +## Import and Export semantics + +* Imports are methods imported from JavaScript/Host side and called from Java. +* Exports are methods exported from Java and called from JavaScript/Host side. -Imports are methods imported from JavaScript and called from Java. +## Emulating classes and methods -## Exports +Bytecoder is based on the OpenJDK JRE classlib. However, it is sometimes neccesary to +patch existing classes to make them compatible with Bytecoder. -Exports are methods exported from Java and called from JavaScript. +Bytecoder introduces a concept called shadow types for this purpose. -## Emulating classes and methods +Take a look at the `java.lang.System` class. It needs some adaptation +for make it compatible with Bytecoder. Now, the shadow type called +`de.mirkosertic.bytecoder.classlib.java.lang.TSystem` is introduced. +Shadow types need the package prefix `de.mirkosertic.bytecoder.classlib` +and the `@de.mirkosertic.bytecoder.api.SubstitutesInClass` annotation. -This is done by using the @de.mirkosertic.bytecoder.api.SubstitutesInClass annotation. +`@SubstitutesInClass` toggles what should be adapted by the shadow +type. It can either override the whole class by setting `completeReplace=true` +or only specified methods by setting `completeReplace=false`. diff --git a/maven/src/main/java/de/mirkosertic/bytecoder/maven/BytecoderMavenMojo.java b/maven/src/main/java/de/mirkosertic/bytecoder/maven/BytecoderMavenMojo.java index e176d9f63..9902b14db 100644 --- a/maven/src/main/java/de/mirkosertic/bytecoder/maven/BytecoderMavenMojo.java +++ b/maven/src/main/java/de/mirkosertic/bytecoder/maven/BytecoderMavenMojo.java @@ -72,7 +72,7 @@ public class BytecoderMavenMojo extends AbstractMojo { * The build target directory. */ @Parameter(defaultValue = "${project.build.directory}") - protected String buldDirectory; + protected String buildDirectory; /** * Shall debug output be generated? @@ -136,7 +136,7 @@ public class BytecoderMavenMojo extends AbstractMojo { @Override public void execute() throws MojoExecutionException { - final File theBaseDirectory = new File(buldDirectory); + final File theBaseDirectory = new File(buildDirectory); final File theBytecoderDirectory = new File(theBaseDirectory, "bytecoder"); theBytecoderDirectory.mkdirs();