diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql new file mode 100644 index 00000000000..0a75d83f805 --- /dev/null +++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql @@ -0,0 +1,158 @@ +/** + * @name Implicit function declaration + * @description An implicitly declared function is assumed to take no + * arguments and return an integer. If this assumption does not hold, it + * may lead to unpredictable behavior. + * @kind problem + * @problem.severity warning + * @precision medium + * @id cpp/implicit-function-declaration + * @tags correctness + * maintainability + */ + +import cpp + +pragma[inline] +predicate arithTypesMatch(Type arg, Type parm) { + arg = parm + or + arg.getSize() = parm.getSize() and + ( + arg instanceof IntegralOrEnumType and + parm instanceof IntegralOrEnumType + or + arg instanceof FloatingPointType and + parm instanceof FloatingPointType + ) +} + +pragma[inline] +predicate nestedPointerArgTypeMayBeUsed(Type arg, Type parm) { + // arithmetic types + arithTypesMatch(arg, parm) + or + // conversion to/from pointers to void is allowed + arg instanceof VoidType + or + parm instanceof VoidType +} + +pragma[inline] +predicate pointerArgTypeMayBeUsed(Type arg, Type parm) { + nestedPointerArgTypeMayBeUsed(arg, parm) + or + // nested pointers + nestedPointerArgTypeMayBeUsed(arg.(PointerType).getBaseType().getUnspecifiedType(), + parm.(PointerType).getBaseType().getUnspecifiedType()) + or + nestedPointerArgTypeMayBeUsed(arg.(ArrayType).getBaseType().getUnspecifiedType(), + parm.(PointerType).getBaseType().getUnspecifiedType()) +} + +pragma[inline] +predicate argTypeMayBeUsed(Type arg, Type parm) { + // arithmetic types + arithTypesMatch(arg, parm) + or + // pointers to compatible types + pointerArgTypeMayBeUsed(arg.(PointerType).getBaseType().getUnspecifiedType(), + parm.(PointerType).getBaseType().getUnspecifiedType()) + or + pointerArgTypeMayBeUsed(arg.(ArrayType).getBaseType().getUnspecifiedType(), + parm.(PointerType).getBaseType().getUnspecifiedType()) + or + // C11 arrays + pointerArgTypeMayBeUsed(arg.(PointerType).getBaseType().getUnspecifiedType(), + parm.(ArrayType).getBaseType().getUnspecifiedType()) + or + pointerArgTypeMayBeUsed(arg.(ArrayType).getBaseType().getUnspecifiedType(), + parm.(ArrayType).getBaseType().getUnspecifiedType()) +} + +// This predicate holds whenever expression `arg` may be used to initialize +// function parameter `parm` without need for run-time conversion. +pragma[inline] +predicate argMayBeUsed(Expr arg, ParameterDeclarationEntry pde) { + argTypeMayBeUsed(arg.getFullyConverted().getUnspecifiedType(), pde.getUnspecifiedType()) +} + +// True if this file (or header) was compiled as a C file +predicate isCompiledAsC(File f) { + f.compiledAsC() + or + exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f) +} + +// True if function was ()-declared, but not (void)-declared or K&R-defined +// or implicitly declared (i.e., lacking a prototype) +predicate hasZeroParamDeclTooMany(Function f) { + exists(FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() | + not fde.isImplicit() and + not fde.hasVoidParamList() and + fde.getNumberOfParameters() = 0 and + not fde.isDefinition() + ) +} + +// True if function was ()-declared, but not (void)-declared or K&R-defined +predicate hasZeroParamDecl(Function f) { + exists(FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() | + not fde.hasVoidParamList() and fde.getNumberOfParameters() = 0 and not fde.isDefinition() + ) +} + +bindingset[name] +predicate notAlreadyReported(string name, FunctionCall fc) { + forall(FunctionDeclarationEntry fdeEx | + fdeEx.getName() = name and + not fdeEx.isImplicit() + | + // only report if not reported by cpp/futile-params + ( + fdeEx.getNumberOfParameters() >= fc.getNumberOfArguments() + or + exists(Function f | + f = fc.getTarget() and + not exists(f.getBlock()) + or + not hasZeroParamDeclTooMany(f) + ) + ) and + // only report if not reported by cpp/too-few-arguments + ( + fdeEx.getNumberOfParameters() <= fc.getNumberOfArguments() + or + exists(Function f | + f.isVarargs() or + f instanceof BuiltInFunction or + not hasZeroParamDecl(f) + ) + ) and + // only report if not reported by cpp/mistyped-function-arguments + ( + not hasZeroParamDecl(fc.getTarget()) + or + forall(ParameterDeclarationEntry pde | + pde = fdeEx.getAParameterDeclarationEntry() and + pde.getIndex() < fc.getNumberOfArguments() + | + argMayBeUsed(fc.getArgument(pde.getIndex()), pde) + ) + ) + ) +} + +predicate sameLocation(Location loc1, Location loc2) { + loc1.getFile() = loc2.getFile() and + loc1.getStartLine() = loc2.getStartLine() and + loc1.getStartColumn() = loc2.getStartColumn() +} + +from FunctionDeclarationEntry fdeIm, FunctionCall fc +where + isCompiledAsC(fdeIm.getFile()) and + fdeIm.isImplicit() and + sameLocation(fdeIm.getLocation(), fc.getLocation()) and + notAlreadyReported(fdeIm.getName(), fc) +select fc, "Function call implicitly declares $@", fc, fc.toString() diff --git a/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.expected b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.expected new file mode 100644 index 00000000000..50985b54176 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.expected @@ -0,0 +1,9 @@ +| test.c:28:3:28:12 | call to undeclared | Function call implicitly declares $@ | test.c:28:3:28:12 | call to undeclared | call to undeclared | +| test.c:31:3:31:19 | call to not_yet_declared1 | Function call implicitly declares $@ | test.c:31:3:31:19 | call to not_yet_declared1 | call to not_yet_declared1 | +| test.c:32:3:32:19 | call to not_yet_declared2 | Function call implicitly declares $@ | test.c:32:3:32:19 | call to not_yet_declared2 | call to not_yet_declared2 | +| test.c:43:3:43:27 | call to not_declared_defined_with | Function call implicitly declares $@ | test.c:43:3:43:27 | call to not_declared_defined_with | call to not_declared_defined_with | +| test.c:54:3:54:21 | call to defined_with_double | Function call implicitly declares $@ | test.c:54:3:54:21 | call to defined_with_double | call to defined_with_double | +| test.c:66:3:66:22 | call to defined_with_ptr_ptr | Function call implicitly declares $@ | test.c:66:3:66:22 | call to defined_with_ptr_ptr | call to defined_with_ptr_ptr | +| test.c:68:3:68:22 | call to defined_with_ptr_arr | Function call implicitly declares $@ | test.c:68:3:68:22 | call to defined_with_ptr_arr | call to defined_with_ptr_arr | +| test.c:132:3:132:22 | call to implicit_declaration | Function call implicitly declares $@ | test.c:132:3:132:22 | call to implicit_declaration | call to implicit_declaration | +| test.c:133:3:133:30 | call to implicit_declaration_k_and_r | Function call implicitly declares $@ | test.c:133:3:133:30 | call to implicit_declaration_k_and_r | call to implicit_declaration_k_and_r | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qlref b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qlref new file mode 100644 index 00000000000..38492f2a203 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qlref @@ -0,0 +1 @@ +Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.expected b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.expected index 14dedbdebed..2fb422155d2 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.expected @@ -18,3 +18,4 @@ | test.c:58:3:58:24 | call to defined_with_long_long | Calling $@: argument $@ of type $@ is incompatible with parameter $@. | test.c:103:11:103:32 | defined_with_long_long | defined_with_long_long | test.c:58:26:58:26 | 3 | 3 | file://:0:0:0:0 | int | int | test.c:103:44:103:45 | ll | long long ll | | test.c:60:3:60:21 | call to defined_with_double | Calling $@: argument $@ of type $@ is incompatible with parameter $@. | test.c:99:8:99:26 | defined_with_double | defined_with_double | test.c:60:23:60:25 | 2 | 2 | file://:0:0:0:0 | long long | long long | test.c:99:35:99:35 | d | double d | | test.c:61:3:61:24 | call to defined_with_long_long | Calling $@: argument $@ of type $@ is incompatible with parameter $@. | test.c:103:11:103:32 | defined_with_long_long | defined_with_long_long | test.c:61:26:61:31 | 3500000000000000.0 | 3500000000000000.0 | file://:0:0:0:0 | double | double | test.c:103:44:103:45 | ll | long long ll | +| test.c:137:3:137:27 | call to implicit_declaration_good | Calling $@: argument $@ of type $@ is incompatible with parameter $@. | test2.c:8:6:8:30 | implicit_declaration_good | implicit_declaration_good | test.c:137:35:137:38 | 2.0 | 2.0 | file://:0:0:0:0 | float | float | test2.c:8:52:8:52 | f | float f | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/test.c b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/test.c index 38059f48f9a..5b54e519b70 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/test.c +++ b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/test.c @@ -25,11 +25,11 @@ void test(int *argv[]) { declared_void(); // GOOD declared_with(1); // GOOD - undeclared(); // GOOD + undeclared(); // BAD (GOOD for everything except cpp/implicit-function-declaration) undeclared(1); // GOOD - not_yet_declared1(1); // GOOD - not_yet_declared2(1); // GOOD + not_yet_declared1(1); // BAD (GOOD for everything except for cpp/implicit-function-declaration) + not_yet_declared2(1); // BAD (GOOD for everything except for cpp/implicit-function-declaration) not_yet_declared2(ca); // BAD not_yet_declared2(); // BAD @@ -40,7 +40,7 @@ void test(int *argv[]) { declared_empty_defined_with(&x); // BAD declared_empty_defined_with(3, &x); // BAD - not_declared_defined_with(-1, 0, 2U); // GOOD + not_declared_defined_with(-1, 0, 2U); // BAD (GOOD for everything except for cpp/implicit-function-declaration) not_declared_defined_with(4LL, 0, 2.5e9f); // BAD declared_with_pointers(pv, ca); // GOOD @@ -51,7 +51,7 @@ void test(int *argv[]) { defined_with_float(2.f); // BAD defined_with_float(2.0); // BAD - defined_with_double(2.f); // GOOD + defined_with_double(2.f); // BAD (GOOD for everything except for cpp/implicit-function-declaration) defined_with_double('c'); // BAD defined_with_long_long('c'); // BAD @@ -63,9 +63,9 @@ void test(int *argv[]) { k_and_r_func(2.5, &s); // GOOD int (*parameterName)[2]; - defined_with_ptr_ptr(parameterName); // GOOD + defined_with_ptr_ptr(parameterName); // // BAD (GOOD for everything except for cpp/implicit-function-declaration) defined_with_ptr_ptr(argv); // GOOD - defined_with_ptr_arr(parameterName); // GOOD + defined_with_ptr_arr(parameterName); // // BAD (GOOD for everything except for cpp/implicit-function-declaration) defined_with_ptr_arr(argv); // GOOD declared_and_defined_empty(); // GOOD @@ -124,3 +124,17 @@ int call_k_and_r(int i) { int will_be_k_and_r(val) int val; { return val + 1; } + +extern int extern_definition(double, double*); + +void test_implicit_function_declaration(int x, double d) { + int y; + implicit_declaration(1, 2); // BAD + implicit_declaration_k_and_r(1, 2); // BAD + + implicit_declaration(1, 2); // GOOD (no longer an implicit declaration) + + implicit_declaration_good(1, x, 2.0f); // BAD + + y = extern_definition(3.0f, &d); // GOOD +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/test2.c b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/test2.c new file mode 100644 index 00000000000..80dbc1db4ce --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Underspecified Functions/test2.c @@ -0,0 +1,10 @@ +void implicit_declaration(int x) {} + +int implicit_declaration_k_and_r(x) int x; +{ + return x; +} + +void implicit_declaration_good(int x, int y, float f) +{ +} \ No newline at end of file