Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14718x 14718x 14718x 14718x 14718x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 86312x 14718x 14718x 14120x 14120x 14718x 14718x 264x 264x 14718x 14718x 374x 374x 374x 14718x 14718x 471x 471x 115x 115x 356x 356x 471x 1237x 1x 1x 1237x 154x 154x 154x 14x 14x 140x 154x 198x 198x 198x 198x 198x 198x 163x 163x 35x 35x 198x 198x 7x 7x 28x 28x 28x 28x 198x 1236x 356x 356x 14718x 14718x 14718x 14718x 151x 178x 178x 178x 163x 163x 15x 178x 11x 11x 178x 140x 140x 14718x 14718x 14718x 42x 42x 42x 35x 35x 7x 42x 42x 14718x 14718x 14718x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 14718x | import * as Expressions from "../abap/2_statements/expressions";
import * as Structures from "../abap/3_structures/structures";
import {StatementNode} from "../abap/nodes";
import {TableType} from "../abap/types/basic";
import {IdentifierMeta, TypedIdentifier} from "../abap/types/_typed_identifier";
import {Issue} from "../issue";
import {IObject} from "../objects/_iobject";
import {IRegistry} from "../_iregistry";
import {BasicRuleConfig} from "./_basic_rule_config";
import {IRule, IRuleMetadata, RuleTag} from "./_irule";
import {IOccurrence, ObjectAnalysis, Procedure, Variable, deleteStatement, expressionPath, formalParameter,
identicalTypes, isReferenceType, literalType, plainClear, plainCreateObject, plainMove, singleField,
singleTarget} from "./_procedure_analysis";
export class ClearBeforeOverwriteConf extends BasicRuleConfig {
}
export class ClearBeforeOverwrite implements IRule {
private reg: IRegistry;
private conf = new ClearBeforeOverwriteConf();
public getMetadata(): IRuleMetadata {
return {
key: "clear_before_overwrite",
title: "CLEAR before overwrite",
shortDescription: `Finds CLEAR of a local variable whose next use overwrites it completely`,
extendedInformation: `Reports CLEAR v. when all of the following hold:
* v is a local variable of a METHOD, FORM or FUNCTION, its address is never taken: no field symbol,
REF #( ), GET REFERENCE, and it is not passed to a parameter by reference
* the CLEAR is not chained and has no WITH addition
* the next reference to v is a full overwrite in the same block: v = <expression>., including v = NEW ...,
or CREATE OBJECT v. with optional TYPE and EXPORTING, where nothing else in the statement references v.
Not ?=, READ TABLE INTO, offset/length or component writes, CREATE OBJECT with EXCEPTIONS, or other
statements that can leave v unchanged
* every statement between the CLEAR and the overwrite is a direct statement of the block, not a nested structure,
and does not reference v
* v is not a table with header line
* if a TRY or CATCH SYSTEM-EXCEPTIONS encloses the CLEAR, nothing from the CLEAR to the overwrite can raise
an exception, a CATCH could see the old value: the overwrite directly follows the CLEAR and assigns a variable
or a literal of identical type, or a reference variable
Unlike clear_after_declaration, the CLEAR can be in a loop.
Objects with syntax errors, macros or unknown statements are skipped, as are procedures with dynamic code,
INCLUDE, test seams, enhancements or native SQL.
The quick fix deletes the CLEAR. No release requirement.`,
tags: [RuleTag.Quickfix],
badExample: `CLEAR lv_name.
lv_name = iv_name.`,
goodExample: `lv_name = iv_name.`,
};
}
public getConfig() {
return this.conf;
}
public setConfig(conf: ClearBeforeOverwriteConf) {
this.conf = conf;
}
public initialize(reg: IRegistry) {
this.reg = reg;
return this;
}
public run(obj: IObject): readonly Issue[] {
const analysis = ObjectAnalysis.run(this.reg, obj);
if (analysis === undefined) {
return [];
}
const issues: Issue[] = [];
for (const procedure of analysis.procedures) {
if (procedure.supported === false) {
continue;
}
for (const variable of procedure.locals()) {
const type = variable.identifier.getType();
if (variable.escapes() || (type instanceof TableType && type.isWithHeader())
|| this.passedByReference(procedure, variable)) {
continue;
}
const occurrences = variable.occurrences;
for (let i = 0; i < occurrences.length - 1; i++) {
const clear = occurrences[i];
const next = occurrences[i + 1];
if (plainClear(clear.statement)?.getStart().equals(clear.token.getStart()) !== true
|| clear.statement.getColon() !== undefined
|| occurrences.some(o => o !== next && o.statement === next.statement)
|| !this.overwrites(next)) {
continue;
}
const between = procedure.straightBetween(clear.statement, next.statement);
if (between === undefined
|| (procedure.enclosedBy(clear.statement, [Structures.Try, Structures.CatchSystemExceptions])
&& (between.length > 0 || !this.cannotRaise(procedure, variable, next.statement)))) {
continue;
}
const message = "CLEAR of " + variable.identifier.getName() + ", which is overwritten before it is read";
issues.push(Issue.atStatement(procedure.file, clear.statement, message, this.getMetadata().key,
this.conf.severity, deleteStatement(procedure.file, clear.statement)));
}
}
}
return issues;
}
/** passed to a method, NEW, CREATE OBJECT or RAISE EXCEPTION, other than to a pass-by-value parameter;
* the called code could keep a reference to the parameter */
private passedByReference(procedure: Procedure, variable: Variable): boolean {
for (const read of variable.reads()) {
const path = expressionPath(read.statement, read.token);
if (!path.some(e => e.get() instanceof Expressions.MethodCallParam || e.get() instanceof Expressions.ParameterListS
|| e.get() instanceof Expressions.MethodParameters)) {
continue;
}
const formal = formalParameter(procedure, read.statement, read.token);
if (formal?.parameter?.getMeta().includes(IdentifierMeta.PassByValue) !== true) {
return true;
}
}
return false;
}
/** v = <expression>. or CREATE OBJECT v [TYPE c] [EXPORTING ...]. */
private overwrites(occurrence: IOccurrence): boolean {
const statement = occurrence.statement;
const move = plainMove(statement);
if (move !== undefined) {
return move.operator === "=" && singleTarget(move.target)?.getStart().equals(occurrence.token.getStart()) === true;
}
const create = plainCreateObject(statement);
return create !== undefined && singleTarget(create.target)?.getStart().equals(occurrence.token.getStart()) === true;
}
/** v = w. or v = literal. without conversion */
private cannotRaise(procedure: Procedure, variable: Variable, statement: StatementNode): boolean {
const move = plainMove(statement);
if (move === undefined) {
return false;
}
const type = variable.identifier.getType();
const field = singleField(move.source);
const constant = move.source.getChildren().length === 1 ? move.source.findDirectExpression(Expressions.Constant) : undefined;
const identifier = field === undefined ? undefined : procedure.identifierAt(field);
const source = identifier instanceof TypedIdentifier ? identifier.getType()
: constant === undefined ? undefined : literalType(constant);
return source !== undefined
&& (identicalTypes(source, type) || (isReferenceType(source) && isReferenceType(type) && field !== undefined));
}
}
|