/* * Copyright (C) 2010-2016 JPEXS, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash.abc.avm2.parser.script; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; import com.jpexs.decompiler.flash.abc.avm2.model.ApplyTypeAVM2Item; import com.jpexs.decompiler.flash.abc.types.ClassInfo; import com.jpexs.decompiler.flash.abc.types.InstanceInfo; import com.jpexs.decompiler.flash.abc.types.Multiname; import com.jpexs.decompiler.flash.abc.types.Namespace; import com.jpexs.decompiler.flash.abc.types.ValueKind; import com.jpexs.decompiler.flash.abc.types.traits.Trait; import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; import com.jpexs.decompiler.flash.abc.types.traits.Traits; import com.jpexs.decompiler.flash.tags.ABCContainerTag; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.decompiler.graph.TypeItem; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * Indexing of ABCs for faster access. Indexes ABC classes for faster class and * property resolving * * @author JPEXS */ public final class AbcIndexing { private AbcIndexing parent = null; private final List<ABC> abcs = new ArrayList<>(); private ABC selectedAbc = null; /** * Creates empty index with parent. * * @param parent If not property/class found, search also in this parent */ public AbcIndexing(AbcIndexing parent) { this(null, parent); } /** * Creates index from SWF file with parent * * @param swf SWF file to load initial ABCs from * @param parent If not property/class found, search also in this parent */ public AbcIndexing(SWF swf, AbcIndexing parent) { this.parent = parent; if (swf != null) { for (ABCContainerTag at : swf.getAbcList()) { addAbc(at.getABC()); } } } /** * Creates index from SWF file. * * @param swf SWF file to load initial ABCs from */ public AbcIndexing(SWF swf) { this(swf, null); } /** * Creates empty index */ public AbcIndexing() { this(null, null); } /** * Property key */ public static class PropertyDef { private final String propName; private final GraphTargetItem parent; private int propNsIndex = 0; private ABC abc = null; @Override public String toString() { return parent.toString() + ":" + propName + (propNsIndex > 0 ? "[ns:" + propNsIndex + "]" : ""); } private void setPrivate(ABC abc, int propNsIndex) { this.propNsIndex = propNsIndex; this.abc = abc; } public String getPropertyName() { return propName; } /** * Creates key to property. * * @param propName Name of the property * @param parent Parent type (usually TypeItem) * @param abc ABC for private/protected namespace resolving * @param propNsIndex Index of property(trait) namespace for * private/protected namespace reolving */ public PropertyDef(String propName, GraphTargetItem parent, ABC abc, int propNsIndex) { this.propName = propName; this.parent = parent; if (abc == null || propNsIndex <= 0) { return; } int k = abc.constants.getNamespace(propNsIndex).kind; if (k != Namespace.KIND_PACKAGE) { setPrivate(abc, propNsIndex); } } @Override public int hashCode() { int hash = 7; hash = 17 * hash + Objects.hashCode(this.propName); hash = 17 * hash + Objects.hashCode(this.parent); hash = 17 * hash + this.propNsIndex; hash = 17 * hash + System.identityHashCode(this.abc); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final PropertyDef other = (PropertyDef) obj; if (!Objects.equals(this.propName, other.propName)) { return false; } if (!Objects.equals(this.parent, other.parent)) { return false; } if (this.propNsIndex != other.propNsIndex) { return false; } return (this.abc == other.abc); } } /** * Namespaced property key */ public static class PropertyNsDef { private final String propName; private final DottedChain ns; private int propNsIndex = 0; private ABC abc = null; private void setPrivate(ABC abc, int propNsIndex) { this.propNsIndex = propNsIndex; this.abc = abc; } public String getPropertyName() { return propName; } @Override public String toString() { return ns.toString() + ":" + propName + (propNsIndex > 0 ? "[ns:" + propNsIndex + "]" : ""); } public PropertyNsDef(String propName, DottedChain ns, ABC abc, int nsIndex) { this.propName = propName; this.ns = ns; if (abc == null || nsIndex <= 0) { return; } int k = abc.constants.getNamespace(nsIndex).kind; if (k != Namespace.KIND_PACKAGE) { setPrivate(abc, nsIndex); } } @Override public int hashCode() { int hash = 7; hash = 19 * hash + Objects.hashCode(this.propName); hash = 19 * hash + Objects.hashCode(this.ns); hash = 19 * hash + this.propNsIndex; hash = 19 * hash + System.identityHashCode(this.abc); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final PropertyNsDef other = (PropertyNsDef) obj; if (!Objects.equals(this.propName, other.propName)) { return false; } if (!Objects.equals(this.ns, other.ns)) { return false; } if (this.propNsIndex != other.propNsIndex) { return false; } return (this.abc == other.abc); } } public static class TraitIndex { public Trait trait; public ABC abc; public GraphTargetItem returnType; public ValueKind value; public GraphTargetItem objType; public TraitIndex(Trait trait, ABC abc, GraphTargetItem type, ValueKind value, GraphTargetItem objType) { this.trait = trait; this.abc = abc; this.returnType = type; this.value = value; this.objType = objType; } } public static class ClassIndex { public int index; public ABC abc; public ClassIndex parent; @Override public String toString() { return abc.constants.getMultiname(abc.instance_info.get(index).name_index).getNameWithNamespace(abc.constants, true).toPrintableString(true); } public ClassIndex(int index, ABC abc, ClassIndex parent) { this.index = index; this.abc = abc; this.parent = parent; } } private final Map<GraphTargetItem, ClassIndex> classes = new HashMap<>(); private final Map<PropertyDef, TraitIndex> instanceProperties = new HashMap<>(); private final Map<PropertyDef, TraitIndex> classProperties = new HashMap<>(); private final Map<PropertyNsDef, TraitIndex> instanceNsProperties = new HashMap<>(); private final Map<PropertyNsDef, TraitIndex> classNsProperties = new HashMap<>(); private final Map<PropertyNsDef, TraitIndex> scriptProperties = new HashMap<>(); public ClassIndex findClass(GraphTargetItem cls) { if (!classes.containsKey(cls)) { if (parent == null) { return null; } return parent.findClass(cls); } return classes.get(cls); } public TraitIndex findScriptProperty(DottedChain ns) { return findScriptProperty(ns.getLast(), ns.getWithoutLast()); } public TraitIndex findScriptProperty(String propName, DottedChain ns) { PropertyNsDef nsd = new PropertyNsDef(propName, ns, null, 0); if (!scriptProperties.containsKey(nsd)) { if (parent != null) { return parent.findScriptProperty(propName, ns); } return null; } return scriptProperties.get(nsd); } public TraitIndex findNsProperty(PropertyNsDef prop, boolean findStatic, boolean findInstance) { if (findStatic && classNsProperties.containsKey(prop)) { if (!classNsProperties.containsKey(prop)) { if (parent != null) { TraitIndex ret = parent.findNsProperty(prop, findStatic, findInstance); if (ret != null) { return ret; } } } else { return classNsProperties.get(prop); } } if (findInstance && instanceNsProperties.containsKey(prop)) { if (!instanceNsProperties.containsKey(prop)) { if (parent != null) { TraitIndex ret = parent.findNsProperty(prop, findStatic, findInstance); if (ret != null) { return ret; } } } else { return instanceNsProperties.get(prop); } } return null; } public TraitIndex findProperty(PropertyDef prop, boolean findStatic, boolean findInstance) { //search all static first if (findStatic && classProperties.containsKey(prop)) { if (!classProperties.containsKey(prop)) { if (parent != null) { TraitIndex ret = parent.findProperty(prop, findStatic, findInstance); if (ret != null) { return ret; } } } else { return classProperties.get(prop); } } //now search instance if (findInstance && instanceProperties.containsKey(prop)) { if (!instanceProperties.containsKey(prop)) { if (parent != null) { TraitIndex ret = parent.findProperty(prop, findStatic, findInstance); if (ret != null) { return ret; } } } else { return instanceProperties.get(prop); } } //now search parent class AbcIndexing.ClassIndex ci = findClass(prop.parent); if (ci != null && ci.parent != null && (prop.abc == null || prop.propNsIndex == 0)) { ci = ci.parent; //parent protected DottedChain parentClass = ci.abc.instance_info.get(ci.index).getName(ci.abc.constants).getNameWithNamespace(ci.abc.constants, true); TraitIndex pti = findProperty(new PropertyDef(prop.propName, new TypeItem(parentClass), ci.abc, ci.abc.instance_info.get(ci.index).protectedNS), findStatic, findInstance); if (pti != null) { return pti; } //parent public return findProperty(new PropertyDef(prop.propName, new TypeItem(parentClass), null, 0), findStatic, findInstance); } return null; } public static GraphTargetItem multinameToType(int m_index, AVM2ConstantPool constants) { if (m_index == 0) { return TypeItem.UNBOUNDED; } Multiname m = constants.getMultiname(m_index); if (m.kind == Multiname.TYPENAME) { GraphTargetItem obj = multinameToType(m.qname_index, constants); List<GraphTargetItem> params = new ArrayList<>(); for (int pm : m.params) { params.add(multinameToType(pm, constants)); } return new ApplyTypeAVM2Item(null, null, obj, params); } else { return new TypeItem(m.getNameWithNamespace(constants, true)); } } private static GraphTargetItem getTraitReturnType(ABC abc, Trait t) { if (t instanceof TraitSlotConst) { TraitSlotConst tsc = (TraitSlotConst) t; if (tsc.type_index == 0) { return TypeItem.UNBOUNDED; } return PropertyAVM2Item.multinameToType(tsc.type_index, abc.constants); } if (t instanceof TraitMethodGetterSetter) { TraitMethodGetterSetter tmgs = (TraitMethodGetterSetter) t; if (tmgs.kindType == Trait.TRAIT_GETTER) { return PropertyAVM2Item.multinameToType(abc.method_info.get(tmgs.method_info).ret_type, abc.constants); } if (tmgs.kindType == Trait.TRAIT_SETTER) { if (abc.method_info.get(tmgs.method_info).param_types.length > 0) { return PropertyAVM2Item.multinameToType(abc.method_info.get(tmgs.method_info).param_types[0], abc.constants); } else { return TypeItem.UNBOUNDED; } } } if (t instanceof TraitFunction) { return new TypeItem(DottedChain.FUNCTION); } return TypeItem.UNBOUNDED; } protected void indexTraits(ABC abc, int name_index, Traits ts, Map<PropertyDef, TraitIndex> map, Map<PropertyNsDef, TraitIndex> mapNs) { for (Trait t : ts.traits) { ValueKind propValue = null; if (t instanceof TraitSlotConst) { TraitSlotConst tsc = (TraitSlotConst) t; propValue = new ValueKind(tsc.value_index, tsc.value_kind); } if (map != null) { PropertyDef dp = new PropertyDef(t.getName(abc).getName(abc.constants, new ArrayList<>() /*?*/, true, true /*FIXME ???*/), multinameToType(name_index, abc.constants), abc, abc.constants.getMultiname(t.name_index).namespace_index); map.put(dp, new TraitIndex(t, abc, getTraitReturnType(abc, t), propValue, multinameToType(name_index, abc.constants))); } if (mapNs != null) { Multiname m = abc.constants.getMultiname(t.name_index); PropertyNsDef ndp = new PropertyNsDef(t.getName(abc).getName(abc.constants, new ArrayList<>() /*?*/, true, true/*FIXME ???*/), m == null || m.namespace_index == 0 ? DottedChain.EMPTY : m.getNamespace(abc.constants).getName(abc.constants), abc, m == null ? 0 : m.namespace_index); TraitIndex ti = new TraitIndex(t, abc, getTraitReturnType(abc, t), propValue, multinameToType(name_index, abc.constants)); if (!mapNs.containsKey(ndp)) { mapNs.put(ndp, ti); } } } } public void refreshSelected() { refreshAbc(getSelectedAbc()); } public void refreshAbc(ABC abc) { if (abc == null) { return; } removeAbc(abc); addAbc(abc); } public void removeAbc(ABC abc) { abcs.remove(abc); Set<GraphTargetItem> gti_keys = new HashSet<>(classes.keySet()); for (GraphTargetItem key : gti_keys) { if (classes.get(key).abc == abc) { classes.remove(key); } } Set<PropertyDef> pd_keys = new HashSet<>(instanceProperties.keySet()); for (PropertyDef key : pd_keys) { if (instanceProperties.get(key).abc == abc) { instanceProperties.remove(key); } } pd_keys = new HashSet<>(classProperties.keySet()); for (PropertyDef key : pd_keys) { if (classProperties.get(key).abc == abc) { classProperties.remove(key); } } Set<PropertyNsDef> pnd_keys = new HashSet<>(scriptProperties.keySet()); for (PropertyNsDef key : pnd_keys) { if (scriptProperties.get(key).abc == abc) { scriptProperties.remove(key); } } pnd_keys = new HashSet<>(classNsProperties.keySet()); for (PropertyNsDef key : pnd_keys) { if (classNsProperties.get(key).abc == abc) { classNsProperties.remove(key); } } pnd_keys = new HashSet<>(instanceNsProperties.keySet()); for (PropertyNsDef key : pnd_keys) { if (instanceNsProperties.get(key).abc == abc) { instanceNsProperties.remove(key); } } } public void addAbc(ABC abc) { if (abc == null) { return; } List<ClassIndex> addedClasses = new ArrayList<>(); for (int i = 0; i < abc.instance_info.size(); i++) { InstanceInfo ii = abc.instance_info.get(i); if (ii.deleted) { continue; } ClassInfo ci = abc.class_info.get(i); ClassIndex cindex = new ClassIndex(i, abc, null); addedClasses.add(cindex); GraphTargetItem cname = multinameToType(ii.name_index, abc.constants); classes.put(cname, cindex); indexTraits(abc, ii.name_index, ii.instance_traits, instanceProperties, instanceNsProperties); indexTraits(abc, ii.name_index, ci.static_traits, classProperties, classNsProperties); } for (int i = 0; i < abc.script_info.size(); i++) { indexTraits(abc, 0, abc.script_info.get(i).traits, null, scriptProperties); } for (ClassIndex cindex : addedClasses) { int parentClassName = abc.instance_info.get(cindex.index).super_index; if (parentClassName > 0) { TypeItem parentClass = new TypeItem(abc.constants.getMultiname(parentClassName).getNameWithNamespace(abc.constants, true)); ClassIndex parentClassIndex = findClass(parentClass); if (parentClassIndex == null) { //Parent class can be deleted, do not check. TODO: handle this better //throw new RuntimeException("Parent class " + parentClass + " definition not found!"); } cindex.parent = parentClassIndex; } } abcs.add(abc); selectedAbc = abc; } public void selectAbc(ABC abc) { if (abcs.contains(abc)) { selectedAbc = abc; } else { addAbc(abc); } } public ABC getSelectedAbc() { return selectedAbc; } public DottedChain nsValueToName(String valueStr) { for (ABC abc : abcs) { DottedChain ret = abc.nsValueToName(valueStr); if (!ret.isEmpty()) { return ret; } } if (parent != null) { return parent.nsValueToName(valueStr); } return null; } }