This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch GROOVY_2_5_X in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_2_5_X by this push: new 7156a3d GROOVY-7812: Static inner classes cannot be accessed from other files when running by 'groovy' command(closes #853) 7156a3d is described below commit 7156a3d4541fe6d761a524f682e6e04f3096a6d9 Author: Daniel Sun <sun...@apache.org> AuthorDate: Wed Jan 16 21:54:22 2019 +0800 GROOVY-7812: Static inner classes cannot be accessed from other files when running by 'groovy' command(closes #853) (cherry picked from commit ade7ecb915ece738193deaa667c54b8a483093a2) --- .../java/org/codehaus/groovy/ast/CompileUnit.java | 43 ++++++++++++- .../codehaus/groovy/control/ResolveVisitor.java | 74 +++++++++++++++++++++- .../groovy/bugs/groovy7812/Main.groovy | 29 +++++++++ .../groovy/bugs/groovy7812/MainWithErrors.groovy | 22 +++++++ .../groovy/bugs/groovy7812/Outer.groovy | 36 +++++++++++ src/test/groovy/bugs/Groovy7812Bug.groovy | 52 +++++++++++++++ 6 files changed, 253 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/ast/CompileUnit.java b/src/main/java/org/codehaus/groovy/ast/CompileUnit.java index 065098d..5758fea 100644 --- a/src/main/java/org/codehaus/groovy/ast/CompileUnit.java +++ b/src/main/java/org/codehaus/groovy/ast/CompileUnit.java @@ -20,11 +20,13 @@ package org.codehaus.groovy.ast; import groovy.lang.GroovyClassLoader; import org.codehaus.groovy.GroovyBugError; +import groovy.transform.Internal; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.messages.SyntaxErrorMessage; import org.codehaus.groovy.syntax.SyntaxException; import org.codehaus.groovy.util.ListHashMap; +import org.objectweb.asm.Opcodes; import java.security.CodeSource; import java.util.ArrayList; @@ -52,6 +54,7 @@ public class CompileUnit { private final Map<String, ClassNode> classesToCompile = new HashMap<String, ClassNode>(); private final Map<String, SourceUnit> classNameToSource = new HashMap<String, SourceUnit>(); private final Map<String, InnerClassNode> generatedInnerClasses = new HashMap(); + private final Map<String, ConstructedOuterNestedClassNode> classesToResolve = new HashMap<>(); private ListHashMap metaDataMap = null; public CompileUnit(GroovyClassLoader classLoader, CompilerConfiguration config) { @@ -91,7 +94,7 @@ public class CompileUnit { /** * @return a list of all the classes in each module in the compilation unit */ - public List getClasses() { + public List<ClassNode> getClasses() { List<ClassNode> answer = new ArrayList<ClassNode>(); for (ModuleNode module : modules) { answer.addAll(module.getClasses()); @@ -192,6 +195,24 @@ public class CompileUnit { return Collections.unmodifiableMap(generatedInnerClasses); } + public Map<String, ClassNode> getClassesToCompile() { + return classesToCompile; + } + + public Map<String, ConstructedOuterNestedClassNode> getClassesToResolve() { + return classesToResolve; + } + + /** + * Add a constructed class node as a placeholder to resolve outer nested class further. + * + * @param cn the constructed class node + */ + public void addClassNodeToResolve(ConstructedOuterNestedClassNode cn) { + classesToResolve.put(cn.getUnresolvedName(), cn); + } + + /** * Gets the node meta data for the provided key. * @@ -266,4 +287,24 @@ public class CompileUnit { public ListHashMap getMetaDataMap() { return metaDataMap; } + + + + /** + * Represents a resolved type as a placeholder, SEE GROOVY-7812 + */ + @Internal + public static class ConstructedOuterNestedClassNode extends ClassNode { + private final ClassNode enclosingClassNode; + + public ConstructedOuterNestedClassNode(ClassNode outer, String innerClassName) { + super(innerClassName, Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE); + this.enclosingClassNode = outer; + this.isPrimaryNode = false; + } + + public ClassNode getEnclosingClassNode() { + return enclosingClassNode; + } + } } diff --git a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java index 80f226d..62ed420 100644 --- a/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java +++ b/src/main/java/org/codehaus/groovy/control/ResolveVisitor.java @@ -78,6 +78,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static org.codehaus.groovy.ast.CompileUnit.ConstructedOuterNestedClassNode; import static org.codehaus.groovy.ast.GenericsType.GenericsTypeName; import static org.codehaus.groovy.ast.tools.GeneralUtils.inSamePackage; import static org.codehaus.groovy.ast.tools.GeneralUtils.isDefaultVisibility; @@ -145,6 +146,7 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer { } + private static String replacePoints(String name) { return name.replace('.','$'); } @@ -339,9 +341,44 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer { private void resolveOrFail(ClassNode type, String msg, ASTNode node) { if (resolve(type)) return; if (resolveToInner(type)) return; + if (resolveToOuterNested(type)) return; + addError("unable to resolve class " + type.getName() + " " + msg, node); } + // GROOVY-7812(#1): Static inner classes cannot be accessed from other files when running by 'groovy' command + // if the type to resolve is an inner class and it is in an outer class which is not resolved, + // we set the resolved type to a placeholder class node, i.e. a ConstructedOuterNestedClass instance + // when resolving the outer class later, we set the resolved type of ConstructedOuterNestedClass instance to the actual inner class node(SEE GROOVY-7812(#2)) + private boolean resolveToOuterNested(ClassNode type) { + CompileUnit compileUnit = currentClass.getCompileUnit(); + Map<String, ClassNode> classesToCompile = compileUnit.getClassesToCompile(); + + for (Map.Entry<String, ClassNode> entry : classesToCompile.entrySet()) { + String enclosingClassName = entry.getKey(); + ClassNode enclosingClassNode = entry.getValue(); + + String outerNestedClassName = tryToConstructOuterNestedClassName(type, enclosingClassName); + if (null != outerNestedClassName) { + compileUnit.addClassNodeToResolve(new ConstructedOuterNestedClassNode(enclosingClassNode, outerNestedClassName)); + return true; + } + } + + return false; + } + + private String tryToConstructOuterNestedClassName(ClassNode type, String enclosingClassName) { + for (String typeName = type.getName(), ident = typeName; ident.contains("."); ) { + ident = ident.substring(0, ident.lastIndexOf(".")); + if (enclosingClassName.endsWith(ident)) { + return enclosingClassName + typeName.substring(ident.length()).replace(".", "$"); + } + } + + return null; + } + private void resolveOrFail(ClassNode type, ASTNode node, boolean prefereImports) { resolveGenericsTypes(type.getGenericsTypes()); if (prefereImports && resolveAliasFromModule(type)) return; @@ -791,10 +828,10 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer { } return true; } + return false; } - public Expression transform(Expression exp) { if (exp == null) return null; Expression ret; @@ -1394,9 +1431,42 @@ public class ResolveVisitor extends ClassCodeExpressionTransformer { super.visitClass(node); + resolveOuterNestedClassFurther(node); + currentClass = oldNode; } - + + // GROOVY-7812(#2): Static inner classes cannot be accessed from other files when running by 'groovy' command + private void resolveOuterNestedClassFurther(ClassNode node) { + CompileUnit compileUnit = currentClass.getCompileUnit(); + + if (null == compileUnit) return; + + Map<String, ConstructedOuterNestedClassNode> classesToResolve = compileUnit.getClassesToResolve(); + List<String> resolvedInnerClassNameList = new LinkedList<>(); + + for (Map.Entry<String, ConstructedOuterNestedClassNode> entry : classesToResolve.entrySet()) { + String innerClassName = entry.getKey(); + ConstructedOuterNestedClassNode constructedOuterNestedClass = entry.getValue(); + + // When the outer class is resolved, all inner classes are resolved too + if (node.getName().equals(constructedOuterNestedClass.getEnclosingClassNode().getName())) { + ClassNode innerClassNode = compileUnit.getClass(innerClassName); // find the resolved inner class + + if (null == innerClassNode) { + return; // "unable to resolve class" error can be thrown already, no need to `addError`, so just return + } + + constructedOuterNestedClass.setRedirect(innerClassNode); + resolvedInnerClassNameList.add(innerClassName); + } + } + + for (String innerClassName : resolvedInnerClassNameList) { + classesToResolve.remove(innerClassName); + } + } + private void checkCyclicInheritance(ClassNode originalNode, ClassNode parentToCompare, ClassNode[] interfacesToCompare) { if(!originalNode.isInterface()) { if(parentToCompare == null) return; diff --git a/src/test-resources/groovy/bugs/groovy7812/Main.groovy b/src/test-resources/groovy/bugs/groovy7812/Main.groovy new file mode 100644 index 0000000..16d2fa4 --- /dev/null +++ b/src/test-resources/groovy/bugs/groovy7812/Main.groovy @@ -0,0 +1,29 @@ +/* + * 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 groovy.bugs.groovy7812 + +assert new Outer() +assert new Outer.Inner() +assert new groovy.bugs.groovy7812.Outer.Inner() +assert new Outer.Inner.Innest() +assert new groovy.bugs.groovy7812.Outer.Inner.Innest() +assert "2" == new Outer.Inner2().innerName +assert "3" == new Outer.Inner3().innerName +assert "1.Innest" == new Outer.Inner.Innest().name +assert "3.Innest" == new Outer.Inner3.Innest().name diff --git a/src/test-resources/groovy/bugs/groovy7812/MainWithErrors.groovy b/src/test-resources/groovy/bugs/groovy7812/MainWithErrors.groovy new file mode 100644 index 0000000..091a59e --- /dev/null +++ b/src/test-resources/groovy/bugs/groovy7812/MainWithErrors.groovy @@ -0,0 +1,22 @@ +/* + * 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 groovy.bugs.groovy7812 + +assert new Outer() +assert new Outer.Inner123() diff --git a/src/test-resources/groovy/bugs/groovy7812/Outer.groovy b/src/test-resources/groovy/bugs/groovy7812/Outer.groovy new file mode 100644 index 0000000..753bbe5 --- /dev/null +++ b/src/test-resources/groovy/bugs/groovy7812/Outer.groovy @@ -0,0 +1,36 @@ +/* + * 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 groovy.bugs.groovy7812 + +class Outer { + static class Inner { + static class Innest { + def name = "1.Innest" + } + } + static class Inner2 { + def innerName = "2" + } + static class Inner3 { + def innerName = "3" + static class Innest { + def name = "3.Innest" + } + } +} \ No newline at end of file diff --git a/src/test/groovy/bugs/Groovy7812Bug.groovy b/src/test/groovy/bugs/Groovy7812Bug.groovy new file mode 100644 index 0000000..ac94ee6 --- /dev/null +++ b/src/test/groovy/bugs/Groovy7812Bug.groovy @@ -0,0 +1,52 @@ +/* + * 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 groovy.bugs + +import org.codehaus.groovy.tools.GroovyStarter + +class Groovy7812Bug extends GroovyTestCase { + void testResolvingOuterNestedClass() { + def mainScriptPath = new File(this.getClass().getResource('/groovy/bugs/groovy7812/Main.groovy').toURI()).absolutePath + runScript(mainScriptPath) + } + +// Even if try to catch `Throwable`, the expected error is thrown all the same..., as a result, the test fails due to the weired problem... +// +// org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: +//D:\_APPS\git_apps\groovy\out\test\resources\groovy\bugs\groovy7812\MainWithErrors.groovy: 22: unable to resolve class Outer.Inner123 +// @ line 22, column 8. +// assert new Outer.Inner123() +// ^ +// +//1 error +// +// void testUnexistingInnerClass() { +// try { +// def mainScriptPath = new File(this.getClass().getResource('/groovy/bugs/groovy7812/MainWithErrors.groovy').toURI()).absolutePath +// runScript(mainScriptPath) +// } catch (Throwable t) { +// assert t.getMessage().contains('unable to resolve class Outer.Inner123') +// } +// } + + + static void runScript(String path) { + GroovyStarter.main(new String[] { "--main", "groovy.ui.GroovyMain", path }) + } +}