| /* |
| * Copyright (c) 2013 QNX Software Systems and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| */ |
| package org.eclipse.cdt.internal.qt.core; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.xml.bind.DatatypeConverter; |
| |
| import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; |
| import org.eclipse.cdt.core.dom.ast.IASTDeclarator; |
| import org.eclipse.cdt.core.dom.ast.IASTNode; |
| import org.eclipse.cdt.core.dom.ast.IASTPointer; |
| import org.eclipse.cdt.core.dom.ast.IASTPointerOperator; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclSpecifier; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclarator; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTParameterDeclaration; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTReferenceOperator; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTypeId; |
| import org.eclipse.cdt.internal.core.dom.parser.ASTAmbiguousNode; |
| import org.eclipse.cdt.internal.qt.core.parser.QtParser; |
| |
| /** |
| * A collection of utility functions for dealing with Qt methods. A Qt method is a normal |
| * C++ method that has been annotated with empty macro expansions. |
| */ |
| @SuppressWarnings("restriction") |
| public class QtMethodUtil { |
| |
| /** |
| * The Qt implementation uses specific rules for generating a signature that is used |
| * to map between invokable function declarations and their use. This function has |
| * be implemented by comparing the output of moc to the various test cases in the |
| * qt test suite. |
| */ |
| public static String getQtNormalizedMethodSignature(String signature) { |
| |
| ICPPASTFunctionDeclarator function = QtParser.parseQtMethodReference(signature); |
| if (function == null) |
| return null; |
| |
| // NOTE: This implementation (both here and in methods that are invoked) used call #getRawSignature |
| // to get the original tokens. This has been changed to use #toString instead. They seem to |
| // provide the same value, so this should be OK. The problem with #getRawSignature is that it |
| // looks for the characters in the file (using offset and length). There isn't a file backing |
| // the StringScanner, so the result is the empty String. If we find cases where #toString |
| // returns the wrong value, then this can be changed back to #getRawSignature. Implement the |
| // AST and LocationResolver to work with ASTNode#getRawSignatureChars: |
| // protected char[] getRawSignatureChars() { |
| // final IASTFileLocation floc= getFileLocation(); |
| // final IASTTranslationUnit ast = getTranslationUnit(); |
| // if (floc != null && ast != null) { |
| // ILocationResolver lr= (ILocationResolver) ast.getAdapter(ILocationResolver.class); |
| // if (lr != null) { |
| // return lr.getUnpreprocessedSignature(getFileLocation()); |
| // } |
| // } |
| |
| StringBuilder result = new StringBuilder(); |
| |
| // raw sig tries to find the file |
| String fnName = function.getName().getLastName().toString(); |
| result.append(stripWS(fnName)); |
| result.append('('); |
| |
| boolean first = true; |
| for (ICPPASTParameterDeclaration param : function.getParameters()) { |
| if (first) |
| first = false; |
| else |
| result.append(','); |
| |
| IASTDeclSpecifier spec = param.getDeclSpecifier(); |
| ICPPASTDeclarator declarator = param.getDeclarator(); |
| |
| // The parameters are encoded so that we can rely on , being used to separate |
| // parameters. All other commas (e.g., to separate template arguments within |
| // the parameter type) will be encoded. |
| StringBuilder paramSig = new StringBuilder(); |
| append(paramSig, spec, declarator, true); |
| |
| result.append(stripWS(paramSig.toString())); |
| } |
| |
| result.append(')'); |
| |
| // Whitespace around operators is not needed, remove it to normalize the signature. |
| return result.toString(); |
| } |
| |
| public static Collection<String> getDecodedQtMethodSignatures(String qtEncSignatures) { |
| if (qtEncSignatures == null) |
| return null; |
| |
| StringBuilder signature = new StringBuilder(); |
| int i = qtEncSignatures.indexOf('('); |
| String name = qtEncSignatures.substring(0, i); |
| |
| signature.append(name); |
| signature.append('('); |
| |
| boolean first = true; |
| List<String> signatures = new ArrayList<>(); |
| qtEncSignatures = qtEncSignatures.substring(i + 1); |
| Pattern p = Pattern.compile("^([a-zA-Z0-9+/=]*)(@?).*$"); //$NON-NLS-1$ |
| while (!qtEncSignatures.isEmpty()) { |
| Matcher m = p.matcher(qtEncSignatures); |
| if (!m.matches()) |
| break; |
| |
| int next = m.end(2) + 1; |
| qtEncSignatures = qtEncSignatures.substring(next); |
| |
| String param = new String(DatatypeConverter.parseBase64Binary(m.group(1))); |
| |
| // If this parameter has a default value, then add a signature for the method up |
| // to this point. |
| if (!m.group(2).isEmpty()) |
| signatures.add(signature.toString() + ')'); |
| |
| if (first) |
| first = false; |
| else |
| signature.append(','); |
| signature.append(param); |
| } |
| |
| signature.append(')'); |
| signatures.add(signature.toString()); |
| return signatures; |
| } |
| |
| /** |
| * The Qt implementation has specific rules for generating a signature that is used |
| * to map between invokable function declarations and their use. This function has |
| * been implemented by comparing the output of moc to the various test cases in the |
| * Qt test suite. |
| */ |
| public static String getEncodedQtMethodSignatures(ICPPASTFunctionDeclarator function) { |
| StringBuilder result = new StringBuilder(); |
| |
| String fnName = function.getName().getLastName().toString(); |
| result.append(stripWS(fnName)); |
| result.append('('); |
| |
| boolean first = true; |
| for (ICPPASTParameterDeclaration param : function.getParameters()) { |
| if (first) |
| first = false; |
| else |
| result.append(','); |
| |
| IASTDeclSpecifier spec = param.getDeclSpecifier(); |
| ICPPASTDeclarator declarator = param.getDeclarator(); |
| |
| // The parameters are encoded so that we can rely on , being used to separate |
| // parameters. All other commas (e.g., to separate template arguments within |
| // the parameter type) will be encoded. |
| StringBuilder paramSig = new StringBuilder(); |
| append(paramSig, spec, declarator, true); |
| |
| String paramStr = stripWS(paramSig.toString()); |
| result.append(DatatypeConverter.printBase64Binary(paramStr.getBytes())); |
| |
| // A special character is used as a suffix on parameters that have a default value. |
| // A previous version of this implementation used '=' within the Base64 encoded |
| // payload. Now that the initializer flag is outside of the payload, '=' is a bad |
| // choice because it is also a valid Base64 encoded character. |
| // Like all the other parts of this encoder, the @ must match the value that is used |
| // in the decoder. |
| if (declarator.getInitializer() != null) |
| result.append('@'); |
| } |
| |
| result.append(')'); |
| |
| // Whitespace around operators is not needed, remove it to normalize the signature. |
| return result.toString(); |
| } |
| |
| private static String stripWS(String str) { |
| return str.trim().replaceAll("\\s+", " "). //$NON-NLS-1$ //$NON-NLS-2$ |
| replaceAll(" ([\\*&,()<>]+)", "$1"). //$NON-NLS-1$ //$NON-NLS-2$ |
| replaceAll("([\\*&,()<>]+) ", "$1"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| private static String asString(IASTPointerOperator ptr) { |
| if (ptr instanceof ICPPASTReferenceOperator) |
| return "&"; //$NON-NLS-1$ |
| if (ptr instanceof IASTPointer) { |
| StringBuilder str = new StringBuilder(); |
| IASTPointer astPtr = (IASTPointer) ptr; |
| str.append('*'); |
| if (astPtr.isConst()) |
| str.append(" const"); //$NON-NLS-1$ |
| if (astPtr.isVolatile()) |
| str.append(" volatile"); //$NON-NLS-1$ |
| return str.toString(); |
| } |
| |
| return ptr.toString(); |
| } |
| |
| private static void append(StringBuilder result, IASTDeclSpecifier spec, IASTDeclarator declarator, |
| boolean pruneConst) { |
| IASTPointerOperator[] ptrs = declarator.getPointerOperators(); |
| if (ptrs == null) |
| ptrs = new IASTPointerOperator[0]; |
| |
| if (!(spec instanceof ICPPASTDeclSpecifier)) { |
| result.append(spec.toString()); |
| return; |
| } |
| |
| ICPPASTDeclSpecifier cppSpec = (ICPPASTDeclSpecifier) spec; |
| |
| // Qt considers the type const if it is marked as const, or if it is a reference |
| // and the previous pointer is const. I.e., we need this: |
| // const T& -> T |
| // const T* const & -> T* |
| boolean isConst = cppSpec.isConst(); |
| boolean stripLastPtrConst = pruneConst && !isConst |
| && (ptrs.length >= 2 && ptrs[ptrs.length - 1] instanceof ICPPASTReferenceOperator |
| && ptrs[ptrs.length - 2] instanceof IASTPointer |
| && ((IASTPointer) ptrs[ptrs.length - 2]).isConst()); |
| |
| if (isConst || stripLastPtrConst) { |
| if (!pruneConst) |
| result.append("const "); //$NON-NLS-1$ |
| else { |
| // Qt signature generation converts const value and const reference types |
| // into simple value types. E.g., |
| // const T => T |
| // const T & => T |
| // From observation, they also convert const pointer to const to const |
| // pointers although I think that is a bug, because simple pointer to |
| // const are not converted to simple pointers. E.g., |
| // const T * => const T * |
| // const T * const => T * const |
| if (ptrs.length > 0) { |
| IASTPointerOperator lastPtr = ptrs[ptrs.length - 1]; |
| if (lastPtr instanceof ICPPASTReferenceOperator) |
| ptrs = Arrays.copyOf(ptrs, ptrs.length - 1); |
| else if (!(lastPtr instanceof IASTPointer) || !((IASTPointer) lastPtr).isConst()) |
| result.append("const "); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| // Qt does no special handling for volatile. This is likely an oversight. |
| if (cppSpec.isVolatile()) |
| result.append("volatile "); //$NON-NLS-1$ |
| |
| IASTNode[] children = cppSpec.getChildren(); |
| if (children == null || children.length <= 0) { |
| // We use the raw signature to get the text that was used to reference the |
| // type (without following typedefs, etc.), and then strip out all const |
| // which has already been handled. |
| String raw = cppSpec.toString(); |
| raw = raw.replaceAll("const\\s", ""); //$NON-NLS-1$ //$NON-NLS-2$ |
| raw = raw.replaceAll("\\sconst", ""); //$NON-NLS-1$ //$NON-NLS-2$ |
| result.append(raw); |
| } else { |
| for (IASTNode child : children) { |
| result.append(' '); |
| if (child instanceof ICPPASTTemplateId) { |
| ICPPASTTemplateId templId = (ICPPASTTemplateId) child; |
| result.append(templId.getTemplateName()); |
| result.append('<'); |
| for (IASTNode templArg : templId.getTemplateArguments()) { |
| append(result, templArg); |
| } |
| result.append('>'); |
| } else |
| result.append(child.toString()); |
| } |
| } |
| |
| // exclude param name, use '=' to indicate an initial value |
| for (int i = 0; i < ptrs.length; ++i) { |
| if (!stripLastPtrConst || i < ptrs.length - 1) |
| result.append(asString(ptrs[i])); |
| else |
| result.append(asString(ptrs[i]).replaceAll("const", "")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| private static void append(StringBuilder result, IASTNode node) { |
| |
| // JI476551: When the code is parsed without full context, e.g., when parsing a Qt method ref, an |
| // ambiguous node could be created. Since we only need the original text, we can use |
| // any of the nodes that triggered the ambiguity. Arbitrarily choose the first one. |
| if (node instanceof ASTAmbiguousNode) { |
| IASTNode[] nodes = ((ASTAmbiguousNode) node).getNodes(); |
| if (nodes != null && nodes.length > 0) { |
| append(result, nodes[0]); |
| return; |
| } |
| } |
| |
| if (node instanceof ICPPASTTypeId) { |
| ICPPASTTypeId typeId = (ICPPASTTypeId) node; |
| IASTDeclSpecifier spec = typeId.getDeclSpecifier(); |
| IASTDeclarator declarator = typeId.getAbstractDeclarator(); |
| append(result, spec, declarator, false); |
| return; |
| } |
| |
| if (!(node instanceof ICPPASTTemplateId)) { |
| result.append(node.toString()); |
| return; |
| } |
| |
| ICPPASTTemplateId templId = (ICPPASTTemplateId) node; |
| result.append(templId.getTemplateName()); |
| result.append('<'); |
| boolean first = true; |
| for (IASTNode child : templId.getTemplateArguments()) { |
| if (first) |
| first = false; |
| else |
| result.append(", "); //$NON-NLS-1$ |
| append(result, child); |
| } |
| result.append('>'); |
| } |
| } |