/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.flex.abc; import org.apache.flex.abc.diagnostics.AbstractDiagnosticVisitor; import org.apache.flex.abc.graph.IFlowgraph; import org.apache.flex.abc.graph.IBasicBlock; import org.apache.flex.abc.instructionlist.InstructionList; import org.apache.flex.abc.optimize.DeadCodeFilter; import org.apache.flex.abc.optimize.PeepholeOptimizerMethodBodyVisitor; import org.apache.flex.abc.semantics.ClassInfo; import org.apache.flex.abc.semantics.InstanceInfo; import org.apache.flex.abc.semantics.Instruction; import org.apache.flex.abc.semantics.Label; import org.apache.flex.abc.semantics.Metadata; import org.apache.flex.abc.semantics.MethodBodyInfo; import org.apache.flex.abc.semantics.MethodInfo; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.Namespace; import org.apache.flex.abc.semantics.Nsset; import org.apache.flex.abc.visitors.DelegatingClassVisitor; import org.apache.flex.abc.visitors.DelegatingMetadataVisitor; import org.apache.flex.abc.visitors.DelegatingMethodBodyVisitor; import org.apache.flex.abc.visitors.DelegatingMethodVisitor; import org.apache.flex.abc.visitors.DelegatingScriptVisitor; import org.apache.flex.abc.visitors.DelegatingTraitVisitor; import org.apache.flex.abc.visitors.DelegatingTraitsVisitor; import org.apache.flex.abc.visitors.IABCVisitor; import org.apache.flex.abc.visitors.IClassVisitor; import org.apache.flex.abc.visitors.IDiagnosticsVisitor; import org.apache.flex.abc.visitors.IMetadataVisitor; import org.apache.flex.abc.visitors.IMethodBodyVisitor; import org.apache.flex.abc.visitors.IMethodVisitor; import org.apache.flex.abc.visitors.IScriptVisitor; import org.apache.flex.abc.visitors.ITraitVisitor; import org.apache.flex.abc.visitors.ITraitsVisitor; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.UnreachableBlockProblem; import java.io.File; import java.util.Collection; /** * The ABCLinker links a sequence of ABC blocks into a single ABC block, * and contains utility methods to perform various transformations on the ABC. */ public class ABCLinker { public static byte[] linkABC(Iterable<byte[]> inputABCs, int majorVersion, int minorVersion, ABCLinkerSettings settings) throws Exception { ABCEmitter emitter = new ABCEmitter(); // ABCs from 4.5 may have non-sensical jumps past the end of a method // so allow those, instead of throwin java exceptions emitter.setAllowBadJumps(true); emitter.visit(majorVersion, minorVersion); for (byte[] inputABC : inputABCs) { ABCParser abcParser = new ABCParser(inputABC); abcParser.parseABC(new LinkingVisitor(emitter, settings)); } emitter.visitEnd(); return emitter.emit(); } public static class ABCLinkerSettings { private boolean optimize = false; private boolean enableInlining = false; private boolean stripDebug = false; private boolean stripFileAttributeFromGotoDefinitionHelp = false; private boolean stripGotoDefinitionHelp = false; private boolean removeDeadCode = false; private Collection<String> meta_names = null; @SuppressWarnings("unused") private int minorVersion = ABCConstants.VERSION_ABC_MINOR_FP10; @SuppressWarnings("unused") private int majorVersion = ABCConstants.VERSION_ABC_MAJOR_FP10; private Collection<ICompilerProblem> problems; /** * Tell the linker whether it should run the peephole optimizer defaults * to false * * @param b true if the ABCs should be optimized */ public void setOptimize(boolean b) { optimize = b; } /** * Tell the linker whether it should enable inlining of functions. * defaults to false * @param b true if the functions should be inlined */ public void setEnableInlining(boolean b) { enableInlining = b; } /** * Tell the linker whether is should strip out debug opcodes defaults to * false * * @param b true if the ABCs should have the debug opcodes stripped out */ public void setStripDebugOpcodes(boolean b) { stripDebug = b; } /** * Tell the linker which metadata names it should keep. If null, it will * keep them all except for "go to definition help" which is control by * the stripGotoDefinitionHelp flag. Defaults to null. * * @param metadata_names A collection of metadata names that the linker * will keep. */ public void setKeepMetadata(Collection<String> metadata_names) { meta_names = metadata_names; } /** * @return true if we should be stripping some metadata */ boolean shouldStripMetadata() { return meta_names != null || stripGotoDefinitionHelp || stripFileAttributeFromGotoDefinitionHelp; } public void setTargetABCVersion(int major, int minor) { this.majorVersion = major; this.minorVersion = minor; } /** * If the metadata to keep is null, this flag determines if * "go to definition help" metadata is removed. * * @param stripGotoDefinitionHelp <code>true</code> to strip the metadata, * <code>false</code> to leave it. */ public void setStripGotoDefinitionHelp(boolean stripGotoDefinitionHelp) { this.stripGotoDefinitionHelp = stripGotoDefinitionHelp; } /** * If "go to definition help" meta is being kept, this flag determines * if we should strip the file attribute from the metadata. * * @param stripFileAttributeFromGotoDefinitionHelp <code>true</code> to strip * the file attribute, <code>false</code> to leave it. */ public void setStripFileAttributeFromGotoDefinitionHelp(boolean stripFileAttributeFromGotoDefinitionHelp) { this.stripFileAttributeFromGotoDefinitionHelp = stripFileAttributeFromGotoDefinitionHelp; } /** * Enable or disable the DeadCodeFilter optimization step. * @param removeDeadCode true if the DeadCodeFilter should be run. */ public void setRemoveDeadCode(final boolean removeDeadCode) { this.removeDeadCode = removeDeadCode; } /** * Set a problems collection for errors or warnings during link. * @param problems the problems collection to receive errors or warnings. */ public void setProblemsCollection(Collection<ICompilerProblem> problems) { this.problems = problems; } } /** * IMethodBodyVisitor implementation that will strip out Debug opcodes */ private static final class DebugStrippingMethodBodyVisitor extends DelegatingMethodBodyVisitor { public DebugStrippingMethodBodyVisitor(IMethodBodyVisitor delegate) { super(delegate); } // flag to keep track of if the last instruction was one we stripped out // needed for figuring out where some labels land - see labelCurrent() private boolean strippedLastInstruction; /** * Determine if the opcode should be stripped out, or passed along to * the next visitor. This also flags whether the opcode was stripped or * not, so if you're calling this make sure you do what it tells you to * * @param opcode The instruction to check * @return true if the instruction should be stripped out. */ private boolean stripInstruction(int opcode) { switch (opcode) { case ABCConstants.OP_debug: case ABCConstants.OP_debugfile: case ABCConstants.OP_debugline: strippedLastInstruction = true; break; default: strippedLastInstruction = false; break; } return strippedLastInstruction; } @Override public void visitInstructionList(InstructionList new_list) { // for the delegates that run after this IVisitor, they will be processing // a new copy of the instruction list, which will not have the debug opcodes InstructionList strippedInstructionList = new InstructionList(new_list.size()); for (Instruction inst : new_list.getInstructions()) { if (!stripInstruction(inst.getOpcode())) strippedInstructionList.addInstruction(inst); } super.visitInstructionList(strippedInstructionList); } @Override public void visitInstruction(int opcode) { if (stripInstruction(opcode)) return; super.visitInstruction(opcode); } @Override public void visitInstruction(int opcode, int immediate_operand) { if (stripInstruction(opcode)) return; super.visitInstruction(opcode, immediate_operand); } @Override public void visitInstruction(int opcode, Object[] operands) { if (stripInstruction(opcode)) return; super.visitInstruction(opcode, operands); } @Override public void visitInstruction(int opcode, Object single_operand) { if (stripInstruction(opcode)) return; super.visitInstruction(opcode, single_operand); } @Override public void visitInstruction(Instruction instruction) { if (stripInstruction(instruction.getOpcode())) return; super.visitInstruction(instruction); } @Override public void labelCurrent(Label l) { // if we're trying to label a debug instruction that got stripped out // then we really want to label the next instruction, and not whatever the // last instruction happens to be. // We only do this if the label may target a non-executable instruction - this // is because we are stripping non executable instructions here - if the label must // target an executable instruction, then the behavior of labelCurrent is correct - // it would have walked back over the instructions until it found the first non-executable // instruction. if (strippedLastInstruction && !l.targetMustBeExecutable()) super.labelNext(l); else super.labelCurrent(l); } } /** * IMethodVisitor that will create a DebugStrippingMethodBodyVisitor - used * when you want to remove debug opcodes from the method body. */ private static class DebugStrippingMethodVisitor extends DelegatingMethodVisitor { public DebugStrippingMethodVisitor(IMethodVisitor delegate) { super(delegate); } @Override public IMethodBodyVisitor visitBody(MethodBodyInfo mbi) { return new DebugStrippingMethodBodyVisitor(super.visitBody(mbi)); } } /** * IMethodVisitor that will create an PeepholeOptimizerMethodBodyVisitor for * the method body - used when you want to run the peephole optimizer for * the method body */ private static class OptimizingMethodVisitor extends DelegatingMethodVisitor { public OptimizingMethodVisitor(IMethodVisitor delegate, Collection<ICompilerProblem> problems, final boolean removeDeadCode) { super(delegate); this.problems = problems; this.removeDeadCode = removeDeadCode; } /** * Sink for problems generated during this phase. */ final Collection<ICompilerProblem> problems; /** * When true, run a DeadCodeFilter as part of the optimization pipeline. */ final boolean removeDeadCode; @Override public IMethodBodyVisitor visitBody(MethodBodyInfo mbi) { // Set up the optimizer pipeline. IMethodBodyVisitor delegate = super.visitBody(mbi); if ( removeDeadCode ) { IDiagnosticsVisitor diagnostics = new AbstractDiagnosticVisitor() { @Override public void unreachableBlock(MethodBodyInfo methodBodyInfo, IFlowgraph cfg, IBasicBlock block) { if ( problems != null ) { String fileName = cfg.findSourcePath(block); if ( fileName != null && new File(fileName).isFile() ) problems.add(new UnreachableBlockProblem(fileName, cfg.findLineNumber(block))); } } }; delegate = new DeadCodeFilter(mbi, delegate, diagnostics); } return new PeepholeOptimizerMethodBodyVisitor(delegate); } } /** * IScriptVisitor that will create a MetadataStrippingTraitsVisitor for the * script traits */ private static class MetadataStrippingScriptVisitor extends DelegatingScriptVisitor { MetadataStrippingScriptVisitor(IScriptVisitor d, Collection<String> meta_names, boolean stripGotoDefinitionHelp, boolean stripFileAttribute) { super(d); this.meta_names = meta_names; this.stripGotoDefinitionHelp = stripGotoDefinitionHelp; this.stripFileAttribute = stripFileAttribute; } private Collection<String> meta_names; private boolean stripGotoDefinitionHelp; private boolean stripFileAttribute; @Override public ITraitsVisitor visitTraits() { return new MetadataStrippingTraitsVisitor(super.visitTraits(), meta_names, stripGotoDefinitionHelp, stripFileAttribute); } } /** * IClassVisitor that will create a MetadataStrippingTraitsVisitor for the * class and instance traits. */ private static class MetadataStrippingClassVisitor extends DelegatingClassVisitor { MetadataStrippingClassVisitor(IClassVisitor d, Collection<String> meta_names, boolean stripGotoDefinitionHelp, boolean stripFileAttribute) { super(d); this.meta_names = meta_names; this.stripGotoDefinitionHelp = stripGotoDefinitionHelp; this.stripFileAttribute = stripFileAttribute; } private Collection<String> meta_names; private boolean stripGotoDefinitionHelp; private boolean stripFileAttribute; @Override public ITraitsVisitor visitClassTraits() { return new MetadataStrippingTraitsVisitor(super.visitClassTraits(), meta_names, stripGotoDefinitionHelp, stripFileAttribute); } @Override public ITraitsVisitor visitInstanceTraits() { return new MetadataStrippingTraitsVisitor(super.visitInstanceTraits(), meta_names, stripGotoDefinitionHelp, stripFileAttribute); } } /** * ITraitVisitor that will create a metadata visitor that will strip out some * metadata. */ private static class MetadataStrippingTraitVisitor extends DelegatingTraitVisitor { MetadataStrippingTraitVisitor(ITraitVisitor d, Collection<String> meta_names, boolean stripGotoDefinitionHelp, boolean stripFileAttribute) { super(d); this.meta_names = meta_names; this.stripGotoDefinitionHelp = stripGotoDefinitionHelp; this.stripFileAttribute = stripFileAttribute; } private Collection<String> meta_names; private boolean stripGotoDefinitionHelp; private boolean stripFileAttribute; @Override public IMetadataVisitor visitMetadata(int count) { return new MetadataStrippingVisitor(super.visitMetadata(count), meta_names, stripGotoDefinitionHelp, stripFileAttribute); } } /** * IMetadataVisitor that will only emit certain metadata, and will strip out * the rest of the metadata. If no metadata is supposed to be stripped, then * you shouldn't create one of these */ private static class MetadataStrippingVisitor extends DelegatingMetadataVisitor { /** * Constants for removing the __go_to_definition_help and __go_to_definition_ctor * help that get injected for go to definition support */ private static final String GO_TO_DEFINITION_HELP = "__go_to_definition_help"; private static final String GO_TO_DEFINITION_CTOR_HELP = "__go_to_ctor_definition_help"; private static final String GO_TO_DEFINITION_HELP_FILE = "file"; /** * If the meta tag is "__go_to_ctor_definition_help" or * "__go_to_definition_help", strip off the "file" attribute. * * @param metadata The data to strip the "file" attribute from. */ static Metadata stripFileAttributeFromGotoDefinitionHelp(Metadata metadata) { String name = metadata.getName(); if (GO_TO_DEFINITION_HELP.equals(name) || GO_TO_DEFINITION_CTOR_HELP.equals(name)) { metadata = removeKey(metadata, GO_TO_DEFINITION_HELP_FILE); } return metadata; } /** * Remove a key from this metadata. * * @param key The key to remove. May not be null. */ static Metadata removeKey(Metadata metadata, String key) { assert metadata != null; assert key != null; String[] keys = metadata.getKeys(); for (int i = 0; i < keys.length; i++) { if (key.equals(keys[i])) { String[] values = metadata.getValues(); String[] newKeys = new String[keys.length - 1]; String[] newValues = new String[keys.length - 1]; // Copy attributes up to found attribute. if (i > 0) { System.arraycopy(keys, 0, newKeys, 0, i); System.arraycopy(values, 0, newValues, 0, i); } // Copy attributes after the found attribute. if (i < (keys.length - 1)) { System.arraycopy(keys, i + 1, newKeys, i, keys.length - i - 1); System.arraycopy(values, i + 1, newValues, i, keys.length - i - 1); } metadata = new Metadata(metadata.getName(), newKeys, newValues); break; } } return metadata; } /** * Constructor * * @param d the delegate visitor to wrap * @param meta_names the names of the metadata which should be emitted. * If null, all the names are emitted except for "go to definition help" * metadata which is controlled by the stripGotoDefinitionHelp parameter * and stripFileAttribute parameter. * @param stripGotoDefinitionHelp true if "go to definition help" * metadata should be stripped out, false otherwise. * @param stripFileAttribute true if the "file" attribute of the goto * definition metadata should be stripped. */ MetadataStrippingVisitor(IMetadataVisitor d, Collection<String> meta_names, boolean stripGotoDefinitionHelp, boolean stripFileAttribute) { super(d); this.metaNames = meta_names; this.stripGotoDefinitionHelp = stripGotoDefinitionHelp; this.stripFileAttribute = stripFileAttribute; } private Collection<String> metaNames; private boolean stripGotoDefinitionHelp; private boolean stripFileAttribute; @Override public void visit(Metadata md) { if (shouldKeep(md)) { if (!stripGotoDefinitionHelp && stripFileAttribute) md = stripFileAttributeFromGotoDefinitionHelp(md); super.visit(md); } return; } boolean shouldKeep(Metadata md) { if (metaNames == null) { if (GO_TO_DEFINITION_HELP.equals(md.getName()) || GO_TO_DEFINITION_CTOR_HELP.equals(md.getName())) { return !stripGotoDefinitionHelp; } return true; } return metaNames.contains(md.getName()); } } /** * ITraitsVisitor that will wrap any generated ITraitVisitor in a * MetadataStrippingTraitVisitor */ private static class MetadataStrippingTraitsVisitor extends DelegatingTraitsVisitor { MetadataStrippingTraitsVisitor(ITraitsVisitor d, Collection<String> meta_names, boolean stripGotoDefinitionHelp, boolean stripFileAttribute) { super(d); this.meta_names = meta_names; this.stripGotoDefinitionHelp = stripGotoDefinitionHelp; this.stripFileAttribute = stripFileAttribute; } private Collection<String> meta_names; private boolean stripGotoDefinitionHelp; private boolean stripFileAttribute; @Override public ITraitVisitor visitSlotTrait(int kind, Name name, int slot_id, Name slot_type, Object slot_value) { return new MetadataStrippingTraitVisitor( super.visitSlotTrait(kind, name, slot_id, slot_type, slot_value), meta_names, stripGotoDefinitionHelp, stripFileAttribute); } @Override public ITraitVisitor visitClassTrait(int kind, Name name, int slot_id, ClassInfo clazz) { return new MetadataStrippingTraitVisitor( super.visitClassTrait(kind, name, slot_id, clazz), meta_names, stripGotoDefinitionHelp, stripFileAttribute); } @Override public ITraitVisitor visitMethodTrait(int kind, Name name, int disp_id, MethodInfo method) { return new MetadataStrippingTraitVisitor( super.visitMethodTrait(kind, name, disp_id, method), meta_names, stripGotoDefinitionHelp, stripFileAttribute); } } /** * IABCVisitor implementation to do various transformations on an ABC file - * stripping debug opcodes, optimizing, etc. */ private static class LinkingVisitor implements IABCVisitor { public LinkingVisitor(ABCEmitter delegate, ABCLinkerSettings linkSettings) { this.delegate = delegate; this.settings = linkSettings; } private final ABCEmitter delegate; private final ABCLinkerSettings settings; @Override public void visit(int major_version, int minor_version) { // Don't call into the delegate, the linker has done that! } @Override public void visitEnd() { // Do nothing... This will be called once by the linker on the // delegate. } @Override public IScriptVisitor visitScript() { IScriptVisitor sv = delegate.visitScript(); if (settings.shouldStripMetadata()) sv = new MetadataStrippingScriptVisitor(sv, settings.meta_names, settings.stripGotoDefinitionHelp, settings.stripFileAttributeFromGotoDefinitionHelp); return sv; } @Override public IClassVisitor visitClass(InstanceInfo iinfo, ClassInfo cinfo) { IClassVisitor cv = delegate.visitClass(iinfo, cinfo); if (settings.shouldStripMetadata()) cv = new MetadataStrippingClassVisitor(cv, settings.meta_names, settings.stripGotoDefinitionHelp, settings.stripFileAttributeFromGotoDefinitionHelp); return cv; } @Override public IMethodVisitor visitMethod(MethodInfo minfo) { IMethodVisitor mv = delegate.visitMethod(minfo); if (settings.optimize) mv = new OptimizingMethodVisitor(mv, settings.problems, settings.removeDeadCode); // Run the debug stripping visitor first, so the debug // instructions won't confuse the peephole optimizer // building a chain of visitors, so the debug stripping will // wrap the optimizing visitor if (settings.stripDebug) mv = new DebugStrippingMethodVisitor(mv); return mv; } @Override public void visitPooledInt(Integer i) { // emitter automatically pools values. } @Override public void visitPooledUInt(Long l) { // emitter automatically pools values. } @Override public void visitPooledDouble(Double d) { // emitter automatically pools values. } @Override public void visitPooledString(String s) { // emitter automatically pools values. } @Override public void visitPooledNamespace(Namespace ns) { if (settings.enableInlining) ns.setMergePrivateNamespaces(true); } @Override public void visitPooledNsSet(Nsset nss) { // emitter automatically pools values. } @Override public void visitPooledName(Name n) { // emitter automatically pools values. } @Override public void visitPooledMetadata(Metadata md) { // emitter automatically pools values. } } }