Skip to content

Commit ca34416

Browse files
adonovangopherbot
authored andcommitted
internal/refactor/inline: fallible constant analysis
This change adds an analysis of "fallible constant" (falcon) expressions, and uses it to reject substitution of parameters by constant arguments in the (rare) cases where it is not safe. When substituting a parameter by its argument expression, it is (perhaps surprisingly) not always safe to replace a variable by a constant expression, even of exactly the same type, as it may turn a dynamic error into a compile-time one, as in this example: func f(s string) { if len(s) > 0 { println(s[0]) } } f("") // inline The call is replaced by: if len("") > 0 { println(""[0]) // error: constant index out of range } In general, operations on constants (in particular -x, x+y, x[i], x[i:j], and f(x)) are subject to additional static checks that don't make sense for non-constant operands. This analysis scans the callee function body for all expressions that are not constant but would become constant if the parameter vars were redeclared as constants, and emits a constraint (a Go expression) for each one that has the property that it will not type-check (using types.CheckExpr) if the particular argument values are unsuitable. The substitution logic checks the constraints and falls back to a binding decl if they are not all satisfied. (More optimal solutions could be found, but this situation is very uncommon.) (It would be easy to detect these problems if we simply type-checked the transformed source, but, by design, we simply don't have the necessary type information for indirect dependencies when running in an environment like unitchecker.) Fixes golang/go#62664 Change-Id: I27d40adb681c469e9c711bf1ee6f7319b5725a2a Reviewed-on: https://go-review.googlesource.com/c/tools/+/531695 Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 6a38a5f commit ca34416

File tree

6 files changed

+1431
-20
lines changed

6 files changed

+1431
-20
lines changed

internal/refactor/inline/callee.go

+15-11
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type gobCallee struct {
4747
TotalReturns int // number of return statements
4848
TrivialReturns int // number of return statements with trivial result conversions
4949
Labels []string // names of all control labels
50+
Falcon falconResult // falcon constraint system
5051
}
5152

5253
// A freeRef records a reference to a free object. Gob-serializable.
@@ -331,7 +332,7 @@ func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Pa
331332
return nil, err
332333
}
333334

334-
params, results, effects := analyzeParams(logf, fset, info, decl)
335+
params, results, effects, falcon := analyzeParams(logf, fset, info, decl)
335336
return &Callee{gobCallee{
336337
Content: content,
337338
PkgPath: pkg.Path(),
@@ -349,6 +350,7 @@ func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Pa
349350
TotalReturns: totalReturns,
350351
TrivialReturns: trivialReturns,
351352
Labels: labels,
353+
Falcon: falcon,
352354
}}, nil
353355
}
354356

@@ -365,15 +367,15 @@ func parseCompact(content []byte) (*token.FileSet, *ast.FuncDecl, error) {
365367
}
366368

367369
// A paramInfo records information about a callee receiver, parameter, or result variable.
368-
// TODO(adonovan): rename to sigVarInfo or paramOrResultInfo?
369370
type paramInfo struct {
370-
Name string // parameter name (may be blank, or even "")
371-
Index int // index within signature
372-
IsResult bool // false for receiver or parameter, true for result variable
373-
Assigned bool // parameter appears on left side of an assignment statement
374-
Escapes bool // parameter has its address taken
375-
Refs []int // FuncDecl-relative byte offset of parameter ref within body
376-
Shadow map[string]bool // names shadowed at one of the above refs
371+
Name string // parameter name (may be blank, or even "")
372+
Index int // index within signature
373+
IsResult bool // false for receiver or parameter, true for result variable
374+
Assigned bool // parameter appears on left side of an assignment statement
375+
Escapes bool // parameter has its address taken
376+
Refs []int // FuncDecl-relative byte offset of parameter ref within body
377+
Shadow map[string]bool // names shadowed at one of the above refs
378+
FalconType string // name of this parameter's type (if basic) in the falcon system
377379
}
378380

379381
// analyzeParams computes information about parameters of function fn,
@@ -383,7 +385,7 @@ type paramInfo struct {
383385
// the other of the result variables of function fn.
384386
//
385387
// The input must be well-typed.
386-
func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.Info, decl *ast.FuncDecl) (params, results []*paramInfo, effects []int) {
388+
func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.Info, decl *ast.FuncDecl) (params, results []*paramInfo, effects []int, _ falconResult) {
387389
fnobj, ok := info.Defs[decl.Name]
388390
if !ok {
389391
panic(fmt.Sprintf("%s: no func object for %q",
@@ -458,7 +460,9 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
458460
effects = calleefx(info, decl.Body, paramInfos)
459461
logf("effects list = %v", effects)
460462

461-
return params, results, effects
463+
falcon := falcon(logf, fset, paramInfos, info, decl)
464+
465+
return params, results, effects, falcon
462466
}
463467

464468
// -- callee helpers --

internal/refactor/inline/doc.go

+19
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,18 @@ which they can be addressed.
219219
be prepared to eliminate the declaration too---this is where an
220220
iterative framework for simplification would really help).
221221
222+
- An expression such as s[i] may be valid if s and i are
223+
variables but invalid if either or both of them are constants.
224+
For example, a negative constant index s[-1] is always out of
225+
bounds, and even a non-negative constant index may be out of
226+
bounds depending on the particular string constant (e.g.
227+
"abc"[4]).
228+
229+
So, if a parameter participates in any expression that is
230+
subject to additional compile-time checks when its operands are
231+
constant, it may be unsafe to substitute that parameter by a
232+
constant argument value (#62664).
233+
222234
More complex callee functions are inlinable with more elaborate and
223235
invasive changes to the statements surrounding the call expression.
224236
@@ -253,6 +265,13 @@ TODO(adonovan): future work:
253265
- Eliminate parens and braces inserted conservatively when they
254266
are redundant.
255267
268+
- Eliminate explicit conversions of "untyped" literals inserted
269+
conservatively when they are redundant. For example, the
270+
conversion int32(1) is redundant when this value is used only as a
271+
slice index; but it may be crucial if it is used in x := int32(1)
272+
as it changes the type of x, which may have further implications.
273+
The conversions may also be important to the falcon analysis.
274+
256275
- Allow non-'go' build systems such as Bazel/Blaze a chance to
257276
decide whether an import is accessible using logic other than
258277
"/internal/" path segments. This could be achieved by returning

0 commit comments

Comments
 (0)