From f238db98a6075e59973c5c391a1946eb7972c7a5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Hannu=20Niemist=C3=B6?= Date: Mon, 4 Dec 2017 14:20:31 +0200 Subject: [PATCH] Collect reference hierarchy for SCL values refs #7662 Change-Id: I4bf296f8c676fdc5c3387d496a898a364ca78a7b --- .../META-INF/MANIFEST.MF | 1 + .../scl/SCL/CallHierarchy.scl | 49 +++++++++++++++++++ .../scl/SCL/Reflection.scl | 44 ++++++++++++++++- .../compilation/CompilationContext.java | 2 + .../scl/compiler/compilation/Elaboration.java | 18 ++++--- .../scl/compiler/compilation/SCLCompiler.java | 2 + .../contexts/TranslationContext.java | 15 +++++- .../scl/compiler/module/ConcreteModule.java | 14 +++++- .../simantics/scl/compiler/module/Module.java | 17 +++++++ .../module/debug/ModuleDebugInfo.java | 7 +++ .../module/debug/SymbolReference.java | 15 ++++++ .../options/ModuleCompilationOptions.java | 1 + .../scl/compiler/top/ExpressionEvaluator.java | 2 +- .../src/org/simantics/scl/osgi/SCLOsgi.java | 16 ++++++ 14 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 bundles/org.simantics.scl.compiler/scl/SCL/CallHierarchy.scl create mode 100644 bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/debug/ModuleDebugInfo.java create mode 100644 bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/debug/SymbolReference.java diff --git a/bundles/org.simantics.scl.compiler/META-INF/MANIFEST.MF b/bundles/org.simantics.scl.compiler/META-INF/MANIFEST.MF index 7f76ebc10..c36c46274 100644 --- a/bundles/org.simantics.scl.compiler/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.scl.compiler/META-INF/MANIFEST.MF @@ -63,6 +63,7 @@ Export-Package: org.cojen.classfile, org.simantics.scl.compiler.markdown.nodes, org.simantics.scl.compiler.module, org.simantics.scl.compiler.module.coverage, + org.simantics.scl.compiler.module.debug, org.simantics.scl.compiler.module.options, org.simantics.scl.compiler.module.repository, org.simantics.scl.compiler.runtime, diff --git a/bundles/org.simantics.scl.compiler/scl/SCL/CallHierarchy.scl b/bundles/org.simantics.scl.compiler/scl/SCL/CallHierarchy.scl new file mode 100644 index 000000000..5ab8e8141 --- /dev/null +++ b/bundles/org.simantics.scl.compiler/scl/SCL/CallHierarchy.scl @@ -0,0 +1,49 @@ +module { + features = [edo], + export = [whoCalls, unusedDefinitions] +} + +import "SCL/Reflection" + +@JavaType "org.simantics.scl.compiler.module.debug.SymbolReference" +data SymbolReference = + @JavaType "org.simantics.scl.compiler.module.debug.SymbolReference" + @FieldNames [referred, referrer, referenceLocation] + SymbolReference {referred :: Name, referrer :: String, referenceLocation :: Location} + +importJava "org.simantics.scl.compiler.module.debug.ModuleDebugInfo" where + data ModuleDebugInfo + + symbolReferences :: ModuleDebugInfo -> [SymbolReference] + +importJava "org.simantics.scl.compiler.module.Module" where + @JavaName "getModuleDebugInfo" + + debugInfo :: Module -> Maybe ModuleDebugInfo + +whoCalls :: String -> String -> [(String, String, Long)] +whoCalls moduleName valueName = + [ (callerModuleName, referrer, referenceLocation) + | callerModuleName <- sclModuleNames + , Just callerModule = moduleByName callerModuleName + , Just debugInfo = debugInfo callerModule + , SymbolReference {referred, referrer, referenceLocation} <- symbolReferences debugInfo + , referred == name + ] + where + name = createName moduleName valueName + +unusedDefinitions :: [Name] +unusedDefinitions = + [ createName moduleName def + | moduleName <- sclModuleNames + , Just module = moduleByName moduleName + , def <- valueNamesOf module + ] + \\ + [ referred + | callerModuleName <- sclModuleNames + , Just callerModule = moduleByName callerModuleName + , Just debugInfo = debugInfo callerModule + , SymbolReference {referred} <- symbolReferences debugInfo + ] \ No newline at end of file diff --git a/bundles/org.simantics.scl.compiler/scl/SCL/Reflection.scl b/bundles/org.simantics.scl.compiler/scl/SCL/Reflection.scl index cf8b88eea..e32401d50 100644 --- a/bundles/org.simantics.scl.compiler/scl/SCL/Reflection.scl +++ b/bundles/org.simantics.scl.compiler/scl/SCL/Reflection.scl @@ -1,5 +1,6 @@ module { - export = [possibleUnsafeSclValueByName, unsafeSclValueByName, sclModuleNames] + export = [possibleUnsafeSclValueByName, unsafeSclValueByName, sclModuleNames, moduleByName, + moduleOf, nameOf, createName, valueNamesOf] } include "SCL/ReflectionJava" @@ -11,11 +12,47 @@ importJava "org.simantics.scl.compiler.module.repository.ModuleRepository" where @JavaName getSourceRepository moduleSourceRepositoryOf :: ModuleRepository -> ModuleSourceRepository + @JavaName getModule + moduleByName_ :: ModuleRepository -> String -> Failable Module + importJava "org.simantics.scl.compiler.source.repository.ModuleSourceRepository" where data ModuleSourceRepository @JavaName getModuleNames sclModuleNames_ :: ModuleSourceRepository -> [String] + +importJava "org.simantics.scl.compiler.errors.Failable" where + data Failable a + + didSucceed :: Failable a -> Boolean + getResult :: Failable a -> Maybe a + +importJava "org.simantics.scl.compiler.module.Module" where + data Module + + @JavaName getValueNames + valueNamesOf_ :: Module -> [String] + +importJava "org.simantics.scl.compiler.common.names.Name" where + data Name + + @JavaName module + moduleOf_ :: Name -> String + @JavaName name + nameOf_ :: Name -> String + + @JavaName create + createName_ :: String -> String -> Name + +instance Show Name where + sb <+ n = sb << moduleOf n << "/" << nameOf n + +moduleOf = moduleOf_ +nameOf = nameOf_ +createName = createName_ +valueNamesOf = valueNamesOf_ + +type Location = Long unsafeSclValueByName :: String -> a unsafeSclValueByName = unsafeSclValueByName_ MODULE_REPOSITORY @@ -25,3 +62,8 @@ possibleUnsafeSclValueByName name = Just (unsafeSclValueByName name) `catch` \(_ sclModuleNames :: [String] sclModuleNames = sclModuleNames_ (moduleSourceRepositoryOf MODULE_REPOSITORY) + +moduleByName :: String -> Maybe Module +moduleByName name = getResult failable + where + failable = moduleByName_ MODULE_REPOSITORY name \ No newline at end of file diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/CompilationContext.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/CompilationContext.java index b9625af78..bd6546dac 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/CompilationContext.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/CompilationContext.java @@ -13,6 +13,7 @@ import org.simantics.scl.compiler.internal.codegen.types.JavaTypeTranslator; import org.simantics.scl.compiler.internal.codegen.utils.JavaNamingPolicy; import org.simantics.scl.compiler.internal.header.ModuleHeader; import org.simantics.scl.compiler.module.ConcreteModule; +import org.simantics.scl.compiler.module.debug.ModuleDebugInfo; import org.simantics.scl.compiler.module.repository.ModuleRepository; import org.simantics.scl.compiler.types.Type; @@ -26,6 +27,7 @@ public class CompilationContext implements EnvironmentalContext { public JavaNamingPolicy namingPolicy; public ConcreteModule module; public ModuleHeader header; + public ModuleDebugInfo moduleDebugInfo; private THashMap valueCache = new THashMap(); diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/Elaboration.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/Elaboration.java index 799b07268..ee21ef641 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/Elaboration.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/Elaboration.java @@ -106,8 +106,10 @@ import org.simantics.scl.compiler.module.ConcreteModule; import org.simantics.scl.compiler.module.ImportDeclaration; import org.simantics.scl.compiler.module.InvalidModulePathException; import org.simantics.scl.compiler.module.ModuleUtils; +import org.simantics.scl.compiler.module.debug.ModuleDebugInfo; import org.simantics.scl.compiler.module.repository.ImportFailure; import org.simantics.scl.compiler.module.repository.ImportFailureException; +import org.simantics.scl.compiler.top.SCLCompilerConfiguration; import org.simantics.scl.compiler.types.TCon; import org.simantics.scl.compiler.types.TForAll; import org.simantics.scl.compiler.types.TFun; @@ -1077,7 +1079,7 @@ public class Elaboration { final THashMap sections = new THashMap(); - final TranslationContext context = createTranslationContext(); + final TranslationContext context = createTranslationContext(ruleName); if(length > 0) { THashMap variables = context.getVariables(); for(TransformationRule extendsRule : extendsRules) { @@ -1228,7 +1230,7 @@ public class Elaboration { continue; try { SCLValue value = module.getValue(name); - TranslationContext context = createTranslationContext(); + TranslationContext context = createTranslationContext(name); Expression expression = context.translateCases2(defs); value.setExpression(expression); @@ -1245,7 +1247,7 @@ public class Elaboration { continue; try { SCLValue value = module.getValue(name); - TranslationContext context = createTranslationContext(); + TranslationContext context = createTranslationContext(name); Expression expression = context.translateCases2(defs); value.setExpression(expression); @@ -1279,13 +1281,13 @@ public class Elaboration { DRelationAst definition = definitions.get(0); ConcreteRelation relation = (ConcreteRelation)module.getRelation(name); relation.location = definition.location; - TranslationContext context = createTranslationContext(); + TranslationContext context = createTranslationContext(name); definition.translateTo(context, relation); } } - private TranslationContext createTranslationContext() { - return new TranslationContext(compilationContext, null); + private TranslationContext createTranslationContext(String definitionName) { + return new TranslationContext(compilationContext, null, definitionName); } private void handleAnnotation(SCLValue value, ArrayList defs, DAnnotationAst annotation) { @@ -1367,4 +1369,8 @@ public class Elaboration { branchPoints.put(valueName, injector.getAndClearBranchPoints()); } } + + public void collectDebugInfo() { + module.moduleDebugInfo = compilationContext.moduleDebugInfo = new ModuleDebugInfo(); + } } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/SCLCompiler.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/SCLCompiler.java index cd43b76e1..0da48676a 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/SCLCompiler.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/compilation/SCLCompiler.java @@ -82,6 +82,8 @@ public class SCLCompiler { declarations.relationDefinitionsAst); if(options.computeCoverage) elaboration.addCoverageBranchPoints(); + if(options.collectDebugInfo) + elaboration.collectDebugInfo(); // Elaboration if(hasErrors()) return; elaboration.addTypesToEnvironment( diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/contexts/TranslationContext.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/contexts/TranslationContext.java index aeeb4cb55..b71c0ad27 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/contexts/TranslationContext.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/contexts/TranslationContext.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Arrays; import org.simantics.scl.compiler.common.names.Name; -import org.simantics.scl.compiler.common.names.Names; import org.simantics.scl.compiler.common.precedence.Associativity; import org.simantics.scl.compiler.common.precedence.Precedence; import org.simantics.scl.compiler.compilation.CompilationContext; @@ -33,6 +32,8 @@ import org.simantics.scl.compiler.environment.Namespace; import org.simantics.scl.compiler.environment.filter.AcceptAllNamespaceFilter; import org.simantics.scl.compiler.errors.Locations; import org.simantics.scl.compiler.internal.parsing.declarations.DValueAst; +import org.simantics.scl.compiler.module.debug.ModuleDebugInfo; +import org.simantics.scl.compiler.module.debug.SymbolReference; import org.simantics.scl.compiler.top.SCLCompilerConfiguration; import org.simantics.scl.compiler.types.Type; @@ -77,6 +78,10 @@ public class TranslationContext extends TypeTranslationContext implements Enviro public CHRRuleset currentRuleset; + public ModuleDebugInfo moduleDebugInfo; + + private String definitionName; + static class Entry { String name; Variable variable; @@ -104,9 +109,11 @@ public class TranslationContext extends TypeTranslationContext implements Enviro } } - public TranslationContext(CompilationContext compilationContext, LocalEnvironment localEnvironment) { + public TranslationContext(CompilationContext compilationContext, LocalEnvironment localEnvironment, String definitionName) { super(compilationContext); this.localEnvironment = localEnvironment; + this.moduleDebugInfo = compilationContext.moduleDebugInfo; + this.definitionName = definitionName; } public static boolean isConstructorName(String name) { @@ -193,6 +200,8 @@ public class TranslationContext extends TypeTranslationContext implements Enviro String deprecatedDescription = value.isDeprecated(); if(deprecatedDescription != null) errorLog.logWarning(location, "Deprecated value " + value.getName().name + "." + (deprecatedDescription.isEmpty() ? "" : " " + deprecatedDescription)); + if(moduleDebugInfo != null) + moduleDebugInfo.symbolReferences.add(new SymbolReference(value.getName(), definitionName, location)); return new EConstant(location, value); } catch (AmbiguousNameException e) { if(SCLCompilerConfiguration.ALLOW_OVERLOADING) @@ -219,6 +228,8 @@ public class TranslationContext extends TypeTranslationContext implements Enviro public Expression realize() { EConstant expression = new EConstant(altValue); expression.location = location; + if(moduleDebugInfo != null) + moduleDebugInfo.symbolReferences.add(new SymbolReference(altValue.getName(), definitionName, location)); return expression; } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/ConcreteModule.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/ConcreteModule.java index e52420925..8a83e5f35 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/ConcreteModule.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/ConcreteModule.java @@ -5,7 +5,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.concurrent.SynchronousQueue; import java.util.function.Consumer; import org.simantics.scl.compiler.common.names.Name; @@ -23,6 +22,7 @@ import org.simantics.scl.compiler.elaboration.rules.TransformationRule; import org.simantics.scl.compiler.environment.filter.NamespaceFilter; import org.simantics.scl.compiler.errors.CompilationError; import org.simantics.scl.compiler.internal.codegen.effects.EffectConstructor; +import org.simantics.scl.compiler.module.debug.ModuleDebugInfo; import org.simantics.scl.compiler.top.ModuleInitializer; import org.simantics.scl.compiler.types.TCon; import org.simantics.scl.runtime.profiling.BranchPoint; @@ -55,6 +55,8 @@ public class ConcreteModule implements Module { ModuleInitializer moduleInitializer; protected Documentation documentation; + + public ModuleDebugInfo moduleDebugInfo; public ConcreteModule(String moduleName) { this.moduleName = moduleName; @@ -270,6 +272,11 @@ public class ConcreteModule implements Module { consumer.accept(value); }); } + + @Override + public List getValueNames() { + return new ArrayList(values.keySet()); + } public Collection getRelations() { return relations.values(); @@ -337,4 +344,9 @@ public class ConcreteModule implements Module { public void setDeprecation(String deprecation) { this.deprecation = deprecation; } + + @Override + public ModuleDebugInfo getModuleDebugInfo() { + return moduleDebugInfo; + } } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/Module.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/Module.java index 6e6690fef..0ea33cb46 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/Module.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/Module.java @@ -1,5 +1,6 @@ package org.simantics.scl.compiler.module; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.function.Consumer; @@ -15,9 +16,11 @@ import org.simantics.scl.compiler.elaboration.relations.SCLEntityType; import org.simantics.scl.compiler.elaboration.relations.SCLRelation; import org.simantics.scl.compiler.elaboration.rules.MappingRelation; import org.simantics.scl.compiler.elaboration.rules.TransformationRule; +import org.simantics.scl.compiler.environment.filter.AcceptAllNamespaceFilter; import org.simantics.scl.compiler.environment.filter.NamespaceFilter; import org.simantics.scl.compiler.errors.CompilationError; import org.simantics.scl.compiler.internal.codegen.effects.EffectConstructor; +import org.simantics.scl.compiler.module.debug.ModuleDebugInfo; import org.simantics.scl.compiler.top.ModuleInitializer; import org.simantics.scl.compiler.types.TCon; import org.simantics.scl.runtime.profiling.BranchPoint; @@ -30,6 +33,13 @@ public interface Module { String getDefaultLocalName(); SCLValue getValue(String name); + default List getValueNames() { + ArrayList valueNames = new ArrayList(); + findValuesForPrefix("", AcceptAllNamespaceFilter.INSTANCE, value -> { + valueNames.add(value.getName().name); + }); + return valueNames; + } List getFieldAccessors(String name); SCLRelation getRelation(String name); SCLEntityType getEntityType(String name); @@ -61,4 +71,11 @@ public interface Module { CompilationError[] getWarnings(); ClassLoader getParentClassLoader(); String getDeprecation(); + + /** + * May return null, if there is no debug info. + */ + default ModuleDebugInfo getModuleDebugInfo() { + return null; + } } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/debug/ModuleDebugInfo.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/debug/ModuleDebugInfo.java new file mode 100644 index 000000000..a40daac32 --- /dev/null +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/debug/ModuleDebugInfo.java @@ -0,0 +1,7 @@ +package org.simantics.scl.compiler.module.debug; + +import java.util.ArrayList; + +public class ModuleDebugInfo { + public final ArrayList symbolReferences = new ArrayList(); +} diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/debug/SymbolReference.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/debug/SymbolReference.java new file mode 100644 index 000000000..a2bea8c59 --- /dev/null +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/debug/SymbolReference.java @@ -0,0 +1,15 @@ +package org.simantics.scl.compiler.module.debug; + +import org.simantics.scl.compiler.common.names.Name; + +public class SymbolReference { + public final Name referred; + public final String referrer; + public final long referenceLocation; + + public SymbolReference(Name referred, String referrer, long referenceLocation) { + this.referred = referred; + this.referrer = referrer; + this.referenceLocation = referenceLocation; + } +} diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/options/ModuleCompilationOptions.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/options/ModuleCompilationOptions.java index 41c5f7763..90c9806ca 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/options/ModuleCompilationOptions.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/module/options/ModuleCompilationOptions.java @@ -10,6 +10,7 @@ public class ModuleCompilationOptions { public boolean computeCoverage; public boolean silent = false; + public boolean collectDebugInfo = false; public ModuleCompilationOptions(boolean computeCoverage) { this.computeCoverage = computeCoverage; diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/top/ExpressionEvaluator.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/top/ExpressionEvaluator.java index 899ee0418..36994d136 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/top/ExpressionEvaluator.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/top/ExpressionEvaluator.java @@ -269,7 +269,7 @@ public class ExpressionEvaluator { // Elaboration { - TranslationContext context = new TranslationContext(compilationContext, localEnvironment); + TranslationContext context = new TranslationContext(compilationContext, localEnvironment, "expression"); expression = expression.resolve(context); if(!errorLog.hasNoErrors()) throw new SCLExpressionCompilationException(errorLog.getErrors()); diff --git a/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/SCLOsgi.java b/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/SCLOsgi.java index 59f8e5fd8..e6bddee7e 100644 --- a/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/SCLOsgi.java +++ b/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/SCLOsgi.java @@ -2,9 +2,12 @@ package org.simantics.scl.osgi; import java.util.ArrayList; +import org.eclipse.core.internal.runtime.PlatformActivator; import org.simantics.scl.compiler.errors.DoesNotExist; import org.simantics.scl.compiler.errors.Failable; import org.simantics.scl.compiler.module.Module; +import org.simantics.scl.compiler.module.options.ModuleCompilationOptions; +import org.simantics.scl.compiler.module.options.ModuleCompilationOptionsAdvisor; import org.simantics.scl.compiler.module.repository.ModuleRepository; import org.simantics.scl.compiler.source.repository.ModuleSourceRepository; import org.simantics.scl.compiler.testing.repository.TestRepository; @@ -23,6 +26,19 @@ public class SCLOsgi { public static ModuleRepository MODULE_REPOSITORY = new ModuleRepository(SOURCE_REPOSITORY); public static TestRepository TEST_REPOSITORY = new ServiceBasedTestRepository(Activator.getContext()); + static { + MODULE_REPOSITORY.setAdvisor(new ModuleCompilationOptionsAdvisor() { + ModuleCompilationOptions options = null; + @Override + public ModuleCompilationOptions getOptions(String moduleName) { + if(options == null) { + options = new ModuleCompilationOptions(false); + options.collectDebugInfo = Activator.getContext().getProperty("osgi.dev") != null; //$NON-NLS-1$ + } + return options; + } + }); + } public static String compileAllModules() { ArrayList modulesWithErrors = new ArrayList(); -- 2.47.1