/******************************************************************************* * Copyright (c) 2005, 2014 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Stephan Herrmann - Contribution for * Bug 425183 - [1.8][inference] make CaptureBinding18 safe *******************************************************************************/ package org.eclipse.jdt.internal.core.util; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.Wildcard; public class BindingKeyParser { int keyStart; static final char C_THROWN = '|'; static class Scanner { static final int PACKAGE = 0; static final int TYPE = 1; static final int FIELD = 2; static final int METHOD = 3; static final int ARRAY = 4; static final int LOCAL_VAR = 5; static final int FLAGS = 6; static final int WILDCARD = 7; static final int CAPTURE = 8; static final int CAPTURE18 = 9; static final int BASE_TYPE = 10; static final int END = 11; static final int START = -1; int index = 0, start; char[] source; int token = START; Scanner(char[] source) { this.source = source; } char[] getTokenSource() { int length = this.index-this.start; char[] result = new char[length]; System.arraycopy(this.source, this.start, result, 0, length); return result; } boolean isAtAnnotationStart() { return this.index < this.source.length && this.source[this.index] == '@'; } boolean isAtCaptureStart() { return this.index < this.source.length && this.source[this.index] == '!'; } boolean isAtCapture18Start() { return this.index < this.source.length && this.source[this.index] == '^'; } boolean isAtFieldOrMethodStart() { return this.index < this.source.length && this.source[this.index] == '.'; } boolean isAtLocalVariableStart() { return this.index < this.source.length && this.source[this.index] == '#'; } boolean isAtMemberTypeStart() { return this.index < this.source.length && (this.source[this.index] == '$' || (this.source[this.index] == '.' && this.source[this.index-1] == '>')); } boolean isAtParametersEnd() { return this.index < this.source.length && this.source[this.index] == '>'; } boolean isAtParametersStart() { char currentChar; return this.index > 0 && this.index < this.source.length && ((currentChar = this.source[this.index]) == '<' || currentChar == '%'); } boolean isAtRawTypeEnd() { return this.index > 0 && this.index < this.source.length && this.source[this.index] == '>'; } boolean isAtSecondaryTypeStart() { return this.index < this.source.length && this.source[this.index] == '~'; } boolean isAtWildcardStart() { return this.index < this.source.length && this.source[this.index] == '{'; // e.g {1}+Ljava/lang/String; } boolean isAtTypeParameterStart() { return this.index < this.source.length && this.source[this.index] == 'T'; } boolean isAtTypeArgumentStart() { return this.index < this.source.length && "LIZVCDBFJS[!".indexOf(this.source[this.index]) != -1; //$NON-NLS-1$ } boolean isAtThrownStart() { return this.index < this.source.length && this.source[this.index] == C_THROWN; } boolean isAtTypeVariableStart() { return this.index < this.source.length && this.source[this.index] == ':'; } boolean isAtTypeWithCaptureStart() { return this.index < this.source.length && this.source[this.index] == '&'; } int nextToken() { int previousTokenEnd = this.index; this.start = this.index; int dollarIndex = -1; int length = this.source.length; while (this.index <= length) { char currentChar = this.index == length ? Character.MIN_VALUE : this.source[this.index]; switch (currentChar) { case 'B': case 'C': case 'D': case 'F': case 'I': case 'J': case 'N': case 'S': case 'V': case 'Z': // base type if (this.index == previousTokenEnd && (this.index == 0 || this.source[this.index-1] != '.')) { // case of field or method starting with one of the character above this.index++; this.token = BASE_TYPE; return this.token; } break; case 'L': case 'T': if (this.index == previousTokenEnd && (this.index == 0 || this.source[this.index-1] != '.')) { // case of field or method starting with one of the character above this.start = this.index+1; dollarIndex = -1; } break; case ';': if (this.index == previousTokenEnd) { this.start = this.index+1; dollarIndex = -1; previousTokenEnd = this.start; } else { if (dollarIndex != -1) this.index = dollarIndex; this.token = TYPE; return this.token; } break; case '$': if (this.index == previousTokenEnd) { this.start = this.index+1; dollarIndex = -1; } else { if (dollarIndex == -1) { dollarIndex = this.index; break; } this.index = dollarIndex; this.token = TYPE; return this.token; } break; case '~': if (this.index == previousTokenEnd) { this.start = this.index+1; dollarIndex = -1; } else { this.token = TYPE; return this.token; } break; case '.': case '%': case ':': case '>': case '@': this.start = this.index+1; dollarIndex = -1; previousTokenEnd = this.start; break; case '[': while (this.index < length && this.source[this.index] == '[') this.index++; this.token = ARRAY; return this.token; case '<': if (this.start > 0) { switch (this.source[this.start-1]) { case '.': if (this.source[this.start-2] == '>') { // case of member type where enclosing type is parameterized if (dollarIndex != -1) this.index = dollarIndex; this.token = TYPE; } else { this.token = METHOD; } return this.token; default: if (this.index == previousTokenEnd) { this.start = this.index+1; dollarIndex = -1; previousTokenEnd = this.start; } else { if (dollarIndex != -1) this.index = dollarIndex; this.token = TYPE; return this.token; } } } break; case '(': this.token = METHOD; return this.token; case ')': if (this.token == TYPE) { this.token = FIELD; return this.token; } this.start = this.index+1; dollarIndex = -1; previousTokenEnd = this.start; break; case '#': if (this.index == previousTokenEnd) { this.start = this.index+1; dollarIndex = -1; previousTokenEnd = this.start; } else { this.token = LOCAL_VAR; return this.token; } break; case Character.MIN_VALUE: switch (this.token) { case START: this.token = PACKAGE; break; case METHOD: case LOCAL_VAR: this.token = LOCAL_VAR; break; case TYPE: if (this.index > this.start && this.source[this.start-1] == '.') this.token = FIELD; else this.token = END; break; case WILDCARD: this.token = TYPE; break; default: this.token = END; break; } return this.token; case '*': case '+': case '-': this.index++; this.token = WILDCARD; return this.token; case '!': case '&': this.index++; this.token = CAPTURE; return this.token; case '^': this.index++; this.token = CAPTURE18; return this.token; } this.index++; } this.token = END; return this.token; } void skipMethodSignature() { this.start = this.index; int braket = 0; while (this.index < this.source.length) { switch (this.source[this.index]) { case '#': case '%': case '@': case C_THROWN: return; case ':': if (braket == 0) return; break; case '<': case '(': braket++; break; case '>': case ')': braket--; break; } this.index++; } } void skipRank() { this.start = this.index; while (this.index < this.source.length && "0123456789".indexOf(this.source[this.index]) != -1) //$NON-NLS-1$ this.index++; } void skipThrownStart() { while (this.index < this.source.length && this.source[this.index] == C_THROWN) this.index++; } void skipParametersStart() { while (this.index < this.source.length && (this.source[this.index] == '<' || this.source[this.index] == '%')) this.index++; } void skipParametersEnd() { while (this.index < this.source.length && this.source[this.index] != '>') this.index++; this.index++; } void skipTypeEnd() { if (this.index < this.source.length && this.source[this.index] == ';') this.index++; } void skipRankStart() { if (this.index < this.source.length && this.source[this.index] == '{') this.index++; } void skipRankEnd() { if (this.index < this.source.length && this.source[this.index] == '}') this.index++; this.start = this.index; } void skipCapture18Delim() { if (this.index < this.source.length && this.source[this.index] == '#') this.index++; this.start = this.index; } public String toString() { StringBuffer buffer = new StringBuffer(); switch (this.token) { case START: buffer.append("START: "); //$NON-NLS-1$ break; case PACKAGE: buffer.append("PACKAGE: "); //$NON-NLS-1$ break; case TYPE: buffer.append("TYPE: "); //$NON-NLS-1$ break; case FIELD: buffer.append("FIELD: "); //$NON-NLS-1$ break; case METHOD: buffer.append("METHOD: "); //$NON-NLS-1$ break; case ARRAY: buffer.append("ARRAY: "); //$NON-NLS-1$ break; case LOCAL_VAR: buffer.append("LOCAL VAR: "); //$NON-NLS-1$ break; case FLAGS: buffer.append("MODIFIERS: "); //$NON-NLS-1$ break; case WILDCARD: buffer.append("WILDCARD: "); //$NON-NLS-1$ break; case CAPTURE: buffer.append("CAPTURE: "); //$NON-NLS-1$ break; case CAPTURE18: buffer.append("CAPTURE18: "); //$NON-NLS-1$ break; case BASE_TYPE: buffer.append("BASE TYPE: "); //$NON-NLS-1$ break; case END: buffer.append("END: "); //$NON-NLS-1$ break; } if (this.index < 0) { buffer.append("**"); //$NON-NLS-1$ buffer.append(this.source); } else if (this.index <= this.source.length) { buffer.append(this.source, 0, this.start); buffer.append('*'); if (this.start <= this.index) { buffer.append(this.source, this.start, this.index - this.start); buffer.append('*'); buffer.append(this.source, this.index, this.source.length - this.index); } else { buffer.append('*'); buffer.append(this.source, this.start, this.source.length - this.start); } } else { buffer.append(this.source); buffer.append("**"); //$NON-NLS-1$ } return buffer.toString(); } } private boolean parsingPaused; private Scanner scanner; private boolean hasTypeName = true; private boolean isMalformed; private boolean isParsingThrownExceptions = false; // https://bugs.eclipse.org/bugs/show_bug.cgi?id=336451 public BindingKeyParser(BindingKeyParser parser) { this(""); //$NON-NLS-1$ this.scanner = parser.scanner; } public BindingKeyParser(String key) { this.scanner = new Scanner(key.toCharArray()); } public void consumeAnnotation() { // default is to do nothing } public void consumeArrayDimension(char[] brakets) { // default is to do nothing } public void consumeBaseType(char[] baseTypeSig) { // default is to do nothing } public void consumeCapture(int position) { // default is to do nothing } public void consumeCapture18ID(int id, int position) { // default is to do nothing } public void consumeException() { // default is to do nothing } public void consumeField(char[] fieldName) { // default is to do nothing } public void consumeParameterizedGenericMethod() { // default is to do nothing } public void consumeLocalType(char[] uniqueKey) { // default is to do nothing } public void consumeLocalVar(char[] varName, int occurrenceCount) { // default is to do nothing } public void consumeMethod(char[] selector, char[] signature) { // default is to do nothing } public void consumeModifiers(char[] modifiers) { // default is to do nothing } public void consumeNonGenericType() { // default is to do nothing } public void consumeMemberType(char[] simpleTypeName) { // default is to do nothing } public void consumePackage(char[] pkgName) { // default is to do nothing } public void consumeParameterizedType(char[] simpleTypeName, boolean isRaw) { // default is to do nothing } public void consumeParser(BindingKeyParser parser) { // default is to do nothing } public void consumeRawType() { // default is to do nothing } public void consumeScope(int scopeNumber) { // default is to do nothing } public void consumeSecondaryType(char[] simpleTypeName) { // default is to do nothing } public void consumeFullyQualifiedName(char[] fullyQualifiedName) { // default is to do nothing } public void consumeKey() { // default is to do nothing } public void consumeTopLevelType() { // default is to do nothing } public void consumeType() { // default is to do nothing } public void consumeTypeParameter(char[] typeParameterName) { // default is to do nothing } public void consumeTypeVariable(char[] position, char[] typeVariableName) { // default is to do nothing } public void consumeTypeWithCapture() { // default is to do nothing } public void consumeWildCard(int kind) { // default is to do nothing } public void consumeWildcardRank(int rank) { // default is to do nothing } /* * Returns the string that this binding key wraps. */ public String getKey() { return new String(this.scanner.source); } public boolean hasTypeName() { return this.hasTypeName; } public void malformedKey() { this.isMalformed = true; } public BindingKeyParser newParser() { return new BindingKeyParser(this); } public void parse() { parse(false/*don't pause after fully qualified name*/); } public void parse(boolean pauseAfterFullyQualifiedName) { if (!this.parsingPaused) { // fully qualified name parseFullyQualifiedName(); parseSecondaryType(); if (pauseAfterFullyQualifiedName) { this.parsingPaused = true; return; } } if (!hasTypeName()) { consumeKey(); return; } consumeTopLevelType(); parseInnerType(); if (this.scanner.isAtParametersStart()) { this.scanner.skipParametersStart(); if (this.scanner.isAtTypeParameterStart()) { // generic type parseGenericType(); // skip ";>" this.scanner.skipParametersEnd(); // local type in generic type parseInnerType(); } else if (this.scanner.isAtTypeArgumentStart()) // parameterized type parseParameterizedType(null/*top level type or member type with raw enclosing type*/, false/*no raw*/); else if (this.scanner.isAtRawTypeEnd()) // raw type parseRawType(); } else { // non-generic type consumeNonGenericType(); } consumeType(); this.scanner.skipTypeEnd(); if (this.scanner.isAtFieldOrMethodStart()) { switch (this.scanner.nextToken()) { case Scanner.FIELD: parseField(); if (this.scanner.isAtAnnotationStart()) { parseAnnotation(); } return; case Scanner.METHOD: parseMethod(); if (this.scanner.isAtLocalVariableStart()) { parseLocalVariable(); } else if (this.scanner.isAtTypeVariableStart()) { parseTypeVariable(); } else if (this.scanner.isAtAnnotationStart()) { parseAnnotation(); } break; default: malformedKey(); return; } } else if (!this.isParsingThrownExceptions && this.scanner.isAtTypeVariableStart()) { parseTypeVariable(); } else if (this.scanner.isAtWildcardStart()) { parseWildcard(); } else if (this.scanner.isAtTypeWithCaptureStart()) { parseTypeWithCapture(); } else if (this.scanner.isAtAnnotationStart()) { parseAnnotation(); } consumeKey(); } private void parseFullyQualifiedName() { if (this.scanner.isAtCaptureStart()) { parseCapture(); this.hasTypeName = false; return; } if (this.scanner.isAtCapture18Start()) { parseCapture18(); this.hasTypeName = false; return; } switch(this.scanner.nextToken()) { case Scanner.PACKAGE: this.keyStart = 0; consumePackage(this.scanner.getTokenSource()); this.hasTypeName = false; return; case Scanner.TYPE: this.keyStart = this.scanner.start-1; consumeFullyQualifiedName(this.scanner.getTokenSource()); break; case Scanner.BASE_TYPE: this.keyStart = this.scanner.start-1; consumeBaseType(this.scanner.getTokenSource()); this.hasTypeName = false; break; case Scanner.ARRAY: this.keyStart = this.scanner.start; consumeArrayDimension(this.scanner.getTokenSource()); switch (this.scanner.nextToken()) { case Scanner.TYPE: consumeFullyQualifiedName(this.scanner.getTokenSource()); break; case Scanner.BASE_TYPE: consumeBaseType(this.scanner.getTokenSource()); this.hasTypeName = false; break; default: malformedKey(); return; } break; case Scanner.WILDCARD: // support the '-' in "Lpack/package-info;", see bug 398920 if (!CharOperation.endsWith(this.scanner.getTokenSource(), new char[] {'/', 'p', 'a', 'c', 'k', 'a', 'g', 'e', '-'})) { malformedKey(); return; } int start = this.scanner.start; if (this.scanner.nextToken() == Scanner.TYPE) { if (!CharOperation.equals(this.scanner.getTokenSource(), new char[] {'i', 'n', 'f', 'o'})) { malformedKey(); return; } this.scanner.start = start; this.keyStart = start-1; consumeFullyQualifiedName(this.scanner.getTokenSource()); break; } break; default: malformedKey(); return; } } private void parseParameterizedMethod() { this.scanner.skipParametersStart(); while (!this.scanner.isAtParametersEnd() && !this.isMalformed) { parseTypeArgument(); } consumeParameterizedGenericMethod(); } private void parseGenericType() { while (!this.scanner.isAtParametersEnd() && !this.isMalformed) { if (this.scanner.nextToken() != Scanner.TYPE) { malformedKey(); return; } consumeTypeParameter(this.scanner.getTokenSource()); this.scanner.skipTypeEnd(); } } private void parseInnerType() { if (!this.scanner.isAtMemberTypeStart() || this.scanner.nextToken() != Scanner.TYPE) return; char[] typeName = this.scanner.getTokenSource(); // Might not actually be an inner type but came here as a consequence of '$' being present in type name if (typeName.length == 0) return; if (Character.isDigit(typeName[0])) { // anonymous or local type int nextToken = Scanner.TYPE; while (this.scanner.isAtMemberTypeStart() && !this.isMalformed) nextToken = this.scanner.nextToken(); typeName = nextToken == Scanner.END ? this.scanner.source : CharOperation.subarray(this.scanner.source, this.keyStart, this.scanner.index+1); consumeLocalType(typeName); } else { consumeMemberType(typeName); parseInnerType(); } } private void parseLocalVariable() { if (this.scanner.nextToken() != Scanner.LOCAL_VAR) { malformedKey(); return; } char[] varName = this.scanner.getTokenSource(); if (Character.isDigit(varName[0])) { int index = Integer.parseInt(new String(varName)); consumeScope(index); if (!this.scanner.isAtLocalVariableStart()) { malformedKey(); return; } parseLocalVariable(); } else { int occurrenceCount = 0; if (this.scanner.isAtLocalVariableStart()) { if (this.scanner.nextToken() != Scanner.LOCAL_VAR) { malformedKey(); return; } char[] occurrence = this.scanner.getTokenSource(); occurrenceCount = Integer.parseInt(new String(occurrence)); } consumeLocalVar(varName, occurrenceCount); } } private void parseMethod() { char[] selector = this.scanner.getTokenSource(); this.scanner.skipMethodSignature(); char[] signature = this.scanner.getTokenSource(); consumeMethod(selector, signature); if (this.scanner.isAtThrownStart()) { parseThrownExceptions(); } if (this.scanner.isAtParametersStart()) parseParameterizedMethod(); } private void parseAnnotation() { /* * The call parser.parse() might have a side-effect on the current token type * See bug 264443 */ int token = this.scanner.token; BindingKeyParser parser = newParser(); parser.parse(); consumeParser(parser); consumeAnnotation(); this.isMalformed = parser.isMalformed; this.scanner.token = token; } private void parseCapture() { if (this.scanner.nextToken() != Scanner.CAPTURE) return; parseCaptureWildcard(); if (this.scanner.nextToken() != Scanner.TYPE) { malformedKey(); return; } char[] positionChars = this.scanner.getTokenSource(); int position = Integer.parseInt(new String(positionChars)); consumeCapture(position); this.scanner.skipTypeEnd(); } private void parseCapture18() { // syntax: ^{int#int} if (this.scanner.nextToken() != Scanner.CAPTURE18) return; this.scanner.skipRankStart(); // { this.scanner.skipRank(); char[] source = this.scanner.getTokenSource(); int position = Integer.parseInt(new String(source)); this.scanner.skipCapture18Delim(); // # this.scanner.skipRank(); source = this.scanner.getTokenSource(); int id = Integer.parseInt(new String(source)); this.scanner.skipRankEnd(); // } consumeCapture18ID(id, position); this.scanner.skipTypeEnd(); } private void parseCaptureWildcard() { /* * The call parser.parse() might have a side-effect on the current token type * See bug 264443 */ int token = this.scanner.token; BindingKeyParser parser = newParser(); parser.parse(); consumeParser(parser); this.isMalformed = parser.isMalformed; this.scanner.token = token; } private void parseField() { char[] fieldName = this.scanner.getTokenSource(); parseReturnType(); consumeField(fieldName); } private void parseThrownExceptions() { /* * The call parser.parse() might have a side-effect on the current token type * See bug 264443 */ int token = this.scanner.token; while (this.scanner.isAtThrownStart() && !this.isMalformed) { this.scanner.skipThrownStart(); BindingKeyParser parser = newParser(); parser.isParsingThrownExceptions = true; parser.parse(); consumeParser(parser); consumeException(); this.isMalformed = parser.isMalformed; } this.scanner.token = token; } private void parseParameterizedType(char[] typeName, boolean isRaw) { if (!isRaw) { while (!this.scanner.isAtParametersEnd() && !this.isMalformed) { parseTypeArgument(); } } // skip ";>" this.scanner.skipParametersEnd(); consumeParameterizedType(typeName, isRaw); this.scanner.skipTypeEnd(); if (this.scanner.isAtMemberTypeStart() && this.scanner.nextToken() == Scanner.TYPE) { typeName = this.scanner.getTokenSource(); if (this.scanner.isAtParametersStart()) { this.scanner.skipParametersStart(); parseParameterizedType(typeName, this.scanner.isAtRawTypeEnd()); } else consumeParameterizedType(typeName, true/*raw*/); } } private void parseRawType() { this.scanner.skipParametersEnd(); consumeRawType(); this.scanner.skipTypeEnd(); if (this.scanner.isAtMemberTypeStart() && this.scanner.nextToken() == Scanner.TYPE) { char[] typeName = this.scanner.getTokenSource(); if (this.scanner.isAtParametersStart()) { this.scanner.skipParametersStart(); parseParameterizedType(typeName, this.scanner.isAtRawTypeEnd()); } else consumeParameterizedType(typeName, true/*raw*/); } } private void parseReturnType() { this.scanner.index++; // skip ')' /* * The call parser.parse() might have a side-effect on the current token type * See bug 264443 */ int token = this.scanner.token; BindingKeyParser parser = newParser(); parser.parse(); consumeParser(parser); this.isMalformed = parser.isMalformed; this.scanner.token = token; } private void parseSecondaryType() { if (!this.scanner.isAtSecondaryTypeStart() || this.scanner.nextToken() != Scanner.TYPE) return; consumeSecondaryType(this.scanner.getTokenSource()); } private void parseTypeArgument() { /* * The call parser.parse() might have a side-effect on the current token type * See bug 264443 */ int token = this.scanner.token; BindingKeyParser parser = newParser(); parser.parse(); consumeParser(parser); this.isMalformed = parser.isMalformed; this.scanner.token = token; } private void parseTypeWithCapture() { if (this.scanner.nextToken() != Scanner.CAPTURE) return; /* * The call parser.parse() might have a side-effect on the current token type * See bug 264443 */ int token = this.scanner.token; BindingKeyParser parser = newParser(); parser.parse(); consumeParser(parser); consumeTypeWithCapture(); this.isMalformed = parser.isMalformed; this.scanner.token = token; } private void parseTypeVariable() { if (this.scanner.nextToken() != Scanner.TYPE) { malformedKey(); return; } char[] typeVariableName = this.scanner.getTokenSource(); char[] position; int length = typeVariableName.length; if (length > 0 && Character.isDigit(typeVariableName[0])) { int firstT = CharOperation.indexOf('T', typeVariableName); position = CharOperation.subarray(typeVariableName, 0, firstT); typeVariableName = CharOperation.subarray(typeVariableName, firstT+1, typeVariableName.length); } else { position = CharOperation.NO_CHAR; } consumeTypeVariable(position, typeVariableName); this.scanner.skipTypeEnd(); } private void parseWildcard() { parseWildcardRank(); if (this.scanner.nextToken() != Scanner.WILDCARD) return; char[] source = this.scanner.getTokenSource(); if (source.length == 0) { malformedKey(); return; } int kind = -1; switch (source[0]) { case '*': kind = Wildcard.UNBOUND; break; case '+': kind = Wildcard.EXTENDS; break; case '-': kind = Wildcard.SUPER; break; } if (kind == -1) { malformedKey(); return; } if (kind != Wildcard.UNBOUND) parseWildcardBound(); consumeWildCard(kind); } private void parseWildcardRank() { this.scanner.skipRankStart(); this.scanner.skipRank(); char[] source = this.scanner.getTokenSource(); consumeWildcardRank(Integer.parseInt(new String(source))); this.scanner.skipRankEnd(); } private void parseWildcardBound() { /* * The call parser.parse() might have a side-effect on the current token type * See bug 264443 */ int token = this.scanner.token; BindingKeyParser parser = newParser(); parser.parse(); consumeParser(parser); this.isMalformed = parser.isMalformed; this.scanner.token = token; } }