blob: 69dbe47e2e328c14925e3b9b1f5053e3cf34feaf [file] [log] [blame]
/*
*
* 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.
}
}
}