001    package org.bridj.demangling;
002    
003    import java.util.ArrayList;
004    import java.util.Arrays;
005    import java.util.List;
006    
007    import org.bridj.CLong;
008    import org.bridj.NativeLibrary;
009    import org.bridj.demangling.Demangler.ClassRef;
010    import org.bridj.demangling.Demangler.DemanglingException;
011    import org.bridj.demangling.Demangler.Ident;
012    import org.bridj.demangling.Demangler.MemberRef;
013    import org.bridj.demangling.Demangler.NamespaceRef;
014    import org.bridj.demangling.Demangler.TypeRef;
015    import org.bridj.demangling.Demangler.SpecialName;
016    import java.util.Collections;
017    import java.util.HashMap;
018    import java.util.HashSet;
019    import java.util.Map;
020    import java.util.Set;
021    import org.bridj.demangling.Demangler.IdentLike;
022    
023    public class GCC4Demangler extends Demangler {
024    
025        public GCC4Demangler(NativeLibrary library, String symbol) {
026            super(library, symbol);
027        }
028        private Map<String, List<IdentLike>> prefixShortcuts = new HashMap<String, List<IdentLike>>() {
029    
030            {
031    
032                // prefix shortcut: e.g. St is for std::
033                put("t", Arrays.asList((IdentLike) new Ident("std")));
034                put("a", Arrays.asList((IdentLike) new Ident("std"), new Ident("allocator")));
035                put("b", Arrays.asList((IdentLike) new Ident("std"), new Ident("basic_string")));
036                TypeRef chartype = classType(Byte.TYPE);
037                ClassRef charTraitsOfChar = enclosed("std", new ClassRef(new Ident("char_traits", new TemplateArg[]{chartype})));
038                ClassRef allocatorOfChar = enclosed("std", new ClassRef(new Ident("allocator", new TemplateArg[]{chartype})));
039                put("d", Arrays.asList((IdentLike) new Ident("std"), new Ident("basic_iostream", new TemplateArg[]{chartype, charTraitsOfChar})));
040                put("i", Arrays.asList((IdentLike) new Ident("std"), new Ident("basic_istream", new TemplateArg[]{chartype, charTraitsOfChar})));
041                put("o", Arrays.asList((IdentLike) new Ident("std"), new Ident("basic_ostream", new TemplateArg[]{chartype, charTraitsOfChar})));
042                // Ss == std::string == std::basic_string<char, std::char_traits<char>, std::allocator<char> >        
043                put("s", Arrays.asList((IdentLike) new Ident("std"), new Ident("basic_string", new TemplateArg[]{classType(Byte.TYPE), charTraitsOfChar, allocatorOfChar})));
044    
045                // used, as an helper: for i in a b c d e f g h i j k l m o p q r s t u v w x y z; do c++filt _Z1_S$i; done
046            }
047    
048            private ClassRef enclosed(String ns, ClassRef classRef) {
049                classRef.setEnclosingType(new NamespaceRef(new Ident(ns)));
050                return classRef;
051            }
052        };
053        private Set<String> shouldContinueAfterPrefix = new HashSet<String>(Arrays.asList("t"));
054        private Map<String, TypeRef> typeShortcuts = new HashMap<String, TypeRef>();
055        
056        private <T> T ensureOfType(Object o, Class<T> type) throws DemanglingException {
057            if (type.isInstance(o)) {
058                return type.cast(o);
059            } else {
060                throw new DemanglingException("Internal error in demangler: trying to cast to " + type.getCanonicalName() + " the object '" + o.toString() + "'");
061            }
062        }
063        int nextShortcutId = -1;
064    
065        private String nextShortcutId() {
066            int n = nextShortcutId++;
067            return n == -1 ? "_" : Integer.toString(n, 36).toUpperCase() + "_";
068        }
069    
070        private TypeRef parsePointerType() throws DemanglingException {
071            TypeRef pointed = parseType();
072            TypeRef res = pointerType(pointed);
073            String id = nextShortcutId();
074            typeShortcuts.put(id, res);
075            return res;
076        }
077    
078        public TemplateArg parseTemplateArg() throws DemanglingException {
079            if (consumeCharIf('L')) {
080                TypeRef tr = parseType();
081                StringBuffer b = new StringBuffer();
082                char c;
083                while (Character.isDigit(c = peekChar())) {
084                    consumeChar();
085                    b.append(c);
086                }
087                expectChars('E');
088                // TODO switch on type !
089                return new Constant(Integer.parseInt(b.toString()));
090            } else {
091                return parseType();
092            }
093        }
094    
095        public TypeRef parseType() throws DemanglingException {
096            if (Character.isDigit(peekChar())) {
097                Ident name = ensureOfType(parseNonCompoundIdent(), Ident.class);
098                String id = nextShortcutId(); // we get the id before parsing the part (might be template parameters and we need to get the ids in the right order)
099                TypeRef res = simpleType(name);
100                typeShortcuts.put(id, res);
101                return res;
102            }
103    
104            char c = consumeChar();
105            switch (c) {
106                case 'S': { // here we first check if we have a type shorcut saved, if not we fallback to the (compound) identifier case
107                    char cc = peekChar();
108                    int delta = 0;
109                    if (Character.isDigit(cc) || Character.isUpperCase(cc) || cc == '_') {
110                        String id = "";
111                        while ((c = peekChar()) != '_' && c != 0) {
112                            id += consumeChar();
113                            delta++;
114                        }
115                        if (peekChar() == 0) {
116                            throw new DemanglingException("Encountered a unexpected end in gcc mangler shortcut '" + id + "' " + prefixShortcuts.keySet());
117                        }
118                        id += consumeChar(); // the '_'
119                        delta++;
120                        if (typeShortcuts.containsKey(id)) {
121                            if (peekChar() != 'I') {
122                                // just a shortcut
123                                return typeShortcuts.get(id);
124                            } else {
125                                // shortcut but templated
126                                List<IdentLike> nsPath = new ArrayList<IdentLike>(prefixShortcuts.get(id));
127                                String templatedId = parsePossibleTemplateArguments(nsPath);
128                                if (templatedId != null) {
129                                    return typeShortcuts.get(templatedId);
130                                }
131                            }
132                        }
133                        position -= delta;
134                    }
135                }
136                // WARNING/INFO/NB: we intentionally continue to the N case
137                case 'N':
138                    position--; // I actually would peek()
139                {
140                    List<IdentLike> ns = new ArrayList<IdentLike>();
141                    String newShortcutId = parseSimpleOrComplexIdentInto(ns, false);
142                    ClassRef res = new ClassRef(ensureOfType(ns.remove(ns.size() - 1), Ident.class));
143                    if (!ns.isEmpty()) {
144                        res.setEnclosingType(new NamespaceRef(ns.toArray()));
145                    }
146                    if (newShortcutId != null) {
147                        typeShortcuts.put(newShortcutId, res);
148                    }
149                    return res;
150                }
151                case 'P':
152                    return parsePointerType();
153                case 'F':
154                    // TODO parse function type correctly !!!
155                    while (consumeChar() != 'E') {
156                    }
157    
158                    return null;
159                case 'K':
160                    return parseType();
161                case 'v': // char
162                    return classType(Void.TYPE);
163                case 'c':
164                case 'a':
165                case 'h': // unsigned
166                    return classType(Byte.TYPE);
167                case 'b': // bool
168                    return classType(Boolean.TYPE);
169                case 'l':
170                case 'm': // unsigned
171                    return classType(CLong.class);
172                //return classType(Platform.is64Bits() ? Long.TYPE : Integer.TYPE);
173                case 'x':
174                case 'y': // unsigned
175                    return classType(Long.TYPE);
176                case 'i':
177                case 'j': // unsigned
178                    return classType(Integer.TYPE);
179                case 's':
180                case 't': // unsigned
181                    return classType(Short.TYPE);
182                case 'f':
183                    return classType(Float.TYPE);
184                case 'd':
185                    return classType(Double.TYPE);
186                case 'z': // varargs
187                    return classType(Object[].class);
188                default:
189                    throw error("Unexpected type char '" + c + "'", -1);
190            }
191        }
192    
193        String parseName() throws DemanglingException { // parses a plain name, e.g. "4plop" (the 4 is the length)
194            char c;
195            StringBuilder b = new StringBuilder();
196            while (Character.isDigit(c = peekChar())) {
197                consumeChar();
198                b.append(c);
199            }
200            int len;
201            try {
202                len = Integer.parseInt(b.toString());
203            } catch (NumberFormatException ex) {
204                throw error("Expected a number", 0);
205            }
206            b.setLength(0);
207            for (int i = 0; i < len; i++) {
208                b.append(consumeChar());
209            }
210            return b.toString();
211        }
212    
213        private String parseSimpleOrComplexIdentInto(List<IdentLike> res, boolean isParsingNonShortcutableElement) throws DemanglingException {
214            String newlyAddedShortcutForThisType = null;
215            boolean shouldContinue = false;
216            boolean expectEInTheEnd = false;
217            if (consumeCharIf('N')) { // complex (NB: they don't recursively nest (they actually can within a template parameter but not elsewhere))
218                if (consumeCharIf('S')) { // it uses some shortcut prefix or type
219                    parseShortcutInto(res);
220                }
221                shouldContinue = true;
222                expectEInTheEnd = true;
223            } else { // simple
224                if (consumeCharIf('S')) { // it uses some shortcut prefix or type
225                    shouldContinue = parseShortcutInto(res);
226                } else {
227                    res.add(parseNonCompoundIdent());
228                }
229            }
230            if (shouldContinue) {
231                do {
232                    String id = nextShortcutId(); // we get the id before parsing the part (might be template parameters and we need to get the ids in the right order)
233                    newlyAddedShortcutForThisType = id;
234                    IdentLike part = parseNonCompoundIdent();
235                    res.add(part);
236                    prefixShortcuts.put(id, new ArrayList<IdentLike>(res)); // the current compound name is saved by gcc as a shortcut (we do the same)
237                    parsePossibleTemplateArguments(res);
238                } while (Character.isDigit(peekChar()) || peekChar() == 'C' || peekChar() == 'D');
239                if (isParsingNonShortcutableElement) {
240                    //prefixShortcuts.remove(previousShortcutId()); // correct the fact that we parsed one too much
241                    nextShortcutId--;
242                }
243            }
244            parsePossibleTemplateArguments(res);
245            if (expectEInTheEnd) {
246                expectAnyChar('E');
247            }
248            return newlyAddedShortcutForThisType;
249        }
250    
251        /**
252         * 
253         * @param res a list of identlikes with the namespace elements and finished with an Ident which will be replaced by a new one enriched with template info
254         * @return null if res was untouched, or the new id created because of the presence of template arguments
255         */
256        private String parsePossibleTemplateArguments(List<IdentLike> res) throws DemanglingException {
257            if (consumeCharIf('I')) {
258                List<TemplateArg> args = new ArrayList<TemplateArg>();
259                while (!consumeCharIf('E')) {
260                    args.add(parseTemplateArg());
261                }
262                String id = nextShortcutId(); // we get the id after parsing the template parameters
263                // It is very important that we create a new Ident as the other one has most probably been added as a shortcut and should be immutable from then
264                Ident templatedIdent = new Ident(ensureOfType(res.remove(res.size() - 1), Ident.class).toString(), args.toArray(new TemplateArg[args.size()]));
265                res.add(templatedIdent);
266                prefixShortcuts.put(id, new ArrayList<IdentLike>(res));
267                {
268                    List<IdentLike> ns = new ArrayList<IdentLike>(res);
269                    ClassRef clss = new ClassRef(ensureOfType(ns.remove(ns.size() - 1), Ident.class));
270                    if (!ns.isEmpty()) {
271                        clss.setEnclosingType(new NamespaceRef(ns.toArray()));
272                    }
273                    typeShortcuts.put(id, clss);
274                }
275                return id;
276            }
277            return null;
278        }
279    
280        /**
281         * @return whether we should expect more parsing after this shortcut (e.g. std::vector<...> is actually not NSt6vectorI...EE but St6vectorI...E (without trailing N)
282         */
283        private boolean parseShortcutInto(List<IdentLike> res) throws DemanglingException {
284            char c = peekChar();
285            // GCC builds shortcuts for each encountered type, they appear in the mangling as: S_, S0_, S1_, ..., SA_, SB_, ..., SZ_, S10_
286            if (c == '_') { // we encounter S_
287                List<IdentLike> toAdd = prefixShortcuts.get(Character.toString(consumeChar()));
288                if (toAdd == null) {
289                    throw new DemanglingException("Encountered a yet undefined gcc mangler shortcut S_ (first one), i.e. '_' " + prefixShortcuts.keySet());
290                }
291                res.addAll(toAdd);
292                return false;
293            } else if (Character.isDigit(c) || Character.isUpperCase(c)) { // memory shorcut S[0-9A-Z]+_
294                String id = "";
295                while ((c = peekChar()) != '_' && c != 0) {
296                    id += consumeChar();
297                }
298                if (peekChar() == 0) {
299                    throw new DemanglingException("Encountered a unexpected end in gcc mangler shortcut '" + id + "' " + prefixShortcuts.keySet());
300                }
301                id += consumeChar(); // the '_'
302                List<IdentLike> toAdd = prefixShortcuts.get(id);
303                if (toAdd == null) {
304                    throw new DemanglingException("Encountered a unexpected gcc mangler shortcut '" + id + "' " + prefixShortcuts.keySet());
305                }
306                res.addAll(toAdd);
307                return false;
308            } else if (Character.isLowerCase(c)) { // other, single character built-in shorcuts. We suppose for now that all shortcuts are lower case (e.g. Ss, St, ...)
309                String id = Character.toString(consumeChar());
310                List<IdentLike> toAdd = prefixShortcuts.get(id);
311                if (toAdd == null) {
312                    throw new DemanglingException("Encountered a unexpected gcc mangler built-in shortcut '" + id + "' " + prefixShortcuts.keySet());
313                }
314                res.addAll(toAdd);
315                return shouldContinueAfterPrefix.contains(id);
316            } else {
317                throw new DemanglingException("Encountered a unexpected gcc unknown shortcut '" + c + "' " + prefixShortcuts.keySet());
318            }
319        }
320    
321        IdentLike parseNonCompoundIdent() throws DemanglingException { // This is a plain name  with possible template parameters (or special like constructor C1, C2, ...)
322            if (consumeCharIf('C')) {
323                if (consumeCharIf('1')) {
324                    return SpecialName.Constructor;
325                } else if (consumeCharIf('2')) {
326                    return SpecialName.SpecialConstructor;
327                } else {
328                    throw error("Unknown constructor type 'C" + peekChar() + "'");
329                }
330            } else if (consumeCharIf('D')) {
331                // see http://zedcode.blogspot.com/2007/02/gcc-c-link-problems-on-small-embedded.html
332                if (consumeCharIf('0')) {
333                    return SpecialName.DeletingDestructor;
334                } else if (consumeCharIf('1')) {
335                    return SpecialName.Destructor;
336                } else if (consumeCharIf('2')) {
337                    return SpecialName.SelfishDestructor;
338                } else {
339                    throw error("Unknown destructor type 'D" + peekChar() + "'");
340                }
341            } else {
342                String n = parseName();
343                return new Ident(n);
344            }
345        }
346    
347        @Override
348        public MemberRef parseSymbol() throws DemanglingException {
349            MemberRef mr = new MemberRef();
350            if (!consumeCharIf('_')) {
351                mr.setMemberName(new Ident(str));
352                return mr;
353            }
354            consumeCharIf('_');
355            expectChars('Z');
356    
357            if (consumeCharIf('T')) {
358                if (consumeCharIf('V')) {
359                    mr.setEnclosingType(ensureOfType(parseType(), ClassRef.class));
360                    mr.setMemberName(SpecialName.VFTable);
361                    return mr;
362                }
363                return null; // can be a type info, a virtual table or strange things like that
364            }
365            /*
366            Reverse engineering of C++ operators :
367            delete[] = __ZdaPv
368            delete  = __ZdlPv
369            new[] = __Znam
370            new = __Znwm
371             */
372            if (consumeCharsIf('d', 'l', 'P', 'v')) {
373                mr.setMemberName(SpecialName.Delete);
374                return mr;
375            }
376            if (consumeCharsIf('d', 'a', 'P', 'v')) {
377                mr.setMemberName(SpecialName.DeleteArray);
378                return mr;
379            }
380            if (consumeCharsIf('n', 'w', 'm')) {
381                mr.setMemberName(SpecialName.New);
382                return mr;
383            }
384            if (consumeCharsIf('n', 'a', 'm')) {
385                mr.setMemberName(SpecialName.NewArray);
386                return mr;
387            }
388    
389            {
390                List<IdentLike> ns = new ArrayList<IdentLike>();
391                parseSimpleOrComplexIdentInto(ns, true);
392                mr.setMemberName(ns.remove(ns.size() - 1));
393                if (!ns.isEmpty()) {
394                    ClassRef parent = new ClassRef(ensureOfType(ns.remove(ns.size() - 1), Ident.class));
395                    if (mr.getMemberName() == SpecialName.Constructor || mr.getMemberName() == SpecialName.SpecialConstructor)
396                        typeShortcuts.put(nextShortcutId(), parent);
397                    if (!ns.isEmpty()) {
398                        parent.setEnclosingType(new NamespaceRef(ns.toArray()));
399                    }
400                    mr.setEnclosingType(parent);
401                }
402            }
403    
404            //System.out.println("mr = " + mr + ", peekChar = " + peekChar());
405    
406            //mr.isStatic =
407            //boolean isMethod = consumeCharIf('E');
408    
409            if (consumeCharIf('v')) {
410                if (position < length) {
411                    error("Expected end of symbol", 0);
412                }
413                mr.paramTypes = new TypeRef[0];
414            } else {
415                List<TypeRef> paramTypes = new ArrayList<TypeRef>();
416                while (position < length) {// && !consumeCharIf('E')) {
417                    paramTypes.add(parseType());
418                }
419                mr.paramTypes = paramTypes.toArray(new TypeRef[paramTypes.size()]);
420            }
421            return mr;
422        }
423    }