mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-06-07 09:46:02 +02:00
Bug 376790 - Codan should not issue warnings for unused variables and
functions that have attribute unused
This commit is contained in:
parent
249589c28c
commit
ae7dc29df9
9 changed files with 91 additions and 49 deletions
|
@ -48,6 +48,7 @@ import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTConstructorInitializer;
|
||||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
|
||||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
|
||||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
|
||||||
|
import org.eclipse.cdt.core.parser.util.AttributeUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checker looking for unused function or variable declarations.
|
* Checker looking for unused function or variable declarations.
|
||||||
|
@ -58,6 +59,7 @@ public class UnusedSymbolInFileScopeChecker extends AbstractIndexAstChecker {
|
||||||
public static final String ER_UNUSED_STATIC_FUNCTION_ID = "org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem"; //$NON-NLS-1$
|
public static final String ER_UNUSED_STATIC_FUNCTION_ID = "org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem"; //$NON-NLS-1$
|
||||||
public static final String PARAM_MACRO_ID = "macro"; //$NON-NLS-1$
|
public static final String PARAM_MACRO_ID = "macro"; //$NON-NLS-1$
|
||||||
public static final String PARAM_EXCEPT_ARG_LIST = "exceptions"; //$NON-NLS-1$
|
public static final String PARAM_EXCEPT_ARG_LIST = "exceptions"; //$NON-NLS-1$
|
||||||
|
private static final String[] ATTRIBUTE_UNUSED = new String[] { "__unused__", "unused" }; //$NON-NLS-1$//$NON-NLS-2$
|
||||||
|
|
||||||
private Map<IBinding, IASTDeclarator> externFunctionDeclarations = new HashMap<IBinding, IASTDeclarator>();
|
private Map<IBinding, IASTDeclarator> externFunctionDeclarations = new HashMap<IBinding, IASTDeclarator>();
|
||||||
private Map<IBinding, IASTDeclarator> staticFunctionDeclarations = new HashMap<IBinding, IASTDeclarator>();
|
private Map<IBinding, IASTDeclarator> staticFunctionDeclarations = new HashMap<IBinding, IASTDeclarator>();
|
||||||
|
@ -130,6 +132,8 @@ public class UnusedSymbolInFileScopeChecker extends AbstractIndexAstChecker {
|
||||||
|
|
||||||
IASTDeclarator[] declarators = simpleDeclaration.getDeclarators();
|
IASTDeclarator[] declarators = simpleDeclaration.getDeclarators();
|
||||||
for (IASTDeclarator decl : declarators) {
|
for (IASTDeclarator decl : declarators) {
|
||||||
|
if (AttributeUtil.hasAttribute(decl, ATTRIBUTE_UNUSED))
|
||||||
|
continue;
|
||||||
IASTName astName = decl.getName();
|
IASTName astName = decl.getName();
|
||||||
if (astName != null) {
|
if (astName != null) {
|
||||||
IBinding binding = astName.resolveBinding();
|
IBinding binding = astName.resolveBinding();
|
||||||
|
@ -191,14 +195,15 @@ public class UnusedSymbolInFileScopeChecker extends AbstractIndexAstChecker {
|
||||||
// definitions
|
// definitions
|
||||||
IASTFunctionDefinition definition = (IASTFunctionDefinition) element;
|
IASTFunctionDefinition definition = (IASTFunctionDefinition) element;
|
||||||
|
|
||||||
IASTName astName = definition.getDeclarator().getName();
|
IASTFunctionDeclarator declarator = definition.getDeclarator();
|
||||||
|
IASTName astName = declarator.getName();
|
||||||
if (astName != null) {
|
if (astName != null) {
|
||||||
IBinding binding = astName.resolveBinding();
|
IBinding binding = astName.resolveBinding();
|
||||||
|
|
||||||
if (definition.getDeclSpecifier().getStorageClass() == IASTDeclSpecifier.sc_static) {
|
if (definition.getDeclSpecifier().getStorageClass() == IASTDeclSpecifier.sc_static &&
|
||||||
if (!(astName instanceof ICPPASTQualifiedName)) {
|
!(astName instanceof ICPPASTQualifiedName) &&
|
||||||
staticFunctionDefinitions.put(binding, definition.getDeclarator());
|
!AttributeUtil.hasAttribute(declarator, ATTRIBUTE_UNUSED)) {
|
||||||
}
|
staticFunctionDefinitions.put(binding, declarator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// externFunctionDeclarators filter out
|
// externFunctionDeclarators filter out
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2011 Andrew Gvozdev and others.
|
* Copyright (c) 2011, 2012 Andrew Gvozdev and others.
|
||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the Eclipse Public License v1.0
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
|
@ -307,4 +307,14 @@ public class UnusedSymbolInFileScopeCheckerTest extends CheckerTestCase {
|
||||||
checkNoErrors();
|
checkNoErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static int v1 __attribute__((unused));
|
||||||
|
// int f1() __attribute__((__unused__));
|
||||||
|
// extern int f2() __attribute__((unused));
|
||||||
|
// static void f3() __attribute__((unused));
|
||||||
|
// static void f4() __attribute__((unused));
|
||||||
|
// static void f4() __attribute__((unused)) {}
|
||||||
|
public void testAttributeUnused() throws IOException {
|
||||||
|
loadCodeAndRun(getAboveComment());
|
||||||
|
checkNoErrors();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2012 Google, Inc 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:
|
||||||
|
* Sergey Prigogin (Google) - initial API and implementation
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.eclipse.cdt.core.parser.util;
|
||||||
|
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTAttribute;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTAttributeOwner;
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of static methods for dealing with attributes.
|
||||||
|
* @see org.eclipse.cdt.core.dom.ast.IASTAttribute
|
||||||
|
* @see org.eclipse.cdt.core.dom.ast.IASTAttributeOwner
|
||||||
|
* @since 5.4
|
||||||
|
*/
|
||||||
|
public class AttributeUtil {
|
||||||
|
private static final String[] ATTRIBUTE_NORETURN = new String[] { "__noreturn__", "noreturn" }; //$NON-NLS-1$//$NON-NLS-2$
|
||||||
|
|
||||||
|
// Not instantiatable.
|
||||||
|
private AttributeUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if a declarator has an attribute with one of the given names.
|
||||||
|
* The {@code names} array is assumed to be small.
|
||||||
|
*/
|
||||||
|
public static boolean hasAttribute(IASTAttributeOwner node, String[] names) {
|
||||||
|
IASTAttribute[] attributes = node.getAttributes();
|
||||||
|
for (IASTAttribute attribute : attributes) {
|
||||||
|
char[] name = attribute.getName();
|
||||||
|
for (int i = 0; i < names.length; i++) {
|
||||||
|
if (CharArrayUtils.equals(name, names[i]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the node has a "noreturn" or "__noreturn__" attribute.
|
||||||
|
*/
|
||||||
|
public static boolean hasNoreturnAttribute(IASTAttributeOwner node) {
|
||||||
|
return hasAttribute(node, ATTRIBUTE_NORETURN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns character representation of the attribute argument, or {@code null} if the attribute
|
||||||
|
* has zero or more than one argument.
|
||||||
|
*/
|
||||||
|
public static char[] getSimpleArgument(IASTAttribute attribute) {
|
||||||
|
IASTToken argumentClause = attribute.getArgumentClause();
|
||||||
|
return argumentClause == null ? null : argumentClause.getTokenCharImage();
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,16 +12,12 @@ package org.eclipse.cdt.internal.core.dom.parser;
|
||||||
|
|
||||||
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
|
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
|
||||||
import org.eclipse.cdt.core.dom.ast.IASTAttribute;
|
import org.eclipse.cdt.core.dom.ast.IASTAttribute;
|
||||||
import org.eclipse.cdt.core.dom.ast.IASTAttributeOwner;
|
|
||||||
import org.eclipse.cdt.core.dom.ast.IASTToken;
|
import org.eclipse.cdt.core.dom.ast.IASTToken;
|
||||||
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for C and C++ attributes.
|
* Base class for C and C++ attributes.
|
||||||
*/
|
*/
|
||||||
public abstract class ASTAttribute extends ASTNode implements IASTAttribute {
|
public abstract class ASTAttribute extends ASTNode implements IASTAttribute {
|
||||||
private static final String[] NORETURN_ATTRIBUTES = new String[] { "__noreturn__", "noreturn" }; //$NON-NLS-1$//$NON-NLS-2$
|
|
||||||
|
|
||||||
private final char[] name;
|
private final char[] name;
|
||||||
private final IASTToken argumentClause;
|
private final IASTToken argumentClause;
|
||||||
|
|
||||||
|
@ -66,38 +62,4 @@ public abstract class ASTAttribute extends ASTNode implements IASTAttribute {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if a declarator has an attribute with one of the given names.
|
|
||||||
* The {@code names} array is assumed to be small.
|
|
||||||
*/
|
|
||||||
public static boolean hasAttribute(IASTAttributeOwner node, String[] names) {
|
|
||||||
IASTAttribute[] attributes = node.getAttributes();
|
|
||||||
for (IASTAttribute attribute : attributes) {
|
|
||||||
char[] name = attribute.getName();
|
|
||||||
for (int i = 0; i < names.length; i++) {
|
|
||||||
if (CharArrayUtils.equals(name, names[i]))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if the node has a "noreturn" or "__noreturn__" attribute.
|
|
||||||
*/
|
|
||||||
public static boolean hasNoreturnAttribute(IASTAttributeOwner node) {
|
|
||||||
return hasAttribute(node, NORETURN_ATTRIBUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns character representation of the attribute argument, or {@code null} if the attribute
|
|
||||||
* has zero or more than one argument.
|
|
||||||
*/
|
|
||||||
public static char[] getSimpleArgument(IASTAttribute attribute) {
|
|
||||||
IASTToken argumentClause = attribute.getArgumentClause();
|
|
||||||
if (argumentClause == null)
|
|
||||||
return null;
|
|
||||||
return argumentClause.getTokenCharImage();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.eclipse.cdt.core.dom.ast.IScope;
|
||||||
import org.eclipse.cdt.core.dom.ast.IType;
|
import org.eclipse.cdt.core.dom.ast.IType;
|
||||||
import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator;
|
import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator;
|
||||||
import org.eclipse.cdt.core.parser.util.ArrayUtil;
|
import org.eclipse.cdt.core.parser.util.ArrayUtil;
|
||||||
|
import org.eclipse.cdt.core.parser.util.AttributeUtil;
|
||||||
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
||||||
import org.eclipse.cdt.internal.core.dom.Linkage;
|
import org.eclipse.cdt.internal.core.dom.Linkage;
|
||||||
import org.eclipse.cdt.internal.core.dom.parser.ASTAttribute;
|
import org.eclipse.cdt.internal.core.dom.parser.ASTAttribute;
|
||||||
|
@ -488,7 +489,7 @@ public class CFunction extends PlatformObject implements IFunction, ICInternalFu
|
||||||
@Override
|
@Override
|
||||||
public boolean isNoReturn() {
|
public boolean isNoReturn() {
|
||||||
IASTFunctionDeclarator dtor = getPreferredDtor();
|
IASTFunctionDeclarator dtor = getPreferredDtor();
|
||||||
return dtor != null && ASTAttribute.hasNoreturnAttribute(dtor);
|
return dtor != null && AttributeUtil.hasNoreturnAttribute(dtor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IASTFunctionDeclarator getPreferredDtor() {
|
protected IASTFunctionDeclarator getPreferredDtor() {
|
||||||
|
|
|
@ -90,6 +90,7 @@ import org.eclipse.cdt.core.dom.ast.gnu.c.ICASTKnRFunctionDeclarator;
|
||||||
import org.eclipse.cdt.core.index.IIndexBinding;
|
import org.eclipse.cdt.core.index.IIndexBinding;
|
||||||
import org.eclipse.cdt.core.index.IIndexFileSet;
|
import org.eclipse.cdt.core.index.IIndexFileSet;
|
||||||
import org.eclipse.cdt.core.parser.util.ArrayUtil;
|
import org.eclipse.cdt.core.parser.util.ArrayUtil;
|
||||||
|
import org.eclipse.cdt.core.parser.util.AttributeUtil;
|
||||||
import org.eclipse.cdt.core.parser.util.CharArraySet;
|
import org.eclipse.cdt.core.parser.util.CharArraySet;
|
||||||
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
||||||
import org.eclipse.cdt.core.parser.util.IContentAssistMatcher;
|
import org.eclipse.cdt.core.parser.util.IContentAssistMatcher;
|
||||||
|
@ -1272,7 +1273,7 @@ public class CVisitor extends ASTQueries {
|
||||||
for (IASTAttribute attribute : attributes) {
|
for (IASTAttribute attribute : attributes) {
|
||||||
char[] name = attribute.getName();
|
char[] name = attribute.getName();
|
||||||
if (CharArrayUtils.equals(name, "__mode__") || CharArrayUtils.equals(name, "mode")) { //$NON-NLS-1$ //$NON-NLS-2$
|
if (CharArrayUtils.equals(name, "__mode__") || CharArrayUtils.equals(name, "mode")) { //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
char[] mode = ASTAttribute.getSimpleArgument(attribute);
|
char[] mode = AttributeUtil.getSimpleArgument(attribute);
|
||||||
if (CharArrayUtils.equals(mode, "__QI__") || CharArrayUtils.equals(mode, "QI")) { //$NON-NLS-1$ //$NON-NLS-2$
|
if (CharArrayUtils.equals(mode, "__QI__") || CharArrayUtils.equals(mode, "QI")) { //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
type = new CBasicType(IBasicType.Kind.eChar,
|
type = new CBasicType(IBasicType.Kind.eChar,
|
||||||
basicType.isUnsigned() ? IBasicType.IS_UNSIGNED : IBasicType.IS_SIGNED);
|
basicType.isUnsigned() ? IBasicType.IS_UNSIGNED : IBasicType.IS_SIGNED);
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
|
||||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
|
||||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
|
||||||
import org.eclipse.cdt.core.parser.util.ArrayUtil;
|
import org.eclipse.cdt.core.parser.util.ArrayUtil;
|
||||||
|
import org.eclipse.cdt.core.parser.util.AttributeUtil;
|
||||||
import org.eclipse.cdt.internal.core.dom.Linkage;
|
import org.eclipse.cdt.internal.core.dom.Linkage;
|
||||||
import org.eclipse.cdt.internal.core.dom.parser.ASTAttribute;
|
import org.eclipse.cdt.internal.core.dom.parser.ASTAttribute;
|
||||||
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
|
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
|
||||||
|
@ -599,6 +600,6 @@ public class CPPFunction extends PlatformObject implements ICPPFunction, ICPPInt
|
||||||
@Override
|
@Override
|
||||||
public boolean isNoReturn() {
|
public boolean isNoReturn() {
|
||||||
ICPPASTFunctionDeclarator dtor = getPreferredDtor();
|
ICPPASTFunctionDeclarator dtor = getPreferredDtor();
|
||||||
return dtor != null && ASTAttribute.hasNoreturnAttribute(dtor);
|
return dtor != null && AttributeUtil.hasNoreturnAttribute(dtor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
|
||||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionTemplate;
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionTemplate;
|
||||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
|
||||||
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
|
import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
|
||||||
|
import org.eclipse.cdt.core.parser.util.AttributeUtil;
|
||||||
import org.eclipse.cdt.internal.core.dom.parser.ASTAttribute;
|
import org.eclipse.cdt.internal.core.dom.parser.ASTAttribute;
|
||||||
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
|
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
|
||||||
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
|
import org.eclipse.cdt.internal.core.dom.parser.ASTQueries;
|
||||||
|
@ -344,7 +345,7 @@ public class CPPFunctionTemplate extends CPPTemplateDefinition
|
||||||
public boolean isNoReturn() {
|
public boolean isNoReturn() {
|
||||||
ICPPASTFunctionDeclarator fdecl= getFirstFunctionDtor();
|
ICPPASTFunctionDeclarator fdecl= getFirstFunctionDtor();
|
||||||
if (fdecl != null) {
|
if (fdecl != null) {
|
||||||
return ASTAttribute.hasNoreturnAttribute(fdecl);
|
return AttributeUtil.hasNoreturnAttribute(fdecl);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,7 @@ import org.eclipse.cdt.core.dom.ast.cpp.ICPPUsingDeclaration;
|
||||||
import org.eclipse.cdt.core.index.IIndex;
|
import org.eclipse.cdt.core.index.IIndex;
|
||||||
import org.eclipse.cdt.core.index.IIndexBinding;
|
import org.eclipse.cdt.core.index.IIndexBinding;
|
||||||
import org.eclipse.cdt.core.parser.util.ArrayUtil;
|
import org.eclipse.cdt.core.parser.util.ArrayUtil;
|
||||||
|
import org.eclipse.cdt.core.parser.util.AttributeUtil;
|
||||||
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
||||||
import org.eclipse.cdt.internal.core.dom.parser.ASTAttribute;
|
import org.eclipse.cdt.internal.core.dom.parser.ASTAttribute;
|
||||||
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
|
import org.eclipse.cdt.internal.core.dom.parser.ASTInternal;
|
||||||
|
@ -1854,7 +1855,7 @@ public class CPPVisitor extends ASTQueries {
|
||||||
for (IASTAttribute attribute : attributes) {
|
for (IASTAttribute attribute : attributes) {
|
||||||
char[] name = attribute.getName();
|
char[] name = attribute.getName();
|
||||||
if (CharArrayUtils.equals(name, "__mode__") || CharArrayUtils.equals(name, "mode")) { //$NON-NLS-1$ //$NON-NLS-2$
|
if (CharArrayUtils.equals(name, "__mode__") || CharArrayUtils.equals(name, "mode")) { //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
char[] mode = ASTAttribute.getSimpleArgument(attribute);
|
char[] mode = AttributeUtil.getSimpleArgument(attribute);
|
||||||
if (CharArrayUtils.equals(mode, "__QI__") || CharArrayUtils.equals(mode, "QI")) { //$NON-NLS-1$ //$NON-NLS-2$
|
if (CharArrayUtils.equals(mode, "__QI__") || CharArrayUtils.equals(mode, "QI")) { //$NON-NLS-1$ //$NON-NLS-2$
|
||||||
type = new CPPBasicType(IBasicType.Kind.eChar,
|
type = new CPPBasicType(IBasicType.Kind.eChar,
|
||||||
basicType.isUnsigned() ? IBasicType.IS_UNSIGNED : IBasicType.IS_SIGNED);
|
basicType.isUnsigned() ? IBasicType.IS_UNSIGNED : IBasicType.IS_SIGNED);
|
||||||
|
|
Loading…
Add table
Reference in a new issue